@incremark/svelte 0.2.6 → 0.2.7

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.
@@ -4,7 +4,7 @@
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
- import { onMount, onDestroy } from 'svelte'
7
+ import { onMount, onDestroy, tick } from 'svelte'
8
8
  import type { HTMLAttributes } from 'svelte/elements';
9
9
 
10
10
  /**
@@ -117,24 +117,24 @@
117
117
  lastScrollTop = containerRef.scrollTop
118
118
  lastScrollHeight = containerRef.scrollHeight
119
119
 
120
- observer = new MutationObserver(() => {
120
+ observer = new MutationObserver(async () => {
121
121
  // 使用 tick 等待 DOM 更新
122
- setTimeout(() => {
123
- if (!containerRef) return
124
-
125
- // 如果没有滚动条,重置状态
126
- if (!hasScrollbar()) {
127
- isUserScrolledUp = false
128
- }
129
-
130
- // 更新 scrollHeight 记录(内容变化后)
131
- lastScrollHeight = containerRef.scrollHeight
132
-
133
- // 自动滚动
134
- if (enabled && !isUserScrolledUp) {
135
- scrollToBottom()
136
- }
137
- }, 0)
122
+ await tick()
123
+
124
+ if (!containerRef) return
125
+
126
+ // 如果没有滚动条,重置状态
127
+ if (!hasScrollbar()) {
128
+ isUserScrolledUp = false
129
+ }
130
+
131
+ // 更新 scrollHeight 记录(内容变化后)
132
+ lastScrollHeight = containerRef.scrollHeight
133
+
134
+ // 自动滚动
135
+ if (enabled && !isUserScrolledUp) {
136
+ scrollToBottom()
137
+ }
138
138
  })
139
139
 
140
140
  observer.observe(containerRef, {
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { getDefinitionsContext } from '../context/definitionsContext'
13
13
  import type { UseIncremarkReturn } from '../stores/useIncremark'
14
- import type { ComponentMap, BlockWithStableId } from './types'
14
+ import type { ComponentMap, RenderableBlock } from './types'
15
15
 
16
16
  // 导入组件
17
17
  import IncremarkFootnotes from './IncremarkFootnotes.svelte'
@@ -29,7 +29,7 @@
29
29
  */
30
30
  interface Props {
31
31
  /** 要渲染的块列表(来自 useIncremark 的 blocks) */
32
- blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>
32
+ blocks?: RenderableBlock[] | Readable<RenderableBlock[]>
33
33
  /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
34
34
  * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
35
35
  isDisplayComplete?: boolean
@@ -80,10 +80,10 @@
80
80
  <!-- 主要内容块 -->
81
81
  {#if incremark}
82
82
  <!-- 使用 incremark 的 blocks store -->
83
- {#each $incremarkBlocks as block (block.stableId)}
83
+ {#each $incremarkBlocks as block (block.id)}
84
84
  {#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
85
85
  <div
86
- class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as BlockWithStableId).isLastPending ? 'incremark-last-pending' : ''}"
86
+ class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as RenderableBlock).isLastPending ? 'incremark-last-pending' : ''}"
87
87
  >
88
88
  <!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
89
89
  <IncremarkRenderer
@@ -98,10 +98,10 @@
98
98
  {/each}
99
99
  {:else}
100
100
  <!-- 使用传入的 blocks 数组 -->
101
- {#each (Array.isArray(blocks) ? blocks : []) as block (block.stableId)}
101
+ {#each (Array.isArray(blocks) ? blocks : []) as block (block.id)}
102
102
  {#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
103
103
  <div
104
- class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {block.isLastPending ? 'incremark-last-pending' : ''}"
104
+ class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as RenderableBlock).isLastPending ? 'incremark-last-pending' : ''}"
105
105
  >
106
106
  <!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
107
107
  <IncremarkRenderer
@@ -1,13 +1,13 @@
1
1
  import type { Component } from 'svelte';
2
2
  import type { Readable } from 'svelte/store';
3
3
  import type { UseIncremarkReturn } from '../stores/useIncremark';
4
- import type { ComponentMap, BlockWithStableId } from './types';
4
+ import type { ComponentMap, RenderableBlock } from './types';
5
5
  /**
6
6
  * 组件 Props
7
7
  */
8
8
  interface Props {
9
9
  /** 要渲染的块列表(来自 useIncremark 的 blocks) */
10
- blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>;
10
+ blocks?: RenderableBlock[] | Readable<RenderableBlock[]>;
11
11
  /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
12
12
  * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
13
13
  isDisplayComplete?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAM5D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,EAAE,GAAG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAC5D;uEACmE;IACnE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,gBAAgB;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA2FH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM1D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD;uEACmE;IACnE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,gBAAgB;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA2FH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -1,11 +1,6 @@
1
- <!--
2
- @file IncremarkCode.svelte - 代码块组件
3
- @description 渲染 Markdown 代码块,支持 Shiki 代码高亮和 Mermaid 图表渲染
4
- -->
5
-
6
1
  <script lang="ts">
7
2
  import type { Code } from 'mdast'
8
- import { onMount, onDestroy } from 'svelte'
3
+ import { useShiki } from '../stores/useShiki.svelte'
9
4
 
10
5
  /**
11
6
  * 组件 Props
@@ -43,8 +38,6 @@
43
38
  // 状态
44
39
  let copied = $state(false)
45
40
  let highlightedHtml = $state('')
46
- let isHighlighting = $state(false)
47
- let highlightError = $state(false)
48
41
 
49
42
  // Mermaid 支持
50
43
  let mermaidSvg = $state('')
@@ -55,10 +48,8 @@
55
48
  // 视图模式:'preview' | 'source'
56
49
  let mermaidViewMode = $state<'preview' | 'source'>('preview')
57
50
 
58
- // 缓存 highlighter
59
- let highlighterRef: any = null
60
- const loadedLanguages = new Set<string>()
61
- const loadedThemes = new Set<string>()
51
+ // 使用 Shiki 单例管理器 - 修复:传入 getter 闭包以保持响应式
52
+ const shiki = useShiki(() => theme)
62
53
 
63
54
  /**
64
55
  * 计算属性
@@ -72,18 +63,10 @@
72
63
  const component = customCodeBlocks?.[language]
73
64
  if (!component) return null
74
65
 
75
- // 检查该语言的配置
76
66
  const config = codeBlockConfigs?.[language]
77
67
 
78
- // 如果配置了 takeOver true,则从一开始就使用
79
- if (config?.takeOver) {
80
- return component
81
- }
82
-
83
- // 否则,默认行为:只在 completed 状态使用
84
- if (blockStatus !== 'completed') {
85
- return null
86
- }
68
+ if (config?.takeOver) return component
69
+ if (blockStatus !== 'completed') return null
87
70
 
88
71
  return component
89
72
  })
@@ -95,54 +78,30 @@
95
78
  mermaidViewMode = mermaidViewMode === 'preview' ? 'source' : 'preview'
96
79
  }
97
80
 
98
- /**
99
- * Mermaid 渲染(带防抖动)
100
- */
101
- function scheduleRenderMermaid() {
102
- if (!isMermaid || !code) return
103
-
104
- // 清除之前的定时器
105
- if (mermaidTimer) {
106
- clearTimeout(mermaidTimer)
107
- }
108
-
109
- // 显示加载状态
110
- mermaidLoading = true
111
-
112
- // 防抖动延迟渲染
113
- mermaidTimer = setTimeout(() => {
114
- doRenderMermaid()
115
- }, mermaidDelay)
116
- }
117
-
118
81
  /**
119
82
  * 执行 Mermaid 渲染
120
83
  */
121
84
  async function doRenderMermaid() {
122
85
  if (!code) return
123
-
124
86
  mermaidError = ''
125
87
 
126
88
  try {
127
- // 动态导入 mermaid
128
89
  if (!mermaidRef) {
129
- // @ts-ignore - mermaid 是可选依赖
90
+ // @ts-ignore
130
91
  const mermaidModule = await import('mermaid')
131
92
  mermaidRef = mermaidModule.default
132
93
  mermaidRef.initialize({
133
94
  startOnLoad: false,
134
95
  theme: 'dark',
135
- securityLevel: 'loose'
96
+ securityLevel: 'loose',
97
+ suppressErrorRendering: true
136
98
  })
137
99
  }
138
100
 
139
- const mermaid = mermaidRef
140
101
  const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
141
-
142
- const { svg } = await mermaid.render(id, code)
102
+ const { svg } = await mermaidRef.render(id, code)
143
103
  mermaidSvg = svg
144
104
  } catch (e: any) {
145
- // 不显示错误,可能是代码还不完整
146
105
  mermaidError = ''
147
106
  mermaidSvg = ''
148
107
  } finally {
@@ -153,9 +112,12 @@
153
112
  /**
154
113
  * 动态加载 shiki 并高亮
155
114
  */
156
- async function highlight() {
115
+ async function doHighlight() {
157
116
  if (isMermaid) {
158
- scheduleRenderMermaid()
117
+ // Mermaid 防抖逻辑
118
+ if (mermaidTimer) clearTimeout(mermaidTimer)
119
+ mermaidLoading = true
120
+ mermaidTimer = setTimeout(doRenderMermaid, mermaidDelay)
159
121
  return
160
122
  }
161
123
 
@@ -164,54 +126,12 @@
164
126
  return
165
127
  }
166
128
 
167
- isHighlighting = true
168
- highlightError = false
169
-
170
129
  try {
171
- // 动态导入 shiki
172
- if (!highlighterRef) {
173
- const { createHighlighter } = await import('shiki')
174
- highlighterRef = await createHighlighter({
175
- themes: [theme as any],
176
- langs: []
177
- })
178
- loadedThemes.add(theme)
179
- }
180
-
181
- const highlighter = highlighterRef
182
- const lang = language
183
-
184
- // 按需加载语言
185
- if (!loadedLanguages.has(lang) && lang !== 'text') {
186
- try {
187
- await highlighter.loadLanguage(lang)
188
- loadedLanguages.add(lang)
189
- } catch {
190
- // 语言不支持,标记但不阻止
191
- }
192
- }
193
-
194
- // 按需加载主题
195
- if (!loadedThemes.has(theme)) {
196
- try {
197
- await highlighter.loadTheme(theme)
198
- loadedThemes.add(theme)
199
- } catch {
200
- // 主题不支持
201
- }
202
- }
203
-
204
- const html = highlighter.codeToHtml(code, {
205
- lang: loadedLanguages.has(lang) ? lang : 'text',
206
- theme: loadedThemes.has(theme) ? theme : fallbackTheme
207
- })
130
+ // 调用经过修复的 shiki.highlight
131
+ const html = await shiki.highlight(code, language, fallbackTheme)
208
132
  highlightedHtml = html
209
133
  } catch (e) {
210
- // Shiki 不可用或加载失败
211
- highlightError = true
212
134
  highlightedHtml = ''
213
- } finally {
214
- isHighlighting = false
215
135
  }
216
136
  }
217
137
 
@@ -222,31 +142,29 @@
222
142
  try {
223
143
  await navigator.clipboard.writeText(code)
224
144
  copied = true
225
- setTimeout(() => {
226
- copied = false
227
- }, 2000)
228
- } catch {
229
- // 复制失败静默处理
230
- }
145
+ setTimeout(() => { copied = false }, 2000)
146
+ } catch { /* 静默处理 */ }
231
147
  }
232
148
 
233
- // 监听代码变化,重新高亮/渲染
149
+ // 监听代码、主题、语言变化并重新渲染
234
150
  $effect(() => {
235
- highlight()
236
- })
237
-
238
- // 清理
239
- onDestroy(() => {
240
- if (mermaidTimer) {
241
- clearTimeout(mermaidTimer)
151
+ doHighlight()
152
+
153
+ // 返回清理函数取代 onDestroy
154
+ return () => {
155
+ if (mermaidTimer) clearTimeout(mermaidTimer)
242
156
  }
243
157
  })
244
158
  </script>
245
159
 
246
- <!-- 自定义代码块组件 -->
247
160
  {#if CustomCodeBlock}
248
161
  {@const Component = CustomCodeBlock}
249
- <Component codeStr={code} lang={language} completed={blockStatus === 'completed'} takeOver={codeBlockConfigs?.[language]?.takeOver} />
162
+ <Component
163
+ codeStr={code}
164
+ lang={language}
165
+ completed={blockStatus === 'completed'}
166
+ takeOver={codeBlockConfigs?.[language]?.takeOver}
167
+ />
250
168
  {:else if isMermaid}
251
169
  <div class="incremark-mermaid">
252
170
  <div class="mermaid-header">
@@ -266,23 +184,18 @@
266
184
  </div>
267
185
  </div>
268
186
  <div class="mermaid-content">
269
- <!-- 加载中 -->
270
187
  {#if mermaidLoading && !mermaidSvg}
271
188
  <pre class="mermaid-source-code">{code}</pre>
272
- <!-- 源码模式 -->
273
189
  {:else if mermaidViewMode === 'source'}
274
190
  <pre class="mermaid-source-code">{code}</pre>
275
- <!-- 预览模式 -->
276
191
  {:else if mermaidSvg}
277
192
  {@html mermaidSvg}
278
- <!-- 无法渲染时显示源码 -->
279
193
  {:else}
280
194
  <pre class="mermaid-source-code">{code}</pre>
281
195
  {/if}
282
196
  </div>
283
197
  </div>
284
198
  {:else}
285
- <!-- 普通代码块 -->
286
199
  <div class="incremark-code">
287
200
  <div class="code-header">
288
201
  <span class="language">{language}</span>
@@ -291,21 +204,17 @@
291
204
  </button>
292
205
  </div>
293
206
  <div class="code-content">
294
- <!-- 正在加载高亮 -->
295
- {#if isHighlighting && !highlightedHtml}
207
+ {#if shiki.isHighlighting && !highlightedHtml}
296
208
  <div class="code-loading">
297
209
  <pre><code>{code}</code></pre>
298
210
  </div>
299
- <!-- 高亮后的代码 -->
300
211
  {:else if highlightedHtml}
301
212
  <div class="shiki-wrapper">
302
213
  {@html highlightedHtml}
303
214
  </div>
304
- <!-- 回退:无高亮 -->
305
215
  {:else}
306
216
  <pre class="code-fallback"><code>{code}</code></pre>
307
217
  {/if}
308
218
  </div>
309
219
  </div>
310
220
  {/if}
311
-
@@ -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;AA8RH,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;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"}
@@ -0,0 +1,114 @@
1
+ <!--
2
+ @file IncremarkContent.svelte - 开箱即用的 Markdown 渲染组件
3
+ @description 自动处理流式内容和普通内容的高级组件
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import type { Component } from 'svelte'
8
+ import { useIncremark, type UseIncremarkOptions } from '../stores/useIncremark'
9
+ import type { ComponentMap, IncremarkContentProps } from './types'
10
+ import Incremark from './Incremark.svelte'
11
+ import { get } from 'svelte/store'
12
+
13
+ let {
14
+ stream,
15
+ content,
16
+ components = {},
17
+ customContainers = {},
18
+ customCodeBlocks = {},
19
+ codeBlockConfigs = {},
20
+ isFinished = false,
21
+ incremarkOptions,
22
+ pendingClass = 'incremark-pending',
23
+ showBlockStatus = false
24
+ }: IncremarkContentProps = $props()
25
+
26
+ // 合并默认选项(初始化时使用)
27
+ const initialOptions: UseIncremarkOptions = {
28
+ gfm: true,
29
+ htmlTree: true,
30
+ containers: true,
31
+ math: true,
32
+ ...incremarkOptions
33
+ }
34
+
35
+ const { blocks, append, finalize, render, reset, isDisplayComplete, markdown, typewriter } = useIncremark(initialOptions)
36
+
37
+ // 监听 incremarkOptions 的变化,更新 typewriter 配置
38
+ $effect(() => {
39
+ if (incremarkOptions?.typewriter) {
40
+ typewriter.setOptions(incremarkOptions.typewriter)
41
+ }
42
+ })
43
+
44
+ // 派生状态
45
+ const isStreamMode = $derived(typeof stream === 'function')
46
+
47
+ // 保存前一个 content 值用于增量更新
48
+ let prevContent: string | undefined = $state(undefined)
49
+ let isStreaming = $state(false)
50
+
51
+ async function handleStreamInput() {
52
+ if (!stream || isStreaming) return
53
+
54
+ isStreaming = true
55
+ try {
56
+ const streamGen = stream()
57
+
58
+ for await (const chunk of streamGen) {
59
+ append(chunk)
60
+ }
61
+
62
+ finalize()
63
+ } catch (error) {
64
+ console.error('Stream error: ', error)
65
+ finalize()
66
+ } finally {
67
+ isStreaming = false
68
+ }
69
+ }
70
+
71
+ function handleContentInput(newContent?: string, oldContent?: string) {
72
+ if (!newContent) {
73
+ if (oldContent) {
74
+ reset()
75
+ }
76
+ return
77
+ }
78
+
79
+ if (newContent?.startsWith(oldContent ?? '')) {
80
+ const delta = newContent.slice((oldContent || '').length)
81
+ append(delta)
82
+ } else {
83
+ render(newContent)
84
+ }
85
+ }
86
+
87
+ // 监听 content 变化
88
+ $effect(() => {
89
+ if (isStreamMode) {
90
+ handleStreamInput()
91
+ } else {
92
+ handleContentInput(content, prevContent)
93
+ }
94
+ prevContent = content
95
+ })
96
+
97
+ // 监听 isFinished 变化
98
+ $effect(() => {
99
+ if (isFinished && content === get(markdown)) {
100
+ finalize()
101
+ }
102
+ })
103
+ </script>
104
+
105
+ <Incremark
106
+ blocks={$blocks}
107
+ isDisplayComplete={$isDisplayComplete}
108
+ {pendingClass}
109
+ {showBlockStatus}
110
+ {components}
111
+ {customContainers}
112
+ {customCodeBlocks}
113
+ {codeBlockConfigs}
114
+ />
@@ -0,0 +1,6 @@
1
+ import type { Component } from 'svelte';
2
+ import type { IncremarkContentProps } from './types';
3
+ declare const IncremarkContent: Component<IncremarkContentProps, {}, "">;
4
+ type IncremarkContent = ReturnType<typeof IncremarkContent>;
5
+ export default IncremarkContent;
6
+ //# sourceMappingURL=IncremarkContent.svelte.d.ts.map
@@ -0,0 +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"}
@@ -76,6 +76,7 @@
76
76
  this={tag}
77
77
  class="incremark-list"
78
78
  class:task-list={isTaskList}
79
+ start={node.start || undefined}
79
80
  >
80
81
  {#each node.children as item, index (index)}
81
82
  <li
@@ -3,6 +3,7 @@
3
3
  * @description 导出所有组件
4
4
  */
5
5
  export { default as Incremark } from './Incremark.svelte';
6
+ export { default as IncremarkContent } from './IncremarkContent.svelte';
6
7
  export { default as IncremarkParagraph } from './IncremarkParagraph.svelte';
7
8
  export { default as IncremarkInline } from './IncremarkInline.svelte';
8
9
  export { default as IncremarkHeading } from './IncremarkHeading.svelte';
@@ -17,5 +18,5 @@ export { default as IncremarkFootnotes } from './IncremarkFootnotes.svelte';
17
18
  export { default as IncremarkDefault } from './IncremarkDefault.svelte';
18
19
  export { default as IncremarkRenderer } from './IncremarkRenderer.svelte';
19
20
  export { default as AutoScrollContainer } from './AutoScrollContainer.svelte';
20
- export type { ComponentMap, BlockWithStableId } from './types';
21
+ export type { ComponentMap, RenderableBlock, IncremarkContentProps, CodeBlockConfig } from './types';
21
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AACnF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAC/E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAE7E,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACnE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAC7E,OAAO,EAAE,OAAO,IAAI,sBAAsB,EAAE,MAAM,iCAAiC,CAAA;AACnF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAA;AAC/E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAA;AAC3E,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AACvE,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAA;AACzE,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,8BAA8B,CAAA;AAE7E,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA"}
@@ -3,6 +3,7 @@
3
3
  * @description 导出所有组件
4
4
  */
5
5
  export { default as Incremark } from './Incremark.svelte';
6
+ export { default as IncremarkContent } from './IncremarkContent.svelte';
6
7
  export { default as IncremarkParagraph } from './IncremarkParagraph.svelte';
7
8
  export { default as IncremarkInline } from './IncremarkInline.svelte';
8
9
  export { default as IncremarkHeading } from './IncremarkHeading.svelte';
@@ -2,17 +2,43 @@
2
2
  * @file Component Types - 组件类型定义
3
3
  * @description 定义组件相关的类型
4
4
  */
5
+ import type { Component } from 'svelte';
5
6
  import type { ParsedBlock } from '@incremark/core';
7
+ import type { UseIncremarkOptions } from '../stores/useIncremark';
6
8
  /**
7
9
  * 组件映射类型
8
10
  * 使用 any 以支持不同类型的组件
9
11
  */
10
12
  export type ComponentMap = Partial<Record<string, any>>;
11
13
  /**
12
- * 带稳定 ID 的块类型
14
+ * 可渲染的块类型(带 isLastPending 字段用于打字机光标)
13
15
  */
14
- export interface BlockWithStableId extends ParsedBlock {
15
- stableId: string;
16
+ export type RenderableBlock = ParsedBlock & {
16
17
  isLastPending?: boolean;
18
+ };
19
+ /**
20
+ * 代码块配置
21
+ */
22
+ export interface CodeBlockConfig {
23
+ /** 是否从一开始就接管渲染,而不是等到 completed 状态 */
24
+ takeOver?: boolean;
25
+ }
26
+ /**
27
+ * IncremarkContent 组件 Props
28
+ */
29
+ export interface IncremarkContentProps {
30
+ stream?: () => AsyncGenerator<string>;
31
+ content?: string;
32
+ components?: ComponentMap;
33
+ /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
34
+ customContainers?: Record<string, Component<any>>;
35
+ /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
36
+ customCodeBlocks?: Record<string, Component<any>>;
37
+ /** 代码块配置映射,key 为代码语言名称 */
38
+ codeBlockConfigs?: Record<string, CodeBlockConfig>;
39
+ isFinished?: boolean;
40
+ incremarkOptions?: UseIncremarkOptions;
41
+ pendingClass?: string;
42
+ showBlockStatus?: boolean;
17
43
  }
18
44
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,WAAW;IACpD,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/components/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAEjE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAA;AAEvD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAEvE;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,EAAE,MAAM,cAAc,CAAC,MAAM,CAAC,CAAA;IACrC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAClD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,gBAAgB,CAAC,EAAE,mBAAmB,CAAA;IACtC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,eAAe,CAAC,EAAE,OAAO,CAAA;CAC1B"}
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { useIncremark, type UseIncremarkOptions, type UseIncremarkReturn, type T
6
6
  export { useDevTools, type UseDevToolsOptions } from './stores/useDevTools';
7
7
  export { useBlockTransformer, type UseBlockTransformerOptions, type UseBlockTransformerReturn } from './stores/useBlockTransformer';
8
8
  export { setDefinitionsContext, getDefinitionsContext, type DefinitionsContextValue } from './context/definitionsContext';
9
- export { Incremark, IncremarkParagraph, IncremarkInline, IncremarkHeading, IncremarkCode, IncremarkList, IncremarkTable, IncremarkBlockquote, IncremarkThematicBreak, IncremarkMath, IncremarkHtmlElement, IncremarkFootnotes, IncremarkDefault, IncremarkRenderer, type ComponentMap, type BlockWithStableId } from './components';
9
+ export { Incremark, IncremarkContent, IncremarkParagraph, IncremarkInline, IncremarkHeading, IncremarkCode, IncremarkList, IncremarkTable, IncremarkBlockquote, IncremarkThematicBreak, IncremarkMath, IncremarkHtmlElement, IncremarkFootnotes, IncremarkDefault, IncremarkRenderer, type ComponentMap, type RenderableBlock, type IncremarkContentProps, type CodeBlockConfig } from './components';
10
10
  export { default as AutoScrollContainer } from './components/AutoScrollContainer.svelte';
11
11
  export { default as ThemeProvider } from './ThemeProvider.svelte';
12
12
  export type { ParsedBlock, IncrementalUpdate, ParserOptions, BlockStatus, Root, RootContent, SourceBlock, DisplayBlock, TransformerPlugin, TransformerOptions, TransformerState, AnimationEffect } from '@incremark/core';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACxJ,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC/B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAGzH,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACvB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AACxF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAGjE,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,IAAI,EACJ,WAAW,EAEX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,eAAe,EACf,aAAa,EACb,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,YAAY,EACb,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,KAAK,YAAY,EACjB,YAAY,EACZ,SAAS,EACT,eAAe,EACf,UAAU,EACV,UAAU,EACX,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,KAAK,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AACxJ,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC/B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAGzH,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACb,cAAc,EACd,mBAAmB,EACnB,sBAAsB,EACtB,aAAa,EACb,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,EACrB,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AACxF,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAGjE,YAAY,EACV,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,IAAI,EACJ,WAAW,EAEX,WAAW,EACX,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EAChB,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,gBAAgB,EAChB,sBAAsB,EACtB,UAAU,EACV,QAAQ,EACR,SAAS,EACT,eAAe,EACf,aAAa,EACb,WAAW,EACX,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,UAAU,EACV,YAAY,EACb,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,KAAK,YAAY,EACjB,YAAY,EACZ,SAAS,EACT,eAAe,EACf,UAAU,EACV,UAAU,EACX,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ export { useBlockTransformer } from './stores/useBlockTransformer';
9
9
  // Context
10
10
  export { setDefinitionsContext, getDefinitionsContext } from './context/definitionsContext';
11
11
  // Components
12
- export { Incremark, IncremarkParagraph, IncremarkInline, IncremarkHeading, IncremarkCode, IncremarkList, IncremarkTable, IncremarkBlockquote, IncremarkThematicBreak, IncremarkMath, IncremarkHtmlElement, IncremarkFootnotes, IncremarkDefault, IncremarkRenderer } from './components';
12
+ export { Incremark, IncremarkContent, IncremarkParagraph, IncremarkInline, IncremarkHeading, IncremarkCode, IncremarkList, IncremarkTable, IncremarkBlockquote, IncremarkThematicBreak, IncremarkMath, IncremarkHtmlElement, IncremarkFootnotes, IncremarkDefault, IncremarkRenderer } from './components';
13
13
  // Additional Components
14
14
  export { default as AutoScrollContainer } from './components/AutoScrollContainer.svelte';
15
15
  export { default as ThemeProvider } from './ThemeProvider.svelte';
@@ -29,8 +29,8 @@ export function useDevTools(incremark, options = {}) {
29
29
  // 设置 parser 的 onChange 回调
30
30
  incremark.parser.setOnChange((state) => {
31
31
  const blocks = [
32
- ...state.completedBlocks.map((b) => ({ ...b, stableId: b.id })),
33
- ...state.pendingBlocks.map((b, i) => ({ ...b, stableId: `pending-${i}` }))
32
+ ...state.completedBlocks,
33
+ ...state.pendingBlocks
34
34
  ];
35
35
  devtools.update({
36
36
  blocks,
@@ -30,6 +30,9 @@ export interface UseIncremarkOptions extends ParserOptions {
30
30
  /** 打字机配置,传入即创建 transformer(可通过 enabled 控制是否启用) */
31
31
  typewriter?: TypewriterOptions;
32
32
  }
33
+ export type RenderableBlock = ParsedBlock & {
34
+ isLastPending?: boolean;
35
+ };
33
36
  /**
34
37
  * 打字机控制对象
35
38
  */
@@ -66,10 +69,7 @@ export interface UseIncremarkReturn {
66
69
  /** 当前完整的 AST */
67
70
  ast: Readable<Root>;
68
71
  /** 用于渲染的 blocks(根据打字机设置自动处理) */
69
- blocks: Readable<Array<ParsedBlock & {
70
- stableId: string;
71
- isLastPending?: boolean;
72
- }>>;
72
+ blocks: Readable<Array<RenderableBlock>>;
73
73
  /** 是否正在加载 */
74
74
  isLoading: Writable<boolean>;
75
75
  /** 是否已完成(finalize) */
@@ -1 +1 @@
1
- {"version":3,"file":"useIncremark.d.ts","sourceRoot":"","sources":["../../src/stores/useIncremark.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useIncremark.d.ts","sourceRoot":"","sources":["../../src/stores/useIncremark.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * 使用 Shiki Highlighter
3
+ * @param themeGetter 传入一个返回主题字符串的函数,例如 () => theme
4
+ */
5
+ export declare function useShiki(themeGetter: () => string): {
6
+ readonly isHighlighting: boolean;
7
+ highlight: (code: string, lang: string, fallbackTheme: string) => Promise<string>;
8
+ };
9
+ //# sourceMappingURL=useShiki.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useShiki.svelte.d.ts","sourceRoot":"","sources":["../../src/stores/useShiki.svelte.ts"],"names":[],"mappings":"AAkFA;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM;;sBAOjB,MAAM,QAAQ,MAAM,iBAAiB,MAAM,KAAG,OAAO,CAAC,MAAM,CAAC;EA+B7F"}
@@ -0,0 +1,98 @@
1
+ // ============ 单例管理器 ============
2
+ class ShikiManager {
3
+ static instance = null;
4
+ highlighters = new Map();
5
+ constructor() { }
6
+ static getInstance() {
7
+ if (!ShikiManager.instance) {
8
+ ShikiManager.instance = new ShikiManager();
9
+ }
10
+ return ShikiManager.instance;
11
+ }
12
+ async getHighlighter(theme) {
13
+ if (this.highlighters.has(theme)) {
14
+ return this.highlighters.get(theme);
15
+ }
16
+ const { createHighlighter } = await import('shiki');
17
+ const highlighter = await createHighlighter({
18
+ themes: [theme],
19
+ langs: []
20
+ });
21
+ const info = {
22
+ highlighter,
23
+ loadedLanguages: new Set(),
24
+ loadedThemes: new Set([theme])
25
+ };
26
+ this.highlighters.set(theme, info);
27
+ return info;
28
+ }
29
+ async loadLanguage(theme, lang) {
30
+ const info = this.highlighters.get(theme);
31
+ if (!info || info.loadedLanguages.has(lang))
32
+ return;
33
+ try {
34
+ await info.highlighter.loadLanguage(lang);
35
+ info.loadedLanguages.add(lang);
36
+ }
37
+ catch { /* 静默处理 */ }
38
+ }
39
+ async loadTheme(theme) {
40
+ const info = this.highlighters.get(theme);
41
+ if (!info || info.loadedThemes.has(theme))
42
+ return;
43
+ try {
44
+ await info.highlighter.loadTheme(theme);
45
+ info.loadedThemes.add(theme);
46
+ }
47
+ catch { /* 静默处理 */ }
48
+ }
49
+ async codeToHtml(theme, code, lang, fallbackTheme) {
50
+ const info = this.highlighters.get(theme);
51
+ if (!info)
52
+ throw new Error('Highlighter not found');
53
+ const actualLang = info.loadedLanguages.has(lang) ? lang : 'text';
54
+ return info.highlighter.codeToHtml(code, {
55
+ lang: actualLang,
56
+ theme: theme
57
+ });
58
+ }
59
+ }
60
+ const shikiManager = ShikiManager.getInstance();
61
+ // ============ Svelte 5 Composable ============
62
+ /**
63
+ * 使用 Shiki Highlighter
64
+ * @param themeGetter 传入一个返回主题字符串的函数,例如 () => theme
65
+ */
66
+ export function useShiki(themeGetter) {
67
+ // 使用 Svelte 5 的原生响应式状态
68
+ let isHighlighting = $state(false);
69
+ /**
70
+ * 高亮代码
71
+ */
72
+ async function highlight(code, lang, fallbackTheme) {
73
+ // 关键:每次执行时通过 Getter 获取最新的主题
74
+ const currentTheme = themeGetter();
75
+ const currentFallback = fallbackTheme;
76
+ isHighlighting = true;
77
+ try {
78
+ const info = await shikiManager.getHighlighter(currentTheme);
79
+ // 按需加载语言
80
+ if (!info.loadedLanguages.has(lang) && lang !== 'text') {
81
+ await shikiManager.loadLanguage(currentTheme, lang);
82
+ }
83
+ // 按需加载主题
84
+ if (!info.loadedThemes.has(currentTheme)) {
85
+ await shikiManager.loadTheme(currentTheme);
86
+ }
87
+ return await shikiManager.codeToHtml(currentTheme, code, lang, currentFallback);
88
+ }
89
+ finally {
90
+ isHighlighting = false;
91
+ }
92
+ }
93
+ return {
94
+ // 使用 getter 暴露只读状态,保持 UI 响应
95
+ get isHighlighting() { return isHighlighting; },
96
+ highlight
97
+ };
98
+ }
@@ -22,7 +22,6 @@ export interface UseTypewriterOptions {
22
22
  export interface UseTypewriterReturn {
23
23
  /** 用于渲染的 blocks(经过打字机处理或原始blocks) */
24
24
  blocks: Readable<Array<ParsedBlock & {
25
- stableId: string;
26
25
  isLastPending?: boolean;
27
26
  }>>;
28
27
  /** 打字机控制对象 */
@@ -1 +1 @@
1
- {"version":3,"file":"useTypewriter.d.ts","sourceRoot":"","sources":["../../src/stores/useTypewriter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAGhB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG3E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY;IACZ,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,oBAAoB;IACpB,eAAe,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IACxC,oBAAoB;IACpB,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAA;IACpF,cAAc;IACd,UAAU,EAAE,kBAAkB,CAAA;IAC9B,qBAAqB;IACrB,WAAW,EAAE,gBAAgB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;IACjD,oCAAoC;IACpC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CA8KhF"}
1
+ {"version":3,"file":"useTypewriter.d.ts","sourceRoot":"","sources":["../../src/stores/useTypewriter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAGhB,KAAK,gBAAgB,EAEtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG3E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY;IACZ,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,oBAAoB;IACpB,eAAe,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IACxC,oBAAoB;IACpB,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,GAAG;QAAE,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAA;IAClE,cAAc;IACd,UAAU,EAAE,kBAAkB,CAAA;IAC9B,qBAAqB;IACrB,WAAW,EAAE,gBAAgB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;IACjD,oCAAoC;IACpC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CA4KhF"}
@@ -79,13 +79,12 @@ export function useTypewriter(options) {
79
79
  const rawBlocks = derived([completedBlocks, pendingBlocks], ([$completedBlocks, $pendingBlocks]) => {
80
80
  const result = [];
81
81
  for (const block of $completedBlocks) {
82
- result.push({ ...block, stableId: block.id });
82
+ result.push(block);
83
83
  }
84
84
  for (let i = 0; i < $pendingBlocks.length; i++) {
85
85
  const isLastPending = i === $pendingBlocks.length - 1;
86
86
  result.push({
87
87
  ...$pendingBlocks[i],
88
- stableId: `pending-${i}`,
89
88
  isLastPending
90
89
  });
91
90
  }
@@ -108,7 +107,6 @@ export function useTypewriter(options) {
108
107
  }
109
108
  return {
110
109
  id: db.id,
111
- stableId: db.id,
112
110
  status: (db.isDisplayComplete ? 'completed' : 'pending'),
113
111
  isLastPending,
114
112
  node,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incremark/svelte",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Incremark Svelte 5 集成",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -24,10 +24,10 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "shiki": "^3.20.0",
27
- "@incremark/devtools": "0.2.6",
28
- "@incremark/core": "0.2.6",
29
- "@incremark/theme": "0.2.6",
30
- "@incremark/shared": "0.2.6"
27
+ "@incremark/devtools": "0.2.7",
28
+ "@incremark/core": "0.2.7",
29
+ "@incremark/shared": "0.2.7",
30
+ "@incremark/theme": "0.2.7"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "svelte": "^5.0.0",