md2ui 1.0.16 → 1.0.19
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/README.md +3 -55
- package/bin/build.js +82 -7
- package/bin/md2ui.js +80 -4
- package/package.json +23 -9
- package/public/docs/00-/345/277/253/351/200/237/345/274/200/345/247/213.md +48 -28
- package/public/docs/01-/345/212/237/350/203/275/347/211/271/346/200/247.md +55 -40
- package/public/docs/02-Markdown/346/270/262/346/237/223/00-/345/237/272/347/241/200/350/257/255/346/263/225.md +86 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/01-/344/273/243/347/240/201/345/235/227.md +91 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/02-/350/241/250/346/240/274.md +187 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/03-Mermaid/345/233/276/350/241/250.md +101 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/04-Frontmatter.md +32 -0
- package/public/docs/02-Markdown/346/270/262/346/237/223/05-/346/225/260/345/255/246/345/205/254/345/274/217.md +47 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/00-/344/270/211/346/240/217/345/270/203/345/261/200.md +33 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/01-/347/233/256/345/275/225/346/240/221/345/257/274/350/210/252.md +43 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/02-/346/226/207/346/241/243/345/244/247/347/272/262.md +51 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/03-/344/270/212/344/270/213/347/257/207/345/257/274/350/210/252.md +29 -0
- package/public/docs/03-/345/257/274/350/210/252/344/270/216/345/270/203/345/261/200/04-/347/253/231/345/206/205/351/223/276/346/216/245.md +39 -0
- package/public/docs/04-/346/220/234/347/264/242/345/212/237/350/203/275/00-/345/205/250/346/226/207/346/220/234/347/264/242.md +46 -0
- package/public/docs/05-/347/274/226/350/276/221/345/212/237/350/203/275/00-/347/274/226/350/276/221/345/231/250/345/237/272/347/241/200.md +65 -0
- package/public/docs/05-/347/274/226/350/276/221/345/212/237/350/203/275/01-/350/207/252/345/212/250/344/277/235/345/255/230.md +38 -0
- package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/00-/351/230/205/350/257/273/350/277/233/345/272/246.md +43 -0
- package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/01-/345/233/276/347/211/207/346/224/276/345/244/247.md +40 -0
- package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/02-/350/277/224/345/233/236/351/241/266/351/203/250.md +38 -0
- package/public/docs/06-/351/230/205/350/257/273/344/275/223/351/252/214/assets/img-1777261394722.png +0 -0
- package/public/docs/07-/347/247/273/345/212/250/347/253/257/351/200/202/351/205/215/00-/345/223/215/345/272/224/345/274/217/345/270/203/345/261/200.md +37 -0
- package/public/docs/08-/346/226/207/346/241/243/347/256/241/347/220/206/00-/346/226/260/345/273/272/344/270/216/345/210/240/351/231/244.md +47 -0
- package/public/docs/09-/345/257/274/345/207/272/345/212/237/350/203/275/00-/345/257/274/345/207/272Word.md +77 -0
- package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/00-CLI/345/267/245/345/205/267.md +52 -0
- package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/01-SSG/351/235/231/346/200/201/346/236/204/345/273/272.md +44 -0
- package/public/docs/10-/351/203/250/347/275/262/344/270/216/351/205/215/347/275/256/02-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +58 -0
- package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/00-/344/270/200/347/272/247/346/226/207/346/241/243.md +20 -0
- package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/01-/345/255/220/347/233/256/345/275/225/00-/344/272/214/347/272/247/346/226/207/346/241/243.md +13 -0
- package/public/docs/11-/345/244/232/347/272/247/347/233/256/345/275/225/346/265/213/350/257/225/01-/345/255/220/347/233/256/345/275/225/01-/346/267/261/345/261/202/345/265/214/345/245/227/00-/344/270/211/347/272/247/346/226/207/346/241/243.md +23 -0
- package/src/App.vue +130 -6
- package/src/components/AppSidebar.vue +181 -21
- package/src/components/CodeBlockNodeView.vue +72 -0
- package/src/components/DocContent.vue +25 -14
- package/src/components/EditorContent.vue +257 -0
- package/src/components/EditorToolbar.vue +264 -0
- package/src/components/ImageZoom.vue +199 -2
- package/src/components/MathBlockNodeView.vue +160 -0
- package/src/components/MathInlineNodeView.vue +145 -0
- package/src/components/MermaidNodeView.vue +149 -0
- package/src/components/TableBubbleMenu.vue +177 -0
- package/src/components/TableOfContents.vue +138 -32
- package/src/components/TopBar.vue +69 -4
- package/src/components/TreeNode.vue +232 -39
- package/src/components/WelcomePage.vue +2 -2
- package/src/composables/useDocHash.js +9 -1
- package/src/composables/useDocManager.js +325 -68
- package/src/composables/useDocTree.js +56 -1
- package/src/composables/useExportPdf.js +102 -0
- package/src/composables/useExportWord.js +73 -10
- package/src/composables/useFileWatcher.js +45 -0
- package/src/composables/useFrontmatter.js +2 -2
- package/src/composables/useMarkdown.js +529 -42
- package/src/composables/useScroll.js +47 -5
- package/src/config.js +1 -1
- package/src/extensions/CodeBlockCustom.js +113 -0
- package/src/extensions/MathBlock.js +107 -0
- package/src/extensions/MathInline.js +100 -0
- package/src/extensions/MermaidBlock.js +73 -0
- package/src/extensions/TableControls.js +670 -0
- package/src/services/DocService.js +184 -0
- package/src/style.css +2194 -39
- package/vite-plugin-doc-api.js +368 -0
- package/vite.config.js +2 -1
- package/public/docs/02-Mermaid/345/233/276/350/241/250.md +0 -102
- package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/01-/347/233/256/345/275/225/347/273/223/346/236/204.md +0 -55
- package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/02-/350/207/252/345/256/232/344/271/211/351/205/215/347/275/256.md +0 -63
- package/public/docs/03-/350/277/233/351/230/266/346/214/207/345/215/227/03-/351/203/250/347/275/262/346/226/271/346/241/210.md +0 -73
- package/public/docs/04-API/345/217/202/350/200/203/01-/347/273/204/344/273/266API.md +0 -80
- package/public/docs/04-API/345/217/202/350/200/203/02-Composables.md +0 -92
- package/src/api/docs.js +0 -106
|
@@ -1,33 +1,64 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="tree-node-root">
|
|
3
3
|
<!-- 文件夹节点 -->
|
|
4
4
|
<div
|
|
5
5
|
v-if="item.type === 'folder'"
|
|
6
6
|
class="nav-item nav-folder"
|
|
7
|
-
:class="{ expanded: item.expanded }"
|
|
7
|
+
:class="{ expanded: item.expanded, 'drag-over': isDragOver }"
|
|
8
8
|
:style="{ paddingLeft: `${20 + item.level * 16}px` }"
|
|
9
9
|
@click="$emit('toggle', item)"
|
|
10
|
-
@mouseenter="
|
|
11
|
-
@mouseleave="
|
|
10
|
+
@mouseenter="onMouseEnter($event, item.label)"
|
|
11
|
+
@mouseleave="onMouseLeave"
|
|
12
|
+
@dragover.prevent="onFolderDragOver"
|
|
13
|
+
@dragleave="onFolderDragLeave"
|
|
14
|
+
@drop.prevent="onFolderDrop"
|
|
12
15
|
>
|
|
13
16
|
<ChevronRight v-if="!item.expanded" class="nav-icon chevron-icon" :size="16" />
|
|
14
17
|
<ChevronDown v-else class="nav-icon chevron-icon" :size="16" />
|
|
15
18
|
<Folder v-if="!item.expanded" class="nav-icon folder-icon" :size="16" />
|
|
16
19
|
<FolderOpen v-else class="nav-icon folder-icon" :size="16" />
|
|
17
20
|
<span class="nav-label">{{ item.label }}</span>
|
|
21
|
+
<span class="nav-item-actions" @click.stop>
|
|
22
|
+
<button class="nav-item-btn" @click="$emit('show-create', $event, item.key)" title="新建">
|
|
23
|
+
<Plus :size="12" />
|
|
24
|
+
</button>
|
|
25
|
+
<button class="nav-item-btn" @click="startRename($event)" title="重命名">
|
|
26
|
+
<Pencil :size="12" />
|
|
27
|
+
</button>
|
|
28
|
+
<button class="nav-item-btn nav-item-btn-danger" @click="startDelete($event)" title="删除">
|
|
29
|
+
<Trash2 :size="12" />
|
|
30
|
+
</button>
|
|
31
|
+
</span>
|
|
18
32
|
</div>
|
|
19
33
|
|
|
20
|
-
<!--
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
34
|
+
<!-- 递归渲染子节点(可拖拽排序) -->
|
|
35
|
+
<draggable
|
|
36
|
+
v-if="item.type === 'folder' && item.expanded && item.children"
|
|
37
|
+
:list="item.children"
|
|
38
|
+
:group="{ name: 'doc-tree', pull: true, put: true }"
|
|
39
|
+
item-key="key"
|
|
40
|
+
:animation="200"
|
|
41
|
+
ghost-class="drag-ghost"
|
|
42
|
+
chosen-class="drag-chosen"
|
|
43
|
+
drag-class="drag-active"
|
|
44
|
+
handle=".nav-item"
|
|
45
|
+
:fallback-on-body="true"
|
|
46
|
+
:swap-threshold="0.65"
|
|
47
|
+
@end="onDragEnd"
|
|
48
|
+
>
|
|
49
|
+
<template #item="{ element }">
|
|
50
|
+
<TreeNode
|
|
51
|
+
:item="element"
|
|
52
|
+
:currentDoc="currentDoc"
|
|
53
|
+
@toggle="$emit('toggle', $event)"
|
|
54
|
+
@select="$emit('select', $event)"
|
|
55
|
+
@show-create="(e, k) => $emit('show-create', e, k)"
|
|
56
|
+
@rename="(payload) => $emit('rename', payload)"
|
|
57
|
+
@delete="(payload) => $emit('delete', payload)"
|
|
58
|
+
@drag-end="$emit('drag-end', $event)"
|
|
59
|
+
/>
|
|
60
|
+
</template>
|
|
61
|
+
</draggable>
|
|
31
62
|
|
|
32
63
|
<!-- 文件节点 -->
|
|
33
64
|
<div
|
|
@@ -36,40 +67,200 @@
|
|
|
36
67
|
:class="{ active: currentDoc === item.key }"
|
|
37
68
|
:style="{ paddingLeft: `${20 + item.level * 16}px` }"
|
|
38
69
|
@click="$emit('select', item.key)"
|
|
39
|
-
@mouseenter="
|
|
40
|
-
@mouseleave="
|
|
70
|
+
@mouseenter="onMouseEnter($event, item.label)"
|
|
71
|
+
@mouseleave="onMouseLeave"
|
|
41
72
|
>
|
|
42
73
|
<FileText class="nav-icon file-icon" :size="16" />
|
|
43
74
|
<span class="nav-label">{{ item.label }}</span>
|
|
75
|
+
<span class="nav-item-actions" @click.stop>
|
|
76
|
+
<button class="nav-item-btn" @click="startRename($event)" title="重命名">
|
|
77
|
+
<Pencil :size="12" />
|
|
78
|
+
</button>
|
|
79
|
+
<button class="nav-item-btn nav-item-btn-danger" @click="startDelete($event)" title="删除">
|
|
80
|
+
<Trash2 :size="12" />
|
|
81
|
+
</button>
|
|
82
|
+
</span>
|
|
44
83
|
</div>
|
|
84
|
+
|
|
85
|
+
<!-- 重命名气泡 -->
|
|
86
|
+
<Teleport to="body">
|
|
87
|
+
<div v-if="renameVisible" class="popover-overlay" @click="cancelRename">
|
|
88
|
+
<div class="popover-bubble" :style="popoverStyle" @click.stop>
|
|
89
|
+
<input
|
|
90
|
+
ref="renameInputRef"
|
|
91
|
+
v-model="renameName"
|
|
92
|
+
class="popover-input"
|
|
93
|
+
placeholder="请输入新名称"
|
|
94
|
+
@keydown.enter="confirmRename"
|
|
95
|
+
@keydown.escape="cancelRename"
|
|
96
|
+
/>
|
|
97
|
+
<div v-if="renameError" class="popover-error">{{ renameError }}</div>
|
|
98
|
+
<div class="popover-actions">
|
|
99
|
+
<button class="popover-btn popover-btn-cancel" @click="cancelRename">取消</button>
|
|
100
|
+
<button class="popover-btn popover-btn-confirm" @click="confirmRename">确定</button>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</Teleport>
|
|
105
|
+
|
|
106
|
+
<!-- 删除确认气泡 -->
|
|
107
|
+
<Teleport to="body">
|
|
108
|
+
<div v-if="deleteVisible" class="popover-overlay" @click="deleteVisible = false">
|
|
109
|
+
<div class="popover-bubble" :style="popoverStyle" @click.stop>
|
|
110
|
+
<div class="popover-message">
|
|
111
|
+
确定删除「<strong>{{ item.label }}</strong>」?
|
|
112
|
+
<template v-if="item.type === 'folder'">
|
|
113
|
+
<br /><span class="popover-warning">目录下所有内容将一并删除</span>
|
|
114
|
+
</template>
|
|
115
|
+
</div>
|
|
116
|
+
<div class="popover-actions">
|
|
117
|
+
<button class="popover-btn popover-btn-cancel" @click="deleteVisible = false">取消</button>
|
|
118
|
+
<button class="popover-btn popover-btn-danger" @click="confirmDelete">删除</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</Teleport>
|
|
45
123
|
</div>
|
|
46
124
|
</template>
|
|
47
125
|
|
|
48
126
|
<script setup>
|
|
49
|
-
import {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
currentDoc: {
|
|
57
|
-
type: String,
|
|
58
|
-
required: true
|
|
59
|
-
}
|
|
127
|
+
import { ref, nextTick } from 'vue'
|
|
128
|
+
import draggable from 'vuedraggable'
|
|
129
|
+
import { ChevronRight, ChevronDown, Folder, FolderOpen, FileText, Plus, Pencil, Trash2 } from 'lucide-vue-next'
|
|
130
|
+
|
|
131
|
+
const props = defineProps({
|
|
132
|
+
item: { type: Object, required: true },
|
|
133
|
+
currentDoc: { type: String, required: true }
|
|
60
134
|
})
|
|
61
135
|
|
|
62
|
-
defineEmits(['toggle', 'select'])
|
|
136
|
+
const emit = defineEmits(['toggle', 'select', 'show-create', 'rename', 'delete', 'drag-end'])
|
|
137
|
+
|
|
138
|
+
// ===== 拖拽相关 =====
|
|
139
|
+
const isDragOver = ref(false)
|
|
140
|
+
let dragOverTimer = null
|
|
141
|
+
|
|
142
|
+
function onFolderDragOver() {
|
|
143
|
+
isDragOver.value = true
|
|
144
|
+
clearTimeout(dragOverTimer)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function onFolderDragLeave() {
|
|
148
|
+
dragOverTimer = setTimeout(() => { isDragOver.value = false }, 100)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function onFolderDrop() {
|
|
152
|
+
isDragOver.value = false
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function onDragEnd(evt) {
|
|
156
|
+
emit('drag-end', evt)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ===== 气泡定位 =====
|
|
160
|
+
const popoverStyle = ref({})
|
|
161
|
+
const bubbleRef = ref(null)
|
|
162
|
+
|
|
163
|
+
// 记录触发按钮的位置
|
|
164
|
+
let triggerRect = null
|
|
165
|
+
|
|
166
|
+
function calcPopoverPos(event) {
|
|
167
|
+
const btn = event.currentTarget
|
|
168
|
+
triggerRect = btn.getBoundingClientRect()
|
|
169
|
+
// 先设置一个不可见的初始位置,等渲染后再修正
|
|
170
|
+
popoverStyle.value = {
|
|
171
|
+
position: 'fixed',
|
|
172
|
+
left: `${Math.max(8, triggerRect.left - 80)}px`,
|
|
173
|
+
top: '0px',
|
|
174
|
+
zIndex: 9999,
|
|
175
|
+
visibility: 'hidden'
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function adjustPopoverPos() {
|
|
180
|
+
nextTick(() => {
|
|
181
|
+
const bubble = document.querySelector('.popover-bubble')
|
|
182
|
+
if (!bubble || !triggerRect) return
|
|
183
|
+
const bRect = bubble.getBoundingClientRect()
|
|
184
|
+
let left = Math.max(8, triggerRect.left - 80)
|
|
185
|
+
// 默认向上弹出
|
|
186
|
+
let top = triggerRect.top - bRect.height - 6
|
|
187
|
+
// 上方空间不够,改为向下
|
|
188
|
+
if (top < 10) {
|
|
189
|
+
top = triggerRect.bottom + 6
|
|
190
|
+
}
|
|
191
|
+
// 向下弹出后底部仍然溢出,贴底显示
|
|
192
|
+
if (top + bRect.height > window.innerHeight - 10) {
|
|
193
|
+
top = window.innerHeight - bRect.height - 10
|
|
194
|
+
}
|
|
195
|
+
// 右侧溢出修正
|
|
196
|
+
if (left + bRect.width > window.innerWidth - 10) {
|
|
197
|
+
left = window.innerWidth - bRect.width - 10
|
|
198
|
+
}
|
|
199
|
+
popoverStyle.value = {
|
|
200
|
+
position: 'fixed',
|
|
201
|
+
left: `${left}px`,
|
|
202
|
+
top: `${top}px`,
|
|
203
|
+
zIndex: 9999,
|
|
204
|
+
visibility: 'visible'
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ===== 重命名 =====
|
|
210
|
+
const renameVisible = ref(false)
|
|
211
|
+
const renameInputRef = ref(null)
|
|
212
|
+
const renameName = ref('')
|
|
213
|
+
const renameError = ref('')
|
|
63
214
|
|
|
215
|
+
function startRename(event) {
|
|
216
|
+
deleteVisible.value = false
|
|
217
|
+
renameName.value = props.item.label
|
|
218
|
+
renameError.value = ''
|
|
219
|
+
calcPopoverPos(event)
|
|
220
|
+
renameVisible.value = true
|
|
221
|
+
nextTick(() => {
|
|
222
|
+
adjustPopoverPos()
|
|
223
|
+
renameInputRef.value?.focus()
|
|
224
|
+
renameInputRef.value?.select()
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function confirmRename() {
|
|
229
|
+
const newName = renameName.value.trim()
|
|
230
|
+
if (!newName) { renameError.value = '名称不能为空'; return }
|
|
231
|
+
if (/[\\/:*?"<>|]/.test(newName)) { renameError.value = '名称包含非法字符'; return }
|
|
232
|
+
if (newName === props.item.label) { renameVisible.value = false; return }
|
|
233
|
+
renameError.value = ''
|
|
234
|
+
emit('rename', { item: props.item, newName })
|
|
235
|
+
renameVisible.value = false
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function cancelRename() {
|
|
239
|
+
renameVisible.value = false
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ===== 删除 =====
|
|
243
|
+
const deleteVisible = ref(false)
|
|
244
|
+
|
|
245
|
+
function startDelete(event) {
|
|
246
|
+
renameVisible.value = false
|
|
247
|
+
calcPopoverPos(event)
|
|
248
|
+
deleteVisible.value = true
|
|
249
|
+
adjustPopoverPos()
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function confirmDelete() {
|
|
253
|
+
emit('delete', props.item)
|
|
254
|
+
deleteVisible.value = false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ===== Tooltip =====
|
|
64
258
|
let tooltipEl = null
|
|
65
259
|
|
|
66
|
-
function
|
|
260
|
+
function onMouseEnter(event, text) {
|
|
67
261
|
hideTooltip()
|
|
68
|
-
|
|
69
262
|
const target = event.currentTarget
|
|
70
263
|
const label = target.querySelector('.nav-label')
|
|
71
|
-
|
|
72
|
-
// 只有文字被截断时才显示 tooltip
|
|
73
264
|
if (label && label.scrollWidth <= label.clientWidth) return
|
|
74
265
|
|
|
75
266
|
tooltipEl = document.createElement('div')
|
|
@@ -89,18 +280,22 @@ function showTooltip(event, text) {
|
|
|
89
280
|
cursor: text;
|
|
90
281
|
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
91
282
|
`
|
|
92
|
-
|
|
93
283
|
document.body.appendChild(tooltipEl)
|
|
94
|
-
|
|
95
284
|
const rect = target.getBoundingClientRect()
|
|
96
285
|
tooltipEl.style.left = `${rect.right + 8}px`
|
|
97
286
|
tooltipEl.style.top = `${rect.top}px`
|
|
98
|
-
|
|
99
|
-
// 防止超出屏幕右侧
|
|
100
287
|
const tooltipRect = tooltipEl.getBoundingClientRect()
|
|
101
288
|
if (tooltipRect.right > window.innerWidth - 10) {
|
|
102
289
|
tooltipEl.style.left = `${rect.left - tooltipRect.width - 8}px`
|
|
103
290
|
}
|
|
291
|
+
// 底部溢出修正
|
|
292
|
+
if (tooltipRect.bottom > window.innerHeight - 10) {
|
|
293
|
+
tooltipEl.style.top = `${window.innerHeight - tooltipRect.height - 10}px`
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function onMouseLeave() {
|
|
298
|
+
hideTooltip()
|
|
104
299
|
}
|
|
105
300
|
|
|
106
301
|
function hideTooltip() {
|
|
@@ -110,5 +305,3 @@ function hideTooltip() {
|
|
|
110
305
|
}
|
|
111
306
|
}
|
|
112
307
|
</script>
|
|
113
|
-
|
|
114
|
-
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
</button>
|
|
24
24
|
</div>
|
|
25
25
|
<!-- GitHub -->
|
|
26
|
-
<a class="welcome-github" href="https://github.com/
|
|
26
|
+
<a class="welcome-github" href="https://github.com/xiaoyaodev/md2ui" target="_blank">
|
|
27
27
|
<GitHubIcon :size="16" />
|
|
28
28
|
<span>GitHub</span>
|
|
29
29
|
<span class="welcome-github-sep"></span>
|
|
30
|
-
<span class="welcome-github-repo">
|
|
30
|
+
<span class="welcome-github-repo">xiaoyaodev/md2ui</span>
|
|
31
31
|
<ExternalLink :size="12" class="welcome-github-arrow" />
|
|
32
32
|
</a>
|
|
33
33
|
</div>
|
|
@@ -33,9 +33,17 @@ function toBase62(h1, h2) {
|
|
|
33
33
|
return result.padStart(8, '0').slice(0, 8)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// 去掉路径中每一段的序号前缀(如 "01-快速开始" → "快速开始")
|
|
37
|
+
// 保证重编号不影响外链地址
|
|
38
|
+
export function stripOrderPrefix(key) {
|
|
39
|
+
return key.split('/').map(seg => seg.replace(/^\d+-/, '')).join('/')
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
// 根据文档 key 生成 8 位 base62 短 hash(同步、确定性、零依赖)
|
|
43
|
+
// 哈希计算前会去掉序号前缀,使得重编号不影响外链
|
|
37
44
|
export function docHash(key) {
|
|
38
|
-
const
|
|
45
|
+
const stripped = stripOrderPrefix(key)
|
|
46
|
+
const [h1, h2] = fnv1a64(stripped)
|
|
39
47
|
return toBase62(h1, h2)
|
|
40
48
|
}
|
|
41
49
|
|