@incremark/vue 0.2.2 → 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 (58) hide show
  1. package/dist/ThemeProvider.vue.d.ts +25 -0
  2. package/dist/components/AutoScrollContainer.vue.d.ts +31 -0
  3. package/dist/components/Incremark.vue.d.ts +30 -0
  4. package/dist/components/IncremarkBlockquote.vue.d.ts +6 -0
  5. package/dist/components/IncremarkCode.vue.d.ts +16 -0
  6. package/dist/components/IncremarkDefault.vue.d.ts +6 -0
  7. package/dist/components/IncremarkFootnotes.vue.d.ts +2 -0
  8. package/dist/components/IncremarkHeading.vue.d.ts +6 -0
  9. package/dist/components/IncremarkHtmlElement.vue.d.ts +20 -0
  10. package/dist/components/IncremarkInline.vue.d.ts +6 -0
  11. package/dist/components/IncremarkList.vue.d.ts +6 -0
  12. package/dist/components/IncremarkMath.vue.d.ts +17 -0
  13. package/dist/components/IncremarkParagraph.vue.d.ts +6 -0
  14. package/dist/components/IncremarkRenderer.vue.d.ts +6 -0
  15. package/dist/components/IncremarkTable.vue.d.ts +6 -0
  16. package/dist/components/IncremarkThematicBreak.vue.d.ts +2 -0
  17. package/{src/components/index.ts → dist/components/index.d.ts} +16 -23
  18. package/dist/composables/index.d.ts +8 -0
  19. package/dist/composables/useBlockTransformer.d.ts +68 -0
  20. package/dist/composables/useDefinationsContext.d.ts +9 -0
  21. package/dist/composables/useDevTools.d.ts +18 -0
  22. package/dist/composables/useIncremark.d.ts +112 -0
  23. package/dist/composables/useProvideDefinations.d.ts +16 -0
  24. package/dist/composables/useStreamRenderer.d.ts +26 -0
  25. package/dist/composables/useTypewriter.d.ts +37 -0
  26. package/dist/index.d.ts +10 -0
  27. package/dist/index.js +1 -2
  28. package/dist/index.js.map +1 -1
  29. package/dist/utils/cursor.d.ts +18 -0
  30. package/package.json +11 -13
  31. package/dist/index.css +0 -6
  32. package/dist/index.css.map +0 -1
  33. package/src/ThemeProvider.vue +0 -41
  34. package/src/components/AutoScrollContainer.vue +0 -164
  35. package/src/components/Incremark.vue +0 -131
  36. package/src/components/IncremarkBlockquote.vue +0 -18
  37. package/src/components/IncremarkCode.vue +0 -236
  38. package/src/components/IncremarkDefault.vue +0 -15
  39. package/src/components/IncremarkFootnotes.vue +0 -78
  40. package/src/components/IncremarkHeading.vue +0 -17
  41. package/src/components/IncremarkHtmlElement.vue +0 -127
  42. package/src/components/IncremarkInline.vue +0 -187
  43. package/src/components/IncremarkList.vue +0 -46
  44. package/src/components/IncremarkMath.vue +0 -105
  45. package/src/components/IncremarkParagraph.vue +0 -14
  46. package/src/components/IncremarkRenderer.vue +0 -50
  47. package/src/components/IncremarkTable.vue +0 -42
  48. package/src/components/IncremarkThematicBreak.vue +0 -8
  49. package/src/composables/index.ts +0 -11
  50. package/src/composables/useBlockTransformer.ts +0 -141
  51. package/src/composables/useDefinationsContext.ts +0 -16
  52. package/src/composables/useDevTools.ts +0 -54
  53. package/src/composables/useIncremark.ts +0 -238
  54. package/src/composables/useProvideDefinations.ts +0 -61
  55. package/src/composables/useStreamRenderer.ts +0 -55
  56. package/src/composables/useTypewriter.ts +0 -205
  57. package/src/index.ts +0 -78
  58. package/src/utils/cursor.ts +0 -46
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file Cursor Utils - 光标工具函数
3
+ *
4
+ * @description
5
+ * 用于在 AST 节点末尾添加光标的工具函数。
6
+ *
7
+ * @author Incremark Team
8
+ * @license MIT
9
+ */
10
+ import type { RootContent } from '@incremark/core';
11
+ /**
12
+ * 在节点末尾添加光标
13
+ *
14
+ * @param node - 要添加光标的节点
15
+ * @param cursor - 光标字符
16
+ * @returns 添加了光标的新节点
17
+ */
18
+ export declare function addCursorToNode(node: RootContent, cursor: string): RootContent;
package/package.json CHANGED
@@ -1,31 +1,27 @@
1
1
  {
2
2
  "name": "@incremark/vue",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Incremark Vue 3 集成",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
8
- "types": "./src/index.ts",
8
+ "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./src/index.ts",
11
+ "types": "./dist/index.d.ts",
12
12
  "import": "./dist/index.js"
13
13
  },
14
- "./components/*": {
15
- "import": "./src/components/*.vue"
16
- },
17
14
  "./style.css": "@incremark/theme/styles.css"
18
15
  },
19
16
  "files": [
20
- "dist",
21
- "src"
17
+ "dist"
22
18
  ],
23
19
  "dependencies": {
24
20
  "shiki": "^3.20.0",
25
- "@incremark/core": "0.2.2",
26
- "@incremark/devtools": "0.2.2",
27
- "@incremark/shared": "0.2.2",
28
- "@incremark/theme": "0.2.2"
21
+ "@incremark/devtools": "0.2.3",
22
+ "@incremark/shared": "0.2.3",
23
+ "@incremark/theme": "0.2.3",
24
+ "@incremark/core": "0.2.3"
29
25
  },
30
26
  "peerDependencies": {
31
27
  "vue": "^3.3.0",
@@ -46,6 +42,7 @@
46
42
  "esbuild-plugin-vue3": "^0.4.2",
47
43
  "tsup": "^8.0.0",
48
44
  "typescript": "^5.3.0",
45
+ "vue-tsc": "^2.2.12",
49
46
  "vue": "^3.4.0"
50
47
  },
51
48
  "keywords": [
@@ -63,7 +60,8 @@
63
60
  },
64
61
  "homepage": "https://www.incremark.com/",
65
62
  "scripts": {
66
- "build": "tsup",
63
+ "build": "tsup && npm run build:types",
64
+ "build:types": "vue-tsc -p tsconfig.build.json",
67
65
  "dev": "tsup --watch"
68
66
  }
69
67
  }
package/dist/index.css DELETED
@@ -1,6 +0,0 @@
1
- /* sfc-style:/Users/yishuai/develop/ai/markdown/packages/vue/src/components/AutoScrollContainer.vue?type=style&index=0 */
2
- .auto-scroll-container[data-v-e0d180b8] {
3
- overflow-y: auto;
4
- height: 100%;
5
- }
6
- /*# sourceMappingURL=index.css.map */
@@ -1 +0,0 @@
1
- {"version":3,"sources":["sfc-style:/Users/yishuai/develop/ai/markdown/packages/vue/src/components/AutoScrollContainer.vue?type=style&index=0"],"sourcesContent":["\n.auto-scroll-container[data-v-e0d180b8] {\n overflow-y: auto;\n height: 100%;\n}\n"],"mappings":";AACA,CAAC,qBAAqB,CAAC;AACrB,cAAY;AACZ,UAAQ;AACV;","names":[]}
@@ -1,41 +0,0 @@
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,164 +0,0 @@
1
- <script setup lang="ts">
2
- import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
3
-
4
- const props = withDefaults(defineProps<{
5
- /** 是否启用自动滚动 */
6
- enabled?: boolean
7
- /** 触发自动滚动的底部阈值(像素) */
8
- threshold?: number
9
- /** 滚动行为 */
10
- behavior?: ScrollBehavior
11
- }>(), {
12
- enabled: true,
13
- threshold: 50,
14
- behavior: 'instant'
15
- })
16
-
17
- const containerRef = ref<HTMLDivElement | null>(null)
18
- const isUserScrolledUp = ref(false)
19
-
20
- // 记录上一次滚动位置,用于判断滚动方向
21
- let lastScrollTop = 0
22
- let lastScrollHeight = 0
23
-
24
- /**
25
- * 检查是否在底部附近
26
- */
27
- function isNearBottom(): boolean {
28
- const container = containerRef.value
29
- if (!container) return true
30
-
31
- const { scrollTop, scrollHeight, clientHeight } = container
32
- return scrollHeight - scrollTop - clientHeight <= props.threshold
33
- }
34
-
35
- /**
36
- * 滚动到底部
37
- */
38
- function scrollToBottom(force = false): void {
39
- const container = containerRef.value
40
- if (!container) return
41
-
42
- // 如果用户手动向上滚动了,且不是强制滚动,则不自动滚动
43
- if (isUserScrolledUp.value && !force) return
44
-
45
- container.scrollTo({
46
- top: container.scrollHeight,
47
- behavior: props.behavior
48
- })
49
- }
50
-
51
- /**
52
- * 检查是否有滚动条
53
- */
54
- function hasScrollbar(): boolean {
55
- const container = containerRef.value
56
- if (!container) return false
57
- return container.scrollHeight > container.clientHeight
58
- }
59
-
60
- /**
61
- * 处理滚动事件
62
- */
63
- function handleScroll(): void {
64
- const container = containerRef.value
65
- if (!container) return
66
-
67
- const { scrollTop, scrollHeight, clientHeight } = container
68
-
69
- // 如果没有滚动条,恢复自动滚动
70
- if (scrollHeight <= clientHeight) {
71
- isUserScrolledUp.value = false
72
- lastScrollTop = 0
73
- lastScrollHeight = scrollHeight
74
- return
75
- }
76
-
77
- // 检查用户是否滚动到底部附近
78
- if (isNearBottom()) {
79
- // 用户滚动到底部,恢复自动滚动
80
- isUserScrolledUp.value = false
81
- } else {
82
- // 判断是否是用户主动向上滚动
83
- // 条件:scrollTop 减少(向上滚动)且 scrollHeight 没有变化(不是因为内容增加)
84
- const isScrollingUp = scrollTop < lastScrollTop
85
- const isContentUnchanged = scrollHeight === lastScrollHeight
86
-
87
- if (isScrollingUp && isContentUnchanged) {
88
- // 用户主动向上滚动,暂停自动滚动
89
- isUserScrolledUp.value = true
90
- }
91
- }
92
-
93
- // 更新记录
94
- lastScrollTop = scrollTop
95
- lastScrollHeight = scrollHeight
96
- }
97
-
98
- // 监听 slot 内容变化(使用 MutationObserver)
99
- let observer: MutationObserver | null = null
100
-
101
- onMounted(() => {
102
- if (!containerRef.value) return
103
-
104
- // 初始化滚动位置记录
105
- lastScrollTop = containerRef.value.scrollTop
106
- lastScrollHeight = containerRef.value.scrollHeight
107
-
108
- observer = new MutationObserver(() => {
109
- nextTick(() => {
110
- if (!containerRef.value) return
111
-
112
- // 如果没有滚动条,重置状态
113
- if (!hasScrollbar()) {
114
- isUserScrolledUp.value = false
115
- }
116
-
117
- // 更新 scrollHeight 记录(内容变化后)
118
- lastScrollHeight = containerRef.value.scrollHeight
119
-
120
- // 自动滚动
121
- if (props.enabled && !isUserScrolledUp.value) {
122
- scrollToBottom()
123
- }
124
- })
125
- })
126
-
127
- observer.observe(containerRef.value, {
128
- childList: true,
129
- subtree: true,
130
- characterData: true
131
- })
132
- })
133
-
134
- onUnmounted(() => {
135
- observer?.disconnect()
136
- })
137
-
138
- // 暴露方法给父组件
139
- defineExpose({
140
- /** 强制滚动到底部 */
141
- scrollToBottom: () => scrollToBottom(true),
142
- /** 是否用户手动向上滚动了 */
143
- isUserScrolledUp: () => isUserScrolledUp.value,
144
- /** 容器元素引用 */
145
- container: containerRef
146
- })
147
- </script>
148
-
149
- <template>
150
- <div
151
- ref="containerRef"
152
- class="auto-scroll-container"
153
- @scroll="handleScroll"
154
- >
155
- <slot />
156
- </div>
157
- </template>
158
-
159
- <style scoped>
160
- .auto-scroll-container {
161
- overflow-y: auto;
162
- height: 100%;
163
- }
164
- </style>
@@ -1,131 +0,0 @@
1
- <script setup lang="ts">
2
- import { computed, type Component } from 'vue'
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'
7
- import IncremarkHeading from './IncremarkHeading.vue'
8
- import IncremarkParagraph from './IncremarkParagraph.vue'
9
- import IncremarkCode from './IncremarkCode.vue'
10
- import IncremarkList from './IncremarkList.vue'
11
- import IncremarkTable from './IncremarkTable.vue'
12
- import IncremarkBlockquote from './IncremarkBlockquote.vue'
13
- import IncremarkThematicBreak from './IncremarkThematicBreak.vue'
14
- import IncremarkMath from './IncremarkMath.vue'
15
- import IncremarkHtmlElement from './IncremarkHtmlElement.vue'
16
- import IncremarkDefault from './IncremarkDefault.vue'
17
- import IncremarkFootnotes from './IncremarkFootnotes.vue'
18
-
19
- // 组件映射类型
20
- export type ComponentMap = Partial<Record<string, Component>>
21
-
22
- /**
23
- * 检查是否是 html 节点
24
- */
25
- function isHtmlNode(node: RootContent): node is HTML {
26
- return node.type === 'html'
27
- }
28
-
29
- // 带稳定 ID 的块类型
30
- export interface BlockWithStableId extends ParsedBlock {
31
- stableId: string
32
- isLastPending?: boolean // 是否是最后一个 pending 块
33
- }
34
-
35
- const props = withDefaults(
36
- defineProps<{
37
- /** 要渲染的块列表(来自 useIncremark 的 blocks) */
38
- blocks?: BlockWithStableId[]
39
- /** 自定义组件映射,key 为节点类型 */
40
- components?: ComponentMap
41
- /** 待处理块的样式类名 */
42
- pendingClass?: string
43
- /** 已完成块的样式类名 */
44
- completedClass?: string
45
- /** 是否显示块状态边框 */
46
- showBlockStatus?: boolean
47
- /** 可选:useIncremark 返回的对象(用于自动注入数据) */
48
- incremark?: UseIncremarkReturn
49
- }>(),
50
- {
51
- blocks: () => [],
52
- components: () => ({}),
53
- pendingClass: 'incremark-pending',
54
- completedClass: 'incremark-completed',
55
- showBlockStatus: false
56
- }
57
- )
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
-
80
- // 默认组件映射
81
- const defaultComponents: Record<string, Component> = {
82
- heading: IncremarkHeading,
83
- paragraph: IncremarkParagraph,
84
- code: IncremarkCode,
85
- list: IncremarkList,
86
- table: IncremarkTable,
87
- blockquote: IncremarkBlockquote,
88
- thematicBreak: IncremarkThematicBreak,
89
- math: IncremarkMath,
90
- inlineMath: IncremarkMath,
91
- htmlElement: IncremarkHtmlElement
92
- }
93
-
94
- // 合并用户组件和默认组件
95
- const mergedComponents = computed(() => ({
96
- ...defaultComponents,
97
- ...props.components
98
- }))
99
-
100
- function getComponent(type: string): Component {
101
- return mergedComponents.value[type] || props.components?.default || IncremarkDefault
102
- }
103
- </script>
104
-
105
- <template>
106
- <div class="incremark">
107
- <!-- 主要内容块 -->
108
- <template v-for="block in actualBlocks">
109
- <div
110
- v-if="block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'"
111
- :key="block.stableId"
112
- :class="[
113
- 'incremark-block',
114
- block.status === 'completed' ? completedClass : pendingClass,
115
- { 'incremark-show-status': showBlockStatus },
116
- { 'incremark-last-pending': block.isLastPending }
117
- ]"
118
- >
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" />
123
- </div>
124
- </template>
125
-
126
- <!-- 脚注列表(仅在 finalize 后显示) -->
127
- <IncremarkFootnotes
128
- v-if="actualIsFinalized && (footnoteReferenceOrder as any).value?.length > 0"
129
- />
130
- </div>
131
- </template>
@@ -1,18 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Blockquote } from 'mdast'
3
- import IncremarkParagraph from './IncremarkParagraph.vue'
4
-
5
- defineProps<{
6
- node: Blockquote
7
- }>()
8
- </script>
9
-
10
- <template>
11
- <blockquote class="incremark-blockquote">
12
- <template v-for="(child, index) in node.children" :key="index">
13
- <IncremarkParagraph v-if="child.type === 'paragraph'" :node="child" />
14
- <div v-else class="unknown-child">{{ child.type }}</div>
15
- </template>
16
- </blockquote>
17
- </template>
18
-
@@ -1,236 +0,0 @@
1
- <script setup lang="ts">
2
- import type { Code } from 'mdast'
3
- import { computed, ref, watch, shallowRef, onUnmounted } from 'vue'
4
-
5
- const props = withDefaults(
6
- defineProps<{
7
- node: Code
8
- /** Shiki 主题,默认 github-dark */
9
- theme?: string
10
- /** 是否禁用代码高亮 */
11
- disableHighlight?: boolean
12
- /** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
13
- mermaidDelay?: number
14
- }>(),
15
- {
16
- theme: 'github-dark',
17
- disableHighlight: false,
18
- mermaidDelay: 500
19
- }
20
- )
21
-
22
- const copied = ref(false)
23
- const highlightedHtml = ref('')
24
- const isHighlighting = ref(false)
25
- const highlightError = ref(false)
26
-
27
- // Mermaid 支持
28
- const mermaidSvg = ref('')
29
- const mermaidError = ref('')
30
- const mermaidLoading = ref(false)
31
- const mermaidRef = shallowRef<any>(null)
32
- let mermaidTimer: ReturnType<typeof setTimeout> | null = null
33
- // 视图模式:'preview' | 'source'
34
- const mermaidViewMode = ref<'preview' | 'source'>('preview')
35
-
36
- function toggleMermaidView() {
37
- mermaidViewMode.value = mermaidViewMode.value === 'preview' ? 'source' : 'preview'
38
- }
39
-
40
- const language = computed(() => props.node.lang || 'text')
41
- const code = computed(() => props.node.value)
42
- const isMermaid = computed(() => language.value === 'mermaid')
43
-
44
- // 缓存 highlighter
45
- const highlighterRef = shallowRef<any>(null)
46
- const loadedLanguages = new Set<string>()
47
- const loadedThemes = new Set<string>()
48
-
49
- // Mermaid 渲染(带防抖动)
50
- function scheduleRenderMermaid() {
51
- if (!isMermaid.value || !code.value) return
52
-
53
- // 清除之前的定时器
54
- if (mermaidTimer) {
55
- clearTimeout(mermaidTimer)
56
- }
57
-
58
- // 显示加载状态
59
- mermaidLoading.value = true
60
-
61
- // 防抖动延迟渲染
62
- mermaidTimer = setTimeout(() => {
63
- doRenderMermaid()
64
- }, props.mermaidDelay)
65
- }
66
-
67
- async function doRenderMermaid() {
68
- if (!code.value) return
69
-
70
- mermaidError.value = ''
71
-
72
- try {
73
- // 动态导入 mermaid
74
- if (!mermaidRef.value) {
75
- // @ts-ignore - mermaid 是可选依赖
76
- const mermaidModule = await import('mermaid')
77
- mermaidRef.value = mermaidModule.default
78
- mermaidRef.value.initialize({
79
- startOnLoad: false,
80
- theme: 'dark',
81
- securityLevel: 'loose'
82
- })
83
- }
84
-
85
- const mermaid = mermaidRef.value
86
- const id = `mermaid-${Date.now()}-${Math.random().toString(36).slice(2)}`
87
-
88
- const { svg } = await mermaid.render(id, code.value)
89
- mermaidSvg.value = svg
90
- } catch (e: any) {
91
- // 不显示错误,可能是代码还不完整
92
- mermaidError.value = ''
93
- mermaidSvg.value = ''
94
- } finally {
95
- mermaidLoading.value = false
96
- }
97
- }
98
-
99
- onUnmounted(() => {
100
- if (mermaidTimer) {
101
- clearTimeout(mermaidTimer)
102
- }
103
- })
104
-
105
- // 动态加载 shiki 并高亮
106
- async function highlight() {
107
- if (isMermaid.value) {
108
- scheduleRenderMermaid()
109
- return
110
- }
111
-
112
- if (!code.value || props.disableHighlight) {
113
- highlightedHtml.value = ''
114
- return
115
- }
116
-
117
- isHighlighting.value = true
118
- highlightError.value = false
119
-
120
- try {
121
- // 动态导入 shiki
122
- if (!highlighterRef.value) {
123
- const { createHighlighter } = await import('shiki')
124
- highlighterRef.value = await createHighlighter({
125
- themes: [props.theme as any],
126
- langs: []
127
- })
128
- loadedThemes.add(props.theme)
129
- }
130
-
131
- const highlighter = highlighterRef.value
132
- const lang = language.value
133
-
134
- // 按需加载语言
135
- if (!loadedLanguages.has(lang) && lang !== 'text') {
136
- try {
137
- await highlighter.loadLanguage(lang)
138
- loadedLanguages.add(lang)
139
- } catch {
140
- // 语言不支持,标记但不阻止
141
- }
142
- }
143
-
144
- // 按需加载主题
145
- if (!loadedThemes.has(props.theme)) {
146
- try {
147
- await highlighter.loadTheme(props.theme)
148
- loadedThemes.add(props.theme)
149
- } catch {
150
- // 主题不支持
151
- }
152
- }
153
-
154
- const html = highlighter.codeToHtml(code.value, {
155
- lang: loadedLanguages.has(lang) ? lang : 'text',
156
- theme: loadedThemes.has(props.theme) ? props.theme : 'github-dark'
157
- })
158
- highlightedHtml.value = html
159
- } catch (e) {
160
- // Shiki 不可用或加载失败
161
- highlightError.value = true
162
- highlightedHtml.value = ''
163
- } finally {
164
- isHighlighting.value = false
165
- }
166
- }
167
-
168
- // 监听代码变化,重新高亮/渲染
169
- watch([code, () => props.theme, isMermaid], highlight, { immediate: true })
170
-
171
- async function copyCode() {
172
- try {
173
- await navigator.clipboard.writeText(code.value)
174
- copied.value = true
175
- setTimeout(() => {
176
- copied.value = false
177
- }, 2000)
178
- } catch {
179
- // 复制失败静默处理
180
- }
181
- }
182
- </script>
183
-
184
- <template>
185
- <!-- Mermaid 图表 -->
186
- <div v-if="isMermaid" class="incremark-mermaid">
187
- <div class="mermaid-header">
188
- <span class="language">MERMAID</span>
189
- <div class="mermaid-actions">
190
- <button
191
- class="code-btn"
192
- @click="toggleMermaidView"
193
- type="button"
194
- :disabled="!mermaidSvg"
195
- >
196
- {{ mermaidViewMode === 'preview' ? '源码' : '预览' }}
197
- </button>
198
- <button class="code-btn" @click="copyCode" type="button">
199
- {{ copied ? '✓ 已复制' : '复制' }}
200
- </button>
201
- </div>
202
- </div>
203
- <div class="mermaid-content">
204
- <!-- 加载中 -->
205
- <div v-if="mermaidLoading && !mermaidSvg" class="mermaid-loading">
206
- <pre class="mermaid-source-code">{{ code }}</pre>
207
- </div>
208
- <!-- 源码模式 -->
209
- <pre v-else-if="mermaidViewMode === 'source'" class="mermaid-source-code">{{ code }}</pre>
210
- <!-- 预览模式 -->
211
- <div v-else-if="mermaidSvg" v-html="mermaidSvg" class="mermaid-svg" />
212
- <!-- 无法渲染时显示源码 -->
213
- <pre v-else class="mermaid-source-code">{{ code }}</pre>
214
- </div>
215
- </div>
216
-
217
- <!-- 普通代码块 -->
218
- <div v-else class="incremark-code">
219
- <div class="code-header">
220
- <span class="language">{{ language }}</span>
221
- <button class="code-btn" @click="copyCode" type="button">
222
- {{ copied ? '✓ 已复制' : '复制' }}
223
- </button>
224
- </div>
225
- <div class="code-content">
226
- <!-- 正在加载高亮 -->
227
- <div v-if="isHighlighting && !highlightedHtml" class="code-loading">
228
- <pre><code>{{ code }}</code></pre>
229
- </div>
230
- <!-- 高亮后的代码 -->
231
- <div v-else-if="highlightedHtml" v-html="highlightedHtml" class="shiki-wrapper" />
232
- <!-- 回退:无高亮 -->
233
- <pre v-else class="code-fallback"><code>{{ code }}</code></pre>
234
- </div>
235
- </div>
236
- </template>
@@ -1,15 +0,0 @@
1
- <script setup lang="ts">
2
- import type { RootContent } from 'mdast'
3
-
4
- defineProps<{
5
- node: RootContent
6
- }>()
7
- </script>
8
-
9
- <template>
10
- <div class="incremark-default">
11
- <span class="type-badge">{{ node.type }}</span>
12
- <pre>{{ JSON.stringify(node, null, 2) }}</pre>
13
- </div>
14
- </template>
15
-