@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/dist/index.css +5 -356
- package/dist/index.css.map +1 -1
- package/dist/index.js +1181 -489
- package/dist/index.js.map +1 -1
- package/package.json +7 -5
- package/src/ThemeProvider.vue +41 -0
- package/src/components/Incremark.vue +53 -23
- package/src/components/IncremarkBlockquote.vue +0 -22
- package/src/components/IncremarkCode.vue +3 -161
- package/src/components/IncremarkDefault.vue +0 -27
- package/src/components/IncremarkFootnotes.vue +78 -0
- package/src/components/IncremarkHeading.vue +1 -17
- package/src/components/IncremarkHtmlElement.vue +138 -0
- package/src/components/IncremarkInline.vue +95 -56
- package/src/components/IncremarkList.vue +0 -37
- package/src/components/IncremarkMath.vue +0 -45
- package/src/components/IncremarkParagraph.vue +0 -7
- package/src/components/IncremarkRenderer.vue +15 -3
- package/src/components/IncremarkTable.vue +0 -32
- package/src/components/IncremarkThematicBreak.vue +0 -8
- package/src/components/index.ts +2 -0
- package/src/composables/useDefinationsContext.ts +16 -0
- package/src/composables/useIncremark.ts +47 -187
- package/src/composables/useProvideDefinations.ts +61 -0
- package/src/composables/useTypewriter.ts +205 -0
- package/src/index.ts +15 -0
- package/src/utils/cursor.ts +46 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { PhrasingContent,
|
|
2
|
+
import type { PhrasingContent, RootContent, ImageReference, LinkReference } from 'mdast'
|
|
3
3
|
import type { TextChunk } from '@incremark/core'
|
|
4
|
+
import {
|
|
5
|
+
type TextNodeWithChunks,
|
|
6
|
+
hasChunks,
|
|
7
|
+
getStableText,
|
|
8
|
+
isHtmlNode
|
|
9
|
+
} from '@incremark/shared'
|
|
4
10
|
import IncremarkMath from './IncremarkMath.vue'
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
interface TextNodeWithChunks extends Text {
|
|
8
|
-
stableLength?: number
|
|
9
|
-
chunks?: TextChunk[]
|
|
10
|
-
}
|
|
11
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.vue'
|
|
12
|
+
import { useDefinationsContext } from '../composables/useDefinationsContext'
|
|
11
13
|
|
|
12
14
|
// Math 节点类型
|
|
13
15
|
interface MathNode {
|
|
@@ -15,44 +17,54 @@ interface MathNode {
|
|
|
15
17
|
value: string
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
// HtmlElement 节点类型
|
|
21
|
+
interface HtmlElementNode {
|
|
22
|
+
type: 'htmlElement'
|
|
23
|
+
tagName: string
|
|
24
|
+
attrs: Record<string, string>
|
|
25
|
+
children: RootContent[]
|
|
26
|
+
}
|
|
21
27
|
|
|
22
28
|
/**
|
|
23
|
-
*
|
|
29
|
+
* 类型守卫:检查是否是 htmlElement 节点
|
|
24
30
|
*/
|
|
25
|
-
function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
function isHtmlElementNode(node: PhrasingContent): node is PhrasingContent & HtmlElementNode {
|
|
32
|
+
return (node as unknown as HtmlElementNode).type === 'htmlElement'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 类型守卫:检查是否是 imageReference 节点
|
|
37
|
+
*/
|
|
38
|
+
function isImageReference(node: PhrasingContent): node is ImageReference {
|
|
39
|
+
return node.type === 'imageReference'
|
|
30
40
|
}
|
|
31
41
|
|
|
32
42
|
/**
|
|
33
|
-
*
|
|
43
|
+
* 类型守卫:检查是否是 linkReference 节点
|
|
34
44
|
*/
|
|
35
|
-
function
|
|
36
|
-
return node.type === '
|
|
45
|
+
function isLinkReference(node: PhrasingContent): node is LinkReference {
|
|
46
|
+
return node.type === 'linkReference'
|
|
37
47
|
}
|
|
38
48
|
|
|
49
|
+
const props = defineProps<{
|
|
50
|
+
nodes: PhrasingContent[]
|
|
51
|
+
}>()
|
|
52
|
+
|
|
53
|
+
const {
|
|
54
|
+
definations,
|
|
55
|
+
footnoteDefinitions
|
|
56
|
+
} = useDefinationsContext()
|
|
57
|
+
|
|
39
58
|
/**
|
|
40
59
|
* 获取节点的 chunks(类型安全)
|
|
41
60
|
*/
|
|
42
61
|
function getChunks(node: PhrasingContent): TextChunk[] | undefined {
|
|
43
62
|
if (hasChunks(node)) {
|
|
44
|
-
return node.chunks
|
|
63
|
+
return (node as TextNodeWithChunks).chunks
|
|
45
64
|
}
|
|
46
65
|
return undefined
|
|
47
66
|
}
|
|
48
67
|
|
|
49
|
-
/**
|
|
50
|
-
* 类型守卫:检查是否是 HTML 节点
|
|
51
|
-
*/
|
|
52
|
-
function isHtmlNode(node: PhrasingContent): node is HTML {
|
|
53
|
-
return node.type === 'html'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
68
|
/**
|
|
57
69
|
* 类型守卫:检查是否是 inlineMath 节点
|
|
58
70
|
* inlineMath 是 mdast-util-math 扩展的类型,不在标准 PhrasingContent 中
|
|
@@ -60,6 +72,7 @@ function isHtmlNode(node: PhrasingContent): node is HTML {
|
|
|
60
72
|
function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode {
|
|
61
73
|
return (node as unknown as MathNode).type === 'inlineMath'
|
|
62
74
|
}
|
|
75
|
+
|
|
63
76
|
</script>
|
|
64
77
|
|
|
65
78
|
<template>
|
|
@@ -79,6 +92,15 @@ function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode
|
|
|
79
92
|
<!-- 行内公式 -->
|
|
80
93
|
<IncremarkMath v-else-if="isInlineMath(node)" :node="(node as unknown as MathNode)" />
|
|
81
94
|
|
|
95
|
+
<!-- htmlElement 节点(结构化的 HTML 元素) -->
|
|
96
|
+
<IncremarkHtmlElement
|
|
97
|
+
v-else-if="isHtmlElementNode(node)"
|
|
98
|
+
:node="(node as unknown as HtmlElementNode)"
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<!-- HTML 节点(原始 HTML,如未启用 htmlTree 选项) -->
|
|
102
|
+
<span v-else-if="isHtmlNode(node)" style="display: contents;" v-html="(node as any).value"></span>
|
|
103
|
+
|
|
82
104
|
<!-- 加粗 -->
|
|
83
105
|
<strong v-else-if="node.type === 'strong'">
|
|
84
106
|
<IncremarkInline :nodes="(node.children as PhrasingContent[])" />
|
|
@@ -90,14 +112,15 @@ function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode
|
|
|
90
112
|
</em>
|
|
91
113
|
|
|
92
114
|
<!-- 行内代码 -->
|
|
93
|
-
<code v-else-if="node.type === 'inlineCode'" class="incremark-inline-code">{{ node.value }}</code>
|
|
115
|
+
<code v-else-if="node.type === 'inlineCode'" class="incremark-inline-code">{{ (node as any).value }}</code>
|
|
94
116
|
|
|
95
117
|
<!-- 链接 -->
|
|
96
118
|
<a
|
|
97
119
|
v-else-if="node.type === 'link'"
|
|
120
|
+
class="incremark-link"
|
|
98
121
|
:href="node.url"
|
|
99
122
|
target="_blank"
|
|
100
|
-
rel="noopener"
|
|
123
|
+
rel="noopener noreferrer"
|
|
101
124
|
>
|
|
102
125
|
<IncremarkInline :nodes="(node.children as PhrasingContent[])" />
|
|
103
126
|
</a>
|
|
@@ -105,11 +128,54 @@ function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode
|
|
|
105
128
|
<!-- 图片 -->
|
|
106
129
|
<img
|
|
107
130
|
v-else-if="node.type === 'image'"
|
|
131
|
+
class="incremark-image"
|
|
108
132
|
:src="node.url"
|
|
109
133
|
:alt="node.alt || ''"
|
|
134
|
+
:title="(node as any).title || undefined"
|
|
110
135
|
loading="lazy"
|
|
111
136
|
/>
|
|
112
137
|
|
|
138
|
+
<!-- 引用式图片(imageReference) -->
|
|
139
|
+
<template v-else-if="isImageReference(node)">
|
|
140
|
+
<img
|
|
141
|
+
v-if="definations[node.identifier]"
|
|
142
|
+
class="incremark-image incremark-reference-image"
|
|
143
|
+
:src="definations[node.identifier].url"
|
|
144
|
+
:alt="(node as ImageReference).alt || ''"
|
|
145
|
+
:title="definations[node.identifier].title || undefined"
|
|
146
|
+
loading="lazy"
|
|
147
|
+
/>
|
|
148
|
+
<!-- 如果没有找到定义,渲染为原始文本(降级处理) -->
|
|
149
|
+
<span v-else class="incremark-image-ref-missing">
|
|
150
|
+
![{{ (node as ImageReference).alt }}][{{ (node as ImageReference).identifier || (node as ImageReference).label }}]
|
|
151
|
+
</span>
|
|
152
|
+
</template>
|
|
153
|
+
|
|
154
|
+
<!-- 引用式链接(linkReference) -->
|
|
155
|
+
<template v-else-if="isLinkReference(node)">
|
|
156
|
+
<a
|
|
157
|
+
v-if="definations[node.identifier]"
|
|
158
|
+
class="incremark-link incremark-reference-link"
|
|
159
|
+
:href="definations[node.identifier].url"
|
|
160
|
+
:title="definations[node.identifier].title || undefined"
|
|
161
|
+
target="_blank"
|
|
162
|
+
rel="noopener noreferrer"
|
|
163
|
+
>
|
|
164
|
+
<IncremarkInline :nodes="((node as LinkReference).children as PhrasingContent[])" />
|
|
165
|
+
</a>
|
|
166
|
+
<!-- 如果没有找到定义,渲染为原始文本(降级处理) -->
|
|
167
|
+
<span v-else class="incremark-link-ref-missing">
|
|
168
|
+
[{{ ((node as LinkReference).children as any[]).map((c: any) => c.value).join('') }}][{{ (node as LinkReference).identifier || (node as LinkReference).label }}]
|
|
169
|
+
</span>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<!-- 脚注引用(footnoteReference) -->
|
|
173
|
+
<sup v-else-if="node.type === 'footnoteReference'" class="incremark-footnote-ref">
|
|
174
|
+
<a :href="`#fn-${(node as any).identifier}`" :id="`fnref-${(node as any).identifier}`">
|
|
175
|
+
[{{ (node as any).identifier }}]
|
|
176
|
+
</a>
|
|
177
|
+
</sup>
|
|
178
|
+
|
|
113
179
|
<!-- 换行 -->
|
|
114
180
|
<br v-else-if="node.type === 'break'" />
|
|
115
181
|
|
|
@@ -117,32 +183,5 @@ function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode
|
|
|
117
183
|
<del v-else-if="node.type === 'delete'">
|
|
118
184
|
<IncremarkInline :nodes="(node.children as PhrasingContent[])" />
|
|
119
185
|
</del>
|
|
120
|
-
|
|
121
|
-
<!-- 原始 HTML -->
|
|
122
|
-
<span v-else-if="isHtmlNode(node)" v-html="node.value"></span>
|
|
123
186
|
</template>
|
|
124
187
|
</template>
|
|
125
|
-
|
|
126
|
-
<style>
|
|
127
|
-
.incremark-inline-code {
|
|
128
|
-
padding: 0.2em 0.4em;
|
|
129
|
-
background: rgba(0, 0, 0, 0.06);
|
|
130
|
-
border-radius: 4px;
|
|
131
|
-
font-family: 'Fira Code', 'SF Mono', Consolas, monospace;
|
|
132
|
-
font-size: 0.9em;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/* 渐入动画 */
|
|
136
|
-
.incremark-fade-in {
|
|
137
|
-
animation: incremark-fade-in 0.4s ease-out;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
@keyframes incremark-fade-in {
|
|
141
|
-
from {
|
|
142
|
-
opacity: 0;
|
|
143
|
-
}
|
|
144
|
-
to {
|
|
145
|
-
opacity: 1;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
</style>
|
|
@@ -44,40 +44,3 @@ function getItemContent(item: ListItem): PhrasingContent[] {
|
|
|
44
44
|
</component>
|
|
45
45
|
</template>
|
|
46
46
|
|
|
47
|
-
<style scoped>
|
|
48
|
-
.incremark-list {
|
|
49
|
-
margin: 0.75em 0;
|
|
50
|
-
padding-left: 2em;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.incremark-list.task-list {
|
|
54
|
-
list-style: none;
|
|
55
|
-
padding-left: 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
.incremark-list-item {
|
|
59
|
-
margin: 0.25em 0;
|
|
60
|
-
line-height: 1.6;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.task-item {
|
|
64
|
-
list-style: none;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.task-label {
|
|
68
|
-
display: flex;
|
|
69
|
-
align-items: flex-start;
|
|
70
|
-
gap: 0.5em;
|
|
71
|
-
cursor: default;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
.checkbox {
|
|
75
|
-
margin-top: 0.3em;
|
|
76
|
-
flex-shrink: 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.task-content {
|
|
80
|
-
flex: 1;
|
|
81
|
-
}
|
|
82
|
-
</style>
|
|
83
|
-
|
|
@@ -103,48 +103,3 @@ watch(formula, scheduleRender, { immediate: true })
|
|
|
103
103
|
<pre v-else class="math-source-block"><code>{{ formula }}</code></pre>
|
|
104
104
|
</div>
|
|
105
105
|
</template>
|
|
106
|
-
|
|
107
|
-
<style scoped>
|
|
108
|
-
.incremark-math-inline {
|
|
109
|
-
display: inline;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.incremark-math-block {
|
|
113
|
-
margin: 1em 0;
|
|
114
|
-
padding: 1em;
|
|
115
|
-
overflow-x: auto;
|
|
116
|
-
text-align: center;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
.math-source {
|
|
120
|
-
background: #f3f4f6;
|
|
121
|
-
padding: 0.1em 0.3em;
|
|
122
|
-
border-radius: 3px;
|
|
123
|
-
font-size: 0.9em;
|
|
124
|
-
color: #6b7280;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.math-source-block {
|
|
128
|
-
margin: 0;
|
|
129
|
-
padding: 1em;
|
|
130
|
-
background: #f3f4f6;
|
|
131
|
-
border-radius: 6px;
|
|
132
|
-
text-align: left;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.math-source-block code {
|
|
136
|
-
font-family: 'Fira Code', monospace;
|
|
137
|
-
font-size: 0.9em;
|
|
138
|
-
color: #374151;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.math-rendered :deep(.katex) {
|
|
142
|
-
font-size: 1.1em;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
.math-rendered :deep(.katex-display) {
|
|
146
|
-
margin: 0;
|
|
147
|
-
overflow-x: auto;
|
|
148
|
-
overflow-y: hidden;
|
|
149
|
-
}
|
|
150
|
-
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { RootContent } from 'mdast'
|
|
2
|
+
import type { RootContent, HTML } from 'mdast'
|
|
3
3
|
import type { Component } from 'vue'
|
|
4
4
|
import IncremarkHeading from './IncremarkHeading.vue'
|
|
5
5
|
import IncremarkParagraph from './IncremarkParagraph.vue'
|
|
@@ -9,9 +9,10 @@ import IncremarkTable from './IncremarkTable.vue'
|
|
|
9
9
|
import IncremarkBlockquote from './IncremarkBlockquote.vue'
|
|
10
10
|
import IncremarkThematicBreak from './IncremarkThematicBreak.vue'
|
|
11
11
|
import IncremarkMath from './IncremarkMath.vue'
|
|
12
|
+
import IncremarkHtmlElement from './IncremarkHtmlElement.vue'
|
|
12
13
|
import IncremarkDefault from './IncremarkDefault.vue'
|
|
13
14
|
|
|
14
|
-
defineProps<{
|
|
15
|
+
const props = defineProps<{
|
|
15
16
|
node: RootContent
|
|
16
17
|
}>()
|
|
17
18
|
|
|
@@ -25,14 +26,25 @@ const componentMap: Record<string, Component> = {
|
|
|
25
26
|
thematicBreak: IncremarkThematicBreak,
|
|
26
27
|
math: IncremarkMath,
|
|
27
28
|
inlineMath: IncremarkMath,
|
|
29
|
+
htmlElement: IncremarkHtmlElement,
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function getComponent(type: string): Component {
|
|
31
33
|
return componentMap[type] || IncremarkDefault
|
|
32
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 检查是否是 html 节点
|
|
38
|
+
*/
|
|
39
|
+
function isHtmlNode(node: RootContent): node is HTML {
|
|
40
|
+
return node.type === 'html'
|
|
41
|
+
}
|
|
33
42
|
</script>
|
|
34
43
|
|
|
35
44
|
<template>
|
|
36
|
-
|
|
45
|
+
<!-- HTML 节点:渲染为代码块显示源代码 -->
|
|
46
|
+
<pre v-if="isHtmlNode(node)" class="incremark-html-code"><code>{{ (node as HTML).value }}</code></pre>
|
|
47
|
+
<!-- 其他节点:使用对应组件 -->
|
|
48
|
+
<component v-else :is="getComponent(node.type)" :node="node" />
|
|
37
49
|
</template>
|
|
38
50
|
|
|
@@ -40,35 +40,3 @@ function getCellContent(cell: TableCell): PhrasingContent[] {
|
|
|
40
40
|
</div>
|
|
41
41
|
</template>
|
|
42
42
|
|
|
43
|
-
<style scoped>
|
|
44
|
-
.incremark-table-wrapper {
|
|
45
|
-
overflow-x: auto;
|
|
46
|
-
margin: 1em 0;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.incremark-table {
|
|
50
|
-
width: 100%;
|
|
51
|
-
border-collapse: collapse;
|
|
52
|
-
font-size: 14px;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.incremark-table th,
|
|
56
|
-
.incremark-table td {
|
|
57
|
-
border: 1px solid #ddd;
|
|
58
|
-
padding: 10px 14px;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.incremark-table th {
|
|
62
|
-
background: #f8f9fa;
|
|
63
|
-
font-weight: 600;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
.incremark-table tr:nth-child(even) {
|
|
67
|
-
background: #fafafa;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.incremark-table tr:hover {
|
|
71
|
-
background: #f0f0f0;
|
|
72
|
-
}
|
|
73
|
-
</style>
|
|
74
|
-
|
package/src/components/index.ts
CHANGED
|
@@ -15,7 +15,9 @@ export { default as IncremarkBlockquote } from './IncremarkBlockquote.vue'
|
|
|
15
15
|
export { default as IncremarkThematicBreak } from './IncremarkThematicBreak.vue'
|
|
16
16
|
export { default as IncremarkInline } from './IncremarkInline.vue'
|
|
17
17
|
export { default as IncremarkMath } from './IncremarkMath.vue'
|
|
18
|
+
export { default as IncremarkHtmlElement } from './IncremarkHtmlElement.vue'
|
|
18
19
|
export { default as IncremarkDefault } from './IncremarkDefault.vue'
|
|
20
|
+
export { default as IncremarkFootnotes } from './IncremarkFootnotes.vue'
|
|
19
21
|
|
|
20
22
|
// 工具组件
|
|
21
23
|
export { default as AutoScrollContainer } from './AutoScrollContainer.vue'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { inject } from 'vue'
|
|
2
|
+
import { definationsInjectionKey } from './useProvideDefinations'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* support definations and footnoteDefinitions
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export function useDefinationsContext() {
|
|
9
|
+
const definationContext = inject(definationsInjectionKey);
|
|
10
|
+
|
|
11
|
+
if (!definationContext) {
|
|
12
|
+
throw new Error('definationContext not found');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return definationContext;
|
|
16
|
+
}
|