@incremark/svelte 0.2.5 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/AutoScrollContainer.svelte +18 -18
- package/dist/components/Incremark.svelte +37 -74
- package/dist/components/Incremark.svelte.d.ts +9 -2
- package/dist/components/Incremark.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkBlockquote.svelte +3 -7
- package/dist/components/IncremarkBlockquote.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkCode.svelte +44 -124
- package/dist/components/IncremarkCode.svelte.d.ts +6 -0
- package/dist/components/IncremarkCode.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkContent.svelte +114 -0
- package/dist/components/IncremarkContent.svelte.d.ts +6 -0
- package/dist/components/IncremarkContent.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkList.svelte +10 -12
- package/dist/components/IncremarkList.svelte.d.ts +0 -1
- package/dist/components/IncremarkList.svelte.d.ts.map +1 -1
- package/dist/components/IncremarkRenderer.svelte +18 -16
- package/dist/components/IncremarkRenderer.svelte.d.ts +3 -0
- package/dist/components/IncremarkRenderer.svelte.d.ts.map +1 -1
- package/dist/components/index.d.ts +2 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -0
- package/dist/components/types.d.ts +29 -3
- package/dist/components/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/stores/useDevTools.js +2 -2
- package/dist/stores/useIncremark.d.ts +4 -3
- package/dist/stores/useIncremark.d.ts.map +1 -1
- package/dist/stores/useIncremark.js +34 -27
- package/dist/stores/useShiki.svelte.d.ts +9 -0
- package/dist/stores/useShiki.svelte.d.ts.map +1 -0
- package/dist/stores/useShiki.svelte.js +98 -0
- package/dist/stores/useTypewriter.d.ts +1 -1
- package/dist/stores/useTypewriter.d.ts.map +1 -1
- package/dist/stores/useTypewriter.js +3 -3
- package/package.json +5 -5
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
<script lang="ts">
|
|
7
|
-
import { onMount, onDestroy } from 'svelte'
|
|
7
|
+
import { onMount, onDestroy, tick } from 'svelte'
|
|
8
8
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -117,24 +117,24 @@
|
|
|
117
117
|
lastScrollTop = containerRef.scrollTop
|
|
118
118
|
lastScrollHeight = containerRef.scrollHeight
|
|
119
119
|
|
|
120
|
-
observer = new MutationObserver(() => {
|
|
120
|
+
observer = new MutationObserver(async () => {
|
|
121
121
|
// 使用 tick 等待 DOM 更新
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
122
|
+
await tick()
|
|
123
|
+
|
|
124
|
+
if (!containerRef) return
|
|
125
|
+
|
|
126
|
+
// 如果没有滚动条,重置状态
|
|
127
|
+
if (!hasScrollbar()) {
|
|
128
|
+
isUserScrolledUp = false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 更新 scrollHeight 记录(内容变化后)
|
|
132
|
+
lastScrollHeight = containerRef.scrollHeight
|
|
133
|
+
|
|
134
|
+
// 自动滚动
|
|
135
|
+
if (enabled && !isUserScrolledUp) {
|
|
136
|
+
scrollToBottom()
|
|
137
|
+
}
|
|
138
138
|
})
|
|
139
139
|
|
|
140
140
|
observer.observe(containerRef, {
|
|
@@ -6,24 +6,14 @@
|
|
|
6
6
|
<script lang="ts">
|
|
7
7
|
import type { Component } from 'svelte'
|
|
8
8
|
import type { Readable } from 'svelte/store'
|
|
9
|
-
import type { RootContent } from '@incremark/core'
|
|
9
|
+
import type { RootContent, ParsedBlock } from '@incremark/core'
|
|
10
10
|
import type { HTML } from 'mdast'
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
import { getDefinitionsContext } from '../context/definitionsContext'
|
|
13
13
|
import type { UseIncremarkReturn } from '../stores/useIncremark'
|
|
14
|
-
import type { ComponentMap,
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
import IncremarkParagraph from './IncremarkParagraph.svelte'
|
|
18
|
-
import IncremarkHeading from './IncremarkHeading.svelte'
|
|
19
|
-
import IncremarkCode from './IncremarkCode.svelte'
|
|
20
|
-
import IncremarkList from './IncremarkList.svelte'
|
|
21
|
-
import IncremarkTable from './IncremarkTable.svelte'
|
|
22
|
-
import IncremarkBlockquote from './IncremarkBlockquote.svelte'
|
|
23
|
-
import IncremarkThematicBreak from './IncremarkThematicBreak.svelte'
|
|
24
|
-
import IncremarkMath from './IncremarkMath.svelte'
|
|
25
|
-
import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
|
|
26
|
-
import IncremarkDefault from './IncremarkDefault.svelte'
|
|
14
|
+
import type { ComponentMap, RenderableBlock } from './types'
|
|
15
|
+
|
|
16
|
+
// 导入组件
|
|
27
17
|
import IncremarkFootnotes from './IncremarkFootnotes.svelte'
|
|
28
18
|
import IncremarkRenderer from './IncremarkRenderer.svelte'
|
|
29
19
|
|
|
@@ -39,13 +29,18 @@
|
|
|
39
29
|
*/
|
|
40
30
|
interface Props {
|
|
41
31
|
/** 要渲染的块列表(来自 useIncremark 的 blocks) */
|
|
42
|
-
blocks?:
|
|
32
|
+
blocks?: RenderableBlock[] | Readable<RenderableBlock[]>
|
|
33
|
+
/** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
|
|
34
|
+
* 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
|
|
35
|
+
isDisplayComplete?: boolean
|
|
43
36
|
/** 自定义组件映射,key 为节点类型 */
|
|
44
37
|
components?: ComponentMap
|
|
45
38
|
/** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
|
|
46
39
|
customContainers?: Record<string, Component<any>>
|
|
47
40
|
/** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
|
|
48
41
|
customCodeBlocks?: Record<string, Component<any>>
|
|
42
|
+
/** 代码块配置映射,key 为代码语言名称 */
|
|
43
|
+
codeBlockConfigs?: Record<string, { takeOver?: boolean }>
|
|
49
44
|
/** 待处理块的样式类名 */
|
|
50
45
|
pendingClass?: string
|
|
51
46
|
/** 已完成块的样式类名 */
|
|
@@ -58,9 +53,11 @@
|
|
|
58
53
|
|
|
59
54
|
let {
|
|
60
55
|
blocks = [],
|
|
56
|
+
isDisplayComplete = false,
|
|
61
57
|
components = {},
|
|
62
58
|
customContainers = {},
|
|
63
59
|
customCodeBlocks = {},
|
|
60
|
+
codeBlockConfigs = {},
|
|
64
61
|
pendingClass = 'incremark-pending',
|
|
65
62
|
completedClass = 'incremark-completed',
|
|
66
63
|
showBlockStatus = false,
|
|
@@ -70,83 +67,49 @@
|
|
|
70
67
|
const context = getDefinitionsContext();
|
|
71
68
|
const footnoteReferenceOrder = $derived(context?.footnoteReferenceOrder ?? []);
|
|
72
69
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const blocksArray = Array.isArray(blocks) ? blocks : []
|
|
81
|
-
return blocksArray.length > 0 && blocksArray.every(b => b.status === 'completed')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
// 默认组件映射
|
|
85
|
-
const defaultComponents: ComponentMap = {
|
|
86
|
-
paragraph: IncremarkParagraph,
|
|
87
|
-
heading: IncremarkHeading,
|
|
88
|
-
code: IncremarkCode,
|
|
89
|
-
list: IncremarkList,
|
|
90
|
-
table: IncremarkTable,
|
|
91
|
-
blockquote: IncremarkBlockquote,
|
|
92
|
-
thematicBreak: IncremarkThematicBreak,
|
|
93
|
-
math: IncremarkMath,
|
|
94
|
-
inlineMath: IncremarkMath,
|
|
95
|
-
htmlElement: IncremarkHtmlElement
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 合并用户组件和默认组件
|
|
99
|
-
const mergedComponents = $derived({
|
|
100
|
-
...defaultComponents,
|
|
101
|
-
...components
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
// 处理 blocks(可能是 store 或数组)
|
|
105
|
-
const blocksArray = $derived.by(() => {
|
|
106
|
-
if (incremark) {
|
|
107
|
-
// 如果提供了 incremark,在模板中直接使用 incremark.blocks(store)
|
|
108
|
-
return []
|
|
109
|
-
}
|
|
110
|
-
return Array.isArray(blocks) ? blocks : []
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// 提取 incremark 的 stores(如果存在)
|
|
114
|
-
const incremarkBlocks = $derived.by(() => incremark?.blocks)
|
|
115
|
-
const incremarkIsDisplayComplete = $derived.by(() => incremark?.isDisplayComplete)
|
|
70
|
+
// 当使用 incremark 时,从 incremark 对象中提取 blocks 和 isDisplayComplete
|
|
71
|
+
const incremarkBlocks = $derived(
|
|
72
|
+
incremark ? (incremark as any).blocks : []
|
|
73
|
+
);
|
|
74
|
+
const incremarkIsDisplayComplete = $derived(
|
|
75
|
+
incremark ? (incremark as any).isDisplayComplete : false
|
|
76
|
+
);
|
|
116
77
|
</script>
|
|
117
78
|
|
|
118
79
|
<div class="incremark">
|
|
119
80
|
<!-- 主要内容块 -->
|
|
120
|
-
{#if incremark
|
|
81
|
+
{#if incremark}
|
|
121
82
|
<!-- 使用 incremark 的 blocks store -->
|
|
122
|
-
{#each
|
|
123
|
-
{#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
|
|
83
|
+
{#each $incremarkBlocks as block (block.id)}
|
|
84
|
+
{#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
|
|
124
85
|
<div
|
|
125
|
-
class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as
|
|
86
|
+
class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as RenderableBlock).isLastPending ? 'incremark-last-pending' : ''}"
|
|
126
87
|
>
|
|
127
88
|
<!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
|
|
128
|
-
<IncremarkRenderer
|
|
129
|
-
node={block.node}
|
|
89
|
+
<IncremarkRenderer
|
|
90
|
+
node={(block as ParsedBlock).node}
|
|
130
91
|
customContainers={customContainers}
|
|
131
92
|
customCodeBlocks={customCodeBlocks}
|
|
132
|
-
|
|
93
|
+
codeBlockConfigs={codeBlockConfigs}
|
|
94
|
+
blockStatus={(block as ParsedBlock).status}
|
|
133
95
|
/>
|
|
134
96
|
</div>
|
|
135
97
|
{/if}
|
|
136
98
|
{/each}
|
|
137
99
|
{:else}
|
|
138
100
|
<!-- 使用传入的 blocks 数组 -->
|
|
139
|
-
{#each
|
|
140
|
-
{#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
|
|
101
|
+
{#each (Array.isArray(blocks) ? blocks : []) as block (block.id)}
|
|
102
|
+
{#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
|
|
141
103
|
<div
|
|
142
|
-
class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {block.isLastPending ? 'incremark-last-pending' : ''}"
|
|
104
|
+
class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as RenderableBlock).isLastPending ? 'incremark-last-pending' : ''}"
|
|
143
105
|
>
|
|
144
106
|
<!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
|
|
145
|
-
<IncremarkRenderer
|
|
146
|
-
node={block.node}
|
|
107
|
+
<IncremarkRenderer
|
|
108
|
+
node={(block as ParsedBlock).node}
|
|
147
109
|
customContainers={customContainers}
|
|
148
110
|
customCodeBlocks={customCodeBlocks}
|
|
149
|
-
|
|
111
|
+
codeBlockConfigs={codeBlockConfigs}
|
|
112
|
+
blockStatus={(block as ParsedBlock).status}
|
|
150
113
|
/>
|
|
151
114
|
</div>
|
|
152
115
|
{/if}
|
|
@@ -154,12 +117,12 @@
|
|
|
154
117
|
{/if}
|
|
155
118
|
|
|
156
119
|
<!-- 脚注列表(仅在内容完全显示后显示) -->
|
|
157
|
-
{#if incremark && incremarkIsDisplayComplete && footnoteReferenceOrder
|
|
120
|
+
{#if incremark && $incremarkIsDisplayComplete && $footnoteReferenceOrder}
|
|
158
121
|
{@const footnoteOrder = $footnoteReferenceOrder ?? []}
|
|
159
122
|
{#if footnoteOrder.length > 0}
|
|
160
123
|
<IncremarkFootnotes />
|
|
161
124
|
{/if}
|
|
162
|
-
{:else if !incremark &&
|
|
125
|
+
{:else if !incremark && isDisplayComplete && $footnoteReferenceOrder}
|
|
163
126
|
{@const footnoteOrder = $footnoteReferenceOrder ?? []}
|
|
164
127
|
{#if footnoteOrder.length > 0}
|
|
165
128
|
<IncremarkFootnotes />
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
2
|
import type { Readable } from 'svelte/store';
|
|
3
3
|
import type { UseIncremarkReturn } from '../stores/useIncremark';
|
|
4
|
-
import type { ComponentMap,
|
|
4
|
+
import type { ComponentMap, RenderableBlock } from './types';
|
|
5
5
|
/**
|
|
6
6
|
* 组件 Props
|
|
7
7
|
*/
|
|
8
8
|
interface Props {
|
|
9
9
|
/** 要渲染的块列表(来自 useIncremark 的 blocks) */
|
|
10
|
-
blocks?:
|
|
10
|
+
blocks?: RenderableBlock[] | Readable<RenderableBlock[]>;
|
|
11
|
+
/** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
|
|
12
|
+
* 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
|
|
13
|
+
isDisplayComplete?: boolean;
|
|
11
14
|
/** 自定义组件映射,key 为节点类型 */
|
|
12
15
|
components?: ComponentMap;
|
|
13
16
|
/** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
|
|
14
17
|
customContainers?: Record<string, Component<any>>;
|
|
15
18
|
/** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
|
|
16
19
|
customCodeBlocks?: Record<string, Component<any>>;
|
|
20
|
+
/** 代码块配置映射,key 为代码语言名称 */
|
|
21
|
+
codeBlockConfigs?: Record<string, {
|
|
22
|
+
takeOver?: boolean;
|
|
23
|
+
}>;
|
|
17
24
|
/** 待处理块的样式类名 */
|
|
18
25
|
pendingClass?: string;
|
|
19
26
|
/** 已完成块的样式类名 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,
|
|
1
|
+
{"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAM1D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,eAAe,EAAE,GAAG,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAA;IACxD;uEACmE;IACnE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,gBAAgB;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA2FH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
@file IncremarkBlockquote.svelte - 引用组件
|
|
3
|
-
@description 渲染 Markdown
|
|
3
|
+
@description 渲染 Markdown 引用块,支持所有块级内容
|
|
4
4
|
-->
|
|
5
5
|
|
|
6
6
|
<script lang="ts">
|
|
7
7
|
import type { Blockquote } from 'mdast'
|
|
8
|
-
import
|
|
8
|
+
import IncremarkRenderer from './IncremarkRenderer.svelte'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 组件 Props
|
|
@@ -20,11 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
<blockquote class="incremark-blockquote">
|
|
22
22
|
{#each node.children as child, index (index)}
|
|
23
|
-
{
|
|
24
|
-
<IncremarkParagraph node={child} />
|
|
25
|
-
{:else}
|
|
26
|
-
<div class="unknown-child">{child.type}</div>
|
|
27
|
-
{/if}
|
|
23
|
+
<IncremarkRenderer node={child} />
|
|
28
24
|
{/each}
|
|
29
25
|
</blockquote>
|
|
30
26
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IncremarkBlockquote.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkBlockquote.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIrC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,UAAU,CAAA;CACjB;
|
|
1
|
+
{"version":3,"file":"IncremarkBlockquote.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkBlockquote.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIrC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,UAAU,CAAA;CACjB;AAoBH,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
@file IncremarkCode.svelte - 代码块组件
|
|
3
|
-
@description 渲染 Markdown 代码块,支持 Shiki 代码高亮和 Mermaid 图表渲染
|
|
4
|
-
-->
|
|
5
|
-
|
|
6
1
|
<script lang="ts">
|
|
7
2
|
import type { Code } from 'mdast'
|
|
8
|
-
import {
|
|
3
|
+
import { useShiki } from '../stores/useShiki.svelte'
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
6
|
* 组件 Props
|
|
@@ -15,12 +10,16 @@
|
|
|
15
10
|
node: Code
|
|
16
11
|
/** Shiki 主题,默认 github-dark */
|
|
17
12
|
theme?: string
|
|
13
|
+
/** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
|
|
14
|
+
fallbackTheme?: string
|
|
18
15
|
/** 是否禁用代码高亮 */
|
|
19
16
|
disableHighlight?: boolean
|
|
20
17
|
/** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
21
18
|
mermaidDelay?: number
|
|
22
19
|
/** 自定义代码块组件映射,key 为代码语言名称 */
|
|
23
20
|
customCodeBlocks?: Record<string, any>
|
|
21
|
+
/** 代码块配置映射,key 为代码语言名称 */
|
|
22
|
+
codeBlockConfigs?: Record<string, { takeOver?: boolean }>
|
|
24
23
|
/** 块状态,用于判断是否使用自定义组件 */
|
|
25
24
|
blockStatus?: 'pending' | 'stable' | 'completed'
|
|
26
25
|
}
|
|
@@ -28,17 +27,17 @@
|
|
|
28
27
|
let {
|
|
29
28
|
node,
|
|
30
29
|
theme = 'github-dark',
|
|
30
|
+
fallbackTheme = 'github-dark',
|
|
31
31
|
disableHighlight = false,
|
|
32
32
|
mermaidDelay = 500,
|
|
33
33
|
customCodeBlocks,
|
|
34
|
+
codeBlockConfigs,
|
|
34
35
|
blockStatus = 'completed'
|
|
35
36
|
}: Props = $props()
|
|
36
37
|
|
|
37
38
|
// 状态
|
|
38
39
|
let copied = $state(false)
|
|
39
40
|
let highlightedHtml = $state('')
|
|
40
|
-
let isHighlighting = $state(false)
|
|
41
|
-
let highlightError = $state(false)
|
|
42
41
|
|
|
43
42
|
// Mermaid 支持
|
|
44
43
|
let mermaidSvg = $state('')
|
|
@@ -49,10 +48,8 @@
|
|
|
49
48
|
// 视图模式:'preview' | 'source'
|
|
50
49
|
let mermaidViewMode = $state<'preview' | 'source'>('preview')
|
|
51
50
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
const loadedLanguages = new Set<string>()
|
|
55
|
-
const loadedThemes = new Set<string>()
|
|
51
|
+
// 使用 Shiki 单例管理器 - 修复:传入 getter 闭包以保持响应式
|
|
52
|
+
const shiki = useShiki(() => theme)
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
55
|
* 计算属性
|
|
@@ -63,15 +60,16 @@
|
|
|
63
60
|
|
|
64
61
|
// 检查是否有自定义代码块组件
|
|
65
62
|
const CustomCodeBlock = $derived.by(() => {
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return customCodeBlocks?.[language] || null
|
|
71
|
-
})
|
|
63
|
+
const component = customCodeBlocks?.[language]
|
|
64
|
+
if (!component) return null
|
|
65
|
+
|
|
66
|
+
const config = codeBlockConfigs?.[language]
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
if (config?.takeOver) return component
|
|
69
|
+
if (blockStatus !== 'completed') return null
|
|
70
|
+
|
|
71
|
+
return component
|
|
72
|
+
})
|
|
75
73
|
|
|
76
74
|
/**
|
|
77
75
|
* 切换 Mermaid 视图模式
|
|
@@ -80,54 +78,30 @@
|
|
|
80
78
|
mermaidViewMode = mermaidViewMode === 'preview' ? 'source' : 'preview'
|
|
81
79
|
}
|
|
82
80
|
|
|
83
|
-
/**
|
|
84
|
-
* Mermaid 渲染(带防抖动)
|
|
85
|
-
*/
|
|
86
|
-
function scheduleRenderMermaid() {
|
|
87
|
-
if (!isMermaid || !code) return
|
|
88
|
-
|
|
89
|
-
// 清除之前的定时器
|
|
90
|
-
if (mermaidTimer) {
|
|
91
|
-
clearTimeout(mermaidTimer)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// 显示加载状态
|
|
95
|
-
mermaidLoading = true
|
|
96
|
-
|
|
97
|
-
// 防抖动延迟渲染
|
|
98
|
-
mermaidTimer = setTimeout(() => {
|
|
99
|
-
doRenderMermaid()
|
|
100
|
-
}, mermaidDelay)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
81
|
/**
|
|
104
82
|
* 执行 Mermaid 渲染
|
|
105
83
|
*/
|
|
106
84
|
async function doRenderMermaid() {
|
|
107
85
|
if (!code) return
|
|
108
|
-
|
|
109
86
|
mermaidError = ''
|
|
110
87
|
|
|
111
88
|
try {
|
|
112
|
-
// 动态导入 mermaid
|
|
113
89
|
if (!mermaidRef) {
|
|
114
|
-
// @ts-ignore
|
|
90
|
+
// @ts-ignore
|
|
115
91
|
const mermaidModule = await import('mermaid')
|
|
116
92
|
mermaidRef = mermaidModule.default
|
|
117
93
|
mermaidRef.initialize({
|
|
118
94
|
startOnLoad: false,
|
|
119
95
|
theme: 'dark',
|
|
120
|
-
securityLevel: 'loose'
|
|
96
|
+
securityLevel: 'loose',
|
|
97
|
+
suppressErrorRendering: true
|
|
121
98
|
})
|
|
122
99
|
}
|
|
123
100
|
|
|
124
|
-
const mermaid = mermaidRef
|
|
125
101
|
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
126
|
-
|
|
127
|
-
const { svg } = await mermaid.render(id, code)
|
|
102
|
+
const { svg } = await mermaidRef.render(id, code)
|
|
128
103
|
mermaidSvg = svg
|
|
129
104
|
} catch (e: any) {
|
|
130
|
-
// 不显示错误,可能是代码还不完整
|
|
131
105
|
mermaidError = ''
|
|
132
106
|
mermaidSvg = ''
|
|
133
107
|
} finally {
|
|
@@ -138,9 +112,12 @@
|
|
|
138
112
|
/**
|
|
139
113
|
* 动态加载 shiki 并高亮
|
|
140
114
|
*/
|
|
141
|
-
async function
|
|
115
|
+
async function doHighlight() {
|
|
142
116
|
if (isMermaid) {
|
|
143
|
-
|
|
117
|
+
// Mermaid 防抖逻辑
|
|
118
|
+
if (mermaidTimer) clearTimeout(mermaidTimer)
|
|
119
|
+
mermaidLoading = true
|
|
120
|
+
mermaidTimer = setTimeout(doRenderMermaid, mermaidDelay)
|
|
144
121
|
return
|
|
145
122
|
}
|
|
146
123
|
|
|
@@ -149,54 +126,12 @@
|
|
|
149
126
|
return
|
|
150
127
|
}
|
|
151
128
|
|
|
152
|
-
isHighlighting = true
|
|
153
|
-
highlightError = false
|
|
154
|
-
|
|
155
129
|
try {
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
const { createHighlighter } = await import('shiki')
|
|
159
|
-
highlighterRef = await createHighlighter({
|
|
160
|
-
themes: [theme as any],
|
|
161
|
-
langs: []
|
|
162
|
-
})
|
|
163
|
-
loadedThemes.add(theme)
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const highlighter = highlighterRef
|
|
167
|
-
const lang = language
|
|
168
|
-
|
|
169
|
-
// 按需加载语言
|
|
170
|
-
if (!loadedLanguages.has(lang) && lang !== 'text') {
|
|
171
|
-
try {
|
|
172
|
-
await highlighter.loadLanguage(lang)
|
|
173
|
-
loadedLanguages.add(lang)
|
|
174
|
-
} catch {
|
|
175
|
-
// 语言不支持,标记但不阻止
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// 按需加载主题
|
|
180
|
-
if (!loadedThemes.has(theme)) {
|
|
181
|
-
try {
|
|
182
|
-
await highlighter.loadTheme(theme)
|
|
183
|
-
loadedThemes.add(theme)
|
|
184
|
-
} catch {
|
|
185
|
-
// 主题不支持
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const html = highlighter.codeToHtml(code, {
|
|
190
|
-
lang: loadedLanguages.has(lang) ? lang : 'text',
|
|
191
|
-
theme: loadedThemes.has(theme) ? theme : 'github-dark'
|
|
192
|
-
})
|
|
130
|
+
// 调用经过修复的 shiki.highlight
|
|
131
|
+
const html = await shiki.highlight(code, language, fallbackTheme)
|
|
193
132
|
highlightedHtml = html
|
|
194
133
|
} catch (e) {
|
|
195
|
-
// Shiki 不可用或加载失败
|
|
196
|
-
highlightError = true
|
|
197
134
|
highlightedHtml = ''
|
|
198
|
-
} finally {
|
|
199
|
-
isHighlighting = false
|
|
200
135
|
}
|
|
201
136
|
}
|
|
202
137
|
|
|
@@ -207,35 +142,29 @@
|
|
|
207
142
|
try {
|
|
208
143
|
await navigator.clipboard.writeText(code)
|
|
209
144
|
copied = true
|
|
210
|
-
setTimeout(() => {
|
|
211
|
-
|
|
212
|
-
}, 2000)
|
|
213
|
-
} catch {
|
|
214
|
-
// 复制失败静默处理
|
|
215
|
-
}
|
|
145
|
+
setTimeout(() => { copied = false }, 2000)
|
|
146
|
+
} catch { /* 静默处理 */ }
|
|
216
147
|
}
|
|
217
148
|
|
|
218
|
-
//
|
|
149
|
+
// 监听代码、主题、语言变化并重新渲染
|
|
219
150
|
$effect(() => {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (mermaidTimer) {
|
|
226
|
-
clearTimeout(mermaidTimer)
|
|
151
|
+
doHighlight()
|
|
152
|
+
|
|
153
|
+
// 返回清理函数取代 onDestroy
|
|
154
|
+
return () => {
|
|
155
|
+
if (mermaidTimer) clearTimeout(mermaidTimer)
|
|
227
156
|
}
|
|
228
157
|
})
|
|
229
158
|
</script>
|
|
230
159
|
|
|
231
|
-
|
|
232
|
-
{
|
|
233
|
-
<
|
|
234
|
-
this={CustomCodeBlock}
|
|
160
|
+
{#if CustomCodeBlock}
|
|
161
|
+
{@const Component = CustomCodeBlock}
|
|
162
|
+
<Component
|
|
235
163
|
codeStr={code}
|
|
236
|
-
lang={language}
|
|
164
|
+
lang={language}
|
|
165
|
+
completed={blockStatus === 'completed'}
|
|
166
|
+
takeOver={codeBlockConfigs?.[language]?.takeOver}
|
|
237
167
|
/>
|
|
238
|
-
<!-- Mermaid 图表 -->
|
|
239
168
|
{:else if isMermaid}
|
|
240
169
|
<div class="incremark-mermaid">
|
|
241
170
|
<div class="mermaid-header">
|
|
@@ -255,23 +184,18 @@
|
|
|
255
184
|
</div>
|
|
256
185
|
</div>
|
|
257
186
|
<div class="mermaid-content">
|
|
258
|
-
<!-- 加载中 -->
|
|
259
187
|
{#if mermaidLoading && !mermaidSvg}
|
|
260
188
|
<pre class="mermaid-source-code">{code}</pre>
|
|
261
|
-
<!-- 源码模式 -->
|
|
262
189
|
{:else if mermaidViewMode === 'source'}
|
|
263
190
|
<pre class="mermaid-source-code">{code}</pre>
|
|
264
|
-
<!-- 预览模式 -->
|
|
265
191
|
{:else if mermaidSvg}
|
|
266
192
|
{@html mermaidSvg}
|
|
267
|
-
<!-- 无法渲染时显示源码 -->
|
|
268
193
|
{:else}
|
|
269
194
|
<pre class="mermaid-source-code">{code}</pre>
|
|
270
195
|
{/if}
|
|
271
196
|
</div>
|
|
272
197
|
</div>
|
|
273
198
|
{:else}
|
|
274
|
-
<!-- 普通代码块 -->
|
|
275
199
|
<div class="incremark-code">
|
|
276
200
|
<div class="code-header">
|
|
277
201
|
<span class="language">{language}</span>
|
|
@@ -280,21 +204,17 @@
|
|
|
280
204
|
</button>
|
|
281
205
|
</div>
|
|
282
206
|
<div class="code-content">
|
|
283
|
-
|
|
284
|
-
{#if isHighlighting && !highlightedHtml}
|
|
207
|
+
{#if shiki.isHighlighting && !highlightedHtml}
|
|
285
208
|
<div class="code-loading">
|
|
286
209
|
<pre><code>{code}</code></pre>
|
|
287
210
|
</div>
|
|
288
|
-
<!-- 高亮后的代码 -->
|
|
289
211
|
{:else if highlightedHtml}
|
|
290
212
|
<div class="shiki-wrapper">
|
|
291
213
|
{@html highlightedHtml}
|
|
292
214
|
</div>
|
|
293
|
-
<!-- 回退:无高亮 -->
|
|
294
215
|
{:else}
|
|
295
216
|
<pre class="code-fallback"><code>{code}</code></pre>
|
|
296
217
|
{/if}
|
|
297
218
|
</div>
|
|
298
219
|
</div>
|
|
299
220
|
{/if}
|
|
300
|
-
|
|
@@ -7,12 +7,18 @@ interface Props {
|
|
|
7
7
|
node: Code;
|
|
8
8
|
/** Shiki 主题,默认 github-dark */
|
|
9
9
|
theme?: string;
|
|
10
|
+
/** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
|
|
11
|
+
fallbackTheme?: string;
|
|
10
12
|
/** 是否禁用代码高亮 */
|
|
11
13
|
disableHighlight?: boolean;
|
|
12
14
|
/** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
13
15
|
mermaidDelay?: number;
|
|
14
16
|
/** 自定义代码块组件映射,key 为代码语言名称 */
|
|
15
17
|
customCodeBlocks?: Record<string, any>;
|
|
18
|
+
/** 代码块配置映射,key 为代码语言名称 */
|
|
19
|
+
codeBlockConfigs?: Record<string, {
|
|
20
|
+
takeOver?: boolean;
|
|
21
|
+
}>;
|
|
16
22
|
/** 块状态,用于判断是否使用自定义组件 */
|
|
17
23
|
blockStatus?: 'pending' | 'stable' | 'completed';
|
|
18
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAI/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,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,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;
|
|
1
|
+
{"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAI/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AAiMH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|