@incremark/vue 0.1.0 → 0.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incremark/vue",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Incremark Vue 3 集成",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,7 +14,7 @@
14
14
  "./components/*": {
15
15
  "import": "./src/components/*.vue"
16
16
  },
17
- "./style.css": "./dist/index.css"
17
+ "./style.css": "@incremark/theme/styles.css"
18
18
  },
19
19
  "files": [
20
20
  "dist",
@@ -22,8 +22,10 @@
22
22
  ],
23
23
  "dependencies": {
24
24
  "shiki": "^3.20.0",
25
- "@incremark/core": "0.1.0",
26
- "@incremark/devtools": "0.1.0"
25
+ "@incremark/core": "0.2.0",
26
+ "@incremark/devtools": "0.2.0",
27
+ "@incremark/shared": "0.2.0",
28
+ "@incremark/theme": "0.2.0"
27
29
  },
28
30
  "peerDependencies": {
29
31
  "vue": "^3.3.0",
@@ -58,7 +60,7 @@
58
60
  "url": "https://github.com/kingshuaishuai/incremark.git",
59
61
  "directory": "packages/vue"
60
62
  },
61
- "homepage": "https://incremark-docs.vercel.app/",
63
+ "homepage": "https://www.incremark.com/",
62
64
  "scripts": {
63
65
  "build": "tsup",
64
66
  "dev": "tsup --watch"
@@ -0,0 +1,41 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch } from 'vue'
3
+ import type { DesignTokens } from '@incremark/theme'
4
+ import { applyTheme } from '@incremark/theme'
5
+
6
+ /**
7
+ * 主题配置,可以是:
8
+ * - 字符串:'default' | 'dark'
9
+ * - 完整主题对象:DesignTokens
10
+ * - 部分主题对象:Partial<DesignTokens>(会合并到默认主题)
11
+ */
12
+ const props = withDefaults(
13
+ defineProps<{
14
+ theme: 'default' | 'dark' | DesignTokens | Partial<DesignTokens>
15
+ class?: string
16
+ }>(),
17
+ {
18
+ class: ''
19
+ }
20
+ )
21
+
22
+ const containerRef = ref<HTMLElement>()
23
+
24
+ watch(
25
+ () => props.theme,
26
+ (theme) => {
27
+ if (containerRef.value) {
28
+ // 应用主题到容器元素
29
+ applyTheme(containerRef.value, theme)
30
+ }
31
+ },
32
+ { immediate: true }
33
+ )
34
+ </script>
35
+
36
+ <template>
37
+ <div ref="containerRef" :class="props.class" class="incremark-theme-provider">
38
+ <slot />
39
+ </div>
40
+ </template>
41
+
@@ -1,6 +1,9 @@
1
1
  <script setup lang="ts">
2
2
  import { computed, type Component } from 'vue'
3
- import type { ParsedBlock } from '@incremark/core'
3
+ import type { ParsedBlock, RootContent } from '@incremark/core'
4
+ import type { HTML } from 'mdast'
5
+ import { useDefinationsContext } from '../composables/useDefinationsContext'
6
+ import type { UseIncremarkReturn } from '../composables/useIncremark'
4
7
  import IncremarkHeading from './IncremarkHeading.vue'
5
8
  import IncremarkParagraph from './IncremarkParagraph.vue'
6
9
  import IncremarkCode from './IncremarkCode.vue'
@@ -9,11 +12,20 @@ import IncremarkTable from './IncremarkTable.vue'
9
12
  import IncremarkBlockquote from './IncremarkBlockquote.vue'
10
13
  import IncremarkThematicBreak from './IncremarkThematicBreak.vue'
11
14
  import IncremarkMath from './IncremarkMath.vue'
15
+ import IncremarkHtmlElement from './IncremarkHtmlElement.vue'
12
16
  import IncremarkDefault from './IncremarkDefault.vue'
17
+ import IncremarkFootnotes from './IncremarkFootnotes.vue'
13
18
 
14
19
  // 组件映射类型
15
20
  export type ComponentMap = Partial<Record<string, Component>>
16
21
 
22
+ /**
23
+ * 检查是否是 html 节点
24
+ */
25
+ function isHtmlNode(node: RootContent): node is HTML {
26
+ return node.type === 'html'
27
+ }
28
+
17
29
  // 带稳定 ID 的块类型
18
30
  export interface BlockWithStableId extends ParsedBlock {
19
31
  stableId: string
@@ -23,7 +35,7 @@ export interface BlockWithStableId extends ParsedBlock {
23
35
  const props = withDefaults(
24
36
  defineProps<{
25
37
  /** 要渲染的块列表(来自 useIncremark 的 blocks) */
26
- blocks: BlockWithStableId[]
38
+ blocks?: BlockWithStableId[]
27
39
  /** 自定义组件映射,key 为节点类型 */
28
40
  components?: ComponentMap
29
41
  /** 待处理块的样式类名 */
@@ -32,8 +44,11 @@ const props = withDefaults(
32
44
  completedClass?: string
33
45
  /** 是否显示块状态边框 */
34
46
  showBlockStatus?: boolean
47
+ /** 可选:useIncremark 返回的对象(用于自动注入数据) */
48
+ incremark?: UseIncremarkReturn
35
49
  }>(),
36
50
  {
51
+ blocks: () => [],
37
52
  components: () => ({}),
38
53
  pendingClass: 'incremark-pending',
39
54
  completedClass: 'incremark-completed',
@@ -41,6 +56,27 @@ const props = withDefaults(
41
56
  }
42
57
  )
43
58
 
59
+ // 从 context 获取 footnoteReferenceOrder(如果有的话)
60
+ let footnoteReferenceOrder
61
+ try {
62
+ const context = useDefinationsContext()
63
+ footnoteReferenceOrder = context.footnoteReferenceOrder
64
+ } catch {
65
+ // 如果没有 context,使用空数组
66
+ footnoteReferenceOrder = computed(() => [])
67
+ }
68
+
69
+ // 计算实际使用的 blocks 和 isFinalized
70
+ const actualBlocks = computed<BlockWithStableId[]>(() => props.incremark?.blocks.value || props.blocks || [])
71
+ const actualIsFinalized = computed(() => {
72
+ if (props.incremark) {
73
+ return props.incremark.isFinalized.value
74
+ }
75
+ // 如果手动传入 blocks,自动判断是否所有 block 都是 completed
76
+ const blocks = props.blocks || []
77
+ return blocks.length > 0 && blocks.every(b => b.status === 'completed')
78
+ })
79
+
44
80
  // 默认组件映射
45
81
  const defaultComponents: Record<string, Component> = {
46
82
  heading: IncremarkHeading,
@@ -51,7 +87,8 @@ const defaultComponents: Record<string, Component> = {
51
87
  blockquote: IncremarkBlockquote,
52
88
  thematicBreak: IncremarkThematicBreak,
53
89
  math: IncremarkMath,
54
- inlineMath: IncremarkMath
90
+ inlineMath: IncremarkMath,
91
+ htmlElement: IncremarkHtmlElement
55
92
  }
56
93
 
57
94
  // 合并用户组件和默认组件
@@ -67,9 +104,10 @@ function getComponent(type: string): Component {
67
104
 
68
105
  <template>
69
106
  <div class="incremark">
70
- <TransitionGroup name="incremark-fade">
107
+ <!-- 主要内容块 -->
108
+ <template v-for="block in actualBlocks">
71
109
  <div
72
- v-for="block in blocks"
110
+ v-if="block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'"
73
111
  :key="block.stableId"
74
112
  :class="[
75
113
  'incremark-block',
@@ -78,24 +116,16 @@ function getComponent(type: string): Component {
78
116
  { 'incremark-last-pending': block.isLastPending }
79
117
  ]"
80
118
  >
81
- <component :is="getComponent(block.node.type)" :node="block.node" />
119
+ <!-- HTML 节点:渲染为代码块显示源代码 -->
120
+ <pre v-if="isHtmlNode(block.node)" class="incremark-html-code"><code>{{ (block.node as HTML).value }}</code></pre>
121
+ <!-- 其他节点:使用对应组件 -->
122
+ <component v-else :is="getComponent(block.node.type)" :node="block.node" />
82
123
  </div>
83
- </TransitionGroup>
124
+ </template>
125
+
126
+ <!-- 脚注列表(仅在 finalize 后显示) -->
127
+ <IncremarkFootnotes
128
+ v-if="actualIsFinalized && (footnoteReferenceOrder as any).value?.length > 0"
129
+ />
84
130
  </div>
85
131
  </template>
86
-
87
- <style scoped>
88
- .incremark-block.incremark-show-status.incremark-pending {
89
- border-left: 3px solid #a855f7;
90
- padding-left: 12px;
91
- opacity: 0.8;
92
- }
93
-
94
- .incremark-fade-enter-active {
95
- transition: opacity 0.2s ease-out;
96
- }
97
-
98
- .incremark-fade-enter-from {
99
- opacity: 0;
100
- }
101
- </style>
@@ -16,25 +16,3 @@ defineProps<{
16
16
  </blockquote>
17
17
  </template>
18
18
 
19
- <style scoped>
20
- .incremark-blockquote {
21
- margin: 1em 0;
22
- padding: 0.5em 1em;
23
- border-left: 4px solid #3b82f6;
24
- background: #f0f7ff;
25
- border-radius: 0 4px 4px 0;
26
- }
27
-
28
- .incremark-blockquote :deep(p) {
29
- margin: 0.5em 0;
30
- }
31
-
32
- .incremark-blockquote :deep(p:first-child) {
33
- margin-top: 0;
34
- }
35
-
36
- .incremark-blockquote :deep(p:last-child) {
37
- margin-bottom: 0;
38
- }
39
- </style>
40
-
@@ -188,14 +188,14 @@ async function copyCode() {
188
188
  <span class="language">MERMAID</span>
189
189
  <div class="mermaid-actions">
190
190
  <button
191
- class="view-toggle"
191
+ class="code-btn"
192
192
  @click="toggleMermaidView"
193
193
  type="button"
194
194
  :disabled="!mermaidSvg"
195
195
  >
196
196
  {{ mermaidViewMode === 'preview' ? '源码' : '预览' }}
197
197
  </button>
198
- <button class="copy-btn" @click="copyCode" type="button">
198
+ <button class="code-btn" @click="copyCode" type="button">
199
199
  {{ copied ? '✓ 已复制' : '复制' }}
200
200
  </button>
201
201
  </div>
@@ -218,7 +218,7 @@ async function copyCode() {
218
218
  <div v-else class="incremark-code">
219
219
  <div class="code-header">
220
220
  <span class="language">{{ language }}</span>
221
- <button class="copy-btn" @click="copyCode" type="button">
221
+ <button class="code-btn" @click="copyCode" type="button">
222
222
  {{ copied ? '✓ 已复制' : '复制' }}
223
223
  </button>
224
224
  </div>
@@ -234,161 +234,3 @@ async function copyCode() {
234
234
  </div>
235
235
  </div>
236
236
  </template>
237
-
238
- <style scoped>
239
- /* Mermaid 样式 */
240
- .incremark-mermaid {
241
- margin: 1em 0;
242
- border-radius: 8px;
243
- overflow: hidden;
244
- background: #1a1a2e;
245
- }
246
-
247
- .mermaid-header {
248
- display: flex;
249
- justify-content: space-between;
250
- align-items: center;
251
- padding: 8px 16px;
252
- background: #16213e;
253
- border-bottom: 1px solid #0f3460;
254
- font-size: 12px;
255
- }
256
-
257
- .mermaid-actions {
258
- display: flex;
259
- gap: 8px;
260
- }
261
-
262
- .view-toggle {
263
- padding: 4px 10px;
264
- border: 1px solid #0f3460;
265
- border-radius: 6px;
266
- background: transparent;
267
- color: #8b949e;
268
- font-size: 12px;
269
- cursor: pointer;
270
- transition: all 0.2s;
271
- }
272
-
273
- .view-toggle:hover:not(:disabled) {
274
- background: #0f3460;
275
- color: #e0e0e0;
276
- }
277
-
278
- .view-toggle:disabled {
279
- opacity: 0.5;
280
- cursor: not-allowed;
281
- }
282
-
283
- .mermaid-content {
284
- padding: 16px;
285
- min-height: 100px;
286
- }
287
-
288
- .mermaid-loading {
289
- color: #8b949e;
290
- font-size: 14px;
291
- }
292
-
293
- .mermaid-source-code {
294
- margin: 0;
295
- padding: 12px;
296
- background: #0d1117;
297
- border-radius: 6px;
298
- color: #c9d1d9;
299
- font-family: 'Fira Code', 'SF Mono', monospace;
300
- font-size: 13px;
301
- line-height: 1.5;
302
- white-space: pre-wrap;
303
- overflow-x: auto;
304
- }
305
-
306
- .mermaid-svg {
307
- overflow-x: auto;
308
- }
309
-
310
- .mermaid-svg :deep(svg) {
311
- max-width: 100%;
312
- height: auto;
313
- }
314
-
315
- /* 代码块样式 */
316
- .incremark-code {
317
- margin: 1em 0;
318
- border-radius: 8px;
319
- overflow: hidden;
320
- background: #24292e;
321
- }
322
-
323
- .code-header {
324
- display: flex;
325
- justify-content: space-between;
326
- align-items: center;
327
- padding: 8px 16px;
328
- background: #1f2428;
329
- border-bottom: 1px solid #30363d;
330
- font-size: 12px;
331
- }
332
-
333
- .language {
334
- color: #8b949e;
335
- text-transform: uppercase;
336
- font-weight: 500;
337
- letter-spacing: 0.5px;
338
- }
339
-
340
- .copy-btn {
341
- padding: 4px 12px;
342
- border: 1px solid #30363d;
343
- border-radius: 6px;
344
- background: transparent;
345
- color: #8b949e;
346
- font-size: 12px;
347
- cursor: pointer;
348
- transition: all 0.2s;
349
- }
350
-
351
- .copy-btn:hover {
352
- background: #30363d;
353
- color: #c9d1d9;
354
- }
355
-
356
- .code-content {
357
- overflow-x: auto;
358
- }
359
-
360
- .code-loading {
361
- opacity: 0.7;
362
- }
363
-
364
- /* Shiki 生成的代码样式 */
365
- .shiki-wrapper :deep(pre) {
366
- margin: 0;
367
- padding: 16px;
368
- background: transparent !important;
369
- overflow-x: auto;
370
- }
371
-
372
- .shiki-wrapper :deep(code) {
373
- font-family: 'Fira Code', 'SF Mono', 'Monaco', 'Consolas', monospace;
374
- font-size: 14px;
375
- line-height: 1.6;
376
- }
377
-
378
- /* 回退样式 */
379
- .code-fallback,
380
- .code-loading pre {
381
- margin: 0;
382
- padding: 16px;
383
- overflow-x: auto;
384
- background: transparent;
385
- }
386
-
387
- .code-fallback code,
388
- .code-loading code {
389
- font-family: 'Fira Code', 'SF Mono', 'Monaco', 'Consolas', monospace;
390
- font-size: 14px;
391
- line-height: 1.6;
392
- color: #c9d1d9;
393
- }
394
- </style>
@@ -13,30 +13,3 @@ defineProps<{
13
13
  </div>
14
14
  </template>
15
15
 
16
- <style scoped>
17
- .incremark-default {
18
- margin: 0.5em 0;
19
- padding: 10px;
20
- background: #fff3cd;
21
- border: 1px solid #ffc107;
22
- border-radius: 4px;
23
- font-size: 12px;
24
- }
25
-
26
- .type-badge {
27
- display: inline-block;
28
- padding: 2px 8px;
29
- background: #ffc107;
30
- border-radius: 4px;
31
- font-weight: 600;
32
- margin-bottom: 8px;
33
- }
34
-
35
- pre {
36
- margin: 0;
37
- white-space: pre-wrap;
38
- word-break: break-all;
39
- font-size: 11px;
40
- }
41
- </style>
42
-
@@ -0,0 +1,78 @@
1
+ <!--
2
+ /**
3
+ * 脚注列表组件
4
+ *
5
+ * 在文档底部渲染所有脚注定义,按引用出现的顺序排列
6
+ *
7
+ * @component IncremarkFootnotes
8
+ *
9
+ * @remarks
10
+ * 样式定义在 @incremark/theme 中的 footnotes.less
11
+ * footnoteReferenceOrder 自动从 context 获取,无需手动传递
12
+ */
13
+ -->
14
+ <script setup lang="ts">
15
+ import { computed } from 'vue'
16
+ import type { FootnoteDefinition, RootContent } from 'mdast'
17
+ import { useDefinationsContext } from '../composables/useDefinationsContext'
18
+ import IncremarkRenderer from './IncremarkRenderer.vue'
19
+
20
+ const { definations, footnoteDefinitions, footnoteReferenceOrder } = useDefinationsContext()
21
+
22
+ /**
23
+ * 按引用顺序排列的脚注列表
24
+ * 只显示已有定义的脚注
25
+ */
26
+ const orderedFootnotes = computed<Array<{ identifier: string; definition: FootnoteDefinition }>>(() => {
27
+ return footnoteReferenceOrder.value
28
+ .map(identifier => ({
29
+ identifier,
30
+ definition: footnoteDefinitions.value[identifier]
31
+ }))
32
+ .filter(item => item.definition !== undefined)
33
+ })
34
+
35
+ /**
36
+ * 是否有脚注需要显示
37
+ */
38
+ const hasFootnotes = computed(() => orderedFootnotes.value.length > 0)
39
+ </script>
40
+
41
+ <template>
42
+ <section v-if="hasFootnotes" class="incremark-footnotes">
43
+ <hr class="incremark-footnotes-divider" />
44
+ <ol class="incremark-footnotes-list">
45
+ <li
46
+ v-for="(item, index) in orderedFootnotes"
47
+ :key="item.identifier"
48
+ :id="`fn-${item.identifier}`"
49
+ class="incremark-footnote-item"
50
+ >
51
+ <div class="incremark-footnote-content">
52
+ <!-- 脚注序号 -->
53
+ <span class="incremark-footnote-number">{{ index + 1 }}.</span>
54
+
55
+ <!-- 脚注内容 -->
56
+ <div class="incremark-footnote-body">
57
+ <IncremarkRenderer
58
+ v-for="(child, childIndex) in item.definition.children"
59
+ :key="childIndex"
60
+ :node="(child as RootContent)"
61
+ />
62
+ </div>
63
+ </div>
64
+
65
+ <!-- 返回链接 -->
66
+ <a
67
+ :href="`#fnref-${item.identifier}`"
68
+ class="incremark-footnote-backref"
69
+ aria-label="返回引用位置"
70
+ >
71
+
72
+ </a>
73
+ </li>
74
+ </ol>
75
+ </section>
76
+ </template>
77
+
78
+
@@ -11,23 +11,7 @@ const tag = computed(() => `h${props.node.depth}`)
11
11
  </script>
12
12
 
13
13
  <template>
14
- <component :is="tag" class="incremark-heading">
14
+ <component :is="tag" :class="`incremark-heading h${node.depth}`">
15
15
  <IncremarkInline :nodes="node.children" />
16
16
  </component>
17
17
  </template>
18
-
19
- <style scoped>
20
- .incremark-heading {
21
- margin: 0.5em 0;
22
- font-weight: 600;
23
- line-height: 1.3;
24
- }
25
-
26
- h1 { font-size: 2em; border-bottom: 1px solid #eee; padding-bottom: 0.3em; }
27
- h2 { font-size: 1.5em; }
28
- h3 { font-size: 1.25em; }
29
- h4 { font-size: 1em; }
30
- h5 { font-size: 0.875em; }
31
- h6 { font-size: 0.85em; color: #666; }
32
- </style>
33
-
@@ -0,0 +1,138 @@
1
+ <script setup lang="ts">
2
+ import type { RootContent, PhrasingContent } from 'mdast'
3
+ import IncremarkInline from './IncremarkInline.vue'
4
+
5
+ /**
6
+ * HtmlElementNode 类型定义(与 @incremark/core 中的定义一致)
7
+ */
8
+ interface HtmlElementNode {
9
+ type: 'htmlElement'
10
+ tagName: string
11
+ attrs: Record<string, string>
12
+ children: RootContent[]
13
+ data?: {
14
+ rawHtml?: string
15
+ parsed?: boolean
16
+ originalType?: string
17
+ }
18
+ }
19
+
20
+ defineProps<{
21
+ node: HtmlElementNode
22
+ }>()
23
+
24
+ /**
25
+ * 判断是否是行内元素
26
+ */
27
+ function isInlineElement(tagName: string): boolean {
28
+ const inlineElements = [
29
+ 'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite',
30
+ 'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map',
31
+ 'object', 'output', 'q', 'samp', 'script', 'select', 'small',
32
+ 'span', 'strong', 'sub', 'sup', 'textarea', 'time', 'tt', 'var'
33
+ ]
34
+ return inlineElements.includes(tagName.toLowerCase())
35
+ }
36
+
37
+ /**
38
+ * 判断是否是自闭合元素
39
+ */
40
+ function isVoidElement(tagName: string): boolean {
41
+ const voidElements = [
42
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
43
+ 'link', 'meta', 'param', 'source', 'track', 'wbr'
44
+ ]
45
+ return voidElements.includes(tagName.toLowerCase())
46
+ }
47
+
48
+ /**
49
+ * 判断子节点是否都是行内内容
50
+ */
51
+ function hasOnlyInlineChildren(children: RootContent[]): boolean {
52
+ if (!children || children.length === 0) return true
53
+
54
+ return children.every(child => {
55
+ const type = child.type
56
+ // 常见的行内类型
57
+ const inlineTypes = ['text', 'strong', 'emphasis', 'inlineCode', 'link', 'image', 'break', 'html', 'htmlElement']
58
+ if (inlineTypes.includes(type)) {
59
+ // 如果是 htmlElement,检查是否是行内元素
60
+ if (type === 'htmlElement') {
61
+ return isInlineElement((child as unknown as HtmlElementNode).tagName)
62
+ }
63
+ return true
64
+ }
65
+ return false
66
+ })
67
+ }
68
+
69
+ /**
70
+ * 将属性对象转换为 HTML 属性字符串(用于 v-bind)
71
+ */
72
+ function getAttrs(attrs: Record<string, string>): Record<string, string> {
73
+ // 过滤掉可能有问题的属性
74
+ const result: Record<string, string> = {}
75
+ for (const [key, value] of Object.entries(attrs)) {
76
+ // 跳过事件属性(已在解析时过滤,这里双重保险)
77
+ if (key.startsWith('on')) continue
78
+ result[key] = value
79
+ }
80
+ return result
81
+ }
82
+ </script>
83
+
84
+ <template>
85
+ <component
86
+ :is="node.tagName"
87
+ v-bind="getAttrs(node.attrs)"
88
+ :class="['incremark-html-element', `incremark-${node.tagName}`]"
89
+ >
90
+ <!-- 自闭合元素没有子节点 -->
91
+ <template v-if="!isVoidElement(node.tagName)">
92
+ <!-- 如果子节点都是行内内容,使用 IncremarkInline -->
93
+ <template v-if="hasOnlyInlineChildren(node.children)">
94
+ <IncremarkInline :nodes="(node.children as PhrasingContent[])" />
95
+ </template>
96
+
97
+ <!-- 否则递归渲染每个子节点 -->
98
+ <template v-else>
99
+ <template v-for="(child, idx) in node.children" :key="idx">
100
+ <!-- 如果子节点是 htmlElement,递归 -->
101
+ <IncremarkHtmlElement
102
+ v-if="child.type === 'htmlElement'"
103
+ :node="(child as unknown as HtmlElementNode)"
104
+ />
105
+ <!-- 如果是文本节点 -->
106
+ <template v-else-if="child.type === 'text'">
107
+ {{ (child as any).value }}
108
+ </template>
109
+ <!-- 其他类型尝试用 IncremarkInline -->
110
+ <IncremarkInline
111
+ v-else-if="['strong', 'emphasis', 'inlineCode', 'link', 'image', 'break'].includes(child.type)"
112
+ :nodes="[child as PhrasingContent]"
113
+ />
114
+ <!-- 段落等块级元素 -->
115
+ <template v-else-if="child.type === 'paragraph'">
116
+ <p><IncremarkInline :nodes="((child as any).children as PhrasingContent[])" /></p>
117
+ </template>
118
+ <!-- 其他未知类型,显示原始 -->
119
+ <template v-else>
120
+ <div class="incremark-unknown-child">{{ child.type }}</div>
121
+ </template>
122
+ </template>
123
+ </template>
124
+ </template>
125
+ </component>
126
+ </template>
127
+
128
+ <style scoped>
129
+ .incremark-html-element {
130
+ /* 保持原有样式 */
131
+ }
132
+
133
+ .incremark-unknown-child {
134
+ color: #999;
135
+ font-style: italic;
136
+ }
137
+ </style>
138
+