@tnotesjs/core 0.1.16 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tnotesjs/core",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "TNotes 知识库核心框架 —— 基于 VitePress 的笔记管理系统",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.17.1",
@@ -426,37 +426,23 @@ function toggleExpandCollapse() {
426
426
 
427
427
  // 获取当前笔记的所有出现位置
428
428
  function getCurrentNotePositions(): HTMLElement[] {
429
- const currentPath = route.path
430
429
  const elements: HTMLElement[] = []
431
430
 
432
431
  if (!navRef.value) {
433
- console.log('❌ [getCurrentNotePositions] navRef is null')
434
432
  return elements
435
433
  }
436
434
 
437
- console.log('🔍 [getCurrentNotePositions] Current route path:', currentPath)
438
-
439
- // 查找所有激活的笔记项
440
435
  const activeItems = navRef.value.querySelectorAll('.nav-item.active')
441
- console.log(
442
- '🔍 [getCurrentNotePositions] Active nav-items:',
443
- activeItems.length
444
- )
445
436
 
446
- activeItems.forEach((item, index) => {
447
- const href = item.getAttribute('href')
448
- console.log(`🔍 [${index}] Active item href:`, href)
437
+ activeItems.forEach((item) => {
449
438
  elements.push(item as HTMLElement)
450
439
  })
451
440
 
452
- console.log('🎯 [getCurrentNotePositions] Found positions:', elements.length)
453
441
  return elements
454
442
  }
455
443
 
456
444
  // 展开指定元素的父级分组
457
445
  function expandParentGroup(element: HTMLElement) {
458
- console.log('📂 [expandParentGroup] Starting to expand parent groups')
459
-
460
446
  // 查找所有父级 group 元素(从最近的开始)
461
447
  let currentElement: HTMLElement | null = element
462
448
  const groupsToExpand: string[] = []
@@ -467,11 +453,10 @@ function expandParentGroup(element: HTMLElement) {
467
453
  currentElement.closest<HTMLElement>('.group')
468
454
  if (!groupElement) break
469
455
 
470
- const groupTitle = groupElement.querySelector('.group-title span')
471
- if (groupTitle) {
472
- const groupText = groupTitle.textContent?.trim()
456
+ const groupTitleText = groupElement.querySelector('.group-title-text')
457
+ if (groupTitleText) {
458
+ const groupText = groupTitleText.textContent?.trim()
473
459
  if (groupText) {
474
- console.log('📌 [expandParentGroup] Found parent group:', groupText)
475
460
  groupsToExpand.push(groupText)
476
461
  }
477
462
  }
@@ -481,57 +466,37 @@ function expandParentGroup(element: HTMLElement) {
481
466
  groupElement.parentElement?.closest<HTMLElement>('.group') || null
482
467
  }
483
468
 
484
- console.log(
485
- '📋 [expandParentGroup] Groups to expand (inner to outer):',
486
- groupsToExpand
487
- )
488
-
489
469
  // 从最外层开始展开,逐层向内
490
470
  // 但是搜索时要确保在正确的上下文中搜索
491
471
  if (groupsToExpand.length === 0) return
492
472
 
493
473
  // 反转数组,从最外层开始处理
494
474
  const outerToInner = [...groupsToExpand].reverse()
495
- console.log(
496
- '📋 [expandParentGroup] Processing order (outer to inner):',
497
- outerToInner
498
- )
499
475
 
500
476
  // 第一层必须从根开始搜索
501
477
  let currentContext: SidebarItem[] | null = null
502
478
 
503
479
  for (let i = 0; i < outerToInner.length; i++) {
504
480
  const groupText = outerToInner[i]
505
- console.log(`🔄 [expandParentGroup] [${i}] Expanding: "${groupText}"`)
506
481
 
507
482
  if (i === 0) {
508
483
  // 第一层从根搜索
509
- console.log(` 🌳 Searching from root`)
510
484
  const found = expandGroupRecursive(sidebarGroups.value, groupText)
511
485
  if (found) {
512
486
  // 找到后,获取这个分组的 items 作为下一层的搜索上下文
513
487
  const foundGroup = findGroupByText(sidebarGroups.value, groupText)
514
488
  if (foundGroup?.items) {
515
489
  currentContext = foundGroup.items
516
- console.log(
517
- ` ✅ Found and set context for next level (${foundGroup.items.length} items)`
518
- )
519
490
  }
520
491
  }
521
492
  } else {
522
493
  // 后续层从上一层的上下文中搜索
523
494
  if (currentContext) {
524
- console.log(
525
- ` 🔍 Searching in context (${currentContext.length} items)`
526
- )
527
495
  const found = expandGroupRecursive(currentContext, groupText)
528
496
  if (found) {
529
497
  const foundGroup = findGroupByText(currentContext, groupText)
530
498
  if (foundGroup?.items) {
531
499
  currentContext = foundGroup.items
532
- console.log(
533
- ` ✅ Found and set context for next level (${foundGroup.items.length} items)`
534
- )
535
500
  }
536
501
  }
537
502
  }
@@ -560,31 +525,16 @@ function findGroupByText(
560
525
  function expandGroupRecursive(
561
526
  items: SidebarItem[],
562
527
  targetText: string,
563
- depth: number = 0
564
528
  ): boolean {
565
- const indent = ' '.repeat(depth)
566
- console.log(
567
- `${indent}🔍 [expandGroupRecursive] Searching for "${targetText}" at depth ${depth}`
568
- )
569
-
570
529
  for (const item of items) {
571
- console.log(`${indent} 📝 Checking item: "${item.text}"`)
572
-
573
530
  if (item.text === targetText) {
574
- console.log(`${indent} ✅ Found target! Setting collapsed = false`)
575
531
  item.collapsed = false
576
532
  return true
577
533
  }
578
534
 
579
535
  if (item.items) {
580
- console.log(
581
- `${indent} 📂 Item has ${item.items.length} children, searching...`
582
- )
583
- const found = expandGroupRecursive(item.items, targetText, depth + 1)
536
+ const found = expandGroupRecursive(item.items, targetText)
584
537
  if (found) {
585
- console.log(
586
- `${indent} ✅ Target found in children, expanding current item "${item.text}"`
587
- )
588
538
  // 如果在子项中找到了,也展开当前项
589
539
  item.collapsed = false
590
540
  return true
@@ -592,9 +542,6 @@ function expandGroupRecursive(
592
542
  }
593
543
  }
594
544
 
595
- console.log(
596
- `${indent}❌ [expandGroupRecursive] Target "${targetText}" not found at depth ${depth}`
597
- )
598
545
  return false
599
546
  }
600
547
 
@@ -639,11 +586,9 @@ function scrollToElement(element: HTMLElement) {
639
586
 
640
587
  // 聚焦到当前笔记(支持多个位置切换)
641
588
  function focusCurrentNote() {
642
- console.log('🎯 [focusCurrentNote] Called')
643
589
  const positions = getCurrentNotePositions()
644
590
 
645
591
  if (positions.length === 0) {
646
- console.log('❌ [focusCurrentNote] No positions found')
647
592
  return
648
593
  }
649
594
 
@@ -651,12 +596,6 @@ function focusCurrentNote() {
651
596
  currentFocusIndex.value = (currentFocusIndex.value + 1) % positions.length
652
597
  const targetElement = positions[currentFocusIndex.value]
653
598
 
654
- console.log(
655
- `🎯 [focusCurrentNote] Focusing position ${currentFocusIndex.value + 1}/${
656
- positions.length
657
- }`
658
- )
659
-
660
599
  // 展开该笔记所在的分组
661
600
  expandParentGroup(targetElement)
662
601
 
@@ -187,6 +187,8 @@
187
187
  <!-- <template #nav-screen-content-after>nav-screen-content-after</template> -->
188
188
  </Layout>
189
189
 
190
+ <SidebarResizeHandle />
191
+
190
192
  <!-- 全局重命名遮罩:关于面板保存或文件系统重命名时由 useRenameOverlay 控制 -->
191
193
  <LoadingPage
192
194
  :visible="renameOverlayState.visible"
@@ -226,6 +228,7 @@ import ImagePreview from "./ImagePreview.vue";
226
228
  import NavBarSettingsTrigger from "./NavBarSettingsTrigger.vue";
227
229
  import NoteStatus from "./NoteStatus.vue";
228
230
  import SidebarNavBefore from "./SidebarNavBefore.vue";
231
+ import SidebarResizeHandle from "./SidebarResizeHandle.vue";
229
232
  import Swiper from "./Swiper.vue";
230
233
  import SettingsDialog from "../Settings/SettingsDialog.vue";
231
234
 
@@ -0,0 +1,319 @@
1
+ <template>
2
+ <div
3
+ class="sidebar-resize-handle"
4
+ :class="{
5
+ 'is-hidden': hidden,
6
+ 'is-dragging': isDragging,
7
+ 'is-fullscreen': isContentFullscreen,
8
+ }"
9
+ >
10
+ <div
11
+ v-if="!hidden"
12
+ class="resize-hotspot"
13
+ :aria-label="`拖动调整侧边栏宽度,当前 ${width}px`"
14
+ role="separator"
15
+ aria-orientation="vertical"
16
+ :aria-valuemin="minWidth"
17
+ :aria-valuemax="maxWidth"
18
+ :aria-valuenow="width"
19
+ @mousedown.prevent="startResize"
20
+ ></div>
21
+
22
+ <div v-if="!hidden" class="resize-indicator" aria-hidden="true"></div>
23
+
24
+ <button
25
+ class="sidebar-edge-toggle"
26
+ type="button"
27
+ :aria-label="toggleTitle"
28
+ :aria-pressed="hidden"
29
+ @click.stop="toggleSidebar"
30
+ >
31
+ <img :src="toggleIcon" alt="" />
32
+ <span class="sidebar-edge-tooltip" role="tooltip">
33
+ <span>{{ toggleActionText }}</span>
34
+ <kbd>{{ shortcutText }}</kbd>
35
+ </span>
36
+ </button>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
42
+
43
+ import { icon__next, icon__prev } from '../../assets/icons'
44
+ import { useSidebarLayout } from './composables/useSidebarLayout'
45
+
46
+ const {
47
+ hidden,
48
+ width,
49
+ minWidth,
50
+ maxWidth,
51
+ initSidebarLayout,
52
+ toggleSidebar,
53
+ setSidebarWidth,
54
+ saveSidebarWidth,
55
+ } = useSidebarLayout()
56
+
57
+ const isDragging = ref(false)
58
+ const isContentFullscreen = ref(false)
59
+ const shortcutText = ref('Ctrl + Alt + ,')
60
+ let fullscreenObserver: MutationObserver | null = null
61
+
62
+ const toggleIcon = computed(() => (hidden.value ? icon__next : icon__prev))
63
+ const toggleActionText = computed(() =>
64
+ hidden.value ? '展开侧边栏' : '收起侧边栏',
65
+ )
66
+ const toggleTitle = computed(
67
+ () => `${toggleActionText.value}\n${shortcutText.value}`,
68
+ )
69
+
70
+ onMounted(() => {
71
+ initSidebarLayout()
72
+ shortcutText.value = getShortcutText()
73
+ updateContentFullscreen()
74
+ fullscreenObserver = new MutationObserver(updateContentFullscreen)
75
+ fullscreenObserver.observe(document.documentElement, {
76
+ attributes: true,
77
+ attributeFilter: ['class'],
78
+ })
79
+ window.addEventListener('keydown', handleShortcut)
80
+ })
81
+
82
+ onBeforeUnmount(() => {
83
+ window.removeEventListener('keydown', handleShortcut)
84
+ fullscreenObserver?.disconnect()
85
+ fullscreenObserver = null
86
+ stopResize()
87
+ })
88
+
89
+ function startResize(event: MouseEvent) {
90
+ if (hidden.value || event.button !== 0) return
91
+
92
+ isDragging.value = true
93
+ document.body.classList.add('is-sidebar-resizing')
94
+ window.addEventListener('mousemove', resize)
95
+ window.addEventListener('mouseup', stopResize)
96
+ setSidebarWidth(event.clientX)
97
+ }
98
+
99
+ function resize(event: MouseEvent) {
100
+ if (!isDragging.value) return
101
+
102
+ setSidebarWidth(event.clientX)
103
+ }
104
+
105
+ function stopResize() {
106
+ if (!isDragging.value) return
107
+
108
+ isDragging.value = false
109
+ document.body.classList.remove('is-sidebar-resizing')
110
+ window.removeEventListener('mousemove', resize)
111
+ window.removeEventListener('mouseup', stopResize)
112
+ saveSidebarWidth()
113
+ }
114
+
115
+ function handleShortcut(event: KeyboardEvent) {
116
+ if (!isToggleShortcut(event) || isEditableTarget(event.target)) return
117
+
118
+ event.preventDefault()
119
+ toggleSidebar()
120
+ }
121
+
122
+ function updateContentFullscreen() {
123
+ isContentFullscreen.value =
124
+ document.documentElement.classList.contains('content-fullscreen')
125
+ }
126
+
127
+ function isToggleShortcut(event: KeyboardEvent): boolean {
128
+ return (
129
+ (event.ctrlKey || event.metaKey) && event.altKey && event.code === 'Comma'
130
+ )
131
+ }
132
+
133
+ function getShortcutText(): string {
134
+ return isMacPlatform() ? 'Cmd + Option + ,' : 'Ctrl + Alt + ,'
135
+ }
136
+
137
+ function isMacPlatform(): boolean {
138
+ if (typeof navigator === 'undefined') return false
139
+
140
+ return /Mac|iPhone|iPad|iPod/i.test(navigator.platform)
141
+ }
142
+
143
+ function isEditableTarget(target: EventTarget | null): boolean {
144
+ if (!(target instanceof HTMLElement)) return false
145
+
146
+ const tagName = target.tagName.toLowerCase()
147
+ if (tagName === 'input' || tagName === 'textarea' || tagName === 'select') {
148
+ return true
149
+ }
150
+
151
+ return target.isContentEditable || !!target.closest('[contenteditable="true"]')
152
+ }
153
+ </script>
154
+
155
+ <style scoped>
156
+ .sidebar-resize-handle {
157
+ position: fixed;
158
+ top: 0;
159
+ bottom: 0;
160
+ left: calc(var(--tn-sidebar-width, 260px) - 5px);
161
+ z-index: 30;
162
+ width: 10px;
163
+ cursor: col-resize;
164
+ }
165
+
166
+ .sidebar-resize-handle::before {
167
+ position: absolute;
168
+ top: var(--vp-nav-height);
169
+ bottom: 0;
170
+ left: 4px;
171
+ width: 2px;
172
+ background: var(--vp-c-brand-1);
173
+ border-radius: 999px;
174
+ box-shadow: 0 0 0 1px var(--vp-c-brand-soft);
175
+ content: '';
176
+ opacity: 0;
177
+ pointer-events: none;
178
+ transition: opacity 0.18s ease;
179
+ }
180
+
181
+ .resize-hotspot {
182
+ position: absolute;
183
+ top: var(--vp-nav-height);
184
+ right: 0;
185
+ bottom: 0;
186
+ left: 0;
187
+ }
188
+
189
+ .resize-indicator {
190
+ position: absolute;
191
+ top: 50%;
192
+ left: 50%;
193
+ width: 4px;
194
+ height: 28px;
195
+ border-right: 1px solid var(--vp-c-brand-1);
196
+ border-left: 1px solid var(--vp-c-brand-1);
197
+ opacity: 0;
198
+ pointer-events: none;
199
+ transform: translate(-50%, -50%);
200
+ transition: opacity 0.18s ease;
201
+ }
202
+
203
+ .sidebar-edge-toggle {
204
+ position: absolute;
205
+ top: 220px;
206
+ left: -2px;
207
+ display: inline-flex;
208
+ align-items: center;
209
+ justify-content: center;
210
+ width: 14px;
211
+ height: 44px;
212
+ padding: 0;
213
+ color: var(--vp-c-text-2);
214
+ background: var(--vp-c-bg);
215
+ border: 1px solid var(--vp-c-divider);
216
+ border-radius: 7px;
217
+ box-shadow: var(--vp-shadow-2);
218
+ cursor: pointer;
219
+ opacity: 0;
220
+ pointer-events: none;
221
+ transition:
222
+ opacity 0.18s ease,
223
+ border-color 0.18s ease,
224
+ background-color 0.18s ease;
225
+ }
226
+
227
+ .sidebar-edge-toggle img {
228
+ width: 10px;
229
+ height: 10px;
230
+ }
231
+
232
+ .sidebar-edge-tooltip {
233
+ position: absolute;
234
+ top: 50%;
235
+ left: calc(100% + 8px);
236
+ display: flex;
237
+ flex-direction: column;
238
+ gap: 2px;
239
+ min-width: 128px;
240
+ padding: 7px 9px;
241
+ color: var(--vp-c-text-1);
242
+ background: var(--vp-c-bg-elv);
243
+ border: 1px solid var(--vp-c-divider);
244
+ border-radius: 6px;
245
+ box-shadow: var(--vp-shadow-2);
246
+ font-size: 12px;
247
+ line-height: 18px;
248
+ opacity: 0;
249
+ pointer-events: none;
250
+ text-align: left;
251
+ transform: translate(2px, -50%);
252
+ transition:
253
+ opacity 0.16s ease,
254
+ transform 0.16s ease;
255
+ white-space: nowrap;
256
+ }
257
+
258
+ .sidebar-edge-tooltip kbd {
259
+ color: var(--vp-c-text-2);
260
+ font-family: var(--vp-font-family-mono);
261
+ font-size: 11px;
262
+ }
263
+
264
+ .sidebar-edge-toggle:hover .sidebar-edge-tooltip,
265
+ .sidebar-edge-toggle:focus-visible .sidebar-edge-tooltip {
266
+ opacity: 1;
267
+ transform: translate(0, -50%);
268
+ }
269
+
270
+ .sidebar-resize-handle:hover::before,
271
+ .sidebar-resize-handle.is-dragging::before,
272
+ .sidebar-resize-handle:hover .resize-indicator,
273
+ .sidebar-resize-handle.is-dragging .resize-indicator,
274
+ .sidebar-resize-handle:hover .sidebar-edge-toggle,
275
+ .sidebar-resize-handle.is-dragging .sidebar-edge-toggle,
276
+ .sidebar-resize-handle.is-hidden .sidebar-edge-toggle {
277
+ opacity: 1;
278
+ pointer-events: auto;
279
+ }
280
+
281
+ .sidebar-edge-toggle:hover {
282
+ color: var(--vp-c-brand-1);
283
+ background: var(--vp-c-bg-soft);
284
+ border-color: var(--vp-c-brand-1);
285
+ }
286
+
287
+ .sidebar-resize-handle.is-hidden {
288
+ left: 0;
289
+ width: 14px;
290
+ cursor: default;
291
+ }
292
+
293
+ .sidebar-resize-handle.is-hidden::before,
294
+ .sidebar-resize-handle.is-hidden .resize-indicator {
295
+ display: none;
296
+ }
297
+
298
+ .sidebar-resize-handle.is-hidden .sidebar-edge-toggle {
299
+ left: 0;
300
+ border-left-color: var(--vp-c-divider);
301
+ border-radius: 0 7px 7px 0;
302
+ }
303
+
304
+ :global(body.is-sidebar-resizing),
305
+ :global(body.is-sidebar-resizing *) {
306
+ cursor: col-resize !important;
307
+ user-select: none !important;
308
+ }
309
+
310
+ .sidebar-resize-handle.is-fullscreen {
311
+ display: none;
312
+ }
313
+
314
+ @media (max-width: 959px) {
315
+ .sidebar-resize-handle {
316
+ display: none;
317
+ }
318
+ }
319
+ </style>
@@ -1,36 +1,26 @@
1
1
  <!-- .vitepress\components\Layout\ToggleSidebar.vue -->
2
2
  <template>
3
- <img @click="toggle" :class="$style.sidebarToggleBtn" :aria-pressed="hidden" :title="hidden ? '显示侧边栏' : '隐藏侧边栏'" :src="hidden ? icon__next : icon__prev" alt="" />
3
+ <img
4
+ @click="toggleSidebar"
5
+ :class="$style.sidebarToggleBtn"
6
+ :aria-pressed="hidden"
7
+ :title="hidden ? '显示侧边栏' : '隐藏侧边栏'"
8
+ :src="hidden ? icon__next : icon__prev"
9
+ alt=""
10
+ />
4
11
  </template>
5
12
 
6
13
  <script setup lang="ts">
7
- import { onMounted, ref } from 'vue'
14
+ import { onMounted } from 'vue'
8
15
 
9
16
  import { icon__next, icon__prev } from '../../assets/icons'
17
+ import { useSidebarLayout } from './composables/useSidebarLayout'
10
18
 
11
- const KEY = 'vp:sidebar:hidden'
12
- const hidden = ref(false)
19
+ const { hidden, initSidebarLayout, toggleSidebar } = useSidebarLayout()
13
20
 
14
21
  onMounted(() => {
15
- try {
16
- hidden.value = localStorage.getItem(KEY) === '1'
17
- } catch {}
18
- apply(hidden.value)
22
+ initSidebarLayout()
19
23
  })
20
-
21
- function apply(val: boolean) {
22
- const root = document.documentElement
23
- if (val) root.classList.add('hide-sidebar')
24
- else root.classList.remove('hide-sidebar')
25
- }
26
-
27
- function toggle() {
28
- hidden.value = !hidden.value
29
- apply(hidden.value)
30
- try {
31
- localStorage.setItem(KEY, hidden.value ? '1' : '0')
32
- } catch {}
33
- }
34
24
  </script>
35
25
 
36
26
  <style module src="./ToggleSidebar.module.scss"></style>
@@ -0,0 +1,122 @@
1
+ import { ref } from 'vue'
2
+
3
+ const SIDEBAR_HIDDEN_KEY = 'vp:sidebar:hidden'
4
+ const SIDEBAR_WIDTH_KEY = 'vp:sidebar:width'
5
+ const SIDEBAR_MIN_WIDTH = 260
6
+ const SIDEBAR_MAX_WIDTH = 480
7
+ const SIDEBAR_DEFAULT_WIDTH = 260
8
+
9
+ const hidden = ref(false)
10
+ const width = ref(SIDEBAR_DEFAULT_WIDTH)
11
+ let initialized = false
12
+
13
+ function canUseDOM(): boolean {
14
+ return typeof window !== 'undefined' && typeof document !== 'undefined'
15
+ }
16
+
17
+ function clampSidebarWidth(nextWidth: number): number {
18
+ if (!Number.isFinite(nextWidth)) return SIDEBAR_DEFAULT_WIDTH
19
+
20
+ return Math.min(
21
+ SIDEBAR_MAX_WIDTH,
22
+ Math.max(SIDEBAR_MIN_WIDTH, Math.round(nextWidth)),
23
+ )
24
+ }
25
+
26
+ function readStoredWidth(): number {
27
+ if (!canUseDOM()) return SIDEBAR_DEFAULT_WIDTH
28
+
29
+ const savedWidth = window.localStorage.getItem(SIDEBAR_WIDTH_KEY)
30
+ if (!savedWidth) return SIDEBAR_DEFAULT_WIDTH
31
+
32
+ return clampSidebarWidth(Number(savedWidth))
33
+ }
34
+
35
+ function applyHiddenState(nextHidden: boolean) {
36
+ if (!canUseDOM()) return
37
+
38
+ document.documentElement.classList.toggle('hide-sidebar', nextHidden)
39
+ }
40
+
41
+ function applySidebarWidth(nextWidth: number, nextHidden = hidden.value) {
42
+ if (!canUseDOM()) return
43
+
44
+ const widthValue = `${clampSidebarWidth(nextWidth)}px`
45
+ const layoutWidthValue = nextHidden ? '0px' : widthValue
46
+ document.documentElement.style.setProperty('--tn-sidebar-width', widthValue)
47
+ document.documentElement.style.setProperty(
48
+ '--tn-sidebar-layout-width',
49
+ layoutWidthValue,
50
+ )
51
+ document.documentElement.style.setProperty(
52
+ '--vp-sidebar-width',
53
+ layoutWidthValue,
54
+ )
55
+ }
56
+
57
+ function persistHiddenState(nextHidden: boolean) {
58
+ if (!canUseDOM()) return
59
+
60
+ try {
61
+ window.localStorage.setItem(SIDEBAR_HIDDEN_KEY, nextHidden ? '1' : '0')
62
+ } catch {}
63
+ }
64
+
65
+ function persistSidebarWidth(nextWidth: number) {
66
+ if (!canUseDOM()) return
67
+
68
+ try {
69
+ window.localStorage.setItem(
70
+ SIDEBAR_WIDTH_KEY,
71
+ String(clampSidebarWidth(nextWidth)),
72
+ )
73
+ } catch {}
74
+ }
75
+
76
+ function initSidebarLayout() {
77
+ if (!canUseDOM()) return
78
+
79
+ if (!initialized) {
80
+ hidden.value = window.localStorage.getItem(SIDEBAR_HIDDEN_KEY) === '1'
81
+ width.value = readStoredWidth()
82
+ initialized = true
83
+ }
84
+
85
+ applyHiddenState(hidden.value)
86
+ applySidebarWidth(width.value, hidden.value)
87
+ }
88
+
89
+ function setSidebarHidden(nextHidden: boolean) {
90
+ hidden.value = nextHidden
91
+ applyHiddenState(hidden.value)
92
+ applySidebarWidth(width.value, hidden.value)
93
+ persistHiddenState(hidden.value)
94
+ }
95
+
96
+ function toggleSidebar() {
97
+ setSidebarHidden(!hidden.value)
98
+ }
99
+
100
+ function setSidebarWidth(nextWidth: number) {
101
+ width.value = clampSidebarWidth(nextWidth)
102
+ applySidebarWidth(width.value)
103
+ }
104
+
105
+ function saveSidebarWidth() {
106
+ persistSidebarWidth(width.value)
107
+ }
108
+
109
+ export function useSidebarLayout() {
110
+ return {
111
+ hidden,
112
+ width,
113
+ minWidth: SIDEBAR_MIN_WIDTH,
114
+ maxWidth: SIDEBAR_MAX_WIDTH,
115
+ defaultWidth: SIDEBAR_DEFAULT_WIDTH,
116
+ initSidebarLayout,
117
+ setSidebarHidden,
118
+ toggleSidebar,
119
+ setSidebarWidth,
120
+ saveSidebarWidth,
121
+ }
122
+ }
@@ -34,26 +34,6 @@
34
34
  </div>
35
35
  </div>
36
36
 
37
- <div :class="$style.settingItem">
38
- <div :class="$style.settingMeta">
39
- <div :class="$style.labelLine">
40
- <span :class="$style.itemName">内容区宽度</span>
41
- <span :class="$style.infoWrap">
42
- <span :class="$style.infoIcon">?</span>
43
- <span :class="$style.tooltip">
44
- 调整文章内容区域的最大宽度。全屏模式下不会限制宽度。
45
- </span>
46
- </span>
47
- </div>
48
- <span :class="$style.statusText">{{ contentWidth }}</span>
49
- </div>
50
-
51
- <select v-model="contentWidth" :class="$style.select">
52
- <option value="688px">标准</option>
53
- <option value="755px">较大</option>
54
- </select>
55
- </div>
56
-
57
37
  <div :class="$style.settingItem">
58
38
  <div :class="$style.settingMeta">
59
39
  <div :class="$style.labelLine">
@@ -200,7 +180,6 @@ import { data as tnotesConfig } from '../tnotes-config.data'
200
180
 
201
181
  type SidebarDensity = 'compact' | 'default' | 'loose'
202
182
 
203
- const CONTENT_WIDTH_KEY = 'tnotes-content-width'
204
183
  const DEFAULT_SIDEBAR_DENSITY: SidebarDensity = 'default'
205
184
  const DEFAULT_DONE_PREFIX = '✅'
206
185
  const DEFAULT_UNDONE_PREFIX = '⏰'
@@ -219,8 +198,6 @@ const markmapTheme = ref('default')
219
198
  const originalMarkmapTheme = ref('default')
220
199
  const markmapExpandLevel = ref(5)
221
200
  const originalMarkmapExpandLevel = ref(5)
222
- const contentWidth = ref('688px')
223
- const originalContentWidth = ref('688px')
224
201
  const showNoteId = ref(false)
225
202
  const originalShowNoteId = ref(false)
226
203
  const sidebarDensity = ref<SidebarDensity>(DEFAULT_SIDEBAR_DENSITY)
@@ -236,7 +213,6 @@ const hasChanges = computed(
236
213
  path.value !== originalPath.value ||
237
214
  markmapTheme.value !== originalMarkmapTheme.value ||
238
215
  markmapExpandLevel.value !== originalMarkmapExpandLevel.value ||
239
- contentWidth.value !== originalContentWidth.value ||
240
216
  showNoteId.value !== originalShowNoteId.value ||
241
217
  sidebarDensity.value !== originalSidebarDensity.value ||
242
218
  donePrefix.value !== originalDonePrefix.value ||
@@ -263,11 +239,6 @@ onMounted(() => {
263
239
  markmapExpandLevel.value = parseInt(savedLevel, 10)
264
240
  originalMarkmapExpandLevel.value = markmapExpandLevel.value
265
241
 
266
- const savedWidth = localStorage.getItem(CONTENT_WIDTH_KEY) || '688px'
267
- contentWidth.value = savedWidth
268
- originalContentWidth.value = savedWidth
269
- applyContentWidth()
270
-
271
242
  const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
272
243
  showNoteId.value =
273
244
  savedShowNoteId !== null
@@ -320,17 +291,14 @@ function save() {
320
291
  MARKMAP_EXPAND_LEVEL_KEY,
321
292
  markmapExpandLevel.value.toString(),
322
293
  )
323
- localStorage.setItem(CONTENT_WIDTH_KEY, contentWidth.value)
324
294
  localStorage.setItem(SIDEBAR_SHOW_NOTE_ID_KEY, showNoteId.value.toString())
325
295
  localStorage.setItem(SIDEBAR_DENSITY_KEY, sidebarDensity.value)
326
296
  localStorage.setItem(SIDEBAR_DONE_PREFIX_KEY, donePrefix.value)
327
297
  localStorage.setItem(SIDEBAR_UNDONE_PREFIX_KEY, undonePrefix.value)
328
- applyContentWidth()
329
298
 
330
299
  originalPath.value = path.value
331
300
  originalMarkmapTheme.value = markmapTheme.value
332
301
  originalMarkmapExpandLevel.value = markmapExpandLevel.value
333
- originalContentWidth.value = contentWidth.value
334
302
  originalShowNoteId.value = showNoteId.value
335
303
  originalSidebarDensity.value = sidebarDensity.value
336
304
  originalDonePrefix.value = donePrefix.value
@@ -356,21 +324,10 @@ function reset() {
356
324
  path.value = originalPath.value
357
325
  markmapTheme.value = originalMarkmapTheme.value
358
326
  markmapExpandLevel.value = originalMarkmapExpandLevel.value
359
- contentWidth.value = originalContentWidth.value
360
327
  showNoteId.value = originalShowNoteId.value
361
328
  sidebarDensity.value = originalSidebarDensity.value
362
329
  donePrefix.value = originalDonePrefix.value
363
330
  undonePrefix.value = originalUndonePrefix.value
364
- applyContentWidth()
365
- }
366
-
367
- function applyContentWidth() {
368
- if (typeof document === 'undefined') return
369
-
370
- document.documentElement.style.setProperty(
371
- '--tn-content-width',
372
- contentWidth.value,
373
- )
374
331
  }
375
332
  </script>
376
333
 
@@ -13,9 +13,6 @@
13
13
  /* 品牌色 */
14
14
  --vp-c-brand-1: #646cff;
15
15
  --vp-c-brand-2: #747bff;
16
-
17
- /* TNotes 自定义变量 */
18
- --tn-content-width: 688px;
19
16
  }
20
17
 
21
18
  /* #endregion */
@@ -41,10 +38,14 @@ ul > li.task-list-item > ul {
41
38
  }
42
39
 
43
40
  .vp-doc h2 {
44
- // 默认值:
41
+ // margin 默认值:
45
42
  // margin: 48px 0 16px;
46
43
  // 缩小上边距
47
- // margin: 32px 0 16px;
44
+ margin: 1rem 0 1rem;
45
+ // border-top 默认值:
46
+ // border-top: 1px solid var(--vp-c-divider);
47
+ // 移除上横线:
48
+ border-top: none;
48
49
  }
49
50
 
50
51
  /* #endregion */
@@ -33,7 +33,7 @@
33
33
  height: 16px;
34
34
  transition: transform 0.25s ease;
35
35
  transform: rotate(90deg); /* 默认向下(展开状态)*/
36
- opacity: 0.3;
36
+ opacity: 0;
37
37
  }
38
38
 
39
39
  /* #endregion */
@@ -100,7 +100,7 @@
100
100
  h2.collapsible-h2 {
101
101
  position: relative !important;
102
102
  cursor: pointer !important;
103
- padding: 8px 40px 8px 12px !important; /* 右侧预留按钮空间 */
103
+ padding: 8px 40px 8px 0px !important; /* 右侧预留按钮空间 */
104
104
  border-radius: 6px !important;
105
105
  transition: all 0.2s ease !important;
106
106
 
@@ -111,12 +111,18 @@ h2.collapsible-h2 {
111
111
  opacity: 1 !important;
112
112
  }
113
113
  }
114
+ .collapse-icon {
115
+ opacity: 0.3;
116
+ }
114
117
 
115
118
  &.collapsed {
116
119
  background: var(--vp-c-bg-soft) !important;
117
- border: 1px solid var(--vp-c-divider) !important;
120
+ border: .1px solid var(--vp-c-divider) !important;
121
+ .collapse-icon {
122
+ opacity: 0.5;
123
+ }
118
124
  // margin-top: 1rem !important;
119
- margin: 12px 0 0 0 !important;
125
+ // margin: 12px 0 0 0 !important;
120
126
 
121
127
  .collapse-icon {
122
128
  transform: rotate(0deg) !important; /* 折叠时向右 */
@@ -130,7 +136,8 @@ h2.collapsible-h2 {
130
136
 
131
137
  /* 修复 anchor 位置 */
132
138
  .header-anchor {
133
- top: 10px !important;
139
+ // top: 10px !important;
140
+ display: none !important;
134
141
  }
135
142
  }
136
143
 
@@ -152,7 +159,10 @@ h2.collapsible-h2 {
152
159
  overflow: hidden;
153
160
  max-height: 10000px;
154
161
  opacity: 1;
155
- transition: max-height 0.3s ease, opacity 0.3s ease, margin 0.3s ease;
162
+ transition:
163
+ max-height 0.3s ease,
164
+ opacity 0.3s ease,
165
+ margin 0.3s ease;
156
166
 
157
167
  &.collapsed {
158
168
  max-height: 0 !important;
@@ -5,8 +5,15 @@
5
5
  * - 特殊布局模式(侧边栏隐藏、全屏等)
6
6
  */
7
7
 
8
+ :root {
9
+ --tn-sidebar-width: 260px;
10
+ --tn-sidebar-layout-width: var(--tn-sidebar-width);
11
+ --vp-sidebar-width: var(--tn-sidebar-width);
12
+ }
13
+
8
14
  // 侧边栏样式重写
9
15
  .VPSidebar {
16
+ width: var(--tn-sidebar-layout-width, var(--tn-sidebar-width)) !important;
10
17
  padding-top: var(--vp-nav-height) !important;
11
18
  padding-left: 1rem !important;
12
19
  padding-right: 1rem !important;
@@ -18,11 +25,18 @@
18
25
  /* ===================================== */
19
26
 
20
27
  .hide-sidebar .VPSidebar {
28
+ padding-left: 0 !important;
29
+ padding-right: 0 !important;
30
+ overflow: hidden;
21
31
  transform: translateX(-100%);
22
32
  pointer-events: none;
23
33
  transition: transform 0.25s ease, opacity 0.25s ease;
24
34
  }
25
35
 
36
+ .hide-sidebar .VPNavBarTitle {
37
+ display: none !important;
38
+ }
39
+
26
40
  .hide-sidebar .VPNavBar {
27
41
  background-color: var(--vp-nav-bg-color) !important;
28
42
  }
@@ -33,13 +47,6 @@
33
47
  padding-left: calc((100vw - var(--vp-layout-max-width)) / 2) !important;
34
48
  }
35
49
 
36
- .hide-sidebar .VPDoc .content-container {
37
- max-width: calc(
38
- var(--vp-sidebar-width) + var(--tn-content-width, 688px) +
39
- var(--vp-sidebar-width)
40
- ) !important;
41
- }
42
-
43
50
  /* #endregion */
44
51
 
45
52
  /* ===================================== */
@@ -48,6 +55,7 @@
48
55
 
49
56
  .content-fullscreen .VPSidebar,
50
57
  .content-fullscreen .VPNav,
58
+ .content-fullscreen .VPLocalNav,
51
59
  .content-fullscreen .VPDocFooter,
52
60
  .content-fullscreen .VPNavBar {
53
61
  display: none !important;
@@ -19,7 +19,7 @@
19
19
 
20
20
  .VPDoc.has-aside .content-container {
21
21
  padding: 0 2px !important;
22
- max-width: var(--tn-content-width, 688px) !important;
22
+ max-width: none !important;
23
23
  }
24
24
 
25
25
  /* #endregion */