@tnotesjs/core 0.1.27 → 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.
Files changed (71) hide show
  1. package/README.md +2 -1
  2. package/package.json +3 -1
  3. package/templates/sub-repo/.gitattributes +30 -0
  4. package/templates/sub-repo/.github/workflows/deploy.yml +87 -0
  5. package/templates/sub-repo/.vitepress/config.mts +12 -0
  6. package/templates/sub-repo/.vitepress/env.d.ts +10 -0
  7. package/templates/sub-repo/.vitepress/theme/index.ts +12 -0
  8. package/templates/sub-repo/.vscode/settings.json +330 -0
  9. package/templates/sub-repo/.vscode/tnotes.code-snippets +110 -0
  10. package/templates/sub-repo/public/favicon.ico +0 -0
  11. package/templates/sub-repo/public/logo.png +0 -0
  12. package/templates/sub-repo/tsconfig.json +45 -0
  13. package/vitepress/assets/icons/icon__setting_dark.svg +4 -0
  14. package/vitepress/assets/icons/icon__setting_light.svg +4 -0
  15. package/vitepress/assets/icons/index.ts +2 -2
  16. package/vitepress/client/localSearchIndexBridge.ts +24 -0
  17. package/vitepress/components/Discussions/Discussions.vue +1 -1
  18. package/vitepress/components/Footprints/Footprints.vue +90 -25
  19. package/vitepress/components/Layout/CustomSidebar.vue +489 -117
  20. package/vitepress/components/Layout/Layout.vue +5 -0
  21. package/vitepress/components/Layout/NavBarSettingsTrigger.vue +10 -2
  22. package/vitepress/components/Layout/SidebarItems.vue +517 -342
  23. package/vitepress/components/Layout/SidebarNavBefore.vue +1 -50
  24. package/vitepress/components/Layout/SidebarResizeHandle.vue +6 -23
  25. package/vitepress/components/Layout/composables/docLayoutLogic.ts +59 -0
  26. package/vitepress/components/Layout/composables/useDocLayout.test.ts +69 -0
  27. package/vitepress/components/Layout/composables/useDocLayout.ts +128 -0
  28. package/vitepress/components/Layout/composables/useSidebarDrag.ts +514 -0
  29. package/vitepress/components/Layout/composables/useSidebarLayout.ts +25 -8
  30. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +8 -3
  31. package/vitepress/components/Layout/sidebarCollapsedState.test.ts +183 -0
  32. package/vitepress/components/Layout/sidebarCollapsedState.ts +241 -0
  33. package/vitepress/components/Layout/sidebarDragLogic.test.ts +1146 -0
  34. package/vitepress/components/Layout/sidebarDragLogic.ts +1307 -0
  35. package/vitepress/components/Layout/sidebarHitTest.test.ts +572 -0
  36. package/vitepress/components/Layout/sidebarHitTest.ts +801 -0
  37. package/vitepress/components/Layout/sidebarItemRenderLogic.test.ts +53 -0
  38. package/vitepress/components/Layout/sidebarItemRenderLogic.ts +50 -0
  39. package/vitepress/components/MarkMap/MarkMap.vue +67 -10
  40. package/vitepress/components/Settings/Settings.vue +78 -74
  41. package/vitepress/components/SidebarCard/FolderTreeItems.vue +268 -0
  42. package/vitepress/components/SidebarCard/SidebarCard.vue +125 -274
  43. package/vitepress/components/constants.ts +18 -6
  44. package/vitepress/components/sidebar.data.ts +13 -11
  45. package/vitepress/components/sidebarTreeHelpers.test.ts +142 -0
  46. package/vitepress/components/sidebarTreeHelpers.ts +168 -0
  47. package/vitepress/components/utils/vscodePaths.test.ts +41 -0
  48. package/vitepress/components/utils/vscodePaths.ts +52 -0
  49. package/vitepress/config/index.ts +2 -0
  50. package/vitepress/plugins/localSearchIndexBuilder.ts +209 -0
  51. package/vitepress/plugins/localSearchReindex.test.ts +46 -0
  52. package/vitepress/plugins/localSearchReindexLogic.ts +50 -0
  53. package/vitepress/plugins/localSearchReindexPatch.test.ts +30 -0
  54. package/vitepress/plugins/localSearchReindexPatch.ts +37 -0
  55. package/vitepress/plugins/localSearchReindexPlugin.ts +318 -0
  56. package/vitepress/plugins/sidebarStructurePlugin.ts +209 -63
  57. package/vitepress/theme/index.ts +8 -0
  58. package/vitepress/theme/styles/base.scss +0 -8
  59. package/vitepress/theme/styles/components/markmap.scss +23 -2
  60. package/vitepress/theme/styles/components/swiper.scss +1 -1
  61. package/vitepress/theme/styles/doc-layout.scss +212 -0
  62. package/vitepress/theme/styles/index.scss +3 -0
  63. package/vitepress/theme/styles/layout.scss +60 -10
  64. package/dist/chunk-4KG6RXFS.js +0 -4637
  65. package/dist/chunk-AGSL3VZE.js +0 -193
  66. package/dist/cli/index.js +0 -683
  67. package/dist/index.js +0 -8
  68. package/dist/vitepress/config/index.js +0 -1178
  69. package/vitepress/assets/icons/icon__mindmap.svg +0 -1
  70. package/vitepress/assets/icons/icon__setting.svg +0 -1
  71. package/vitepress/components/SidebarCard/MindMapView.vue +0 -488
@@ -12,15 +12,25 @@ import {
12
12
  icon__vscode,
13
13
  icon__folder,
14
14
  icon__search,
15
- icon__mindmap,
16
15
  icon__number_gray,
17
16
  icon__number_purple,
18
17
  } from '../../assets/icons'
19
18
  import { NOTES_DIR_KEY, REPO_NAME, AUTHOR, ROOT_ITEM } from '../constants.ts'
20
- import MindMapView from './MindMapView.vue'
19
+ import FolderTreeItems from './FolderTreeItems.vue'
21
20
  import NotesTrendChart from './NotesTrendChart.vue'
22
21
  import { useSettingsDialog } from '../Settings/composables/useSettingsDialog'
23
22
  import { data as sidebarConfig } from '../sidebar.data'
23
+ import {
24
+ buildSidebarTree,
25
+ collectParentKeys,
26
+ extractRealNumberFromLink,
27
+ extractTitleFromText,
28
+ } from '../sidebarTreeHelpers'
29
+ import {
30
+ getRepoRootPath,
31
+ resolveNoteReadmePath,
32
+ toVscodeFileUrl,
33
+ } from '../utils/vscodePaths'
24
34
  import { formatDate } from '../utils.ts'
25
35
 
26
36
  // #region props
@@ -38,7 +48,7 @@ const props = defineProps({
38
48
 
39
49
  // #region data
40
50
  const viewMode = ref('folder')
41
- const expandedGroupsFolder = ref(new Set())
51
+ const expandedTreeKeys = ref(new Set())
42
52
  const searchQuery = ref('')
43
53
  const debouncedQuery = ref('')
44
54
  const showAbout = ref(false)
@@ -50,54 +60,21 @@ let debounceTimer = null
50
60
  const searchResults = computed(() => {
51
61
  const query = debouncedQuery.value.trim().toLowerCase()
52
62
  if (!query) return []
53
- return articles.filter((article) => {
63
+ return articles.value.filter((article) => {
54
64
  const fullTitle = `${article.realNumber}. ${article.text}`.toLowerCase()
55
65
  return fullTitle.includes(query)
56
66
  })
57
67
  })
58
68
 
59
- const folderViewData = computed(() => {
60
- const result = {}
61
-
62
- // 按完整分组路径构建层级结构(平铺模式)
63
- articles.forEach((article) => {
64
- // 将所有层级铺出来,路径信息作为分组名
65
- // eg. 1. 第一层级/第一个第二层级
66
- // article 1
67
- // article 2
68
- // ……
69
- // 1. 第一层级/第二个第二层级
70
- // article 1
71
- // article 2
72
- // ……
73
- // 1. 第一层级/第三个第二层级
74
- // article 1
75
- // article 2
76
- // ……
77
- // const group = article.group
78
- // if (!result[group]) {
79
- // result[group] = {
80
- // name: group,
81
- // articles: [],
82
- // fullPath: group,
83
- // }
84
- // }
85
- // result[group].articles.push(article)
86
-
87
- // 只铺出第一层级
88
- const firstLevelCategory = article.firstLevelCategory
89
- if (!result[firstLevelCategory]) {
90
- result[firstLevelCategory] = {
91
- name: firstLevelCategory,
92
- articles: [],
93
- fullPath: firstLevelCategory,
94
- }
95
- }
96
- result[firstLevelCategory].articles.push(article)
97
- })
69
+ const folderTree = computed(() =>
70
+ buildSidebarTree(sidebarData.value, {
71
+ showPending: props.pending,
72
+ showDone: props.done,
73
+ base: baseUrl,
74
+ }),
75
+ )
98
76
 
99
- return result
100
- })
77
+ const readmeLink = computed(() => `${site.value.base || '/'}README`)
101
78
  // #endregion
102
79
 
103
80
  const { open: openSettings } = useSettingsDialog()
@@ -111,10 +88,12 @@ const sidebarData = computed(() => {
111
88
  : []
112
89
  })
113
90
 
114
- const { articles, groups } = extractArticlesWithGroups(
115
- sidebarData.value,
116
- props.pending,
117
- props.done,
91
+ const articles = computed(() =>
92
+ extractArticlesWithGroups(
93
+ sidebarData.value,
94
+ props.pending,
95
+ props.done,
96
+ ).articles,
118
97
  )
119
98
 
120
99
  // 根据笔记数量动态计算防抖时间
@@ -123,24 +102,24 @@ const { articles, groups } = extractArticlesWithGroups(
123
102
  // ≤2000: 150ms
124
103
  // ≤5000: 200ms,3k+ 笔记场景(如 LeetCode 知识库)
125
104
  // >5000: 300ms,接近万篇上限时适当增加
126
- const searchDebounceDelay = (() => {
127
- const count = articles.length
105
+ const searchDebounceDelay = computed(() => {
106
+ const count = articles.value.length
128
107
  if (count <= 100) return 0
129
108
  if (count <= 500) return 100
130
109
  if (count <= 2000) return 150
131
110
  if (count <= 5000) return 200
132
111
  return 300
133
- })()
112
+ })
134
113
 
135
114
  watch(searchQuery, (val) => {
136
115
  if (debounceTimer) clearTimeout(debounceTimer)
137
- if (searchDebounceDelay === 0) {
116
+ if (searchDebounceDelay.value === 0) {
138
117
  debouncedQuery.value = val
139
118
  return
140
119
  }
141
120
  debounceTimer = setTimeout(() => {
142
121
  debouncedQuery.value = val
143
- }, searchDebounceDelay)
122
+ }, searchDebounceDelay.value)
144
123
  })
145
124
 
146
125
  function extractArticlesWithGroups(sidebar, showPending, showDone) {
@@ -203,21 +182,6 @@ function extractArticlesWithGroups(sidebar, showPending, showDone) {
203
182
  return { articles, groups }
204
183
  }
205
184
 
206
- // 从 link 中提取真实编号
207
- function extractRealNumberFromLink(link) {
208
- if (!link) return '0000'
209
-
210
- const match = link.match(/\/notes\/(\d{4})/)
211
- return match ? match[1] : '0000'
212
- }
213
-
214
- // 从文本中提取标题(去除开头的编号部分)
215
- function extractTitleFromText(text) {
216
- if (!text) return ''
217
-
218
- return text.replace(/^\d{4}\.?\s/, '')
219
- }
220
-
221
185
  function handleCardClick(link) {
222
186
  // open in new tab
223
187
  if (link) {
@@ -230,27 +194,22 @@ function handleCardClick(link) {
230
194
  // }
231
195
  }
232
196
 
233
- // 在文件夹视图中切换分组展开状态
234
- function toggleGroupFolder(groupName) {
235
- if (expandedGroupsFolder.value.has(groupName)) {
236
- expandedGroupsFolder.value.delete(groupName)
197
+ function toggleTreeNode(key) {
198
+ const next = new Set(expandedTreeKeys.value)
199
+ if (next.has(key)) {
200
+ next.delete(key)
237
201
  } else {
238
- expandedGroupsFolder.value.add(groupName)
202
+ next.add(key)
239
203
  }
204
+ expandedTreeKeys.value = next
240
205
  }
241
206
 
242
- // 切换所有折叠状态
243
207
  function toggleAllFold() {
208
+ const allKeys = collectParentKeys(folderTree.value)
244
209
  const isAllExpanded =
245
- expandedGroupsFolder.value.size === Object.keys(folderViewData.value).length
210
+ allKeys.length > 0 && allKeys.every((key) => expandedTreeKeys.value.has(key))
246
211
 
247
- if (isAllExpanded) {
248
- expandedGroupsFolder.value.clear()
249
- } else {
250
- Object.keys(folderViewData.value).forEach((folderName) => {
251
- expandedGroupsFolder.value.add(folderViewData.value[folderName].fullPath)
252
- })
253
- }
212
+ expandedTreeKeys.value = isAllExpanded ? new Set() : new Set(allKeys)
254
213
  }
255
214
 
256
215
  // 打开 GitHub 仓库
@@ -275,7 +234,7 @@ function openVSCodeRepo() {
275
234
  window.open('vscode://file/' + notesDir, '_blank')
276
235
  }
277
236
 
278
- // 打开 VS Code 中的笔记目录
237
+ // 打开 VS Code 中的笔记 README.md
279
238
  function openVSCodeArticle(article) {
280
239
  const notesDir = localStorage.getItem(NOTES_DIR_KEY)
281
240
 
@@ -289,10 +248,27 @@ function openVSCodeArticle(article) {
289
248
  return
290
249
  }
291
250
 
292
- // 构建笔记目录路径
293
- const notePath = `${notesDir}/${article.relativePath.replace('/README', '')}`
294
- // console.log(notePath, encodeURI(notePath))
295
- window.open('vscode://file/' + encodeURI(notePath), '_blank')
251
+ const notePath = resolveNoteReadmePath(notesDir, article.relativePath)
252
+ if (!notePath) return
253
+
254
+ window.open(toVscodeFileUrl(notePath), '_blank')
255
+ }
256
+
257
+ function openVSCodeReadme() {
258
+ const notesDir = localStorage.getItem(NOTES_DIR_KEY)
259
+
260
+ if (!notesDir) {
261
+ const shouldRedirect = confirm(
262
+ '请先配置本地知识库所在位置,点击确定打开设置面板',
263
+ )
264
+ if (shouldRedirect) {
265
+ openSettings()
266
+ }
267
+ return
268
+ }
269
+
270
+ const readmePath = `${getRepoRootPath(notesDir)}/README.md`
271
+ window.open(toVscodeFileUrl(readmePath), '_blank')
296
272
  }
297
273
  </script>
298
274
 
@@ -314,12 +290,6 @@ function openVSCodeArticle(article) {
314
290
  >
315
291
  <img :src="icon__search" alt="搜索视图" />
316
292
  </button>
317
- <button
318
- :class="{ active: viewMode === 'mindmap' }"
319
- @click="viewMode = 'mindmap'"
320
- >
321
- <img :src="icon__mindmap" alt="思维导图" />
322
- </button>
323
293
  </div>
324
294
  <!-- 右侧控制按钮 -->
325
295
  <div class="actions">
@@ -432,8 +402,6 @@ function openVSCodeArticle(article) {
432
402
  </div>
433
403
  </div>
434
404
  </div>
435
- <!-- 思维导图视图 -->
436
- <MindMapView v-if="viewMode === 'mindmap'" :sidebarData="sidebarData" />
437
405
  <!-- 文件夹视图 -->
438
406
  <div class="folder-view" v-if="viewMode === 'folder'">
439
407
  <NotesTrendChart
@@ -441,65 +409,25 @@ function openVSCodeArticle(article) {
441
409
  :completedNotesCount="ROOT_ITEM.completed_notes_count"
442
410
  />
443
411
  <div class="folder-tree">
444
- <div
445
- v-for="(folder, folderName) in folderViewData"
446
- :key="folderName"
447
- class="folder-group"
448
- >
449
- <!-- 文件夹组 -->
450
- <div
451
- class="folder-header"
452
- @click="toggleGroupFolder(folder.fullPath)"
453
- >
454
- <span class="folder-icon">
455
- {{ expandedGroupsFolder.has(folder.fullPath) ? '📂' : '📁' }}
456
- </span>
457
- <span class="folder-name">{{ folderName }}</span>
458
- <span class="folder-count">{{ folder.articles.length }}</span>
459
- </div>
460
-
461
- <!-- 展开的文章 -->
462
- <div
463
- class="folder-content"
464
- v-if="expandedGroupsFolder.has(folder.fullPath)"
412
+ <div class="tree-readme-row">
413
+ <span class="tree-readme-spacer" aria-hidden="true" />
414
+ <a :href="readmeLink" class="tree-readme-link">README</a>
415
+ <button
416
+ class="vscode-article"
417
+ type="button"
418
+ title="在 VS Code 中打开 README.md"
419
+ @click="openVSCodeReadme"
465
420
  >
466
- <!-- 当前文件夹中的文章 -->
467
- <div
468
- v-for="article in folder.articles"
469
- :key="article.link"
470
- class="folder-article"
471
- >
472
- <div class="article-info" @click="handleCardClick(article.link)">
473
- <span
474
- class="article-status"
475
- :class="`status-${article.status}`"
476
- >
477
- {{
478
- article.status === 'done'
479
- ? '✅'
480
- : article.status === 'pending'
481
- ? '⏰'
482
- : '📄'
483
- }}
484
- </span>
485
- <!-- <span class="article-number">{{ article.realNumber }}</span> -->
486
- <span class="article-title">{{
487
- showNumber
488
- ? `${article.realNumber}. ${article.text}`
489
- : article.text
490
- }}</span>
491
- </div>
492
-
493
- <button
494
- class="vscode-article"
495
- @click.stop="openVSCodeArticle(article)"
496
- title="在 VS Code 中打开笔记目录"
497
- >
498
- <img :src="icon__vscode" alt="打开笔记目录" />
499
- </button>
500
- </div>
501
- </div>
421
+ <img :src="icon__vscode" alt="打开 README.md" />
422
+ </button>
502
423
  </div>
424
+ <FolderTreeItems
425
+ :nodes="folderTree"
426
+ :show-number="showNumber"
427
+ :expanded-keys="expandedTreeKeys"
428
+ @toggle-node="toggleTreeNode"
429
+ @open-vscode="openVSCodeArticle"
430
+ />
503
431
  </div>
504
432
  </div>
505
433
  </div>
@@ -611,138 +539,61 @@ function openVSCodeArticle(article) {
611
539
  padding: 1rem 0;
612
540
 
613
541
  .folder-tree {
614
- // background-color: var(--vp-c-bg-soft);
615
542
  border-radius: 8px;
616
543
  padding: 1rem;
544
+ }
617
545
 
618
- .folder-group {
619
- margin-bottom: 1rem;
620
- border: 1px solid var(--vp-c-divider);
621
- border-radius: 8px;
622
- overflow: hidden;
546
+ .tree-readme-row {
547
+ display: flex;
548
+ align-items: center;
549
+ gap: 6px;
550
+ min-height: 48px;
551
+ padding: 3px 9px;
552
+ margin-bottom: 3px;
553
+ border-radius: 6px;
554
+ transition: background-color 0.2s;
623
555
 
624
- .folder-header {
625
- display: flex;
626
- align-items: center;
627
- padding: 0.75rem 1rem;
628
- // background-color: var(--vp-c-bg-soft);
629
- cursor: pointer;
630
- transition: background-color 0.3s;
631
-
632
- // &:hover {
633
- // background-color: var(--vp-c-bg-elv);
634
- // }
635
-
636
- .folder-icon {
637
- margin-right: 0.75rem;
638
- font-size: 1.1rem;
639
- }
556
+ &:hover {
557
+ background-color: var(--vp-c-bg-soft);
558
+ }
559
+ }
640
560
 
641
- .folder-name {
642
- flex: 1;
643
- font-weight: 500;
644
- font-size: 0.95rem;
645
- }
561
+ .tree-readme-spacer {
562
+ flex: 0 0 20px;
563
+ width: 20px;
564
+ }
646
565
 
647
- .folder-count {
648
- background-color: var(--vp-c-default-soft);
649
- color: var(--vp-c-text-2);
650
- font-size: 0.8rem;
651
- padding: 0.2rem 0.5rem;
652
- border-radius: 4px;
653
- }
654
- }
566
+ .tree-readme-link {
567
+ flex: 1;
568
+ color: var(--vp-c-text-1);
569
+ text-decoration: none;
570
+ font-size: 0.9rem;
571
+ font-weight: 600;
655
572
 
656
- .folder-content {
657
- padding: 0.5rem 1rem;
658
- background-color: var(--vp-c-bg);
659
-
660
- .folder-article {
661
- display: flex;
662
- align-items: center;
663
- justify-content: space-between;
664
- padding: 0.6rem 0.8rem;
665
- margin: 0.4rem 0;
666
- border-radius: 6px;
667
- transition: background-color 0.3s;
668
-
669
- &:hover {
670
- background-color: var(--vp-c-bg-soft);
671
- }
573
+ &:hover {
574
+ color: var(--vp-c-brand-1);
575
+ }
576
+ }
672
577
 
673
- .article-info {
674
- display: flex;
675
- align-items: center;
676
- flex: 1;
677
- cursor: pointer;
678
- overflow: hidden;
679
-
680
- .article-status {
681
- margin-right: 0.8rem;
682
- font-size: 0.9rem;
683
- width: 1.2rem;
684
- text-align: center;
685
- flex-shrink: 0;
686
-
687
- &.status-done {
688
- color: var(--vp-c-green-1);
689
- }
690
-
691
- &.status-pending {
692
- color: var(--vp-c-yellow-1);
693
- }
694
- }
695
-
696
- .article-number {
697
- background-color: var(--vp-c-default-soft);
698
- color: var(--vp-c-text-2);
699
- font-size: 0.75rem;
700
- padding: 0.2rem 0.5rem;
701
- border-radius: 4px;
702
- font-family: var(--vp-font-family-mono);
703
- flex-shrink: 0;
704
- margin-right: 0.8rem;
705
- }
706
-
707
- .article-title {
708
- font-size: 0.9rem;
709
- overflow: hidden;
710
- text-overflow: ellipsis;
711
- white-space: nowrap;
712
- }
713
- }
578
+ .vscode-article {
579
+ background: none;
580
+ border: none;
581
+ padding: 0.3rem;
582
+ cursor: pointer;
583
+ border-radius: 4px;
584
+ flex-shrink: 0;
585
+ opacity: 0.5;
586
+ transition: opacity 0.2s ease;
714
587
 
715
- .vscode-article {
716
- background: none;
717
- border: none;
718
- padding: 0.3rem;
719
- cursor: pointer;
720
- border-radius: 4px;
721
- flex-shrink: 0;
722
- opacity: 0.5;
723
- transition: opacity 0.3s ease;
724
-
725
- &:hover {
726
- opacity: 1;
727
- background-color: var(--vp-c-bg-soft-down);
728
- }
729
-
730
- img {
731
- display: block;
732
- height: 1rem;
733
- width: 1rem;
734
- }
735
- }
736
- }
737
- }
588
+ &:hover {
589
+ opacity: 1;
590
+ background-color: var(--vp-c-bg-soft-down);
738
591
  }
739
- }
740
592
 
741
- /* 响应式调整 */
742
- @media (max-width: 768px) {
743
- .folder-content {
744
- padding-left: 1rem !important;
745
- padding-right: 1rem !important;
593
+ img {
594
+ display: block;
595
+ height: 1rem;
596
+ width: 1rem;
746
597
  }
747
598
  }
748
599
  }
@@ -72,12 +72,6 @@ export const SIDEBAR_SHOW_NOTE_ID_KEY: string =
72
72
  export const SIDEBAR_MAX_DEPTH_KEY: string =
73
73
  'SIDEBAR_MAX_DEPTH_KEY__' + REPO_NAME
74
74
 
75
- /**
76
- * 侧边栏目录风格配置
77
- */
78
- export const SIDEBAR_DENSITY_KEY: string =
79
- 'SIDEBAR_DENSITY_KEY__' + REPO_NAME
80
-
81
75
  /**
82
76
  * 侧边栏已完成笔记前缀配置
83
77
  */
@@ -90,6 +84,24 @@ export const SIDEBAR_DONE_PREFIX_KEY: string =
90
84
  export const SIDEBAR_UNDONE_PREFIX_KEY: string =
91
85
  'SIDEBAR_UNDONE_PREFIX_KEY__' + REPO_NAME
92
86
 
87
+ /**
88
+ * 侧边栏文件夹展开/折叠状态(按 noteIndex 缓存)
89
+ */
90
+ export const SIDEBAR_COLLAPSED_STATE_KEY: string =
91
+ 'SIDEBAR_COLLAPSED_STATE_KEY__' + REPO_NAME
92
+
93
+ /**
94
+ * 正文区域页宽模式:wide(超宽显示)| standard(标准 750px)
95
+ */
96
+ export const CONTENT_WIDTH_MODE_KEY: string =
97
+ 'CONTENT_WIDTH_MODE_KEY__' + REPO_NAME
98
+
99
+ /**
100
+ * reorder 后短时抑制「展开当前路由父级 + 滚动到 active 项」
101
+ */
102
+ export const SIDEBAR_SUPPRESS_ACTIVE_SCROLL_KEY: string =
103
+ 'SIDEBAR_SUPPRESS_ACTIVE_SCROLL_KEY__' + REPO_NAME
104
+
93
105
  /**
94
106
  * VitePress HOME README 文件名
95
107
  * 该文件内容基于 HOME README 而生成,作为 github pages 中的 README 文件,主要用于展示笔记的目录结构。
@@ -5,11 +5,15 @@
5
5
  import fs from 'node:fs'
6
6
  import path from 'node:path'
7
7
 
8
+ import { TocService } from '../../services/toc/service'
9
+
8
10
  const rootPath = process.cwd()
11
+ const sidebarFilePath = path.resolve(rootPath, 'sidebar.json')
12
+ const tocFilePath = path.resolve(rootPath, 'TOC.md')
9
13
 
10
14
  /**
11
15
  * VitePress Data Loader for Sidebar
12
- * 通过监听 sidebar.json 文件变化,使用 HMR 热更新
16
+ * TOC.md 为唯一数据源,加载前重建 sidebar.json,并监听 TOC/sidebar 变化做 HMR
13
17
  */
14
18
 
15
19
  interface SidebarItem {
@@ -19,8 +23,9 @@ interface SidebarItem {
19
23
 
20
24
  interface SidebarGroup {
21
25
  text: string
26
+ link?: string
22
27
  collapsed?: boolean
23
- items: SidebarItem[]
28
+ items?: SidebarItem[]
24
29
  }
25
30
 
26
31
  interface SidebarConfig {
@@ -28,29 +33,26 @@ interface SidebarConfig {
28
33
  }
29
34
 
30
35
  export default {
31
- // 监听 sidebar.json 文件变化
32
- watch: [path.resolve(rootPath, 'sidebar.json')],
33
-
34
- load(watchedFiles: string[]): SidebarConfig {
35
- const sidebarFilePath = watchedFiles[0]
36
+ watch: [sidebarFilePath, tocFilePath],
36
37
 
38
+ async load(): Promise<SidebarConfig> {
37
39
  try {
38
- // 读取 sidebar.json 文件
40
+ const tocService = TocService.getInstance()
41
+ await tocService.regenerateSidebar()
42
+
39
43
  const fileContent = fs.readFileSync(sidebarFilePath, 'utf-8')
40
44
  const sidebarArray = JSON.parse(fileContent) as SidebarGroup[]
41
45
 
42
- // 期望的格式是 { '/notes/': [...] }
43
46
  const sidebarData: SidebarConfig = {
44
47
  '/notes/': sidebarArray,
45
48
  }
46
49
 
47
- console.log('[sidebar.data.ts] Sidebar loaded,')
50
+ console.log('[sidebar.data.ts] Sidebar loaded from TOC.md')
48
51
 
49
52
  return sidebarData
50
53
  } catch (error) {
51
54
  console.error('❌ [sidebar.data.ts] Failed to load sidebar.json:', error)
52
55
 
53
- // 返回空的 sidebar 配置作为后备
54
56
  return {
55
57
  '/notes/': [],
56
58
  }