@tnotesjs/core 0.1.21 → 0.1.23

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 (122) hide show
  1. package/README.md +87 -87
  2. package/dist/{chunk-4MO7F7Q4.js → chunk-BSTQOJVE.js} +1 -1
  3. package/dist/{chunk-SQVHJR2Z.js → chunk-O4DCXHOV.js} +0 -3
  4. package/dist/cli/index.js +2 -2
  5. package/dist/index.js +1 -1
  6. package/dist/vitepress/config/index.js +2 -2
  7. package/package.json +3 -4
  8. package/types/config.ts +60 -61
  9. package/types/index.ts +11 -11
  10. package/types/note.ts +33 -33
  11. package/vitepress/assets/icons/icon__check.svg +3 -3
  12. package/vitepress/assets/icons/icon__clipboard.svg +7 -7
  13. package/vitepress/assets/icons/icon__close.svg +1 -1
  14. package/vitepress/assets/icons/icon__collapse.svg +1 -1
  15. package/vitepress/assets/icons/icon__confirm.svg +1 -1
  16. package/vitepress/assets/icons/icon__copy.svg +4 -4
  17. package/vitepress/assets/icons/icon__focus.svg +1 -1
  18. package/vitepress/assets/icons/icon__fold.svg +3 -3
  19. package/vitepress/assets/icons/icon__folder.svg +1 -1
  20. package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
  21. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
  22. package/vitepress/assets/icons/icon__github.svg +3 -3
  23. package/vitepress/assets/icons/icon__mindmap.svg +1 -1
  24. package/vitepress/assets/icons/icon__next.svg +1 -1
  25. package/vitepress/assets/icons/icon__number_gray.svg +1 -1
  26. package/vitepress/assets/icons/icon__number_purple.svg +1 -1
  27. package/vitepress/assets/icons/icon__prev.svg +1 -1
  28. package/vitepress/assets/icons/icon__restore.svg +1 -1
  29. package/vitepress/assets/icons/icon__rotate.svg +4 -4
  30. package/vitepress/assets/icons/icon__search.svg +1 -1
  31. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
  32. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
  33. package/vitepress/assets/icons/icon__totop.svg +5 -5
  34. package/vitepress/assets/icons/icon__vscode.svg +5 -5
  35. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
  36. package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
  37. package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
  38. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
  39. package/vitepress/assets/icons/index.ts +39 -39
  40. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
  41. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +369 -369
  42. package/vitepress/components/CodeBlockFullscreen/index.ts +209 -213
  43. package/vitepress/components/CodeBlockFullscreen/styles.css +126 -126
  44. package/vitepress/components/Discussions/Discussions.vue +244 -211
  45. package/vitepress/components/EnWordList/EnWordList.vue +663 -543
  46. package/vitepress/components/EnWordList/RightClickMenu.vue +89 -66
  47. package/vitepress/components/Footprints/Footprints.vue +472 -377
  48. package/vitepress/components/Layout/AboutModal.vue +331 -105
  49. package/vitepress/components/Layout/AboutPanel.vue +484 -270
  50. package/vitepress/components/Layout/ContentCollapse.vue +590 -603
  51. package/vitepress/components/Layout/CustomSidebar.vue +817 -817
  52. package/vitepress/components/Layout/DocBeforeControls.vue +351 -149
  53. package/vitepress/components/Layout/DocFooter.vue +233 -233
  54. package/vitepress/components/Layout/ImagePreview.vue +484 -281
  55. package/vitepress/components/Layout/Layout.vue +744 -621
  56. package/vitepress/components/Layout/NavBarSettingsTrigger.vue +0 -2
  57. package/vitepress/components/Layout/NoteStatus.vue +140 -140
  58. package/vitepress/components/Layout/SidebarItems.vue +1083 -1083
  59. package/vitepress/components/Layout/SidebarNavBefore.vue +170 -171
  60. package/vitepress/components/Layout/SidebarResizeHandle.vue +311 -319
  61. package/vitepress/components/Layout/Swiper.vue +167 -167
  62. package/vitepress/components/Layout/ToggleFullContent.vue +47 -35
  63. package/vitepress/components/Layout/ToggleSidebar.vue +37 -26
  64. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
  65. package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -119
  66. package/vitepress/components/Layout/composables/useNoteSave.ts +178 -178
  67. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
  68. package/vitepress/components/Layout/composables/useRedirect.ts +113 -113
  69. package/vitepress/components/Layout/composables/useSidebarLayout.ts +122 -122
  70. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
  71. package/vitepress/components/Layout/homeReadme.data.ts +124 -124
  72. package/vitepress/components/LoadingPage/LoadingPage.vue +1 -6
  73. package/vitepress/components/MarkMap/MarkMap.vue +564 -405
  74. package/vitepress/components/Mermaid/Mermaid.vue +643 -365
  75. package/vitepress/components/NotesTable/NotesTable.vue +174 -98
  76. package/vitepress/components/NotesTable/README.md +67 -67
  77. package/vitepress/components/Settings/Settings.vue +711 -334
  78. package/vitepress/components/Settings/SettingsDialog.vue +1 -11
  79. package/vitepress/components/SidebarCard/MindMapView.vue +484 -484
  80. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
  81. package/vitepress/components/SidebarCard/SidebarCard.vue +945 -946
  82. package/vitepress/components/Tooltip/Tooltip.vue +70 -70
  83. package/vitepress/components/constants.ts +109 -109
  84. package/vitepress/components/notesConfig.data.ts +73 -73
  85. package/vitepress/components/sidebar.data.ts +59 -59
  86. package/vitepress/components/tnotes-config.data.ts +21 -21
  87. package/vitepress/components/utils.ts +26 -26
  88. package/vitepress/config/index.ts +169 -169
  89. package/vitepress/configs/constants.ts +26 -26
  90. package/vitepress/configs/head.config.ts +25 -25
  91. package/vitepress/configs/index.ts +9 -9
  92. package/vitepress/configs/markdown-it.d.ts +23 -23
  93. package/vitepress/configs/markdown.config.ts +368 -367
  94. package/vitepress/configs/theme.config.ts +108 -108
  95. package/vitepress/plugins/buildProgressPlugin.ts +434 -434
  96. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
  97. package/vitepress/plugins/renameNotePlugin.ts +68 -68
  98. package/vitepress/plugins/sidebarStructurePlugin.ts +260 -260
  99. package/vitepress/plugins/updateConfigPlugin.ts +63 -63
  100. package/vitepress/theme/index.ts +137 -137
  101. package/vitepress/theme/styles/base.scss +66 -64
  102. package/vitepress/theme/styles/components/404.scss +31 -31
  103. package/vitepress/theme/styles/components/collapse.scss +172 -172
  104. package/vitepress/theme/styles/components/markmap.scss +101 -101
  105. package/vitepress/theme/styles/components/swiper.scss +255 -255
  106. package/vitepress/theme/styles/index.scss +25 -25
  107. package/vitepress/theme/styles/layout.scss +78 -78
  108. package/vitepress/theme/styles/utilities.scss +39 -39
  109. package/vitepress/theme/styles/vitepress-override.scss +25 -25
  110. package/vitepress/components/Discussions/Discussions.module.scss +0 -32
  111. package/vitepress/components/EnWordList/EnWordList.module.scss +0 -124
  112. package/vitepress/components/EnWordList/RightClickMenu.module.scss +0 -22
  113. package/vitepress/components/Footprints/Footprints.module.scss +0 -93
  114. package/vitepress/components/Layout/AboutModal.module.scss +0 -233
  115. package/vitepress/components/Layout/ImagePreview.module.scss +0 -201
  116. package/vitepress/components/Layout/Layout.module.scss +0 -661
  117. package/vitepress/components/Layout/ToggleFullContent.module.scss +0 -11
  118. package/vitepress/components/Layout/ToggleSidebar.module.scss +0 -11
  119. package/vitepress/components/MarkMap/MarkMap.module.scss +0 -159
  120. package/vitepress/components/Mermaid/Mermaid.module.scss +0 -275
  121. package/vitepress/components/NotesTable/NotesTable.module.scss +0 -77
  122. package/vitepress/components/Settings/Settings.module.scss +0 -375
@@ -1,334 +1,711 @@
1
- <template>
2
- <div :class="$style.settingsWrapper">
3
- <section :class="$style.settingsSection">
4
- <div :class="$style.settingItem">
5
- <div :class="$style.settingMeta">
6
- <div :class="$style.labelLine">
7
- <span :class="$style.itemName">本地知识库路径</span>
8
- <span :class="$style.infoWrap">
9
- <span :class="$style.infoIcon">?</span>
10
- <span :class="$style.tooltip">
11
- 适用于 PC 桌面环境,需要本地安装 VS Code。配置后可从页面快速打开本地笔记目录。
12
- </span>
13
- </span>
14
- </div>
15
- <span v-if="path" :class="$style.statusText">已配置</span>
16
- </div>
17
-
18
- <div :class="$style.inputGroup">
19
- <input
20
- v-model="path"
21
- type="text"
22
- placeholder="/Users/username/notes"
23
- :class="$style.input"
24
- />
25
- <button
26
- v-if="path"
27
- @click="clearPath"
28
- :class="$style.clearBtn"
29
- title="清空"
30
- type="button"
31
- >
32
- ×
33
- </button>
34
- </div>
35
- </div>
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 v-if="showNoteId" :class="$style.statusText">显示编号</span>
49
- </div>
50
-
51
- <div :class="$style.fieldStack">
52
- <label :class="$style.checkRow">
53
- <input
54
- v-model="showNoteId"
55
- type="checkbox"
56
- :class="$style.checkbox"
57
- />
58
- <span>显示笔记编号</span>
59
- </label>
60
-
61
- <div :class="$style.controlRow">
62
- <span :class="$style.controlLabel">目录风格</span>
63
- <div :class="$style.segmented">
64
- <label
65
- v-for="option in sidebarDensityOptions"
66
- :key="option.value"
67
- :class="[
68
- $style.segment,
69
- { [$style.activeSegment]: sidebarDensity === option.value },
70
- ]"
71
- >
72
- <input
73
- v-model="sidebarDensity"
74
- type="radio"
75
- name="sidebar-density"
76
- :value="option.value"
77
- />
78
- <span>{{ option.label }}</span>
79
- </label>
80
- </div>
81
- </div>
82
-
83
- <div :class="$style.prefixGrid">
84
- <label :class="$style.field">
85
- <span :class="$style.controlLabel">已完成笔记的前缀</span>
86
- <input
87
- v-model="donePrefix"
88
- type="text"
89
- placeholder="可留空"
90
- :class="[$style.input, $style.prefixInput]"
91
- />
92
- </label>
93
-
94
- <label :class="$style.field">
95
- <span :class="$style.controlLabel">未完成笔记前缀</span>
96
- <input
97
- v-model="undonePrefix"
98
- type="text"
99
- placeholder="可留空"
100
- :class="[$style.input, $style.prefixInput]"
101
- />
102
- </label>
103
- </div>
104
- </div>
105
- </div>
106
-
107
- <div :class="$style.settingItem">
108
- <div :class="$style.settingMeta">
109
- <div :class="$style.labelLine">
110
- <span :class="$style.itemName">MarkMap 思维导图</span>
111
- <span :class="$style.infoWrap">
112
- <span :class="$style.infoIcon">?</span>
113
- <span :class="$style.tooltip">
114
- 配置 MarkMap 的默认分支配色和初始展开层级。
115
- </span>
116
- </span>
117
- </div>
118
- </div>
119
-
120
- <div :class="$style.inlineGrid">
121
- <label :class="$style.field">
122
- <span :class="$style.controlLabel">分支主题</span>
123
- <select v-model="markmapTheme" :class="$style.select">
124
- <option value="default">默认</option>
125
- <option value="colorful">多彩</option>
126
- <option value="dark">深色</option>
127
- </select>
128
- </label>
129
-
130
- <label :class="$style.field">
131
- <span :class="$style.controlLabel">展开层级</span>
132
- <div :class="$style.inputWithUnit">
133
- <input
134
- v-model.number="markmapExpandLevel"
135
- type="number"
136
- min="1"
137
- max="100"
138
- :class="$style.input"
139
- />
140
- <span :class="$style.unit">层</span>
141
- </div>
142
- </label>
143
- </div>
144
- </div>
145
- </section>
146
-
147
- <div :class="$style.actionBar">
148
- <button v-if="hasChanges" @click="reset" :class="$style.resetBtn">
149
- 重置
150
- </button>
151
- <button
152
- @click="save"
153
- :class="[$style.saveBtn, { [$style.disabled]: !hasChanges }]"
154
- :disabled="!hasChanges"
155
- >
156
- {{ saveText }}
157
- </button>
158
- </div>
159
-
160
- <Transition name="toast">
161
- <div v-if="showSuccessToast" :class="$style.toast">保存成功</div>
162
- </Transition>
163
- </div>
164
- </template>
165
-
166
- <script setup lang="ts">
167
- import { ref, computed, onMounted } from 'vue'
168
-
169
- import {
170
- NOTES_DIR_KEY,
171
- MARKMAP_THEME_KEY,
172
- MARKMAP_EXPAND_LEVEL_KEY,
173
- SIDEBAR_SHOW_NOTE_ID_KEY,
174
- SIDEBAR_DENSITY_KEY,
175
- SIDEBAR_DONE_PREFIX_KEY,
176
- SIDEBAR_UNDONE_PREFIX_KEY,
177
- } from '../constants'
178
- // @ts-expect-error - VitePress Data Loader
179
- import { data as tnotesConfig } from '../tnotes-config.data'
180
-
181
- type SidebarDensity = 'compact' | 'default' | 'loose'
182
-
183
- const DEFAULT_SIDEBAR_DENSITY: SidebarDensity = 'default'
184
- const DEFAULT_DONE_PREFIX = ''
185
- const DEFAULT_UNDONE_PREFIX = ''
186
- const sidebarDensityOptions: Array<{
187
- label: string
188
- value: SidebarDensity
189
- }> = [
190
- { label: '紧凑', value: 'compact' },
191
- { label: '默认', value: 'default' },
192
- { label: '宽松', value: 'loose' },
193
- ]
194
-
195
- const path = ref('')
196
- const originalPath = ref('')
197
- const markmapTheme = ref('default')
198
- const originalMarkmapTheme = ref('default')
199
- const markmapExpandLevel = ref(5)
200
- const originalMarkmapExpandLevel = ref(5)
201
- const showNoteId = ref(false)
202
- const originalShowNoteId = ref(false)
203
- const sidebarDensity = ref<SidebarDensity>(DEFAULT_SIDEBAR_DENSITY)
204
- const originalSidebarDensity = ref<SidebarDensity>(DEFAULT_SIDEBAR_DENSITY)
205
- const donePrefix = ref(DEFAULT_DONE_PREFIX)
206
- const originalDonePrefix = ref(DEFAULT_DONE_PREFIX)
207
- const undonePrefix = ref(DEFAULT_UNDONE_PREFIX)
208
- const originalUndonePrefix = ref(DEFAULT_UNDONE_PREFIX)
209
- const showSuccessToast = ref(false)
210
-
211
- const hasChanges = computed(
212
- () =>
213
- path.value !== originalPath.value ||
214
- markmapTheme.value !== originalMarkmapTheme.value ||
215
- markmapExpandLevel.value !== originalMarkmapExpandLevel.value ||
216
- showNoteId.value !== originalShowNoteId.value ||
217
- sidebarDensity.value !== originalSidebarDensity.value ||
218
- donePrefix.value !== originalDonePrefix.value ||
219
- undonePrefix.value !== originalUndonePrefix.value,
220
- )
221
-
222
- const saveText = computed(() => {
223
- if (!hasChanges.value) return '无更改'
224
- return '保存'
225
- })
226
-
227
- onMounted(() => {
228
- if (typeof window === 'undefined') return
229
-
230
- const savedPath = localStorage.getItem(NOTES_DIR_KEY) || ''
231
- path.value = savedPath
232
- originalPath.value = savedPath
233
-
234
- const savedTheme = localStorage.getItem(MARKMAP_THEME_KEY) || 'default'
235
- markmapTheme.value = savedTheme
236
- originalMarkmapTheme.value = savedTheme
237
-
238
- const savedLevel = localStorage.getItem(MARKMAP_EXPAND_LEVEL_KEY) || '5'
239
- markmapExpandLevel.value = parseInt(savedLevel, 10)
240
- originalMarkmapExpandLevel.value = markmapExpandLevel.value
241
-
242
- const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
243
- showNoteId.value =
244
- savedShowNoteId !== null
245
- ? savedShowNoteId === 'true'
246
- : tnotesConfig?.sidebarShowNoteId ?? false
247
- originalShowNoteId.value = showNoteId.value
248
-
249
- const savedSidebarDensity = normalizeSidebarDensity(
250
- localStorage.getItem(SIDEBAR_DENSITY_KEY),
251
- )
252
- sidebarDensity.value = savedSidebarDensity
253
- originalSidebarDensity.value = savedSidebarDensity
254
-
255
- const savedDonePrefix = localStorage.getItem(SIDEBAR_DONE_PREFIX_KEY)
256
- donePrefix.value =
257
- savedDonePrefix !== null ? savedDonePrefix : DEFAULT_DONE_PREFIX
258
- originalDonePrefix.value = donePrefix.value
259
-
260
- const savedUndonePrefix = localStorage.getItem(SIDEBAR_UNDONE_PREFIX_KEY)
261
- undonePrefix.value =
262
- savedUndonePrefix !== null ? savedUndonePrefix : DEFAULT_UNDONE_PREFIX
263
- originalUndonePrefix.value = undonePrefix.value
264
- })
265
-
266
- function normalizeSidebarDensity(value: string | null): SidebarDensity {
267
- if (value === 'compact' || value === 'default' || value === 'loose') {
268
- return value
269
- }
270
-
271
- return DEFAULT_SIDEBAR_DENSITY
272
- }
273
-
274
- function clearPath() {
275
- path.value = ''
276
- }
277
-
278
- function save() {
279
- if (!hasChanges.value) return
280
-
281
- try {
282
- const needReload =
283
- showNoteId.value !== originalShowNoteId.value ||
284
- sidebarDensity.value !== originalSidebarDensity.value ||
285
- donePrefix.value !== originalDonePrefix.value ||
286
- undonePrefix.value !== originalUndonePrefix.value
287
-
288
- localStorage.setItem(NOTES_DIR_KEY, path.value)
289
- localStorage.setItem(MARKMAP_THEME_KEY, markmapTheme.value)
290
- localStorage.setItem(
291
- MARKMAP_EXPAND_LEVEL_KEY,
292
- markmapExpandLevel.value.toString(),
293
- )
294
- localStorage.setItem(SIDEBAR_SHOW_NOTE_ID_KEY, showNoteId.value.toString())
295
- localStorage.setItem(SIDEBAR_DENSITY_KEY, sidebarDensity.value)
296
- localStorage.setItem(SIDEBAR_DONE_PREFIX_KEY, donePrefix.value)
297
- localStorage.setItem(SIDEBAR_UNDONE_PREFIX_KEY, undonePrefix.value)
298
-
299
- originalPath.value = path.value
300
- originalMarkmapTheme.value = markmapTheme.value
301
- originalMarkmapExpandLevel.value = markmapExpandLevel.value
302
- originalShowNoteId.value = showNoteId.value
303
- originalSidebarDensity.value = sidebarDensity.value
304
- originalDonePrefix.value = donePrefix.value
305
- originalUndonePrefix.value = undonePrefix.value
306
-
307
- showSuccessToast.value = true
308
- setTimeout(() => {
309
- showSuccessToast.value = false
310
- }, 2400)
311
-
312
- if (needReload) {
313
- setTimeout(() => {
314
- window.location.reload()
315
- }, 450)
316
- }
317
- } catch (error) {
318
- console.error('保存配置失败:', error)
319
- alert('保存失败,请检查浏览器设置')
320
- }
321
- }
322
-
323
- function reset() {
324
- path.value = originalPath.value
325
- markmapTheme.value = originalMarkmapTheme.value
326
- markmapExpandLevel.value = originalMarkmapExpandLevel.value
327
- showNoteId.value = originalShowNoteId.value
328
- sidebarDensity.value = originalSidebarDensity.value
329
- donePrefix.value = originalDonePrefix.value
330
- undonePrefix.value = originalUndonePrefix.value
331
- }
332
- </script>
333
-
334
- <style module src="./Settings.module.scss"></style>
1
+ <template>
2
+ <div class="settingsWrapper">
3
+ <section class="settingsSection">
4
+ <div class="settingItem">
5
+ <div class="settingMeta">
6
+ <div class="labelLine">
7
+ <span class="itemName">本地知识库路径</span>
8
+ <span class="infoWrap">
9
+ <span class="infoIcon">?</span>
10
+ <span class="tooltip">
11
+ 适用于 PC 桌面环境,需要本地安装 VS Code。配置后可从页面快速打开本地笔记目录。
12
+ </span>
13
+ </span>
14
+ </div>
15
+ <span v-if="path" class="statusText">已配置</span>
16
+ </div>
17
+
18
+ <div class="inputGroup">
19
+ <input
20
+ v-model="path"
21
+ type="text"
22
+ placeholder="/Users/username/notes"
23
+ class="input"
24
+ />
25
+ <button
26
+ v-if="path"
27
+ @click="clearPath"
28
+ class="clearBtn"
29
+ title="清空"
30
+ type="button"
31
+ >
32
+ ×
33
+ </button>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="settingItem">
38
+ <div class="settingMeta">
39
+ <div class="labelLine">
40
+ <span class="itemName">侧边栏目录</span>
41
+ <span class="infoWrap">
42
+ <span class="infoIcon">?</span>
43
+ <span class="tooltip">
44
+ 控制侧边栏笔记编号显示,以及目录行距密度。保存后会刷新页面以应用侧边栏配置。
45
+ </span>
46
+ </span>
47
+ </div>
48
+ <span v-if="showNoteId" class="statusText">显示编号</span>
49
+ </div>
50
+
51
+ <div class="fieldStack">
52
+ <label class="checkRow">
53
+ <input
54
+ v-model="showNoteId"
55
+ type="checkbox"
56
+ class="checkbox"
57
+ />
58
+ <span>显示笔记编号</span>
59
+ </label>
60
+
61
+ <div class="controlRow">
62
+ <span class="controlLabel">目录风格</span>
63
+ <div class="segmented">
64
+ <label
65
+ v-for="option in sidebarDensityOptions"
66
+ :key="option.value"
67
+ :class="[
68
+ 'segment',
69
+ { activeSegment: sidebarDensity === option.value },
70
+ ]"
71
+ >
72
+ <input
73
+ v-model="sidebarDensity"
74
+ type="radio"
75
+ name="sidebar-density"
76
+ :value="option.value"
77
+ />
78
+ <span>{{ option.label }}</span>
79
+ </label>
80
+ </div>
81
+ </div>
82
+
83
+ <div class="prefixGrid">
84
+ <label class="field">
85
+ <span class="controlLabel">已完成笔记的前缀</span>
86
+ <input
87
+ v-model="donePrefix"
88
+ type="text"
89
+ placeholder="可留空"
90
+ class="input prefixInput"
91
+ />
92
+ </label>
93
+
94
+ <label class="field">
95
+ <span class="controlLabel">未完成笔记前缀</span>
96
+ <input
97
+ v-model="undonePrefix"
98
+ type="text"
99
+ placeholder="可留空"
100
+ class="input prefixInput"
101
+ />
102
+ </label>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="settingItem">
108
+ <div class="settingMeta">
109
+ <div class="labelLine">
110
+ <span class="itemName">MarkMap 思维导图</span>
111
+ <span class="infoWrap">
112
+ <span class="infoIcon">?</span>
113
+ <span class="tooltip">
114
+ 配置 MarkMap 的默认分支配色和初始展开层级。
115
+ </span>
116
+ </span>
117
+ </div>
118
+ </div>
119
+
120
+ <div class="inlineGrid">
121
+ <label class="field">
122
+ <span class="controlLabel">分支主题</span>
123
+ <select v-model="markmapTheme" class="select">
124
+ <option value="default">默认</option>
125
+ <option value="colorful">多彩</option>
126
+ <option value="dark">深色</option>
127
+ </select>
128
+ </label>
129
+
130
+ <label class="field">
131
+ <span class="controlLabel">展开层级</span>
132
+ <div class="inputWithUnit">
133
+ <input
134
+ v-model.number="markmapExpandLevel"
135
+ type="number"
136
+ min="1"
137
+ max="100"
138
+ class="input"
139
+ />
140
+ <span class="unit">层</span>
141
+ </div>
142
+ </label>
143
+ </div>
144
+ </div>
145
+ </section>
146
+
147
+ <div class="actionBar">
148
+ <button v-if="hasChanges" @click="reset" class="resetBtn">
149
+ 重置
150
+ </button>
151
+ <button
152
+ @click="save"
153
+ class="saveBtn"
154
+ :class="{ disabled: !hasChanges }"
155
+ :disabled="!hasChanges"
156
+ >
157
+ {{ saveText }}
158
+ </button>
159
+ </div>
160
+
161
+ <Transition name="toast">
162
+ <div v-if="showSuccessToast" class="toast">保存成功</div>
163
+ </Transition>
164
+ </div>
165
+ </template>
166
+
167
+ <script setup lang="ts">
168
+ import { ref, computed, onMounted } from 'vue'
169
+
170
+ import {
171
+ NOTES_DIR_KEY,
172
+ MARKMAP_THEME_KEY,
173
+ MARKMAP_EXPAND_LEVEL_KEY,
174
+ SIDEBAR_SHOW_NOTE_ID_KEY,
175
+ SIDEBAR_DENSITY_KEY,
176
+ SIDEBAR_DONE_PREFIX_KEY,
177
+ SIDEBAR_UNDONE_PREFIX_KEY,
178
+ } from '../constants'
179
+ // @ts-expect-error - VitePress Data Loader
180
+ import { data as tnotesConfig } from '../tnotes-config.data'
181
+
182
+ type SidebarDensity = 'compact' | 'default' | 'loose'
183
+
184
+ const DEFAULT_SIDEBAR_DENSITY: SidebarDensity = 'default'
185
+ const DEFAULT_DONE_PREFIX = ''
186
+ const DEFAULT_UNDONE_PREFIX = '⏰'
187
+ const sidebarDensityOptions: Array<{
188
+ label: string
189
+ value: SidebarDensity
190
+ }> = [
191
+ { label: '紧凑', value: 'compact' },
192
+ { label: '默认', value: 'default' },
193
+ { label: '宽松', value: 'loose' },
194
+ ]
195
+
196
+ const path = ref('')
197
+ const originalPath = ref('')
198
+ const markmapTheme = ref('default')
199
+ const originalMarkmapTheme = ref('default')
200
+ const markmapExpandLevel = ref(5)
201
+ const originalMarkmapExpandLevel = ref(5)
202
+ const showNoteId = ref(false)
203
+ const originalShowNoteId = ref(false)
204
+ const sidebarDensity = ref<SidebarDensity>(DEFAULT_SIDEBAR_DENSITY)
205
+ const originalSidebarDensity = ref<SidebarDensity>(DEFAULT_SIDEBAR_DENSITY)
206
+ const donePrefix = ref(DEFAULT_DONE_PREFIX)
207
+ const originalDonePrefix = ref(DEFAULT_DONE_PREFIX)
208
+ const undonePrefix = ref(DEFAULT_UNDONE_PREFIX)
209
+ const originalUndonePrefix = ref(DEFAULT_UNDONE_PREFIX)
210
+ const showSuccessToast = ref(false)
211
+
212
+ const hasChanges = computed(
213
+ () =>
214
+ path.value !== originalPath.value ||
215
+ markmapTheme.value !== originalMarkmapTheme.value ||
216
+ markmapExpandLevel.value !== originalMarkmapExpandLevel.value ||
217
+ showNoteId.value !== originalShowNoteId.value ||
218
+ sidebarDensity.value !== originalSidebarDensity.value ||
219
+ donePrefix.value !== originalDonePrefix.value ||
220
+ undonePrefix.value !== originalUndonePrefix.value,
221
+ )
222
+
223
+ const saveText = computed(() => {
224
+ if (!hasChanges.value) return '无更改'
225
+ return '保存'
226
+ })
227
+
228
+ onMounted(() => {
229
+ if (typeof window === 'undefined') return
230
+
231
+ const savedPath = localStorage.getItem(NOTES_DIR_KEY) || ''
232
+ path.value = savedPath
233
+ originalPath.value = savedPath
234
+
235
+ const savedTheme = localStorage.getItem(MARKMAP_THEME_KEY) || 'default'
236
+ markmapTheme.value = savedTheme
237
+ originalMarkmapTheme.value = savedTheme
238
+
239
+ const savedLevel = localStorage.getItem(MARKMAP_EXPAND_LEVEL_KEY) || '5'
240
+ markmapExpandLevel.value = parseInt(savedLevel, 10)
241
+ originalMarkmapExpandLevel.value = markmapExpandLevel.value
242
+
243
+ const savedShowNoteId = localStorage.getItem(SIDEBAR_SHOW_NOTE_ID_KEY)
244
+ showNoteId.value =
245
+ savedShowNoteId !== null
246
+ ? savedShowNoteId === 'true'
247
+ : tnotesConfig?.sidebarShowNoteId ?? false
248
+ originalShowNoteId.value = showNoteId.value
249
+
250
+ const savedSidebarDensity = normalizeSidebarDensity(
251
+ localStorage.getItem(SIDEBAR_DENSITY_KEY),
252
+ )
253
+ sidebarDensity.value = savedSidebarDensity
254
+ originalSidebarDensity.value = savedSidebarDensity
255
+
256
+ const savedDonePrefix = localStorage.getItem(SIDEBAR_DONE_PREFIX_KEY)
257
+ donePrefix.value =
258
+ savedDonePrefix !== null ? savedDonePrefix : DEFAULT_DONE_PREFIX
259
+ originalDonePrefix.value = donePrefix.value
260
+
261
+ const savedUndonePrefix = localStorage.getItem(SIDEBAR_UNDONE_PREFIX_KEY)
262
+ undonePrefix.value =
263
+ savedUndonePrefix !== null ? savedUndonePrefix : DEFAULT_UNDONE_PREFIX
264
+ originalUndonePrefix.value = undonePrefix.value
265
+ })
266
+
267
+ function normalizeSidebarDensity(value: string | null): SidebarDensity {
268
+ if (value === 'compact' || value === 'default' || value === 'loose') {
269
+ return value
270
+ }
271
+
272
+ return DEFAULT_SIDEBAR_DENSITY
273
+ }
274
+
275
+ function clearPath() {
276
+ path.value = ''
277
+ }
278
+
279
+ function save() {
280
+ if (!hasChanges.value) return
281
+
282
+ try {
283
+ const needReload =
284
+ showNoteId.value !== originalShowNoteId.value ||
285
+ sidebarDensity.value !== originalSidebarDensity.value ||
286
+ donePrefix.value !== originalDonePrefix.value ||
287
+ undonePrefix.value !== originalUndonePrefix.value
288
+
289
+ localStorage.setItem(NOTES_DIR_KEY, path.value)
290
+ localStorage.setItem(MARKMAP_THEME_KEY, markmapTheme.value)
291
+ localStorage.setItem(
292
+ MARKMAP_EXPAND_LEVEL_KEY,
293
+ markmapExpandLevel.value.toString(),
294
+ )
295
+ localStorage.setItem(SIDEBAR_SHOW_NOTE_ID_KEY, showNoteId.value.toString())
296
+ localStorage.setItem(SIDEBAR_DENSITY_KEY, sidebarDensity.value)
297
+ localStorage.setItem(SIDEBAR_DONE_PREFIX_KEY, donePrefix.value)
298
+ localStorage.setItem(SIDEBAR_UNDONE_PREFIX_KEY, undonePrefix.value)
299
+
300
+ originalPath.value = path.value
301
+ originalMarkmapTheme.value = markmapTheme.value
302
+ originalMarkmapExpandLevel.value = markmapExpandLevel.value
303
+ originalShowNoteId.value = showNoteId.value
304
+ originalSidebarDensity.value = sidebarDensity.value
305
+ originalDonePrefix.value = donePrefix.value
306
+ originalUndonePrefix.value = undonePrefix.value
307
+
308
+ showSuccessToast.value = true
309
+ setTimeout(() => {
310
+ showSuccessToast.value = false
311
+ }, 2400)
312
+
313
+ if (needReload) {
314
+ setTimeout(() => {
315
+ window.location.reload()
316
+ }, 450)
317
+ }
318
+ } catch (error) {
319
+ console.error('保存配置失败:', error)
320
+ alert('保存失败,请检查浏览器设置')
321
+ }
322
+ }
323
+
324
+ function reset() {
325
+ path.value = originalPath.value
326
+ markmapTheme.value = originalMarkmapTheme.value
327
+ markmapExpandLevel.value = originalMarkmapExpandLevel.value
328
+ showNoteId.value = originalShowNoteId.value
329
+ sidebarDensity.value = originalSidebarDensity.value
330
+ donePrefix.value = originalDonePrefix.value
331
+ undonePrefix.value = originalUndonePrefix.value
332
+ }
333
+ </script>
334
+
335
+ <style scoped lang="scss">
336
+ .settingsWrapper {
337
+ max-width: 680px;
338
+ margin: 0 auto;
339
+ padding: 12px 4px 0;
340
+ }
341
+
342
+ .settingsSection {
343
+ border-top: 1px solid var(--vp-c-divider);
344
+ }
345
+
346
+ .settingItem {
347
+ display: grid;
348
+ grid-template-columns: minmax(150px, 0.42fr) minmax(220px, 1fr);
349
+ gap: 18px;
350
+ padding: 18px 0;
351
+ border-bottom: 1px solid var(--vp-c-divider);
352
+ }
353
+
354
+ .settingMeta {
355
+ min-width: 0;
356
+ }
357
+
358
+ .labelLine {
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 6px;
362
+ }
363
+
364
+ .itemName {
365
+ color: var(--vp-c-text-1);
366
+ font-size: 14px;
367
+ font-weight: 600;
368
+ line-height: 22px;
369
+ }
370
+
371
+ .statusText {
372
+ display: inline-block;
373
+ margin-top: 4px;
374
+ color: var(--vp-c-text-3);
375
+ font-size: 12px;
376
+ line-height: 18px;
377
+ }
378
+
379
+ .infoWrap {
380
+ position: relative;
381
+ display: inline-flex;
382
+ }
383
+
384
+ .infoIcon {
385
+ display: inline-flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+ width: 16px;
389
+ height: 16px;
390
+ color: var(--vp-c-text-3);
391
+ border: 1px solid var(--vp-c-divider);
392
+ border-radius: 50%;
393
+ cursor: help;
394
+ font-size: 11px;
395
+ line-height: 1;
396
+ }
397
+
398
+ .tooltip {
399
+ position: absolute;
400
+ left: 50%;
401
+ bottom: calc(100% + 8px);
402
+ z-index: 10;
403
+ width: 240px;
404
+ padding: 8px 10px;
405
+ color: var(--vp-c-text-1);
406
+ background: var(--vp-c-bg-elv);
407
+ border: 1px solid var(--vp-c-divider);
408
+ border-radius: 6px;
409
+ box-shadow: var(--vp-shadow-2);
410
+ font-size: 12px;
411
+ line-height: 18px;
412
+ opacity: 0;
413
+ pointer-events: none;
414
+ transform: translate(-50%, 4px);
415
+ transition:
416
+ opacity 0.15s ease,
417
+ transform 0.15s ease;
418
+ }
419
+
420
+ .infoWrap:hover .tooltip,
421
+ .infoWrap:focus-within .tooltip {
422
+ opacity: 1;
423
+ transform: translate(-50%, 0);
424
+ }
425
+
426
+ .inputGroup {
427
+ position: relative;
428
+ }
429
+
430
+ .input,
431
+ .select {
432
+ width: 100%;
433
+ height: 34px;
434
+ box-sizing: border-box;
435
+ color: var(--vp-c-text-1);
436
+ background: var(--vp-c-bg);
437
+ border: 1px solid var(--vp-c-divider);
438
+ border-radius: 6px;
439
+ font-size: 13px;
440
+ outline: none;
441
+ }
442
+
443
+ .input {
444
+ padding: 0 34px 0 10px;
445
+ font-family: var(--vp-font-family-mono);
446
+ }
447
+
448
+ .select {
449
+ padding: 0 10px;
450
+ cursor: pointer;
451
+ }
452
+
453
+ .input::placeholder {
454
+ color: var(--vp-c-text-3);
455
+ }
456
+
457
+ .input:focus,
458
+ .select:focus {
459
+ border-color: var(--vp-c-brand-1);
460
+ }
461
+
462
+ .clearBtn {
463
+ position: absolute;
464
+ top: 50%;
465
+ right: 7px;
466
+ display: inline-flex;
467
+ align-items: center;
468
+ justify-content: center;
469
+ width: 20px;
470
+ height: 20px;
471
+ padding: 0;
472
+ color: var(--vp-c-text-3);
473
+ background: transparent;
474
+ border: none;
475
+ border-radius: 4px;
476
+ cursor: pointer;
477
+ font-size: 16px;
478
+ transform: translateY(-50%);
479
+ }
480
+
481
+ .clearBtn:hover {
482
+ color: var(--vp-c-danger-1);
483
+ background: var(--vp-c-danger-soft);
484
+ }
485
+
486
+ .fieldStack {
487
+ display: flex;
488
+ flex-direction: column;
489
+ gap: 12px;
490
+ }
491
+
492
+ .checkRow {
493
+ display: inline-flex;
494
+ align-items: center;
495
+ gap: 8px;
496
+ width: max-content;
497
+ color: var(--vp-c-text-1);
498
+ cursor: pointer;
499
+ font-size: 13px;
500
+ line-height: 20px;
501
+ user-select: none;
502
+ }
503
+
504
+ .checkbox {
505
+ width: 16px;
506
+ height: 16px;
507
+ margin: 0;
508
+ accent-color: var(--vp-c-brand-1);
509
+ cursor: pointer;
510
+ }
511
+
512
+ .controlRow,
513
+ .field {
514
+ display: flex;
515
+ flex-direction: column;
516
+ gap: 6px;
517
+ }
518
+
519
+ .controlLabel {
520
+ color: var(--vp-c-text-2);
521
+ font-size: 12px;
522
+ line-height: 18px;
523
+ }
524
+
525
+ .segmented {
526
+ display: grid;
527
+ grid-template-columns: repeat(3, 1fr);
528
+ overflow: hidden;
529
+ border: 1px solid var(--vp-c-divider);
530
+ border-radius: 6px;
531
+ }
532
+
533
+ .segment {
534
+ display: inline-flex;
535
+ align-items: center;
536
+ justify-content: center;
537
+ min-width: 0;
538
+ height: 32px;
539
+ color: var(--vp-c-text-2);
540
+ border-right: 1px solid var(--vp-c-divider);
541
+ cursor: pointer;
542
+ font-size: 13px;
543
+ user-select: none;
544
+ }
545
+
546
+ .segment:last-child {
547
+ border-right: none;
548
+ }
549
+
550
+ .segment input {
551
+ position: absolute;
552
+ opacity: 0;
553
+ pointer-events: none;
554
+ }
555
+
556
+ .segment:hover,
557
+ .activeSegment {
558
+ color: var(--vp-c-brand-1);
559
+ background: var(--vp-c-default-soft);
560
+ }
561
+
562
+ .activeSegment {
563
+ font-weight: 600;
564
+ }
565
+
566
+ .inlineGrid {
567
+ display: grid;
568
+ grid-template-columns: 1fr 120px;
569
+ gap: 12px;
570
+ }
571
+
572
+ .prefixGrid {
573
+ display: grid;
574
+ grid-template-columns: repeat(2, minmax(0, 1fr));
575
+ gap: 12px;
576
+ }
577
+
578
+ .prefixInput {
579
+ padding-right: 10px;
580
+ font-family: var(--vp-font-family-base);
581
+ }
582
+
583
+ .inputWithUnit {
584
+ position: relative;
585
+ }
586
+
587
+ .inputWithUnit .input {
588
+ padding-right: 34px;
589
+ }
590
+
591
+ .unit {
592
+ position: absolute;
593
+ top: 50%;
594
+ right: 10px;
595
+ color: var(--vp-c-text-3);
596
+ font-size: 12px;
597
+ pointer-events: none;
598
+ transform: translateY(-50%);
599
+ }
600
+
601
+ .actionBar {
602
+ display: flex;
603
+ justify-content: flex-end;
604
+ gap: 8px;
605
+ padding-top: 16px;
606
+ }
607
+
608
+ .saveBtn,
609
+ .resetBtn {
610
+ display: inline-flex;
611
+ align-items: center;
612
+ justify-content: center;
613
+ min-width: 72px;
614
+ height: 34px;
615
+ padding: 0 14px;
616
+ border-radius: 6px;
617
+ cursor: pointer;
618
+ font-size: 13px;
619
+ font-weight: 500;
620
+ }
621
+
622
+ .saveBtn {
623
+ color: #fff;
624
+ background: var(--vp-c-brand-1);
625
+ border: 1px solid var(--vp-c-brand-1);
626
+ }
627
+
628
+ .saveBtn:hover:not(.disabled) {
629
+ background: var(--vp-c-brand-2);
630
+ }
631
+
632
+ .saveBtn.disabled {
633
+ opacity: 0.45;
634
+ cursor: not-allowed;
635
+ }
636
+
637
+ .resetBtn {
638
+ color: var(--vp-c-text-1);
639
+ background: var(--vp-c-bg);
640
+ border: 1px solid var(--vp-c-divider);
641
+ }
642
+
643
+ .resetBtn:hover {
644
+ border-color: var(--vp-c-brand-1);
645
+ }
646
+
647
+ .toast {
648
+ position: fixed;
649
+ top: 78px;
650
+ left: 50%;
651
+ z-index: 1000;
652
+ padding: 8px 12px;
653
+ color: #fff;
654
+ background: var(--vp-c-brand-1);
655
+ border-radius: 6px;
656
+ box-shadow: var(--vp-shadow-2);
657
+ font-size: 13px;
658
+ transform: translateX(-50%);
659
+ }
660
+
661
+ :global(.toast-enter-active),
662
+ :global(.toast-leave-active) {
663
+ transition: all 0.2s ease;
664
+ }
665
+
666
+ :global(.toast-enter-from),
667
+ :global(.toast-leave-to) {
668
+ opacity: 0;
669
+ transform: translate(-50%, -8px);
670
+ }
671
+
672
+ @media (max-width: 768px) {
673
+ .settingsWrapper {
674
+ padding: 0;
675
+ }
676
+
677
+ .settingItem {
678
+ grid-template-columns: 1fr;
679
+ gap: 10px;
680
+ padding: 16px 0;
681
+ }
682
+
683
+ .inlineGrid {
684
+ grid-template-columns: 1fr;
685
+ }
686
+
687
+ .prefixGrid {
688
+ grid-template-columns: 1fr;
689
+ }
690
+
691
+ .tooltip {
692
+ left: 0;
693
+ width: min(260px, calc(100vw - 48px));
694
+ transform: translate(0, 4px);
695
+ }
696
+
697
+ .infoWrap:hover .tooltip,
698
+ .infoWrap:focus-within .tooltip {
699
+ transform: translate(0, 0);
700
+ }
701
+
702
+ .actionBar {
703
+ flex-direction: column-reverse;
704
+ }
705
+
706
+ .saveBtn,
707
+ .resetBtn {
708
+ width: 100%;
709
+ }
710
+ }
711
+ </style>