@incremark/svelte 0.2.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/LICENSE +22 -0
- package/README.md +91 -0
- package/dist/ThemeProvider.svelte +52 -0
- package/dist/ThemeProvider.svelte.d.ts +21 -0
- package/dist/ThemeProvider.svelte.d.ts.map +1 -0
- package/dist/components/AutoScrollContainer.svelte +163 -0
- package/dist/components/AutoScrollContainer.svelte.d.ts +17 -0
- package/dist/components/AutoScrollContainer.svelte.d.ts.map +1 -0
- package/dist/components/Incremark.svelte +183 -0
- package/dist/components/Incremark.svelte.d.ts +24 -0
- package/dist/components/Incremark.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkBlockquote.svelte +30 -0
- package/dist/components/IncremarkBlockquote.svelte.d.ts +12 -0
- package/dist/components/IncremarkBlockquote.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkCode.svelte +275 -0
- package/dist/components/IncremarkCode.svelte.d.ts +18 -0
- package/dist/components/IncremarkCode.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkDefault.svelte +24 -0
- package/dist/components/IncremarkDefault.svelte.d.ts +12 -0
- package/dist/components/IncremarkDefault.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkFootnotes.svelte +84 -0
- package/dist/components/IncremarkFootnotes.svelte.d.ts +4 -0
- package/dist/components/IncremarkFootnotes.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkHeading.svelte +35 -0
- package/dist/components/IncremarkHeading.svelte.d.ts +12 -0
- package/dist/components/IncremarkHeading.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkHtmlElement.svelte +138 -0
- package/dist/components/IncremarkHtmlElement.svelte.d.ts +27 -0
- package/dist/components/IncremarkHtmlElement.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkInline.svelte +233 -0
- package/dist/components/IncremarkInline.svelte.d.ts +13 -0
- package/dist/components/IncremarkInline.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkList.svelte +85 -0
- package/dist/components/IncremarkList.svelte.d.ts +12 -0
- package/dist/components/IncremarkList.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkMath.svelte +137 -0
- package/dist/components/IncremarkMath.svelte.d.ts +24 -0
- package/dist/components/IncremarkMath.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkParagraph.svelte +24 -0
- package/dist/components/IncremarkParagraph.svelte.d.ts +12 -0
- package/dist/components/IncremarkParagraph.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkRenderer.svelte +70 -0
- package/dist/components/IncremarkRenderer.svelte.d.ts +12 -0
- package/dist/components/IncremarkRenderer.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkTable.svelte +79 -0
- package/dist/components/IncremarkTable.svelte.d.ts +12 -0
- package/dist/components/IncremarkTable.svelte.d.ts.map +1 -0
- package/dist/components/IncremarkThematicBreak.svelte +11 -0
- package/dist/components/IncremarkThematicBreak.svelte.d.ts +19 -0
- package/dist/components/IncremarkThematicBreak.svelte.d.ts.map +1 -0
- package/dist/components/index.d.ts +21 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +19 -0
- package/dist/components/types.d.ts +18 -0
- package/dist/components/types.d.ts.map +1 -0
- package/dist/components/types.js +5 -0
- package/dist/context/definitionsContext.d.ts +64 -0
- package/dist/context/definitionsContext.d.ts.map +1 -0
- package/dist/context/definitionsContext.js +117 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/stores/useBlockTransformer.d.ts +89 -0
- package/dist/stores/useBlockTransformer.d.ts.map +1 -0
- package/dist/stores/useBlockTransformer.js +110 -0
- package/dist/stores/useDevTools.d.ts +33 -0
- package/dist/stores/useDevTools.d.ts.map +1 -0
- package/dist/stores/useDevTools.js +53 -0
- package/dist/stores/useIncremark.d.ts +128 -0
- package/dist/stores/useIncremark.d.ts.map +1 -0
- package/dist/stores/useIncremark.js +177 -0
- package/dist/stores/useTypewriter.d.ts +42 -0
- package/dist/stores/useTypewriter.d.ts.map +1 -0
- package/dist/stores/useTypewriter.js +153 -0
- package/dist/svelte-runes.d.ts +32 -0
- package/dist/utils/cursor.d.ts +14 -0
- package/dist/utils/cursor.d.ts.map +1 -0
- package/dist/utils/cursor.js +36 -0
- package/package.json +70 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkInline.svelte - 行内元素组件
|
|
3
|
+
@description 渲染行内 Markdown 元素(文本、链接、加粗、斜体等)
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { Readable } from 'svelte/store'
|
|
8
|
+
import type { PhrasingContent, ImageReference, LinkReference } from 'mdast'
|
|
9
|
+
import type { TextChunk } from '@incremark/core'
|
|
10
|
+
import {
|
|
11
|
+
type TextNodeWithChunks,
|
|
12
|
+
hasChunks,
|
|
13
|
+
getStableText,
|
|
14
|
+
isHtmlNode
|
|
15
|
+
} from '@incremark/shared'
|
|
16
|
+
import { getDefinitionsContext } from '../context/definitionsContext'
|
|
17
|
+
import IncremarkMath from './IncremarkMath.svelte'
|
|
18
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
|
|
19
|
+
import IncremarkInline from './IncremarkInline.svelte'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 组件 Props
|
|
23
|
+
*/
|
|
24
|
+
interface Props {
|
|
25
|
+
/** 行内节点列表 */
|
|
26
|
+
nodes: PhrasingContent[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let { nodes }: Props = $props()
|
|
30
|
+
|
|
31
|
+
// Math 节点类型
|
|
32
|
+
interface MathNode {
|
|
33
|
+
type: 'math' | 'inlineMath'
|
|
34
|
+
value: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// HtmlElement 节点类型
|
|
38
|
+
interface HtmlElementNode {
|
|
39
|
+
type: 'htmlElement'
|
|
40
|
+
tagName: string
|
|
41
|
+
attrs: Record<string, string>
|
|
42
|
+
children: PhrasingContent[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 类型守卫:检查是否是 htmlElement 节点
|
|
47
|
+
*/
|
|
48
|
+
function isHtmlElementNode(node: PhrasingContent): node is PhrasingContent & HtmlElementNode {
|
|
49
|
+
return (node as unknown as HtmlElementNode).type === 'htmlElement'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 类型守卫:检查是否是 imageReference 节点
|
|
54
|
+
*/
|
|
55
|
+
function isImageReference(node: PhrasingContent): node is ImageReference {
|
|
56
|
+
return node.type === 'imageReference'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 类型守卫:检查是否是 linkReference 节点
|
|
61
|
+
*/
|
|
62
|
+
function isLinkReference(node: PhrasingContent): node is LinkReference {
|
|
63
|
+
return node.type === 'linkReference'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 类型守卫:检查是否是 inlineMath 节点
|
|
68
|
+
*/
|
|
69
|
+
function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode {
|
|
70
|
+
return (node as unknown as MathNode).type === 'inlineMath'
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 获取 definitions context(可能不存在)
|
|
74
|
+
// 使用 $derived 来确保响应式
|
|
75
|
+
const definations = $derived.by(() => {
|
|
76
|
+
try {
|
|
77
|
+
const context = getDefinitionsContext()
|
|
78
|
+
return context.definations
|
|
79
|
+
} catch {
|
|
80
|
+
// Context 不存在,返回 null
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const footnoteDefinitions = $derived.by(() => {
|
|
86
|
+
try {
|
|
87
|
+
const context = getDefinitionsContext()
|
|
88
|
+
return context.footnoteDefinitions
|
|
89
|
+
} catch {
|
|
90
|
+
// Context 不存在,返回 null
|
|
91
|
+
return null
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 获取节点的 chunks(类型安全)
|
|
97
|
+
*/
|
|
98
|
+
function getChunks(node: PhrasingContent): TextChunk[] | undefined {
|
|
99
|
+
if (hasChunks(node)) {
|
|
100
|
+
return (node as TextNodeWithChunks).chunks
|
|
101
|
+
}
|
|
102
|
+
return undefined
|
|
103
|
+
}
|
|
104
|
+
</script>
|
|
105
|
+
|
|
106
|
+
{#each nodes as node, idx (idx)}
|
|
107
|
+
<!-- 文本(支持 chunks 渐入动画) -->
|
|
108
|
+
{#if node.type === 'text'}
|
|
109
|
+
<!-- 稳定文本(已经显示过的部分,无动画) -->
|
|
110
|
+
{getStableText(node as TextNodeWithChunks)}
|
|
111
|
+
<!-- 新增的 chunk 部分(带渐入动画) -->
|
|
112
|
+
{#each getChunks(node) || [] as chunk (chunk.createdAt)}
|
|
113
|
+
<span class="incremark-fade-in">{chunk.text}</span>
|
|
114
|
+
{/each}
|
|
115
|
+
{/if}
|
|
116
|
+
|
|
117
|
+
<!-- 行内公式 -->
|
|
118
|
+
{#if isInlineMath(node)}
|
|
119
|
+
<IncremarkMath node={node as unknown as MathNode} />
|
|
120
|
+
{/if}
|
|
121
|
+
|
|
122
|
+
<!-- htmlElement 节点(结构化的 HTML 元素) -->
|
|
123
|
+
{#if isHtmlElementNode(node)}
|
|
124
|
+
<IncremarkHtmlElement node={node as unknown as HtmlElementNode} />
|
|
125
|
+
{/if}
|
|
126
|
+
|
|
127
|
+
<!-- HTML 节点(原始 HTML,如未启用 htmlTree 选项) -->
|
|
128
|
+
{#if isHtmlNode(node)}
|
|
129
|
+
{@html (node as any).value}
|
|
130
|
+
{/if}
|
|
131
|
+
|
|
132
|
+
<!-- 加粗 -->
|
|
133
|
+
{#if node.type === 'strong'}
|
|
134
|
+
<strong>
|
|
135
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
136
|
+
</strong>
|
|
137
|
+
{/if}
|
|
138
|
+
|
|
139
|
+
<!-- 斜体 -->
|
|
140
|
+
{#if node.type === 'emphasis'}
|
|
141
|
+
<em>
|
|
142
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
143
|
+
</em>
|
|
144
|
+
{/if}
|
|
145
|
+
|
|
146
|
+
<!-- 行内代码 -->
|
|
147
|
+
{#if node.type === 'inlineCode'}
|
|
148
|
+
<code class="incremark-inline-code">{(node as any).value}</code>
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
<!-- 链接 -->
|
|
152
|
+
{#if node.type === 'link'}
|
|
153
|
+
<a
|
|
154
|
+
class="incremark-link"
|
|
155
|
+
href={node.url}
|
|
156
|
+
target="_blank"
|
|
157
|
+
rel="noopener noreferrer"
|
|
158
|
+
>
|
|
159
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
160
|
+
</a>
|
|
161
|
+
{/if}
|
|
162
|
+
|
|
163
|
+
<!-- 图片 -->
|
|
164
|
+
{#if node.type === 'image'}
|
|
165
|
+
<img
|
|
166
|
+
class="incremark-image"
|
|
167
|
+
src={node.url}
|
|
168
|
+
alt={node.alt || ''}
|
|
169
|
+
title={(node as any).title || undefined}
|
|
170
|
+
loading="lazy"
|
|
171
|
+
/>
|
|
172
|
+
{/if}
|
|
173
|
+
|
|
174
|
+
<!-- 引用式图片(imageReference) -->
|
|
175
|
+
{#if isImageReference(node)}
|
|
176
|
+
{#if definations && $definations[node.identifier]}
|
|
177
|
+
<img
|
|
178
|
+
class="incremark-image incremark-reference-image"
|
|
179
|
+
src={$definations[node.identifier].url}
|
|
180
|
+
alt={node.alt || ''}
|
|
181
|
+
title={$definations[node.identifier].title || undefined}
|
|
182
|
+
loading="lazy"
|
|
183
|
+
/>
|
|
184
|
+
{:else}
|
|
185
|
+
<!-- 如果没有找到定义,渲染为原始文本(降级处理) -->
|
|
186
|
+
<span class="incremark-image-ref-missing">
|
|
187
|
+
![{node.alt}][{node.identifier || node.label}]
|
|
188
|
+
</span>
|
|
189
|
+
{/if}
|
|
190
|
+
{/if}
|
|
191
|
+
|
|
192
|
+
<!-- 引用式链接(linkReference) -->
|
|
193
|
+
{#if isLinkReference(node)}
|
|
194
|
+
{#if definations && $definations[node.identifier]}
|
|
195
|
+
<a
|
|
196
|
+
class="incremark-link incremark-reference-link"
|
|
197
|
+
href={$definations[node.identifier].url}
|
|
198
|
+
title={$definations[node.identifier].title || undefined}
|
|
199
|
+
target="_blank"
|
|
200
|
+
rel="noopener noreferrer"
|
|
201
|
+
>
|
|
202
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
203
|
+
</a>
|
|
204
|
+
{:else}
|
|
205
|
+
<!-- 如果没有找到定义,渲染为原始文本(降级处理) -->
|
|
206
|
+
<span class="incremark-link-ref-missing">
|
|
207
|
+
[{node.children.map((c: any) => c.value).join('')}][{node.identifier || node.label}]
|
|
208
|
+
</span>
|
|
209
|
+
{/if}
|
|
210
|
+
{/if}
|
|
211
|
+
|
|
212
|
+
<!-- 脚注引用(footnoteReference) -->
|
|
213
|
+
{#if node.type === 'footnoteReference'}
|
|
214
|
+
<sup class="incremark-footnote-ref">
|
|
215
|
+
<a href="#fn-{(node as any).identifier}" id="fnref-{(node as any).identifier}">
|
|
216
|
+
[{(node as any).identifier}]
|
|
217
|
+
</a>
|
|
218
|
+
</sup>
|
|
219
|
+
{/if}
|
|
220
|
+
|
|
221
|
+
<!-- 换行 -->
|
|
222
|
+
{#if node.type === 'break'}
|
|
223
|
+
<br />
|
|
224
|
+
{/if}
|
|
225
|
+
|
|
226
|
+
<!-- 删除线 -->
|
|
227
|
+
{#if node.type === 'delete'}
|
|
228
|
+
<del>
|
|
229
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
230
|
+
</del>
|
|
231
|
+
{/if}
|
|
232
|
+
{/each}
|
|
233
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PhrasingContent } from 'mdast';
|
|
2
|
+
import IncremarkInline from './IncremarkInline.svelte';
|
|
3
|
+
/**
|
|
4
|
+
* 组件 Props
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
/** 行内节点列表 */
|
|
8
|
+
nodes: PhrasingContent[];
|
|
9
|
+
}
|
|
10
|
+
declare const IncremarkInline: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type IncremarkInline = ReturnType<typeof IncremarkInline>;
|
|
12
|
+
export default IncremarkInline;
|
|
13
|
+
//# sourceMappingURL=IncremarkInline.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkInline.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkInline.svelte.ts"],"names":[],"mappings":"AAIA,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;AAyMH,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkList.svelte - 列表组件
|
|
3
|
+
@description 渲染 Markdown 列表(有序列表和无序列表),支持任务列表
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { List, ListItem, PhrasingContent } from 'mdast'
|
|
8
|
+
import IncremarkInline from './IncremarkInline.svelte'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 组件 Props
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
/** 列表节点 */
|
|
15
|
+
node: List
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { node }: Props = $props()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 根据 ordered 属性计算标签名
|
|
22
|
+
*/
|
|
23
|
+
const tag = $derived(node.ordered ? 'ol' : 'ul')
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 判断是否是任务列表
|
|
27
|
+
*/
|
|
28
|
+
const isTaskList = $derived(
|
|
29
|
+
node.children.some(item => item.checked !== null && item.checked !== undefined)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 获取列表项内容
|
|
34
|
+
*
|
|
35
|
+
* @param item - 列表项节点
|
|
36
|
+
* @returns 行内内容数组
|
|
37
|
+
*/
|
|
38
|
+
function getItemContent(item: ListItem): PhrasingContent[] {
|
|
39
|
+
const firstChild = item.children[0]
|
|
40
|
+
if (firstChild?.type === 'paragraph') {
|
|
41
|
+
return firstChild.children as PhrasingContent[]
|
|
42
|
+
}
|
|
43
|
+
return []
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 判断列表项是否是任务项
|
|
48
|
+
*
|
|
49
|
+
* @param item - 列表项节点
|
|
50
|
+
* @returns 是否是任务项
|
|
51
|
+
*/
|
|
52
|
+
function isTaskItem(item: ListItem): boolean {
|
|
53
|
+
return item.checked !== null && item.checked !== undefined
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<svelte:element
|
|
58
|
+
this={tag}
|
|
59
|
+
class="incremark-list"
|
|
60
|
+
class:task-list={isTaskList}
|
|
61
|
+
>
|
|
62
|
+
{#each node.children as item, index (index)}
|
|
63
|
+
<li
|
|
64
|
+
class="incremark-list-item"
|
|
65
|
+
class:task-item={isTaskItem(item)}
|
|
66
|
+
>
|
|
67
|
+
{#if isTaskItem(item)}
|
|
68
|
+
<label class="task-label">
|
|
69
|
+
<input
|
|
70
|
+
type="checkbox"
|
|
71
|
+
checked={item.checked}
|
|
72
|
+
disabled
|
|
73
|
+
class="checkbox"
|
|
74
|
+
/>
|
|
75
|
+
<span class="task-content">
|
|
76
|
+
<IncremarkInline nodes={getItemContent(item)} />
|
|
77
|
+
</span>
|
|
78
|
+
</label>
|
|
79
|
+
{:else}
|
|
80
|
+
<IncremarkInline nodes={getItemContent(item)} />
|
|
81
|
+
{/if}
|
|
82
|
+
</li>
|
|
83
|
+
{/each}
|
|
84
|
+
</svelte:element>
|
|
85
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { List } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* 组件 Props
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** 列表节点 */
|
|
7
|
+
node: List;
|
|
8
|
+
}
|
|
9
|
+
declare const IncremarkList: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type IncremarkList = ReturnType<typeof IncremarkList>;
|
|
11
|
+
export default IncremarkList;
|
|
12
|
+
//# sourceMappingURL=IncremarkList.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkList.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAA6B,MAAM,OAAO,CAAA;AAI1D;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;CACX;AAmEH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkMath.svelte - 数学公式组件
|
|
3
|
+
@description 渲染 Markdown 数学公式(行内和块级),使用 KaTeX 渲染
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import { onDestroy } from 'svelte'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Math 节点类型(来自 mdast-util-math)
|
|
11
|
+
*/
|
|
12
|
+
interface MathNode {
|
|
13
|
+
type: 'math' | 'inlineMath'
|
|
14
|
+
value: string
|
|
15
|
+
data?: {
|
|
16
|
+
hName?: string
|
|
17
|
+
hProperties?: Record<string, any>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 组件 Props
|
|
23
|
+
*/
|
|
24
|
+
interface Props {
|
|
25
|
+
/** 数学公式节点 */
|
|
26
|
+
node: MathNode
|
|
27
|
+
/** 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
28
|
+
renderDelay?: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
node,
|
|
33
|
+
renderDelay = 300
|
|
34
|
+
}: Props = $props()
|
|
35
|
+
|
|
36
|
+
// 状态
|
|
37
|
+
let renderedHtml = $state('')
|
|
38
|
+
let renderError = $state('')
|
|
39
|
+
let isLoading = $state(false)
|
|
40
|
+
let katexRef: any = null
|
|
41
|
+
let renderTimer: ReturnType<typeof setTimeout> | null = null
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 计算属性
|
|
45
|
+
*/
|
|
46
|
+
const isInline = $derived(node.type === 'inlineMath')
|
|
47
|
+
const formula = $derived(node.value)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 带防抖动的渲染
|
|
51
|
+
*/
|
|
52
|
+
function scheduleRender() {
|
|
53
|
+
if (!formula) {
|
|
54
|
+
renderedHtml = ''
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 清除之前的定时器
|
|
59
|
+
if (renderTimer) {
|
|
60
|
+
clearTimeout(renderTimer)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
isLoading = true
|
|
64
|
+
|
|
65
|
+
// 防抖动延迟渲染
|
|
66
|
+
renderTimer = setTimeout(() => {
|
|
67
|
+
doRender()
|
|
68
|
+
}, renderDelay)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 执行渲染
|
|
73
|
+
*/
|
|
74
|
+
async function doRender() {
|
|
75
|
+
if (!formula) return
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// 动态导入 KaTeX
|
|
79
|
+
if (!katexRef) {
|
|
80
|
+
// @ts-ignore - katex 是可选依赖
|
|
81
|
+
const katexModule = await import('katex')
|
|
82
|
+
katexRef = katexModule.default
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const katex = katexRef
|
|
86
|
+
renderedHtml = katex.renderToString(formula, {
|
|
87
|
+
displayMode: !isInline,
|
|
88
|
+
throwOnError: false,
|
|
89
|
+
strict: false
|
|
90
|
+
})
|
|
91
|
+
renderError = ''
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
// 静默失败,可能是公式不完整
|
|
94
|
+
renderError = ''
|
|
95
|
+
renderedHtml = ''
|
|
96
|
+
} finally {
|
|
97
|
+
isLoading = false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 监听公式变化,重新渲染
|
|
102
|
+
$effect(() => {
|
|
103
|
+
scheduleRender()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// 清理
|
|
107
|
+
onDestroy(() => {
|
|
108
|
+
if (renderTimer) {
|
|
109
|
+
clearTimeout(renderTimer)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<!-- 行内公式 -->
|
|
115
|
+
{#if isInline}
|
|
116
|
+
<span class="incremark-math-inline">
|
|
117
|
+
<!-- 渲染成功 -->
|
|
118
|
+
{#if renderedHtml && !isLoading}
|
|
119
|
+
{@html renderedHtml}
|
|
120
|
+
<!-- 加载中或未渲染:显示源码 -->
|
|
121
|
+
{:else}
|
|
122
|
+
<code class="math-source">{formula}</code>
|
|
123
|
+
{/if}
|
|
124
|
+
</span>
|
|
125
|
+
{:else}
|
|
126
|
+
<!-- 块级公式 -->
|
|
127
|
+
<div class="incremark-math-block">
|
|
128
|
+
<!-- 渲染成功 -->
|
|
129
|
+
{#if renderedHtml && !isLoading}
|
|
130
|
+
<div class="math-rendered">{@html renderedHtml}</div>
|
|
131
|
+
<!-- 加载中或未渲染:显示源码 -->
|
|
132
|
+
{:else}
|
|
133
|
+
<pre class="math-source-block"><code>{formula}</code></pre>
|
|
134
|
+
{/if}
|
|
135
|
+
</div>
|
|
136
|
+
{/if}
|
|
137
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Math 节点类型(来自 mdast-util-math)
|
|
3
|
+
*/
|
|
4
|
+
interface MathNode {
|
|
5
|
+
type: 'math' | 'inlineMath';
|
|
6
|
+
value: string;
|
|
7
|
+
data?: {
|
|
8
|
+
hName?: string;
|
|
9
|
+
hProperties?: Record<string, any>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 组件 Props
|
|
14
|
+
*/
|
|
15
|
+
interface Props {
|
|
16
|
+
/** 数学公式节点 */
|
|
17
|
+
node: MathNode;
|
|
18
|
+
/** 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
19
|
+
renderDelay?: number;
|
|
20
|
+
}
|
|
21
|
+
declare const IncremarkMath: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type IncremarkMath = ReturnType<typeof IncremarkMath>;
|
|
23
|
+
export default IncremarkMath;
|
|
24
|
+
//# sourceMappingURL=IncremarkMath.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkMath.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkMath.svelte.ts"],"names":[],"mappings":"AAME;;GAEG;AACH,UAAU,QAAQ;IAChB,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAClC,CAAA;CACF;AAED;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,IAAI,EAAE,QAAQ,CAAA;IACd,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAsHH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkParagraph.svelte - 段落组件
|
|
3
|
+
@description 渲染 Markdown 段落节点
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { Paragraph } from 'mdast'
|
|
8
|
+
import IncremarkInline from './IncremarkInline.svelte'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 组件 Props
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
/** 段落节点 */
|
|
15
|
+
node: Paragraph
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { node }: Props = $props()
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<p class="incremark-paragraph">
|
|
22
|
+
<IncremarkInline nodes={node.children} />
|
|
23
|
+
</p>
|
|
24
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Paragraph } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* 组件 Props
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** 段落节点 */
|
|
7
|
+
node: Paragraph;
|
|
8
|
+
}
|
|
9
|
+
declare const IncremarkParagraph: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type IncremarkParagraph = ReturnType<typeof IncremarkParagraph>;
|
|
11
|
+
export default IncremarkParagraph;
|
|
12
|
+
//# sourceMappingURL=IncremarkParagraph.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkParagraph.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkParagraph.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAIpC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,SAAS,CAAA;CAChB;AAkBH,QAAA,MAAM,kBAAkB,2CAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkRenderer.svelte - 渲染器组件
|
|
3
|
+
@description 用于渲染单个 RootContent 节点
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { RootContent, HTML } from 'mdast'
|
|
8
|
+
import IncremarkHeading from './IncremarkHeading.svelte'
|
|
9
|
+
import IncremarkParagraph from './IncremarkParagraph.svelte'
|
|
10
|
+
import IncremarkCode from './IncremarkCode.svelte'
|
|
11
|
+
import IncremarkList from './IncremarkList.svelte'
|
|
12
|
+
import IncremarkTable from './IncremarkTable.svelte'
|
|
13
|
+
import IncremarkBlockquote from './IncremarkBlockquote.svelte'
|
|
14
|
+
import IncremarkThematicBreak from './IncremarkThematicBreak.svelte'
|
|
15
|
+
import IncremarkMath from './IncremarkMath.svelte'
|
|
16
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
|
|
17
|
+
import IncremarkDefault from './IncremarkDefault.svelte'
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 组件 Props
|
|
21
|
+
*/
|
|
22
|
+
interface Props {
|
|
23
|
+
/** 要渲染的节点 */
|
|
24
|
+
node: RootContent
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { node }: Props = $props()
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 组件映射
|
|
31
|
+
*/
|
|
32
|
+
const componentMap: Record<string, any> = {
|
|
33
|
+
heading: IncremarkHeading,
|
|
34
|
+
paragraph: IncremarkParagraph,
|
|
35
|
+
code: IncremarkCode,
|
|
36
|
+
list: IncremarkList,
|
|
37
|
+
table: IncremarkTable,
|
|
38
|
+
blockquote: IncremarkBlockquote,
|
|
39
|
+
thematicBreak: IncremarkThematicBreak,
|
|
40
|
+
math: IncremarkMath,
|
|
41
|
+
inlineMath: IncremarkMath,
|
|
42
|
+
htmlElement: IncremarkHtmlElement,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 获取组件
|
|
47
|
+
*/
|
|
48
|
+
function getComponent(type: string): any {
|
|
49
|
+
return componentMap[type] || IncremarkDefault
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 检查是否是 html 节点
|
|
54
|
+
*/
|
|
55
|
+
function isHtmlNode(node: RootContent): node is HTML {
|
|
56
|
+
return node.type === 'html'
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<!-- HTML 节点:渲染为代码块显示源代码 -->
|
|
61
|
+
{#if isHtmlNode(node)}
|
|
62
|
+
<pre class="incremark-html-code"><code>{node.value}</code></pre>
|
|
63
|
+
{:else}
|
|
64
|
+
<!-- 其他节点:使用对应组件 -->
|
|
65
|
+
{@const Component = getComponent(node.type)}
|
|
66
|
+
{#if Component}
|
|
67
|
+
<Component node={node} />
|
|
68
|
+
{/if}
|
|
69
|
+
{/if}
|
|
70
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RootContent } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* 组件 Props
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** 要渲染的节点 */
|
|
7
|
+
node: RootContent;
|
|
8
|
+
}
|
|
9
|
+
declare const IncremarkRenderer: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type IncremarkRenderer = ReturnType<typeof IncremarkRenderer>;
|
|
11
|
+
export default IncremarkRenderer;
|
|
12
|
+
//# sourceMappingURL=IncremarkRenderer.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkRenderer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAQ,MAAM,OAAO,CAAA;AAa5C;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,IAAI,EAAE,WAAW,CAAA;CAClB;AAgEH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|