@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.
- package/README.md +2 -1
- package/package.json +3 -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/dist/chunk-4KG6RXFS.js +0 -4637
- package/dist/chunk-AGSL3VZE.js +0 -193
- package/dist/cli/index.js +0 -683
- package/dist/index.js +0 -8
- package/dist/vitepress/config/index.js +0 -1178
- 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
|
@@ -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,
|
|
52
|
+
function applySidebarWidth(nextWidth: number, layoutCollapsed = isSidebarLayoutCollapsed()) {
|
|
48
53
|
if (!canUseDOM()) return
|
|
49
54
|
|
|
50
55
|
const widthValue = `${clampSidebarWidth(nextWidth)}px`
|
|
51
|
-
const layoutWidthValue =
|
|
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
|
-
|
|
92
|
-
|
|
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(
|
|
98
|
-
applySidebarWidth(width.value,
|
|
99
|
-
persistHiddenState(
|
|
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
|
-
|
|
28
|
-
?
|
|
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
|
|