@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,275 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkCode.svelte - 代码块组件
|
|
3
|
+
@description 渲染 Markdown 代码块,支持 Shiki 代码高亮和 Mermaid 图表渲染
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { Code } from 'mdast'
|
|
8
|
+
import { onMount, onDestroy } from 'svelte'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 组件 Props
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
/** 代码节点 */
|
|
15
|
+
node: Code
|
|
16
|
+
/** Shiki 主题,默认 github-dark */
|
|
17
|
+
theme?: string
|
|
18
|
+
/** 是否禁用代码高亮 */
|
|
19
|
+
disableHighlight?: boolean
|
|
20
|
+
/** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
21
|
+
mermaidDelay?: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
node,
|
|
26
|
+
theme = 'github-dark',
|
|
27
|
+
disableHighlight = false,
|
|
28
|
+
mermaidDelay = 500
|
|
29
|
+
}: Props = $props()
|
|
30
|
+
|
|
31
|
+
// 状态
|
|
32
|
+
let copied = $state(false)
|
|
33
|
+
let highlightedHtml = $state('')
|
|
34
|
+
let isHighlighting = $state(false)
|
|
35
|
+
let highlightError = $state(false)
|
|
36
|
+
|
|
37
|
+
// Mermaid 支持
|
|
38
|
+
let mermaidSvg = $state('')
|
|
39
|
+
let mermaidError = $state('')
|
|
40
|
+
let mermaidLoading = $state(false)
|
|
41
|
+
let mermaidRef: any = null
|
|
42
|
+
let mermaidTimer: ReturnType<typeof setTimeout> | null = null
|
|
43
|
+
// 视图模式:'preview' | 'source'
|
|
44
|
+
let mermaidViewMode = $state<'preview' | 'source'>('preview')
|
|
45
|
+
|
|
46
|
+
// 缓存 highlighter
|
|
47
|
+
let highlighterRef: any = null
|
|
48
|
+
const loadedLanguages = new Set<string>()
|
|
49
|
+
const loadedThemes = new Set<string>()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 计算属性
|
|
53
|
+
*/
|
|
54
|
+
const language = $derived(node.lang || 'text')
|
|
55
|
+
const code = $derived(node.value)
|
|
56
|
+
const isMermaid = $derived(language === 'mermaid')
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 切换 Mermaid 视图模式
|
|
60
|
+
*/
|
|
61
|
+
function toggleMermaidView() {
|
|
62
|
+
mermaidViewMode = mermaidViewMode === 'preview' ? 'source' : 'preview'
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Mermaid 渲染(带防抖动)
|
|
67
|
+
*/
|
|
68
|
+
function scheduleRenderMermaid() {
|
|
69
|
+
if (!isMermaid || !code) return
|
|
70
|
+
|
|
71
|
+
// 清除之前的定时器
|
|
72
|
+
if (mermaidTimer) {
|
|
73
|
+
clearTimeout(mermaidTimer)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 显示加载状态
|
|
77
|
+
mermaidLoading = true
|
|
78
|
+
|
|
79
|
+
// 防抖动延迟渲染
|
|
80
|
+
mermaidTimer = setTimeout(() => {
|
|
81
|
+
doRenderMermaid()
|
|
82
|
+
}, mermaidDelay)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 执行 Mermaid 渲染
|
|
87
|
+
*/
|
|
88
|
+
async function doRenderMermaid() {
|
|
89
|
+
if (!code) return
|
|
90
|
+
|
|
91
|
+
mermaidError = ''
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// 动态导入 mermaid
|
|
95
|
+
if (!mermaidRef) {
|
|
96
|
+
// @ts-ignore - mermaid 是可选依赖
|
|
97
|
+
const mermaidModule = await import('mermaid')
|
|
98
|
+
mermaidRef = mermaidModule.default
|
|
99
|
+
mermaidRef.initialize({
|
|
100
|
+
startOnLoad: false,
|
|
101
|
+
theme: 'dark',
|
|
102
|
+
securityLevel: 'loose'
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const mermaid = mermaidRef
|
|
107
|
+
const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
108
|
+
|
|
109
|
+
const { svg } = await mermaid.render(id, code)
|
|
110
|
+
mermaidSvg = svg
|
|
111
|
+
} catch (e: any) {
|
|
112
|
+
// 不显示错误,可能是代码还不完整
|
|
113
|
+
mermaidError = ''
|
|
114
|
+
mermaidSvg = ''
|
|
115
|
+
} finally {
|
|
116
|
+
mermaidLoading = false
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 动态加载 shiki 并高亮
|
|
122
|
+
*/
|
|
123
|
+
async function highlight() {
|
|
124
|
+
if (isMermaid) {
|
|
125
|
+
scheduleRenderMermaid()
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!code || disableHighlight) {
|
|
130
|
+
highlightedHtml = ''
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
isHighlighting = true
|
|
135
|
+
highlightError = false
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// 动态导入 shiki
|
|
139
|
+
if (!highlighterRef) {
|
|
140
|
+
const { createHighlighter } = await import('shiki')
|
|
141
|
+
highlighterRef = await createHighlighter({
|
|
142
|
+
themes: [theme as any],
|
|
143
|
+
langs: []
|
|
144
|
+
})
|
|
145
|
+
loadedThemes.add(theme)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const highlighter = highlighterRef
|
|
149
|
+
const lang = language
|
|
150
|
+
|
|
151
|
+
// 按需加载语言
|
|
152
|
+
if (!loadedLanguages.has(lang) && lang !== 'text') {
|
|
153
|
+
try {
|
|
154
|
+
await highlighter.loadLanguage(lang)
|
|
155
|
+
loadedLanguages.add(lang)
|
|
156
|
+
} catch {
|
|
157
|
+
// 语言不支持,标记但不阻止
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 按需加载主题
|
|
162
|
+
if (!loadedThemes.has(theme)) {
|
|
163
|
+
try {
|
|
164
|
+
await highlighter.loadTheme(theme)
|
|
165
|
+
loadedThemes.add(theme)
|
|
166
|
+
} catch {
|
|
167
|
+
// 主题不支持
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const html = highlighter.codeToHtml(code, {
|
|
172
|
+
lang: loadedLanguages.has(lang) ? lang : 'text',
|
|
173
|
+
theme: loadedThemes.has(theme) ? theme : 'github-dark'
|
|
174
|
+
})
|
|
175
|
+
highlightedHtml = html
|
|
176
|
+
} catch (e) {
|
|
177
|
+
// Shiki 不可用或加载失败
|
|
178
|
+
highlightError = true
|
|
179
|
+
highlightedHtml = ''
|
|
180
|
+
} finally {
|
|
181
|
+
isHighlighting = false
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 复制代码
|
|
187
|
+
*/
|
|
188
|
+
async function copyCode() {
|
|
189
|
+
try {
|
|
190
|
+
await navigator.clipboard.writeText(code)
|
|
191
|
+
copied = true
|
|
192
|
+
setTimeout(() => {
|
|
193
|
+
copied = false
|
|
194
|
+
}, 2000)
|
|
195
|
+
} catch {
|
|
196
|
+
// 复制失败静默处理
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 监听代码变化,重新高亮/渲染
|
|
201
|
+
$effect(() => {
|
|
202
|
+
highlight()
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
// 清理
|
|
206
|
+
onDestroy(() => {
|
|
207
|
+
if (mermaidTimer) {
|
|
208
|
+
clearTimeout(mermaidTimer)
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<!-- Mermaid 图表 -->
|
|
214
|
+
{#if isMermaid}
|
|
215
|
+
<div class="incremark-mermaid">
|
|
216
|
+
<div class="mermaid-header">
|
|
217
|
+
<span class="language">MERMAID</span>
|
|
218
|
+
<div class="mermaid-actions">
|
|
219
|
+
<button
|
|
220
|
+
class="code-btn"
|
|
221
|
+
onclick={toggleMermaidView}
|
|
222
|
+
type="button"
|
|
223
|
+
disabled={!mermaidSvg}
|
|
224
|
+
>
|
|
225
|
+
{mermaidViewMode === 'preview' ? '源码' : '预览'}
|
|
226
|
+
</button>
|
|
227
|
+
<button class="code-btn" onclick={copyCode} type="button">
|
|
228
|
+
{copied ? '✓ 已复制' : '复制'}
|
|
229
|
+
</button>
|
|
230
|
+
</div>
|
|
231
|
+
</div>
|
|
232
|
+
<div class="mermaid-content">
|
|
233
|
+
<!-- 加载中 -->
|
|
234
|
+
{#if mermaidLoading && !mermaidSvg}
|
|
235
|
+
<pre class="mermaid-source-code">{code}</pre>
|
|
236
|
+
<!-- 源码模式 -->
|
|
237
|
+
{:else if mermaidViewMode === 'source'}
|
|
238
|
+
<pre class="mermaid-source-code">{code}</pre>
|
|
239
|
+
<!-- 预览模式 -->
|
|
240
|
+
{:else if mermaidSvg}
|
|
241
|
+
{@html mermaidSvg}
|
|
242
|
+
<!-- 无法渲染时显示源码 -->
|
|
243
|
+
{:else}
|
|
244
|
+
<pre class="mermaid-source-code">{code}</pre>
|
|
245
|
+
{/if}
|
|
246
|
+
</div>
|
|
247
|
+
</div>
|
|
248
|
+
{:else}
|
|
249
|
+
<!-- 普通代码块 -->
|
|
250
|
+
<div class="incremark-code">
|
|
251
|
+
<div class="code-header">
|
|
252
|
+
<span class="language">{language}</span>
|
|
253
|
+
<button class="code-btn" onclick={copyCode} type="button">
|
|
254
|
+
{copied ? '✓ 已复制' : '复制'}
|
|
255
|
+
</button>
|
|
256
|
+
</div>
|
|
257
|
+
<div class="code-content">
|
|
258
|
+
<!-- 正在加载高亮 -->
|
|
259
|
+
{#if isHighlighting && !highlightedHtml}
|
|
260
|
+
<div class="code-loading">
|
|
261
|
+
<pre><code>{code}</code></pre>
|
|
262
|
+
</div>
|
|
263
|
+
<!-- 高亮后的代码 -->
|
|
264
|
+
{:else if highlightedHtml}
|
|
265
|
+
<div class="shiki-wrapper">
|
|
266
|
+
{@html highlightedHtml}
|
|
267
|
+
</div>
|
|
268
|
+
<!-- 回退:无高亮 -->
|
|
269
|
+
{:else}
|
|
270
|
+
<pre class="code-fallback"><code>{code}</code></pre>
|
|
271
|
+
{/if}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
{/if}
|
|
275
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Code } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* 组件 Props
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** 代码节点 */
|
|
7
|
+
node: Code;
|
|
8
|
+
/** Shiki 主题,默认 github-dark */
|
|
9
|
+
theme?: string;
|
|
10
|
+
/** 是否禁用代码高亮 */
|
|
11
|
+
disableHighlight?: boolean;
|
|
12
|
+
/** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
|
|
13
|
+
mermaidDelay?: number;
|
|
14
|
+
}
|
|
15
|
+
declare const IncremarkCode: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
type IncremarkCode = ReturnType<typeof IncremarkCode>;
|
|
17
|
+
export default IncremarkCode;
|
|
18
|
+
//# sourceMappingURL=IncremarkCode.svelte.d.ts.map
|
|
@@ -0,0 +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;CACtB;AAkQH,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 IncremarkDefault.svelte - 默认组件
|
|
3
|
+
@description 用于显示未实现的节点类型(降级处理)
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { RootContent } from 'mdast'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 组件 Props
|
|
11
|
+
*/
|
|
12
|
+
interface Props {
|
|
13
|
+
/** 节点 */
|
|
14
|
+
node: RootContent
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let { node }: Props = $props()
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="incremark-default">
|
|
21
|
+
<span class="type-badge">{node.type}</span>
|
|
22
|
+
<pre>{JSON.stringify(node, null, 2)}</pre>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
@@ -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 IncremarkDefault: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type IncremarkDefault = ReturnType<typeof IncremarkDefault>;
|
|
11
|
+
export default IncremarkDefault;
|
|
12
|
+
//# sourceMappingURL=IncremarkDefault.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkDefault.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkDefault.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAGvC;;GAEG;AACH,UAAU,KAAK;IACb,SAAS;IACT,IAAI,EAAE,WAAW,CAAA;CAClB;AAkBH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkFootnotes.svelte - 脚注列表组件
|
|
3
|
+
@description 在文档底部渲染所有脚注定义,按引用出现的顺序排列
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { Readable } from 'svelte/store'
|
|
8
|
+
import type { FootnoteDefinition, RootContent } from 'mdast'
|
|
9
|
+
import { getDefinitionsContext } from '../context/definitionsContext'
|
|
10
|
+
import IncremarkRenderer from './IncremarkRenderer.svelte'
|
|
11
|
+
|
|
12
|
+
// 从 context 获取数据
|
|
13
|
+
let definations: Readable<Record<string, any>> | null = null
|
|
14
|
+
let footnoteDefinitions: Readable<Record<string, FootnoteDefinition>> | null = null
|
|
15
|
+
let footnoteReferenceOrder: Readable<string[]> | null = null
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const context = getDefinitionsContext()
|
|
19
|
+
definations = context.definations
|
|
20
|
+
footnoteDefinitions = context.footnoteDefinitions
|
|
21
|
+
footnoteReferenceOrder = context.footnoteReferenceOrder
|
|
22
|
+
} catch {
|
|
23
|
+
// Context 不存在
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 按引用顺序排列的脚注列表
|
|
28
|
+
* 只显示已有定义的脚注
|
|
29
|
+
*/
|
|
30
|
+
const orderedFootnotes = $derived.by(() => {
|
|
31
|
+
if (!footnoteReferenceOrder || !footnoteDefinitions) {
|
|
32
|
+
return []
|
|
33
|
+
}
|
|
34
|
+
const order = $footnoteReferenceOrder
|
|
35
|
+
const definitions = $footnoteDefinitions
|
|
36
|
+
return order
|
|
37
|
+
.map(identifier => ({
|
|
38
|
+
identifier,
|
|
39
|
+
definition: definitions[identifier]
|
|
40
|
+
}))
|
|
41
|
+
.filter(item => item.definition !== undefined)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 是否有脚注需要显示
|
|
46
|
+
*/
|
|
47
|
+
const hasFootnotes = $derived(orderedFootnotes.length > 0)
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#if hasFootnotes}
|
|
51
|
+
<section class="incremark-footnotes">
|
|
52
|
+
<hr class="incremark-footnotes-divider" />
|
|
53
|
+
<ol class="incremark-footnotes-list">
|
|
54
|
+
{#each orderedFootnotes as item, index (item.identifier)}
|
|
55
|
+
<li
|
|
56
|
+
id="fn-{item.identifier}"
|
|
57
|
+
class="incremark-footnote-item"
|
|
58
|
+
>
|
|
59
|
+
<div class="incremark-footnote-content">
|
|
60
|
+
<!-- 脚注序号 -->
|
|
61
|
+
<span class="incremark-footnote-number">{index + 1}.</span>
|
|
62
|
+
|
|
63
|
+
<!-- 脚注内容 -->
|
|
64
|
+
<div class="incremark-footnote-body">
|
|
65
|
+
{#each item.definition.children as child, childIndex (childIndex)}
|
|
66
|
+
<IncremarkRenderer node={child as RootContent} />
|
|
67
|
+
{/each}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- 返回链接 -->
|
|
72
|
+
<a
|
|
73
|
+
href="#fnref-{item.identifier}"
|
|
74
|
+
class="incremark-footnote-backref"
|
|
75
|
+
aria-label="返回引用位置"
|
|
76
|
+
>
|
|
77
|
+
↩
|
|
78
|
+
</a>
|
|
79
|
+
</li>
|
|
80
|
+
{/each}
|
|
81
|
+
</ol>
|
|
82
|
+
</section>
|
|
83
|
+
{/if}
|
|
84
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkFootnotes.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkFootnotes.svelte.ts"],"names":[],"mappings":"AAqFA,QAAA,MAAM,kBAAkB,2DAAwC,CAAC;AACjE,KAAK,kBAAkB,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAChE,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkHeading.svelte - 标题组件
|
|
3
|
+
@description 渲染 Markdown 标题节点(h1-h6)
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { Heading } from 'mdast'
|
|
8
|
+
import IncremarkInline from './IncremarkInline.svelte'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 组件 Props
|
|
12
|
+
*/
|
|
13
|
+
interface Props {
|
|
14
|
+
/** 标题节点 */
|
|
15
|
+
node: Heading
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let { node }: Props = $props()
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 根据 depth 计算标签名
|
|
22
|
+
* depth 范围:1-6,对应 h1-h6
|
|
23
|
+
*/
|
|
24
|
+
const tag = $derived(`h${node.depth}`)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* CSS 类名
|
|
28
|
+
*/
|
|
29
|
+
const className = $derived(`incremark-heading h${node.depth}`)
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<svelte:element this={tag} class={className}>
|
|
33
|
+
<IncremarkInline nodes={node.children} />
|
|
34
|
+
</svelte:element>
|
|
35
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Heading } from 'mdast';
|
|
2
|
+
/**
|
|
3
|
+
* 组件 Props
|
|
4
|
+
*/
|
|
5
|
+
interface Props {
|
|
6
|
+
/** 标题节点 */
|
|
7
|
+
node: Heading;
|
|
8
|
+
}
|
|
9
|
+
declare const IncremarkHeading: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type IncremarkHeading = ReturnType<typeof IncremarkHeading>;
|
|
11
|
+
export default IncremarkHeading;
|
|
12
|
+
//# sourceMappingURL=IncremarkHeading.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkHeading.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkHeading.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,OAAO,CAAA;AAIlC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,OAAO,CAAA;CACd;AA6BH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@file IncremarkHtmlElement.svelte - HTML 元素组件
|
|
3
|
+
@description 渲染结构化的 HTML 元素节点
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<script lang="ts">
|
|
7
|
+
import type { RootContent, PhrasingContent } from 'mdast'
|
|
8
|
+
import IncremarkInline from './IncremarkInline.svelte'
|
|
9
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* HtmlElementNode 类型定义(与 @incremark/core 中的定义一致)
|
|
13
|
+
*/
|
|
14
|
+
interface HtmlElementNode {
|
|
15
|
+
type: 'htmlElement'
|
|
16
|
+
tagName: string
|
|
17
|
+
attrs: Record<string, string>
|
|
18
|
+
children: RootContent[]
|
|
19
|
+
data?: {
|
|
20
|
+
rawHtml?: string
|
|
21
|
+
parsed?: boolean
|
|
22
|
+
originalType?: string
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 组件 Props
|
|
28
|
+
*/
|
|
29
|
+
interface Props {
|
|
30
|
+
/** HTML 元素节点 */
|
|
31
|
+
node: HtmlElementNode
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let { node }: Props = $props()
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 判断是否是行内元素
|
|
38
|
+
*/
|
|
39
|
+
function isInlineElement(tagName: string): boolean {
|
|
40
|
+
const inlineElements = [
|
|
41
|
+
'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite',
|
|
42
|
+
'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map',
|
|
43
|
+
'object', 'output', 'q', 'samp', 'script', 'select', 'small',
|
|
44
|
+
'span', 'strong', 'sub', 'sup', 'textarea', 'time', 'tt', 'var'
|
|
45
|
+
]
|
|
46
|
+
return inlineElements.includes(tagName.toLowerCase())
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 判断是否是自闭合元素
|
|
51
|
+
*/
|
|
52
|
+
function isVoidElement(tagName: string): boolean {
|
|
53
|
+
const voidElements = [
|
|
54
|
+
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
|
|
55
|
+
'link', 'meta', 'param', 'source', 'track', 'wbr'
|
|
56
|
+
]
|
|
57
|
+
return voidElements.includes(tagName.toLowerCase())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 判断子节点是否都是行内内容
|
|
62
|
+
*/
|
|
63
|
+
function hasOnlyInlineChildren(children: RootContent[]): boolean {
|
|
64
|
+
if (!children || children.length === 0) return true
|
|
65
|
+
|
|
66
|
+
return children.every(child => {
|
|
67
|
+
const type = child.type
|
|
68
|
+
// 常见的行内类型
|
|
69
|
+
const inlineTypes = ['text', 'strong', 'emphasis', 'inlineCode', 'link', 'image', 'break', 'html', 'htmlElement']
|
|
70
|
+
if (inlineTypes.includes(type)) {
|
|
71
|
+
// 如果是 htmlElement,检查是否是行内元素
|
|
72
|
+
if (type === 'htmlElement') {
|
|
73
|
+
return isInlineElement((child as unknown as HtmlElementNode).tagName)
|
|
74
|
+
}
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
return false
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 将属性对象转换为 HTML 属性对象(过滤危险属性)
|
|
83
|
+
*/
|
|
84
|
+
function getAttrs(attrs: Record<string, string>): Record<string, string> {
|
|
85
|
+
// 过滤掉可能有问题的属性
|
|
86
|
+
const result: Record<string, string> = {}
|
|
87
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
88
|
+
// 跳过事件属性(已在解析时过滤,这里双重保险)
|
|
89
|
+
if (key.startsWith('on')) continue
|
|
90
|
+
result[key] = value
|
|
91
|
+
}
|
|
92
|
+
return result
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 计算属性
|
|
97
|
+
*/
|
|
98
|
+
const attrs = $derived(getAttrs(node.attrs))
|
|
99
|
+
const className = $derived(`incremark-html-element incremark-${node.tagName}`)
|
|
100
|
+
const isVoid = $derived(isVoidElement(node.tagName))
|
|
101
|
+
const hasOnlyInline = $derived(hasOnlyInlineChildren(node.children))
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<svelte:element
|
|
105
|
+
this={node.tagName}
|
|
106
|
+
{...attrs}
|
|
107
|
+
class={className}
|
|
108
|
+
>
|
|
109
|
+
{#if !isVoid}
|
|
110
|
+
<!-- 如果子节点都是行内内容,使用 IncremarkInline -->
|
|
111
|
+
{#if hasOnlyInline}
|
|
112
|
+
<IncremarkInline nodes={node.children as PhrasingContent[]} />
|
|
113
|
+
{:else}
|
|
114
|
+
<!-- 否则递归渲染每个子节点 -->
|
|
115
|
+
{#each node.children as child, idx (idx)}
|
|
116
|
+
<!-- 如果子节点是 htmlElement,递归 -->
|
|
117
|
+
{#if child.type === 'htmlElement'}
|
|
118
|
+
<IncremarkHtmlElement node={child as unknown as HtmlElementNode} />
|
|
119
|
+
<!-- 如果是文本节点 -->
|
|
120
|
+
{:else if child.type === 'text'}
|
|
121
|
+
{(child as any).value}
|
|
122
|
+
<!-- 其他类型尝试用 IncremarkInline -->
|
|
123
|
+
{:else if ['strong', 'emphasis', 'inlineCode', 'link', 'image', 'break'].includes(child.type)}
|
|
124
|
+
<IncremarkInline nodes={[child as PhrasingContent]} />
|
|
125
|
+
<!-- 段落等块级元素 -->
|
|
126
|
+
{:else if child.type === 'paragraph'}
|
|
127
|
+
<p>
|
|
128
|
+
<IncremarkInline nodes={((child as any).children as PhrasingContent[])} />
|
|
129
|
+
</p>
|
|
130
|
+
<!-- 其他未知类型,显示原始 -->
|
|
131
|
+
{:else}
|
|
132
|
+
<div class="incremark-unknown-child">{child.type}</div>
|
|
133
|
+
{/if}
|
|
134
|
+
{/each}
|
|
135
|
+
{/if}
|
|
136
|
+
{/if}
|
|
137
|
+
</svelte:element>
|
|
138
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { RootContent } from 'mdast';
|
|
2
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.svelte';
|
|
3
|
+
/**
|
|
4
|
+
* HtmlElementNode 类型定义(与 @incremark/core 中的定义一致)
|
|
5
|
+
*/
|
|
6
|
+
interface HtmlElementNode {
|
|
7
|
+
type: 'htmlElement';
|
|
8
|
+
tagName: string;
|
|
9
|
+
attrs: Record<string, string>;
|
|
10
|
+
children: RootContent[];
|
|
11
|
+
data?: {
|
|
12
|
+
rawHtml?: string;
|
|
13
|
+
parsed?: boolean;
|
|
14
|
+
originalType?: string;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 组件 Props
|
|
19
|
+
*/
|
|
20
|
+
interface Props {
|
|
21
|
+
/** HTML 元素节点 */
|
|
22
|
+
node: HtmlElementNode;
|
|
23
|
+
}
|
|
24
|
+
declare const IncremarkHtmlElement: import("svelte").Component<Props, {}, "">;
|
|
25
|
+
type IncremarkHtmlElement = ReturnType<typeof IncremarkHtmlElement>;
|
|
26
|
+
export default IncremarkHtmlElement;
|
|
27
|
+
//# sourceMappingURL=IncremarkHtmlElement.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IncremarkHtmlElement.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkHtmlElement.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,OAAO,CAAA;AAEzD,OAAO,oBAAoB,MAAM,+BAA+B,CAAC;AAG/D;;GAEG;AACH,UAAU,eAAe;IACvB,IAAI,EAAE,aAAa,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,QAAQ,EAAE,WAAW,EAAE,CAAA;IACvB,IAAI,CAAC,EAAE;QACL,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,MAAM,CAAC,EAAE,OAAO,CAAA;QAChB,YAAY,CAAC,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED;;GAEG;AACH,UAAU,KAAK;IACb,gBAAgB;IAChB,IAAI,EAAE,eAAe,CAAA;CACtB;AAkHH,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
|