@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.
- package/dist/components/CachedCodeRenderer.svelte +120 -0
- package/dist/components/CachedCodeRenderer.svelte.d.ts +10 -0
- package/dist/components/CachedCodeRenderer.svelte.d.ts.map +1 -0
- package/dist/components/ConfigProvider.svelte +5 -4
- package/dist/components/ConfigProvider.svelte.d.ts +1 -1
- package/dist/components/ConfigProvider.svelte.d.ts.map +1 -1
- package/dist/components/Incremark.svelte +10 -37
- package/dist/components/Incremark.svelte.d.ts +1 -1
- package/dist/components/Incremark.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkCode.svelte +8 -6
- package/dist/components/IncremarkCode.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkCodeDefault.svelte +60 -31
- package/dist/components/IncremarkCodeDefault.svelte.d.ts +2 -0
- package/dist/components/IncremarkCodeDefault.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkContent.svelte +44 -18
- package/dist/components/IncremarkContent.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkFootnotes.svelte +3 -6
- package/dist/components/IncremarkFootnotes.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkInline.svelte +9 -10
- package/dist/components/IncremarkInline.svelte.d.ts.map +1 -1
- package/dist/components/types.d.ts +1 -1
- package/dist/components/types.d.ts.map +1 -1
- package/dist/context/{definitionsContext.d.ts → definitionsContext.svelte.d.ts} +13 -11
- package/dist/context/definitionsContext.svelte.d.ts.map +1 -0
- package/dist/context/{definitionsContext.js → definitionsContext.svelte.js} +18 -15
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/stores/{useBlockTransformer.d.ts → useBlockTransformer.svelte.d.ts} +33 -35
- package/dist/stores/useBlockTransformer.svelte.d.ts.map +1 -0
- package/dist/stores/useBlockTransformer.svelte.js +106 -0
- package/dist/stores/useDevTools.svelte.d.ts +1 -1
- package/dist/stores/useDevTools.svelte.d.ts.map +1 -1
- package/dist/stores/{useIncremark.d.ts → useIncremark.svelte.d.ts} +35 -36
- package/dist/stores/useIncremark.svelte.d.ts.map +1 -0
- package/dist/stores/useIncremark.svelte.js +226 -0
- package/dist/stores/useLocale.svelte.d.ts.map +1 -1
- package/dist/stores/useLocale.svelte.js +3 -2
- package/dist/stores/useShiki.svelte.d.ts +26 -0
- package/dist/stores/useShiki.svelte.d.ts.map +1 -1
- package/dist/stores/useShiki.svelte.js +27 -0
- package/dist/stores/useTypewriter.svelte.d.ts +49 -0
- package/dist/stores/useTypewriter.svelte.d.ts.map +1 -0
- package/dist/stores/useTypewriter.svelte.js +212 -0
- package/dist/utils/cursor.d.ts.map +1 -1
- package/dist/utils/cursor.js +8 -0
- package/package.json +9 -6
- package/dist/context/definitionsContext.d.ts.map +0 -1
- package/dist/stores/useBlockTransformer.d.ts.map +0 -1
- package/dist/stores/useBlockTransformer.js +0 -110
- package/dist/stores/useIncremark.d.ts.map +0 -1
- package/dist/stores/useIncremark.js +0 -189
- package/dist/stores/useTypewriter.d.ts +0 -44
- package/dist/stores/useTypewriter.d.ts.map +0 -1
- 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/
|
|
3
|
-
import {
|
|
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
|
-
|
|
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 +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;
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
61
|
+
incremark ? incremark.blocks : (blocks ?? [])
|
|
92
62
|
)
|
|
93
63
|
|
|
94
64
|
// 计算是否显示完成
|
|
95
65
|
const displayComplete = $derived(
|
|
96
|
-
|
|
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 &&
|
|
93
|
+
{#if displayComplete && footnoteOrder.length > 0}
|
|
121
94
|
<IncremarkFootnotes />
|
|
122
95
|
{/if}
|
|
123
96
|
</div>
|
|
@@ -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,
|
|
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 = '
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
{
|
|
86
|
-
{
|
|
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;
|
|
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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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;
|
|
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
|
|
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
|
-
//
|
|
27
|
-
|
|
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
|
-
|
|
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 ===
|
|
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={
|
|
108
|
-
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;
|
|
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 =
|
|
21
|
-
const definitions =
|
|
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":"
|
|
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"}
|