@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.
Files changed (79) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +91 -0
  3. package/dist/ThemeProvider.svelte +52 -0
  4. package/dist/ThemeProvider.svelte.d.ts +21 -0
  5. package/dist/ThemeProvider.svelte.d.ts.map +1 -0
  6. package/dist/components/AutoScrollContainer.svelte +163 -0
  7. package/dist/components/AutoScrollContainer.svelte.d.ts +17 -0
  8. package/dist/components/AutoScrollContainer.svelte.d.ts.map +1 -0
  9. package/dist/components/Incremark.svelte +183 -0
  10. package/dist/components/Incremark.svelte.d.ts +24 -0
  11. package/dist/components/Incremark.svelte.d.ts.map +1 -0
  12. package/dist/components/IncremarkBlockquote.svelte +30 -0
  13. package/dist/components/IncremarkBlockquote.svelte.d.ts +12 -0
  14. package/dist/components/IncremarkBlockquote.svelte.d.ts.map +1 -0
  15. package/dist/components/IncremarkCode.svelte +275 -0
  16. package/dist/components/IncremarkCode.svelte.d.ts +18 -0
  17. package/dist/components/IncremarkCode.svelte.d.ts.map +1 -0
  18. package/dist/components/IncremarkDefault.svelte +24 -0
  19. package/dist/components/IncremarkDefault.svelte.d.ts +12 -0
  20. package/dist/components/IncremarkDefault.svelte.d.ts.map +1 -0
  21. package/dist/components/IncremarkFootnotes.svelte +84 -0
  22. package/dist/components/IncremarkFootnotes.svelte.d.ts +4 -0
  23. package/dist/components/IncremarkFootnotes.svelte.d.ts.map +1 -0
  24. package/dist/components/IncremarkHeading.svelte +35 -0
  25. package/dist/components/IncremarkHeading.svelte.d.ts +12 -0
  26. package/dist/components/IncremarkHeading.svelte.d.ts.map +1 -0
  27. package/dist/components/IncremarkHtmlElement.svelte +138 -0
  28. package/dist/components/IncremarkHtmlElement.svelte.d.ts +27 -0
  29. package/dist/components/IncremarkHtmlElement.svelte.d.ts.map +1 -0
  30. package/dist/components/IncremarkInline.svelte +233 -0
  31. package/dist/components/IncremarkInline.svelte.d.ts +13 -0
  32. package/dist/components/IncremarkInline.svelte.d.ts.map +1 -0
  33. package/dist/components/IncremarkList.svelte +85 -0
  34. package/dist/components/IncremarkList.svelte.d.ts +12 -0
  35. package/dist/components/IncremarkList.svelte.d.ts.map +1 -0
  36. package/dist/components/IncremarkMath.svelte +137 -0
  37. package/dist/components/IncremarkMath.svelte.d.ts +24 -0
  38. package/dist/components/IncremarkMath.svelte.d.ts.map +1 -0
  39. package/dist/components/IncremarkParagraph.svelte +24 -0
  40. package/dist/components/IncremarkParagraph.svelte.d.ts +12 -0
  41. package/dist/components/IncremarkParagraph.svelte.d.ts.map +1 -0
  42. package/dist/components/IncremarkRenderer.svelte +70 -0
  43. package/dist/components/IncremarkRenderer.svelte.d.ts +12 -0
  44. package/dist/components/IncremarkRenderer.svelte.d.ts.map +1 -0
  45. package/dist/components/IncremarkTable.svelte +79 -0
  46. package/dist/components/IncremarkTable.svelte.d.ts +12 -0
  47. package/dist/components/IncremarkTable.svelte.d.ts.map +1 -0
  48. package/dist/components/IncremarkThematicBreak.svelte +11 -0
  49. package/dist/components/IncremarkThematicBreak.svelte.d.ts +19 -0
  50. package/dist/components/IncremarkThematicBreak.svelte.d.ts.map +1 -0
  51. package/dist/components/index.d.ts +21 -0
  52. package/dist/components/index.d.ts.map +1 -0
  53. package/dist/components/index.js +19 -0
  54. package/dist/components/types.d.ts +18 -0
  55. package/dist/components/types.d.ts.map +1 -0
  56. package/dist/components/types.js +5 -0
  57. package/dist/context/definitionsContext.d.ts +64 -0
  58. package/dist/context/definitionsContext.d.ts.map +1 -0
  59. package/dist/context/definitionsContext.js +117 -0
  60. package/dist/index.d.ts +15 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +19 -0
  63. package/dist/stores/useBlockTransformer.d.ts +89 -0
  64. package/dist/stores/useBlockTransformer.d.ts.map +1 -0
  65. package/dist/stores/useBlockTransformer.js +110 -0
  66. package/dist/stores/useDevTools.d.ts +33 -0
  67. package/dist/stores/useDevTools.d.ts.map +1 -0
  68. package/dist/stores/useDevTools.js +53 -0
  69. package/dist/stores/useIncremark.d.ts +128 -0
  70. package/dist/stores/useIncremark.d.ts.map +1 -0
  71. package/dist/stores/useIncremark.js +177 -0
  72. package/dist/stores/useTypewriter.d.ts +42 -0
  73. package/dist/stores/useTypewriter.d.ts.map +1 -0
  74. package/dist/stores/useTypewriter.js +153 -0
  75. package/dist/svelte-runes.d.ts +32 -0
  76. package/dist/utils/cursor.d.ts +14 -0
  77. package/dist/utils/cursor.d.ts.map +1 -0
  78. package/dist/utils/cursor.js +36 -0
  79. 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,4 @@
1
+ declare const IncremarkFootnotes: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type IncremarkFootnotes = ReturnType<typeof IncremarkFootnotes>;
3
+ export default IncremarkFootnotes;
4
+ //# sourceMappingURL=IncremarkFootnotes.svelte.d.ts.map
@@ -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"}