@incremark/svelte 0.2.7 → 0.3.1

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.
Files changed (48) hide show
  1. package/README.en.md +274 -0
  2. package/dist/ThemeProvider.svelte +4 -1
  3. package/dist/ThemeProvider.svelte.d.ts.map +1 -1
  4. package/dist/components/ConfigProvider.svelte +18 -0
  5. package/dist/components/ConfigProvider.svelte.d.ts +9 -0
  6. package/dist/components/ConfigProvider.svelte.d.ts.map +1 -0
  7. package/dist/components/Incremark.svelte +62 -71
  8. package/dist/components/Incremark.svelte.d.ts +4 -6
  9. package/dist/components/Incremark.svelte.d.ts.map +1 -1
  10. package/dist/components/IncremarkCode.svelte +20 -152
  11. package/dist/components/IncremarkCode.svelte.d.ts +4 -1
  12. package/dist/components/IncremarkCode.svelte.d.ts.map +1 -1
  13. package/dist/components/IncremarkCodeDefault.svelte +115 -0
  14. package/dist/components/IncremarkCodeDefault.svelte.d.ts +18 -0
  15. package/dist/components/IncremarkCodeDefault.svelte.d.ts.map +1 -0
  16. package/dist/components/IncremarkCodeMermaid.svelte +148 -0
  17. package/dist/components/IncremarkCodeMermaid.svelte.d.ts +14 -0
  18. package/dist/components/IncremarkCodeMermaid.svelte.d.ts.map +1 -0
  19. package/dist/components/IncremarkContent.svelte +41 -10
  20. package/dist/components/IncremarkContent.svelte.d.ts +1 -2
  21. package/dist/components/IncremarkContent.svelte.d.ts.map +1 -1
  22. package/dist/components/IncremarkFootnotes.svelte +4 -8
  23. package/dist/components/IncremarkFootnotes.svelte.d.ts.map +1 -1
  24. package/dist/components/IncremarkInline.svelte +3 -3
  25. package/dist/components/IncremarkRenderer.svelte +4 -0
  26. package/dist/components/IncremarkRenderer.svelte.d.ts +2 -0
  27. package/dist/components/IncremarkRenderer.svelte.d.ts.map +1 -1
  28. package/dist/components/IncremarkTable.svelte +6 -9
  29. package/dist/components/IncremarkTable.svelte.d.ts.map +1 -1
  30. package/dist/components/SvgIcon.svelte +25 -0
  31. package/dist/components/SvgIcon.svelte.d.ts +12 -0
  32. package/dist/components/SvgIcon.svelte.d.ts.map +1 -0
  33. package/dist/index.d.ts +7 -1
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +6 -1
  36. package/dist/stores/{useDevTools.d.ts → useDevTools.svelte.d.ts} +1 -1
  37. package/dist/stores/useDevTools.svelte.d.ts.map +1 -0
  38. package/dist/stores/{useDevTools.js → useDevTools.svelte.js} +8 -7
  39. package/dist/stores/useIncremark.d.ts +4 -2
  40. package/dist/stores/useIncremark.d.ts.map +1 -1
  41. package/dist/stores/useIncremark.js +19 -0
  42. package/dist/stores/useLocale.svelte.d.ts +29 -0
  43. package/dist/stores/useLocale.svelte.d.ts.map +1 -0
  44. package/dist/stores/useLocale.svelte.js +32 -0
  45. package/dist/stores/useShiki.svelte.d.ts.map +1 -1
  46. package/dist/stores/useShiki.svelte.js +17 -5
  47. package/package.json +13 -7
  48. package/dist/stores/useDevTools.d.ts.map +0 -1
@@ -1,6 +1,8 @@
1
1
  <script lang="ts">
2
2
  import type { Code } from 'mdast'
3
- import { useShiki } from '../stores/useShiki.svelte'
3
+ import type { Component } from 'svelte'
4
+ import IncremarkCodeMermaid from './IncremarkCodeMermaid.svelte'
5
+ import IncremarkCodeDefault from './IncremarkCodeDefault.svelte'
4
6
 
5
7
  /**
6
8
  * 组件 Props
@@ -22,6 +24,8 @@
22
24
  codeBlockConfigs?: Record<string, { takeOver?: boolean }>
23
25
  /** 块状态,用于判断是否使用自定义组件 */
24
26
  blockStatus?: 'pending' | 'stable' | 'completed'
27
+ /** 默认代码块渲染组件(当不是 mermaid 且没有自定义组件时使用) */
28
+ defaultCodeComponent?: Component<any>
25
29
  }
26
30
 
27
31
  let {
@@ -32,25 +36,10 @@
32
36
  mermaidDelay = 500,
33
37
  customCodeBlocks,
34
38
  codeBlockConfigs,
35
- blockStatus = 'completed'
39
+ blockStatus = 'completed',
40
+ defaultCodeComponent
36
41
  }: Props = $props()
37
42
 
38
- // 状态
39
- let copied = $state(false)
40
- let highlightedHtml = $state('')
41
-
42
- // Mermaid 支持
43
- let mermaidSvg = $state('')
44
- let mermaidError = $state('')
45
- let mermaidLoading = $state(false)
46
- let mermaidRef: any = null
47
- let mermaidTimer: ReturnType<typeof setTimeout> | null = null
48
- // 视图模式:'preview' | 'source'
49
- let mermaidViewMode = $state<'preview' | 'source'>('preview')
50
-
51
- // 使用 Shiki 单例管理器 - 修复:传入 getter 闭包以保持响应式
52
- const shiki = useShiki(() => theme)
53
-
54
43
  /**
55
44
  * 计算属性
56
45
  */
@@ -71,90 +60,8 @@
71
60
  return component
72
61
  })
73
62
 
74
- /**
75
- * 切换 Mermaid 视图模式
76
- */
77
- function toggleMermaidView() {
78
- mermaidViewMode = mermaidViewMode === 'preview' ? 'source' : 'preview'
79
- }
80
-
81
- /**
82
- * 执行 Mermaid 渲染
83
- */
84
- async function doRenderMermaid() {
85
- if (!code) return
86
- mermaidError = ''
87
-
88
- try {
89
- if (!mermaidRef) {
90
- // @ts-ignore
91
- const mermaidModule = await import('mermaid')
92
- mermaidRef = mermaidModule.default
93
- mermaidRef.initialize({
94
- startOnLoad: false,
95
- theme: 'dark',
96
- securityLevel: 'loose',
97
- suppressErrorRendering: true
98
- })
99
- }
100
-
101
- const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
102
- const { svg } = await mermaidRef.render(id, code)
103
- mermaidSvg = svg
104
- } catch (e: any) {
105
- mermaidError = ''
106
- mermaidSvg = ''
107
- } finally {
108
- mermaidLoading = false
109
- }
110
- }
111
-
112
- /**
113
- * 动态加载 shiki 并高亮
114
- */
115
- async function doHighlight() {
116
- if (isMermaid) {
117
- // Mermaid 防抖逻辑
118
- if (mermaidTimer) clearTimeout(mermaidTimer)
119
- mermaidLoading = true
120
- mermaidTimer = setTimeout(doRenderMermaid, mermaidDelay)
121
- return
122
- }
123
-
124
- if (!code || disableHighlight) {
125
- highlightedHtml = ''
126
- return
127
- }
128
-
129
- try {
130
- // 调用经过修复的 shiki.highlight
131
- const html = await shiki.highlight(code, language, fallbackTheme)
132
- highlightedHtml = html
133
- } catch (e) {
134
- highlightedHtml = ''
135
- }
136
- }
137
-
138
- /**
139
- * 复制代码
140
- */
141
- async function copyCode() {
142
- try {
143
- await navigator.clipboard.writeText(code)
144
- copied = true
145
- setTimeout(() => { copied = false }, 2000)
146
- } catch { /* 静默处理 */ }
147
- }
148
-
149
- // 监听代码、主题、语言变化并重新渲染
150
- $effect(() => {
151
- doHighlight()
152
-
153
- // 返回清理函数取代 onDestroy
154
- return () => {
155
- if (mermaidTimer) clearTimeout(mermaidTimer)
156
- }
157
- })
63
+ // 默认代码块组件
64
+ const DefaultCodeBlock = $derived(defaultCodeComponent || IncremarkCodeDefault)
158
65
  </script>
159
66
 
160
67
  {#if CustomCodeBlock}
@@ -166,55 +73,16 @@
166
73
  takeOver={codeBlockConfigs?.[language]?.takeOver}
167
74
  />
168
75
  {:else if isMermaid}
169
- <div class="incremark-mermaid">
170
- <div class="mermaid-header">
171
- <span class="language">MERMAID</span>
172
- <div class="mermaid-actions">
173
- <button
174
- class="code-btn"
175
- onclick={toggleMermaidView}
176
- type="button"
177
- disabled={!mermaidSvg}
178
- >
179
- {mermaidViewMode === 'preview' ? '源码' : '预览'}
180
- </button>
181
- <button class="code-btn" onclick={copyCode} type="button">
182
- {copied ? '✓ 已复制' : '复制'}
183
- </button>
184
- </div>
185
- </div>
186
- <div class="mermaid-content">
187
- {#if mermaidLoading && !mermaidSvg}
188
- <pre class="mermaid-source-code">{code}</pre>
189
- {:else if mermaidViewMode === 'source'}
190
- <pre class="mermaid-source-code">{code}</pre>
191
- {:else if mermaidSvg}
192
- {@html mermaidSvg}
193
- {:else}
194
- <pre class="mermaid-source-code">{code}</pre>
195
- {/if}
196
- </div>
197
- </div>
76
+ <IncremarkCodeMermaid
77
+ {node}
78
+ {mermaidDelay}
79
+ />
198
80
  {:else}
199
- <div class="incremark-code">
200
- <div class="code-header">
201
- <span class="language">{language}</span>
202
- <button class="code-btn" onclick={copyCode} type="button">
203
- {copied ? '✓ 已复制' : '复制'}
204
- </button>
205
- </div>
206
- <div class="code-content">
207
- {#if shiki.isHighlighting && !highlightedHtml}
208
- <div class="code-loading">
209
- <pre><code>{code}</code></pre>
210
- </div>
211
- {:else if highlightedHtml}
212
- <div class="shiki-wrapper">
213
- {@html highlightedHtml}
214
- </div>
215
- {:else}
216
- <pre class="code-fallback"><code>{code}</code></pre>
217
- {/if}
218
- </div>
219
- </div>
81
+ {@const Component = DefaultCodeBlock}
82
+ <Component
83
+ {node}
84
+ {theme}
85
+ {fallbackTheme}
86
+ {disableHighlight}
87
+ />
220
88
  {/if}
@@ -1,4 +1,5 @@
1
1
  import type { Code } from 'mdast';
2
+ import type { Component } from 'svelte';
2
3
  /**
3
4
  * 组件 Props
4
5
  */
@@ -21,8 +22,10 @@ interface Props {
21
22
  }>;
22
23
  /** 块状态,用于判断是否使用自定义组件 */
23
24
  blockStatus?: 'pending' | 'stable' | 'completed';
25
+ /** 默认代码块渲染组件(当不是 mermaid 且没有自定义组件时使用) */
26
+ defaultCodeComponent?: Component<any>;
24
27
  }
25
- declare const IncremarkCode: import("svelte").Component<Props, {}, "">;
28
+ declare const IncremarkCode: Component<Props, {}, "">;
26
29
  type IncremarkCode = ReturnType<typeof IncremarkCode>;
27
30
  export default IncremarkCode;
28
31
  //# sourceMappingURL=IncremarkCode.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAI/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AAiMH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AACjC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAKrC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;IAChD,yCAAyC;IACzC,oBAAoB,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;CACtC;AAwDH,QAAA,MAAM,aAAa,0BAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,115 @@
1
+ <script lang="ts">
2
+ import type { Code } from 'mdast'
3
+ import { onDestroy } from 'svelte'
4
+ import { LucideCopy, LucideCopyCheck } from '@incremark/icons'
5
+ import { isClipboardAvailable } from '@incremark/shared'
6
+ import SvgIcon from './SvgIcon.svelte'
7
+ import { useShiki } from '../stores/useShiki.svelte'
8
+ import { useLocale } from '../stores/useLocale.svelte'
9
+
10
+ /**
11
+ * 组件 Props
12
+ */
13
+ interface Props {
14
+ /** 代码节点 */
15
+ node: Code
16
+ /** Shiki 主题,默认 github-dark */
17
+ theme?: string
18
+ /** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
19
+ fallbackTheme?: string
20
+ /** 是否禁用代码高亮 */
21
+ disableHighlight?: boolean
22
+ }
23
+
24
+ let {
25
+ node,
26
+ theme = 'github-dark',
27
+ fallbackTheme = 'github-dark',
28
+ disableHighlight = false
29
+ }: Props = $props()
30
+
31
+ // 状态
32
+ let copied = $state(false)
33
+ let highlightedHtml = $state('')
34
+
35
+ // 使用 Shiki 单例管理器
36
+ const shiki = useShiki(() => theme)
37
+
38
+ // 使用 i18n
39
+ const { t } = useLocale()
40
+
41
+ // 计算属性
42
+ const language = $derived(node.lang || 'text')
43
+ const code = $derived(node.value)
44
+
45
+ /**
46
+ * 动态加载 shiki 并高亮
47
+ */
48
+ async function doHighlight() {
49
+ if (!code || disableHighlight) {
50
+ highlightedHtml = ''
51
+ return
52
+ }
53
+
54
+ try {
55
+ const html = await shiki.highlight(code, language, fallbackTheme)
56
+ highlightedHtml = html
57
+ } catch (e) {
58
+ highlightedHtml = ''
59
+ }
60
+ }
61
+
62
+ let copyTimeoutId: ReturnType<typeof setTimeout> | null = null
63
+
64
+ /**
65
+ * 复制代码
66
+ */
67
+ async function copyCode() {
68
+ if (!isClipboardAvailable()) return
69
+
70
+ try {
71
+ await navigator.clipboard.writeText(code)
72
+ copied = true
73
+
74
+ if (copyTimeoutId) clearTimeout(copyTimeoutId)
75
+ copyTimeoutId = setTimeout(() => { copied = false }, 2000)
76
+ } catch { /* 静默处理 */ }
77
+ }
78
+
79
+ onDestroy(() => {
80
+ if (copyTimeoutId) clearTimeout(copyTimeoutId)
81
+ })
82
+
83
+ // 监听代码、主题、语言变化并重新渲染
84
+ $effect(() => {
85
+ doHighlight()
86
+ })
87
+ </script>
88
+
89
+ <div class="incremark-code">
90
+ <div class="code-header">
91
+ <span class="language">{language}</span>
92
+ <button
93
+ class="code-btn"
94
+ onclick={copyCode}
95
+ type="button"
96
+ aria-label={copied ? t('code.copied') : t('code.copy')}
97
+ title={copied ? 'Copied!' : 'Copy'}
98
+ >
99
+ <SvgIcon svg={copied ? LucideCopyCheck : LucideCopy} />
100
+ </button>
101
+ </div>
102
+ <div class="code-content">
103
+ {#if shiki.isHighlighting && !highlightedHtml}
104
+ <div class="code-loading">
105
+ <pre><code>{code}</code></pre>
106
+ </div>
107
+ {:else if highlightedHtml}
108
+ <div class="shiki-wrapper">
109
+ {@html highlightedHtml}
110
+ </div>
111
+ {:else}
112
+ <pre class="code-fallback"><code>{code}</code></pre>
113
+ {/if}
114
+ </div>
115
+ </div>
@@ -0,0 +1,18 @@
1
+ import type { Code } from 'mdast';
2
+ /**
3
+ * 组件 Props
4
+ */
5
+ interface Props {
6
+ /** 代码节点 */
7
+ node: Code;
8
+ /** Shiki 主题,默认 github-dark */
9
+ theme?: string;
10
+ /** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
11
+ fallbackTheme?: string;
12
+ /** 是否禁用代码高亮 */
13
+ disableHighlight?: boolean;
14
+ }
15
+ declare const IncremarkCodeDefault: import("svelte").Component<Props, {}, "">;
16
+ type IncremarkCodeDefault = ReturnType<typeof IncremarkCodeDefault>;
17
+ export default IncremarkCodeDefault;
18
+ //# sourceMappingURL=IncremarkCodeDefault.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IncremarkCodeDefault.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCodeDefault.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAS/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAoGH,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -0,0 +1,148 @@
1
+ <script lang="ts">
2
+ import type { Code } from 'mdast'
3
+ import { onDestroy } from 'svelte'
4
+ import { GravityMermaid, LucideCode, LucideEye, LucideCopy, LucideCopyCheck } from '@incremark/icons'
5
+ import { isClipboardAvailable } from '@incremark/shared'
6
+ import SvgIcon from './SvgIcon.svelte'
7
+ import { useLocale } from '../stores/useLocale.svelte'
8
+
9
+ /**
10
+ * 组件 Props
11
+ */
12
+ interface Props {
13
+ /** 代码节点 */
14
+ node: Code
15
+ /** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
16
+ mermaidDelay?: number
17
+ }
18
+
19
+ let {
20
+ node,
21
+ mermaidDelay = 500
22
+ }: Props = $props()
23
+
24
+ // 状态
25
+ let copied = $state(false)
26
+ let mermaidSvg = $state('')
27
+ let mermaidLoading = $state(false)
28
+ let mermaidRef: any = null
29
+ let mermaidTimer: ReturnType<typeof setTimeout> | null = null
30
+ let mermaidViewMode = $state<'preview' | 'source'>('preview')
31
+
32
+ // 计算属性
33
+ const code = $derived(node.value)
34
+
35
+ // 使用 i18n
36
+ const { t } = useLocale()
37
+
38
+ /**
39
+ * 切换 Mermaid 视图模式
40
+ */
41
+ function toggleMermaidView() {
42
+ mermaidViewMode = mermaidViewMode === 'preview' ? 'source' : 'preview'
43
+ }
44
+
45
+ /**
46
+ * 执行 Mermaid 渲染
47
+ */
48
+ async function doRenderMermaid() {
49
+ if (!code) return
50
+
51
+ try {
52
+ if (!mermaidRef) {
53
+ // @ts-ignore
54
+ const mermaidModule = await import('mermaid')
55
+ mermaidRef = mermaidModule.default
56
+ mermaidRef.initialize({
57
+ startOnLoad: false,
58
+ theme: 'dark',
59
+ securityLevel: 'loose',
60
+ suppressErrorRendering: true
61
+ })
62
+ }
63
+
64
+ const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
65
+ const { svg } = await mermaidRef.render(id, code)
66
+ mermaidSvg = svg
67
+ } catch (e: any) {
68
+ mermaidSvg = ''
69
+ } finally {
70
+ mermaidLoading = false
71
+ }
72
+ }
73
+
74
+ let copyTimeoutId: ReturnType<typeof setTimeout> | null = null
75
+
76
+ /**
77
+ * 复制代码
78
+ */
79
+ async function copyCode() {
80
+ if (!isClipboardAvailable()) return
81
+
82
+ try {
83
+ await navigator.clipboard.writeText(code)
84
+ copied = true
85
+
86
+ if (copyTimeoutId) clearTimeout(copyTimeoutId)
87
+ copyTimeoutId = setTimeout(() => { copied = false }, 2000)
88
+ } catch { /* 静默处理 */ }
89
+ }
90
+
91
+ // 监听代码变化并重新渲染
92
+ $effect(() => {
93
+ // Mermaid 防抖逻辑
94
+ if (mermaidTimer) clearTimeout(mermaidTimer)
95
+ mermaidLoading = true
96
+ mermaidTimer = setTimeout(doRenderMermaid, mermaidDelay)
97
+
98
+ // 返回清理函数
99
+ return () => {
100
+ if (mermaidTimer) clearTimeout(mermaidTimer)
101
+ }
102
+ })
103
+
104
+ onDestroy(() => {
105
+ if (copyTimeoutId) clearTimeout(copyTimeoutId)
106
+ })
107
+ </script>
108
+
109
+ <div class="incremark-mermaid">
110
+ <div class="mermaid-header">
111
+ <span class="language">
112
+ <SvgIcon svg={GravityMermaid} class="language-icon" />
113
+ MERMAID
114
+ </span>
115
+ <div class="mermaid-actions">
116
+ <button
117
+ class="code-btn"
118
+ onclick={toggleMermaidView}
119
+ type="button"
120
+ disabled={!mermaidSvg}
121
+ aria-label={mermaidViewMode === 'preview' ? t('mermaid.viewSource') : t('mermaid.preview')}
122
+ title={mermaidViewMode === 'preview' ? 'View Source' : 'Preview'}
123
+ >
124
+ <SvgIcon svg={mermaidViewMode === 'preview' ? LucideCode : LucideEye} />
125
+ </button>
126
+ <button
127
+ class="code-btn"
128
+ onclick={copyCode}
129
+ type="button"
130
+ aria-label={copied ? t('mermaid.copied') : t('mermaid.copy')}
131
+ title={copied ? 'Copied!' : 'Copy'}
132
+ >
133
+ <SvgIcon svg={copied ? LucideCopyCheck : LucideCopy} />
134
+ </button>
135
+ </div>
136
+ </div>
137
+ <div class="mermaid-content">
138
+ {#if mermaidLoading && !mermaidSvg}
139
+ <pre class="mermaid-source-code">{code}</pre>
140
+ {:else if mermaidViewMode === 'source'}
141
+ <pre class="mermaid-source-code">{code}</pre>
142
+ {:else if mermaidSvg}
143
+ {@html mermaidSvg}
144
+ {:else}
145
+ <pre class="mermaid-source-code">{code}</pre>
146
+ {/if}
147
+ </div>
148
+ </div>
@@ -0,0 +1,14 @@
1
+ import type { Code } from 'mdast';
2
+ /**
3
+ * 组件 Props
4
+ */
5
+ interface Props {
6
+ /** 代码节点 */
7
+ node: Code;
8
+ /** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
9
+ mermaidDelay?: number;
10
+ }
11
+ declare const IncremarkCodeMermaid: import("svelte").Component<Props, {}, "">;
12
+ type IncremarkCodeMermaid = ReturnType<typeof IncremarkCodeMermaid>;
13
+ export default IncremarkCodeMermaid;
14
+ //# sourceMappingURL=IncremarkCodeMermaid.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"IncremarkCodeMermaid.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCodeMermaid.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAQ/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAkIH,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -4,11 +4,11 @@
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
- import type { Component } from 'svelte'
8
7
  import { useIncremark, type UseIncremarkOptions } from '../stores/useIncremark'
9
- import type { ComponentMap, IncremarkContentProps } from './types'
8
+ import type { IncremarkContentProps } from './types'
10
9
  import Incremark from './Incremark.svelte'
11
10
  import { get } from 'svelte/store'
11
+ import { untrack } from 'svelte'
12
12
 
13
13
  let {
14
14
  stream,
@@ -23,16 +23,47 @@
23
23
  showBlockStatus = false
24
24
  }: IncremarkContentProps = $props()
25
25
 
26
- // 合并默认选项(初始化时使用)
27
- const initialOptions: UseIncremarkOptions = {
28
- gfm: true,
29
- htmlTree: true,
30
- containers: true,
31
- math: true,
32
- ...incremarkOptions
26
+ // 创建 incremark 实例(只创建一次)
27
+ const incremarkInstance = untrack(() => useIncremark({
28
+ gfm: incremarkOptions?.gfm ?? true,
29
+ htmlTree: incremarkOptions?.htmlTree ?? true,
30
+ containers: incremarkOptions?.containers ?? true,
31
+ math: incremarkOptions?.math ?? true,
32
+ astBuilder: incremarkOptions?.astBuilder,
33
+ typewriter: incremarkOptions?.typewriter
34
+ }))
35
+
36
+ // 解构出需要的方法和状态
37
+ const { blocks, append, finalize, render, reset, updateOptions, isDisplayComplete, markdown, typewriter } = incremarkInstance
38
+
39
+ // 计算 parser 配置的稳定 key(用于检测变化)
40
+ // astBuilder 用名称标识,因为它是类不能 JSON.stringify
41
+ function getConfigKey() {
42
+ const { typewriter: _, astBuilder, ...parserOptions } = incremarkOptions ?? {}
43
+ return JSON.stringify(parserOptions) + '|' + (astBuilder?.name ?? 'default')
33
44
  }
34
45
 
35
- const { blocks, append, finalize, render, reset, isDisplayComplete, markdown, typewriter } = useIncremark(initialOptions)
46
+ // 监听 parser 配置变化,使用 updateOptions 动态更新(不重建实例)
47
+ let prevConfigKey = getConfigKey()
48
+ let isFirstRender = true
49
+ $effect(() => {
50
+ const currentConfigKey = getConfigKey()
51
+ if (isFirstRender) {
52
+ isFirstRender = false
53
+ return
54
+ }
55
+ if (prevConfigKey !== currentConfigKey) {
56
+ prevConfigKey = currentConfigKey
57
+ // 使用 updateOptions 动态更新配置(包括引擎切换)
58
+ updateOptions({
59
+ gfm: incremarkOptions?.gfm ?? true,
60
+ htmlTree: incremarkOptions?.htmlTree ?? true,
61
+ containers: incremarkOptions?.containers ?? true,
62
+ math: incremarkOptions?.math ?? true,
63
+ astBuilder: incremarkOptions?.astBuilder
64
+ })
65
+ }
66
+ })
36
67
 
37
68
  // 监听 incremarkOptions 的变化,更新 typewriter 配置
38
69
  $effect(() => {
@@ -1,6 +1,5 @@
1
- import type { Component } from 'svelte';
2
1
  import type { IncremarkContentProps } from './types';
3
- declare const IncremarkContent: Component<IncremarkContentProps, {}, "">;
2
+ declare const IncremarkContent: import("svelte").Component<IncremarkContentProps, {}, "">;
4
3
  type IncremarkContent = ReturnType<typeof IncremarkContent>;
5
4
  export default IncremarkContent;
6
5
  //# sourceMappingURL=IncremarkContent.svelte.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkContent.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkContent.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAEvC,OAAO,KAAK,EAAgB,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA6GlE,QAAA,MAAM,gBAAgB,0CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"IncremarkContent.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkContent.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA6IpD,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -4,26 +4,22 @@
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
- import type { Readable } from 'svelte/store'
8
- import type { FootnoteDefinition, RootContent } from 'mdast'
7
+ import type { RootContent } from 'mdast'
9
8
  import { getDefinitionsContext } from '../context/definitionsContext'
10
9
  import IncremarkRenderer from './IncremarkRenderer.svelte'
11
10
 
12
-
13
11
  const context = getDefinitionsContext()
14
- const footnoteDefinitions = $derived(context?.footnoteDefinitions ?? {});
15
- const footnoteReferenceOrder = $derived(context?.footnoteReferenceOrder ?? []);
12
+ // 解构 store 以便使用 $ 语法订阅
13
+ const { footnoteDefinitions, footnoteReferenceOrder } = context
16
14
 
17
15
  /**
18
16
  * 按引用顺序排列的脚注列表
19
17
  * 只显示已有定义的脚注
20
18
  */
21
19
  const orderedFootnotes = $derived.by(() => {
22
- if (!footnoteReferenceOrder || !footnoteDefinitions) {
23
- return []
24
- }
25
20
  const order = $footnoteReferenceOrder
26
21
  const definitions = $footnoteDefinitions
22
+
27
23
  return order
28
24
  .map(identifier => ({
29
25
  identifier,
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkFootnotes.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkFootnotes.svelte.ts"],"names":[],"mappings":"AA4EA,QAAA,MAAM,kBAAkB,2DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"IncremarkFootnotes.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkFootnotes.svelte.ts"],"names":[],"mappings":"AAuEA,QAAA,MAAM,kBAAkB,2DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -69,10 +69,10 @@
69
69
  return (node as unknown as MathNode).type === 'inlineMath'
70
70
  }
71
71
 
72
- // 获取 definitions context(可能不存在)
73
- // 使用 $derived 来确保响应式
72
+ // 获取 definitions context
74
73
  const context = getDefinitionsContext()
75
- const definations = $derived(context?.definations ?? {});
74
+ // 解构 store 以便使用 $ 语法订阅
75
+ const { definations } = context
76
76
 
77
77
 
78
78
  /**