@tnotesjs/core 0.1.28 → 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
@@ -0,0 +1,514 @@
1
+ /**
2
+ * vitepress/components/Layout/composables/useSidebarDrag.ts
3
+ *
4
+ * 语雀式侧边栏拖拽 v3:overlay 指示、仅 drop 写后端。
5
+ */
6
+
7
+ import {
8
+ computed,
9
+ inject,
10
+ onBeforeUnmount,
11
+ provide,
12
+ ref,
13
+ type ComputedRef,
14
+ type InjectionKey,
15
+ type Ref,
16
+ } from 'vue'
17
+
18
+ import { computeSidebarNodeId } from '../../../../utils/tocNodeId'
19
+ import {
20
+ buildSidebarDragPreviewTree,
21
+ extractNoteIndexFromLink,
22
+ findTreeItem,
23
+ getDepthContentLeft,
24
+ SIDEBAR_INDENT_SIZE,
25
+ isDropIntentNoOp,
26
+ type SidebarTreeItem,
27
+ type YuqueReorderPayload,
28
+ } from '../sidebarDragLogic'
29
+ import {
30
+ buildFlatForHitTest,
31
+ collectRowMetrics,
32
+ createHitTestState,
33
+ dropIntentFingerprint,
34
+ resolveDropIntent,
35
+ type DropIntent,
36
+ type HitTestState,
37
+ } from '../sidebarHitTest'
38
+
39
+ export interface DropIndicatorState {
40
+ visible: boolean
41
+ top: number
42
+ left: number
43
+ width: number
44
+ mode: 'line' | 'inside' | 'tail-rail' | 'root-prepend'
45
+ targetNodeId: string | null
46
+ depthLevels: number[]
47
+ activeDepth: number
48
+ indentSize: number
49
+ }
50
+
51
+ export interface DropSlotState {
52
+ visible: boolean
53
+ top: number
54
+ left: number
55
+ width: number
56
+ height: number
57
+ }
58
+
59
+ export interface DragReorderMeta {
60
+ sourceCollapseKey: string | null
61
+ wasExpanded: boolean
62
+ targetNodeId: string | null
63
+ action: 'moveAfter' | 'prependChild' | null
64
+ }
65
+
66
+ export interface SidebarDragContext {
67
+ isDragging: ComputedRef<boolean>
68
+ draggingNodeId: Ref<string | null>
69
+ previewItems: Ref<SidebarTreeItem[] | null>
70
+ dropIndicator: Ref<DropIndicatorState>
71
+ dropSlot: Ref<DropSlotState>
72
+ startDrag: (
73
+ noteIndex: string,
74
+ tocLineIndex: number,
75
+ rowEl: HTMLElement,
76
+ event: PointerEvent,
77
+ ) => void
78
+ startFolderDrag: (
79
+ tocLineIndex: number,
80
+ rowEl: HTMLElement,
81
+ event: PointerEvent,
82
+ ) => void
83
+ }
84
+
85
+ export const SIDEBAR_DRAG_KEY: InjectionKey<SidebarDragContext> =
86
+ Symbol('sidebar-drag')
87
+
88
+ interface UseSidebarDragOptions {
89
+ items: Ref<SidebarTreeItem[]>
90
+ navRef: Ref<HTMLElement | null>
91
+ maxDepth: ComputedRef<number>
92
+ isDev: ComputedRef<boolean>
93
+ resolveCollapseKey: (item: SidebarTreeItem) => string | null
94
+ onReorder: (payload: YuqueReorderPayload, meta: DragReorderMeta) => void
95
+ }
96
+
97
+ function emptyDropIndicator(): DropIndicatorState {
98
+ return {
99
+ visible: false,
100
+ top: 0,
101
+ left: 0,
102
+ width: 0,
103
+ mode: 'line',
104
+ targetNodeId: null,
105
+ depthLevels: [],
106
+ activeDepth: 0,
107
+ indentSize: SIDEBAR_INDENT_SIZE,
108
+ }
109
+ }
110
+
111
+ function emptyDropSlot(): DropSlotState {
112
+ return {
113
+ visible: false,
114
+ top: 0,
115
+ left: 0,
116
+ width: 0,
117
+ height: 0,
118
+ }
119
+ }
120
+
121
+ function cloneSidebarTree(items: SidebarTreeItem[]): SidebarTreeItem[] {
122
+ return JSON.parse(JSON.stringify(items)) as SidebarTreeItem[]
123
+ }
124
+
125
+ function createCollapsedGhost(rowEl: HTMLElement, rect: DOMRect): HTMLElement {
126
+ const ghost = document.createElement('div')
127
+ ghost.className = rowEl.className
128
+ ghost.classList.add('sidebar-drag-ghost', 'sidebar-row')
129
+
130
+ const titleEl =
131
+ rowEl.querySelector('.group-title-text') ??
132
+ rowEl.querySelector('.row-main') ??
133
+ rowEl.querySelector('.nav-item')
134
+
135
+ const title = document.createElement('span')
136
+ title.className = 'ghost-title-text'
137
+ title.textContent = titleEl?.textContent?.trim() ?? ''
138
+ ghost.appendChild(title)
139
+
140
+ ghost.style.pointerEvents = 'none'
141
+ ghost.style.width = `${rect.width}px`
142
+ return ghost
143
+ }
144
+
145
+ export function useSidebarDrag(options: UseSidebarDragOptions) {
146
+ const draggingNodeId = ref<string | null>(null)
147
+ const activeIntent = ref<DropIntent | null>(null)
148
+ const previewItems = ref<SidebarTreeItem[] | null>(null)
149
+ const dropIndicator = ref<DropIndicatorState>(emptyDropIndicator())
150
+ const dropSlot = ref<DropSlotState>(emptyDropSlot())
151
+
152
+ let ghostEl: HTMLElement | null = null
153
+ let pointerOffsetX = 0
154
+ let pointerOffsetY = 0
155
+ let activePointerId: number | null = null
156
+ let dragSourceRow: HTMLElement | null = null
157
+ let baseSnapshot: SidebarTreeItem[] | null = null
158
+ let hitState: HitTestState = createHitTestState()
159
+ let dragSource: {
160
+ kind: 'note' | 'folder'
161
+ tocLineIndex: number
162
+ noteIndex?: string
163
+ nodeId: string
164
+ } | null = null
165
+ let dragReorderMeta: DragReorderMeta = {
166
+ sourceCollapseKey: null,
167
+ wasExpanded: false,
168
+ targetNodeId: null,
169
+ action: null,
170
+ }
171
+ let pendingDropIntent: DropIntent | null = null
172
+
173
+ const isDragging = computed(() => dragSource !== null)
174
+
175
+ function findTreeItemByTocLineIndex(
176
+ items: SidebarTreeItem[],
177
+ tocLineIndex: number,
178
+ ): SidebarTreeItem | null {
179
+ for (const node of items) {
180
+ if (node.tocLineIndex === tocLineIndex) return node
181
+ if (node.items?.length) {
182
+ const found = findTreeItemByTocLineIndex(node.items, tocLineIndex)
183
+ if (found) return found
184
+ }
185
+ }
186
+ return null
187
+ }
188
+
189
+ function cleanupDrag() {
190
+ draggingNodeId.value = null
191
+ dragSource = null
192
+ activeIntent.value = null
193
+ pendingDropIntent = null
194
+ previewItems.value = null
195
+ baseSnapshot = null
196
+ hitState = createHitTestState()
197
+ dropIndicator.value = emptyDropIndicator()
198
+ dropSlot.value = emptyDropSlot()
199
+
200
+ if (ghostEl) {
201
+ ghostEl.remove()
202
+ ghostEl = null
203
+ }
204
+
205
+ if (
206
+ dragSourceRow &&
207
+ activePointerId !== null &&
208
+ dragSourceRow.hasPointerCapture(activePointerId)
209
+ ) {
210
+ dragSourceRow.releasePointerCapture(activePointerId)
211
+ }
212
+ dragSourceRow = null
213
+ activePointerId = null
214
+
215
+ window.removeEventListener('pointermove', onPointerMove)
216
+ window.removeEventListener('pointerup', onPointerUp)
217
+ window.removeEventListener('pointercancel', onPointerUp)
218
+ document.body.classList.remove('sidebar-drag-active')
219
+ }
220
+
221
+ function findRowElementByNodeId(nodeId: string): HTMLElement | null {
222
+ const nav = options.navRef.value
223
+ if (!nav) return null
224
+ return nav.querySelector<HTMLElement>(`.sidebar-row[data-node-id="${nodeId}"]`)
225
+ }
226
+
227
+ function updateOverlays(intent: DropIntent | null) {
228
+ const nav = options.navRef.value
229
+ if (!nav || !intent || intent.isRootPrepend) {
230
+ dropIndicator.value = emptyDropIndicator()
231
+ dropSlot.value = emptyDropSlot()
232
+ return
233
+ }
234
+
235
+ const indentSize = SIDEBAR_INDENT_SIZE
236
+ const navRect = nav.getBoundingClientRect()
237
+ const targetRow =
238
+ intent.targetNodeId !== null
239
+ ? findRowElementByNodeId(intent.targetNodeId)
240
+ : null
241
+ const hoverMetric = collectRowMetrics(nav).find(
242
+ (m) => m.tocLineIndex !== null && dragSource && m.tocLineIndex !== dragSource.tocLineIndex,
243
+ )
244
+
245
+ let rowRect: DOMRect | null = targetRow?.getBoundingClientRect() ?? null
246
+ if (!rowRect && intent.hoverRowIndex >= 0) {
247
+ const metrics = collectRowMetrics(nav)
248
+ rowRect = metrics[intent.hoverRowIndex]?.rect ?? null
249
+ }
250
+
251
+ const left = getDepthContentLeft(intent.indicator.activeDepth, indentSize)
252
+
253
+ if (intent.indicator.mode === 'inside') {
254
+ const insideRow =
255
+ findRowElementByNodeId(intent.targetNodeId) ??
256
+ (hoverMetric ? findRowElementByNodeId(hoverMetric.nodeId) : null)
257
+ const insideRect = insideRow?.getBoundingClientRect()
258
+ dropSlot.value = emptyDropSlot()
259
+ dropIndicator.value = {
260
+ visible: true,
261
+ top: insideRect ? insideRect.top - navRect.top : 0,
262
+ left,
263
+ width: Math.max(navRect.width - left - 8, 24),
264
+ mode: 'inside',
265
+ targetNodeId: intent.targetNodeId,
266
+ depthLevels: intent.indicator.depthLevels,
267
+ activeDepth: intent.indicator.activeDepth,
268
+ indentSize,
269
+ }
270
+ return
271
+ }
272
+
273
+ if (rowRect) {
274
+ const height = Math.max(rowRect.height, 28)
275
+ const lineAfterTarget = intent.action === 'moveAfter'
276
+ const slotTop = lineAfterTarget
277
+ ? rowRect.bottom - navRect.top - height
278
+ : rowRect.top - navRect.top
279
+
280
+ dropSlot.value = {
281
+ visible: intent.action === 'moveAfter',
282
+ top: slotTop,
283
+ left,
284
+ width: Math.max(navRect.width - left - 8, 24),
285
+ height,
286
+ }
287
+
288
+ const lineTop = lineAfterTarget
289
+ ? rowRect.bottom - navRect.top - 1
290
+ : rowRect.top - navRect.top - 1
291
+
292
+ dropIndicator.value = {
293
+ visible: true,
294
+ top: lineTop,
295
+ left,
296
+ width: Math.max(navRect.width - left - 8, 24),
297
+ mode: intent.indicator.mode,
298
+ targetNodeId: intent.targetNodeId,
299
+ depthLevels: intent.indicator.depthLevels,
300
+ activeDepth: intent.indicator.activeDepth,
301
+ indentSize,
302
+ }
303
+ }
304
+ }
305
+
306
+ function resolveIntentAtPoint(clientX: number, clientY: number): DropIntent | null {
307
+ if (!dragSource || !baseSnapshot || !options.navRef.value) return null
308
+
309
+ const nav = options.navRef.value
310
+ const navRect = nav.getBoundingClientRect()
311
+ const flat = buildFlatForHitTest(baseSnapshot, dragSource.tocLineIndex)
312
+ const rowMetrics = collectRowMetrics(nav)
313
+
314
+ return resolveDropIntent(
315
+ {
316
+ tree: baseSnapshot,
317
+ flat,
318
+ dragSource,
319
+ sourceNodeId: dragSource.nodeId,
320
+ clientX,
321
+ clientY,
322
+ navLeft: navRect.left,
323
+ navTop: navRect.top,
324
+ navWidth: navRect.width,
325
+ rowMetrics,
326
+ maxDepth: options.maxDepth.value,
327
+ },
328
+ hitState,
329
+ )
330
+ }
331
+
332
+ function onPointerMove(event: PointerEvent) {
333
+ if (!dragSource || !baseSnapshot || !options.navRef.value) return
334
+
335
+ if (ghostEl) {
336
+ ghostEl.style.left = `${event.clientX - pointerOffsetX}px`
337
+ ghostEl.style.top = `${event.clientY - pointerOffsetY}px`
338
+ }
339
+
340
+ const intent = resolveIntentAtPoint(event.clientX, event.clientY)
341
+
342
+ const fingerprint = intent ? dropIntentFingerprint(intent) : null
343
+ if (fingerprint !== hitState.lastIntentFingerprint) {
344
+ hitState.lastIntentFingerprint = fingerprint
345
+ activeIntent.value = intent
346
+ previewItems.value = buildSidebarDragPreviewTree(
347
+ baseSnapshot,
348
+ dragSource,
349
+ intent,
350
+ )
351
+ updateOverlays(intent)
352
+ }
353
+
354
+ if (
355
+ intent &&
356
+ !isDropIntentNoOp(baseSnapshot, dragSource.tocLineIndex, intent)
357
+ ) {
358
+ pendingDropIntent = intent
359
+ }
360
+ }
361
+
362
+ function onPointerUp(event: PointerEvent) {
363
+ const meta = { ...dragReorderMeta }
364
+ const source = dragSource
365
+ const snapshot = baseSnapshot
366
+ const savedActiveIntent = activeIntent.value
367
+ const savedPendingIntent = pendingDropIntent
368
+
369
+ const intentAtRelease = resolveIntentAtPoint(event.clientX, event.clientY)
370
+ const intent =
371
+ intentAtRelease ??
372
+ savedPendingIntent ??
373
+ savedActiveIntent
374
+
375
+ const isNoOp =
376
+ !!intent &&
377
+ !!snapshot &&
378
+ !!source &&
379
+ isDropIntentNoOp(snapshot, source.tocLineIndex, intent)
380
+
381
+ cleanupDrag()
382
+
383
+ if (!intent || !source || !snapshot || !options.isDev.value) return
384
+ if (isNoOp) return
385
+
386
+ const payload: YuqueReorderPayload = {
387
+ node_uuid: source.nodeId,
388
+ action: intent.action,
389
+ }
390
+ if (intent.targetNodeId) {
391
+ payload.target_uuid = intent.targetNodeId
392
+ }
393
+ if (intent.isRootPrepend && snapshot?.[0]?.tocLineIndex !== undefined) {
394
+ payload.dragTocLineIndex = source.tocLineIndex
395
+ payload.placement = 'before'
396
+ payload.targetTocLineIndex = snapshot[0].tocLineIndex
397
+ }
398
+
399
+ options.onReorder(payload, {
400
+ ...meta,
401
+ targetNodeId: intent.targetNodeId,
402
+ action: intent.action,
403
+ })
404
+ }
405
+
406
+ function startDragEntry(
407
+ source: {
408
+ kind: 'note' | 'folder'
409
+ tocLineIndex: number
410
+ noteIndex?: string
411
+ },
412
+ rowEl: HTMLElement,
413
+ event: PointerEvent,
414
+ ) {
415
+ if (!options.isDev.value) return
416
+
417
+ event.preventDefault()
418
+ event.stopPropagation()
419
+
420
+ const sourceItem =
421
+ source.noteIndex !== undefined
422
+ ? findTreeItem(options.items.value, source.noteIndex)
423
+ : findTreeItemByTocLineIndex(options.items.value, source.tocLineIndex)
424
+
425
+ const nodeId = sourceItem
426
+ ? (sourceItem.nodeId ?? computeSidebarNodeId(sourceItem))
427
+ : `line:${source.tocLineIndex}`
428
+
429
+ dragReorderMeta = {
430
+ sourceCollapseKey: sourceItem
431
+ ? options.resolveCollapseKey(sourceItem)
432
+ : null,
433
+ wasExpanded: sourceItem ? !sourceItem.collapsed : false,
434
+ targetNodeId: null,
435
+ action: null,
436
+ }
437
+
438
+ baseSnapshot = cloneSidebarTree(options.items.value)
439
+ dragSource = { ...source, nodeId }
440
+ draggingNodeId.value = nodeId
441
+ previewItems.value = buildSidebarDragPreviewTree(
442
+ baseSnapshot,
443
+ dragSource,
444
+ null,
445
+ )
446
+
447
+ dragSourceRow = rowEl
448
+ activePointerId = event.pointerId
449
+ rowEl.setPointerCapture(event.pointerId)
450
+ document.body.classList.add('sidebar-drag-active')
451
+
452
+ const rect = rowEl.getBoundingClientRect()
453
+ pointerOffsetX = event.clientX - rect.left
454
+ pointerOffsetY = event.clientY - rect.top
455
+
456
+ ghostEl = createCollapsedGhost(rowEl, rect)
457
+ ghostEl.style.left = `${rect.left}px`
458
+ ghostEl.style.top = `${rect.top}px`
459
+ document.body.appendChild(ghostEl)
460
+
461
+ window.addEventListener('pointermove', onPointerMove)
462
+ window.addEventListener('pointerup', onPointerUp)
463
+ window.addEventListener('pointercancel', onPointerUp)
464
+ }
465
+
466
+ function startDrag(
467
+ noteIndex: string,
468
+ tocLineIndex: number,
469
+ rowEl: HTMLElement,
470
+ event: PointerEvent,
471
+ ) {
472
+ startDragEntry({ kind: 'note', tocLineIndex, noteIndex }, rowEl, event)
473
+ }
474
+
475
+ function startFolderDrag(
476
+ tocLineIndex: number,
477
+ rowEl: HTMLElement,
478
+ event: PointerEvent,
479
+ ) {
480
+ startDragEntry({ kind: 'folder', tocLineIndex }, rowEl, event)
481
+ }
482
+
483
+ onBeforeUnmount(() => {
484
+ cleanupDrag()
485
+ })
486
+
487
+ const context: SidebarDragContext = {
488
+ isDragging,
489
+ draggingNodeId,
490
+ previewItems,
491
+ dropIndicator,
492
+ dropSlot,
493
+ startDrag,
494
+ startFolderDrag,
495
+ }
496
+
497
+ provide(SIDEBAR_DRAG_KEY, context)
498
+
499
+ return context
500
+ }
501
+
502
+ export function useSidebarDragContext(): SidebarDragContext | null {
503
+ return inject(SIDEBAR_DRAG_KEY, null)
504
+ }
505
+
506
+ export function getDropRailLeft(depth: number, indentSize: number): number {
507
+ return getDepthContentLeft(depth, indentSize)
508
+ }
509
+
510
+ export type { YuqueReorderPayload }
511
+
512
+ export function extractNoteIdFromLink(link?: string): string | null {
513
+ return extractNoteIndexFromLink(link)
514
+ }
@@ -13,6 +13,7 @@ const SIDEBAR_MAX_WIDTH = 480
13
13
  const SIDEBAR_DEFAULT_WIDTH = 260
14
14
 
15
15
  const hidden = ref(false)
16
+ const autoHidden = ref(false)
16
17
  const width = ref(SIDEBAR_DEFAULT_WIDTH)
17
18
  let initialized = false
18
19
 
@@ -38,17 +39,21 @@ function readStoredWidth(): number {
38
39
  return clampSidebarWidth(Number(savedWidth))
39
40
  }
40
41
 
42
+ function isSidebarLayoutCollapsed(): boolean {
43
+ return hidden.value || autoHidden.value
44
+ }
45
+
41
46
  function applyHiddenState(nextHidden: boolean) {
42
47
  if (!canUseDOM()) return
43
48
 
44
49
  document.documentElement.classList.toggle('hide-sidebar', nextHidden)
45
50
  }
46
51
 
47
- function applySidebarWidth(nextWidth: number, nextHidden = hidden.value) {
52
+ function applySidebarWidth(nextWidth: number, layoutCollapsed = isSidebarLayoutCollapsed()) {
48
53
  if (!canUseDOM()) return
49
54
 
50
55
  const widthValue = `${clampSidebarWidth(nextWidth)}px`
51
- const layoutWidthValue = nextHidden ? '0px' : widthValue
56
+ const layoutWidthValue = layoutCollapsed ? '0px' : widthValue
52
57
  document.documentElement.style.setProperty('--tn-sidebar-width', widthValue)
53
58
  document.documentElement.style.setProperty(
54
59
  '--tn-sidebar-layout-width',
@@ -79,6 +84,11 @@ function persistSidebarWidth(nextWidth: number) {
79
84
  } catch {}
80
85
  }
81
86
 
87
+ function applySidebarLayout() {
88
+ applyHiddenState(hidden.value)
89
+ applySidebarWidth(width.value, isSidebarLayoutCollapsed())
90
+ }
91
+
82
92
  function initSidebarLayout() {
83
93
  if (!canUseDOM()) return
84
94
 
@@ -88,15 +98,19 @@ function initSidebarLayout() {
88
98
  initialized = true
89
99
  }
90
100
 
91
- applyHiddenState(hidden.value)
92
- applySidebarWidth(width.value, hidden.value)
101
+ applySidebarLayout()
102
+ }
103
+
104
+ function setSidebarAutoHidden(nextAutoHidden: boolean) {
105
+ autoHidden.value = nextAutoHidden
106
+ applySidebarWidth(width.value, isSidebarLayoutCollapsed())
93
107
  }
94
108
 
95
109
  function setSidebarHidden(nextHidden: boolean) {
96
110
  hidden.value = nextHidden
97
- applyHiddenState(hidden.value)
98
- applySidebarWidth(width.value, hidden.value)
99
- persistHiddenState(hidden.value)
111
+ applyHiddenState(nextHidden)
112
+ applySidebarWidth(width.value, isSidebarLayoutCollapsed())
113
+ persistHiddenState(nextHidden)
100
114
  }
101
115
 
102
116
  function toggleSidebar() {
@@ -105,7 +119,7 @@ function toggleSidebar() {
105
119
 
106
120
  function setSidebarWidth(nextWidth: number) {
107
121
  width.value = clampSidebarWidth(nextWidth)
108
- applySidebarWidth(width.value)
122
+ applySidebarWidth(width.value, isSidebarLayoutCollapsed())
109
123
  }
110
124
 
111
125
  function saveSidebarWidth() {
@@ -115,14 +129,17 @@ function saveSidebarWidth() {
115
129
  export function useSidebarLayout() {
116
130
  return {
117
131
  hidden,
132
+ autoHidden,
118
133
  width,
119
134
  minWidth: SIDEBAR_MIN_WIDTH,
120
135
  maxWidth: SIDEBAR_MAX_WIDTH,
121
136
  defaultWidth: SIDEBAR_DEFAULT_WIDTH,
122
137
  initSidebarLayout,
138
+ setSidebarAutoHidden,
123
139
  setSidebarHidden,
124
140
  toggleSidebar,
125
141
  setSidebarWidth,
126
142
  saveSidebarWidth,
143
+ isSidebarLayoutCollapsed,
127
144
  }
128
145
  }
@@ -8,6 +8,10 @@ import { useData } from 'vitepress'
8
8
  import { ref } from 'vue'
9
9
 
10
10
  import { NOTES_DIR_KEY } from '../../constants'
11
+ import {
12
+ resolveNoteReadmePath,
13
+ toVscodeFileUrl,
14
+ } from '../../utils/vscodePaths'
11
15
 
12
16
  import type { Router } from 'vitepress'
13
17
  import type { Ref, ComputedRef } from 'vue'
@@ -24,9 +28,10 @@ export function useVSCodeIntegration() {
24
28
  const updateVscodeNoteDir = () => {
25
29
  if (typeof window !== 'undefined') {
26
30
  const notesDir = localStorage.getItem(NOTES_DIR_KEY)
27
- vscodeNotesDir.value = notesDir
28
- ? `vscode://file/${notesDir}/${vpData.page.value.relativePath}`
29
- : ''
31
+ const notePath = notesDir
32
+ ? resolveNoteReadmePath(notesDir, vpData.page.value.relativePath)
33
+ : null
34
+ vscodeNotesDir.value = notePath ? toVscodeFileUrl(notePath) : ''
30
35
  }
31
36
  }
32
37