@tnotesjs/core 0.1.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.

Potentially problematic release.


This version of @tnotesjs/core might be problematic. Click here for more details.

Files changed (117) hide show
  1. package/README.md +105 -0
  2. package/dist/chunk-K3X5OP3N.js +1532 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.js +4199 -0
  5. package/dist/index.d.ts +138 -0
  6. package/dist/index.js +9 -0
  7. package/package.json +74 -0
  8. package/types/config.ts +61 -0
  9. package/types/index.ts +11 -0
  10. package/types/note.ts +33 -0
  11. package/vitepress/assets/icons/icon__check.svg +3 -0
  12. package/vitepress/assets/icons/icon__clipboard.svg +8 -0
  13. package/vitepress/assets/icons/icon__close.svg +1 -0
  14. package/vitepress/assets/icons/icon__collapse.svg +1 -0
  15. package/vitepress/assets/icons/icon__confirm.svg +1 -0
  16. package/vitepress/assets/icons/icon__copy.svg +4 -0
  17. package/vitepress/assets/icons/icon__focus.svg +1 -0
  18. package/vitepress/assets/icons/icon__fold.svg +3 -0
  19. package/vitepress/assets/icons/icon__folder.svg +1 -0
  20. package/vitepress/assets/icons/icon__fullscreen.svg +1 -0
  21. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -0
  22. package/vitepress/assets/icons/icon__github.svg +4 -0
  23. package/vitepress/assets/icons/icon__mindmap.svg +1 -0
  24. package/vitepress/assets/icons/icon__next.svg +1 -0
  25. package/vitepress/assets/icons/icon__number_gray.svg +1 -0
  26. package/vitepress/assets/icons/icon__number_purple.svg +1 -0
  27. package/vitepress/assets/icons/icon__prev.svg +1 -0
  28. package/vitepress/assets/icons/icon__restore.svg +1 -0
  29. package/vitepress/assets/icons/icon__rotate.svg +4 -0
  30. package/vitepress/assets/icons/icon__search.svg +1 -0
  31. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -0
  32. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -0
  33. package/vitepress/assets/icons/icon__totop.svg +6 -0
  34. package/vitepress/assets/icons/icon__vscode.svg +6 -0
  35. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -0
  36. package/vitepress/assets/icons/icon__zoom_in.svg +1 -0
  37. package/vitepress/assets/icons/icon__zoom_out.svg +1 -0
  38. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -0
  39. package/vitepress/assets/icons/index.ts +38 -0
  40. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -0
  41. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -0
  42. package/vitepress/components/CodeBlockFullscreen/index.ts +115 -0
  43. package/vitepress/components/CodeBlockFullscreen/styles.css +64 -0
  44. package/vitepress/components/Discussions/Discussions.module.scss +32 -0
  45. package/vitepress/components/Discussions/Discussions.vue +211 -0
  46. package/vitepress/components/EnWordList/EnWordList.module.scss +124 -0
  47. package/vitepress/components/EnWordList/EnWordList.vue +543 -0
  48. package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -0
  49. package/vitepress/components/EnWordList/RightClickMenu.vue +66 -0
  50. package/vitepress/components/Footprints/Footprints.module.scss +93 -0
  51. package/vitepress/components/Footprints/Footprints.vue +377 -0
  52. package/vitepress/components/Layout/AboutModal.module.scss +233 -0
  53. package/vitepress/components/Layout/AboutModal.vue +105 -0
  54. package/vitepress/components/Layout/AboutPanel.vue +266 -0
  55. package/vitepress/components/Layout/ContentCollapse.vue +603 -0
  56. package/vitepress/components/Layout/CustomSidebar.vue +605 -0
  57. package/vitepress/components/Layout/DocBeforeControls.vue +139 -0
  58. package/vitepress/components/Layout/DocFooter.vue +225 -0
  59. package/vitepress/components/Layout/ImagePreview.module.scss +201 -0
  60. package/vitepress/components/Layout/ImagePreview.vue +281 -0
  61. package/vitepress/components/Layout/Layout.module.scss +661 -0
  62. package/vitepress/components/Layout/Layout.vue +542 -0
  63. package/vitepress/components/Layout/NoteStatus.vue +140 -0
  64. package/vitepress/components/Layout/SidebarItems.vue +263 -0
  65. package/vitepress/components/Layout/SidebarNavBefore.vue +92 -0
  66. package/vitepress/components/Layout/Swiper.vue +167 -0
  67. package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -0
  68. package/vitepress/components/Layout/ToggleFullContent.vue +34 -0
  69. package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -0
  70. package/vitepress/components/Layout/ToggleSidebar.vue +35 -0
  71. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -0
  72. package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -0
  73. package/vitepress/components/Layout/composables/useNoteSave.ts +173 -0
  74. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -0
  75. package/vitepress/components/Layout/composables/useRedirect.ts +110 -0
  76. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -0
  77. package/vitepress/components/Layout/homeReadme.data.ts +124 -0
  78. package/vitepress/components/LoadingPage/LoadingPage.vue +192 -0
  79. package/vitepress/components/MarkMap/MarkMap.module.scss +159 -0
  80. package/vitepress/components/MarkMap/MarkMap.vue +404 -0
  81. package/vitepress/components/Mermaid/Mermaid.module.scss +275 -0
  82. package/vitepress/components/Mermaid/Mermaid.vue +364 -0
  83. package/vitepress/components/NotesTable/NotesTable.module.scss +77 -0
  84. package/vitepress/components/NotesTable/NotesTable.vue +98 -0
  85. package/vitepress/components/NotesTable/README.md +67 -0
  86. package/vitepress/components/Settings/Settings.module.scss +433 -0
  87. package/vitepress/components/Settings/Settings.vue +306 -0
  88. package/vitepress/components/SidebarCard/MindMapView.vue +483 -0
  89. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -0
  90. package/vitepress/components/SidebarCard/SidebarCard.vue +948 -0
  91. package/vitepress/components/Tooltip/Tooltip.vue +70 -0
  92. package/vitepress/components/constants.ts +91 -0
  93. package/vitepress/components/notesConfig.data.ts +73 -0
  94. package/vitepress/components/sidebar.data.ts +59 -0
  95. package/vitepress/components/tnotes-config.data.ts +21 -0
  96. package/vitepress/components/utils.ts +26 -0
  97. package/vitepress/config/index.ts +126 -0
  98. package/vitepress/configs/constants.ts +26 -0
  99. package/vitepress/configs/head.config.ts +25 -0
  100. package/vitepress/configs/index.ts +9 -0
  101. package/vitepress/configs/markdown-it.d.ts +23 -0
  102. package/vitepress/configs/markdown.config.ts +366 -0
  103. package/vitepress/configs/theme.config.ts +108 -0
  104. package/vitepress/plugins/buildProgressPlugin.ts +390 -0
  105. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -0
  106. package/vitepress/plugins/renameNotePlugin.ts +60 -0
  107. package/vitepress/plugins/updateConfigPlugin.ts +63 -0
  108. package/vitepress/theme/index.ts +95 -0
  109. package/vitepress/theme/styles/base.scss +50 -0
  110. package/vitepress/theme/styles/components/404.scss +31 -0
  111. package/vitepress/theme/styles/components/collapse.scss +175 -0
  112. package/vitepress/theme/styles/components/markmap.scss +101 -0
  113. package/vitepress/theme/styles/components/swiper.scss +255 -0
  114. package/vitepress/theme/styles/index.scss +25 -0
  115. package/vitepress/theme/styles/layout.scss +62 -0
  116. package/vitepress/theme/styles/utilities.scss +39 -0
  117. package/vitepress/theme/styles/vitepress-override.scss +25 -0
@@ -0,0 +1,605 @@
1
+ <template>
2
+ <div class="custom-sidebar-wrapper">
3
+ <nav class="nav" ref="navRef">
4
+ <!-- 使用递归组件渲染侧边栏,支持任意层级嵌套 -->
5
+ <SidebarItems
6
+ :items="sidebarGroups"
7
+ :depth="0"
8
+ :max-depth="maxDepth"
9
+ :show-note-id="showNoteId"
10
+ :base="base"
11
+ :current-path="route.path"
12
+ />
13
+ </nav>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { ref, onMounted, watch, computed } from 'vue'
19
+ import { useRoute, useData } from 'vitepress'
20
+ import SidebarItems from './SidebarItems.vue'
21
+ // @ts-expect-error - VitePress Data Loader
22
+ import { data as sidebarConfig } from '../sidebar.data'
23
+ // @ts-expect-error - VitePress Data Loader
24
+ import { data as tnotesConfig } from '../tnotes-config.data'
25
+ import { SIDEBAR_SHOW_NOTE_ID_KEY, SIDEBAR_MAX_DEPTH_KEY } from '../constants'
26
+
27
+ // 支持递归的侧边栏项类型
28
+ interface SidebarItem {
29
+ text: string
30
+ link?: string
31
+ items?: SidebarItem[]
32
+ collapsed?: boolean
33
+ }
34
+
35
+ const route = useRoute()
36
+ const { site } = useData()
37
+ const sidebarGroups = ref<SidebarItem[]>([])
38
+ const navRef = ref<HTMLElement | null>(null)
39
+ const currentFocusIndex = ref(0)
40
+
41
+ // 最大解析层级(默认 3 层)
42
+ const maxDepth = computed(() => {
43
+ if (typeof window === 'undefined') {
44
+ return tnotesConfig.sidebarMaxDepth ?? 3
45
+ }
46
+
47
+ const savedMaxDepth = localStorage.getItem(SIDEBAR_MAX_DEPTH_KEY)
48
+ if (savedMaxDepth !== null) {
49
+ return parseInt(savedMaxDepth, 10)
50
+ }
51
+
52
+ return tnotesConfig.sidebarMaxDepth ?? 3
53
+ })
54
+
55
+ // 获取配置:是否显示笔记 ID
56
+ const showNoteId = computed(() => {
57
+ if (typeof window === 'undefined') {
58
+ return tnotesConfig.sidebarShowNoteId ?? false
59
+ }
60
+
61
+ const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
62
+ if (savedShowNoteId !== null) {
63
+ return savedShowNoteId === 'true'
64
+ }
65
+
66
+ return tnotesConfig.sidebarShowNoteId ?? false
67
+ })
68
+
69
+ // 获取 base 路径
70
+ const base = computed(() => site.value.base || '/')
71
+
72
+ // 加载 sidebar 数据
73
+ function loadSidebar() {
74
+ if (sidebarConfig && sidebarConfig['/notes/']) {
75
+ sidebarGroups.value = processItems(sidebarConfig['/notes/'])
76
+ }
77
+ }
78
+
79
+ // 递归处理侧边栏项,添加 collapsed 状态
80
+ function processItems(items: any[]): SidebarItem[] {
81
+ return items.map((item) => ({
82
+ ...item,
83
+ collapsed: item.collapsed ?? true,
84
+ items: item.items ? processItems(item.items) : undefined,
85
+ }))
86
+ }
87
+
88
+ // 判断项是否有子项
89
+ function hasChildren(item: SidebarItem): boolean {
90
+ return !!(item.items && item.items.length > 0)
91
+ }
92
+
93
+ // 获取项的唯一 key
94
+ function getItemKey(item: SidebarItem): string {
95
+ return item.link || item.text
96
+ }
97
+
98
+ // 切换项的展开/折叠状态(支持递归)
99
+ function toggleItem(item: SidebarItem) {
100
+ item.collapsed = !item.collapsed
101
+ }
102
+
103
+ // 递归查找并切换项
104
+ function toggleItemRecursive(items: SidebarItem[], text: string): boolean {
105
+ for (const item of items) {
106
+ if (item.text === text) {
107
+ item.collapsed = !item.collapsed
108
+ return true
109
+ }
110
+ if (item.items && toggleItemRecursive(item.items, text)) {
111
+ return true
112
+ }
113
+ }
114
+ return false
115
+ }
116
+
117
+ // 切换组展开/折叠(保留兼容性)
118
+ function toggleGroup(groupText: string) {
119
+ toggleItemRecursive(sidebarGroups.value, groupText)
120
+ }
121
+
122
+ // 递归展开/折叠所有项
123
+ function setAllCollapsed(items: SidebarItem[], collapsed: boolean) {
124
+ items.forEach((item) => {
125
+ if (item.items) {
126
+ item.collapsed = collapsed
127
+ setAllCollapsed(item.items, collapsed)
128
+ }
129
+ })
130
+ }
131
+
132
+ // 检查是否有任何一级章节处于展开状态
133
+ function hasAnyFirstLevelExpanded(): boolean {
134
+ return sidebarGroups.value.some((group) => !group.collapsed)
135
+ }
136
+
137
+ // 展开全部
138
+ function expandAll() {
139
+ setAllCollapsed(sidebarGroups.value, false)
140
+ }
141
+
142
+ // 折叠全部
143
+ function collapseAll() {
144
+ setAllCollapsed(sidebarGroups.value, true)
145
+ }
146
+
147
+ // 智能切换:如果有展开的一级章节则折叠全部,否则展开全部
148
+ function toggleExpandCollapse() {
149
+ if (hasAnyFirstLevelExpanded()) {
150
+ collapseAll()
151
+ } else {
152
+ expandAll()
153
+ }
154
+ }
155
+
156
+ // 获取当前笔记的所有出现位置
157
+ function getCurrentNotePositions(): HTMLElement[] {
158
+ const currentPath = route.path
159
+ const elements: HTMLElement[] = []
160
+
161
+ if (!navRef.value) {
162
+ console.log('❌ [getCurrentNotePositions] navRef is null')
163
+ return elements
164
+ }
165
+
166
+ console.log('🔍 [getCurrentNotePositions] Current route path:', currentPath)
167
+
168
+ // 查找所有激活的笔记项
169
+ const activeItems = navRef.value.querySelectorAll('.nav-item.active')
170
+ console.log(
171
+ '🔍 [getCurrentNotePositions] Active nav-items:',
172
+ activeItems.length
173
+ )
174
+
175
+ activeItems.forEach((item, index) => {
176
+ const href = item.getAttribute('href')
177
+ console.log(`🔍 [${index}] Active item href:`, href)
178
+ elements.push(item as HTMLElement)
179
+ })
180
+
181
+ console.log('🎯 [getCurrentNotePositions] Found positions:', elements.length)
182
+ return elements
183
+ }
184
+
185
+ // 展开指定元素的父级分组
186
+ function expandParentGroup(element: HTMLElement) {
187
+ console.log('📂 [expandParentGroup] Starting to expand parent groups')
188
+
189
+ // 查找所有父级 group 元素(从最近的开始)
190
+ let currentElement: HTMLElement | null = element
191
+ const groupsToExpand: string[] = []
192
+
193
+ // 向上遍历,收集所有父级 group 的标题文本
194
+ while (currentElement) {
195
+ const groupElement = currentElement.closest('.group')
196
+ if (!groupElement) break
197
+
198
+ const groupTitle = groupElement.querySelector('.group-title span')
199
+ if (groupTitle) {
200
+ const groupText = groupTitle.textContent?.trim()
201
+ if (groupText) {
202
+ console.log('📌 [expandParentGroup] Found parent group:', groupText)
203
+ groupsToExpand.push(groupText)
204
+ }
205
+ }
206
+
207
+ // 继续向上查找
208
+ currentElement = groupElement.parentElement?.closest('.group') || null
209
+ }
210
+
211
+ console.log(
212
+ '📋 [expandParentGroup] Groups to expand (inner to outer):',
213
+ groupsToExpand
214
+ )
215
+
216
+ // 从最外层开始展开,逐层向内
217
+ // 但是搜索时要确保在正确的上下文中搜索
218
+ if (groupsToExpand.length === 0) return
219
+
220
+ // 反转数组,从最外层开始处理
221
+ const outerToInner = [...groupsToExpand].reverse()
222
+ console.log(
223
+ '📋 [expandParentGroup] Processing order (outer to inner):',
224
+ outerToInner
225
+ )
226
+
227
+ // 第一层必须从根开始搜索
228
+ let currentContext: SidebarItem[] | null = null
229
+
230
+ for (let i = 0; i < outerToInner.length; i++) {
231
+ const groupText = outerToInner[i]
232
+ console.log(`🔄 [expandParentGroup] [${i}] Expanding: "${groupText}"`)
233
+
234
+ if (i === 0) {
235
+ // 第一层从根搜索
236
+ console.log(` 🌳 Searching from root`)
237
+ const found = expandGroupRecursive(sidebarGroups.value, groupText)
238
+ if (found) {
239
+ // 找到后,获取这个分组的 items 作为下一层的搜索上下文
240
+ const foundGroup = findGroupByText(sidebarGroups.value, groupText)
241
+ if (foundGroup?.items) {
242
+ currentContext = foundGroup.items
243
+ console.log(
244
+ ` ✅ Found and set context for next level (${foundGroup.items.length} items)`
245
+ )
246
+ }
247
+ }
248
+ } else {
249
+ // 后续层从上一层的上下文中搜索
250
+ if (currentContext) {
251
+ console.log(
252
+ ` 🔍 Searching in context (${currentContext.length} items)`
253
+ )
254
+ const found = expandGroupRecursive(currentContext, groupText)
255
+ if (found) {
256
+ const foundGroup = findGroupByText(currentContext, groupText)
257
+ if (foundGroup?.items) {
258
+ currentContext = foundGroup.items
259
+ console.log(
260
+ ` ✅ Found and set context for next level (${foundGroup.items.length} items)`
261
+ )
262
+ }
263
+ }
264
+ }
265
+ }
266
+ }
267
+ }
268
+
269
+ // 查找分组(不展开,只返回引用)
270
+ function findGroupByText(
271
+ items: SidebarItem[],
272
+ targetText: string
273
+ ): SidebarItem | null {
274
+ for (const item of items) {
275
+ if (item.text === targetText) {
276
+ return item
277
+ }
278
+ if (item.items) {
279
+ const found = findGroupByText(item.items, targetText)
280
+ if (found) return found
281
+ }
282
+ }
283
+ return null
284
+ }
285
+
286
+ // 递归查找并展开分组
287
+ function expandGroupRecursive(
288
+ items: SidebarItem[],
289
+ targetText: string,
290
+ depth: number = 0
291
+ ): boolean {
292
+ const indent = ' '.repeat(depth)
293
+ console.log(
294
+ `${indent}🔍 [expandGroupRecursive] Searching for "${targetText}" at depth ${depth}`
295
+ )
296
+
297
+ for (const item of items) {
298
+ console.log(`${indent} 📝 Checking item: "${item.text}"`)
299
+
300
+ if (item.text === targetText) {
301
+ console.log(`${indent} ✅ Found target! Setting collapsed = false`)
302
+ item.collapsed = false
303
+ return true
304
+ }
305
+
306
+ if (item.items) {
307
+ console.log(
308
+ `${indent} 📂 Item has ${item.items.length} children, searching...`
309
+ )
310
+ const found = expandGroupRecursive(item.items, targetText, depth + 1)
311
+ if (found) {
312
+ console.log(
313
+ `${indent} ✅ Target found in children, expanding current item "${item.text}"`
314
+ )
315
+ // 如果在子项中找到了,也展开当前项
316
+ item.collapsed = false
317
+ return true
318
+ }
319
+ }
320
+ }
321
+
322
+ console.log(
323
+ `${indent}❌ [expandGroupRecursive] Target "${targetText}" not found at depth ${depth}`
324
+ )
325
+ return false
326
+ }
327
+
328
+ // 滚动到指定元素
329
+ function scrollToElement(element: HTMLElement) {
330
+ if (!element || !navRef.value) {
331
+ // console.log('❌ [scrollToElement] No element or navRef')
332
+ return
333
+ }
334
+
335
+ const navContainer = navRef.value.closest('.VPSidebar')
336
+ if (!navContainer) {
337
+ // console.log('❌ [scrollToElement] No VPSidebar container found')
338
+ return
339
+ }
340
+
341
+ // console.log('📍 [scrollToElement] Scrolling to element')
342
+
343
+ // 计算元素相对于容器的位置
344
+ const elementRect = element.getBoundingClientRect()
345
+ const containerRect = navContainer.getBoundingClientRect()
346
+
347
+ // 计算需要滚动的距离(将元素放在容器中间)
348
+ const scrollTop =
349
+ navContainer.scrollTop +
350
+ elementRect.top -
351
+ containerRect.top -
352
+ containerRect.height / 2 +
353
+ elementRect.height / 2
354
+
355
+ navContainer.scrollTo({
356
+ top: scrollTop,
357
+ behavior: 'smooth',
358
+ })
359
+
360
+ // 添加临时高亮动画
361
+ element.classList.add('focus-highlight')
362
+ setTimeout(() => {
363
+ element.classList.remove('focus-highlight')
364
+ }, 1000)
365
+ }
366
+
367
+ // 聚焦到当前笔记(支持多个位置切换)
368
+ function focusCurrentNote() {
369
+ console.log('🎯 [focusCurrentNote] Called')
370
+ const positions = getCurrentNotePositions()
371
+
372
+ if (positions.length === 0) {
373
+ console.log('❌ [focusCurrentNote] No positions found')
374
+ return
375
+ }
376
+
377
+ // 循环切换聚焦位置
378
+ currentFocusIndex.value = (currentFocusIndex.value + 1) % positions.length
379
+ const targetElement = positions[currentFocusIndex.value]
380
+
381
+ console.log(
382
+ `🎯 [focusCurrentNote] Focusing position ${currentFocusIndex.value + 1}/${
383
+ positions.length
384
+ }`
385
+ )
386
+
387
+ // 展开该笔记所在的分组
388
+ expandParentGroup(targetElement)
389
+
390
+ // 滚动到该笔记
391
+ setTimeout(() => {
392
+ scrollToElement(targetElement)
393
+ }, 100)
394
+ }
395
+
396
+ // 展开当前激活笔记的所有父级分组
397
+ function expandActiveItemParents() {
398
+ expandActiveItemParentsRecursive(sidebarGroups.value)
399
+ }
400
+
401
+ // 递归展开包含激活项的父级
402
+ function expandActiveItemParentsRecursive(items: SidebarItem[]): boolean {
403
+ let hasActive = false
404
+
405
+ for (const item of items) {
406
+ if (item.link) {
407
+ // 检查当前项是否激活
408
+ const fullLink = getFullLink(item.link)
409
+ const decodedRoutePath = decodeURIComponent(route.path)
410
+ const decodedFullLink = decodeURIComponent(fullLink)
411
+ const itemActive =
412
+ decodedRoutePath === decodedFullLink ||
413
+ decodedRoutePath === decodedFullLink + '.html'
414
+
415
+ if (itemActive) {
416
+ hasActive = true
417
+ }
418
+ } else if (item.items) {
419
+ const childHasActive = expandActiveItemParentsRecursive(item.items)
420
+ if (childHasActive) {
421
+ item.collapsed = false
422
+ hasActive = true
423
+ }
424
+ }
425
+ }
426
+
427
+ return hasActive
428
+ }
429
+
430
+ // 获取完整链接(包含 base)
431
+ function getFullLink(link: string) {
432
+ const cleanLink = link.startsWith('/') ? link.slice(1) : link
433
+ return base.value + cleanLink
434
+ }
435
+
436
+ // 滚动到当前激活的笔记
437
+ function scrollToActiveItem() {
438
+ // 等待 DOM 更新
439
+ setTimeout(() => {
440
+ const positions = getCurrentNotePositions()
441
+ if (positions.length > 0) {
442
+ // 展开所有包含当前笔记的分组
443
+ expandActiveItemParents()
444
+
445
+ // 滚动到第一个位置
446
+ setTimeout(() => {
447
+ scrollToElement(positions[0])
448
+ }, 100)
449
+ }
450
+ }, 300)
451
+ }
452
+
453
+ // 暴露函数给父组件使用
454
+ defineExpose({
455
+ expandAll,
456
+ collapseAll,
457
+ toggleExpandCollapse,
458
+ hasAnyFirstLevelExpanded,
459
+ focusCurrentNote,
460
+ })
461
+
462
+ onMounted(() => {
463
+ loadSidebar()
464
+
465
+ // 调试:打印配置信息
466
+ // console.log('🔧 [CustomSidebar] showNoteId:', showNoteId.value)
467
+ // if (typeof window !== 'undefined') {
468
+ // console.log(
469
+ // '🔧 [CustomSidebar] localStorage value:',
470
+ // localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
471
+ // )
472
+ // }
473
+ // console.log(
474
+ // '🔧 [CustomSidebar] tnotesConfig value:',
475
+ // tnotesConfig.sidebarShowNoteId
476
+ // )
477
+
478
+ // 自动滚动到当前激活的笔记
479
+ scrollToActiveItem()
480
+ })
481
+
482
+ // 监听 sidebarConfig 的变化(HMR 会更新这个导入的数据)
483
+ watch(
484
+ () => sidebarConfig,
485
+ () => {
486
+ // console.log('🔄 [CustomSidebar] Sidebar config changed, reloading...')
487
+ loadSidebar()
488
+ },
489
+ { deep: true }
490
+ )
491
+
492
+ // 监听 tnotesConfig 的变化
493
+ watch(
494
+ () => tnotesConfig,
495
+ () => {
496
+ // console.log(
497
+ // '🔄 [CustomSidebar] TNotes config changed, sidebarShowNoteId:',
498
+ // tnotesConfig.sidebarShowNoteId
499
+ // )
500
+ },
501
+ { deep: true }
502
+ )
503
+
504
+ // 监听路由变化,自动展开当前激活项所在的组并滚动
505
+ watch(
506
+ () => route.path,
507
+ () => {
508
+ // 重置聚焦索引
509
+ currentFocusIndex.value = 0
510
+
511
+ // 展开并滚动到当前笔记
512
+ expandActiveItemParents()
513
+ scrollToActiveItem()
514
+ }
515
+ )
516
+ </script>
517
+
518
+ <style scoped>
519
+ /* 自定义 sidebar 容器,适配 VitePress 的 sidebar-nav-before 插槽 */
520
+ .custom-sidebar-wrapper {
521
+ /* 不需要设置 position 和尺寸,因为它在 VitePress 的 sidebar 容器内 */
522
+ }
523
+
524
+ .nav {
525
+ font-size: 14px;
526
+ line-height: 2;
527
+ }
528
+
529
+ .group {
530
+ margin-bottom: 16px;
531
+ }
532
+
533
+ .group-title {
534
+ display: flex;
535
+ justify-content: space-between;
536
+ align-items: center;
537
+ width: 100%;
538
+ padding: 6px 0;
539
+ font-weight: 600;
540
+ color: var(--vp-c-text-1);
541
+ background: none;
542
+ border: none;
543
+ cursor: pointer;
544
+ text-align: left;
545
+ transition: color 0.25s;
546
+ }
547
+
548
+ .group-title:hover {
549
+ color: var(--vp-c-brand-1);
550
+ }
551
+
552
+ .arrow {
553
+ font-size: 10px;
554
+ transform: rotate(90deg);
555
+ transition: transform 0.25s;
556
+ }
557
+
558
+ .arrow.collapsed {
559
+ transform: rotate(0deg);
560
+ }
561
+
562
+ .nav-item {
563
+ display: block;
564
+ padding: 4px;
565
+ color: var(--vp-c-text-2);
566
+ text-decoration: none;
567
+ border-radius: 4px;
568
+ font-size: 14px;
569
+ line-height: 24px;
570
+ transition: all 0.25s;
571
+ }
572
+
573
+ .nav-item:hover {
574
+ color: var(--vp-c-brand-1);
575
+ background-color: var(--vp-c-default-soft);
576
+ }
577
+
578
+ .nav-item.active {
579
+ color: var(--vp-c-brand-1);
580
+ font-weight: 600;
581
+ }
582
+
583
+ /* 聚焦高亮动画 */
584
+ .nav-item.focus-highlight {
585
+ animation: focusPulse 1s ease-in-out;
586
+ }
587
+
588
+ @keyframes focusPulse {
589
+ 0%,
590
+ 100% {
591
+ background-color: transparent;
592
+ }
593
+ 50% {
594
+ background-color: var(--vp-c-brand-soft);
595
+ }
596
+ }
597
+ </style>
598
+
599
+ <!-- 全局样式:隐藏 VitePress 默认的 sidebar nav -->
600
+ <style>
601
+ /* 隐藏 VitePress 默认的 sidebar 导航内容(保留容器) */
602
+ .VPSidebarNav {
603
+ display: none !important;
604
+ }
605
+ </style>