@hejiayue/x-markdown-test 0.0.1-beta.140 → 0.0.1-beta.142
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/x-markdown.cjs7.js +1 -1
- package/dist/x-markdown.cjs7.js.map +1 -1
- package/dist/x-markdown.cjs9.js +1 -1
- package/dist/x-markdown.cjs9.js.map +1 -1
- package/dist/x-markdown.es7.js +4 -0
- package/dist/x-markdown.es7.js.map +1 -1
- package/dist/x-markdown.es9.js +29 -21
- package/dist/x-markdown.es9.js.map +1 -1
- package/package.json +100 -100
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 element-plus-x
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/x-markdown.cjs7.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue");let t=null,l=null,o=!1,n=!1;const a=e=>{if(!e.length)return[[]];const t=[[]];let l=t[0];const o=()=>{l=[],t.push(l)};return e.forEach(e=>{const t=e.content??"";if("\n"===t)return void o();if(!t.includes("\n"))return void l.push(e);const n=t.split("\n");n.forEach((t,a)=>{t&&l.push({...e,content:t}),a<n.length-1&&o()})}),0===t.length?[[]]:t};exports.useHighlight=function(
|
|
1
|
+
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("vue");let t=null,l=null,o=!1,n=!1;const a=()=>{n||(n=!0,console.log("%c[x-markdown]%c shiki-stream 未安装,已降级为非流式高亮","font-weight: bold; color: #0066cc;","color: #666;"),console.log("%c如需流式代码高亮功能(推荐用于 AI 场景),请安装:","color: #666; font-weight: bold;"),console.log("%c pnpm add shiki-stream","color: #00aa00; font-family: monospace;"),console.log("%c安装后请重启开发服务器","color: #999; font-size: 12px;"))},c=async()=>(l||(l=(async()=>{try{const e=await import("shiki-stream");return e&&null===e.default?null:e}catch{return null}})()),l),r=e=>{if(!e.length)return[[]];const t=[[]];let l=t[0];const o=()=>{l=[],t.push(l)};return e.forEach(e=>{const t=e.content??"";if("\n"===t)return void o();if(!t.includes("\n"))return void l.push(e);const n=t.split("\n");n.forEach((t,a)=>{t&&l.push({...e,content:t}),a<n.length-1&&o()})}),0===t.length?[[]]:t};exports.useHighlight=function(l,n){const i=e.ref(),s=e.ref(!1),u=e.ref(null);let p=null,h="",g=null,m="",v="";const y=e.computed(()=>(e.isRef(n.theme)?n.theme.value:n.theme)||"slack-dark"),d=e.computed(()=>e.toValue(n.language)||"text"),f=e.computed(()=>i.value?.lines||[[]]),w=e.computed(()=>i.value?.preStyle),S=async(e,t=!1)=>{if(p){t&&(p.clear(),h="");const o=!t&&e.startsWith(h);let a=e;if(o?a=e.slice(h.length):t||p.clear(),h=e,!a){const e=[...p.tokensStable,...p.tokensUnstable];return void(i.value={colorReplacements:n.colorReplacements,lines:e.length?r(e):[[]],preStyle:i.value?.preStyle})}try{await p.enqueue(a);const e=[...p.tokensStable,...p.tokensUnstable];i.value={colorReplacements:n.colorReplacements,lines:r(e),preStyle:i.value?.preStyle}}catch(l){console.error("[x-markdown] Streaming highlighting failed:",l),u.value=l}}else if(g)try{const t=m||"plaintext",l=y.value,o=g.codeToTokens(e,{lang:t,theme:l});i.value={colorReplacements:n.colorReplacements,lines:r(o),preStyle:i.value?.preStyle}}catch(l){console.error("[x-markdown] Direct highlighting failed:",l),i.value={colorReplacements:n.colorReplacements,lines:[[{content:e}]],preStyle:i.value?.preStyle}}},k=async()=>{s.value=!0,u.value=null;let e=d.value;const r=y.value;try{const s=await(async()=>(t||(t=(async()=>{try{const e=await import("shiki");return e&&null===e.default?null:e}catch{return null}})()),t))();if(!s)return i.value={colorReplacements:n.colorReplacements,lines:[[{content:l.value}]],preStyle:void 0},o||(o=!0,console.log("%c[x-markdown]%c Shiki 代码高亮库未安装,已降级为纯文本模式","font-weight: bold; color: #0066cc;","color: #666;"),console.log("%c如需语法高亮功能,请安装:","color: #666; font-weight: bold;"),console.log("%c pnpm add shiki","color: #00aa00; font-family: monospace;"),console.log("%c安装后请重启开发服务器","color: #999; font-size: 12px;")),void(await c()||a());g=await s.createHighlighter({themes:[r],langs:[]}),v=e;try{await g.loadLanguage(e),m=e}catch{e="plaintext",m="plaintext"}const u=await c();u?p=new u.ShikiStreamTokenizer({highlighter:g,lang:e,theme:r}):a(),h="";const y=g.getTheme(r),d=((e,t)=>{if(e||t)return{backgroundColor:e,color:t}})(y?.bg,y?.fg);l.value?(await S(l.value,!0),i.value&&(i.value.preStyle=d)):i.value={colorReplacements:n.colorReplacements,lines:[[]],preStyle:d}}catch(f){i.value={colorReplacements:n.colorReplacements,lines:[[{content:l.value}]],preStyle:void 0}}finally{s.value=!1}};return e.watch(()=>[d.value,y.value],async([e])=>{const t=e;if(g&&"plaintext"===m&&t!==v&&"plaintext"!==t)try{return await g.loadLanguage(t),void k()}catch{return void(v=t)}k()},{immediate:!0}),e.watch(l,async e=>{const t=d.value;if(g&&"plaintext"===m&&t!==v&&"plaintext"!==t)try{return await g.loadLanguage(t),void(await k())}catch{v=t}p||g?S(e):i.value={colorReplacements:n.colorReplacements,lines:[[{content:e}]],preStyle:i.value?.preStyle}}),e.onUnmounted(()=>{p?.clear(),p=null,h=""}),{streaming:i,lines:f,preStyle:w,isLoading:s,error:u}};
|
|
2
2
|
//# sourceMappingURL=x-markdown.cjs7.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.cjs7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import { ref, watch, onUnmounted, computed, isRef, toValue, type Ref, type MaybeRef, type CSSProperties } from 'vue'\r\n\r\ninterface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRef<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Shiki 代码高亮库未安装,已降级为纯文本模式',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需语法高亮功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c shiki-stream 未安装,已降级为非流式高亮',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需流式代码高亮功能(推荐用于 AI 场景),请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki-stream',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n tokens.forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment, index) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Streaming highlighting failed:', err)\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Direct highlighting failed:', err)\r\n // 降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n return\r\n }\r\n\r\n // shiki 3.x API\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n\r\n lastRequestedLang = currentLang\r\n\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":["shikiModulePromise","shikiStreamModulePromise","hasShownShikiHint","hasShownShikiStreamHint","tokensToLineTokens","tokens","length","lines","currentLine","startNewLine","push","forEach","token","content","includes","segments","split","segment","index","text","options","streaming","ref","isLoading","error","tokenizer","previousText","highlighter","currentUsedLang","lastRequestedLang","effectiveTheme","computed","isRef","theme","value","effectiveLanguage","toValue","language","preStyle","updateTokens","async","nextText","forceReset","clear","canAppend","startsWith","chunk","slice","mergedTokens","tokensStable","tokensUnstable","colorReplacements","enqueue","err","console","currentLang","currentTheme","codeToTokens","lang","initHighlighter","mod","import","default","loadShiki","log","createHighlighter","themes","langs","loadLanguage","shikiStreamMod","loadShikiStream","ShikiStreamTokenizer","themeInfo","getTheme","preStyleValue","bg","fg","backgroundColor","color","createPreStyle","watch","newLang","requestedLang","immediate","newText","onUnmounted"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.cjs7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import { ref, watch, onUnmounted, computed, isRef, toValue, type Ref, type MaybeRef, type CSSProperties } from 'vue'\r\n\r\ninterface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRef<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Shiki 代码高亮库未安装,已降级为纯文本模式',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需语法高亮功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c shiki-stream 未安装,已降级为非流式高亮',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需流式代码高亮功能(推荐用于 AI 场景),请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki-stream',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n tokens.forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment, index) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Streaming highlighting failed:', err)\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Direct highlighting failed:', err)\r\n // 降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n\r\n // 即使 shiki 不可用,也检查 shiki-stream 并显示提示\r\n const shikiStreamMod = await loadShikiStream()\r\n if (!shikiStreamMod) {\r\n // shiki 和 shiki-stream 都不可用\r\n showShikiStreamHint()\r\n }\r\n return\r\n }\r\n\r\n // shiki 3.x API\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n\r\n lastRequestedLang = currentLang\r\n\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":["shikiModulePromise","shikiStreamModulePromise","hasShownShikiHint","hasShownShikiStreamHint","showShikiStreamHint","console","log","loadShikiStream","async","mod","import","default","tokensToLineTokens","tokens","length","lines","currentLine","startNewLine","push","forEach","token","content","includes","segments","split","segment","index","text","options","streaming","ref","isLoading","error","tokenizer","previousText","highlighter","currentUsedLang","lastRequestedLang","effectiveTheme","computed","isRef","theme","value","effectiveLanguage","toValue","language","preStyle","updateTokens","nextText","forceReset","clear","canAppend","startsWith","chunk","slice","mergedTokens","tokensStable","tokensUnstable","colorReplacements","enqueue","err","currentLang","currentTheme","codeToTokens","lang","initHighlighter","loadShiki","createHighlighter","themes","langs","loadLanguage","shikiStreamMod","ShikiStreamTokenizer","themeInfo","getTheme","preStyleValue","bg","fg","backgroundColor","color","createPreStyle","watch","newLang","requestedLang","immediate","newText","onUnmounted"],"mappings":"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"}
|
package/dist/x-markdown.cjs9.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("lodash-es"),t=require("vue");let n=null,o=!1,a=null,r=null;const l=[];let
|
|
1
|
+
"use strict";Object.create,Object.defineProperty,Object.getOwnPropertyDescriptor,Object.getOwnPropertyNames,Object.getPrototypeOf,Object.prototype.hasOwnProperty,Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("lodash-es"),t=require("vue");let n=null,o=!1,a=null,r=null;const l=()=>{o||(o=!0,console.log("%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示","font-weight: bold; color: #9333ea;","color: #666;"),console.log("%c如需 Mermaid 图表渲染功能,请安装:","color: #666; font-weight: bold;"),console.log("%c pnpm add mermaid","color: #9333ea; font-family: monospace;"),console.log("%c安装后请重启开发服务器","color: #999; font-size: 12px;"))},u=[];let c=!1;exports.checkMermaidAvailable=async function(){return null!==a?a:r||(r=(async()=>{try{const e=await import("mermaid"),t=e?.default,n=t&&("function"==typeof t.render||"function"==typeof t.initialize||"function"==typeof t.run);return a=n,n||l(),a}catch{return a=!1,l(),!1}})(),r)},exports.downloadSvgAsPng=function(e){if(e)try{const t=`data:image/svg+xml;charset=utf-8,${encodeURIComponent(e)}`,n=new Image;n.onload=()=>{try{const o=document.createElement("canvas"),a=o.getContext("2d",{willReadFrequently:!1});if(!a)return;const r=2;o.width=n.width*r,o.height=n.height*r,a.imageSmoothingEnabled=!0,a.imageSmoothingQuality="high",a.fillStyle="#ffffff",a.fillRect(0,0,o.width,o.height),a.drawImage(n,0,0,o.width,o.height);const l=(new Date).toISOString().slice(0,19).replace(/:/g,"-");try{o.toBlob(e=>{if(!e)return;const t=URL.createObjectURL(e),n=document.createElement("a");n.href=t,n.download=`mermaid-diagram-${l}.png`,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(t)},"image/png",.95)}catch(e){console.error("Failed to convert canvas to blob:",e);try{const e=o.toDataURL("image/png",.95),t=document.createElement("a");t.href=e,t.download=`mermaid-diagram-${l}.png`,document.body.appendChild(t),t.click(),document.body.removeChild(t)}catch(t){console.error("Failed to convert canvas to data URL:",t)}}}catch(o){console.error("Canvas operation failed:",o)}},n.onerror=e=>{console.error("Failed to load image:",e)},n.src=t}catch(t){console.error("Failed to download SVG:",t)}},exports.useMermaid=function(o,a={}){const r=t.computed(()=>"object"==typeof a&&"value"in a?a.value:a),i=t.computed(()=>({suppressErrorRendering:!0,startOnLoad:!1,securityLevel:"loose",theme:r.value.theme||"default",...r.value.config||{}})),s=t.ref(""),d=t.ref(null),v=t.ref(!1);let m=!1;const f=e.throttle(()=>{const e="string"==typeof o?o:o.value;if(!e?.trim())return s.value="",d.value=null,void(v.value=!1);var t;v.value=!0,t=async()=>{if(!m)try{const t=await async function(){return"undefined"==typeof window?null:(n||(n=(async()=>{try{const e=await import("mermaid");return e&&null===e.default?(l(),null):e?.default}catch{return l(),null}})()),n)}();if(!t)return s.value=e,d.value=null,void(v.value=!1);if(t.initialize(i.value),!(await t.parse(e.trim())))return s.value="",d.value=new Error("Mermaid parse error: Invalid syntax"),void(v.value=!1);const o=`${r.value.id||"mermaid"}-${Math.random().toString(36).substring(2,11)}`,a=(()=>{const e=r.value.container;return e?"object"==typeof e&&"value"in e?e.value:e:null})();if(!a)return void(v.value=!1);const{svg:u}=await t.render(o,e,a);s.value=u,d.value=null,v.value=!1}catch(t){s.value="",d.value=t,v.value=!1}},u.push(t),async function(){if(!c){for(c=!0;u.length>0;){const t=u.shift();if(t)try{await t()}catch(e){console.error("Mermaid render queue error:",e)}}c=!1}}()},100,{leading:!1,trailing:!0});return t.watch([()=>"string"==typeof o?o:o.value,()=>i.value],()=>{f()},{immediate:!0}),t.onUnmounted(()=>{m=!0}),{data:s,error:d,isLoading:v}},exports.useMermaidZoom=function(n){const{container:o}=n,a=t.ref(1),r=t.ref(0),l=t.ref(0),u=t.ref(!1);let c=null;const i=()=>o.value?.querySelector(".syntax-mermaid__content svg"),s=e=>{e.style.transformOrigin="center center",e.style.transform=`translate(${r.value}px, ${l.value}px) scale(${a.value})`},d=()=>{a.value=1,r.value=0,l.value=0,u.value=!1},v=()=>{c?.(),c=null,d()};return t.watch(()=>o.value,()=>{v(),d()}),t.onUnmounted(v),{zoomIn:()=>{const e=i();e&&(a.value=Math.min(a.value+.2,10),s(e))},zoomOut:()=>{const e=i();e&&(a.value=Math.max(a.value-.2,.1),s(e))},reset:()=>{const e=i();e&&(d(),s(e))},fullscreen:()=>{o.value&&(document.fullscreenElement?document.exitFullscreen():o.value.requestFullscreen?.())},destroy:v,initialize:()=>{if(!o.value)return;d(),c=(t=>{let n=0,o=0,c=!1;const d=(e,t)=>{u.value=!0,n=e-r.value,o=t-l.value,document.body.style.userSelect="none"},v=(e,t)=>{if(u.value&&c){r.value=e-n,l.value=t-o;const a=i();a&&s(a)}},m=()=>{u.value=!1,c=!1,document.body.style.userSelect=""},f=e=>{0===e.button&&(e.target===t||t.contains(e.target))&&(e.preventDefault(),c=!0,d(e.clientX,e.clientY))},h=e=>{c&&v(e.clientX,e.clientY)},g=e.throttle(e=>{const n=i();if(!n)return;const o=t.getBoundingClientRect(),u=n.getBoundingClientRect(),c=e.clientX-o.left,d=e.clientY-o.top,v=u.left-o.left+u.width/2,m=u.top-o.top+u.height/2,f=(c-v-r.value)/a.value,h=(d-m-l.value)/a.value,g=e.deltaY>0?-.05:.05,p=Math.min(Math.max(a.value+g,.1),10);p!==a.value&&(a.value=p,r.value=c-v-f*a.value,l.value=d-m-h*a.value,s(n))},20,{leading:!0,trailing:!0}),p=e=>{(e.target===t||t.contains(e.target))&&(e.preventDefault(),g(e))},y=e=>{(e.target===t||t.contains(e.target))&&1===e.touches.length&&(e.preventDefault(),c=!0,d(e.touches[0].clientX,e.touches[0].clientY))},w=e=>{c&&(e.preventDefault(),v(e.touches[0].clientX,e.touches[0].clientY))};return t.addEventListener("mousedown",f),document.addEventListener("mousemove",h),document.addEventListener("mouseup",m),t.addEventListener("wheel",p,{passive:!1}),t.addEventListener("touchstart",y,{passive:!1}),t.addEventListener("touchmove",w,{passive:!1}),document.addEventListener("touchend",m),()=>{t.removeEventListener("mousedown",f),document.removeEventListener("mousemove",h),document.removeEventListener("mouseup",m),t.removeEventListener("wheel",p),t.removeEventListener("touchstart",y),t.removeEventListener("touchmove",w),document.removeEventListener("touchend",m),document.body.style.userSelect=""}})(o.value);const t=i();t&&s(t)}}};
|
|
2
2
|
//# sourceMappingURL=x-markdown.cjs9.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.cjs9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需 Mermaid 图表渲染功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add mermaid',\r\n 'color: #9333ea; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":["mermaidPromise","hasShownMermaidHint","mermaidAvailableCache","mermaidCheckPromise","renderQueue","isProcessingQueue","async","mod","import","mermaidInstance","default","hasMermaidAPI","render","initialize","run","svg","svgDataUrl","encodeURIComponent","img","Image","onload","canvas","document","createElement","ctx","getContext","willReadFrequently","scale","width","height","imageSmoothingEnabled","imageSmoothingQuality","fillStyle","fillRect","drawImage","timestamp","Date","toISOString","slice","replace","toBlob","blob","url","URL","createObjectURL","link","href","download","body","appendChild","click","removeChild","revokeObjectURL","toBlobError","console","error","dataUrl","toDataURL","dataUrlError","canvasError","onerror","src","content","options","optionsRef","computed","value","mermaidConfig","suppressErrorRendering","startOnLoad","securityLevel","theme","config","data","ref","isLoading","isUnmounted","throttledRender","throttle","contentValue","trim","task","window","log","loadMermaid","parse","Error","renderId","id","Math","random","toString","substring","container","containerOption","getRenderContainer","err","push","length","shift","processRenderQueue","leading","trailing","watch","immediate","onUnmounted","posX","posY","isDragging","removeEvents","getSvg","querySelector","updateTransform","style","transformOrigin","transform","resetState","destroy","zoomIn","min","zoomOut","max","reset","fullscreen","fullscreenElement","exitFullscreen","requestFullscreen","containerEl","startX","startY","isInteractingWithMermaid","onStart","clientX","clientY","userSelect","onMove","onEnd","onMouseDown","e","button","target","contains","preventDefault","onMouseMove","throttledWheelZoom","containerRect","getBoundingClientRect","svgRect","mouseX","left","mouseY","top","svgCenterX","svgCenterY","offsetX","offsetY","delta","deltaY","newScale","onWheel","onTouchStart","touches","onTouchMove","addEventListener","passive","removeEventListener","addInteractionEvents"],"mappings":"mRAoFA,IAAIA,EAAsC,KACtCC,GAAsB,EACtBC,EAAwC,KACxCC,EAAsD,KA0F1D,MAAMC,EAA4B,GAClC,IAAIC,GAAoB,gCA9ExBC,iBAEE,OAA8B,OAA1BJ,EACKA,EAILC,IAKJA,EAAA,WACE,IACE,MAAMI,QAAYC,OAAO,WAGnBC,EAAmBF,GAAaG,QAEhCC,EAAgBF,IACc,mBAA3BA,EAAgBG,QACe,mBAA/BH,EAAgBI,YACQ,mBAAxBJ,EAAgBK,KAGzB,OADAZ,EAAwBS,EACjBT,CACT,CAAA,MAGE,OADAA,GAAwB,GACjB,CACT,CACF,EAnBA,GAqBOC,EACT,2BAjIO,SAA0BY,GAC/B,GAAKA,EAEL,IACE,MAAMC,EAAa,oCAAoCC,mBAAmBF,KACpEG,EAAM,IAAIC,MAEhBD,EAAIE,OAAS,KACX,IACE,MAAMC,EAASC,SAASC,cAAc,UAChCC,EAAMH,EAAOI,WAAW,KAAM,CAAEC,oBAAoB,IAC1D,IAAKF,EAAK,OAEV,MAAMG,EAAQ,EACdN,EAAOO,MAAQV,EAAIU,MAAQD,EAC3BN,EAAOQ,OAASX,EAAIW,OAASF,EAC7BH,EAAIM,uBAAwB,EAC5BN,EAAIO,sBAAwB,OAE5BP,EAAIQ,UAAY,UAChBR,EAAIS,SAAS,EAAG,EAAGZ,EAAOO,MAAOP,EAAOQ,QACxCL,EAAIU,UAAUhB,EAAK,EAAG,EAAGG,EAAOO,MAAOP,EAAOQ,QAE9C,MAAMM,GAAA,IAAgBC,MAAOC,cAAcC,MAAM,EAAG,IAAIC,QAAQ,KAAM,KAEtE,IACElB,EAAOmB,OACJC,IACC,IAAKA,EAAM,OACX,MAAMC,EAAMC,IAAIC,gBAAgBH,GAC1BI,EAAOvB,SAASC,cAAc,KACpCsB,EAAKC,KAAOJ,EACZG,EAAKE,SAAW,mBAAmBZ,QACnCb,SAAS0B,KAAKC,YAAYJ,GAC1BA,EAAKK,QACL5B,SAAS0B,KAAKG,YAAYN,GAC1BF,IAAIS,gBAAgBV,IAEtB,YACA,IAEJ,OAASW,GACPC,QAAQC,MAAM,oCAAqCF,GACnD,IACE,MAAMG,EAAUnC,EAAOoC,UAAU,YAAa,KACxCZ,EAAOvB,SAASC,cAAc,KACpCsB,EAAKC,KAAOU,EACZX,EAAKE,SAAW,mBAAmBZ,QACnCb,SAAS0B,KAAKC,YAAYJ,GAC1BA,EAAKK,QACL5B,SAAS0B,KAAKG,YAAYN,EAC5B,OAASa,GACPJ,QAAQC,MAAM,wCAAyCG,EACzD,CACF,CACF,OAASC,GACPL,QAAQC,MAAM,2BAA4BI,EAC5C,GAGFzC,EAAI0C,QAAWL,IACbD,QAAQC,MAAM,wBAAyBA,IAGzCrC,EAAI2C,IAAM7C,CACZ,OAASuC,GACPD,QAAQC,MAAM,0BAA2BA,EAC3C,CACF,qBAkIO,SAAoBO,EAA+BC,EAAkC,IAC1F,MAAMC,EAAaC,EAAAA,SAAS,IAA0B,iBAAZF,GAAwB,UAAWA,EAAUA,EAAQG,MAAQH,GACjGI,EAAgBF,EAAAA,SAAS,KAAA,CAC7BG,wBAAwB,EACxBC,aAAa,EACbC,cAAe,QACfC,MAAOP,EAAWE,MAAMK,OAAS,aAC7BP,EAAWE,MAAMM,QAAU,CAAA,KAE3BC,EAAOC,EAAAA,IAAI,IACXnB,EAAQmB,EAAAA,IAAa,MACrBC,EAAYD,EAAAA,KAAI,GAEtB,IAAIE,GAAc,EAElB,MAQMC,EAAkBC,EAAAA,SACtB,KACE,MAAMC,EAAkC,iBAAZjB,EAAuBA,EAAUA,EAAQI,MACrE,IAAKa,GAAcC,OAIjB,OAHAP,EAAKP,MAAQ,GACbX,EAAMW,MAAQ,UACdS,EAAUT,OAAQ,GAlC1B,IAA0Be,EAsCpBN,EAAUT,OAAQ,EAtCEe,EAwCH3E,UACf,IAAIsE,EAEJ,IACE,MAAMnE,QAnFhBH,iBACE,MAAsB,oBAAX4E,OAA+B,MACrClF,IACHA,EAAA,WACE,IACE,MAAMO,QAAYC,OAAO,WACzB,OAAQD,GAAaG,OACvB,CAAA,MAGE,OAhCFT,IACJA,GAAsB,EAEtBqD,QAAQ6B,IACN,yCACA,qCACA,gBAEF7B,QAAQ6B,IACN,2BACA,mCAEF7B,QAAQ6B,IACN,uBACA,2CAEF7B,QAAQ6B,IACN,gBACA,kCAcW,IACT,CACF,EATA,IAWKnF,EACT,CAoEwCoF,GAC9B,IAAK3E,EAIH,OAHAgE,EAAKP,MAAQa,EACbxB,EAAMW,MAAQ,UACdS,EAAUT,OAAQ,GAOpB,GAHAzD,EAAgBI,WAAWsD,EAAcD,eAEnBzD,EAAgB4E,MAAMN,EAAaC,SAKvD,OAHAP,EAAKP,MAAQ,GACbX,EAAMW,MAAQ,IAAIoB,MAAM,4CACxBX,EAAUT,OAAQ,GAIpB,MAAMqB,EAAW,GAAGvB,EAAWE,MAAMsB,IAAM,aAAaC,KAAKC,SAASC,SAAS,IAAIC,UAAU,EAAG,MAC1FC,EA3Ca,MACzB,MAAMC,EAAkB9B,EAAWE,MAAM2B,UACzC,OAAIC,EACgC,iBAApBA,GAAgC,UAAWA,EAAkBA,EAAgB5B,MAAQ4B,EAE9F,MAsCiBC,GAClB,IAAKF,EAEH,YADAlB,EAAUT,OAAQ,GAIpB,MAAMnD,IAAEA,SAAcN,EAAgBG,OAAO2E,EAAUR,EAAcc,GACrEpB,EAAKP,MAAQnD,EACbwC,EAAMW,MAAQ,KACdS,EAAUT,OAAQ,CACpB,OAAS8B,GAEPvB,EAAKP,MAAQ,GACbX,EAAMW,MAAQ8B,EACdrB,EAAUT,OAAQ,CACpB,GA7EN9D,EAAY6F,KAAKhB,GAnBnB3E,iBACE,IAAID,EAAJ,CAGA,IAFAA,GAAoB,EAEbD,EAAY8F,OAAS,GAAG,CAC7B,MAAMjB,EAAO7E,EAAY+F,QACzB,GAAIlB,EACF,UACQA,GACR,OAASe,GACP1C,QAAQC,MAAM,8BAA+ByC,EAC/C,CAEJ,CAEA3F,GAAoB,CAdG,CAezB,CAIE+F,IA+EE,IACA,CAAEC,SAAS,EAAOC,UAAU,IAe9B,OAZAC,EAAAA,MACE,CAAC,IAA0B,iBAAZzC,EAAuBA,EAAUA,EAAQI,MAAQ,IAAMC,EAAcD,OACpF,KACEW,KAEF,CAAE2B,WAAW,IAGfC,EAAAA,YAAY,KACV7B,GAAc,IAGT,CACLH,OACAlB,QACAoB,YAEJ,yBAEO,SAAwBZ,GAC7B,MAAM8B,UAAEA,GAAc9B,EAEhBpC,EAAQ+C,EAAAA,IAAI,GACZgC,EAAOhC,EAAAA,IAAI,GACXiC,EAAOjC,EAAAA,IAAI,GACXkC,EAAalC,EAAAA,KAAI,GAEvB,IAAImC,EAAoC,KAExC,MAAMC,EAAS,IAAMjB,EAAU3B,OAAO6C,cAAc,gCAE9CC,EAAmBjG,IACvBA,EAAIkG,MAAMC,gBAAkB,gBAC5BnG,EAAIkG,MAAME,UAAY,aAAaT,EAAKxC,YAAYyC,EAAKzC,kBAAkBvC,EAAMuC,UAG7EkD,EAAa,KACjBzF,EAAMuC,MAAQ,EACdwC,EAAKxC,MAAQ,EACbyC,EAAKzC,MAAQ,EACb0C,EAAW1C,OAAQ,GAyKfmD,EAAU,KACdR,MACAA,EAAe,KACfO,KAaF,OAVAb,EAAAA,MACE,IAAMV,EAAU3B,MAChB,KACEmD,IACAD,MAIJX,EAAAA,YAAYY,GAEL,CACLC,OAhEa,KACb,MAAMvG,EAAM+F,IACR/F,IACFY,EAAMuC,MAAQuB,KAAK8B,IAAI5F,EAAMuC,MAAQ,GAAK,IAC1C8C,EAAgBjG,KA6DlByG,QAzDc,KACd,MAAMzG,EAAM+F,IACR/F,IACFY,EAAMuC,MAAQuB,KAAKgC,IAAI9F,EAAMuC,MAAQ,GAAK,IAC1C8C,EAAgBjG,KAsDlB2G,MAlDY,KACZ,MAAM3G,EAAM+F,IACR/F,IACFqG,IACAJ,EAAgBjG,KA+ClB4G,WA3CiB,KACZ9B,EAAU3B,QAEX5C,SAASsG,kBACXtG,SAASuG,iBAEThC,EAAU3B,MAAM4D,wBAsClBT,UACAxG,WAnCiB,KACjB,IAAKgF,EAAU3B,MAAO,OAEtBkD,IAEAP,EA9J2B,CAACkB,IAC5B,IAAIC,EAAS,EACTC,EAAS,EACTC,GAA2B,EAE/B,MAAMC,EAAU,CAACC,EAAiBC,KAChCzB,EAAW1C,OAAQ,EACnB8D,EAASI,EAAU1B,EAAKxC,MACxB+D,EAASI,EAAU1B,EAAKzC,MACxB5C,SAAS0B,KAAKiE,MAAMqB,WAAa,QAG7BC,EAAS,CAACH,EAAiBC,KAC/B,GAAIzB,EAAW1C,OAASgE,EAA0B,CAChDxB,EAAKxC,MAAQkE,EAAUJ,EACvBrB,EAAKzC,MAAQmE,EAAUJ,EACvB,MAAMlH,EAAM+F,IACR/F,GACFiG,EAAgBjG,EAEpB,GAGIyH,EAAQ,KACZ5B,EAAW1C,OAAQ,EACnBgE,GAA2B,EAC3B5G,SAAS0B,KAAKiE,MAAMqB,WAAa,IAG7BG,EAAeC,IACF,IAAbA,EAAEC,SACFD,EAAEE,SAAWb,GAAeA,EAAYc,SAASH,EAAEE,WACrDF,EAAEI,iBACFZ,GAA2B,EAC3BC,EAAQO,EAAEN,QAASM,EAAEL,WAInBU,EAAeL,IACfR,GACFK,EAAOG,EAAEN,QAASM,EAAEL,UAiClBW,EAAqBlE,WA7BF4D,IACvB,MAAM3H,EAAM+F,IACZ,IAAK/F,EAAK,OAEV,MAAMkI,EAAgBlB,EAAYmB,wBAC5BC,EAAUpI,EAAImI,wBAEdE,EAASV,EAAEN,QAAUa,EAAcI,KACnCC,EAASZ,EAAEL,QAAUY,EAAcM,IAEnCC,EAAaL,EAAQE,KAAOJ,EAAcI,KAAOF,EAAQvH,MAAQ,EACjE6H,EAAaN,EAAQI,IAAMN,EAAcM,IAAMJ,EAAQtH,OAAS,EAEhE6H,GAAWN,EAASI,EAAa9C,EAAKxC,OAASvC,EAAMuC,MACrDyF,GAAWL,EAASG,EAAa9C,EAAKzC,OAASvC,EAAMuC,MAErD0F,EAAQlB,EAAEmB,OAAS,GAAI,IAAQ,IAC/BC,EAAWrE,KAAK8B,IAAI9B,KAAKgC,IAAI9F,EAAMuC,MAAQ0F,EAAO,IAAM,IAE1DE,IAAanI,EAAMuC,QAEvBvC,EAAMuC,MAAQ4F,EAEdpD,EAAKxC,MAAQkF,EAASI,EAAaE,EAAU/H,EAAMuC,MACnDyC,EAAKzC,MAAQoF,EAASG,EAAaE,EAAUhI,EAAMuC,MAEnD8C,EAAgBjG,KAGmC,GAAI,CAAEsF,SAAS,EAAMC,UAAU,IAE9EyD,EAAWrB,KACXA,EAAEE,SAAWb,GAAeA,EAAYc,SAASH,EAAEE,WACrDF,EAAEI,iBACFE,EAAmBN,KAIjBsB,EAAgBtB,KAChBA,EAAEE,SAAWb,GAAeA,EAAYc,SAASH,EAAEE,UAC5B,IAArBF,EAAEuB,QAAQ/D,SACZwC,EAAEI,iBACFZ,GAA2B,EAC3BC,EAAQO,EAAEuB,QAAQ,GAAG7B,QAASM,EAAEuB,QAAQ,GAAG5B,WAK3C6B,EAAexB,IACfR,IACFQ,EAAEI,iBACFP,EAAOG,EAAEuB,QAAQ,GAAG7B,QAASM,EAAEuB,QAAQ,GAAG5B,WAY9C,OARAN,EAAYoC,iBAAiB,YAAa1B,GAC1CnH,SAAS6I,iBAAiB,YAAapB,GACvCzH,SAAS6I,iBAAiB,UAAW3B,GACrCT,EAAYoC,iBAAiB,QAASJ,EAAS,CAAEK,SAAS,IAC1DrC,EAAYoC,iBAAiB,aAAcH,EAAc,CAAEI,SAAS,IACpErC,EAAYoC,iBAAiB,YAAaD,EAAa,CAAEE,SAAS,IAClE9I,SAAS6I,iBAAiB,WAAY3B,GAE/B,KACLT,EAAYsC,oBAAoB,YAAa5B,GAC7CnH,SAAS+I,oBAAoB,YAAatB,GAC1CzH,SAAS+I,oBAAoB,UAAW7B,GACxCT,EAAYsC,oBAAoB,QAASN,GACzChC,EAAYsC,oBAAoB,aAAcL,GAC9CjC,EAAYsC,oBAAoB,YAAaH,GAC7C5I,SAAS+I,oBAAoB,WAAY7B,GACzClH,SAAS0B,KAAKiE,MAAMqB,WAAa,KA2CpBgC,CAAqBzE,EAAU3B,OAE9C,MAAMnD,EAAM+F,IACR/F,GACFiG,EAAgBjG,IA4BtB"}
|
|
1
|
+
{"version":3,"file":"x-markdown.cjs9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需 Mermaid 图表渲染功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add mermaid',\r\n 'color: #9333ea; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":["mermaidPromise","hasShownMermaidHint","mermaidAvailableCache","mermaidCheckPromise","showMermaidHint","console","log","renderQueue","isProcessingQueue","async","mod","import","mermaidInstance","default","hasMermaidAPI","render","initialize","run","svg","svgDataUrl","encodeURIComponent","img","Image","onload","canvas","document","createElement","ctx","getContext","willReadFrequently","scale","width","height","imageSmoothingEnabled","imageSmoothingQuality","fillStyle","fillRect","drawImage","timestamp","Date","toISOString","slice","replace","toBlob","blob","url","URL","createObjectURL","link","href","download","body","appendChild","click","removeChild","revokeObjectURL","toBlobError","error","dataUrl","toDataURL","dataUrlError","canvasError","onerror","src","content","options","optionsRef","computed","value","mermaidConfig","suppressErrorRendering","startOnLoad","securityLevel","theme","config","data","ref","isLoading","isUnmounted","throttledRender","throttle","contentValue","trim","task","window","loadMermaid","parse","Error","renderId","id","Math","random","toString","substring","container","containerOption","getRenderContainer","err","push","length","shift","processRenderQueue","leading","trailing","watch","immediate","onUnmounted","posX","posY","isDragging","removeEvents","getSvg","querySelector","updateTransform","style","transformOrigin","transform","resetState","destroy","zoomIn","min","zoomOut","max","reset","fullscreen","fullscreenElement","exitFullscreen","requestFullscreen","containerEl","startX","startY","isInteractingWithMermaid","onStart","clientX","clientY","userSelect","onMove","onEnd","onMouseDown","e","button","target","contains","preventDefault","onMouseMove","throttledWheelZoom","containerRect","getBoundingClientRect","svgRect","mouseX","left","mouseY","top","svgCenterX","svgCenterY","offsetX","offsetY","delta","deltaY","newScale","onWheel","onTouchStart","touches","onTouchMove","addEventListener","passive","removeEventListener","addInteractionEvents"],"mappings":"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"}
|
package/dist/x-markdown.es7.js
CHANGED
|
@@ -207,6 +207,10 @@ function useHighlight(text, options) {
|
|
|
207
207
|
preStyle: void 0
|
|
208
208
|
};
|
|
209
209
|
showShikiHint();
|
|
210
|
+
const shikiStreamMod2 = await loadShikiStream();
|
|
211
|
+
if (!shikiStreamMod2) {
|
|
212
|
+
showShikiStreamHint();
|
|
213
|
+
}
|
|
210
214
|
return;
|
|
211
215
|
}
|
|
212
216
|
highlighter = await mod.createHighlighter({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.es7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import { ref, watch, onUnmounted, computed, isRef, toValue, type Ref, type MaybeRef, type CSSProperties } from 'vue'\r\n\r\ninterface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRef<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Shiki 代码高亮库未安装,已降级为纯文本模式',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需语法高亮功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c shiki-stream 未安装,已降级为非流式高亮',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需流式代码高亮功能(推荐用于 AI 场景),请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki-stream',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n tokens.forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment, index) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Streaming highlighting failed:', err)\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Direct highlighting failed:', err)\r\n // 降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n return\r\n }\r\n\r\n // shiki 3.x API\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n\r\n lastRequestedLang = currentLang\r\n\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":[],"mappings":";AAsBA,IAAI,qBAAiD;AACrD,IAAI,2BAAuD;AAC3D,IAAI,oBAAoB;AACxB,IAAI,0BAA0B;AAG9B,MAAM,gBAAgB,MAAM;AAC1B,MAAI,kBAAmB;AACvB,sBAAoB;AAEpB,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,MAAM,sBAAsB,MAAM;AAChC,MAAI,wBAAyB;AAC7B,4BAA0B;AAE1B,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,MAAM,YAAY,YAAY;AAC5B,MAAI,CAAC,oBAAoB;AACvB,0BAAsB,YAAY;AAChC,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,OAAO;AAEhC,YAAI,OAAQ,IAAY,YAAY,MAAM;AACxC,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF,GAAA;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,kBAAkB,YAAY;AAClC,MAAI,CAAC,0BAA0B;AAC7B,gCAA4B,YAAY;AACtC,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,cAAc;AAEvC,YAAI,OAAQ,IAAY,YAAY,MAAM;AACxC,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF,GAAA;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,qBAAqB,CAAC,WAAiD;AAC3E,MAAI,CAAC,OAAO,OAAQ,QAAO,CAAC,CAAA,CAAE;AAE9B,QAAM,QAA4B,CAAC,EAAE;AACrC,MAAI,cAAc,MAAM,CAAC;AAEzB,QAAM,eAAe,MAAM;AACzB,kBAAc,CAAA;AACd,UAAM,KAAK,WAAW;AAAA,EACxB;AAEA,SAAO,QAAQ,CAAC,UAAU;AACxB,UAAM,UAAU,MAAM,WAAW;AAEjC,QAAI,YAAY,MAAM;AACpB,mBAAA;AACA;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC3B,kBAAY,KAAK,KAAK;AACtB;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,MAAM,IAAI;AACnC,aAAS,QAAQ,CAAC,SAAS,UAAU;AACnC,UAAI,SAAS;AACX,oBAAY,KAAK;AAAA,UACf,GAAG;AAAA,UACH,SAAS;AAAA,QAAA,CACV;AAAA,MACH;AAEA,UAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,qBAAA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,SAAO,MAAM,WAAW,IAAI,CAAC,CAAA,CAAE,IAAI;AACrC;AAEA,MAAM,iBAAiB,CAAC,IAAa,OAA2C;AAC9E,MAAI,CAAC,MAAM,CAAC,GAAI,QAAO;AACvB,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,OAAO;AAAA,EAAA;AAEX;AAEO,SAAS,aAAa,MAAmB,SAA8B;AAC5E,QAAM,YAAY,IAAA;AAClB,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,QAAQ,IAAkB,IAAI;AAEpC,MAAI,YAAwB;AAC5B,MAAI,eAAe;AACnB,MAAI,cAA0B;AAC9B,MAAI,kBAAkB;AACtB,MAAI,oBAAoB;AAExB,QAAM,iBAAiB,SAAS,MAAM;AACpC,UAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,QAAQ;AACnE,WAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,oBAAoB,SAAS,MAAM;AACvC,WAAO,QAAQ,QAAQ,QAAQ,KAAK;AAAA,EACtC,CAAC;AAED,QAAM,QAAQ,SAAS,MAAM,UAAU,OAAO,SAAS,CAAC,CAAA,CAAE,CAAC;AAC3D,QAAM,WAAW,SAAS,MAAM,UAAU,OAAO,QAAQ;AAEzD,QAAM,eAAe,OAAO,UAAkB,aAAa,UAAU;AAEnE,QAAI,WAAW;AACb,UAAI,YAAY;AACd,kBAAU,MAAA;AACV,uBAAe;AAAA,MACjB;AAEA,YAAM,YAAY,CAAC,cAAc,SAAS,WAAW,YAAY;AACjE,UAAI,QAAQ;AAEZ,UAAI,WAAW;AACb,gBAAQ,SAAS,MAAM,aAAa,MAAM;AAAA,MAC5C,WAAW,CAAC,YAAY;AACtB,kBAAU,MAAA;AAAA,MACZ;AAEA,qBAAe;AAEf,UAAI,CAAC,OAAO;AACV,cAAM,eAAe,CAAC,GAAG,UAAU,cAAc,GAAG,UAAU,cAAc;AAC5E,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,aAAa,SAAS,mBAAmB,YAAY,IAAI,CAAC,EAAE;AAAA,UACnE,UAAU,UAAU,OAAO;AAAA,QAAA;AAE7B;AAAA,MACF;AAEA,UAAI;AACF,cAAM,UAAU,QAAQ,KAAK;AAE7B,cAAM,eAAe,CAAC,GAAG,UAAU,cAAc,GAAG,UAAU,cAAc;AAE5E,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,mBAAmB,YAAY;AAAA,UACtC,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B,SAAS,KAAK;AACZ,gBAAQ,MAAM,+CAA+C,GAAG;AAChE,cAAM,QAAQ;AAAA,MAChB;AAAA,IACF,WAAW,aAAa;AAGtB,UAAI;AACF,cAAM,cAAc,mBAAmB;AACvC,cAAM,eAAe,eAAe;AACpC,cAAM,SAAS,YAAY,aAAa,UAAU;AAAA,UAChD,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AAED,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,mBAAmB,MAAM;AAAA,UAChC,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B,SAAS,KAAK;AACZ,gBAAQ,MAAM,4CAA4C,GAAG;AAE7D,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,SAAA,CAAU,CAAC;AAAA,UAC/B,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,kBAAkB,YAAY;AAClC,cAAU,QAAQ;AAClB,UAAM,QAAQ;AAEd,QAAI,cAAc,kBAAkB;AACpC,UAAM,eAAe,eAAe;AAEpC,QAAI;AACF,YAAM,MAAM,MAAM,UAAA;AAClB,UAAI,CAAC,KAAK;AAER,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,KAAK,MAAA,CAAO,CAAC;AAAA,UACjC,UAAU;AAAA,QAAA;AAEZ,sBAAA;AACA;AAAA,MACF;AAGA,oBAAc,MAAM,IAAI,kBAAkB;AAAA,QACxC,QAAQ,CAAC,YAAY;AAAA,QACrB,OAAO,CAAA;AAAA;AAAA,MAAC,CACT;AAED,0BAAoB;AAEpB,UAAI;AACF,cAAM,YAAY,aAAa,WAAkB;AACjD,0BAAkB;AAAA,MACpB,QAAQ;AACN,sBAAc;AACd,0BAAkB;AAAA,MACpB;AAGA,YAAM,iBAAiB,MAAM,gBAAA;AAC7B,UAAI,gBAAgB;AAClB,oBAAY,IAAI,eAAe,qBAAqB;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AAAA,MACH,OAAO;AAEL,4BAAA;AAAA,MACF;AAEA,qBAAe;AAEf,YAAM,YAAY,YAAY,SAAS,YAAY;AACnD,YAAM,gBAAgB,eAAe,WAAW,IAAI,WAAW,EAAE;AAEjE,UAAI,KAAK,OAAO;AACd,cAAM,aAAa,KAAK,OAAO,IAAI;AACnC,YAAI,UAAU,OAAO;AACnB,oBAAU,MAAM,WAAW;AAAA,QAC7B;AAAA,MACF,OAAO;AACL,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAA,CAAE;AAAA,UACV,UAAU;AAAA,QAAA;AAAA,MAEd;AAAA,IACF,SAAS,KAAK;AAEZ,gBAAU,QAAQ;AAAA,QAChB,mBAAmB,QAAQ;AAAA,QAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,KAAK,MAAA,CAAO,CAAC;AAAA,QACjC,UAAU;AAAA,MAAA;AAAA,IAEd,UAAA;AACE,gBAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA;AAAA,IACE,MAAM,CAAC,kBAAkB,OAAO,eAAe,KAAK;AAAA,IACpD,OAAO,CAAC,OAAO,MAAM;AACnB,YAAM,gBAAgB;AAEtB,UACE,eACA,oBAAoB,eACpB,kBAAkB,qBAClB,kBAAkB,aAClB;AACA,YAAI;AACF,gBAAM,YAAY,aAAa,aAAoB;AACnD,0BAAA;AACA;AAAA,QACF,QAAQ;AACN,8BAAoB;AACpB;AAAA,QACF;AAAA,MACF;AAEA,sBAAA;AAAA,IACF;AAAA,IACA,EAAE,WAAW,KAAA;AAAA,EAAK;AAGpB,QAAM,MAAM,OAAO,YAAY;AAC7B,UAAM,gBAAgB,kBAAkB;AACxC,QACE,eACA,oBAAoB,eACpB,kBAAkB,qBAClB,kBAAkB,aAClB;AACA,UAAI;AACF,cAAM,YAAY,aAAa,aAAoB;AACnD,cAAM,gBAAA;AACN;AAAA,MACF,QAAQ;AACN,4BAAoB;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,aAAa,aAAa;AAG5B,mBAAa,OAAO;AAAA,IACtB,OAAO;AAEL,gBAAU,QAAQ;AAAA,QAChB,mBAAmB,QAAQ;AAAA,QAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,QAAA,CAAS,CAAC;AAAA,QAC9B,UAAU,UAAU,OAAO;AAAA,MAAA;AAAA,IAE/B;AAAA,EACF,CAAC;AAED,cAAY,MAAM;AAChB,eAAW,MAAA;AACX,gBAAY;AACZ,mBAAe;AAAA,EACjB,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"x-markdown.es7.js","sources":["../src/hooks/useHighlight.ts"],"sourcesContent":["import { ref, watch, onUnmounted, computed, isRef, toValue, type Ref, type MaybeRef, type CSSProperties } from 'vue'\r\n\r\ninterface HighlightToken {\r\n content?: string\r\n color?: string\r\n fontStyle?: 'italic' | null\r\n fontWeight?: 'normal' | 'bold' | null\r\n htmlStyle?: Record<string, string>\r\n}\r\n\r\ninterface StreamingHighlightResult {\r\n colorReplacements?: Record<string, string>\r\n lines: HighlightToken[][]\r\n preStyle?: CSSProperties\r\n}\r\n\r\ninterface UseHighlightOptions {\r\n language: MaybeRef<string>\r\n theme?: string | Ref<string>\r\n colorReplacements?: Record<string, string>\r\n}\r\n\r\nlet shikiModulePromise: Promise<any | null> | null = null\r\nlet shikiStreamModulePromise: Promise<any | null> | null = null\r\nlet hasShownShikiHint = false\r\nlet hasShownShikiStreamHint = false\r\n\r\n\r\nconst showShikiHint = () => {\r\n if (hasShownShikiHint) return\r\n hasShownShikiHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Shiki 代码高亮库未安装,已降级为纯文本模式',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需语法高亮功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst showShikiStreamHint = () => {\r\n if (hasShownShikiStreamHint) return\r\n hasShownShikiStreamHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c shiki-stream 未安装,已降级为非流式高亮',\r\n 'font-weight: bold; color: #0066cc;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需流式代码高亮功能(推荐用于 AI 场景),请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add shiki-stream',\r\n 'color: #00aa00; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nconst loadShiki = async () => {\r\n if (!shikiModulePromise) {\r\n shikiModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiModulePromise\r\n}\r\n\r\nconst loadShikiStream = async () => {\r\n if (!shikiStreamModulePromise) {\r\n shikiStreamModulePromise = (async () => {\r\n try {\r\n const mod = await import('shiki-stream')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n return null\r\n }\r\n return mod\r\n } catch {\r\n // 静默失败,返回 null\r\n return null\r\n }\r\n })()\r\n }\r\n return shikiStreamModulePromise\r\n}\r\n\r\nconst tokensToLineTokens = (tokens: HighlightToken[]): HighlightToken[][] => {\r\n if (!tokens.length) return [[]]\r\n\r\n const lines: HighlightToken[][] = [[]]\r\n let currentLine = lines[0]\r\n\r\n const startNewLine = () => {\r\n currentLine = []\r\n lines.push(currentLine)\r\n }\r\n\r\n tokens.forEach((token) => {\r\n const content = token.content ?? ''\r\n\r\n if (content === '\\n') {\r\n startNewLine()\r\n return\r\n }\r\n\r\n if (!content.includes('\\n')) {\r\n currentLine.push(token)\r\n return\r\n }\r\n\r\n const segments = content.split('\\n')\r\n segments.forEach((segment, index) => {\r\n if (segment) {\r\n currentLine.push({\r\n ...token,\r\n content: segment,\r\n })\r\n }\r\n\r\n if (index < segments.length - 1) {\r\n startNewLine()\r\n }\r\n })\r\n })\r\n\r\n return lines.length === 0 ? [[]] : lines\r\n}\r\n\r\nconst createPreStyle = (bg?: string, fg?: string): CSSProperties | undefined => {\r\n if (!bg && !fg) return undefined\r\n return {\r\n backgroundColor: bg,\r\n color: fg,\r\n }\r\n}\r\n\r\nexport function useHighlight(text: Ref<string>, options: UseHighlightOptions) {\r\n const streaming = ref<StreamingHighlightResult>()\r\n const isLoading = ref(false)\r\n const error = ref<Error | null>(null)\r\n\r\n let tokenizer: any | null = null\r\n let previousText = ''\r\n let highlighter: any | null = null\r\n let currentUsedLang = ''\r\n let lastRequestedLang = ''\r\n\r\n const effectiveTheme = computed(() => {\r\n const theme = isRef(options.theme) ? options.theme.value : options.theme\r\n return theme || 'slack-dark'\r\n })\r\n\r\n const effectiveLanguage = computed(() => {\r\n return toValue(options.language) || 'text'\r\n })\r\n\r\n const lines = computed(() => streaming.value?.lines || [[]])\r\n const preStyle = computed(() => streaming.value?.preStyle)\r\n\r\n const updateTokens = async (nextText: string, forceReset = false) => {\r\n // 当有 tokenizer 时使用流式处理\r\n if (tokenizer) {\r\n if (forceReset) {\r\n tokenizer.clear()\r\n previousText = ''\r\n }\r\n\r\n const canAppend = !forceReset && nextText.startsWith(previousText)\r\n let chunk = nextText\r\n\r\n if (canAppend) {\r\n chunk = nextText.slice(previousText.length)\r\n } else if (!forceReset) {\r\n tokenizer.clear()\r\n }\r\n\r\n previousText = nextText\r\n\r\n if (!chunk) {\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: mergedTokens.length ? tokensToLineTokens(mergedTokens) : [[]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n return\r\n }\r\n\r\n try {\r\n await tokenizer.enqueue(chunk)\r\n\r\n const mergedTokens = [...tokenizer.tokensStable, ...tokenizer.tokensUnstable]\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(mergedTokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Streaming highlighting failed:', err)\r\n error.value = err as Error\r\n }\r\n } else if (highlighter) {\r\n // 当没有 tokenizer 但有 highlighter 时,使用非流式方式高亮\r\n // 这发生在 shiki 可用但 shiki-stream 不可用时\r\n try {\r\n const currentLang = currentUsedLang || 'plaintext'\r\n const currentTheme = effectiveTheme.value\r\n const tokens = highlighter.codeToTokens(nextText, {\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: tokensToLineTokens(tokens),\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n } catch (err) {\r\n console.error('[x-markdown] Direct highlighting failed:', err)\r\n // 降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: nextText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n }\r\n }\r\n\r\n const initHighlighter = async () => {\r\n isLoading.value = true\r\n error.value = null\r\n\r\n let currentLang = effectiveLanguage.value\r\n const currentTheme = effectiveTheme.value\r\n\r\n try {\r\n const mod = await loadShiki()\r\n if (!mod) {\r\n // shiki 完全不可用\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n showShikiHint()\r\n\r\n // 即使 shiki 不可用,也检查 shiki-stream 并显示提示\r\n const shikiStreamMod = await loadShikiStream()\r\n if (!shikiStreamMod) {\r\n // shiki 和 shiki-stream 都不可用\r\n showShikiStreamHint()\r\n }\r\n return\r\n }\r\n\r\n // shiki 3.x API\r\n highlighter = await mod.createHighlighter({\r\n themes: [currentTheme],\r\n langs: [], // 将动态加载语言\r\n })\r\n\r\n lastRequestedLang = currentLang\r\n\r\n try {\r\n await highlighter.loadLanguage(currentLang as any)\r\n currentUsedLang = currentLang\r\n } catch {\r\n currentLang = 'plaintext'\r\n currentUsedLang = 'plaintext'\r\n }\r\n\r\n // 动态加载 shiki-stream\r\n const shikiStreamMod = await loadShikiStream()\r\n if (shikiStreamMod) {\r\n tokenizer = new shikiStreamMod.ShikiStreamTokenizer({\r\n highlighter: highlighter,\r\n lang: currentLang,\r\n theme: currentTheme,\r\n })\r\n } else {\r\n // shiki 可用但 shiki-stream 不可用\r\n showShikiStreamHint()\r\n }\r\n\r\n previousText = ''\r\n\r\n const themeInfo = highlighter.getTheme(currentTheme)\r\n const preStyleValue = createPreStyle(themeInfo?.bg, themeInfo?.fg)\r\n\r\n if (text.value) {\r\n await updateTokens(text.value, true)\r\n if (streaming.value) {\r\n streaming.value.preStyle = preStyleValue\r\n }\r\n } else {\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[]],\r\n preStyle: preStyleValue,\r\n }\r\n }\r\n } catch (err) {\r\n // 静默降级\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: text.value }]],\r\n preStyle: undefined,\r\n }\r\n } finally {\r\n isLoading.value = false\r\n }\r\n }\r\n\r\n watch(\r\n () => [effectiveLanguage.value, effectiveTheme.value],\r\n async ([newLang]) => {\r\n const requestedLang = newLang as string\r\n\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n return\r\n }\r\n }\r\n\r\n initHighlighter()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n watch(text, async (newText) => {\r\n const requestedLang = effectiveLanguage.value\r\n if (\r\n highlighter &&\r\n currentUsedLang === 'plaintext' &&\r\n requestedLang !== lastRequestedLang &&\r\n requestedLang !== 'plaintext'\r\n ) {\r\n try {\r\n await highlighter.loadLanguage(requestedLang as any)\r\n await initHighlighter()\r\n return\r\n } catch {\r\n lastRequestedLang = requestedLang\r\n }\r\n }\r\n\r\n if (tokenizer || highlighter) {\r\n // 当有 tokenizer 或 highlighter 时都调用 updateTokens\r\n // updateTokens 内部会处理两种情况\r\n updateTokens(newText)\r\n } else {\r\n // 两者都没有时降级为纯文本\r\n streaming.value = {\r\n colorReplacements: options.colorReplacements,\r\n lines: [[{ content: newText }]],\r\n preStyle: streaming.value?.preStyle,\r\n }\r\n }\r\n })\r\n\r\n onUnmounted(() => {\r\n tokenizer?.clear()\r\n tokenizer = null\r\n previousText = ''\r\n })\r\n\r\n return {\r\n streaming,\r\n lines,\r\n preStyle,\r\n isLoading,\r\n error,\r\n }\r\n}\r\n"],"names":["shikiStreamMod"],"mappings":";AAsBA,IAAI,qBAAiD;AACrD,IAAI,2BAAuD;AAC3D,IAAI,oBAAoB;AACxB,IAAI,0BAA0B;AAG9B,MAAM,gBAAgB,MAAM;AAC1B,MAAI,kBAAmB;AACvB,sBAAoB;AAEpB,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,MAAM,sBAAsB,MAAM;AAChC,MAAI,wBAAyB;AAC7B,4BAA0B;AAE1B,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEF,UAAQ;AAAA,IACN;AAAA,IACA;AAAA,EAAA;AAEJ;AAEA,MAAM,YAAY,YAAY;AAC5B,MAAI,CAAC,oBAAoB;AACvB,0BAAsB,YAAY;AAChC,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,OAAO;AAEhC,YAAI,OAAQ,IAAY,YAAY,MAAM;AACxC,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF,GAAA;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,kBAAkB,YAAY;AAClC,MAAI,CAAC,0BAA0B;AAC7B,gCAA4B,YAAY;AACtC,UAAI;AACF,cAAM,MAAM,MAAM,OAAO,cAAc;AAEvC,YAAI,OAAQ,IAAY,YAAY,MAAM;AACxC,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT,QAAQ;AAEN,eAAO;AAAA,MACT;AAAA,IACF,GAAA;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,qBAAqB,CAAC,WAAiD;AAC3E,MAAI,CAAC,OAAO,OAAQ,QAAO,CAAC,CAAA,CAAE;AAE9B,QAAM,QAA4B,CAAC,EAAE;AACrC,MAAI,cAAc,MAAM,CAAC;AAEzB,QAAM,eAAe,MAAM;AACzB,kBAAc,CAAA;AACd,UAAM,KAAK,WAAW;AAAA,EACxB;AAEA,SAAO,QAAQ,CAAC,UAAU;AACxB,UAAM,UAAU,MAAM,WAAW;AAEjC,QAAI,YAAY,MAAM;AACpB,mBAAA;AACA;AAAA,IACF;AAEA,QAAI,CAAC,QAAQ,SAAS,IAAI,GAAG;AAC3B,kBAAY,KAAK,KAAK;AACtB;AAAA,IACF;AAEA,UAAM,WAAW,QAAQ,MAAM,IAAI;AACnC,aAAS,QAAQ,CAAC,SAAS,UAAU;AACnC,UAAI,SAAS;AACX,oBAAY,KAAK;AAAA,UACf,GAAG;AAAA,UACH,SAAS;AAAA,QAAA,CACV;AAAA,MACH;AAEA,UAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,qBAAA;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AAED,SAAO,MAAM,WAAW,IAAI,CAAC,CAAA,CAAE,IAAI;AACrC;AAEA,MAAM,iBAAiB,CAAC,IAAa,OAA2C;AAC9E,MAAI,CAAC,MAAM,CAAC,GAAI,QAAO;AACvB,SAAO;AAAA,IACL,iBAAiB;AAAA,IACjB,OAAO;AAAA,EAAA;AAEX;AAEO,SAAS,aAAa,MAAmB,SAA8B;AAC5E,QAAM,YAAY,IAAA;AAClB,QAAM,YAAY,IAAI,KAAK;AAC3B,QAAM,QAAQ,IAAkB,IAAI;AAEpC,MAAI,YAAwB;AAC5B,MAAI,eAAe;AACnB,MAAI,cAA0B;AAC9B,MAAI,kBAAkB;AACtB,MAAI,oBAAoB;AAExB,QAAM,iBAAiB,SAAS,MAAM;AACpC,UAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,QAAQ,QAAQ;AACnE,WAAO,SAAS;AAAA,EAClB,CAAC;AAED,QAAM,oBAAoB,SAAS,MAAM;AACvC,WAAO,QAAQ,QAAQ,QAAQ,KAAK;AAAA,EACtC,CAAC;AAED,QAAM,QAAQ,SAAS,MAAM,UAAU,OAAO,SAAS,CAAC,CAAA,CAAE,CAAC;AAC3D,QAAM,WAAW,SAAS,MAAM,UAAU,OAAO,QAAQ;AAEzD,QAAM,eAAe,OAAO,UAAkB,aAAa,UAAU;AAEnE,QAAI,WAAW;AACb,UAAI,YAAY;AACd,kBAAU,MAAA;AACV,uBAAe;AAAA,MACjB;AAEA,YAAM,YAAY,CAAC,cAAc,SAAS,WAAW,YAAY;AACjE,UAAI,QAAQ;AAEZ,UAAI,WAAW;AACb,gBAAQ,SAAS,MAAM,aAAa,MAAM;AAAA,MAC5C,WAAW,CAAC,YAAY;AACtB,kBAAU,MAAA;AAAA,MACZ;AAEA,qBAAe;AAEf,UAAI,CAAC,OAAO;AACV,cAAM,eAAe,CAAC,GAAG,UAAU,cAAc,GAAG,UAAU,cAAc;AAC5E,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,aAAa,SAAS,mBAAmB,YAAY,IAAI,CAAC,EAAE;AAAA,UACnE,UAAU,UAAU,OAAO;AAAA,QAAA;AAE7B;AAAA,MACF;AAEA,UAAI;AACF,cAAM,UAAU,QAAQ,KAAK;AAE7B,cAAM,eAAe,CAAC,GAAG,UAAU,cAAc,GAAG,UAAU,cAAc;AAE5E,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,mBAAmB,YAAY;AAAA,UACtC,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B,SAAS,KAAK;AACZ,gBAAQ,MAAM,+CAA+C,GAAG;AAChE,cAAM,QAAQ;AAAA,MAChB;AAAA,IACF,WAAW,aAAa;AAGtB,UAAI;AACF,cAAM,cAAc,mBAAmB;AACvC,cAAM,eAAe,eAAe;AACpC,cAAM,SAAS,YAAY,aAAa,UAAU;AAAA,UAChD,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AAED,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,mBAAmB,MAAM;AAAA,UAChC,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B,SAAS,KAAK;AACZ,gBAAQ,MAAM,4CAA4C,GAAG;AAE7D,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,SAAA,CAAU,CAAC;AAAA,UAC/B,UAAU,UAAU,OAAO;AAAA,QAAA;AAAA,MAE/B;AAAA,IACF;AAAA,EACF;AAEA,QAAM,kBAAkB,YAAY;AAClC,cAAU,QAAQ;AAClB,UAAM,QAAQ;AAEd,QAAI,cAAc,kBAAkB;AACpC,UAAM,eAAe,eAAe;AAEpC,QAAI;AACF,YAAM,MAAM,MAAM,UAAA;AAClB,UAAI,CAAC,KAAK;AAER,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,KAAK,MAAA,CAAO,CAAC;AAAA,UACjC,UAAU;AAAA,QAAA;AAEZ,sBAAA;AAGA,cAAMA,kBAAiB,MAAM,gBAAA;AAC7B,YAAI,CAACA,iBAAgB;AAEnB,8BAAA;AAAA,QACF;AACA;AAAA,MACF;AAGA,oBAAc,MAAM,IAAI,kBAAkB;AAAA,QACxC,QAAQ,CAAC,YAAY;AAAA,QACrB,OAAO,CAAA;AAAA;AAAA,MAAC,CACT;AAED,0BAAoB;AAEpB,UAAI;AACF,cAAM,YAAY,aAAa,WAAkB;AACjD,0BAAkB;AAAA,MACpB,QAAQ;AACN,sBAAc;AACd,0BAAkB;AAAA,MACpB;AAGA,YAAM,iBAAiB,MAAM,gBAAA;AAC7B,UAAI,gBAAgB;AAClB,oBAAY,IAAI,eAAe,qBAAqB;AAAA,UAClD;AAAA,UACA,MAAM;AAAA,UACN,OAAO;AAAA,QAAA,CACR;AAAA,MACH,OAAO;AAEL,4BAAA;AAAA,MACF;AAEA,qBAAe;AAEf,YAAM,YAAY,YAAY,SAAS,YAAY;AACnD,YAAM,gBAAgB,eAAe,WAAW,IAAI,WAAW,EAAE;AAEjE,UAAI,KAAK,OAAO;AACd,cAAM,aAAa,KAAK,OAAO,IAAI;AACnC,YAAI,UAAU,OAAO;AACnB,oBAAU,MAAM,WAAW;AAAA,QAC7B;AAAA,MACF,OAAO;AACL,kBAAU,QAAQ;AAAA,UAChB,mBAAmB,QAAQ;AAAA,UAC3B,OAAO,CAAC,CAAA,CAAE;AAAA,UACV,UAAU;AAAA,QAAA;AAAA,MAEd;AAAA,IACF,SAAS,KAAK;AAEZ,gBAAU,QAAQ;AAAA,QAChB,mBAAmB,QAAQ;AAAA,QAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,KAAK,MAAA,CAAO,CAAC;AAAA,QACjC,UAAU;AAAA,MAAA;AAAA,IAEd,UAAA;AACE,gBAAU,QAAQ;AAAA,IACpB;AAAA,EACF;AAEA;AAAA,IACE,MAAM,CAAC,kBAAkB,OAAO,eAAe,KAAK;AAAA,IACpD,OAAO,CAAC,OAAO,MAAM;AACnB,YAAM,gBAAgB;AAEtB,UACE,eACA,oBAAoB,eACpB,kBAAkB,qBAClB,kBAAkB,aAClB;AACA,YAAI;AACF,gBAAM,YAAY,aAAa,aAAoB;AACnD,0BAAA;AACA;AAAA,QACF,QAAQ;AACN,8BAAoB;AACpB;AAAA,QACF;AAAA,MACF;AAEA,sBAAA;AAAA,IACF;AAAA,IACA,EAAE,WAAW,KAAA;AAAA,EAAK;AAGpB,QAAM,MAAM,OAAO,YAAY;AAC7B,UAAM,gBAAgB,kBAAkB;AACxC,QACE,eACA,oBAAoB,eACpB,kBAAkB,qBAClB,kBAAkB,aAClB;AACA,UAAI;AACF,cAAM,YAAY,aAAa,aAAoB;AACnD,cAAM,gBAAA;AACN;AAAA,MACF,QAAQ;AACN,4BAAoB;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,aAAa,aAAa;AAG5B,mBAAa,OAAO;AAAA,IACtB,OAAO;AAEL,gBAAU,QAAQ;AAAA,QAChB,mBAAmB,QAAQ;AAAA,QAC3B,OAAO,CAAC,CAAC,EAAE,SAAS,QAAA,CAAS,CAAC;AAAA,QAC9B,UAAU,UAAU,OAAO;AAAA,MAAA;AAAA,IAE/B;AAAA,EACF,CAAC;AAED,cAAY,MAAM;AAChB,eAAW,MAAA;AACX,gBAAY;AACZ,mBAAe;AAAA,EACjB,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
|
package/dist/x-markdown.es9.js
CHANGED
|
@@ -65,27 +65,6 @@ let mermaidPromise = null;
|
|
|
65
65
|
let hasShownMermaidHint = false;
|
|
66
66
|
let mermaidAvailableCache = null;
|
|
67
67
|
let mermaidCheckPromise = null;
|
|
68
|
-
async function checkMermaidAvailable() {
|
|
69
|
-
if (mermaidAvailableCache !== null) {
|
|
70
|
-
return mermaidAvailableCache;
|
|
71
|
-
}
|
|
72
|
-
if (mermaidCheckPromise) {
|
|
73
|
-
return mermaidCheckPromise;
|
|
74
|
-
}
|
|
75
|
-
mermaidCheckPromise = (async () => {
|
|
76
|
-
try {
|
|
77
|
-
const mod = await import("mermaid");
|
|
78
|
-
const mermaidInstance = mod?.default;
|
|
79
|
-
const hasMermaidAPI = mermaidInstance && (typeof mermaidInstance.render === "function" || typeof mermaidInstance.initialize === "function" || typeof mermaidInstance.run === "function");
|
|
80
|
-
mermaidAvailableCache = hasMermaidAPI;
|
|
81
|
-
return mermaidAvailableCache;
|
|
82
|
-
} catch {
|
|
83
|
-
mermaidAvailableCache = false;
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
})();
|
|
87
|
-
return mermaidCheckPromise;
|
|
88
|
-
}
|
|
89
68
|
const showMermaidHint = () => {
|
|
90
69
|
if (hasShownMermaidHint) return;
|
|
91
70
|
hasShownMermaidHint = true;
|
|
@@ -107,12 +86,41 @@ const showMermaidHint = () => {
|
|
|
107
86
|
"color: #999; font-size: 12px;"
|
|
108
87
|
);
|
|
109
88
|
};
|
|
89
|
+
async function checkMermaidAvailable() {
|
|
90
|
+
if (mermaidAvailableCache !== null) {
|
|
91
|
+
return mermaidAvailableCache;
|
|
92
|
+
}
|
|
93
|
+
if (mermaidCheckPromise) {
|
|
94
|
+
return mermaidCheckPromise;
|
|
95
|
+
}
|
|
96
|
+
mermaidCheckPromise = (async () => {
|
|
97
|
+
try {
|
|
98
|
+
const mod = await import("mermaid");
|
|
99
|
+
const mermaidInstance = mod?.default;
|
|
100
|
+
const hasMermaidAPI = mermaidInstance && (typeof mermaidInstance.render === "function" || typeof mermaidInstance.initialize === "function" || typeof mermaidInstance.run === "function");
|
|
101
|
+
mermaidAvailableCache = hasMermaidAPI;
|
|
102
|
+
if (!hasMermaidAPI) {
|
|
103
|
+
showMermaidHint();
|
|
104
|
+
}
|
|
105
|
+
return mermaidAvailableCache;
|
|
106
|
+
} catch {
|
|
107
|
+
mermaidAvailableCache = false;
|
|
108
|
+
showMermaidHint();
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
})();
|
|
112
|
+
return mermaidCheckPromise;
|
|
113
|
+
}
|
|
110
114
|
async function loadMermaid() {
|
|
111
115
|
if (typeof window === "undefined") return null;
|
|
112
116
|
if (!mermaidPromise) {
|
|
113
117
|
mermaidPromise = (async () => {
|
|
114
118
|
try {
|
|
115
119
|
const mod = await import("mermaid");
|
|
120
|
+
if (mod && mod.default === null) {
|
|
121
|
+
showMermaidHint();
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
116
124
|
return mod?.default;
|
|
117
125
|
} catch {
|
|
118
126
|
showMermaidHint();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"x-markdown.es9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需 Mermaid 图表渲染功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add mermaid',\r\n 'color: #9333ea; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"x-markdown.es9.js","sources":["../src/hooks/useMermaid.ts"],"sourcesContent":["import type { Ref } from 'vue'\r\nimport { throttle } from 'lodash-es'\r\nimport { computed, ref, watch, onUnmounted } from 'vue'\r\nimport type { MermaidZoomControls, UseMermaidZoomOptions, UseMermaidResult } from '../components/Mermaid/types'\r\n\r\nexport function downloadSvgAsPng(svg: string): void {\r\n if (!svg) return\r\n\r\n try {\r\n const svgDataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`\r\n const img = new Image()\r\n\r\n img.onload = () => {\r\n try {\r\n const canvas = document.createElement('canvas')\r\n const ctx = canvas.getContext('2d', { willReadFrequently: false })\r\n if (!ctx) return\r\n\r\n const scale = 2\r\n canvas.width = img.width * scale\r\n canvas.height = img.height * scale\r\n ctx.imageSmoothingEnabled = true\r\n ctx.imageSmoothingQuality = 'high'\r\n\r\n ctx.fillStyle = '#ffffff'\r\n ctx.fillRect(0, 0, canvas.width, canvas.height)\r\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height)\r\n\r\n const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-')\r\n\r\n try {\r\n canvas.toBlob(\r\n (blob) => {\r\n if (!blob) return\r\n const url = URL.createObjectURL(blob)\r\n const link = document.createElement('a')\r\n link.href = url\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n URL.revokeObjectURL(url)\r\n },\r\n 'image/png',\r\n 0.95,\r\n )\r\n } catch (toBlobError) {\r\n console.error('Failed to convert canvas to blob:', toBlobError)\r\n try {\r\n const dataUrl = canvas.toDataURL('image/png', 0.95)\r\n const link = document.createElement('a')\r\n link.href = dataUrl\r\n link.download = `mermaid-diagram-${timestamp}.png`\r\n document.body.appendChild(link)\r\n link.click()\r\n document.body.removeChild(link)\r\n } catch (dataUrlError) {\r\n console.error('Failed to convert canvas to data URL:', dataUrlError)\r\n }\r\n }\r\n } catch (canvasError) {\r\n console.error('Canvas operation failed:', canvasError)\r\n }\r\n }\r\n\r\n img.onerror = (error) => {\r\n console.error('Failed to load image:', error)\r\n }\r\n\r\n img.src = svgDataUrl\r\n } catch (error) {\r\n console.error('Failed to download SVG:', error)\r\n }\r\n}\r\n\r\ninterface UseMermaidOptions {\r\n id?: string\r\n theme?: 'default' | 'dark' | 'forest' | 'neutral' | string\r\n config?: any\r\n container?: HTMLElement | Ref<HTMLElement | null> | null\r\n}\r\n\r\ntype UseMermaidOptionsInput = UseMermaidOptions | Ref<UseMermaidOptions>\r\n\r\nlet mermaidPromise: Promise<any> | null = null\r\nlet hasShownMermaidHint = false\r\nlet mermaidAvailableCache: boolean | null = null\r\nlet mermaidCheckPromise: Promise<boolean | null> | null = null\r\n\r\n/**\r\n * 显示 mermaid 安装提示\r\n */\r\nconst showMermaidHint = () => {\r\n if (hasShownMermaidHint) return\r\n hasShownMermaidHint = true\r\n\r\n console.log(\r\n '%c[x-markdown]%c Mermaid 图表功能已降级为代码块显示',\r\n 'font-weight: bold; color: #9333ea;',\r\n 'color: #666;'\r\n )\r\n console.log(\r\n '%c如需 Mermaid 图表渲染功能,请安装:',\r\n 'color: #666; font-weight: bold;'\r\n )\r\n console.log(\r\n '%c pnpm add mermaid',\r\n 'color: #9333ea; font-family: monospace;'\r\n )\r\n console.log(\r\n '%c安装后请重启开发服务器',\r\n 'color: #999; font-size: 12px;'\r\n )\r\n}\r\n\r\n/**\r\n * 同步检查缓存状态(不触发检测)\r\n * @returns 缓存状态,null 表示未检测\r\n */\r\nexport function getMermaidAvailableCache(): boolean | null {\r\n return mermaidAvailableCache\r\n}\r\n\r\n/**\r\n * 检测 mermaid 是否可用(全局缓存,只检测一次)\r\n */\r\nexport async function checkMermaidAvailable(): Promise<boolean> {\r\n // 如果已经有缓存结果,直接返回\r\n if (mermaidAvailableCache !== null) {\r\n return mermaidAvailableCache\r\n }\r\n\r\n // 如果正在检测,返回检测 Promise\r\n if (mermaidCheckPromise) {\r\n return mermaidCheckPromise as Promise<boolean>\r\n }\r\n\r\n // 开始检测\r\n mermaidCheckPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查模块是否有实际的 mermaid 功能\r\n // 虚拟模块返回 { default: null },所以需要检查 default 是否存在且有效\r\n const mermaidInstance = (mod as any)?.default\r\n // 检查是否有 mermaid 的关键方法或属性\r\n const hasMermaidAPI = mermaidInstance && (\r\n typeof mermaidInstance.render === 'function' ||\r\n typeof mermaidInstance.initialize === 'function' ||\r\n typeof mermaidInstance.run === 'function'\r\n )\r\n mermaidAvailableCache = hasMermaidAPI\r\n\r\n // 当 mermaid 不可用时,显示提示\r\n if (!hasMermaidAPI) {\r\n showMermaidHint()\r\n }\r\n\r\n return mermaidAvailableCache\r\n } catch {\r\n // 静默失败,mermaid 不可用\r\n mermaidAvailableCache = false\r\n showMermaidHint()\r\n return false\r\n }\r\n })()\r\n\r\n return mermaidCheckPromise as Promise<boolean>\r\n}\r\n\r\nasync function loadMermaid() {\r\n if (typeof window === 'undefined') return null\r\n if (!mermaidPromise) {\r\n mermaidPromise = (async () => {\r\n try {\r\n const mod = await import('mermaid')\r\n // 检查是否是虚拟模块(虚拟模块返回 { default: null })\r\n if (mod && (mod as any).default === null) {\r\n showMermaidHint()\r\n return null\r\n }\r\n return (mod as any)?.default\r\n } catch {\r\n // 静默失败,显示友好提示\r\n showMermaidHint()\r\n return null\r\n }\r\n })()\r\n }\r\n return mermaidPromise\r\n}\r\n\r\ntype RenderTask = () => Promise<void>\r\nconst renderQueue: RenderTask[] = []\r\nlet isProcessingQueue = false\r\n\r\nasync function processRenderQueue() {\r\n if (isProcessingQueue) return\r\n isProcessingQueue = true\r\n\r\n while (renderQueue.length > 0) {\r\n const task = renderQueue.shift()\r\n if (task) {\r\n try {\r\n await task()\r\n } catch (err) {\r\n console.error('Mermaid render queue error:', err)\r\n }\r\n }\r\n }\r\n\r\n isProcessingQueue = false\r\n}\r\n\r\nfunction addToRenderQueue(task: RenderTask) {\r\n renderQueue.push(task)\r\n processRenderQueue()\r\n}\r\n\r\nexport function useMermaid(content: string | Ref<string>, options: UseMermaidOptionsInput = {}): UseMermaidResult {\r\n const optionsRef = computed(() => (typeof options === 'object' && 'value' in options ? options.value : options))\r\n const mermaidConfig = computed(() => ({\r\n suppressErrorRendering: true,\r\n startOnLoad: false,\r\n securityLevel: 'loose',\r\n theme: optionsRef.value.theme || 'default',\r\n ...(optionsRef.value.config || {}),\r\n }))\r\n const data = ref('')\r\n const error = ref<unknown>(null)\r\n const isLoading = ref(false)\r\n\r\n let isUnmounted = false\r\n\r\n const getRenderContainer = () => {\r\n const containerOption = optionsRef.value.container\r\n if (containerOption) {\r\n return typeof containerOption === 'object' && 'value' in containerOption ? containerOption.value : containerOption\r\n }\r\n return null\r\n }\r\n\r\n const throttledRender = throttle(\r\n () => {\r\n const contentValue = typeof content === 'string' ? content : content.value\r\n if (!contentValue?.trim()) {\r\n data.value = ''\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n isLoading.value = true\r\n\r\n addToRenderQueue(async () => {\r\n if (isUnmounted) return\r\n\r\n try {\r\n const mermaidInstance = await loadMermaid()\r\n if (!mermaidInstance) {\r\n data.value = contentValue\r\n error.value = null\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n mermaidInstance.initialize(mermaidConfig.value)\r\n\r\n const isValid = await mermaidInstance.parse(contentValue.trim())\r\n if (!isValid) {\r\n data.value = ''\r\n error.value = new Error('Mermaid parse error: Invalid syntax')\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const renderId = `${optionsRef.value.id || 'mermaid'}-${Math.random().toString(36).substring(2, 11)}`\r\n const container = getRenderContainer()\r\n if (!container) {\r\n isLoading.value = false\r\n return\r\n }\r\n\r\n const { svg } = await mermaidInstance.render(renderId, contentValue, container)\r\n data.value = svg\r\n error.value = null\r\n isLoading.value = false\r\n } catch (err) {\r\n // Mermaid render error\r\n data.value = ''\r\n error.value = err\r\n isLoading.value = false\r\n }\r\n })\r\n },\r\n 100,\r\n { leading: false, trailing: true },\r\n )\r\n\r\n watch(\r\n [() => (typeof content === 'string' ? content : content.value), () => mermaidConfig.value],\r\n () => {\r\n throttledRender()\r\n },\r\n { immediate: true },\r\n )\r\n\r\n onUnmounted(() => {\r\n isUnmounted = true\r\n })\r\n\r\n return {\r\n data,\r\n error,\r\n isLoading,\r\n }\r\n}\r\n\r\nexport function useMermaidZoom(options: UseMermaidZoomOptions): MermaidZoomControls {\r\n const { container } = options\r\n\r\n const scale = ref(1)\r\n const posX = ref(0)\r\n const posY = ref(0)\r\n const isDragging = ref(false)\r\n\r\n let removeEvents: (() => void) | null = null\r\n\r\n const getSvg = () => container.value?.querySelector('.syntax-mermaid__content svg') as HTMLElement\r\n\r\n const updateTransform = (svg: HTMLElement) => {\r\n svg.style.transformOrigin = 'center center'\r\n svg.style.transform = `translate(${posX.value}px, ${posY.value}px) scale(${scale.value})`\r\n }\r\n\r\n const resetState = () => {\r\n scale.value = 1\r\n posX.value = 0\r\n posY.value = 0\r\n isDragging.value = false\r\n }\r\n\r\n const addInteractionEvents = (containerEl: HTMLElement) => {\r\n let startX = 0\r\n let startY = 0\r\n let isInteractingWithMermaid = false\r\n\r\n const onStart = (clientX: number, clientY: number) => {\r\n isDragging.value = true\r\n startX = clientX - posX.value\r\n startY = clientY - posY.value\r\n document.body.style.userSelect = 'none'\r\n }\r\n\r\n const onMove = (clientX: number, clientY: number) => {\r\n if (isDragging.value && isInteractingWithMermaid) {\r\n posX.value = clientX - startX\r\n posY.value = clientY - startY\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n }\r\n\r\n const onEnd = () => {\r\n isDragging.value = false\r\n isInteractingWithMermaid = false\r\n document.body.style.userSelect = ''\r\n }\r\n\r\n const onMouseDown = (e: MouseEvent) => {\r\n if (e.button !== 0) return\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const onMouseMove = (e: MouseEvent) => {\r\n if (isInteractingWithMermaid) {\r\n onMove(e.clientX, e.clientY)\r\n }\r\n }\r\n\r\n const handleWheelZoom = (e: WheelEvent) => {\r\n const svg = getSvg()\r\n if (!svg) return\r\n\r\n const containerRect = containerEl.getBoundingClientRect()\r\n const svgRect = svg.getBoundingClientRect()\r\n\r\n const mouseX = e.clientX - containerRect.left\r\n const mouseY = e.clientY - containerRect.top\r\n\r\n const svgCenterX = svgRect.left - containerRect.left + svgRect.width / 2\r\n const svgCenterY = svgRect.top - containerRect.top + svgRect.height / 2\r\n\r\n const offsetX = (mouseX - svgCenterX - posX.value) / scale.value\r\n const offsetY = (mouseY - svgCenterY - posY.value) / scale.value\r\n\r\n const delta = e.deltaY > 0 ? -0.05 : 0.05\r\n const newScale = Math.min(Math.max(scale.value + delta, 0.1), 10)\r\n\r\n if (newScale === scale.value) return\r\n\r\n scale.value = newScale\r\n\r\n posX.value = mouseX - svgCenterX - offsetX * scale.value\r\n posY.value = mouseY - svgCenterY - offsetY * scale.value\r\n\r\n updateTransform(svg)\r\n }\r\n\r\n const throttledWheelZoom = throttle(handleWheelZoom, 20, { leading: true, trailing: true })\r\n\r\n const onWheel = (e: WheelEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n e.preventDefault()\r\n throttledWheelZoom(e)\r\n }\r\n }\r\n\r\n const onTouchStart = (e: TouchEvent) => {\r\n if (e.target === containerEl || containerEl.contains(e.target as Node)) {\r\n if (e.touches.length === 1) {\r\n e.preventDefault()\r\n isInteractingWithMermaid = true\r\n onStart(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n }\r\n\r\n const onTouchMove = (e: TouchEvent) => {\r\n if (isInteractingWithMermaid) {\r\n e.preventDefault()\r\n onMove(e.touches[0].clientX, e.touches[0].clientY)\r\n }\r\n }\r\n\r\n containerEl.addEventListener('mousedown', onMouseDown)\r\n document.addEventListener('mousemove', onMouseMove)\r\n document.addEventListener('mouseup', onEnd)\r\n containerEl.addEventListener('wheel', onWheel, { passive: false })\r\n containerEl.addEventListener('touchstart', onTouchStart, { passive: false })\r\n containerEl.addEventListener('touchmove', onTouchMove, { passive: false })\r\n document.addEventListener('touchend', onEnd)\r\n\r\n return () => {\r\n containerEl.removeEventListener('mousedown', onMouseDown)\r\n document.removeEventListener('mousemove', onMouseMove)\r\n document.removeEventListener('mouseup', onEnd)\r\n containerEl.removeEventListener('wheel', onWheel)\r\n containerEl.removeEventListener('touchstart', onTouchStart)\r\n containerEl.removeEventListener('touchmove', onTouchMove)\r\n document.removeEventListener('touchend', onEnd)\r\n document.body.style.userSelect = ''\r\n }\r\n }\r\n\r\n const zoomIn = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.min(scale.value + 0.2, 10)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const zoomOut = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n scale.value = Math.max(scale.value - 0.2, 0.1)\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const reset = () => {\r\n const svg = getSvg()\r\n if (svg) {\r\n resetState()\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const fullscreen = () => {\r\n if (!container.value) return\r\n\r\n if (document.fullscreenElement) {\r\n document.exitFullscreen()\r\n } else {\r\n container.value.requestFullscreen?.()\r\n }\r\n }\r\n\r\n const initialize = () => {\r\n if (!container.value) return\r\n\r\n resetState()\r\n\r\n removeEvents = addInteractionEvents(container.value)\r\n\r\n const svg = getSvg()\r\n if (svg) {\r\n updateTransform(svg)\r\n }\r\n }\r\n\r\n const destroy = () => {\r\n removeEvents?.()\r\n removeEvents = null\r\n resetState()\r\n }\r\n\r\n watch(\r\n () => container.value,\r\n () => {\r\n destroy()\r\n resetState()\r\n },\r\n )\r\n\r\n onUnmounted(destroy)\r\n\r\n return {\r\n zoomIn,\r\n zoomOut,\r\n reset,\r\n fullscreen,\r\n destroy,\r\n initialize,\r\n }\r\n}\r\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@hejiayue/x-markdown-test",
|
|
3
|
-
"version": "0.0.1-beta.
|
|
4
|
-
"description": "A markdown component library for Vue",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./dist/x-markdown.cjs.js",
|
|
7
|
-
"module": "./dist/x-markdown.es.js",
|
|
8
|
-
"types": "./dist/types/index.d.ts",
|
|
9
|
-
"exports": {
|
|
10
|
-
".": {
|
|
11
|
-
"import": {
|
|
12
|
-
"types": "./dist/types/index.d.ts",
|
|
13
|
-
"default": "./dist/x-markdown.es.js"
|
|
14
|
-
},
|
|
15
|
-
"require": {
|
|
16
|
-
"types": "./dist/types/index.d.ts",
|
|
17
|
-
"default": "./dist/x-markdown.cjs.js"
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
"./style": "./dist/style.css",
|
|
21
|
-
"./vite-plugin": "./src/vite-plugin.js"
|
|
22
|
-
},
|
|
23
|
-
"files": [
|
|
24
|
-
"dist",
|
|
25
|
-
"src/vite-plugin.js"
|
|
26
|
-
],
|
|
27
|
-
"sideEffects": [
|
|
28
|
-
"*.css",
|
|
29
|
-
"*.scss",
|
|
30
|
-
"*.vue"
|
|
31
|
-
],
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"rehype
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
},
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
"
|
|
76
|
-
"optional": true
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"@
|
|
87
|
-
"
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
"
|
|
96
|
-
"
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@hejiayue/x-markdown-test",
|
|
3
|
+
"version": "0.0.1-beta.142",
|
|
4
|
+
"description": "A markdown component library for Vue",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/x-markdown.cjs.js",
|
|
7
|
+
"module": "./dist/x-markdown.es.js",
|
|
8
|
+
"types": "./dist/types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/types/index.d.ts",
|
|
13
|
+
"default": "./dist/x-markdown.es.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/types/index.d.ts",
|
|
17
|
+
"default": "./dist/x-markdown.cjs.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"./style": "./dist/style.css",
|
|
21
|
+
"./vite-plugin": "./src/vite-plugin.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src/vite-plugin.js"
|
|
26
|
+
],
|
|
27
|
+
"sideEffects": [
|
|
28
|
+
"*.css",
|
|
29
|
+
"*.scss",
|
|
30
|
+
"*.vue"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"vue",
|
|
34
|
+
"markdown",
|
|
35
|
+
"component",
|
|
36
|
+
"vue3"
|
|
37
|
+
],
|
|
38
|
+
"author": "",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"deepmerge": "^4.3.1",
|
|
42
|
+
"dompurify": "^3.0.0",
|
|
43
|
+
"element-plus": "^2.5.0",
|
|
44
|
+
"hast": "^1.0.0",
|
|
45
|
+
"lodash-es": "^4.17.21",
|
|
46
|
+
"mdast": "^3.0.0",
|
|
47
|
+
"mdast-util-to-hast": "^13.2.1",
|
|
48
|
+
"property-information": "^7.1.0",
|
|
49
|
+
"rehype": "^13.0.0",
|
|
50
|
+
"rehype-katex": "^7.0.0",
|
|
51
|
+
"rehype-raw": "^7.0.0",
|
|
52
|
+
"rehype-sanitize": "^6.0.0",
|
|
53
|
+
"remark": "^15.0.0",
|
|
54
|
+
"remark-breaks": "^4.0.0",
|
|
55
|
+
"remark-gfm": "^4.0.0",
|
|
56
|
+
"remark-math": "^6.0.0",
|
|
57
|
+
"remark-parse": "^11.0.0",
|
|
58
|
+
"remark-rehype": "^11.0.0",
|
|
59
|
+
"unified": "^11.0.0",
|
|
60
|
+
"unist-util-visit": "^5.0.0"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"vue": "^3.3.0",
|
|
64
|
+
"shiki": "^1.0.0 || ^3.0.0",
|
|
65
|
+
"shiki-stream": "^0.1.4",
|
|
66
|
+
"mermaid": "^10.0.0 || ^11.0.0"
|
|
67
|
+
},
|
|
68
|
+
"peerDependenciesMeta": {
|
|
69
|
+
"shiki": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"shiki-stream": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"mermaid": {
|
|
76
|
+
"optional": true
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"devDependencies": {
|
|
80
|
+
"@types/dompurify": "^3.0.0",
|
|
81
|
+
"@types/hast": "^3.0.4",
|
|
82
|
+
"@types/lodash-es": "^4.17.12",
|
|
83
|
+
"@types/mdast": "^4.0.4",
|
|
84
|
+
"@types/node": "^20.0.0",
|
|
85
|
+
"@vitejs/plugin-vue": "^5.0.0",
|
|
86
|
+
"@vueuse/core": "^14.1.0",
|
|
87
|
+
"sass": "^1.70.0",
|
|
88
|
+
"terser": "^5.44.1",
|
|
89
|
+
"typescript": "^5.3.0",
|
|
90
|
+
"vite": "^5.0.0",
|
|
91
|
+
"vue": "^3.3.0",
|
|
92
|
+
"vue-tsc": "^2.0.0"
|
|
93
|
+
},
|
|
94
|
+
"scripts": {
|
|
95
|
+
"dev": "vite",
|
|
96
|
+
"build": "vite build && tsc --project tsconfig.build.json",
|
|
97
|
+
"lint": "eslint src --ext .ts,.tsx,.vue",
|
|
98
|
+
"clean": "rm -rf dist node_modules"
|
|
99
|
+
}
|
|
100
|
+
}
|