@tnotesjs/core 0.1.15 → 0.1.17
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 +87 -87
- package/dist/{chunk-FVKQPBPP.js → chunk-4MO7F7Q4.js} +2005 -1769
- package/dist/cli/index.js +1 -1
- package/dist/vitepress/config/index.js +212 -4
- package/package.json +1 -1
- package/types/config.ts +61 -61
- package/types/index.ts +11 -11
- package/types/note.ts +33 -33
- package/vitepress/assets/icons/icon__check.svg +3 -3
- package/vitepress/assets/icons/icon__clipboard.svg +7 -7
- package/vitepress/assets/icons/icon__close.svg +1 -1
- package/vitepress/assets/icons/icon__collapse.svg +1 -1
- package/vitepress/assets/icons/icon__confirm.svg +1 -1
- package/vitepress/assets/icons/icon__copy.svg +4 -4
- package/vitepress/assets/icons/icon__focus.svg +1 -1
- package/vitepress/assets/icons/icon__fold.svg +3 -3
- package/vitepress/assets/icons/icon__folder.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
- package/vitepress/assets/icons/icon__github.svg +3 -3
- package/vitepress/assets/icons/icon__mindmap.svg +1 -1
- package/vitepress/assets/icons/icon__next.svg +1 -1
- package/vitepress/assets/icons/icon__number_gray.svg +1 -1
- package/vitepress/assets/icons/icon__number_purple.svg +1 -1
- package/vitepress/assets/icons/icon__prev.svg +1 -1
- package/vitepress/assets/icons/icon__restore.svg +1 -1
- package/vitepress/assets/icons/icon__rotate.svg +4 -4
- package/vitepress/assets/icons/icon__search.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
- package/vitepress/assets/icons/icon__totop.svg +5 -5
- package/vitepress/assets/icons/icon__vscode.svg +5 -5
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
- package/vitepress/assets/icons/index.ts +39 -39
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -373
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -115
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -64
- package/vitepress/components/Discussions/Discussions.module.scss +32 -32
- package/vitepress/components/Discussions/Discussions.vue +211 -211
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
- package/vitepress/components/EnWordList/EnWordList.vue +543 -543
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
- package/vitepress/components/Footprints/Footprints.module.scss +93 -93
- package/vitepress/components/Footprints/Footprints.vue +377 -377
- package/vitepress/components/Layout/AboutModal.module.scss +233 -233
- package/vitepress/components/Layout/AboutModal.vue +105 -105
- package/vitepress/components/Layout/AboutPanel.vue +270 -270
- package/vitepress/components/Layout/ContentCollapse.vue +603 -603
- package/vitepress/components/Layout/CustomSidebar.vue +817 -608
- package/vitepress/components/Layout/DocBeforeControls.vue +149 -149
- package/vitepress/components/Layout/DocFooter.vue +233 -233
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
- package/vitepress/components/Layout/ImagePreview.vue +281 -281
- package/vitepress/components/Layout/Layout.module.scss +661 -661
- package/vitepress/components/Layout/Layout.vue +621 -559
- package/vitepress/components/Layout/NoteStatus.vue +140 -140
- package/vitepress/components/Layout/SidebarItems.vue +1083 -268
- package/vitepress/components/Layout/SidebarNavBefore.vue +171 -108
- package/vitepress/components/Layout/SidebarResizeHandle.vue +319 -0
- package/vitepress/components/Layout/Swiper.vue +167 -167
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
- package/vitepress/components/Layout/ToggleFullContent.vue +35 -35
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
- package/vitepress/components/Layout/ToggleSidebar.vue +26 -36
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
- package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -119
- package/vitepress/components/Layout/composables/useNoteSave.ts +178 -178
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
- package/vitepress/components/Layout/composables/useRedirect.ts +113 -113
- package/vitepress/components/Layout/composables/useSidebarLayout.ts +122 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
- package/vitepress/components/Layout/homeReadme.data.ts +124 -124
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
- package/vitepress/components/MarkMap/MarkMap.vue +405 -405
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
- package/vitepress/components/Mermaid/Mermaid.vue +365 -365
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
- package/vitepress/components/NotesTable/NotesTable.vue +98 -98
- package/vitepress/components/NotesTable/README.md +67 -67
- package/vitepress/components/Settings/Settings.module.scss +375 -433
- package/vitepress/components/Settings/Settings.vue +333 -306
- package/vitepress/components/SidebarCard/MindMapView.vue +484 -484
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
- package/vitepress/components/SidebarCard/SidebarCard.vue +946 -946
- package/vitepress/components/Tooltip/Tooltip.vue +70 -70
- package/vitepress/components/constants.ts +109 -91
- package/vitepress/components/notesConfig.data.ts +73 -73
- package/vitepress/components/sidebar.data.ts +59 -59
- package/vitepress/components/tnotes-config.data.ts +21 -21
- package/vitepress/components/utils.ts +26 -26
- package/vitepress/config/index.ts +169 -167
- package/vitepress/configs/constants.ts +26 -26
- package/vitepress/configs/head.config.ts +25 -25
- package/vitepress/configs/index.ts +9 -9
- package/vitepress/configs/markdown-it.d.ts +23 -23
- package/vitepress/configs/markdown.config.ts +366 -366
- package/vitepress/configs/theme.config.ts +108 -108
- package/vitepress/plugins/buildProgressPlugin.ts +434 -434
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
- package/vitepress/plugins/renameNotePlugin.ts +68 -68
- package/vitepress/plugins/sidebarStructurePlugin.ts +260 -0
- package/vitepress/plugins/updateConfigPlugin.ts +63 -63
- package/vitepress/theme/index.ts +137 -137
- package/vitepress/theme/styles/base.scss +51 -50
- package/vitepress/theme/styles/components/404.scss +31 -31
- package/vitepress/theme/styles/components/collapse.scss +185 -175
- package/vitepress/theme/styles/components/markmap.scss +101 -101
- package/vitepress/theme/styles/components/swiper.scss +255 -255
- package/vitepress/theme/styles/index.scss +25 -25
- package/vitepress/theme/styles/layout.scss +78 -62
- package/vitepress/theme/styles/utilities.scss +39 -39
- package/vitepress/theme/styles/vitepress-override.scss +25 -25
|
@@ -1,608 +1,817 @@
|
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
//
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
)
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
.
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
.
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
.
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
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
|
+
:is-dev="isDev"
|
|
13
|
+
:sidebar-density="sidebarDensity"
|
|
14
|
+
:done-prefix="donePrefix"
|
|
15
|
+
:undone-prefix="undonePrefix"
|
|
16
|
+
@rename-group="renameGroup"
|
|
17
|
+
@delete-group="deleteGroup"
|
|
18
|
+
@create-note-in-group="createNoteInGroup"
|
|
19
|
+
@create-notes-in-group="createNotesInGroup"
|
|
20
|
+
@rename-note="renameNote"
|
|
21
|
+
@delete-note="deleteNote"
|
|
22
|
+
@create-note-around="createNoteAround"
|
|
23
|
+
@open-note-about="openNoteAbout"
|
|
24
|
+
@reorder-sidebar="reorderSidebar"
|
|
25
|
+
/>
|
|
26
|
+
</nav>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import { useRoute, useData } from 'vitepress'
|
|
32
|
+
import { ref, onMounted, watch, computed } from 'vue'
|
|
33
|
+
|
|
34
|
+
import SidebarItems from './SidebarItems.vue'
|
|
35
|
+
import {
|
|
36
|
+
SIDEBAR_SHOW_NOTE_ID_KEY,
|
|
37
|
+
SIDEBAR_MAX_DEPTH_KEY,
|
|
38
|
+
SIDEBAR_DENSITY_KEY,
|
|
39
|
+
SIDEBAR_DONE_PREFIX_KEY,
|
|
40
|
+
SIDEBAR_UNDONE_PREFIX_KEY,
|
|
41
|
+
} from '../constants'
|
|
42
|
+
// @ts-expect-error - VitePress Data Loader
|
|
43
|
+
import { data as sidebarConfig } from '../sidebar.data'
|
|
44
|
+
// @ts-expect-error - VitePress Data Loader
|
|
45
|
+
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
46
|
+
|
|
47
|
+
// 支持递归的侧边栏项类型
|
|
48
|
+
interface SidebarItem {
|
|
49
|
+
text: string
|
|
50
|
+
link?: string
|
|
51
|
+
items?: SidebarItem[]
|
|
52
|
+
collapsed?: boolean
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const route = useRoute()
|
|
56
|
+
const { site } = useData()
|
|
57
|
+
const sidebarGroups = ref<SidebarItem[]>([])
|
|
58
|
+
const navRef = ref<HTMLElement | null>(null)
|
|
59
|
+
const currentFocusIndex = ref(0)
|
|
60
|
+
|
|
61
|
+
const emit = defineEmits<{
|
|
62
|
+
'open-note-about': [noteIndex: string]
|
|
63
|
+
}>()
|
|
64
|
+
|
|
65
|
+
interface GroupActionPayload {
|
|
66
|
+
groupPath: string[]
|
|
67
|
+
text: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface NoteActionPayload {
|
|
71
|
+
noteIndex: string
|
|
72
|
+
text: string
|
|
73
|
+
link?: string
|
|
74
|
+
placement?: 'before' | 'after'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
interface SidebarActionResult {
|
|
78
|
+
success: boolean
|
|
79
|
+
message?: string
|
|
80
|
+
redirectUrl?: string
|
|
81
|
+
newUrl?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface ReorderPayload {
|
|
85
|
+
dragType: 'group' | 'note'
|
|
86
|
+
groupPath?: string[]
|
|
87
|
+
noteIndex?: string
|
|
88
|
+
targetType: 'group' | 'note'
|
|
89
|
+
targetGroupPath?: string[]
|
|
90
|
+
targetNoteIndex?: string
|
|
91
|
+
placement?: 'before' | 'after'
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
type SidebarDensity = 'compact' | 'default' | 'loose'
|
|
95
|
+
|
|
96
|
+
const DEFAULT_SIDEBAR_DENSITY: SidebarDensity = 'default'
|
|
97
|
+
const DEFAULT_DONE_PREFIX = '✅'
|
|
98
|
+
const DEFAULT_UNDONE_PREFIX = '⏰'
|
|
99
|
+
|
|
100
|
+
// 最大解析层级(默认 3 层)
|
|
101
|
+
const maxDepth = computed(() => {
|
|
102
|
+
if (typeof window === 'undefined') {
|
|
103
|
+
return tnotesConfig.sidebarMaxDepth ?? 3
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const savedMaxDepth = localStorage.getItem(SIDEBAR_MAX_DEPTH_KEY)
|
|
107
|
+
if (savedMaxDepth !== null) {
|
|
108
|
+
return parseInt(savedMaxDepth, 10)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return tnotesConfig.sidebarMaxDepth ?? 3
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// 获取配置:是否显示笔记 ID
|
|
115
|
+
const showNoteId = computed(() => {
|
|
116
|
+
if (typeof window === 'undefined') {
|
|
117
|
+
return tnotesConfig.sidebarShowNoteId ?? false
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
|
|
121
|
+
if (savedShowNoteId !== null) {
|
|
122
|
+
return savedShowNoteId === 'true'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return tnotesConfig.sidebarShowNoteId ?? false
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const sidebarDensity = computed<SidebarDensity>(() => {
|
|
129
|
+
if (typeof window === 'undefined') return DEFAULT_SIDEBAR_DENSITY
|
|
130
|
+
|
|
131
|
+
return normalizeSidebarDensity(localStorage.getItem(SIDEBAR_DENSITY_KEY))
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
const donePrefix = computed(() => {
|
|
135
|
+
if (typeof window === 'undefined') return DEFAULT_DONE_PREFIX
|
|
136
|
+
|
|
137
|
+
const savedPrefix = localStorage.getItem(SIDEBAR_DONE_PREFIX_KEY)
|
|
138
|
+
return savedPrefix !== null ? savedPrefix : DEFAULT_DONE_PREFIX
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const undonePrefix = computed(() => {
|
|
142
|
+
if (typeof window === 'undefined') return DEFAULT_UNDONE_PREFIX
|
|
143
|
+
|
|
144
|
+
const savedPrefix = localStorage.getItem(SIDEBAR_UNDONE_PREFIX_KEY)
|
|
145
|
+
return savedPrefix !== null ? savedPrefix : DEFAULT_UNDONE_PREFIX
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
// 获取 base 路径
|
|
149
|
+
const base = computed(() => site.value.base || '/')
|
|
150
|
+
|
|
151
|
+
const isDev = computed(() => {
|
|
152
|
+
if (typeof window === 'undefined') return false
|
|
153
|
+
return (
|
|
154
|
+
window.location.hostname === 'localhost' ||
|
|
155
|
+
window.location.hostname === '127.0.0.1'
|
|
156
|
+
)
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// 加载 sidebar 数据
|
|
160
|
+
function loadSidebar() {
|
|
161
|
+
if (sidebarConfig && sidebarConfig['/notes/']) {
|
|
162
|
+
sidebarGroups.value = processItems(sidebarConfig['/notes/'])
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 递归处理侧边栏项,添加 collapsed 状态
|
|
167
|
+
function processItems(items: any[]): SidebarItem[] {
|
|
168
|
+
return items.map((item) => ({
|
|
169
|
+
...item,
|
|
170
|
+
collapsed: item.collapsed ?? true,
|
|
171
|
+
items: item.items ? processItems(item.items) : undefined,
|
|
172
|
+
}))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function stripHeadingNumber(text: string): string {
|
|
176
|
+
return text.replace(/^\d+(?:\.\d+)*\.\s*/, '')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function stripNoteTitlePrefix(text: string): string {
|
|
180
|
+
return text
|
|
181
|
+
.replace(/^[✅⏰]\s*/, '')
|
|
182
|
+
.replace(/^\d{4}\.?\s*/, '')
|
|
183
|
+
.trim()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function normalizeSidebarDensity(value: string | null): SidebarDensity {
|
|
187
|
+
if (value === 'compact' || value === 'default' || value === 'loose') {
|
|
188
|
+
return value
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return DEFAULT_SIDEBAR_DENSITY
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isCurrentNote(noteIndex: string): boolean {
|
|
195
|
+
const decodedRoutePath = decodeURIComponent(route.path)
|
|
196
|
+
return decodedRoutePath.includes(`/notes/${noteIndex}.`)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function resolveUrl(path: string): string {
|
|
200
|
+
const normalizedBase = base.value.endsWith('/') ? base.value : `${base.value}/`
|
|
201
|
+
return normalizedBase + path.replace(/^\/+/, '')
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function requestSidebarAction(
|
|
205
|
+
path: string,
|
|
206
|
+
body: unknown,
|
|
207
|
+
): Promise<SidebarActionResult> {
|
|
208
|
+
const response = await fetch(path, {
|
|
209
|
+
method: 'POST',
|
|
210
|
+
headers: {
|
|
211
|
+
'Content-Type': 'application/json',
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify(body),
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
const result = (await response.json().catch(() => null)) as
|
|
217
|
+
| SidebarActionResult
|
|
218
|
+
| null
|
|
219
|
+
|
|
220
|
+
if (!response.ok || !result?.success) {
|
|
221
|
+
throw new Error(result?.message || response.statusText || '操作失败')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
setTimeout(() => {
|
|
225
|
+
loadSidebar()
|
|
226
|
+
}, 150)
|
|
227
|
+
|
|
228
|
+
return result
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async function runSidebarAction(action: () => Promise<void>) {
|
|
232
|
+
if (!isDev.value) return
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
await action()
|
|
236
|
+
} catch (error) {
|
|
237
|
+
alert(error instanceof Error ? error.message : String(error))
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renameGroup(payload: GroupActionPayload) {
|
|
242
|
+
void runSidebarAction(async () => {
|
|
243
|
+
const newTitle = window.prompt('请输入新的目录名称', stripHeadingNumber(payload.text))
|
|
244
|
+
if (newTitle === null) return
|
|
245
|
+
|
|
246
|
+
await requestSidebarAction('/__tnotes_sidebar_rename_group', {
|
|
247
|
+
groupPath: payload.groupPath,
|
|
248
|
+
newTitle,
|
|
249
|
+
})
|
|
250
|
+
})
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function deleteGroup(payload: GroupActionPayload) {
|
|
254
|
+
void runSidebarAction(async () => {
|
|
255
|
+
const confirmed = window.confirm(
|
|
256
|
+
`确定删除目录「${payload.groupPath.join(' / ')}」及其下所有笔记吗?`,
|
|
257
|
+
)
|
|
258
|
+
if (!confirmed) return
|
|
259
|
+
|
|
260
|
+
const result = await requestSidebarAction('/__tnotes_sidebar_delete_group', {
|
|
261
|
+
groupPath: payload.groupPath,
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
if (result.redirectUrl) {
|
|
265
|
+
window.location.replace(resolveUrl(result.redirectUrl))
|
|
266
|
+
}
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function createNoteInGroup(payload: GroupActionPayload) {
|
|
271
|
+
void runSidebarAction(async () => {
|
|
272
|
+
await requestSidebarAction('/__tnotes_sidebar_create_note', {
|
|
273
|
+
groupPath: payload.groupPath,
|
|
274
|
+
count: 1,
|
|
275
|
+
})
|
|
276
|
+
})
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function createNotesInGroup(payload: GroupActionPayload) {
|
|
280
|
+
void runSidebarAction(async () => {
|
|
281
|
+
const input = window.prompt('请输入新增笔记数量(1-100)', '1')
|
|
282
|
+
if (input === null) return
|
|
283
|
+
|
|
284
|
+
const count = Number(input)
|
|
285
|
+
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
286
|
+
throw new Error('新增多篇笔记的数量必须是 1-100 的整数')
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await requestSidebarAction('/__tnotes_sidebar_create_notes', {
|
|
290
|
+
groupPath: payload.groupPath,
|
|
291
|
+
count,
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function renameNote(payload: NoteActionPayload) {
|
|
297
|
+
void runSidebarAction(async () => {
|
|
298
|
+
if (!payload.noteIndex) return
|
|
299
|
+
|
|
300
|
+
const newTitle = window.prompt(
|
|
301
|
+
'请输入新的笔记名称',
|
|
302
|
+
stripNoteTitlePrefix(payload.text),
|
|
303
|
+
)
|
|
304
|
+
if (newTitle === null) return
|
|
305
|
+
|
|
306
|
+
const result = await requestSidebarAction('/__tnotes_rename_note', {
|
|
307
|
+
noteIndex: payload.noteIndex,
|
|
308
|
+
newTitle,
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
if (result.newUrl && isCurrentNote(payload.noteIndex)) {
|
|
312
|
+
window.location.replace(resolveUrl(result.newUrl))
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function deleteNote(payload: NoteActionPayload) {
|
|
318
|
+
void runSidebarAction(async () => {
|
|
319
|
+
if (!payload.noteIndex) return
|
|
320
|
+
|
|
321
|
+
const confirmed = window.confirm(
|
|
322
|
+
`确定删除笔记「${stripHeadingNumber(payload.text)}」吗?`,
|
|
323
|
+
)
|
|
324
|
+
if (!confirmed) return
|
|
325
|
+
|
|
326
|
+
const result = await requestSidebarAction('/__tnotes_sidebar_delete_note', {
|
|
327
|
+
noteIndex: payload.noteIndex,
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
if (result.redirectUrl && isCurrentNote(payload.noteIndex)) {
|
|
331
|
+
window.location.replace(resolveUrl(result.redirectUrl))
|
|
332
|
+
}
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function createNoteAround(payload: NoteActionPayload) {
|
|
337
|
+
void runSidebarAction(async () => {
|
|
338
|
+
if (!payload.noteIndex || !payload.placement) return
|
|
339
|
+
|
|
340
|
+
await requestSidebarAction('/__tnotes_sidebar_create_note', {
|
|
341
|
+
targetNoteIndex: payload.noteIndex,
|
|
342
|
+
placement: payload.placement,
|
|
343
|
+
count: 1,
|
|
344
|
+
})
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function openNoteAbout(payload: NoteActionPayload) {
|
|
349
|
+
if (!payload.noteIndex) return
|
|
350
|
+
emit('open-note-about', payload.noteIndex)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function reorderSidebar(payload: ReorderPayload) {
|
|
354
|
+
void runSidebarAction(async () => {
|
|
355
|
+
await requestSidebarAction('/__tnotes_sidebar_reorder', payload)
|
|
356
|
+
})
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 判断项是否有子项
|
|
360
|
+
function hasChildren(item: SidebarItem): boolean {
|
|
361
|
+
return !!(item.items && item.items.length > 0)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// 获取项的唯一 key
|
|
365
|
+
function getItemKey(item: SidebarItem): string {
|
|
366
|
+
return item.link || item.text
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 切换项的展开/折叠状态(支持递归)
|
|
370
|
+
function toggleItem(item: SidebarItem) {
|
|
371
|
+
item.collapsed = !item.collapsed
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 递归查找并切换项
|
|
375
|
+
function toggleItemRecursive(items: SidebarItem[], text: string): boolean {
|
|
376
|
+
for (const item of items) {
|
|
377
|
+
if (item.text === text) {
|
|
378
|
+
item.collapsed = !item.collapsed
|
|
379
|
+
return true
|
|
380
|
+
}
|
|
381
|
+
if (item.items && toggleItemRecursive(item.items, text)) {
|
|
382
|
+
return true
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return false
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 切换组展开/折叠(保留兼容性)
|
|
389
|
+
function toggleGroup(groupText: string) {
|
|
390
|
+
toggleItemRecursive(sidebarGroups.value, groupText)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// 递归展开/折叠所有项
|
|
394
|
+
function setAllCollapsed(items: SidebarItem[], collapsed: boolean) {
|
|
395
|
+
items.forEach((item) => {
|
|
396
|
+
if (item.items) {
|
|
397
|
+
item.collapsed = collapsed
|
|
398
|
+
setAllCollapsed(item.items, collapsed)
|
|
399
|
+
}
|
|
400
|
+
})
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 检查是否有任何一级章节处于展开状态
|
|
404
|
+
function hasAnyFirstLevelExpanded(): boolean {
|
|
405
|
+
return sidebarGroups.value.some((group) => !group.collapsed)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 展开全部
|
|
409
|
+
function expandAll() {
|
|
410
|
+
setAllCollapsed(sidebarGroups.value, false)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 折叠全部
|
|
414
|
+
function collapseAll() {
|
|
415
|
+
setAllCollapsed(sidebarGroups.value, true)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// 智能切换:如果有展开的一级章节则折叠全部,否则展开全部
|
|
419
|
+
function toggleExpandCollapse() {
|
|
420
|
+
if (hasAnyFirstLevelExpanded()) {
|
|
421
|
+
collapseAll()
|
|
422
|
+
} else {
|
|
423
|
+
expandAll()
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 获取当前笔记的所有出现位置
|
|
428
|
+
function getCurrentNotePositions(): HTMLElement[] {
|
|
429
|
+
const elements: HTMLElement[] = []
|
|
430
|
+
|
|
431
|
+
if (!navRef.value) {
|
|
432
|
+
return elements
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const activeItems = navRef.value.querySelectorAll('.nav-item.active')
|
|
436
|
+
|
|
437
|
+
activeItems.forEach((item) => {
|
|
438
|
+
elements.push(item as HTMLElement)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
return elements
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// 展开指定元素的父级分组
|
|
445
|
+
function expandParentGroup(element: HTMLElement) {
|
|
446
|
+
// 查找所有父级 group 元素(从最近的开始)
|
|
447
|
+
let currentElement: HTMLElement | null = element
|
|
448
|
+
const groupsToExpand: string[] = []
|
|
449
|
+
|
|
450
|
+
// 向上遍历,收集所有父级 group 的标题文本
|
|
451
|
+
while (currentElement) {
|
|
452
|
+
const groupElement: HTMLElement | null =
|
|
453
|
+
currentElement.closest<HTMLElement>('.group')
|
|
454
|
+
if (!groupElement) break
|
|
455
|
+
|
|
456
|
+
const groupTitleText = groupElement.querySelector('.group-title-text')
|
|
457
|
+
if (groupTitleText) {
|
|
458
|
+
const groupText = groupTitleText.textContent?.trim()
|
|
459
|
+
if (groupText) {
|
|
460
|
+
groupsToExpand.push(groupText)
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 继续向上查找
|
|
465
|
+
currentElement =
|
|
466
|
+
groupElement.parentElement?.closest<HTMLElement>('.group') || null
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 从最外层开始展开,逐层向内
|
|
470
|
+
// 但是搜索时要确保在正确的上下文中搜索
|
|
471
|
+
if (groupsToExpand.length === 0) return
|
|
472
|
+
|
|
473
|
+
// 反转数组,从最外层开始处理
|
|
474
|
+
const outerToInner = [...groupsToExpand].reverse()
|
|
475
|
+
|
|
476
|
+
// 第一层必须从根开始搜索
|
|
477
|
+
let currentContext: SidebarItem[] | null = null
|
|
478
|
+
|
|
479
|
+
for (let i = 0; i < outerToInner.length; i++) {
|
|
480
|
+
const groupText = outerToInner[i]
|
|
481
|
+
|
|
482
|
+
if (i === 0) {
|
|
483
|
+
// 第一层从根搜索
|
|
484
|
+
const found = expandGroupRecursive(sidebarGroups.value, groupText)
|
|
485
|
+
if (found) {
|
|
486
|
+
// 找到后,获取这个分组的 items 作为下一层的搜索上下文
|
|
487
|
+
const foundGroup = findGroupByText(sidebarGroups.value, groupText)
|
|
488
|
+
if (foundGroup?.items) {
|
|
489
|
+
currentContext = foundGroup.items
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
} else {
|
|
493
|
+
// 后续层从上一层的上下文中搜索
|
|
494
|
+
if (currentContext) {
|
|
495
|
+
const found = expandGroupRecursive(currentContext, groupText)
|
|
496
|
+
if (found) {
|
|
497
|
+
const foundGroup = findGroupByText(currentContext, groupText)
|
|
498
|
+
if (foundGroup?.items) {
|
|
499
|
+
currentContext = foundGroup.items
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// 查找分组(不展开,只返回引用)
|
|
508
|
+
function findGroupByText(
|
|
509
|
+
items: SidebarItem[],
|
|
510
|
+
targetText: string
|
|
511
|
+
): SidebarItem | null {
|
|
512
|
+
for (const item of items) {
|
|
513
|
+
if (item.text === targetText) {
|
|
514
|
+
return item
|
|
515
|
+
}
|
|
516
|
+
if (item.items) {
|
|
517
|
+
const found = findGroupByText(item.items, targetText)
|
|
518
|
+
if (found) return found
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return null
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// 递归查找并展开分组
|
|
525
|
+
function expandGroupRecursive(
|
|
526
|
+
items: SidebarItem[],
|
|
527
|
+
targetText: string,
|
|
528
|
+
): boolean {
|
|
529
|
+
for (const item of items) {
|
|
530
|
+
if (item.text === targetText) {
|
|
531
|
+
item.collapsed = false
|
|
532
|
+
return true
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
if (item.items) {
|
|
536
|
+
const found = expandGroupRecursive(item.items, targetText)
|
|
537
|
+
if (found) {
|
|
538
|
+
// 如果在子项中找到了,也展开当前项
|
|
539
|
+
item.collapsed = false
|
|
540
|
+
return true
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return false
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 滚动到指定元素
|
|
549
|
+
function scrollToElement(element: HTMLElement) {
|
|
550
|
+
if (!element || !navRef.value) {
|
|
551
|
+
// console.log('❌ [scrollToElement] No element or navRef')
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const navContainer = navRef.value.closest('.VPSidebar')
|
|
556
|
+
if (!navContainer) {
|
|
557
|
+
// console.log('❌ [scrollToElement] No VPSidebar container found')
|
|
558
|
+
return
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// console.log('📍 [scrollToElement] Scrolling to element')
|
|
562
|
+
|
|
563
|
+
// 计算元素相对于容器的位置
|
|
564
|
+
const elementRect = element.getBoundingClientRect()
|
|
565
|
+
const containerRect = navContainer.getBoundingClientRect()
|
|
566
|
+
|
|
567
|
+
// 计算需要滚动的距离(将元素放在容器中间)
|
|
568
|
+
const scrollTop =
|
|
569
|
+
navContainer.scrollTop +
|
|
570
|
+
elementRect.top -
|
|
571
|
+
containerRect.top -
|
|
572
|
+
containerRect.height / 2 +
|
|
573
|
+
elementRect.height / 2
|
|
574
|
+
|
|
575
|
+
navContainer.scrollTo({
|
|
576
|
+
top: scrollTop,
|
|
577
|
+
behavior: 'smooth',
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
// 添加临时高亮动画
|
|
581
|
+
element.classList.add('focus-highlight')
|
|
582
|
+
setTimeout(() => {
|
|
583
|
+
element.classList.remove('focus-highlight')
|
|
584
|
+
}, 1000)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// 聚焦到当前笔记(支持多个位置切换)
|
|
588
|
+
function focusCurrentNote() {
|
|
589
|
+
const positions = getCurrentNotePositions()
|
|
590
|
+
|
|
591
|
+
if (positions.length === 0) {
|
|
592
|
+
return
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 循环切换聚焦位置
|
|
596
|
+
currentFocusIndex.value = (currentFocusIndex.value + 1) % positions.length
|
|
597
|
+
const targetElement = positions[currentFocusIndex.value]
|
|
598
|
+
|
|
599
|
+
// 展开该笔记所在的分组
|
|
600
|
+
expandParentGroup(targetElement)
|
|
601
|
+
|
|
602
|
+
// 滚动到该笔记
|
|
603
|
+
setTimeout(() => {
|
|
604
|
+
scrollToElement(targetElement)
|
|
605
|
+
}, 100)
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// 展开当前激活笔记的所有父级分组
|
|
609
|
+
function expandActiveItemParents() {
|
|
610
|
+
expandActiveItemParentsRecursive(sidebarGroups.value)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// 递归展开包含激活项的父级
|
|
614
|
+
function expandActiveItemParentsRecursive(items: SidebarItem[]): boolean {
|
|
615
|
+
let hasActive = false
|
|
616
|
+
|
|
617
|
+
for (const item of items) {
|
|
618
|
+
if (item.link) {
|
|
619
|
+
// 检查当前项是否激活
|
|
620
|
+
const fullLink = getFullLink(item.link)
|
|
621
|
+
const decodedRoutePath = decodeURIComponent(route.path)
|
|
622
|
+
const decodedFullLink = decodeURIComponent(fullLink)
|
|
623
|
+
const itemActive =
|
|
624
|
+
decodedRoutePath === decodedFullLink ||
|
|
625
|
+
decodedRoutePath === decodedFullLink + '.html'
|
|
626
|
+
|
|
627
|
+
if (itemActive) {
|
|
628
|
+
hasActive = true
|
|
629
|
+
}
|
|
630
|
+
} else if (item.items) {
|
|
631
|
+
const childHasActive = expandActiveItemParentsRecursive(item.items)
|
|
632
|
+
if (childHasActive) {
|
|
633
|
+
item.collapsed = false
|
|
634
|
+
hasActive = true
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return hasActive
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// 获取完整链接(包含 base)
|
|
643
|
+
function getFullLink(link: string) {
|
|
644
|
+
const cleanLink = link.startsWith('/') ? link.slice(1) : link
|
|
645
|
+
return base.value + cleanLink
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// 滚动到当前激活的笔记
|
|
649
|
+
function scrollToActiveItem() {
|
|
650
|
+
// 等待 DOM 更新
|
|
651
|
+
setTimeout(() => {
|
|
652
|
+
const positions = getCurrentNotePositions()
|
|
653
|
+
if (positions.length > 0) {
|
|
654
|
+
// 展开所有包含当前笔记的分组
|
|
655
|
+
expandActiveItemParents()
|
|
656
|
+
|
|
657
|
+
// 滚动到第一个位置
|
|
658
|
+
setTimeout(() => {
|
|
659
|
+
scrollToElement(positions[0])
|
|
660
|
+
}, 100)
|
|
661
|
+
}
|
|
662
|
+
}, 300)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 暴露函数给父组件使用
|
|
666
|
+
defineExpose({
|
|
667
|
+
expandAll,
|
|
668
|
+
collapseAll,
|
|
669
|
+
toggleExpandCollapse,
|
|
670
|
+
hasAnyFirstLevelExpanded,
|
|
671
|
+
focusCurrentNote,
|
|
672
|
+
})
|
|
673
|
+
|
|
674
|
+
onMounted(() => {
|
|
675
|
+
loadSidebar()
|
|
676
|
+
|
|
677
|
+
// 调试:打印配置信息
|
|
678
|
+
// console.log('🔧 [CustomSidebar] showNoteId:', showNoteId.value)
|
|
679
|
+
// if (typeof window !== 'undefined') {
|
|
680
|
+
// console.log(
|
|
681
|
+
// '🔧 [CustomSidebar] localStorage value:',
|
|
682
|
+
// localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
|
|
683
|
+
// )
|
|
684
|
+
// }
|
|
685
|
+
// console.log(
|
|
686
|
+
// '🔧 [CustomSidebar] tnotesConfig value:',
|
|
687
|
+
// tnotesConfig.sidebarShowNoteId
|
|
688
|
+
// )
|
|
689
|
+
|
|
690
|
+
// 自动滚动到当前激活的笔记
|
|
691
|
+
scrollToActiveItem()
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
// 监听 sidebarConfig 的变化(HMR 会更新这个导入的数据)
|
|
695
|
+
watch(
|
|
696
|
+
() => sidebarConfig,
|
|
697
|
+
() => {
|
|
698
|
+
// console.log('🔄 [CustomSidebar] Sidebar config changed, reloading...')
|
|
699
|
+
loadSidebar()
|
|
700
|
+
},
|
|
701
|
+
{ deep: true }
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
// 监听 tnotesConfig 的变化
|
|
705
|
+
watch(
|
|
706
|
+
() => tnotesConfig,
|
|
707
|
+
() => {
|
|
708
|
+
// console.log(
|
|
709
|
+
// '🔄 [CustomSidebar] TNotes config changed, sidebarShowNoteId:',
|
|
710
|
+
// tnotesConfig.sidebarShowNoteId
|
|
711
|
+
// )
|
|
712
|
+
},
|
|
713
|
+
{ deep: true }
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
// 监听路由变化,自动展开当前激活项所在的组并滚动
|
|
717
|
+
watch(
|
|
718
|
+
() => route.path,
|
|
719
|
+
() => {
|
|
720
|
+
// 重置聚焦索引
|
|
721
|
+
currentFocusIndex.value = 0
|
|
722
|
+
|
|
723
|
+
// 展开并滚动到当前笔记
|
|
724
|
+
expandActiveItemParents()
|
|
725
|
+
scrollToActiveItem()
|
|
726
|
+
}
|
|
727
|
+
)
|
|
728
|
+
</script>
|
|
729
|
+
|
|
730
|
+
<style scoped>
|
|
731
|
+
/* 自定义 sidebar 容器,适配 VitePress 的 sidebar-nav-before 插槽 */
|
|
732
|
+
.custom-sidebar-wrapper {
|
|
733
|
+
/* 不需要设置 position 和尺寸,因为它在 VitePress 的 sidebar 容器内 */
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.nav {
|
|
737
|
+
font-size: 14px;
|
|
738
|
+
line-height: 2;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
.group {
|
|
742
|
+
margin-bottom: 16px;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.group-title {
|
|
746
|
+
display: flex;
|
|
747
|
+
justify-content: space-between;
|
|
748
|
+
align-items: center;
|
|
749
|
+
width: 100%;
|
|
750
|
+
padding: 6px 0;
|
|
751
|
+
font-weight: 600;
|
|
752
|
+
color: var(--vp-c-text-1);
|
|
753
|
+
background: none;
|
|
754
|
+
border: none;
|
|
755
|
+
cursor: pointer;
|
|
756
|
+
text-align: left;
|
|
757
|
+
transition: color 0.25s;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.group-title:hover {
|
|
761
|
+
color: var(--vp-c-brand-1);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.arrow {
|
|
765
|
+
font-size: 10px;
|
|
766
|
+
transform: rotate(90deg);
|
|
767
|
+
transition: transform 0.25s;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.arrow.collapsed {
|
|
771
|
+
transform: rotate(0deg);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.nav-item {
|
|
775
|
+
display: block;
|
|
776
|
+
padding: 4px;
|
|
777
|
+
color: var(--vp-c-text-2);
|
|
778
|
+
text-decoration: none;
|
|
779
|
+
border-radius: 4px;
|
|
780
|
+
font-size: 14px;
|
|
781
|
+
line-height: 24px;
|
|
782
|
+
transition: all 0.25s;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.nav-item:hover {
|
|
786
|
+
color: var(--vp-c-brand-1);
|
|
787
|
+
background-color: var(--vp-c-default-soft);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
.nav-item.active {
|
|
791
|
+
color: var(--vp-c-brand-1);
|
|
792
|
+
font-weight: 600;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/* 聚焦高亮动画 */
|
|
796
|
+
.nav-item.focus-highlight {
|
|
797
|
+
animation: focusPulse 1s ease-in-out;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
@keyframes focusPulse {
|
|
801
|
+
0%,
|
|
802
|
+
100% {
|
|
803
|
+
background-color: transparent;
|
|
804
|
+
}
|
|
805
|
+
50% {
|
|
806
|
+
background-color: var(--vp-c-brand-soft);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
</style>
|
|
810
|
+
|
|
811
|
+
<!-- 全局样式:隐藏 VitePress 默认的 sidebar nav -->
|
|
812
|
+
<style>
|
|
813
|
+
/* 隐藏 VitePress 默认的 sidebar 导航内容(保留容器) */
|
|
814
|
+
.VPSidebarNav {
|
|
815
|
+
display: none !important;
|
|
816
|
+
}
|
|
817
|
+
</style>
|