@incremark/svelte 0.3.0 → 0.3.2

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 (55) hide show
  1. package/dist/components/CachedCodeRenderer.svelte +120 -0
  2. package/dist/components/CachedCodeRenderer.svelte.d.ts +10 -0
  3. package/dist/components/CachedCodeRenderer.svelte.d.ts.map +1 -0
  4. package/dist/components/ConfigProvider.svelte +5 -4
  5. package/dist/components/ConfigProvider.svelte.d.ts +1 -1
  6. package/dist/components/ConfigProvider.svelte.d.ts.map +1 -1
  7. package/dist/components/Incremark.svelte +10 -37
  8. package/dist/components/Incremark.svelte.d.ts +1 -1
  9. package/dist/components/Incremark.svelte.d.ts.map +1 -1
  10. package/dist/components/IncremarkCode.svelte +8 -6
  11. package/dist/components/IncremarkCode.svelte.d.ts.map +1 -1
  12. package/dist/components/IncremarkCodeDefault.svelte +60 -31
  13. package/dist/components/IncremarkCodeDefault.svelte.d.ts +2 -0
  14. package/dist/components/IncremarkCodeDefault.svelte.d.ts.map +1 -1
  15. package/dist/components/IncremarkContent.svelte +44 -18
  16. package/dist/components/IncremarkContent.svelte.d.ts.map +1 -1
  17. package/dist/components/IncremarkFootnotes.svelte +3 -6
  18. package/dist/components/IncremarkFootnotes.svelte.d.ts.map +1 -1
  19. package/dist/components/IncremarkInline.svelte +9 -10
  20. package/dist/components/IncremarkInline.svelte.d.ts.map +1 -1
  21. package/dist/components/types.d.ts +1 -1
  22. package/dist/components/types.d.ts.map +1 -1
  23. package/dist/context/{definitionsContext.d.ts → definitionsContext.svelte.d.ts} +13 -11
  24. package/dist/context/definitionsContext.svelte.d.ts.map +1 -0
  25. package/dist/context/{definitionsContext.js → definitionsContext.svelte.js} +18 -15
  26. package/dist/index.d.ts +5 -5
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -5
  29. package/dist/stores/{useBlockTransformer.d.ts → useBlockTransformer.svelte.d.ts} +33 -35
  30. package/dist/stores/useBlockTransformer.svelte.d.ts.map +1 -0
  31. package/dist/stores/useBlockTransformer.svelte.js +106 -0
  32. package/dist/stores/useDevTools.svelte.d.ts +1 -1
  33. package/dist/stores/useDevTools.svelte.d.ts.map +1 -1
  34. package/dist/stores/{useIncremark.d.ts → useIncremark.svelte.d.ts} +35 -36
  35. package/dist/stores/useIncremark.svelte.d.ts.map +1 -0
  36. package/dist/stores/useIncremark.svelte.js +226 -0
  37. package/dist/stores/useLocale.svelte.d.ts.map +1 -1
  38. package/dist/stores/useLocale.svelte.js +3 -2
  39. package/dist/stores/useShiki.svelte.d.ts +26 -0
  40. package/dist/stores/useShiki.svelte.d.ts.map +1 -1
  41. package/dist/stores/useShiki.svelte.js +27 -0
  42. package/dist/stores/useTypewriter.svelte.d.ts +49 -0
  43. package/dist/stores/useTypewriter.svelte.d.ts.map +1 -0
  44. package/dist/stores/useTypewriter.svelte.js +212 -0
  45. package/dist/utils/cursor.d.ts.map +1 -1
  46. package/dist/utils/cursor.js +8 -0
  47. package/package.json +9 -6
  48. package/dist/context/definitionsContext.d.ts.map +0 -1
  49. package/dist/stores/useBlockTransformer.d.ts.map +0 -1
  50. package/dist/stores/useBlockTransformer.js +0 -110
  51. package/dist/stores/useIncremark.d.ts.map +0 -1
  52. package/dist/stores/useIncremark.js +0 -189
  53. package/dist/stores/useTypewriter.d.ts +0 -44
  54. package/dist/stores/useTypewriter.d.ts.map +0 -1
  55. package/dist/stores/useTypewriter.js +0 -163
@@ -0,0 +1,120 @@
1
+ <!--
2
+ CachedCodeRenderer - 使用 shiki-stream 实现流式代码高亮
3
+
4
+ 基于 shiki-stream 的设计模式,适配 Svelte 5:
5
+ - 在脚本顶层创建和消费 stream(类似 Vue setup)
6
+ - 使用 $state 存储 tokens
7
+ - 使用 $effect 监听 code 变化
8
+ -->
9
+ <script lang="ts">
10
+ import type { ThemedToken } from '@shikijs/core'
11
+ import { onDestroy, untrack } from 'svelte'
12
+ import { CodeToTokenTransformStream } from 'shiki-stream'
13
+ import { getTokenStyleObject } from '@shikijs/core'
14
+ import { objectId } from '@antfu/utils'
15
+
16
+ interface Props {
17
+ code: string
18
+ lang: string
19
+ theme: string
20
+ highlighter: any
21
+ }
22
+
23
+ let { code, lang, theme, highlighter }: Props = $props()
24
+
25
+ // Stream 错误状态
26
+ let hasStreamError = $state(false)
27
+
28
+ // Tokens 数组(与 Vue 的 reactive 数组对齐)
29
+ let tokens = $state<ThemedToken[]>([])
30
+
31
+ // 已处理的 code 长度
32
+ let index = $state(0)
33
+
34
+ // 创建文本流(在脚本顶层,类似 Vue setup)
35
+ let controller: ReadableStreamController<string> | null = null
36
+ const textStream = new ReadableStream<string>({
37
+ start(_controller) {
38
+ controller = _controller
39
+ }
40
+ })
41
+
42
+ // 创建 token stream
43
+ // 使用 untrack 获取初始值,因为 stream 创建后不支持动态更改 lang/theme/highlighter
44
+ let tokenStream: ReadableStream<any> | null = null
45
+
46
+ try {
47
+ const initialHighlighter = untrack(() => highlighter)
48
+ const initialLang = untrack(() => lang)
49
+ const initialTheme = untrack(() => theme)
50
+
51
+ tokenStream = textStream.pipeThrough(
52
+ new CodeToTokenTransformStream({
53
+ highlighter: initialHighlighter,
54
+ lang: initialLang,
55
+ theme: initialTheme,
56
+ allowRecalls: true
57
+ })
58
+ )
59
+ } catch (error) {
60
+ console.error('Failed to create token stream:', error)
61
+ hasStreamError = true
62
+ }
63
+
64
+ // 消费 token stream(与 Vue/React 版本对齐)
65
+ if (tokenStream) {
66
+ tokenStream.pipeTo(
67
+ new WritableStream({
68
+ write(token: any) {
69
+ if ('recall' in token) {
70
+ tokens.splice(tokens.length - token.recall, token.recall)
71
+ } else {
72
+ tokens.push(token)
73
+ }
74
+ }
75
+ })
76
+ ).catch((error) => {
77
+ console.error('Stream error:', error)
78
+ hasStreamError = true
79
+ })
80
+ }
81
+
82
+ // 监听 code 变化,增量推送到流中(与 Vue 的 watchEffect 对齐)
83
+ $effect(() => {
84
+ if (hasStreamError) return
85
+
86
+ // 读取 code 的值来建立依赖
87
+ const currentCode = code
88
+
89
+ // 使用 untrack 避免 index 建立依赖
90
+ const currentIndex = untrack(() => index)
91
+
92
+ // 只处理增量更新:传入新增的部分
93
+ if (currentCode.length > currentIndex) {
94
+ const incremental = currentCode.slice(currentIndex)
95
+ controller?.enqueue(incremental as any)
96
+ index = currentCode.length
97
+ }
98
+ })
99
+
100
+ // 组件卸载时清理
101
+ onDestroy(() => {
102
+ controller?.close()
103
+ })
104
+ </script>
105
+
106
+ {#if hasStreamError}
107
+ <!-- 错误状态:渲染纯文本 -->
108
+ <pre class="shiki incremark-code-stream"><code>{code}</code></pre>
109
+ {:else}
110
+ <!-- 正常渲染高亮代码(与 Vue/React 的渲染结构对齐) -->
111
+ {@const getStyle = (token: any) => {
112
+ const style = token.htmlStyle || getTokenStyleObject(token)
113
+ // 如果是对象,转换为 CSS 字符串
114
+ if (typeof style === 'object') {
115
+ return Object.entries(style).map(([k, v]) => `${k}: ${v}`).join('; ')
116
+ }
117
+ return style
118
+ }}
119
+ <pre class="shiki incremark-code-stream"><code>{#each tokens as token (objectId(token))}<span style={getStyle(token)}>{token.content}</span>{/each}</code></pre>
120
+ {/if}
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ code: string;
3
+ lang: string;
4
+ theme: string;
5
+ highlighter: any;
6
+ }
7
+ declare const CachedCodeRenderer: import("svelte").Component<Props, {}, "">;
8
+ type CachedCodeRenderer = ReturnType<typeof CachedCodeRenderer>;
9
+ export default CachedCodeRenderer;
10
+ //# sourceMappingURL=CachedCodeRenderer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CachedCodeRenderer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/CachedCodeRenderer.svelte.ts"],"names":[],"mappings":"AAUE,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,GAAG,CAAA;CACjB;AA+GH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import type { IncremarkLocale } from '@incremark/svelte'
3
- import { en } from '../index'
2
+ import type { IncremarkLocale } from '@incremark/shared'
3
+ import { zhCN } from '@incremark/shared'
4
4
  import { provideLocale } from '../stores/useLocale.svelte'
5
5
 
6
6
  interface Props {
@@ -8,11 +8,12 @@
8
8
  children: import('svelte').Snippet
9
9
  }
10
10
 
11
- const { locale = en, children }: Props = $props()
11
+ // 提供 locale 给子组件(确保不是 undefined)
12
+ const { locale, children }: Props = $props()
12
13
 
13
14
  // 在组件初始化时同步设置 context
14
15
  // 使用函数返回 locale 以支持响应式更新
15
- provideLocale(() => locale)
16
+ provideLocale(() => locale || zhCN)
16
17
  </script>
17
18
 
18
19
  {@render children()}
@@ -1,4 +1,4 @@
1
- import type { IncremarkLocale } from '@incremark/svelte';
1
+ import type { IncremarkLocale } from '@incremark/shared';
2
2
  interface Props {
3
3
  locale?: IncremarkLocale;
4
4
  children: import('svelte').Snippet;
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/components/ConfigProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAKtD,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAA;CACnC;AAkBH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"ConfigProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/components/ConfigProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAKtD,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,QAAQ,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAA;CACnC;AAmBH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -5,10 +5,10 @@
5
5
 
6
6
  <script lang="ts">
7
7
  import type { Component } from 'svelte'
8
- import type { RootContent, ParsedBlock } from '@incremark/core'
8
+ import type { RootContent } from '@incremark/core'
9
9
 
10
- import { getDefinitionsContext } from '../context/definitionsContext'
11
- import type { UseIncremarkReturn } from '../stores/useIncremark'
10
+ import { getDefinitionsContext } from '../context/definitionsContext.svelte.ts'
11
+ import type { UseIncremarkReturn } from '../stores/useIncremark.svelte.ts'
12
12
  import type { ComponentMap, RenderableBlock } from './types'
13
13
 
14
14
  // 导入组件
@@ -55,46 +55,19 @@
55
55
  }: Props = $props()
56
56
 
57
57
  const context = getDefinitionsContext()
58
- // 解构 store 以便使用 $ 语法订阅
59
- const { footnoteReferenceOrder } = context
60
58
 
61
- // 获取 incremark 的 stores(使用 getter 访问以保持响应性)
62
- const blocksStore = $derived(incremark?.blocks)
63
- const displayCompleteStore = $derived(incremark?.isDisplayComplete)
64
-
65
- // 订阅 stores 的值
66
- let blocksFromStore = $state<RenderableBlock[]>([])
67
- let displayCompleteFromStore = $state(false)
68
-
69
- // 使用 effect 订阅 blocks store
70
- $effect(() => {
71
- if (blocksStore) {
72
- const unsubscribe = blocksStore.subscribe((value) => {
73
- blocksFromStore = value
74
- })
75
- return unsubscribe
76
- }
77
- })
78
-
79
- // 使用 effect 订阅 displayComplete store
80
- $effect(() => {
81
- if (displayCompleteStore) {
82
- const unsubscribe = displayCompleteStore.subscribe((value) => {
83
- displayCompleteFromStore = value
84
- })
85
- return unsubscribe
86
- }
87
- })
88
-
89
- // 计算最终要渲染的 blocks
59
+ // 计算最终要渲染的 blocks(优先使用 incremark 对象)
90
60
  const renderBlocks = $derived<RenderableBlock[]>(
91
- blocksStore ? blocksFromStore : (blocks ?? [])
61
+ incremark ? incremark.blocks : (blocks ?? [])
92
62
  )
93
63
 
94
64
  // 计算是否显示完成
95
65
  const displayComplete = $derived(
96
- displayCompleteStore ? displayCompleteFromStore : isDisplayComplete
66
+ incremark ? incremark.isDisplayComplete : isDisplayComplete
97
67
  )
68
+
69
+ // 获取脚注引用顺序
70
+ const footnoteOrder = $derived(context.getFootnoteReferenceOrder())
98
71
  </script>
99
72
 
100
73
  <div class="incremark">
@@ -117,7 +90,7 @@
117
90
  {/each}
118
91
 
119
92
  <!-- 脚注列表(仅在内容完全显示后显示) -->
120
- {#if displayComplete && $footnoteReferenceOrder.length > 0}
93
+ {#if displayComplete && footnoteOrder.length > 0}
121
94
  <IncremarkFootnotes />
122
95
  {/if}
123
96
  </div>
@@ -1,5 +1,5 @@
1
1
  import type { Component } from 'svelte';
2
- import type { UseIncremarkReturn } from '../stores/useIncremark';
2
+ import type { UseIncremarkReturn } from '../stores/useIncremark.svelte.ts';
3
3
  import type { ComponentMap, RenderableBlock } from './types';
4
4
  /**
5
5
  * 组件 Props
@@ -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;AAIvC,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,gBAAgB;IAChB,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;IAC1B,iBAAiB;IACjB,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,qEAAqE;IACrE,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA0FH,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;AAIvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAA;AAC1E,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM1D;;GAEG;AACH,UAAU,KAAK;IACb,gBAAgB;IAChB,MAAM,CAAC,EAAE,eAAe,EAAE,CAAA;IAC1B,iBAAiB;IACjB,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,qEAAqE;IACrE,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA+DH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -36,7 +36,7 @@
36
36
  mermaidDelay = 500,
37
37
  customCodeBlocks,
38
38
  codeBlockConfigs,
39
- blockStatus = 'completed',
39
+ blockStatus = 'pending',
40
40
  defaultCodeComponent
41
41
  }: Props = $props()
42
42
 
@@ -79,10 +79,12 @@
79
79
  />
80
80
  {:else}
81
81
  {@const Component = DefaultCodeBlock}
82
- <Component
83
- {node}
84
- {theme}
85
- {fallbackTheme}
86
- {disableHighlight}
82
+ <!-- 默认代码块渲染(支持用户自定义,使用 stream 高亮)-->
83
+ <Component
84
+ {node}
85
+ {theme}
86
+ {fallbackTheme}
87
+ {disableHighlight}
88
+ {blockStatus}
87
89
  />
88
90
  {/if}
@@ -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;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"}
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;AAyDH,QAAA,MAAM,aAAa,0BAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -6,6 +6,7 @@
6
6
  import SvgIcon from './SvgIcon.svelte'
7
7
  import { useShiki } from '../stores/useShiki.svelte'
8
8
  import { useLocale } from '../stores/useLocale.svelte'
9
+ import CachedCodeRenderer from './CachedCodeRenderer.svelte'
9
10
 
10
11
  /**
11
12
  * 组件 Props
@@ -19,18 +20,21 @@
19
20
  fallbackTheme?: string
20
21
  /** 是否禁用代码高亮 */
21
22
  disableHighlight?: boolean
23
+ /** block 状态 */
24
+ blockStatus?: 'pending' | 'stable' | 'completed'
22
25
  }
23
26
 
24
27
  let {
25
28
  node,
26
29
  theme = 'github-dark',
27
30
  fallbackTheme = 'github-dark',
28
- disableHighlight = false
31
+ disableHighlight = false,
32
+ blockStatus = 'pending'
29
33
  }: Props = $props()
30
34
 
31
35
  // 状态
32
36
  let copied = $state(false)
33
- let highlightedHtml = $state('')
37
+ let isLanguageLoaded = $state(false)
34
38
 
35
39
  // 使用 Shiki 单例管理器
36
40
  const shiki = useShiki(() => theme)
@@ -42,22 +46,49 @@
42
46
  const language = $derived(node.lang || 'text')
43
47
  const code = $derived(node.value)
44
48
 
45
- /**
46
- * 动态加载 shiki 并高亮
47
- */
48
- async function doHighlight() {
49
- if (!code || disableHighlight) {
50
- highlightedHtml = ''
49
+ // 是否应该启用高亮(需要有代码内容才开始高亮逻辑)
50
+ const shouldEnableHighlight = $derived(!disableHighlight && code && code.length > 0)
51
+
52
+ // 初始化 highlighter 并加载语言
53
+ $effect(() => {
54
+ // 如果不需要高亮,直接返回
55
+ if (!shouldEnableHighlight) {
51
56
  return
52
57
  }
53
58
 
54
- try {
55
- const html = await shiki.highlight(code, language, fallbackTheme)
56
- highlightedHtml = html
57
- } catch (e) {
58
- highlightedHtml = ''
59
- }
60
- }
59
+ // 使用立即执行的异步函数
60
+ ;(async () => {
61
+ if (!shiki.highlighterInfo) {
62
+ await shiki.initHighlighter()
63
+ } else if (language && language !== 'text') {
64
+ // 检查语言是否已加载
65
+ if (!shiki.highlighterInfo.loadedLanguages.has(language as any)) {
66
+ try {
67
+ isLanguageLoaded = false
68
+ // 检查语言是否被 shiki 支持
69
+ const supportedLangs = shiki.highlighterInfo.highlighter.getLoadedLanguages()
70
+ const bundledLangs = await import('shiki').then(m => Object.keys(m.bundledLanguages || {}))
71
+ const isSupported = supportedLangs.includes(language) || bundledLangs.includes(language)
72
+
73
+ if (isSupported) {
74
+ await shiki.highlighterInfo.highlighter.loadLanguage(language as any)
75
+ shiki.highlighterInfo.loadedLanguages.add(language as any)
76
+ }
77
+ // 无论是否支持,都标记为已加载(不支持的语言会 fallback 到纯文本显示)
78
+ isLanguageLoaded = true
79
+ } catch {
80
+ // 语言加载失败,标记为已加载(回退到无高亮)
81
+ isLanguageLoaded = true
82
+ }
83
+ } else {
84
+ isLanguageLoaded = true
85
+ }
86
+ } else {
87
+ // text 语言不需要加载
88
+ isLanguageLoaded = true
89
+ }
90
+ })()
91
+ })
61
92
 
62
93
  let copyTimeoutId: ReturnType<typeof setTimeout> | null = null
63
94
 
@@ -79,11 +110,6 @@
79
110
  onDestroy(() => {
80
111
  if (copyTimeoutId) clearTimeout(copyTimeoutId)
81
112
  })
82
-
83
- // 监听代码、主题、语言变化并重新渲染
84
- $effect(() => {
85
- doHighlight()
86
- })
87
113
  </script>
88
114
 
89
115
  <div class="incremark-code">
@@ -100,16 +126,19 @@
100
126
  </button>
101
127
  </div>
102
128
  <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}
129
+ <div class="shiki-wrapper">
130
+ {#if shouldEnableHighlight && shiki.highlighterInfo && isLanguageLoaded}
131
+ <!-- Stream 高亮(只有当存在代码内容且语言加载完成后才渲染) -->
132
+ <CachedCodeRenderer
133
+ code={code}
134
+ lang={language}
135
+ theme={theme}
136
+ highlighter={shiki.highlighterInfo.highlighter}
137
+ />
138
+ {:else}
139
+ <!-- 无高亮模式(禁用高亮、无代码内容、或语言未加载完成时显示) -->
140
+ <pre class="code-fallback"><code>{code}</code></pre>
141
+ {/if}
142
+ </div>
114
143
  </div>
115
144
  </div>
@@ -11,6 +11,8 @@ interface Props {
11
11
  fallbackTheme?: string;
12
12
  /** 是否禁用代码高亮 */
13
13
  disableHighlight?: boolean;
14
+ /** block 状态 */
15
+ blockStatus?: 'pending' | 'stable' | 'completed';
14
16
  }
15
17
  declare const IncremarkCodeDefault: import("svelte").Component<Props, {}, "">;
16
18
  type IncremarkCodeDefault = ReturnType<typeof IncremarkCodeDefault>;
@@ -1 +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"}
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;AAU/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,eAAe;IACf,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AA0HH,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
@@ -4,10 +4,9 @@
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
- import { useIncremark, type UseIncremarkOptions } from '../stores/useIncremark'
7
+ import { useIncremark } from '../stores/useIncremark.svelte.ts'
8
8
  import type { IncremarkContentProps } from './types'
9
9
  import Incremark from './Incremark.svelte'
10
- import { get } from 'svelte/store'
11
10
  import { untrack } from 'svelte'
12
11
 
13
12
  let {
@@ -23,22 +22,49 @@
23
22
  showBlockStatus = false
24
23
  }: IncremarkContentProps = $props()
25
24
 
26
- // 创建稳定的 incremark 实例
27
- // 使用 untrack 读取初始值,因为 useIncremark 只需初始化一次
28
- // 后续的配置更新通过 typewriter.setOptions 在 $effect 中处理
29
- const incremarkInstance = untrack(() => useIncremark({
25
+ // 创建 incremark 实例(只创建一次)
26
+ const incremark = untrack(() => useIncremark({
30
27
  gfm: incremarkOptions?.gfm ?? true,
31
28
  htmlTree: incremarkOptions?.htmlTree ?? true,
32
29
  containers: incremarkOptions?.containers ?? true,
33
30
  math: incremarkOptions?.math ?? true,
31
+ astBuilder: incremarkOptions?.astBuilder,
34
32
  typewriter: incremarkOptions?.typewriter
35
33
  }))
36
- const { blocks, append, finalize, render, reset, isDisplayComplete, markdown, typewriter } = incremarkInstance
34
+
35
+ // 计算 parser 配置的稳定 key(用于检测变化)
36
+ // astBuilder 用名称标识,因为它是类不能 JSON.stringify
37
+ function getConfigKey() {
38
+ const { typewriter: _, astBuilder, ...parserOptions } = incremarkOptions ?? {}
39
+ return JSON.stringify(parserOptions) + '|' + (astBuilder?.name ?? 'default')
40
+ }
41
+
42
+ // 监听 parser 配置变化,使用 updateOptions 动态更新(不重建实例)
43
+ let prevConfigKey = getConfigKey()
44
+ let isFirstRender = true
45
+ $effect(() => {
46
+ const currentConfigKey = getConfigKey()
47
+ if (isFirstRender) {
48
+ isFirstRender = false
49
+ return
50
+ }
51
+ if (prevConfigKey !== currentConfigKey) {
52
+ prevConfigKey = currentConfigKey
53
+ // 使用 updateOptions 动态更新配置(包括引擎切换)
54
+ incremark.updateOptions({
55
+ gfm: incremarkOptions?.gfm ?? true,
56
+ htmlTree: incremarkOptions?.htmlTree ?? true,
57
+ containers: incremarkOptions?.containers ?? true,
58
+ math: incremarkOptions?.math ?? true,
59
+ astBuilder: incremarkOptions?.astBuilder
60
+ })
61
+ }
62
+ })
37
63
 
38
64
  // 监听 incremarkOptions 的变化,更新 typewriter 配置
39
65
  $effect(() => {
40
66
  if (incremarkOptions?.typewriter) {
41
- typewriter.setOptions(incremarkOptions.typewriter)
67
+ incremark.typewriter.setOptions(incremarkOptions.typewriter)
42
68
  }
43
69
  })
44
70
 
@@ -57,13 +83,13 @@
57
83
  const streamGen = stream()
58
84
 
59
85
  for await (const chunk of streamGen) {
60
- append(chunk)
86
+ incremark.append(chunk)
61
87
  }
62
88
 
63
- finalize()
89
+ incremark.finalize()
64
90
  } catch (error) {
65
91
  console.error('Stream error: ', error)
66
- finalize()
92
+ incremark.finalize()
67
93
  } finally {
68
94
  isStreaming = false
69
95
  }
@@ -72,16 +98,16 @@
72
98
  function handleContentInput(newContent?: string, oldContent?: string) {
73
99
  if (!newContent) {
74
100
  if (oldContent) {
75
- reset()
101
+ incremark.reset()
76
102
  }
77
103
  return
78
104
  }
79
105
 
80
106
  if (newContent?.startsWith(oldContent ?? '')) {
81
107
  const delta = newContent.slice((oldContent || '').length)
82
- append(delta)
108
+ incremark.append(delta)
83
109
  } else {
84
- render(newContent)
110
+ incremark.render(newContent)
85
111
  }
86
112
  }
87
113
 
@@ -97,15 +123,15 @@
97
123
 
98
124
  // 监听 isFinished 变化
99
125
  $effect(() => {
100
- if (isFinished && content === get(markdown)) {
101
- finalize()
126
+ if (isFinished && content === incremark.markdown) {
127
+ incremark.finalize()
102
128
  }
103
129
  })
104
130
  </script>
105
131
 
106
132
  <Incremark
107
- blocks={$blocks}
108
- isDisplayComplete={$isDisplayComplete}
133
+ blocks={incremark.blocks}
134
+ isDisplayComplete={incremark.isDisplayComplete}
109
135
  {pendingClass}
110
136
  {showBlockStatus}
111
137
  {components}
@@ -1 +1 @@
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;AA+GpD,QAAA,MAAM,gBAAgB,2DAAwC,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;AAwIpD,QAAA,MAAM,gBAAgB,2DAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -5,20 +5,18 @@
5
5
 
6
6
  <script lang="ts">
7
7
  import type { RootContent } from 'mdast'
8
- import { getDefinitionsContext } from '../context/definitionsContext'
8
+ import { getDefinitionsContext } from '../context/definitionsContext.svelte.ts'
9
9
  import IncremarkRenderer from './IncremarkRenderer.svelte'
10
10
 
11
11
  const context = getDefinitionsContext()
12
- // 解构 store 以便使用 $ 语法订阅
13
- const { footnoteDefinitions, footnoteReferenceOrder } = context
14
12
 
15
13
  /**
16
14
  * 按引用顺序排列的脚注列表
17
15
  * 只显示已有定义的脚注
18
16
  */
19
17
  const orderedFootnotes = $derived.by(() => {
20
- const order = $footnoteReferenceOrder
21
- const definitions = $footnoteDefinitions
18
+ const order = context.getFootnoteReferenceOrder()
19
+ const definitions = context.getFootnoteDefinitions()
22
20
 
23
21
  return order
24
22
  .map(identifier => ({
@@ -68,4 +66,3 @@
68
66
  </ol>
69
67
  </section>
70
68
  {/if}
71
-
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"IncremarkFootnotes.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkFootnotes.svelte.ts"],"names":[],"mappings":"AAoEA,QAAA,MAAM,kBAAkB,2DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}