md2ui 1.0.3 → 1.0.7

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": "md2ui",
3
- "version": "1.0.3",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "description": "将本地 Markdown 文档转换为美观的 HTML 页面",
6
6
  "author": "",
package/src/App.vue CHANGED
@@ -57,8 +57,9 @@
57
57
  </template>
58
58
 
59
59
  <script setup>
60
- import { ref, onMounted } from 'vue'
60
+ import { ref, onMounted, nextTick } from 'vue'
61
61
  import { ChevronsDownUp, ChevronsUpDown, ArrowUp, PanelLeftOpen, PanelLeftClose, PanelRightOpen, Github } from 'lucide-vue-next'
62
+ import MD5 from 'crypto-js/md5'
62
63
  import Logo from './components/Logo.vue'
63
64
  import TreeNode from './components/TreeNode.vue'
64
65
  import TableOfContents from './components/TableOfContents.vue'
@@ -76,7 +77,24 @@ const zoomVisible = ref(false)
76
77
  const zoomContent = ref('')
77
78
 
78
79
  const { htmlContent, tocItems, renderMarkdown } = useMarkdown()
79
- const { scrollProgress, showBackToTop, activeHeading, handleScroll, scrollToHeading, scrollToTop } = useScroll()
80
+ const { scrollProgress, showBackToTop, activeHeading, handleScroll: _handleScroll, scrollToHeading: _scrollToHeading, scrollToTop } = useScroll()
81
+
82
+ // 包装滚动处理,同步锚点到 URL
83
+ function handleScroll(e) {
84
+ _handleScroll(e)
85
+ // 滚动时更新 URL 锚点
86
+ if (activeHeading.value) {
87
+ updateHash(activeHeading.value)
88
+ } else {
89
+ updateHash('')
90
+ }
91
+ }
92
+
93
+ // 包装目录点击,同步锚点到 URL
94
+ function scrollToHeading(id) {
95
+ _scrollToHeading(id)
96
+ updateHash(id)
97
+ }
80
98
  const { sidebarWidth, tocWidth, startResize } = useResize()
81
99
 
82
100
  async function loadDocsList() {
@@ -91,8 +109,21 @@ function loadFirstDoc() {
91
109
  }
92
110
  }
93
111
 
112
+ // 根据 key 生成 hash
113
+ function docHash(key) {
114
+ return MD5(key).toString()
115
+ }
116
+
117
+ // 更新 URL hash(文档hash + 可选锚点)
118
+ function updateHash(anchor) {
119
+ if (!currentDoc.value) return
120
+ const base = docHash(currentDoc.value)
121
+ window.location.hash = anchor ? `${base}/${anchor}` : base
122
+ }
123
+
94
124
  async function loadDoc(key) {
95
125
  currentDoc.value = key
126
+ updateHash('')
96
127
  const doc = findDoc(docsList.value, key)
97
128
  if (!doc) return
98
129
  try {
@@ -192,10 +223,59 @@ your-docs/
192
223
  `)
193
224
  }
194
225
 
226
+ // 根据 hash 查找文档
227
+ function findDocByHash(items, hash) {
228
+ for (const item of items) {
229
+ if (item.type === 'file' && docHash(item.key) === hash) return item
230
+ if (item.type === 'folder' && item.children) {
231
+ const found = findDocByHash(item.children, hash)
232
+ if (found) return found
233
+ }
234
+ }
235
+ return null
236
+ }
237
+
238
+ // 展开文档所在的所有父级文件夹
239
+ function expandParents(items, targetKey) {
240
+ for (const item of items) {
241
+ if (item.type === 'file' && item.key === targetKey) return true
242
+ if (item.type === 'folder' && item.children) {
243
+ if (expandParents(item.children, targetKey)) {
244
+ item.expanded = true
245
+ return true
246
+ }
247
+ }
248
+ }
249
+ return false
250
+ }
251
+
195
252
  onMounted(async () => {
196
253
  await loadDocsList()
197
254
 
198
- // 如果有文档,加载第一个;否则显示提示
255
+ const rawHash = window.location.hash.replace('#', '')
256
+ const [hash, anchor] = rawHash.includes('/')
257
+ ? [rawHash.split('/')[0], rawHash.split('/').slice(1).join('/')]
258
+ : [rawHash, '']
259
+
260
+ if (hash) {
261
+ const doc = findDocByHash(docsList.value, hash)
262
+ if (doc) {
263
+ expandParents(docsList.value, doc.key)
264
+ await loadDoc(doc.key)
265
+ // 恢复锚点位置
266
+ if (anchor) {
267
+ await nextTick()
268
+ // 等待 DOM 渲染完成后再滚动
269
+ setTimeout(() => {
270
+ _scrollToHeading(anchor)
271
+ updateHash(anchor)
272
+ }, 100)
273
+ }
274
+ return
275
+ }
276
+ }
277
+
278
+ // 没有 hash 或找不到对应文档,加载第一个
199
279
  const firstDoc = findFirstDoc(docsList.value)
200
280
  if (firstDoc) {
201
281
  await loadDoc(firstDoc.key)
@@ -112,8 +112,10 @@ function handleOverlayClick(event) {
112
112
  function handleWheel(event) {
113
113
  event.preventDefault()
114
114
 
115
- const delta = event.deltaY > 0 ? -scaleStep : scaleStep
116
- const newScale = Math.max(minScale, Math.min(maxScale, scale.value + delta))
115
+ // 使用比例缩放,每次滚轮缩放 2%,体感更平滑
116
+ const zoomFactor = 0.02
117
+ const direction = event.deltaY > 0 ? -1 : 1
118
+ const newScale = Math.max(minScale, Math.min(maxScale, scale.value * (1 + direction * zoomFactor)))
117
119
 
118
120
  if (newScale !== scale.value) {
119
121
  scale.value = newScale