@tnotesjs/core 0.1.28 → 0.2.1
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 +2 -1
- package/dist/{chunk-4KG6RXFS.js → chunk-5QN44ES5.js} +2946 -1533
- package/dist/{chunk-AGSL3VZE.js → chunk-5Y5IYS6C.js} +7 -0
- package/dist/cli/index.js +343 -40
- package/dist/index.js +1 -1
- package/dist/vitepress/config/index.js +573 -65
- package/package.json +4 -1
- package/templates/sub-repo/.gitattributes +30 -0
- package/templates/sub-repo/.github/workflows/deploy.yml +87 -0
- package/templates/sub-repo/.vitepress/config.mts +12 -0
- package/templates/sub-repo/.vitepress/env.d.ts +10 -0
- package/templates/sub-repo/.vitepress/theme/index.ts +12 -0
- package/templates/sub-repo/.vscode/settings.json +330 -0
- package/templates/sub-repo/.vscode/tnotes.code-snippets +110 -0
- package/templates/sub-repo/public/favicon.ico +0 -0
- package/templates/sub-repo/public/logo.png +0 -0
- package/templates/sub-repo/tsconfig.json +45 -0
- package/vitepress/assets/icons/icon__setting_dark.svg +4 -0
- package/vitepress/assets/icons/icon__setting_light.svg +4 -0
- package/vitepress/assets/icons/index.ts +2 -2
- package/vitepress/client/localSearchIndexBridge.ts +24 -0
- package/vitepress/components/Discussions/Discussions.vue +1 -1
- package/vitepress/components/Footprints/Footprints.vue +90 -25
- package/vitepress/components/Layout/CustomSidebar.vue +489 -117
- package/vitepress/components/Layout/Layout.vue +5 -0
- package/vitepress/components/Layout/NavBarSettingsTrigger.vue +10 -2
- package/vitepress/components/Layout/SidebarItems.vue +517 -342
- package/vitepress/components/Layout/SidebarNavBefore.vue +1 -50
- package/vitepress/components/Layout/SidebarResizeHandle.vue +6 -23
- package/vitepress/components/Layout/composables/docLayoutLogic.ts +59 -0
- package/vitepress/components/Layout/composables/useDocLayout.test.ts +69 -0
- package/vitepress/components/Layout/composables/useDocLayout.ts +128 -0
- package/vitepress/components/Layout/composables/useSidebarDrag.ts +514 -0
- package/vitepress/components/Layout/composables/useSidebarLayout.ts +25 -8
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +8 -3
- package/vitepress/components/Layout/sidebarCollapsedState.test.ts +183 -0
- package/vitepress/components/Layout/sidebarCollapsedState.ts +241 -0
- package/vitepress/components/Layout/sidebarDragLogic.test.ts +1146 -0
- package/vitepress/components/Layout/sidebarDragLogic.ts +1307 -0
- package/vitepress/components/Layout/sidebarHitTest.test.ts +572 -0
- package/vitepress/components/Layout/sidebarHitTest.ts +801 -0
- package/vitepress/components/Layout/sidebarItemRenderLogic.test.ts +53 -0
- package/vitepress/components/Layout/sidebarItemRenderLogic.ts +50 -0
- package/vitepress/components/MarkMap/MarkMap.vue +67 -10
- package/vitepress/components/Settings/Settings.vue +78 -74
- package/vitepress/components/SidebarCard/FolderTreeItems.vue +268 -0
- package/vitepress/components/SidebarCard/SidebarCard.vue +125 -274
- package/vitepress/components/constants.ts +18 -6
- package/vitepress/components/sidebar.data.ts +13 -11
- package/vitepress/components/sidebarTreeHelpers.test.ts +142 -0
- package/vitepress/components/sidebarTreeHelpers.ts +168 -0
- package/vitepress/components/utils/vscodePaths.test.ts +41 -0
- package/vitepress/components/utils/vscodePaths.ts +52 -0
- package/vitepress/config/index.ts +2 -0
- package/vitepress/plugins/localSearchIndexBuilder.ts +209 -0
- package/vitepress/plugins/localSearchReindex.test.ts +46 -0
- package/vitepress/plugins/localSearchReindexLogic.ts +50 -0
- package/vitepress/plugins/localSearchReindexPatch.test.ts +30 -0
- package/vitepress/plugins/localSearchReindexPatch.ts +37 -0
- package/vitepress/plugins/localSearchReindexPlugin.ts +318 -0
- package/vitepress/plugins/sidebarStructurePlugin.ts +209 -63
- package/vitepress/theme/index.ts +8 -0
- package/vitepress/theme/styles/base.scss +0 -8
- package/vitepress/theme/styles/components/markmap.scss +23 -2
- package/vitepress/theme/styles/components/swiper.scss +1 -1
- package/vitepress/theme/styles/doc-layout.scss +212 -0
- package/vitepress/theme/styles/index.scss +3 -0
- package/vitepress/theme/styles/layout.scss +60 -10
- package/vitepress/assets/icons/icon__mindmap.svg +0 -1
- package/vitepress/assets/icons/icon__setting.svg +0 -1
- 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
|
|
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
|
|
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
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
235
|
-
if (
|
|
236
|
-
|
|
197
|
+
function toggleTreeNode(key) {
|
|
198
|
+
const next = new Set(expandedTreeKeys.value)
|
|
199
|
+
if (next.has(key)) {
|
|
200
|
+
next.delete(key)
|
|
237
201
|
} else {
|
|
238
|
-
|
|
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
|
-
|
|
210
|
+
allKeys.length > 0 && allKeys.every((key) => expandedTreeKeys.value.has(key))
|
|
246
211
|
|
|
247
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
window.open(
|
|
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
|
-
|
|
446
|
-
:
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
561
|
+
.tree-readme-spacer {
|
|
562
|
+
flex: 0 0 20px;
|
|
563
|
+
width: 20px;
|
|
564
|
+
}
|
|
646
565
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
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
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|