@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
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 wangyishuai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
package/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @incremark/svelte
2
+
3
+ Incremark 的 Svelte 5 集成库。
4
+
5
+ 🇨🇳 中文 | **[🇺🇸 English](./README.en.md)**
6
+
7
+ ## 特性
8
+
9
+ - 📦 **开箱即用** - 提供 `useIncremark` store 和 `<Incremark>` 组件
10
+ - ⌨️ **打字机效果** - 内置 `useBlockTransformer` 实现逐字符显示
11
+ - 🎨 **可定制** - 支持自定义渲染组件
12
+ - ⚡ **高性能** - 使用 Svelte 5 Runes 优化性能
13
+ - 🔧 **DevTools** - 内置开发者工具
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ pnpm add @incremark/core @incremark/svelte
19
+ ```
20
+
21
+ ## 快速开始
22
+
23
+ **1. 引入样式**
24
+
25
+ ```ts
26
+ import '@incremark/svelte/style.css'
27
+ ```
28
+
29
+ **2. 在组件中使用**
30
+
31
+ ```svelte
32
+ <script>
33
+ import { useIncremark, Incremark } from '@incremark/svelte'
34
+ import '@incremark/svelte/style.css'
35
+
36
+ const { blocks, append, finalize, reset } = useIncremark({ gfm: true })
37
+
38
+ async function handleStream(stream) {
39
+ reset()
40
+ for await (const chunk of stream) {
41
+ append(chunk)
42
+ }
43
+ finalize()
44
+ }
45
+ </script>
46
+
47
+ <button on:click={handleStream}>开始</button>
48
+ <Incremark {blocks} />
49
+ ```
50
+
51
+ ## API
52
+
53
+ ### useIncremark(options)
54
+
55
+ 核心 store。
56
+
57
+ **返回值:**
58
+
59
+ | 属性 | 类型 | 说明 |
60
+ |------|------|------|
61
+ | `markdown` | `Writable<string>` | 完整 Markdown |
62
+ | `blocks` | `Readable<Block[]>` | 所有块 |
63
+ | `completedBlocks` | `Writable<Block[]>` | 已完成块 |
64
+ | `pendingBlocks` | `Writable<Block[]>` | 待处理块 |
65
+ | `isLoading` | `Writable<boolean>` | 是否正在加载 |
66
+ | `append` | `Function` | 追加内容 |
67
+ | `finalize` | `Function` | 完成解析 |
68
+ | `reset` | `Function` | 重置状态 |
69
+ | `render` | `Function` | 一次性渲染(reset + append + finalize) |
70
+
71
+ ### useBlockTransformer(sourceBlocks, options)
72
+
73
+ 打字机效果 store。作为解析器和渲染器之间的中间层,控制内容的逐步显示。
74
+
75
+ ## 自定义组件
76
+
77
+ ```svelte
78
+ <script>
79
+ import { useIncremark, Incremark } from '@incremark/svelte'
80
+ import MyCode from './MyCode.svelte'
81
+
82
+ const { blocks } = useIncremark()
83
+ </script>
84
+
85
+ <Incremark {blocks} components={{ code: MyCode }} />
86
+ ```
87
+
88
+ ## 许可证
89
+
90
+ MIT
91
+
@@ -0,0 +1,52 @@
1
+ <!--
2
+ @file ThemeProvider.svelte - 主题提供者组件
3
+ @description 应用主题到容器元素
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import type { DesignTokens } from '@incremark/theme'
8
+ import { applyTheme } from '@incremark/theme'
9
+
10
+ /**
11
+ * 组件 Props
12
+ */
13
+ interface Props {
14
+ /**
15
+ * 主题配置,可以是:
16
+ * - 字符串:'default' | 'dark'
17
+ * - 完整主题对象:DesignTokens
18
+ * - 部分主题对象:Partial<DesignTokens>(会合并到默认主题)
19
+ */
20
+ theme: 'default' | 'dark' | DesignTokens | Partial<DesignTokens>
21
+ /** 额外的 CSS 类名 */
22
+ class?: string
23
+ /** 子组件 */
24
+ children?: import('svelte').Snippet
25
+ }
26
+
27
+ let {
28
+ theme,
29
+ class: className = '',
30
+ children
31
+ }: Props = $props()
32
+
33
+ // 容器引用
34
+ let containerRef: HTMLElement | null = null
35
+
36
+ // 监听主题变化
37
+ // 在 Svelte 5 中,$effect 会自动追踪在 effect 内部访问的响应式值
38
+ // 直接访问 theme prop 和 containerRef,确保都被追踪
39
+ $effect(() => {
40
+ // 在 effect 内部直接访问 theme prop,确保被追踪
41
+ if (containerRef) {
42
+ applyTheme(containerRef, theme)
43
+ }
44
+ })
45
+ </script>
46
+
47
+ <div bind:this={containerRef} class="incremark-theme-provider {className}">
48
+ {#if children}
49
+ {@render children()}
50
+ {/if}
51
+ </div>
52
+
@@ -0,0 +1,21 @@
1
+ import type { DesignTokens } from '@incremark/theme';
2
+ /**
3
+ * 组件 Props
4
+ */
5
+ interface Props {
6
+ /**
7
+ * 主题配置,可以是:
8
+ * - 字符串:'default' | 'dark'
9
+ * - 完整主题对象:DesignTokens
10
+ * - 部分主题对象:Partial<DesignTokens>(会合并到默认主题)
11
+ */
12
+ theme: 'default' | 'dark' | DesignTokens | Partial<DesignTokens>;
13
+ /** 额外的 CSS 类名 */
14
+ class?: string;
15
+ /** 子组件 */
16
+ children?: import('svelte').Snippet;
17
+ }
18
+ declare const ThemeProvider: import("svelte").Component<Props, {}, "">;
19
+ type ThemeProvider = ReturnType<typeof ThemeProvider>;
20
+ export default ThemeProvider;
21
+ //# sourceMappingURL=ThemeProvider.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ThemeProvider.svelte.d.ts","sourceRoot":"","sources":["../src/ThemeProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAIlD;;GAEG;AACH,UAAU,KAAK;IACb;;;;;OAKG;IACH,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAChE,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,UAAU;IACV,QAAQ,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAA;CACpC;AAqCH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -0,0 +1,163 @@
1
+ <!--
2
+ @file AutoScrollContainer.svelte - 自动滚动容器组件
3
+ @description 自动滚动到底部,但尊重用户的手动滚动行为
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import { onMount, onDestroy } from 'svelte'
8
+
9
+ /**
10
+ * 组件 Props
11
+ */
12
+ interface Props {
13
+ /** 是否启用自动滚动 */
14
+ enabled?: boolean
15
+ /** 触发自动滚动的底部阈值(像素) */
16
+ threshold?: number
17
+ /** 滚动行为 */
18
+ behavior?: ScrollBehavior
19
+ /** 子组件 */
20
+ children?: import('svelte').Snippet
21
+ }
22
+
23
+ let {
24
+ enabled = true,
25
+ threshold = 50,
26
+ behavior = 'instant',
27
+ children
28
+ }: Props = $props()
29
+
30
+ // 容器引用
31
+ let containerRef: HTMLDivElement | null = null
32
+
33
+ // 状态
34
+ let isUserScrolledUp = $state(false)
35
+
36
+ // 记录上一次滚动位置,用于判断滚动方向
37
+ let lastScrollTop = 0
38
+ let lastScrollHeight = 0
39
+ let observer: MutationObserver | null = null
40
+
41
+ /**
42
+ * 检查是否在底部附近
43
+ */
44
+ function isNearBottom(): boolean {
45
+ if (!containerRef) return true
46
+
47
+ const { scrollTop, scrollHeight, clientHeight } = containerRef
48
+ return scrollHeight - scrollTop - clientHeight <= threshold
49
+ }
50
+
51
+ /**
52
+ * 滚动到底部
53
+ */
54
+ function scrollToBottom(force = false): void {
55
+ if (!containerRef) return
56
+
57
+ // 如果用户手动向上滚动了,且不是强制滚动,则不自动滚动
58
+ if (isUserScrolledUp && !force) return
59
+
60
+ containerRef.scrollTo({
61
+ top: containerRef.scrollHeight,
62
+ behavior
63
+ })
64
+ }
65
+
66
+ /**
67
+ * 检查是否有滚动条
68
+ */
69
+ function hasScrollbar(): boolean {
70
+ if (!containerRef) return false
71
+ return containerRef.scrollHeight > containerRef.clientHeight
72
+ }
73
+
74
+ /**
75
+ * 处理滚动事件
76
+ */
77
+ function handleScroll(): void {
78
+ if (!containerRef) return
79
+
80
+ const { scrollTop, scrollHeight, clientHeight } = containerRef
81
+
82
+ // 如果没有滚动条,恢复自动滚动
83
+ if (scrollHeight <= clientHeight) {
84
+ isUserScrolledUp = false
85
+ lastScrollTop = 0
86
+ lastScrollHeight = scrollHeight
87
+ return
88
+ }
89
+
90
+ // 检查用户是否滚动到底部附近
91
+ if (isNearBottom()) {
92
+ // 用户滚动到底部,恢复自动滚动
93
+ isUserScrolledUp = false
94
+ } else {
95
+ // 判断是否是用户主动向上滚动
96
+ // 条件:scrollTop 减少(向上滚动)且 scrollHeight 没有变化(不是因为内容增加)
97
+ const isScrollingUp = scrollTop < lastScrollTop
98
+ const isContentUnchanged = scrollHeight === lastScrollHeight
99
+
100
+ if (isScrollingUp && isContentUnchanged) {
101
+ // 用户主动向上滚动,暂停自动滚动
102
+ isUserScrolledUp = true
103
+ }
104
+ }
105
+
106
+ // 更新记录
107
+ lastScrollTop = scrollTop
108
+ lastScrollHeight = scrollHeight
109
+ }
110
+
111
+ // 监听 slot 内容变化(使用 MutationObserver)
112
+ onMount(() => {
113
+ if (!containerRef) return
114
+
115
+ // 初始化滚动位置记录
116
+ lastScrollTop = containerRef.scrollTop
117
+ lastScrollHeight = containerRef.scrollHeight
118
+
119
+ observer = new MutationObserver(() => {
120
+ // 使用 tick 等待 DOM 更新
121
+ setTimeout(() => {
122
+ if (!containerRef) return
123
+
124
+ // 如果没有滚动条,重置状态
125
+ if (!hasScrollbar()) {
126
+ isUserScrolledUp = false
127
+ }
128
+
129
+ // 更新 scrollHeight 记录(内容变化后)
130
+ lastScrollHeight = containerRef.scrollHeight
131
+
132
+ // 自动滚动
133
+ if (enabled && !isUserScrolledUp) {
134
+ scrollToBottom()
135
+ }
136
+ }, 0)
137
+ })
138
+
139
+ observer.observe(containerRef, {
140
+ childList: true,
141
+ subtree: true,
142
+ characterData: true
143
+ })
144
+ })
145
+
146
+ onDestroy(() => {
147
+ observer?.disconnect()
148
+ })
149
+
150
+ // 暴露方法(通过 bind:this 或使用 Svelte 5 的方式)
151
+ // 在 Svelte 5 中,可以使用 $props 的 bind:this 或者通过 context 暴露
152
+ </script>
153
+
154
+ <div
155
+ bind:this={containerRef}
156
+ class="auto-scroll-container"
157
+ onscroll={handleScroll}
158
+ >
159
+ {#if children}
160
+ {@render children()}
161
+ {/if}
162
+ </div>
163
+
@@ -0,0 +1,17 @@
1
+ /**
2
+ * 组件 Props
3
+ */
4
+ interface Props {
5
+ /** 是否启用自动滚动 */
6
+ enabled?: boolean;
7
+ /** 触发自动滚动的底部阈值(像素) */
8
+ threshold?: number;
9
+ /** 滚动行为 */
10
+ behavior?: ScrollBehavior;
11
+ /** 子组件 */
12
+ children?: import('svelte').Snippet;
13
+ }
14
+ declare const AutoScrollContainer: import("svelte").Component<Props, {}, "">;
15
+ type AutoScrollContainer = ReturnType<typeof AutoScrollContainer>;
16
+ export default AutoScrollContainer;
17
+ //# sourceMappingURL=AutoScrollContainer.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AutoScrollContainer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/AutoScrollContainer.svelte.ts"],"names":[],"mappings":"AAME;;GAEG;AACH,UAAU,KAAK;IACb,eAAe;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW;IACX,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,UAAU;IACV,QAAQ,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAA;CACpC;AAmJH,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,183 @@
1
+ <!--
2
+ @file Incremark.svelte - 主渲染组件
3
+ @description 主入口组件,管理 blocks 渲染
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import type { Readable } from 'svelte/store'
8
+ import type { ParsedBlock, RootContent } from '@incremark/core'
9
+ import type { HTML } from 'mdast'
10
+
11
+ import { getDefinitionsContext } from '../context/definitionsContext'
12
+ import type { UseIncremarkReturn } from '../stores/useIncremark'
13
+ import type { ComponentMap, BlockWithStableId } from './types'
14
+
15
+ // 导入默认组件
16
+ import IncremarkParagraph from './IncremarkParagraph.svelte'
17
+ import IncremarkHeading from './IncremarkHeading.svelte'
18
+ import IncremarkCode from './IncremarkCode.svelte'
19
+ import IncremarkList from './IncremarkList.svelte'
20
+ import IncremarkTable from './IncremarkTable.svelte'
21
+ import IncremarkBlockquote from './IncremarkBlockquote.svelte'
22
+ import IncremarkThematicBreak from './IncremarkThematicBreak.svelte'
23
+ import IncremarkMath from './IncremarkMath.svelte'
24
+ import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
25
+ import IncremarkDefault from './IncremarkDefault.svelte'
26
+ import IncremarkFootnotes from './IncremarkFootnotes.svelte'
27
+
28
+ /**
29
+ * 检查是否是 html 节点
30
+ */
31
+ function isHtmlNode(node: RootContent): node is HTML {
32
+ return node.type === 'html'
33
+ }
34
+
35
+ /**
36
+ * 组件 Props
37
+ */
38
+ interface Props {
39
+ /** 要渲染的块列表(来自 useIncremark 的 blocks) */
40
+ blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>
41
+ /** 自定义组件映射,key 为节点类型 */
42
+ components?: ComponentMap
43
+ /** 待处理块的样式类名 */
44
+ pendingClass?: string
45
+ /** 已完成块的样式类名 */
46
+ completedClass?: string
47
+ /** 是否显示块状态边框 */
48
+ showBlockStatus?: boolean
49
+ /** 可选:useIncremark 返回的对象(用于自动注入数据) */
50
+ incremark?: UseIncremarkReturn
51
+ }
52
+
53
+ let {
54
+ blocks = [],
55
+ components = {},
56
+ pendingClass = 'incremark-pending',
57
+ completedClass = 'incremark-completed',
58
+ showBlockStatus = false,
59
+ incremark
60
+ }: Props = $props()
61
+
62
+ // 从 context 获取 footnoteReferenceOrder(如果有的话)
63
+ // 使用 $derived 来确保响应式
64
+ const footnoteReferenceOrder = $derived.by(() => {
65
+ try {
66
+ const context = getDefinitionsContext()
67
+ return context.footnoteReferenceOrder
68
+ } catch {
69
+ // 如果没有 context,返回 null
70
+ return null
71
+ }
72
+ })
73
+
74
+ // 计算 isFinalized(当不使用 incremark 时)
75
+ const actualIsFinalized = $derived.by(() => {
76
+ if (incremark) {
77
+ // 如果提供了 incremark,在模板中直接使用 $incremark.isFinalized
78
+ return false
79
+ }
80
+ // 如果手动传入 blocks,自动判断是否所有 block 都是 completed
81
+ const blocksArray = Array.isArray(blocks) ? blocks : []
82
+ return blocksArray.length > 0 && blocksArray.every(b => b.status === 'completed')
83
+ })
84
+
85
+ // 默认组件映射
86
+ const defaultComponents: ComponentMap = {
87
+ paragraph: IncremarkParagraph,
88
+ heading: IncremarkHeading,
89
+ code: IncremarkCode,
90
+ list: IncremarkList,
91
+ table: IncremarkTable,
92
+ blockquote: IncremarkBlockquote,
93
+ thematicBreak: IncremarkThematicBreak,
94
+ math: IncremarkMath,
95
+ inlineMath: IncremarkMath,
96
+ htmlElement: IncremarkHtmlElement
97
+ }
98
+
99
+ // 合并用户组件和默认组件
100
+ const mergedComponents = $derived({
101
+ ...defaultComponents,
102
+ ...components
103
+ })
104
+
105
+ /**
106
+ * 获取组件
107
+ */
108
+ function getComponent(type: string): any {
109
+ return mergedComponents[type] || components?.default || IncremarkDefault
110
+ }
111
+
112
+ // 处理 blocks(可能是 store 或数组)
113
+ const blocksArray = $derived.by(() => {
114
+ if (incremark) {
115
+ // 如果提供了 incremark,在模板中直接使用 incremark.blocks(store)
116
+ return []
117
+ }
118
+ return Array.isArray(blocks) ? blocks : []
119
+ })
120
+
121
+ // 提取 incremark 的 stores(如果存在)
122
+ const incremarkBlocks = $derived.by(() => incremark?.blocks)
123
+ const incremarkIsFinalized = $derived.by(() => incremark?.isFinalized)
124
+ </script>
125
+
126
+ <div class="incremark">
127
+ <!-- 主要内容块 -->
128
+ {#if incremark && incremarkBlocks}
129
+ <!-- 使用 incremark 的 blocks store -->
130
+ {#each ($incremarkBlocks || []) as block (block.stableId)}
131
+ {#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
132
+ <div
133
+ class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as BlockWithStableId).isLastPending ? 'incremark-last-pending' : ''}"
134
+ >
135
+ <!-- HTML 节点:渲染为代码块显示源代码 -->
136
+ {#if isHtmlNode(block.node)}
137
+ <pre class="incremark-html-code"><code>{block.node.value}</code></pre>
138
+ {:else}
139
+ <!-- 其他节点:使用对应组件 -->
140
+ {@const Component = getComponent(block.node.type)}
141
+ {#if Component}
142
+ <Component node={block.node} />
143
+ {/if}
144
+ {/if}
145
+ </div>
146
+ {/if}
147
+ {/each}
148
+ {:else}
149
+ <!-- 使用传入的 blocks 数组 -->
150
+ {#each blocksArray as block (block.stableId)}
151
+ {#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
152
+ <div
153
+ class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {block.isLastPending ? 'incremark-last-pending' : ''}"
154
+ >
155
+ <!-- HTML 节点:渲染为代码块显示源代码 -->
156
+ {#if isHtmlNode(block.node)}
157
+ <pre class="incremark-html-code"><code>{block.node.value}</code></pre>
158
+ {:else}
159
+ <!-- 其他节点:使用对应组件 -->
160
+ {@const Component = getComponent(block.node.type)}
161
+ {#if Component}
162
+ <Component node={block.node} />
163
+ {/if}
164
+ {/if}
165
+ </div>
166
+ {/if}
167
+ {/each}
168
+ {/if}
169
+
170
+ <!-- 脚注列表(仅在 finalize 后显示) -->
171
+ {#if incremark && incremarkIsFinalized && footnoteReferenceOrder && $incremarkIsFinalized}
172
+ {@const footnoteOrder = $footnoteReferenceOrder ?? []}
173
+ {#if footnoteOrder.length > 0}
174
+ <IncremarkFootnotes />
175
+ {/if}
176
+ {:else if !incremark && actualIsFinalized && footnoteReferenceOrder}
177
+ {@const footnoteOrder = $footnoteReferenceOrder ?? []}
178
+ {#if footnoteOrder.length > 0}
179
+ <IncremarkFootnotes />
180
+ {/if}
181
+ {/if}
182
+ </div>
183
+
@@ -0,0 +1,24 @@
1
+ import type { Readable } from 'svelte/store';
2
+ import type { UseIncremarkReturn } from '../stores/useIncremark';
3
+ import type { ComponentMap, BlockWithStableId } from './types';
4
+ /**
5
+ * 组件 Props
6
+ */
7
+ interface Props {
8
+ /** 要渲染的块列表(来自 useIncremark 的 blocks) */
9
+ blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>;
10
+ /** 自定义组件映射,key 为节点类型 */
11
+ components?: ComponentMap;
12
+ /** 待处理块的样式类名 */
13
+ pendingClass?: string;
14
+ /** 已完成块的样式类名 */
15
+ completedClass?: string;
16
+ /** 是否显示块状态边框 */
17
+ showBlockStatus?: boolean;
18
+ /** 可选:useIncremark 返回的对象(用于自动注入数据) */
19
+ incremark?: UseIncremarkReturn;
20
+ }
21
+ declare const Incremark: import("svelte").Component<Props, {}, "">;
22
+ type Incremark = ReturnType<typeof Incremark>;
23
+ export default Incremark;
24
+ //# sourceMappingURL=Incremark.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAe5D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,EAAE,GAAG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAC5D,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,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;AAmKH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -0,0 +1,30 @@
1
+ <!--
2
+ @file IncremarkBlockquote.svelte - 引用组件
3
+ @description 渲染 Markdown 引用块
4
+ -->
5
+
6
+ <script lang="ts">
7
+ import type { Blockquote } from 'mdast'
8
+ import IncremarkParagraph from './IncremarkParagraph.svelte'
9
+
10
+ /**
11
+ * 组件 Props
12
+ */
13
+ interface Props {
14
+ /** 引用节点 */
15
+ node: Blockquote
16
+ }
17
+
18
+ let { node }: Props = $props()
19
+ </script>
20
+
21
+ <blockquote class="incremark-blockquote">
22
+ {#each node.children as child, index (index)}
23
+ {#if child.type === 'paragraph'}
24
+ <IncremarkParagraph node={child} />
25
+ {:else}
26
+ <div class="unknown-child">{child.type}</div>
27
+ {/if}
28
+ {/each}
29
+ </blockquote>
30
+
@@ -0,0 +1,12 @@
1
+ import type { Blockquote } from 'mdast';
2
+ /**
3
+ * 组件 Props
4
+ */
5
+ interface Props {
6
+ /** 引用节点 */
7
+ node: Blockquote;
8
+ }
9
+ declare const IncremarkBlockquote: import("svelte").Component<Props, {}, "">;
10
+ type IncremarkBlockquote = ReturnType<typeof IncremarkBlockquote>;
11
+ export default IncremarkBlockquote;
12
+ //# sourceMappingURL=IncremarkBlockquote.svelte.d.ts.map
@@ -0,0 +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;AAwBH,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}