@incremark/svelte 0.3.1 → 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 +14 -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} +31 -34
  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 -208
  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 {
@@ -24,7 +23,7 @@
24
23
  }: IncremarkContentProps = $props()
25
24
 
26
25
  // 创建 incremark 实例(只创建一次)
27
- const incremarkInstance = untrack(() => useIncremark({
26
+ const incremark = untrack(() => useIncremark({
28
27
  gfm: incremarkOptions?.gfm ?? true,
29
28
  htmlTree: incremarkOptions?.htmlTree ?? true,
30
29
  containers: incremarkOptions?.containers ?? true,
@@ -33,9 +32,6 @@
33
32
  typewriter: incremarkOptions?.typewriter
34
33
  }))
35
34
 
36
- // 解构出需要的方法和状态
37
- const { blocks, append, finalize, render, reset, updateOptions, isDisplayComplete, markdown, typewriter } = incremarkInstance
38
-
39
35
  // 计算 parser 配置的稳定 key(用于检测变化)
40
36
  // astBuilder 用名称标识,因为它是类不能 JSON.stringify
41
37
  function getConfigKey() {
@@ -55,7 +51,7 @@
55
51
  if (prevConfigKey !== currentConfigKey) {
56
52
  prevConfigKey = currentConfigKey
57
53
  // 使用 updateOptions 动态更新配置(包括引擎切换)
58
- updateOptions({
54
+ incremark.updateOptions({
59
55
  gfm: incremarkOptions?.gfm ?? true,
60
56
  htmlTree: incremarkOptions?.htmlTree ?? true,
61
57
  containers: incremarkOptions?.containers ?? true,
@@ -68,7 +64,7 @@
68
64
  // 监听 incremarkOptions 的变化,更新 typewriter 配置
69
65
  $effect(() => {
70
66
  if (incremarkOptions?.typewriter) {
71
- typewriter.setOptions(incremarkOptions.typewriter)
67
+ incremark.typewriter.setOptions(incremarkOptions.typewriter)
72
68
  }
73
69
  })
74
70
 
@@ -87,13 +83,13 @@
87
83
  const streamGen = stream()
88
84
 
89
85
  for await (const chunk of streamGen) {
90
- append(chunk)
86
+ incremark.append(chunk)
91
87
  }
92
88
 
93
- finalize()
89
+ incremark.finalize()
94
90
  } catch (error) {
95
91
  console.error('Stream error: ', error)
96
- finalize()
92
+ incremark.finalize()
97
93
  } finally {
98
94
  isStreaming = false
99
95
  }
@@ -102,16 +98,16 @@
102
98
  function handleContentInput(newContent?: string, oldContent?: string) {
103
99
  if (!newContent) {
104
100
  if (oldContent) {
105
- reset()
101
+ incremark.reset()
106
102
  }
107
103
  return
108
104
  }
109
105
 
110
106
  if (newContent?.startsWith(oldContent ?? '')) {
111
107
  const delta = newContent.slice((oldContent || '').length)
112
- append(delta)
108
+ incremark.append(delta)
113
109
  } else {
114
- render(newContent)
110
+ incremark.render(newContent)
115
111
  }
116
112
  }
117
113
 
@@ -127,15 +123,15 @@
127
123
 
128
124
  // 监听 isFinished 变化
129
125
  $effect(() => {
130
- if (isFinished && content === get(markdown)) {
131
- finalize()
126
+ if (isFinished && content === incremark.markdown) {
127
+ incremark.finalize()
132
128
  }
133
129
  })
134
130
  </script>
135
131
 
136
132
  <Incremark
137
- blocks={$blocks}
138
- isDisplayComplete={$isDisplayComplete}
133
+ blocks={incremark.blocks}
134
+ isDisplayComplete={incremark.isDisplayComplete}
139
135
  {pendingClass}
140
136
  {showBlockStatus}
141
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;AA6IpD,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"}
@@ -12,7 +12,7 @@
12
12
  getStableText,
13
13
  isHtmlNode
14
14
  } from '@incremark/shared'
15
- import { getDefinitionsContext } from '../context/definitionsContext'
15
+ import { getDefinitionsContext } from '../context/definitionsContext.svelte.ts'
16
16
  import IncremarkMath from './IncremarkMath.svelte'
17
17
  import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
18
18
  import IncremarkInline from './IncremarkInline.svelte'
@@ -71,9 +71,9 @@
71
71
 
72
72
  // 获取 definitions context
73
73
  const context = getDefinitionsContext()
74
- // 解构 store 以便使用 $ 语法订阅
75
- const { definations } = context
76
74
 
75
+ // 使用 getter 获取 definitions
76
+ const definitions = $derived(context.getDefinations())
77
77
 
78
78
  /**
79
79
  * 获取节点的 chunks(类型安全)
@@ -156,12 +156,12 @@
156
156
 
157
157
  <!-- 引用式图片(imageReference) -->
158
158
  {#if isImageReference(node)}
159
- {#if $definations && $definations[node.identifier]}
159
+ {#if definitions && definitions[node.identifier]}
160
160
  <img
161
161
  class="incremark-image incremark-reference-image"
162
- src={$definations[node.identifier].url}
162
+ src={definitions[node.identifier].url}
163
163
  alt={node.alt || ''}
164
- title={$definations[node.identifier].title || undefined}
164
+ title={definitions[node.identifier].title || undefined}
165
165
  loading="lazy"
166
166
  />
167
167
  {:else}
@@ -174,11 +174,11 @@
174
174
 
175
175
  <!-- 引用式链接(linkReference) -->
176
176
  {#if isLinkReference(node)}
177
- {#if $definations && $definations[node.identifier]}
177
+ {#if definitions && definitions[node.identifier]}
178
178
  <a
179
179
  class="incremark-link incremark-reference-link"
180
- href={$definations[node.identifier].url}
181
- title={$definations[node.identifier].title || undefined}
180
+ href={definitions[node.identifier].url}
181
+ title={definitions[node.identifier].title || undefined}
182
182
  target="_blank"
183
183
  rel="noopener noreferrer"
184
184
  >
@@ -213,4 +213,3 @@
213
213
  </del>
214
214
  {/if}
215
215
  {/each}
216
-
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkInline.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkInline.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAiC,MAAM,OAAO,CAAA;AAW3E,OAAO,eAAe,MAAM,0BAA0B,CAAC;AAGrD;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAwLH,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"IncremarkInline.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkInline.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAiC,MAAM,OAAO,CAAA;AAW3E,OAAO,eAAe,MAAM,0BAA0B,CAAC;AAGrD;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,KAAK,EAAE,eAAe,EAAE,CAAA;CACzB;AAuLH,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}