diff --git a/src/components/modules/shared/CodeBlock.tsx b/src/components/modules/shared/CodeBlock.tsx index 327c39e9e6..145fa68777 100644 --- a/src/components/modules/shared/CodeBlock.tsx +++ b/src/components/modules/shared/CodeBlock.tsx @@ -137,7 +137,7 @@ function formatCode(code: string): string { // 如果是空行,则直接返回,避免移除空行的非空格字符(例如\t) return line } else { - return line.substring(minIndent) + return line.slice(Math.max(0, minIndent)) } }) diff --git a/src/components/ui/code-highlighter/CodeHighlighter.tsx b/src/components/ui/code-highlighter/CodeHighlighter.tsx index 163968a433..74f166e9de 100644 --- a/src/components/ui/code-highlighter/CodeHighlighter.tsx +++ b/src/components/ui/code-highlighter/CodeHighlighter.tsx @@ -164,9 +164,7 @@ export const ShikiFallback: FC = (props) => { return bundledLanguagesKeysSet.has(lang) }, [lang]), ) - - if (!shikiSupported) { - return - } - return + return ( + + ) } diff --git a/src/components/ui/code-highlighter/shiki/Shiki.module.css b/src/components/ui/code-highlighter/shiki/Shiki.module.css index 7a8be1a4c3..41838471b9 100644 --- a/src/components/ui/code-highlighter/shiki/Shiki.module.css +++ b/src/components/ui/code-highlighter/shiki/Shiki.module.css @@ -85,4 +85,8 @@ .scroll-container pre::-webkit-scrollbar { background-color: transparent !important; } + + pre { + @apply rounded-none; + } } diff --git a/src/components/ui/code-highlighter/shiki/Shiki.tsx b/src/components/ui/code-highlighter/shiki/Shiki.tsx index eeca059499..32943e56e0 100644 --- a/src/components/ui/code-highlighter/shiki/Shiki.tsx +++ b/src/components/ui/code-highlighter/shiki/Shiki.tsx @@ -1,47 +1,40 @@ +import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect' import type { FC } from 'react' -import { use, useEffect, useMemo, useState } from 'react' +import { use, useMemo, useState } from 'react' +import { createHighlighterCoreSync, createJavaScriptRegexEngine } from 'shiki' +import githubDark from 'shiki/themes/github-dark.mjs' +import githubLight from 'shiki/themes/github-light.mjs' import { isServerSide } from '~/lib/env' +import { codeHighlighter as shiki } from './core' import { ShikiHighLighterWrapper } from './ShikiWrapper' import { parseShouldCollapsedFromAttrs } from './utils' export interface ShikiProps { lang: string | undefined content: string - attrs?: string } -const codeHighlighterPromise = (async () => { +const codeHighlighter = (() => { if (isServerSide) return - const [{ createHighlighterCore }, getWasm, { codeHighlighter }] = - await Promise.all([ - import('shiki/core'), - import('shiki/wasm').then((m) => m.default), - import('./core'), - ]) - const core = await createHighlighterCore({ - themes: [ - import('shiki/themes/github-light.mjs'), - import('shiki/themes/github-dark.mjs'), - ], + const js = createJavaScriptRegexEngine() + const core = createHighlighterCoreSync({ + themes: [githubDark, githubLight], langs: [], - loadWasm: getWasm, + engine: js, }) return { codeHighlighter: core, - fn: (o: { lang: string; attrs: string; code: string }) => { - return codeHighlighter(core, o) - }, + fn: (o: { lang: string; attrs: string; code: string }) => shiki(core, o), } })() export const ShikiHighLighter: FC = (props) => { const { lang: language, content: value, attrs } = props - const codeHighlighter = use(codeHighlighterPromise) use( useMemo(async () => { @@ -59,45 +52,42 @@ export const ShikiHighLighter: FC = (props) => { const importFn = (bundledLanguages as any)[language] if (!importFn) return return loadShikiLanguage(language || '', importFn) - }, [codeHighlighter?.codeHighlighter, language]), + }, [language]), + ) + const highlightedHtml = useMemo( + () => + codeHighlighter?.fn?.({ + attrs: attrs || '', + // code: `${value.split('\n')[0].repeat(10)} // [!code highlight]\n${value}`, + code: value, + lang: language ? language.toLowerCase() : '', + }), + [attrs, language, value], ) - const highlightedHtml = useMemo(() => { - return codeHighlighter?.fn?.({ - attrs: attrs || '', - // code: `${value.split('\n')[0].repeat(10)} // [!code highlight]\n${value}`, - code: value, - lang: language ? language.toLowerCase() : '', - }) - }, [attrs, codeHighlighter, language, value]) - const [renderedHtml, setRenderedHtml] = useState(highlightedHtml) + // const [renderedHtml, setRenderedHtml] = useState(highlightedHtml) const [codeBlockRef, setCodeBlockRef] = useState(null) - useEffect(() => { - setRenderedHtml(highlightedHtml) - requestAnimationFrame(() => { - if (!highlightedHtml) return - if (!codeBlockRef) return + useIsomorphicLayoutEffect(() => { + if (!highlightedHtml) return + if (!codeBlockRef) return - const $lines = codeBlockRef.querySelectorAll('.line') - const maxLineWidth = Math.max( - ...Array.from($lines).map((el) => { - return (el as HTMLElement).scrollWidth - }), - ) - $lines.forEach((el) => { - ;(el as HTMLElement).style.width = `${maxLineWidth}px` - }) - - const pre = codeBlockRef.querySelector('pre') - if (pre) setRenderedHtml(pre.outerHTML) + const $lines = codeBlockRef.querySelectorAll('.line') + const maxLineWidth = Math.max( + ...Array.from($lines).map((el) => (el as HTMLElement).scrollWidth), + ) + $lines.forEach((el) => { + ;(el as HTMLElement).style.width = `${maxLineWidth}px` }) + + // const pre = codeBlockRef.querySelector('pre') + // if (pre) setRenderedHtml(pre.outerHTML) }, [codeBlockRef, highlightedHtml]) return ( ) diff --git a/src/components/ui/code-highlighter/shiki/ShikiWrapper.tsx b/src/components/ui/code-highlighter/shiki/ShikiWrapper.tsx index 266ab0ce46..e2b98585d4 100644 --- a/src/components/ui/code-highlighter/shiki/ShikiWrapper.tsx +++ b/src/components/ui/code-highlighter/shiki/ShikiWrapper.tsx @@ -111,9 +111,7 @@ export const ShikiHighLighterWrapper = forwardRef< } }, [value, codeBlockRef]) - const filename = useMemo(() => { - return parseFilenameFromAttrs(attrs || '') - }, [attrs]) + const filename = useMemo(() => parseFilenameFromAttrs(attrs || ''), [attrs]) const [, maskClassName] = useMaskScrollArea({ element: codeBlockRef!, size: 'lg', diff --git a/src/components/ui/code-highlighter/shiki/utils.tsx b/src/components/ui/code-highlighter/shiki/utils.tsx index 930f3c31d1..aaefc0d43c 100644 --- a/src/components/ui/code-highlighter/shiki/utils.tsx +++ b/src/components/ui/code-highlighter/shiki/utils.tsx @@ -10,15 +10,13 @@ export const parseFilenameFromAttrs = (attrs: string) => { return null } -export const parseShouldCollapsedFromAttrs = (attrs: string) => { +export const parseShouldCollapsedFromAttrs = (attrs: string) => // collapsed - return attrs.includes('collapsed') || !attrs.includes('expand') -} + attrs.includes('collapsed') || !attrs.includes('expand') // const shikiSupportLangSet = new Set(Object.keys(bundledLanguages)) -export const isSupportedShikiLang = (lang: string) => { +export const isSupportedShikiLang = (lang: string) => // require esm error, fuck nextjs 14.12.x // @see https://github.com/vercel/next.js/issues/64434 // return shikiSupportLangSet.has(lang) - return true -} + true