@tnotesjs/core 0.1.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.
Potentially problematic release.
This version of @tnotesjs/core might be problematic. Click here for more details.
- package/README.md +105 -0
- package/dist/chunk-K3X5OP3N.js +1532 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +4199 -0
- package/dist/index.d.ts +138 -0
- package/dist/index.js +9 -0
- package/package.json +74 -0
- package/types/config.ts +61 -0
- package/types/index.ts +11 -0
- package/types/note.ts +33 -0
- package/vitepress/assets/icons/icon__check.svg +3 -0
- package/vitepress/assets/icons/icon__clipboard.svg +8 -0
- package/vitepress/assets/icons/icon__close.svg +1 -0
- package/vitepress/assets/icons/icon__collapse.svg +1 -0
- package/vitepress/assets/icons/icon__confirm.svg +1 -0
- package/vitepress/assets/icons/icon__copy.svg +4 -0
- package/vitepress/assets/icons/icon__focus.svg +1 -0
- package/vitepress/assets/icons/icon__fold.svg +3 -0
- package/vitepress/assets/icons/icon__folder.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen.svg +1 -0
- package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -0
- package/vitepress/assets/icons/icon__github.svg +4 -0
- package/vitepress/assets/icons/icon__mindmap.svg +1 -0
- package/vitepress/assets/icons/icon__next.svg +1 -0
- package/vitepress/assets/icons/icon__number_gray.svg +1 -0
- package/vitepress/assets/icons/icon__number_purple.svg +1 -0
- package/vitepress/assets/icons/icon__prev.svg +1 -0
- package/vitepress/assets/icons/icon__restore.svg +1 -0
- package/vitepress/assets/icons/icon__rotate.svg +4 -0
- package/vitepress/assets/icons/icon__search.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -0
- package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -0
- package/vitepress/assets/icons/icon__totop.svg +6 -0
- package/vitepress/assets/icons/icon__vscode.svg +6 -0
- package/vitepress/assets/icons/icon__zoom_fit.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_in.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_out.svg +1 -0
- package/vitepress/assets/icons/icon__zoom_reset.svg +1 -0
- package/vitepress/assets/icons/index.ts +38 -0
- package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -0
- package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -0
- package/vitepress/components/CodeBlockFullscreen/index.ts +115 -0
- package/vitepress/components/CodeBlockFullscreen/styles.css +64 -0
- package/vitepress/components/Discussions/Discussions.module.scss +32 -0
- package/vitepress/components/Discussions/Discussions.vue +211 -0
- package/vitepress/components/EnWordList/EnWordList.module.scss +124 -0
- package/vitepress/components/EnWordList/EnWordList.vue +543 -0
- package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -0
- package/vitepress/components/EnWordList/RightClickMenu.vue +66 -0
- package/vitepress/components/Footprints/Footprints.module.scss +93 -0
- package/vitepress/components/Footprints/Footprints.vue +377 -0
- package/vitepress/components/Layout/AboutModal.module.scss +233 -0
- package/vitepress/components/Layout/AboutModal.vue +105 -0
- package/vitepress/components/Layout/AboutPanel.vue +266 -0
- package/vitepress/components/Layout/ContentCollapse.vue +603 -0
- package/vitepress/components/Layout/CustomSidebar.vue +605 -0
- package/vitepress/components/Layout/DocBeforeControls.vue +139 -0
- package/vitepress/components/Layout/DocFooter.vue +225 -0
- package/vitepress/components/Layout/ImagePreview.module.scss +201 -0
- package/vitepress/components/Layout/ImagePreview.vue +281 -0
- package/vitepress/components/Layout/Layout.module.scss +661 -0
- package/vitepress/components/Layout/Layout.vue +542 -0
- package/vitepress/components/Layout/NoteStatus.vue +140 -0
- package/vitepress/components/Layout/SidebarItems.vue +263 -0
- package/vitepress/components/Layout/SidebarNavBefore.vue +92 -0
- package/vitepress/components/Layout/Swiper.vue +167 -0
- package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -0
- package/vitepress/components/Layout/ToggleFullContent.vue +34 -0
- package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -0
- package/vitepress/components/Layout/ToggleSidebar.vue +35 -0
- package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -0
- package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -0
- package/vitepress/components/Layout/composables/useNoteSave.ts +173 -0
- package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -0
- package/vitepress/components/Layout/composables/useRedirect.ts +110 -0
- package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -0
- package/vitepress/components/Layout/homeReadme.data.ts +124 -0
- package/vitepress/components/LoadingPage/LoadingPage.vue +192 -0
- package/vitepress/components/MarkMap/MarkMap.module.scss +159 -0
- package/vitepress/components/MarkMap/MarkMap.vue +404 -0
- package/vitepress/components/Mermaid/Mermaid.module.scss +275 -0
- package/vitepress/components/Mermaid/Mermaid.vue +364 -0
- package/vitepress/components/NotesTable/NotesTable.module.scss +77 -0
- package/vitepress/components/NotesTable/NotesTable.vue +98 -0
- package/vitepress/components/NotesTable/README.md +67 -0
- package/vitepress/components/Settings/Settings.module.scss +433 -0
- package/vitepress/components/Settings/Settings.vue +306 -0
- package/vitepress/components/SidebarCard/MindMapView.vue +483 -0
- package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -0
- package/vitepress/components/SidebarCard/SidebarCard.vue +948 -0
- package/vitepress/components/Tooltip/Tooltip.vue +70 -0
- package/vitepress/components/constants.ts +91 -0
- package/vitepress/components/notesConfig.data.ts +73 -0
- package/vitepress/components/sidebar.data.ts +59 -0
- package/vitepress/components/tnotes-config.data.ts +21 -0
- package/vitepress/components/utils.ts +26 -0
- package/vitepress/config/index.ts +126 -0
- package/vitepress/configs/constants.ts +26 -0
- package/vitepress/configs/head.config.ts +25 -0
- package/vitepress/configs/index.ts +9 -0
- package/vitepress/configs/markdown-it.d.ts +23 -0
- package/vitepress/configs/markdown.config.ts +366 -0
- package/vitepress/configs/theme.config.ts +108 -0
- package/vitepress/plugins/buildProgressPlugin.ts +390 -0
- package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -0
- package/vitepress/plugins/renameNotePlugin.ts +60 -0
- package/vitepress/plugins/updateConfigPlugin.ts +63 -0
- package/vitepress/theme/index.ts +95 -0
- package/vitepress/theme/styles/base.scss +50 -0
- package/vitepress/theme/styles/components/404.scss +31 -0
- package/vitepress/theme/styles/components/collapse.scss +175 -0
- package/vitepress/theme/styles/components/markmap.scss +101 -0
- package/vitepress/theme/styles/components/swiper.scss +255 -0
- package/vitepress/theme/styles/index.scss +25 -0
- package/vitepress/theme/styles/layout.scss +62 -0
- package/vitepress/theme/styles/utilities.scss +39 -0
- package/vitepress/theme/styles/vitepress-override.scss +25 -0
|
@@ -0,0 +1,605 @@
|
|
|
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
|
+
</nav>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { ref, onMounted, watch, computed } from 'vue'
|
|
19
|
+
import { useRoute, useData } from 'vitepress'
|
|
20
|
+
import SidebarItems from './SidebarItems.vue'
|
|
21
|
+
// @ts-expect-error - VitePress Data Loader
|
|
22
|
+
import { data as sidebarConfig } from '../sidebar.data'
|
|
23
|
+
// @ts-expect-error - VitePress Data Loader
|
|
24
|
+
import { data as tnotesConfig } from '../tnotes-config.data'
|
|
25
|
+
import { SIDEBAR_SHOW_NOTE_ID_KEY, SIDEBAR_MAX_DEPTH_KEY } from '../constants'
|
|
26
|
+
|
|
27
|
+
// 支持递归的侧边栏项类型
|
|
28
|
+
interface SidebarItem {
|
|
29
|
+
text: string
|
|
30
|
+
link?: string
|
|
31
|
+
items?: SidebarItem[]
|
|
32
|
+
collapsed?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const route = useRoute()
|
|
36
|
+
const { site } = useData()
|
|
37
|
+
const sidebarGroups = ref<SidebarItem[]>([])
|
|
38
|
+
const navRef = ref<HTMLElement | null>(null)
|
|
39
|
+
const currentFocusIndex = ref(0)
|
|
40
|
+
|
|
41
|
+
// 最大解析层级(默认 3 层)
|
|
42
|
+
const maxDepth = computed(() => {
|
|
43
|
+
if (typeof window === 'undefined') {
|
|
44
|
+
return tnotesConfig.sidebarMaxDepth ?? 3
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const savedMaxDepth = localStorage.getItem(SIDEBAR_MAX_DEPTH_KEY)
|
|
48
|
+
if (savedMaxDepth !== null) {
|
|
49
|
+
return parseInt(savedMaxDepth, 10)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return tnotesConfig.sidebarMaxDepth ?? 3
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
// 获取配置:是否显示笔记 ID
|
|
56
|
+
const showNoteId = computed(() => {
|
|
57
|
+
if (typeof window === 'undefined') {
|
|
58
|
+
return tnotesConfig.sidebarShowNoteId ?? false
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
|
|
62
|
+
if (savedShowNoteId !== null) {
|
|
63
|
+
return savedShowNoteId === 'true'
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return tnotesConfig.sidebarShowNoteId ?? false
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
// 获取 base 路径
|
|
70
|
+
const base = computed(() => site.value.base || '/')
|
|
71
|
+
|
|
72
|
+
// 加载 sidebar 数据
|
|
73
|
+
function loadSidebar() {
|
|
74
|
+
if (sidebarConfig && sidebarConfig['/notes/']) {
|
|
75
|
+
sidebarGroups.value = processItems(sidebarConfig['/notes/'])
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 递归处理侧边栏项,添加 collapsed 状态
|
|
80
|
+
function processItems(items: any[]): SidebarItem[] {
|
|
81
|
+
return items.map((item) => ({
|
|
82
|
+
...item,
|
|
83
|
+
collapsed: item.collapsed ?? true,
|
|
84
|
+
items: item.items ? processItems(item.items) : undefined,
|
|
85
|
+
}))
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 判断项是否有子项
|
|
89
|
+
function hasChildren(item: SidebarItem): boolean {
|
|
90
|
+
return !!(item.items && item.items.length > 0)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 获取项的唯一 key
|
|
94
|
+
function getItemKey(item: SidebarItem): string {
|
|
95
|
+
return item.link || item.text
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 切换项的展开/折叠状态(支持递归)
|
|
99
|
+
function toggleItem(item: SidebarItem) {
|
|
100
|
+
item.collapsed = !item.collapsed
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 递归查找并切换项
|
|
104
|
+
function toggleItemRecursive(items: SidebarItem[], text: string): boolean {
|
|
105
|
+
for (const item of items) {
|
|
106
|
+
if (item.text === text) {
|
|
107
|
+
item.collapsed = !item.collapsed
|
|
108
|
+
return true
|
|
109
|
+
}
|
|
110
|
+
if (item.items && toggleItemRecursive(item.items, text)) {
|
|
111
|
+
return true
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 切换组展开/折叠(保留兼容性)
|
|
118
|
+
function toggleGroup(groupText: string) {
|
|
119
|
+
toggleItemRecursive(sidebarGroups.value, groupText)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 递归展开/折叠所有项
|
|
123
|
+
function setAllCollapsed(items: SidebarItem[], collapsed: boolean) {
|
|
124
|
+
items.forEach((item) => {
|
|
125
|
+
if (item.items) {
|
|
126
|
+
item.collapsed = collapsed
|
|
127
|
+
setAllCollapsed(item.items, collapsed)
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// 检查是否有任何一级章节处于展开状态
|
|
133
|
+
function hasAnyFirstLevelExpanded(): boolean {
|
|
134
|
+
return sidebarGroups.value.some((group) => !group.collapsed)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 展开全部
|
|
138
|
+
function expandAll() {
|
|
139
|
+
setAllCollapsed(sidebarGroups.value, false)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// 折叠全部
|
|
143
|
+
function collapseAll() {
|
|
144
|
+
setAllCollapsed(sidebarGroups.value, true)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 智能切换:如果有展开的一级章节则折叠全部,否则展开全部
|
|
148
|
+
function toggleExpandCollapse() {
|
|
149
|
+
if (hasAnyFirstLevelExpanded()) {
|
|
150
|
+
collapseAll()
|
|
151
|
+
} else {
|
|
152
|
+
expandAll()
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 获取当前笔记的所有出现位置
|
|
157
|
+
function getCurrentNotePositions(): HTMLElement[] {
|
|
158
|
+
const currentPath = route.path
|
|
159
|
+
const elements: HTMLElement[] = []
|
|
160
|
+
|
|
161
|
+
if (!navRef.value) {
|
|
162
|
+
console.log('❌ [getCurrentNotePositions] navRef is null')
|
|
163
|
+
return elements
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log('🔍 [getCurrentNotePositions] Current route path:', currentPath)
|
|
167
|
+
|
|
168
|
+
// 查找所有激活的笔记项
|
|
169
|
+
const activeItems = navRef.value.querySelectorAll('.nav-item.active')
|
|
170
|
+
console.log(
|
|
171
|
+
'🔍 [getCurrentNotePositions] Active nav-items:',
|
|
172
|
+
activeItems.length
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
activeItems.forEach((item, index) => {
|
|
176
|
+
const href = item.getAttribute('href')
|
|
177
|
+
console.log(`🔍 [${index}] Active item href:`, href)
|
|
178
|
+
elements.push(item as HTMLElement)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
console.log('🎯 [getCurrentNotePositions] Found positions:', elements.length)
|
|
182
|
+
return elements
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 展开指定元素的父级分组
|
|
186
|
+
function expandParentGroup(element: HTMLElement) {
|
|
187
|
+
console.log('📂 [expandParentGroup] Starting to expand parent groups')
|
|
188
|
+
|
|
189
|
+
// 查找所有父级 group 元素(从最近的开始)
|
|
190
|
+
let currentElement: HTMLElement | null = element
|
|
191
|
+
const groupsToExpand: string[] = []
|
|
192
|
+
|
|
193
|
+
// 向上遍历,收集所有父级 group 的标题文本
|
|
194
|
+
while (currentElement) {
|
|
195
|
+
const groupElement = currentElement.closest('.group')
|
|
196
|
+
if (!groupElement) break
|
|
197
|
+
|
|
198
|
+
const groupTitle = groupElement.querySelector('.group-title span')
|
|
199
|
+
if (groupTitle) {
|
|
200
|
+
const groupText = groupTitle.textContent?.trim()
|
|
201
|
+
if (groupText) {
|
|
202
|
+
console.log('📌 [expandParentGroup] Found parent group:', groupText)
|
|
203
|
+
groupsToExpand.push(groupText)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 继续向上查找
|
|
208
|
+
currentElement = groupElement.parentElement?.closest('.group') || null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
console.log(
|
|
212
|
+
'📋 [expandParentGroup] Groups to expand (inner to outer):',
|
|
213
|
+
groupsToExpand
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
// 从最外层开始展开,逐层向内
|
|
217
|
+
// 但是搜索时要确保在正确的上下文中搜索
|
|
218
|
+
if (groupsToExpand.length === 0) return
|
|
219
|
+
|
|
220
|
+
// 反转数组,从最外层开始处理
|
|
221
|
+
const outerToInner = [...groupsToExpand].reverse()
|
|
222
|
+
console.log(
|
|
223
|
+
'📋 [expandParentGroup] Processing order (outer to inner):',
|
|
224
|
+
outerToInner
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
// 第一层必须从根开始搜索
|
|
228
|
+
let currentContext: SidebarItem[] | null = null
|
|
229
|
+
|
|
230
|
+
for (let i = 0; i < outerToInner.length; i++) {
|
|
231
|
+
const groupText = outerToInner[i]
|
|
232
|
+
console.log(`🔄 [expandParentGroup] [${i}] Expanding: "${groupText}"`)
|
|
233
|
+
|
|
234
|
+
if (i === 0) {
|
|
235
|
+
// 第一层从根搜索
|
|
236
|
+
console.log(` 🌳 Searching from root`)
|
|
237
|
+
const found = expandGroupRecursive(sidebarGroups.value, groupText)
|
|
238
|
+
if (found) {
|
|
239
|
+
// 找到后,获取这个分组的 items 作为下一层的搜索上下文
|
|
240
|
+
const foundGroup = findGroupByText(sidebarGroups.value, groupText)
|
|
241
|
+
if (foundGroup?.items) {
|
|
242
|
+
currentContext = foundGroup.items
|
|
243
|
+
console.log(
|
|
244
|
+
` ✅ Found and set context for next level (${foundGroup.items.length} items)`
|
|
245
|
+
)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} else {
|
|
249
|
+
// 后续层从上一层的上下文中搜索
|
|
250
|
+
if (currentContext) {
|
|
251
|
+
console.log(
|
|
252
|
+
` 🔍 Searching in context (${currentContext.length} items)`
|
|
253
|
+
)
|
|
254
|
+
const found = expandGroupRecursive(currentContext, groupText)
|
|
255
|
+
if (found) {
|
|
256
|
+
const foundGroup = findGroupByText(currentContext, groupText)
|
|
257
|
+
if (foundGroup?.items) {
|
|
258
|
+
currentContext = foundGroup.items
|
|
259
|
+
console.log(
|
|
260
|
+
` ✅ Found and set context for next level (${foundGroup.items.length} items)`
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 查找分组(不展开,只返回引用)
|
|
270
|
+
function findGroupByText(
|
|
271
|
+
items: SidebarItem[],
|
|
272
|
+
targetText: string
|
|
273
|
+
): SidebarItem | null {
|
|
274
|
+
for (const item of items) {
|
|
275
|
+
if (item.text === targetText) {
|
|
276
|
+
return item
|
|
277
|
+
}
|
|
278
|
+
if (item.items) {
|
|
279
|
+
const found = findGroupByText(item.items, targetText)
|
|
280
|
+
if (found) return found
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return null
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 递归查找并展开分组
|
|
287
|
+
function expandGroupRecursive(
|
|
288
|
+
items: SidebarItem[],
|
|
289
|
+
targetText: string,
|
|
290
|
+
depth: number = 0
|
|
291
|
+
): boolean {
|
|
292
|
+
const indent = ' '.repeat(depth)
|
|
293
|
+
console.log(
|
|
294
|
+
`${indent}🔍 [expandGroupRecursive] Searching for "${targetText}" at depth ${depth}`
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
for (const item of items) {
|
|
298
|
+
console.log(`${indent} 📝 Checking item: "${item.text}"`)
|
|
299
|
+
|
|
300
|
+
if (item.text === targetText) {
|
|
301
|
+
console.log(`${indent} ✅ Found target! Setting collapsed = false`)
|
|
302
|
+
item.collapsed = false
|
|
303
|
+
return true
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (item.items) {
|
|
307
|
+
console.log(
|
|
308
|
+
`${indent} 📂 Item has ${item.items.length} children, searching...`
|
|
309
|
+
)
|
|
310
|
+
const found = expandGroupRecursive(item.items, targetText, depth + 1)
|
|
311
|
+
if (found) {
|
|
312
|
+
console.log(
|
|
313
|
+
`${indent} ✅ Target found in children, expanding current item "${item.text}"`
|
|
314
|
+
)
|
|
315
|
+
// 如果在子项中找到了,也展开当前项
|
|
316
|
+
item.collapsed = false
|
|
317
|
+
return true
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
console.log(
|
|
323
|
+
`${indent}❌ [expandGroupRecursive] Target "${targetText}" not found at depth ${depth}`
|
|
324
|
+
)
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// 滚动到指定元素
|
|
329
|
+
function scrollToElement(element: HTMLElement) {
|
|
330
|
+
if (!element || !navRef.value) {
|
|
331
|
+
// console.log('❌ [scrollToElement] No element or navRef')
|
|
332
|
+
return
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const navContainer = navRef.value.closest('.VPSidebar')
|
|
336
|
+
if (!navContainer) {
|
|
337
|
+
// console.log('❌ [scrollToElement] No VPSidebar container found')
|
|
338
|
+
return
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// console.log('📍 [scrollToElement] Scrolling to element')
|
|
342
|
+
|
|
343
|
+
// 计算元素相对于容器的位置
|
|
344
|
+
const elementRect = element.getBoundingClientRect()
|
|
345
|
+
const containerRect = navContainer.getBoundingClientRect()
|
|
346
|
+
|
|
347
|
+
// 计算需要滚动的距离(将元素放在容器中间)
|
|
348
|
+
const scrollTop =
|
|
349
|
+
navContainer.scrollTop +
|
|
350
|
+
elementRect.top -
|
|
351
|
+
containerRect.top -
|
|
352
|
+
containerRect.height / 2 +
|
|
353
|
+
elementRect.height / 2
|
|
354
|
+
|
|
355
|
+
navContainer.scrollTo({
|
|
356
|
+
top: scrollTop,
|
|
357
|
+
behavior: 'smooth',
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
// 添加临时高亮动画
|
|
361
|
+
element.classList.add('focus-highlight')
|
|
362
|
+
setTimeout(() => {
|
|
363
|
+
element.classList.remove('focus-highlight')
|
|
364
|
+
}, 1000)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// 聚焦到当前笔记(支持多个位置切换)
|
|
368
|
+
function focusCurrentNote() {
|
|
369
|
+
console.log('🎯 [focusCurrentNote] Called')
|
|
370
|
+
const positions = getCurrentNotePositions()
|
|
371
|
+
|
|
372
|
+
if (positions.length === 0) {
|
|
373
|
+
console.log('❌ [focusCurrentNote] No positions found')
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 循环切换聚焦位置
|
|
378
|
+
currentFocusIndex.value = (currentFocusIndex.value + 1) % positions.length
|
|
379
|
+
const targetElement = positions[currentFocusIndex.value]
|
|
380
|
+
|
|
381
|
+
console.log(
|
|
382
|
+
`🎯 [focusCurrentNote] Focusing position ${currentFocusIndex.value + 1}/${
|
|
383
|
+
positions.length
|
|
384
|
+
}`
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
// 展开该笔记所在的分组
|
|
388
|
+
expandParentGroup(targetElement)
|
|
389
|
+
|
|
390
|
+
// 滚动到该笔记
|
|
391
|
+
setTimeout(() => {
|
|
392
|
+
scrollToElement(targetElement)
|
|
393
|
+
}, 100)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 展开当前激活笔记的所有父级分组
|
|
397
|
+
function expandActiveItemParents() {
|
|
398
|
+
expandActiveItemParentsRecursive(sidebarGroups.value)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// 递归展开包含激活项的父级
|
|
402
|
+
function expandActiveItemParentsRecursive(items: SidebarItem[]): boolean {
|
|
403
|
+
let hasActive = false
|
|
404
|
+
|
|
405
|
+
for (const item of items) {
|
|
406
|
+
if (item.link) {
|
|
407
|
+
// 检查当前项是否激活
|
|
408
|
+
const fullLink = getFullLink(item.link)
|
|
409
|
+
const decodedRoutePath = decodeURIComponent(route.path)
|
|
410
|
+
const decodedFullLink = decodeURIComponent(fullLink)
|
|
411
|
+
const itemActive =
|
|
412
|
+
decodedRoutePath === decodedFullLink ||
|
|
413
|
+
decodedRoutePath === decodedFullLink + '.html'
|
|
414
|
+
|
|
415
|
+
if (itemActive) {
|
|
416
|
+
hasActive = true
|
|
417
|
+
}
|
|
418
|
+
} else if (item.items) {
|
|
419
|
+
const childHasActive = expandActiveItemParentsRecursive(item.items)
|
|
420
|
+
if (childHasActive) {
|
|
421
|
+
item.collapsed = false
|
|
422
|
+
hasActive = true
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return hasActive
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 获取完整链接(包含 base)
|
|
431
|
+
function getFullLink(link: string) {
|
|
432
|
+
const cleanLink = link.startsWith('/') ? link.slice(1) : link
|
|
433
|
+
return base.value + cleanLink
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 滚动到当前激活的笔记
|
|
437
|
+
function scrollToActiveItem() {
|
|
438
|
+
// 等待 DOM 更新
|
|
439
|
+
setTimeout(() => {
|
|
440
|
+
const positions = getCurrentNotePositions()
|
|
441
|
+
if (positions.length > 0) {
|
|
442
|
+
// 展开所有包含当前笔记的分组
|
|
443
|
+
expandActiveItemParents()
|
|
444
|
+
|
|
445
|
+
// 滚动到第一个位置
|
|
446
|
+
setTimeout(() => {
|
|
447
|
+
scrollToElement(positions[0])
|
|
448
|
+
}, 100)
|
|
449
|
+
}
|
|
450
|
+
}, 300)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// 暴露函数给父组件使用
|
|
454
|
+
defineExpose({
|
|
455
|
+
expandAll,
|
|
456
|
+
collapseAll,
|
|
457
|
+
toggleExpandCollapse,
|
|
458
|
+
hasAnyFirstLevelExpanded,
|
|
459
|
+
focusCurrentNote,
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
onMounted(() => {
|
|
463
|
+
loadSidebar()
|
|
464
|
+
|
|
465
|
+
// 调试:打印配置信息
|
|
466
|
+
// console.log('🔧 [CustomSidebar] showNoteId:', showNoteId.value)
|
|
467
|
+
// if (typeof window !== 'undefined') {
|
|
468
|
+
// console.log(
|
|
469
|
+
// '🔧 [CustomSidebar] localStorage value:',
|
|
470
|
+
// localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
|
|
471
|
+
// )
|
|
472
|
+
// }
|
|
473
|
+
// console.log(
|
|
474
|
+
// '🔧 [CustomSidebar] tnotesConfig value:',
|
|
475
|
+
// tnotesConfig.sidebarShowNoteId
|
|
476
|
+
// )
|
|
477
|
+
|
|
478
|
+
// 自动滚动到当前激活的笔记
|
|
479
|
+
scrollToActiveItem()
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// 监听 sidebarConfig 的变化(HMR 会更新这个导入的数据)
|
|
483
|
+
watch(
|
|
484
|
+
() => sidebarConfig,
|
|
485
|
+
() => {
|
|
486
|
+
// console.log('🔄 [CustomSidebar] Sidebar config changed, reloading...')
|
|
487
|
+
loadSidebar()
|
|
488
|
+
},
|
|
489
|
+
{ deep: true }
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
// 监听 tnotesConfig 的变化
|
|
493
|
+
watch(
|
|
494
|
+
() => tnotesConfig,
|
|
495
|
+
() => {
|
|
496
|
+
// console.log(
|
|
497
|
+
// '🔄 [CustomSidebar] TNotes config changed, sidebarShowNoteId:',
|
|
498
|
+
// tnotesConfig.sidebarShowNoteId
|
|
499
|
+
// )
|
|
500
|
+
},
|
|
501
|
+
{ deep: true }
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
// 监听路由变化,自动展开当前激活项所在的组并滚动
|
|
505
|
+
watch(
|
|
506
|
+
() => route.path,
|
|
507
|
+
() => {
|
|
508
|
+
// 重置聚焦索引
|
|
509
|
+
currentFocusIndex.value = 0
|
|
510
|
+
|
|
511
|
+
// 展开并滚动到当前笔记
|
|
512
|
+
expandActiveItemParents()
|
|
513
|
+
scrollToActiveItem()
|
|
514
|
+
}
|
|
515
|
+
)
|
|
516
|
+
</script>
|
|
517
|
+
|
|
518
|
+
<style scoped>
|
|
519
|
+
/* 自定义 sidebar 容器,适配 VitePress 的 sidebar-nav-before 插槽 */
|
|
520
|
+
.custom-sidebar-wrapper {
|
|
521
|
+
/* 不需要设置 position 和尺寸,因为它在 VitePress 的 sidebar 容器内 */
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
.nav {
|
|
525
|
+
font-size: 14px;
|
|
526
|
+
line-height: 2;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.group {
|
|
530
|
+
margin-bottom: 16px;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.group-title {
|
|
534
|
+
display: flex;
|
|
535
|
+
justify-content: space-between;
|
|
536
|
+
align-items: center;
|
|
537
|
+
width: 100%;
|
|
538
|
+
padding: 6px 0;
|
|
539
|
+
font-weight: 600;
|
|
540
|
+
color: var(--vp-c-text-1);
|
|
541
|
+
background: none;
|
|
542
|
+
border: none;
|
|
543
|
+
cursor: pointer;
|
|
544
|
+
text-align: left;
|
|
545
|
+
transition: color 0.25s;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.group-title:hover {
|
|
549
|
+
color: var(--vp-c-brand-1);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.arrow {
|
|
553
|
+
font-size: 10px;
|
|
554
|
+
transform: rotate(90deg);
|
|
555
|
+
transition: transform 0.25s;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
.arrow.collapsed {
|
|
559
|
+
transform: rotate(0deg);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.nav-item {
|
|
563
|
+
display: block;
|
|
564
|
+
padding: 4px;
|
|
565
|
+
color: var(--vp-c-text-2);
|
|
566
|
+
text-decoration: none;
|
|
567
|
+
border-radius: 4px;
|
|
568
|
+
font-size: 14px;
|
|
569
|
+
line-height: 24px;
|
|
570
|
+
transition: all 0.25s;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.nav-item:hover {
|
|
574
|
+
color: var(--vp-c-brand-1);
|
|
575
|
+
background-color: var(--vp-c-default-soft);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.nav-item.active {
|
|
579
|
+
color: var(--vp-c-brand-1);
|
|
580
|
+
font-weight: 600;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* 聚焦高亮动画 */
|
|
584
|
+
.nav-item.focus-highlight {
|
|
585
|
+
animation: focusPulse 1s ease-in-out;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
@keyframes focusPulse {
|
|
589
|
+
0%,
|
|
590
|
+
100% {
|
|
591
|
+
background-color: transparent;
|
|
592
|
+
}
|
|
593
|
+
50% {
|
|
594
|
+
background-color: var(--vp-c-brand-soft);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
</style>
|
|
598
|
+
|
|
599
|
+
<!-- 全局样式:隐藏 VitePress 默认的 sidebar nav -->
|
|
600
|
+
<style>
|
|
601
|
+
/* 隐藏 VitePress 默认的 sidebar 导航内容(保留容器) */
|
|
602
|
+
.VPSidebarNav {
|
|
603
|
+
display: none !important;
|
|
604
|
+
}
|
|
605
|
+
</style>
|