@tnotesjs/core 0.7.0 → 0.8.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 (55) hide show
  1. package/commands/BaseCommand.ts +58 -0
  2. package/commands/build/BuildCommand.ts +25 -0
  3. package/commands/build/PreviewCommand.ts +29 -0
  4. package/commands/build/index.ts +8 -0
  5. package/commands/dev/DevCommand.ts +75 -0
  6. package/commands/dev/index.ts +7 -0
  7. package/commands/git/PullCommand.ts +25 -0
  8. package/commands/git/PushCommand.ts +64 -0
  9. package/commands/git/index.ts +8 -0
  10. package/commands/index.ts +11 -0
  11. package/commands/init-sub-repo/InitSubRepoCommand.ts +206 -0
  12. package/commands/init-sub-repo/index.ts +1 -0
  13. package/commands/misc/HelpCommand.ts +104 -0
  14. package/commands/misc/index.ts +7 -0
  15. package/commands/models.ts +87 -0
  16. package/commands/note/CreateNoteCommand.ts +160 -0
  17. package/commands/note/RenameNoteCommand.ts +147 -0
  18. package/commands/note/UpdateNoteConfigCommand.ts +78 -0
  19. package/commands/note/index.ts +9 -0
  20. package/commands/registry.ts +47 -0
  21. package/commands/update/UpdateCommand.ts +219 -0
  22. package/commands/update/index.ts +7 -0
  23. package/commands/update-completed-count/UpdateCompletedCountCommand.ts +208 -0
  24. package/commands/update-completed-count/index.ts +5 -0
  25. package/dist/markdown/index.cjs +6 -9
  26. package/dist/markdown/index.js +6 -9
  27. package/dist/vitepress/config/index.cjs +214 -58
  28. package/dist/vitepress/config/index.js +208 -52
  29. package/markdown/components.ts +86 -0
  30. package/markdown/index.ts +17 -0
  31. package/markdown/noteFormatter.test.ts +44 -0
  32. package/markdown/noteFormatter.ts +237 -0
  33. package/package.json +7 -3
  34. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +9 -18
  35. package/vitepress/components/EnWordList/EnWordList.vue +16 -662
  36. package/vitepress/components/Footprints/Footprints.vue +15 -537
  37. package/vitepress/components/Mermaid/Mermaid.vue +13 -588
  38. package/vitepress/components/MindmapPreview/MindmapPreview.vue +12 -434
  39. package/vitepress/components/MindmapPreview/markdown.ts +1 -1
  40. package/vitepress/components/NotesTable/NotesTable.vue +11 -130
  41. package/vitepress/configs/markdown.config.ts +170 -26
  42. package/vitepress/theme/index.ts +9 -13
  43. package/vitepress/theme/styles/base.scss +15 -0
  44. package/workspace/atomic.ts +113 -0
  45. package/workspace/errors.ts +27 -0
  46. package/workspace/index.ts +40 -0
  47. package/workspace/mutationQueue.ts +28 -0
  48. package/workspace/paths.ts +64 -0
  49. package/workspace/reconcile.test.ts +300 -0
  50. package/workspace/reconcile.ts +95 -0
  51. package/workspace/scanner.ts +292 -0
  52. package/workspace/types.ts +224 -0
  53. package/workspace/workspace.test.ts +333 -0
  54. package/workspace/workspace.ts +1020 -0
  55. package/vitepress/components/EnWordList/RightClickMenu.vue +0 -93
@@ -1,442 +1,20 @@
1
+ <!-- Legacy path: re-export shared Mindmap as MindmapPreview for old fence output. -->
1
2
  <script setup lang="ts">
2
- import { CanvasViewer, MindmapSession } from '@tnotesjs/mindmap-core'
3
- import { onContentUpdated, useData } from 'vitepress'
4
- import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
3
+ import { Mindmap } from '@tnotesjs/ui'
5
4
 
6
- import { applyInitialExpandLevel } from './expandLevel'
7
- import { normalizeMindmapMarkdown } from './markdown'
8
- import MindmapOutlineNode from './MindmapOutlineNode.vue'
9
- import MindmapViewIcon from './MindmapViewIcon.vue'
10
- import { gateMindmapWheel } from './wheelInteraction'
11
- import { icon__fullscreen, icon__fullscreen_exit, icon__zoom_fit } from '../../assets/icons'
12
-
13
- type PreviewView = 'mindmap' | 'outline' | 'source'
14
-
15
- const props = withDefaults(defineProps<{
5
+ defineProps<{
16
6
  content?: string
7
+ source?: string
17
8
  initialExpandLevel?: number
18
- }>(), {
19
- content: '',
20
- initialExpandLevel: 3,
21
- })
22
-
23
- const { isDark } = useData()
24
- const activeView = ref<PreviewView>('mindmap')
25
- const previewRoot = ref<HTMLElement | null>(null)
26
- const canvasHost = ref<HTMLElement | null>(null)
27
- const session = shallowRef<MindmapSession | null>(null)
28
- const renderVersion = ref(0)
29
- const isFullscreen = ref(false)
30
- const isCanvasActive = ref(false)
31
- let viewer: CanvasViewer | null = null
32
- let mounted = false
33
-
34
- const viewOptions = [
35
- { value: 'mindmap', label: '脑图' },
36
- { value: 'outline', label: '大纲' },
37
- { value: 'source', label: '源码' },
38
- ] as const
39
-
40
- function decodeContent(value: string): string {
41
- try {
42
- return decodeURIComponent(value)
43
- } catch {
44
- return value
45
- }
46
- }
47
-
48
- const normalizedContent = computed(() => normalizeMindmapMarkdown(decodeContent(props.content)))
49
-
50
- function createViewer(): void {
51
- if (!mounted || !canvasHost.value || !session.value || viewer) return
52
- viewer = new CanvasViewer(canvasHost.value, session.value, {
53
- theme: isDark.value ? 'dark' : 'light',
54
- })
55
- }
56
-
57
- function rebuildSession(): void {
58
- viewer?.destroy()
59
- viewer = null
60
- const next = new MindmapSession({
61
- markdown: normalizedContent.value,
62
- fileName: 'mindmap-preview.tn-mindmap.md',
63
- })
64
- applyInitialExpandLevel(next, props.initialExpandLevel)
65
- const invalidate = () => { renderVersion.value += 1 }
66
- next.on('collapseChange', invalidate)
67
- next.on('focusChange', invalidate)
68
- next.on('change', invalidate)
69
- session.value = next
70
- renderVersion.value += 1
71
- void nextTick(createViewer)
72
- }
73
-
74
- function setView(view: PreviewView): void {
75
- activeView.value = view
76
- if (view !== 'mindmap') isCanvasActive.value = false
77
- if (view === 'mindmap') void nextTick(() => viewer?.zoomToFit())
78
- }
79
-
80
- function toggleNode(id: string): void {
81
- session.value?.toggleCollapse(id)
82
- }
83
-
84
- function exitFocusTo(index: number): void {
85
- session.value?.exitFocusTo(index)
86
- }
87
-
88
- async function toggleFullscreen(): Promise<void> {
89
- const root = previewRoot.value
90
- if (!root) return
91
-
92
- try {
93
- if (document.fullscreenElement === root) await document.exitFullscreen()
94
- else await root.requestFullscreen()
95
- } catch {
96
- // Fullscreen can be rejected by the browser or an embedded document policy.
97
- }
98
- }
99
-
100
- function handleFullscreenChange(): void {
101
- isFullscreen.value = document.fullscreenElement === previewRoot.value
102
- if (activeView.value === 'mindmap') void nextTick(() => viewer?.zoomToFit())
103
- }
104
-
105
- function activateCanvas(): void {
106
- isCanvasActive.value = true
107
- }
108
-
109
- function handleCanvasWheelCapture(event: WheelEvent): void {
110
- gateMindmapWheel(event, isCanvasActive.value)
111
- }
112
-
113
- function handleDocumentPointerDown(event: PointerEvent): void {
114
- if (event.target instanceof Node && !previewRoot.value?.contains(event.target)) {
115
- isCanvasActive.value = false
116
- }
117
- }
118
-
119
- function handleDocumentKeydown(event: KeyboardEvent): void {
120
- if (event.key !== 'Escape' || !isCanvasActive.value) return
121
- isCanvasActive.value = false
122
- canvasHost.value?.blur()
123
- }
124
-
125
- watch([normalizedContent, () => props.initialExpandLevel], rebuildSession, { immediate: true })
126
- watch(isDark, (dark) => viewer?.setTheme(dark ? 'dark' : 'light'))
127
-
128
- onMounted(() => {
129
- mounted = true
130
- document.addEventListener('fullscreenchange', handleFullscreenChange)
131
- document.addEventListener('pointerdown', handleDocumentPointerDown, true)
132
- document.addEventListener('keydown', handleDocumentKeydown, true)
133
- createViewer()
134
- })
135
-
136
- onContentUpdated(() => {
137
- if (activeView.value === 'mindmap') void nextTick(() => viewer?.zoomToFit())
138
- })
139
-
140
- onBeforeUnmount(() => {
141
- mounted = false
142
- document.removeEventListener('fullscreenchange', handleFullscreenChange)
143
- document.removeEventListener('pointerdown', handleDocumentPointerDown, true)
144
- document.removeEventListener('keydown', handleDocumentKeydown, true)
145
- viewer?.destroy()
146
- viewer = null
147
- })
9
+ isDark?: boolean
10
+ }>()
148
11
  </script>
149
12
 
150
13
  <template>
151
- <section
152
- ref="previewRoot"
153
- class="mindmap-preview"
154
- :class="{ 'is-dark': isDark, 'is-fullscreen': isFullscreen }"
155
- :data-version="renderVersion"
156
- >
157
- <div class="mindmap-preview-actions">
158
- <nav class="mindmap-preview-tabs" aria-label="脑图预览视图">
159
- <button
160
- v-for="item in viewOptions"
161
- :key="item.value"
162
- type="button"
163
- class="mindmap-preview-action"
164
- :class="{ 'is-active': activeView === item.value }"
165
- :aria-label="item.label"
166
- :aria-pressed="activeView === item.value"
167
- :title="item.label"
168
- @click="setView(item.value)"
169
- >
170
- <MindmapViewIcon :view="item.value" />
171
- </button>
172
- </nav>
173
- <span class="mindmap-preview-action-divider" aria-hidden="true" />
174
- <button
175
- v-if="activeView === 'mindmap'"
176
- type="button"
177
- class="mindmap-preview-action"
178
- aria-label="适应视口"
179
- title="适应视口"
180
- @click="viewer?.zoomToFit()"
181
- >
182
- <img :src="icon__zoom_fit" alt="" />
183
- </button>
184
- <button
185
- type="button"
186
- class="mindmap-preview-action"
187
- :aria-label="isFullscreen ? '退出全屏' : '全屏查看'"
188
- :title="isFullscreen ? '退出全屏' : '全屏查看'"
189
- @click="toggleFullscreen"
190
- >
191
- <img :src="isFullscreen ? icon__fullscreen_exit : icon__fullscreen" alt="" />
192
- </button>
193
- </div>
194
-
195
- <nav v-if="session && session.focusPath.length > 0" class="mindmap-focus-path" aria-label="当前主题路径">
196
- <button type="button" @click="exitFocusTo(0)">全部</button>
197
- <template v-for="(node, index) in session.focusPath" :key="node.id">
198
- <span aria-hidden="true">/</span>
199
- <button type="button" @click="exitFocusTo(index + 1)">{{ node.content.text }}</button>
200
- </template>
201
- </nav>
202
-
203
- <div
204
- v-show="activeView === 'mindmap'"
205
- ref="canvasHost"
206
- class="mindmap-canvas-host"
207
- :class="{ 'is-interaction-active': isCanvasActive }"
208
- @pointerdown.capture="activateCanvas"
209
- @wheel.capture="handleCanvasWheelCapture"
210
- />
211
-
212
- <div v-if="activeView === 'outline' && session" class="mindmap-outline" :data-version="renderVersion">
213
- <ul class="mindmap-outline-root">
214
- <MindmapOutlineNode
215
- :node="session.focusRootNode"
216
- :version="renderVersion"
217
- root
218
- @toggle="toggleNode"
219
- />
220
- </ul>
221
- </div>
222
-
223
- <pre v-if="activeView === 'source'" class="mindmap-source"><code>{{ normalizedContent }}</code></pre>
224
- </section>
14
+ <Mindmap
15
+ :content="content"
16
+ :source="source"
17
+ :initial-expand-level="initialExpandLevel"
18
+ :is-dark="isDark"
19
+ />
225
20
  </template>
226
-
227
- <style scoped lang="scss">
228
- .mindmap-preview {
229
- --mindmap-panel: var(--vp-c-bg-soft);
230
- --mindmap-border: var(--vp-c-divider);
231
- position: relative;
232
- margin: 1.5rem 0;
233
- overflow: hidden;
234
- border: 1px solid var(--mindmap-border);
235
- border-radius: 10px;
236
- background: var(--vp-c-bg);
237
- }
238
-
239
- .mindmap-preview-actions {
240
- position: absolute;
241
- top: 8px;
242
- right: 8px;
243
- z-index: 20;
244
- display: flex;
245
- align-items: center;
246
- gap: 4px;
247
- padding: 2px;
248
- border: .1px solid var(--vp-c-divider);
249
- border-radius: 7px;
250
- background-color: color-mix(in srgb, var(--vp-code-block-bg) 92%, transparent);
251
- box-shadow: var(--vp-shadow-1);
252
- opacity: 0;
253
- pointer-events: none;
254
- transition: opacity .2s;
255
- }
256
-
257
- .mindmap-preview-tabs {
258
- display: flex;
259
- gap: 4px;
260
- }
261
-
262
- .mindmap-preview:hover > .mindmap-preview-actions,
263
- .mindmap-preview-actions:focus-within {
264
- opacity: 1;
265
- pointer-events: auto;
266
- }
267
-
268
- .mindmap-preview-action {
269
- display: inline-flex;
270
- align-items: center;
271
- justify-content: center;
272
- width: 32px;
273
- height: 32px;
274
- padding: 0;
275
- border: 0;
276
- border-radius: 6px;
277
- background: transparent;
278
- color: var(--vp-c-brand-1);
279
- cursor: pointer;
280
- transition: background-color .2s, transform .2s;
281
-
282
- svg,
283
- img {
284
- width: 18px;
285
- height: 18px;
286
- }
287
-
288
- &:hover,
289
- &.is-active {
290
- background-color: var(--vp-c-default-soft);
291
- }
292
-
293
- &.is-active {
294
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vp-c-brand-1) 35%, transparent);
295
- }
296
-
297
- &:hover { transform: scale(1.05); }
298
- &:active { transform: scale(.95); }
299
- }
300
-
301
- .mindmap-preview-action-divider {
302
- width: 1px;
303
- height: 20px;
304
- margin: 0 1px;
305
- background: var(--vp-c-divider);
306
- }
307
-
308
- .mindmap-focus-path button {
309
- color: var(--vp-c-text-2);
310
- font-size: 12px;
311
-
312
- &:hover { color: var(--vp-c-brand-1); }
313
- }
314
-
315
- .mindmap-focus-path {
316
- display: flex;
317
- gap: 6px;
318
- align-items: center;
319
- padding: 6px 12px;
320
- overflow-x: auto;
321
- border-bottom: 1px solid var(--mindmap-border);
322
- white-space: nowrap;
323
- color: var(--vp-c-text-3);
324
- }
325
-
326
- .mindmap-canvas-host {
327
- position: relative;
328
- width: 100%;
329
- height: 440px;
330
- overflow: hidden;
331
- background: var(--vp-c-bg);
332
- touch-action: none;
333
- user-select: none;
334
-
335
- &.is-interaction-active {
336
- box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--vp-c-brand-1) 38%, transparent);
337
- }
338
- }
339
-
340
- .mindmap-canvas-host:deep(.mm-canvas),
341
- .mindmap-canvas-host:deep(.mm-overlay) {
342
- position: absolute;
343
- inset: 0;
344
- width: 100%;
345
- height: 100%;
346
- }
347
-
348
- .mindmap-canvas-host:deep(.mm-overlay) { pointer-events: none; }
349
- .mindmap-canvas-host:deep(.mm-editor) { outline: none; }
350
-
351
- .mindmap-outline {
352
- max-height: 560px;
353
- padding: 18px 22px 22px;
354
- overflow: auto;
355
- }
356
-
357
- .mindmap-outline-root,
358
- .mindmap-outline :deep(ul) {
359
- margin: 0;
360
- padding: 0;
361
- list-style: none;
362
- }
363
-
364
- .mindmap-outline :deep(.mindmap-outline-children) {
365
- margin-left: 10px;
366
- padding-left: 19px;
367
- border-left: 1px solid var(--vp-c-divider);
368
- }
369
-
370
- .mindmap-outline :deep(.mindmap-outline-row) {
371
- display: flex;
372
- align-items: flex-start;
373
- gap: 7px;
374
- min-height: 30px;
375
- padding: 3px 0;
376
- color: var(--vp-c-text-1);
377
- line-height: 24px;
378
- }
379
-
380
- .mindmap-outline :deep(.mindmap-outline-toggle),
381
- .mindmap-outline :deep(.mindmap-outline-leaf) {
382
- flex: 0 0 18px;
383
- width: 18px;
384
- color: var(--vp-c-text-3);
385
- text-align: center;
386
- }
387
-
388
- .mindmap-outline :deep(.mindmap-outline-toggle:hover) { color: var(--vp-c-brand-1); }
389
- .mindmap-outline :deep(.mindmap-outline-checkbox) { margin-top: 5px; }
390
- .mindmap-outline :deep(.mindmap-outline-label) { min-width: 0; overflow-wrap: anywhere; }
391
- .mindmap-outline :deep(.mindmap-outline-node.is-root > .mindmap-outline-row) { font-size: 18px; font-weight: 700; }
392
- .mindmap-outline :deep(.mindmap-outline-node.is-done > .mindmap-outline-row .mindmap-outline-label) { opacity: .58; text-decoration: line-through; }
393
- .mindmap-outline :deep(.mindmap-outline-image) { display: block; max-width: min(100%, 560px); max-height: 360px; margin: 5px 0 12px 25px; border-radius: 6px; }
394
- .mindmap-outline :deep(.is-bold) { font-weight: 700; }
395
- .mindmap-outline :deep(.is-italic) { font-style: italic; }
396
- .mindmap-outline :deep(.is-underline) { text-decoration: underline; }
397
- .mindmap-outline :deep(.is-strike) { text-decoration: line-through; }
398
- .mindmap-outline :deep(.is-highlight) { padding: 0 2px; border-radius: 2px; background: #ffe56b; color: #252525; }
399
- .mindmap-outline :deep(.is-code) { padding: 1px 5px; border-radius: 4px; background: var(--vp-c-bg-soft); color: var(--vp-c-danger-1); font-family: var(--vp-font-family-mono); }
400
- .mindmap-outline :deep(.is-link) { color: var(--vp-c-brand-1); text-decoration: underline; text-underline-offset: 3px; }
401
-
402
- .mindmap-source {
403
- max-height: 560px;
404
- margin: 0;
405
- padding: 18px 22px;
406
- overflow: auto;
407
- border-radius: 0;
408
- background: var(--vp-code-block-bg);
409
- color: var(--vp-code-block-color);
410
- font-size: 13px;
411
- line-height: 1.65;
412
- white-space: pre;
413
- }
414
-
415
- .mindmap-preview:fullscreen {
416
- display: flex;
417
- width: 100%;
418
- height: 100%;
419
- margin: 0;
420
- border: 0;
421
- border-radius: 0;
422
- background: var(--vp-c-bg);
423
- flex-direction: column;
424
-
425
- .mindmap-canvas-host,
426
- .mindmap-outline,
427
- .mindmap-source {
428
- flex: 1 1 auto;
429
- max-height: none;
430
- min-height: 0;
431
- }
432
-
433
- .mindmap-canvas-host { height: auto; }
434
- }
435
-
436
- @media (max-width: 768px) {
437
- .mindmap-canvas-host { height: 360px; }
438
- .mindmap-preview-actions { top: 6px; right: 6px; }
439
- .mindmap-preview-action { width: 28px; height: 28px; }
440
- .mindmap-outline { padding-inline: 12px; }
441
- }
442
- </style>
@@ -33,7 +33,7 @@ export function parseMindmapFence(openLine: string): MindmapFenceOptions | null
33
33
  return options
34
34
  }
35
35
 
36
- /** Parse `<<< file.md [title]`; the title is optional and paths may be quoted. */
36
+ /** @deprecated Mindmap fences no longer resolve `<<<` includes; kept for tests / migration docs. */
37
37
  export function parseMindmapReference(line: string): MindmapReference | null {
38
38
  const match = line.trim().match(/^<<<\s+(.+?)\s*$/)
39
39
  if (!match) return null
@@ -1,42 +1,14 @@
1
- <!--
2
- vitepress/components/NotesTable/NotesTable.vue
1
+ <!--
2
+ VitePress adapter: resolves note ids via notesConfig + useData,
3
+ then renders host-neutral @tnotesjs/ui NotesTable.
3
4
  -->
4
-
5
5
  <template>
6
- <div v-if="errorMessage" class="error">
7
- {{ errorMessage }}
8
- </div>
9
-
10
- <div v-else-if="notFoundIds.length > 0" class="warning">
11
- 以下笔记 ID 未找到配置: {{ notFoundIds.join(', ') }}
12
- </div>
13
-
14
- <table v-if="tableData.length > 0" class="notesTable">
15
- <thead>
16
- <tr>
17
- <th>笔记</th>
18
- <th>简介</th>
19
- </tr>
20
- </thead>
21
- <tbody>
22
- <tr v-for="note in tableData" :key="note.id">
23
- <td>
24
- <a :href="note.url" class="noteLink">
25
- <span class="noteId">{{ note.id }}.</span>
26
- <span>{{ note.title }}</span>
27
- </a>
28
- </td>
29
- <td>
30
- <span class="description" :class="{ empty: !note.description }">
31
- {{ note.description || '暂无简介' }}
32
- </span>
33
- </td>
34
- </tr>
35
- </tbody>
36
- </table>
6
+ <NotesTable :notes="tableData" :missing-ids="notFoundIds" :error="errorMessage" />
37
7
  </template>
38
8
 
39
9
  <script setup lang="ts">
10
+ import { NotesTable } from '@tnotesjs/ui'
11
+ import type { NotesTableRow } from '@tnotesjs/ui'
40
12
  import { useData } from 'vitepress'
41
13
  import { computed } from 'vue'
42
14
 
@@ -50,7 +22,6 @@ interface Props {
50
22
  const props = defineProps<Props>()
51
23
  const vpData = useData()
52
24
 
53
- // 错误消息
54
25
  const errorMessage = computed(() => {
55
26
  if (!props.ids || !Array.isArray(props.ids)) {
56
27
  return '错误: ids 属性必须是一个数组'
@@ -61,119 +32,29 @@ const errorMessage = computed(() => {
61
32
  return null
62
33
  })
63
34
 
64
- // 未找到的笔记 ID
65
35
  const notFoundIds = computed(() => {
66
36
  if (errorMessage.value) return []
67
-
68
37
  return props.ids.filter((id) => !allNotesConfig[id])
69
38
  })
70
39
 
71
- // 表格数据
72
- const tableData = computed(() => {
40
+ const tableData = computed((): NotesTableRow[] => {
73
41
  if (errorMessage.value) return []
74
-
42
+ const base = vpData.site.value.base || '/'
75
43
  return props.ids
76
- .filter((id) => allNotesConfig[id]) // 只保留存在的笔记
44
+ .filter((id) => allNotesConfig[id])
77
45
  .map((id) => {
78
46
  const config = allNotesConfig[id]
79
- const base = vpData.site.value.base || '/'
80
-
81
- // 从 redirect 路径中提取笔记标题
82
- // redirect 格式: notes/0001. 标题/README
83
47
  let title = id
84
48
  if (config.redirect) {
85
49
  const match = config.redirect.match(/notes\/\d{4}\.\s*([^/]+)\/README/)
86
- if (match) {
87
- title = match[1]
88
- }
50
+ if (match) title = match[1]
89
51
  }
90
-
91
52
  return {
92
53
  id,
93
54
  title,
94
55
  description: config.description || '',
95
- url: config.redirect ? `${base}${config.redirect}` : '#',
56
+ url: config.redirect ? `${base}${config.redirect}` : '#'
96
57
  }
97
58
  })
98
59
  })
99
60
  </script>
100
-
101
- <style scoped lang="scss">
102
- .notesTable {
103
- width: 100%;
104
- border-collapse: collapse;
105
- margin: 1.5rem 0;
106
- font-size: 0.95rem;
107
-
108
- th,
109
- td {
110
- padding: 0.75rem 1rem;
111
- text-align: left;
112
- border: 1px solid var(--vp-c-divider);
113
- }
114
-
115
- th {
116
- background-color: var(--vp-c-bg-soft);
117
- font-weight: 600;
118
- color: var(--vp-c-text-1);
119
- }
120
-
121
- tbody tr {
122
- transition: background-color 0.2s;
123
-
124
- &:hover {
125
- background-color: var(--vp-c-bg-soft);
126
- }
127
- }
128
-
129
- td {
130
- color: var(--vp-c-text-2);
131
- }
132
-
133
- .noteLink {
134
- color: var(--vp-c-brand-1);
135
- text-decoration: none;
136
- font-weight: 500;
137
- transition: color 0.2s;
138
-
139
- &:hover {
140
- color: var(--vp-c-brand-2);
141
- text-decoration: underline;
142
- }
143
- }
144
-
145
- .noteId {
146
- font-family: var(--vp-font-family-mono);
147
- font-size: 0.9em;
148
- color: var(--vp-c-text-3);
149
- margin-right: 0.5rem;
150
- }
151
-
152
- .description {
153
- line-height: 1.6;
154
-
155
- &.empty {
156
- color: var(--vp-c-text-3);
157
- font-style: italic;
158
- }
159
- }
160
- }
161
-
162
- .error {
163
- padding: 1rem;
164
- margin: 1rem 0;
165
- border-left: 4px solid var(--vp-c-danger-1);
166
- background-color: var(--vp-c-danger-soft);
167
- color: var(--vp-c-danger-1);
168
- border-radius: 4px;
169
- }
170
-
171
- .warning {
172
- padding: 1rem;
173
- margin: 1rem 0;
174
- border-left: 4px solid var(--vp-c-warning-1);
175
- background-color: var(--vp-c-warning-soft);
176
- color: var(--vp-c-warning-1);
177
- border-radius: 4px;
178
- }
179
- </style>