@incremark/svelte 0.3.1 → 0.3.3
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 +125 -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 +14 -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/IncremarkMath.svelte +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 +6 -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} +31 -34
- 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 -208
- 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,125 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
CachedCodeRenderer - 使用 shiki-stream 实现流式代码高亮
|
|
3
|
+
|
|
4
|
+
基于 shiki-stream 的设计模式,适配 Svelte 5:
|
|
5
|
+
- 在脚本顶层创建和消费 stream(类似 Vue setup)
|
|
6
|
+
- 使用 $state 存储 tokens
|
|
7
|
+
- 使用 $effect 监听 code 变化
|
|
8
|
+
- SSR 兼容:Web Streams API 只在浏览器中使用
|
|
9
|
+
-->
|
|
10
|
+
<script lang="ts">
|
|
11
|
+
import type { ThemedToken } from '@shikijs/core'
|
|
12
|
+
import { onDestroy, untrack } from 'svelte'
|
|
13
|
+
import { CodeToTokenTransformStream } from 'shiki-stream'
|
|
14
|
+
import { getTokenStyleObject } from '@shikijs/core'
|
|
15
|
+
import { objectId } from '@antfu/utils'
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
code: string
|
|
19
|
+
lang: string
|
|
20
|
+
theme: string
|
|
21
|
+
highlighter: any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let { code, lang, theme, highlighter }: Props = $props()
|
|
25
|
+
|
|
26
|
+
// SSR 检测:Web Streams API 只在浏览器中可用
|
|
27
|
+
const isBrowser = typeof window !== 'undefined'
|
|
28
|
+
|
|
29
|
+
// Stream 错误状态
|
|
30
|
+
let hasStreamError = $state(false)
|
|
31
|
+
|
|
32
|
+
// Tokens 数组(与 Vue 的 reactive 数组对齐)
|
|
33
|
+
let tokens = $state<ThemedToken[]>([])
|
|
34
|
+
|
|
35
|
+
// 已处理的 code 长度
|
|
36
|
+
let index = $state(0)
|
|
37
|
+
|
|
38
|
+
// Stream 相关状态(只在浏览器中初始化)
|
|
39
|
+
let controller: ReadableStreamController<string> | null = null
|
|
40
|
+
let tokenStream: ReadableStream<any> | null = null
|
|
41
|
+
|
|
42
|
+
// 只在浏览器环境中创建 stream
|
|
43
|
+
if (isBrowser) {
|
|
44
|
+
const textStream = new ReadableStream<string>({
|
|
45
|
+
start(_controller) {
|
|
46
|
+
controller = _controller
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const initialHighlighter = untrack(() => highlighter)
|
|
52
|
+
const initialLang = untrack(() => lang)
|
|
53
|
+
const initialTheme = untrack(() => theme)
|
|
54
|
+
|
|
55
|
+
tokenStream = textStream.pipeThrough(
|
|
56
|
+
new CodeToTokenTransformStream({
|
|
57
|
+
highlighter: initialHighlighter,
|
|
58
|
+
lang: initialLang,
|
|
59
|
+
theme: initialTheme,
|
|
60
|
+
allowRecalls: true
|
|
61
|
+
})
|
|
62
|
+
)
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Failed to create token stream:', error)
|
|
65
|
+
hasStreamError = true
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 消费 token stream
|
|
69
|
+
if (tokenStream) {
|
|
70
|
+
tokenStream.pipeTo(
|
|
71
|
+
new WritableStream({
|
|
72
|
+
write(token: any) {
|
|
73
|
+
if ('recall' in token) {
|
|
74
|
+
tokens.splice(tokens.length - token.recall, token.recall)
|
|
75
|
+
} else {
|
|
76
|
+
tokens.push(token)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
).catch((error) => {
|
|
81
|
+
console.error('Stream error:', error)
|
|
82
|
+
hasStreamError = true
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 监听 code 变化,增量推送到流中(与 Vue 的 watchEffect 对齐)
|
|
88
|
+
$effect(() => {
|
|
89
|
+
if (!isBrowser || hasStreamError || !controller) return
|
|
90
|
+
|
|
91
|
+
// 读取 code 的值来建立依赖
|
|
92
|
+
const currentCode = code
|
|
93
|
+
|
|
94
|
+
// 使用 untrack 避免 index 建立依赖
|
|
95
|
+
const currentIndex = untrack(() => index)
|
|
96
|
+
|
|
97
|
+
// 只处理增量更新:传入新增的部分
|
|
98
|
+
if (currentCode.length > currentIndex) {
|
|
99
|
+
const incremental = currentCode.slice(currentIndex)
|
|
100
|
+
controller.enqueue(incremental as any)
|
|
101
|
+
index = currentCode.length
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// 组件卸载时清理
|
|
106
|
+
onDestroy(() => {
|
|
107
|
+
controller?.close()
|
|
108
|
+
})
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
{#if hasStreamError || !isBrowser || tokens.length === 0}
|
|
112
|
+
<!-- SSR 或错误时渲染原始代码 -->
|
|
113
|
+
<pre class="shiki incremark-code-stream"><code>{code}</code></pre>
|
|
114
|
+
{:else}
|
|
115
|
+
<!-- 正常渲染高亮代码(与 Vue/React 的渲染结构对齐) -->
|
|
116
|
+
{@const getStyle = (token: any) => {
|
|
117
|
+
const style = token.htmlStyle || getTokenStyleObject(token)
|
|
118
|
+
// 如果是对象,转换为 CSS 字符串
|
|
119
|
+
if (typeof style === 'object') {
|
|
120
|
+
return Object.entries(style).map(([k, v]) => `${k}: ${v}`).join('; ')
|
|
121
|
+
}
|
|
122
|
+
return style
|
|
123
|
+
}}
|
|
124
|
+
<pre class="shiki incremark-code-stream"><code>{#each tokens as token (objectId(token))}<span style={getStyle(token)}>{token.content}</span>{/each}</code></pre>
|
|
125
|
+
{/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;AAmHH,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 {
|
|
@@ -24,7 +23,7 @@
|
|
|
24
23
|
}: IncremarkContentProps = $props()
|
|
25
24
|
|
|
26
25
|
// 创建 incremark 实例(只创建一次)
|
|
27
|
-
const
|
|
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 ===
|
|
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={
|
|
138
|
-
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;
|
|
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"}
|
|
@@ -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
|
|
159
|
+
{#if definitions && definitions[node.identifier]}
|
|
160
160
|
<img
|
|
161
161
|
class="incremark-image incremark-reference-image"
|
|
162
|
-
src={
|
|
162
|
+
src={definitions[node.identifier].url}
|
|
163
163
|
alt={node.alt || ''}
|
|
164
|
-
title={
|
|
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
|
|
177
|
+
{#if definitions && definitions[node.identifier]}
|
|
178
178
|
<a
|
|
179
179
|
class="incremark-link incremark-reference-link"
|
|
180
|
-
href={
|
|
181
|
-
title={
|
|
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;
|
|
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"}
|