@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.
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
@@ -4,7 +4,9 @@
4
4
  * VitePress dev middleware - 处理语雀风格侧边栏的目录结构操作。
5
5
  */
6
6
 
7
- import { FileWatcherService, NoteService, ReadmeService } from '../../services'
7
+ import { scheduleNoteSearchReindex } from './localSearchReindexPlugin'
8
+ import { FileWatcherService, NoteService, TocService } from '../../services'
9
+
8
10
 
9
11
  import type { NoteInfo } from '../../types'
10
12
  import type { IncomingMessage, ServerResponse } from 'http'
@@ -15,6 +17,7 @@ interface JsonResponse {
15
17
  message?: string
16
18
  sidebarChanged?: boolean
17
19
  redirectUrl?: string
20
+ redirectNoteIndex?: string | null
18
21
  createdNotes?: Array<{ index: string; dirName: string; link: string }>
19
22
  deletedNoteIndexes?: string[]
20
23
  }
@@ -47,14 +50,6 @@ function normalizePathname(url?: string): string {
47
50
  return (url || '').split('?')[0]
48
51
  }
49
52
 
50
- function normalizeGroupPath(value: unknown): string[] {
51
- if (!Array.isArray(value) || value.length === 0) {
52
- throw new Error('Missing groupPath')
53
- }
54
-
55
- return value.map((item) => String(item))
56
- }
57
-
58
53
  function normalizeCount(value: unknown): number {
59
54
  const count = Number(value ?? 1)
60
55
  if (!Number.isInteger(count) || count < 1 || count > 100) {
@@ -71,6 +66,22 @@ function getCreatedNotePayload(notes: NoteInfo[]) {
71
66
  }))
72
67
  }
73
68
 
69
+ function buildNoteReadmeLink(note: NoteInfo): string {
70
+ return `/notes/${encodeURIComponent(note.dirName)}/README`
71
+ }
72
+
73
+ function resolveDeleteRedirectUrl(
74
+ noteService: NoteService,
75
+ previousNoteIndex: string | null,
76
+ ): string {
77
+ if (!previousNoteIndex) return '/'
78
+
79
+ const previousNote = noteService.getNoteByIndex(previousNoteIndex)
80
+ if (!previousNote) return '/'
81
+
82
+ return buildNoteReadmeLink(previousNote)
83
+ }
84
+
74
85
  async function withSuspendedWatcher<T>(task: () => Promise<T>): Promise<T> {
75
86
  const watcher = FileWatcherService.getInstance()
76
87
 
@@ -84,7 +95,7 @@ async function withSuspendedWatcher<T>(task: () => Promise<T>): Promise<T> {
84
95
 
85
96
  export function sidebarStructurePlugin(): PluginOption {
86
97
  const noteService = NoteService.getInstance()
87
- const readmeService = ReadmeService.getInstance()
98
+ const tocService = TocService.getInstance()
88
99
 
89
100
  async function createNotes(count: number) {
90
101
  const notes = []
@@ -112,17 +123,22 @@ export function sidebarStructurePlugin(): PluginOption {
112
123
  return notes
113
124
  }
114
125
 
126
+ async function refreshTocAndSidebar() {
127
+ await tocService.regenerateSidebar()
128
+ }
129
+
115
130
  return {
116
131
  name: 'tnotes-sidebar-structure',
117
132
  configureServer(server) {
118
133
  server.middlewares.use(async (req, res, next) => {
119
134
  const pathname = normalizePathname(req.url)
120
135
  const handledPaths = new Set([
121
- '/__tnotes_sidebar_rename_group',
122
- '/__tnotes_sidebar_delete_group',
123
136
  '/__tnotes_sidebar_create_note',
124
137
  '/__tnotes_sidebar_create_notes',
138
+ '/__tnotes_sidebar_create_folder',
125
139
  '/__tnotes_sidebar_delete_note',
140
+ '/__tnotes_sidebar_delete_entry',
141
+ '/__tnotes_sidebar_rename_folder',
126
142
  '/__tnotes_sidebar_reorder',
127
143
  ])
128
144
 
@@ -140,81 +156,165 @@ export function sidebarStructurePlugin(): PluginOption {
140
156
  const data = await readJsonBody(req)
141
157
 
142
158
  const result = await withSuspendedWatcher(async () => {
143
- if (pathname === '/__tnotes_sidebar_rename_group') {
144
- const groupPath = normalizeGroupPath(data.groupPath)
145
- const newTitle = String(data.newTitle || '')
146
- await readmeService.renameGroupInReadme(groupPath, newTitle)
147
- await readmeService.refreshHomeReadmeAndSidebar()
159
+ if (pathname === '/__tnotes_sidebar_delete_note') {
160
+ const noteIndex = String(data.noteIndex || '')
161
+ if (!noteIndex) throw new Error('Missing noteIndex')
162
+
163
+ const previousNoteIndex =
164
+ await tocService.getPreviousNoteIndexBeforeDelete(noteIndex)
165
+
166
+ await tocService.deleteNoteFromToc(noteIndex)
167
+ await noteService.deleteNote(noteIndex)
168
+ await refreshTocAndSidebar()
169
+ scheduleNoteSearchReindex('api:delete-note')
170
+
148
171
  return {
149
172
  success: true,
150
173
  sidebarChanged: true,
151
- message: '目录已重命名',
174
+ redirectUrl: resolveDeleteRedirectUrl(
175
+ noteService,
176
+ previousNoteIndex,
177
+ ),
178
+ redirectNoteIndex: previousNoteIndex,
179
+ deletedNoteIndexes: [noteIndex],
180
+ message: '笔记已删除',
152
181
  }
153
182
  }
154
183
 
155
- if (pathname === '/__tnotes_sidebar_delete_group') {
156
- const groupPath = normalizeGroupPath(data.groupPath)
157
- const noteIndexes = await readmeService.deleteGroupFromReadme(
158
- groupPath,
159
- )
160
-
161
- for (const noteIndex of noteIndexes) {
162
- await noteService.deleteNote(noteIndex)
184
+ if (pathname === '/__tnotes_sidebar_delete_entry') {
185
+ const tocLineIndex = Number(data.tocLineIndex)
186
+ if (!Number.isInteger(tocLineIndex) || tocLineIndex < 0) {
187
+ throw new Error('Missing tocLineIndex')
163
188
  }
164
189
 
165
- await readmeService.refreshHomeReadmeAndSidebar()
190
+ const currentNoteIndex = String(data.currentNoteIndex || '')
191
+ const previousNoteIndex =
192
+ await tocService.getPreviousNoteIndexBeforeEntryDelete(
193
+ tocLineIndex,
194
+ )
195
+ const inSubtree =
196
+ currentNoteIndex &&
197
+ (await tocService.isNoteInTocEntrySubtree(
198
+ tocLineIndex,
199
+ currentNoteIndex,
200
+ ))
201
+
202
+ const deletedNoteIndexes =
203
+ await tocService.deleteTocEntryCascade(tocLineIndex)
204
+ await refreshTocAndSidebar()
205
+ scheduleNoteSearchReindex('api:delete-entry')
206
+
166
207
  return {
167
208
  success: true,
168
209
  sidebarChanged: true,
169
- redirectUrl: '/',
170
- deletedNoteIndexes: noteIndexes,
171
- message: '目录已删除',
210
+ redirectUrl: inSubtree
211
+ ? resolveDeleteRedirectUrl(noteService, previousNoteIndex)
212
+ : undefined,
213
+ redirectNoteIndex: inSubtree ? previousNoteIndex : null,
214
+ deletedNoteIndexes,
215
+ message: '已删除',
172
216
  }
173
217
  }
174
218
 
175
- if (pathname === '/__tnotes_sidebar_delete_note') {
176
- const noteIndex = String(data.noteIndex || '')
177
- if (!noteIndex) throw new Error('Missing noteIndex')
219
+ if (pathname === '/__tnotes_sidebar_rename_folder') {
220
+ const tocLineIndex = Number(data.tocLineIndex)
221
+ const newTitle = String(data.newTitle || '')
222
+ if (!Number.isInteger(tocLineIndex) || tocLineIndex < 0) {
223
+ throw new Error('Missing tocLineIndex')
224
+ }
178
225
 
179
- await readmeService.deleteNoteFromReadme(noteIndex)
180
- await noteService.deleteNote(noteIndex)
181
- await readmeService.refreshHomeReadmeAndSidebar()
226
+ await tocService.renameFolderInToc(tocLineIndex, newTitle)
227
+ await refreshTocAndSidebar()
182
228
 
183
229
  return {
184
230
  success: true,
185
231
  sidebarChanged: true,
186
- redirectUrl: '/',
187
- deletedNoteIndexes: [noteIndex],
188
- message: '笔记已删除',
232
+ message: '目录已重命名',
189
233
  }
190
234
  }
191
235
 
192
236
  if (pathname === '/__tnotes_sidebar_reorder') {
193
- if (data.dragType === 'note') {
194
- const noteIndex = String(data.noteIndex || '')
195
- if (!noteIndex) throw new Error('Missing noteIndex')
196
-
197
- if (data.targetType === 'group') {
198
- await readmeService.moveNoteInReadme(noteIndex, {
199
- targetGroupPath: normalizeGroupPath(data.targetGroupPath),
200
- })
237
+ if (
238
+ data.node_uuid &&
239
+ data.action === 'prependChild' &&
240
+ !data.target_uuid
241
+ ) {
242
+ await tocService.prependToRootByNodeId(String(data.node_uuid))
243
+ await refreshTocAndSidebar()
244
+ return {
245
+ success: true,
246
+ sidebarChanged: true,
247
+ message: '排序已更新',
248
+ }
249
+ }
250
+
251
+ if (
252
+ data.node_uuid &&
253
+ data.target_uuid &&
254
+ (data.action === 'moveAfter' || data.action === 'prependChild')
255
+ ) {
256
+ if (data.action === 'moveAfter') {
257
+ await tocService.moveAfterByNodeId(
258
+ String(data.node_uuid),
259
+ String(data.target_uuid),
260
+ )
201
261
  } else {
202
- await readmeService.moveNoteInReadme(noteIndex, {
203
- targetNoteIndex: String(data.targetNoteIndex || ''),
204
- placement: data.placement === 'after' ? 'after' : 'before',
205
- })
262
+ await tocService.prependChildByNodeId(
263
+ String(data.node_uuid),
264
+ String(data.target_uuid),
265
+ )
206
266
  }
207
- } else if (data.dragType === 'group') {
208
- await readmeService.moveGroupInReadme(
209
- normalizeGroupPath(data.groupPath),
210
- normalizeGroupPath(data.targetGroupPath),
211
- data.placement === 'after' ? 'after' : 'before',
212
- )
267
+ await refreshTocAndSidebar()
268
+ return {
269
+ success: true,
270
+ sidebarChanged: true,
271
+ message: '排序已更新',
272
+ }
273
+ }
274
+
275
+ const dragTocLineIndex = Number(data.dragTocLineIndex)
276
+ if (!Number.isInteger(dragTocLineIndex) || dragTocLineIndex < 0) {
277
+ throw new Error('Missing dragTocLineIndex')
278
+ }
279
+
280
+ const placement =
281
+ data.targetType === 'group' || data.placement === 'inside'
282
+ ? 'inside'
283
+ : data.placement === 'after'
284
+ ? 'after'
285
+ : 'before'
286
+
287
+ if (
288
+ data.targetTocLineIndex !== undefined &&
289
+ data.targetTocLineIndex !== null
290
+ ) {
291
+ await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
292
+ targetTocLineIndex: Number(data.targetTocLineIndex),
293
+ placement,
294
+ })
295
+ } else if (
296
+ Array.isArray(data.targetFolderPath) &&
297
+ data.targetFolderPath.length > 0
298
+ ) {
299
+ await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
300
+ targetType: 'folder',
301
+ targetFolderPath: data.targetFolderPath.map(String),
302
+ placement,
303
+ })
213
304
  } else {
214
- throw new Error('Missing dragType')
305
+ const targetNoteIndex = String(
306
+ data.targetNoteIndex || data.targetGroupNoteIndex || '',
307
+ )
308
+ if (!targetNoteIndex) throw new Error('Missing targetNoteIndex')
309
+
310
+ await tocService.moveTocEntryByLineIndex(dragTocLineIndex, {
311
+ targetType: 'note',
312
+ targetNoteIndex,
313
+ placement,
314
+ })
215
315
  }
216
316
 
217
- await readmeService.refreshHomeReadmeAndSidebar()
317
+ await refreshTocAndSidebar()
218
318
  return {
219
319
  success: true,
220
320
  sidebarChanged: true,
@@ -222,22 +322,68 @@ export function sidebarStructurePlugin(): PluginOption {
222
322
  }
223
323
  }
224
324
 
325
+ if (pathname === '/__tnotes_sidebar_create_folder') {
326
+ const parentTocLineIndex = Number(data.parentTocLineIndex)
327
+ const title = String(data.title || '')
328
+ if (
329
+ !Number.isInteger(parentTocLineIndex) ||
330
+ parentTocLineIndex < 0
331
+ ) {
332
+ throw new Error('Missing parentTocLineIndex')
333
+ }
334
+
335
+ await tocService.insertFolderUnderParent(
336
+ parentTocLineIndex,
337
+ title,
338
+ )
339
+ await refreshTocAndSidebar()
340
+
341
+ return {
342
+ success: true,
343
+ sidebarChanged: true,
344
+ message: '目录已创建',
345
+ }
346
+ }
347
+
225
348
  const count = normalizeCount(data.count)
226
349
  const notes = await createNotes(count)
227
350
 
228
351
  if (data.targetNoteIndex) {
229
352
  const placement = data.placement === 'after' ? 'after' : 'before'
230
- await readmeService.insertNotesAroundNote(
353
+ await tocService.insertNotesAroundNote(
231
354
  String(data.targetNoteIndex),
232
355
  notes,
233
356
  placement,
234
357
  )
358
+ } else if (
359
+ data.parentTocLineIndex !== undefined &&
360
+ data.parentTocLineIndex !== null
361
+ ) {
362
+ await tocService.insertNotesUnderTocLine(
363
+ Number(data.parentTocLineIndex),
364
+ notes,
365
+ )
366
+ } else if (
367
+ Array.isArray(data.parentFolderPath) &&
368
+ data.parentFolderPath.length > 0
369
+ ) {
370
+ await tocService.insertNotesUnderFolder(
371
+ data.parentFolderPath.map(String),
372
+ notes,
373
+ )
374
+ } else if (data.parentNoteIndex) {
375
+ await tocService.insertNotesUnderParent(
376
+ String(data.parentNoteIndex),
377
+ notes,
378
+ )
235
379
  } else {
236
- const groupPath = normalizeGroupPath(data.groupPath)
237
- await readmeService.insertNotesAtGroupStart(groupPath, notes)
380
+ for (const note of notes) {
381
+ await tocService.appendNoteToToc(note.index)
382
+ }
238
383
  }
239
384
 
240
- await readmeService.refreshHomeReadmeAndSidebar()
385
+ await refreshTocAndSidebar()
386
+ scheduleNoteSearchReindex('api:create-notes')
241
387
 
242
388
  return {
243
389
  success: true,
@@ -14,6 +14,7 @@
14
14
 
15
15
  import DefaultTheme from 'vitepress/theme'
16
16
 
17
+ import { initTnotesSearchIndexHmr } from '../client/localSearchIndexBridge'
17
18
  import BilibiliOutsidePlayer from '../components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue'
18
19
  import Discussions from '../components/Discussions/Discussions.vue'
19
20
  import EnWordList from '../components/EnWordList/EnWordList.vue'
@@ -27,6 +28,7 @@ import NotesTable from '../components/NotesTable/NotesTable.vue'
27
28
  import SidebarCard from '../components/SidebarCard/SidebarCard.vue'
28
29
  import Tooltip from '../components/Tooltip/Tooltip.vue'
29
30
 
31
+
30
32
  import type { Theme, EnhanceAppContext } from 'vitepress'
31
33
  import './styles/index.scss'
32
34
 
@@ -88,6 +90,10 @@ function registerRenameHmrListener() {
88
90
  )
89
91
  }
90
92
 
93
+ function registerSearchIndexHmrListener() {
94
+ initTnotesSearchIndexHmr()
95
+ }
96
+
91
97
  /**
92
98
  * 覆盖选项
93
99
  */
@@ -116,6 +122,7 @@ export function defineNotesTheme(overrides: NotesThemeOverrides = {}): Theme {
116
122
  enhanceApp(ctx) {
117
123
  registerCoreComponents(ctx)
118
124
  registerRenameHmrListener()
125
+ registerSearchIndexHmrListener()
119
126
  overrides.enhanceApp?.(ctx)
120
127
  },
121
128
  }
@@ -130,5 +137,6 @@ export default {
130
137
  enhanceApp(ctx: EnhanceAppContext) {
131
138
  registerCoreComponents(ctx)
132
139
  registerRenameHmrListener()
140
+ registerSearchIndexHmrListener()
133
141
  },
134
142
  } satisfies Theme
@@ -55,12 +55,4 @@ ul > li.task-list-item > ul {
55
55
  margin: 1rem 0 1rem;
56
56
  }
57
57
 
58
- // 代码块外层 div
59
- .vp-doc div[class*=language-] {
60
- // 默认值:
61
- // margin: 16px 24px;
62
- // 移除 margin:
63
- margin: 0px !important;
64
- }
65
-
66
58
  /* #endregion */
@@ -16,8 +16,9 @@
16
16
  border-radius: 6px;
17
17
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
18
18
  backdrop-filter: blur(8px);
19
- opacity: 0;
20
- pointer-events: none;
19
+ opacity: 1;
20
+ pointer-events: auto;
21
+ z-index: 10;
21
22
  transition: opacity 0.3s ease;
22
23
  color: var(--vp-c-tip-1);
23
24
  font-weight: 700;
@@ -73,6 +74,26 @@
73
74
  background-color: rgba(255, 255, 255, 0.1);
74
75
  }
75
76
 
77
+ html.markmap-dark .markmapContainer,
78
+ .markmapContainer.is-markmap-dark {
79
+ background-color: #1d1d1d;
80
+ box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
81
+ }
82
+
83
+ html.markmap-dark .markmapContainer:hover,
84
+ .markmapContainer.is-markmap-dark:hover {
85
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
86
+ }
87
+
88
+ html.markmap-dark .markmapContainer:fullscreen,
89
+ html.markmap-dark .markmapContainer:-ms-fullscreen,
90
+ html.markmap-dark .markmapContainer:-webkit-full-screen,
91
+ .markmapContainer.is-markmap-dark:fullscreen,
92
+ .markmapContainer.is-markmap-dark:-ms-fullscreen,
93
+ .markmapContainer.is-markmap-dark:-webkit-full-screen {
94
+ background: #1d1d1d;
95
+ }
96
+
76
97
  /* #endregion */
77
98
 
78
99
  /* ===================================== */
@@ -149,7 +149,7 @@
149
149
  width: 100%;
150
150
  position: relative;
151
151
  overflow: hidden;
152
- margin: 1rem 0;
152
+ margin: 0;
153
153
  transition: height 0.3s ease;
154
154
  border-radius: 0 0 8px 8px;
155
155
  background: var(--vp-c-bg-soft);
@@ -0,0 +1,212 @@
1
+ /**
2
+ * 文档三栏布局(语雀式)
3
+ * - 左目录 / 右正文目录贴齐视口左右两侧
4
+ * - 中间正文:超宽 / 标准 750px
5
+ * - 视口不足时 aside-auto-hidden / sidebar-auto-hidden
6
+ */
7
+
8
+ :root {
9
+ --tn-aside-width: 300px;
10
+ --tn-content-standard-width: 750px;
11
+ --tn-content-min-width: 640px;
12
+ --tn-doc-layout-padding: 16px;
13
+ }
14
+
15
+ /* 取消 VitePress 1440px 居中壳:顶栏与正文区贴齐左目录、右目录 */
16
+ @media (min-width: 960px) {
17
+ .VPNavBar.has-sidebar .container {
18
+ max-width: 100% !important;
19
+ margin: 0 !important;
20
+ }
21
+
22
+ /* 站点标题固定在视口左上方,宽度与左目录一致 */
23
+ .VPNavBar.has-sidebar .title {
24
+ left: 0 !important;
25
+ padding-left: var(--tn-sidebar-inset-x, 8px) !important;
26
+ padding-right: var(--tn-sidebar-inset-x, 8px) !important;
27
+ width: var(--vp-sidebar-width) !important;
28
+ box-sizing: border-box;
29
+ }
30
+
31
+ .VPNavBar.has-sidebar .divider {
32
+ padding-left: var(--vp-sidebar-width) !important;
33
+ }
34
+
35
+ .VPNavBar.has-sidebar .content {
36
+ padding-left: var(--vp-sidebar-width) !important;
37
+ padding-right: var(--tn-sidebar-inset-x, 8px) !important;
38
+ }
39
+
40
+ .VPContent.has-sidebar {
41
+ padding-left: var(--vp-sidebar-width) !important;
42
+ padding-right: 0 !important;
43
+ }
44
+ }
45
+
46
+ @media (min-width: 1440px) {
47
+ .VPNavBar.has-sidebar .title {
48
+ padding-left: var(--tn-sidebar-inset-x, 8px) !important;
49
+ width: var(--vp-sidebar-width) !important;
50
+ }
51
+
52
+ .VPNavBar.has-sidebar .divider {
53
+ padding-left: var(--vp-sidebar-width) !important;
54
+ }
55
+
56
+ .VPNavBar.has-sidebar .content {
57
+ padding-left: var(--vp-sidebar-width) !important;
58
+ padding-right: var(--tn-sidebar-inset-x, 8px) !important;
59
+ }
60
+
61
+ .VPContent.has-sidebar {
62
+ padding-left: var(--vp-sidebar-width) !important;
63
+ padding-right: 0 !important;
64
+ }
65
+ }
66
+
67
+ /* 有右栏时为正文区预留 300px,右栏 fixed 贴视口右侧 */
68
+ @media (min-width: 1280px) {
69
+ html:not(.aside-auto-hidden):not(.content-fullscreen)
70
+ .VPContent.has-sidebar:has(.VPDoc.has-aside) {
71
+ padding-right: var(--tn-aside-width) !important;
72
+ }
73
+
74
+ .VPDoc.has-aside .aside {
75
+ position: fixed !important;
76
+ top: var(--vp-nav-height) !important;
77
+ right: 0 !important;
78
+ left: auto !important;
79
+ bottom: 0 !important;
80
+ z-index: 10;
81
+ order: 0 !important;
82
+ flex: none !important;
83
+ width: var(--tn-aside-width) !important;
84
+ max-width: var(--tn-aside-width) !important;
85
+ padding: 0 var(--tn-sidebar-inset-x, 8px) !important;
86
+ box-sizing: border-box;
87
+ }
88
+
89
+ .VPDoc .aside-container {
90
+ position: relative !important;
91
+ top: auto !important;
92
+ width: 100% !important;
93
+ height: 100% !important;
94
+ padding-top: 48px !important;
95
+ overflow-x: hidden;
96
+ overflow-y: auto;
97
+ }
98
+
99
+ .VPDoc .aside-curtain {
100
+ width: 100% !important;
101
+ }
102
+
103
+ /* aside 已 fixed,container 不再为右栏预留 flex 列 */
104
+ .VPDoc.has-aside .container {
105
+ display: block !important;
106
+ max-width: none !important;
107
+ }
108
+
109
+ .VPDoc.has-aside .content {
110
+ width: 100% !important;
111
+ max-width: none !important;
112
+ min-width: 0 !important;
113
+ margin: 0 !important;
114
+ padding: 48px var(--tn-sidebar-inset-x, 8px) 128px !important;
115
+ box-sizing: border-box;
116
+ }
117
+ }
118
+
119
+ /* 正文页宽模式 */
120
+ html.content-width-wide .VPDoc.has-aside .content-container {
121
+ max-width: none !important;
122
+ }
123
+
124
+ html.content-width-standard .VPDoc.has-aside .content-container {
125
+ max-width: var(--tn-content-standard-width) !important;
126
+ margin-inline: auto !important;
127
+ }
128
+
129
+ html.content-width-standard .VPDoc:not(.has-aside) .content-container {
130
+ max-width: var(--tn-content-standard-width) !important;
131
+ margin-inline: auto !important;
132
+ }
133
+
134
+ /* 视口不足:先隐藏右侧正文目录 */
135
+ html.aside-auto-hidden .VPDoc .aside {
136
+ display: none !important;
137
+ }
138
+
139
+ html.aside-auto-hidden .VPDoc.has-aside .content {
140
+ max-width: none !important;
141
+ }
142
+
143
+ html.aside-auto-hidden .VPContent.has-sidebar {
144
+ padding-right: 0 !important;
145
+ }
146
+
147
+ /* 视口仍不足:自动折叠左目录(仅桌面;移动端由 VitePress 抽屉侧栏负责) */
148
+ @media (min-width: 960px) {
149
+ html.sidebar-auto-hidden .VPSidebar {
150
+ padding-left: 0 !important;
151
+ padding-right: 0 !important;
152
+ overflow: hidden;
153
+ transform: translateX(-100%);
154
+ pointer-events: none;
155
+ transition: transform 0.25s ease, opacity 0.25s ease;
156
+ }
157
+
158
+ html.sidebar-auto-hidden .VPNavBar.has-sidebar .VPNavBarTitle {
159
+ display: none !important;
160
+ }
161
+
162
+ html.sidebar-auto-hidden .VPNavBar.has-sidebar {
163
+ background-color: var(--vp-nav-bg-color) !important;
164
+ }
165
+
166
+ html.sidebar-auto-hidden .VPNavBar.has-sidebar .divider {
167
+ padding-left: 0 !important;
168
+ }
169
+
170
+ html.sidebar-auto-hidden .VPNavBar.has-sidebar .content {
171
+ padding-left: 0 !important;
172
+ }
173
+
174
+ html.sidebar-auto-hidden .VPContent.has-sidebar {
175
+ margin-left: 0 !important;
176
+ padding-left: 0 !important;
177
+ }
178
+ }
179
+
180
+ /* 全屏内容模式:隐藏右栏,正文横向铺满视口(覆盖标准/超宽页宽) */
181
+ html.content-fullscreen {
182
+ .VPDoc .aside {
183
+ display: none !important;
184
+ }
185
+
186
+ .VPContent.has-sidebar {
187
+ padding-left: 0 !important;
188
+ padding-right: 0 !important;
189
+ }
190
+
191
+ .VPDoc.has-aside .container,
192
+ .VPDoc .container {
193
+ max-width: none !important;
194
+ width: 100% !important;
195
+ }
196
+
197
+ .VPDoc.has-aside .content,
198
+ .VPDoc .content {
199
+ max-width: none !important;
200
+ width: 100% !important;
201
+ margin: 0 !important;
202
+ padding: 48px var(--tn-sidebar-inset-x, 8px) 128px !important;
203
+ box-sizing: border-box;
204
+ }
205
+
206
+ &.content-width-standard .VPDoc .content-container,
207
+ &.content-width-wide .VPDoc .content-container {
208
+ max-width: none !important;
209
+ width: 100% !important;
210
+ margin-inline: 0 !important;
211
+ }
212
+ }