@tnotesjs/core 0.1.19 → 0.1.21

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 (114) hide show
  1. package/README.md +87 -87
  2. package/package.json +1 -1
  3. package/types/config.ts +61 -61
  4. package/types/index.ts +11 -11
  5. package/types/note.ts +33 -33
  6. package/vitepress/assets/icons/icon__check.svg +3 -3
  7. package/vitepress/assets/icons/icon__clipboard.svg +7 -7
  8. package/vitepress/assets/icons/icon__close.svg +1 -1
  9. package/vitepress/assets/icons/icon__collapse.svg +1 -1
  10. package/vitepress/assets/icons/icon__confirm.svg +1 -1
  11. package/vitepress/assets/icons/icon__copy.svg +4 -4
  12. package/vitepress/assets/icons/icon__focus.svg +1 -1
  13. package/vitepress/assets/icons/icon__fold.svg +3 -3
  14. package/vitepress/assets/icons/icon__folder.svg +1 -1
  15. package/vitepress/assets/icons/icon__fullscreen.svg +1 -1
  16. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -1
  17. package/vitepress/assets/icons/icon__github.svg +3 -3
  18. package/vitepress/assets/icons/icon__mindmap.svg +1 -1
  19. package/vitepress/assets/icons/icon__next.svg +1 -1
  20. package/vitepress/assets/icons/icon__number_gray.svg +1 -1
  21. package/vitepress/assets/icons/icon__number_purple.svg +1 -1
  22. package/vitepress/assets/icons/icon__prev.svg +1 -1
  23. package/vitepress/assets/icons/icon__restore.svg +1 -1
  24. package/vitepress/assets/icons/icon__rotate.svg +4 -4
  25. package/vitepress/assets/icons/icon__search.svg +1 -1
  26. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -1
  27. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -1
  28. package/vitepress/assets/icons/icon__totop.svg +5 -5
  29. package/vitepress/assets/icons/icon__vscode.svg +5 -5
  30. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -1
  31. package/vitepress/assets/icons/icon__zoom_in.svg +1 -1
  32. package/vitepress/assets/icons/icon__zoom_out.svg +1 -1
  33. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -1
  34. package/vitepress/assets/icons/index.ts +39 -39
  35. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -20
  36. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +369 -369
  37. package/vitepress/components/CodeBlockFullscreen/index.ts +213 -213
  38. package/vitepress/components/CodeBlockFullscreen/styles.css +126 -126
  39. package/vitepress/components/Discussions/Discussions.module.scss +32 -32
  40. package/vitepress/components/Discussions/Discussions.vue +211 -211
  41. package/vitepress/components/EnWordList/EnWordList.module.scss +124 -124
  42. package/vitepress/components/EnWordList/EnWordList.vue +543 -543
  43. package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -22
  44. package/vitepress/components/EnWordList/RightClickMenu.vue +66 -66
  45. package/vitepress/components/Footprints/Footprints.module.scss +93 -93
  46. package/vitepress/components/Footprints/Footprints.vue +377 -377
  47. package/vitepress/components/Layout/AboutModal.module.scss +233 -233
  48. package/vitepress/components/Layout/AboutModal.vue +105 -105
  49. package/vitepress/components/Layout/AboutPanel.vue +270 -270
  50. package/vitepress/components/Layout/ContentCollapse.vue +603 -603
  51. package/vitepress/components/Layout/CustomSidebar.vue +817 -817
  52. package/vitepress/components/Layout/DocBeforeControls.vue +149 -149
  53. package/vitepress/components/Layout/DocFooter.vue +233 -233
  54. package/vitepress/components/Layout/ImagePreview.module.scss +201 -201
  55. package/vitepress/components/Layout/ImagePreview.vue +281 -281
  56. package/vitepress/components/Layout/Layout.module.scss +661 -661
  57. package/vitepress/components/Layout/Layout.vue +621 -621
  58. package/vitepress/components/Layout/NoteStatus.vue +140 -140
  59. package/vitepress/components/Layout/SidebarItems.vue +1083 -1083
  60. package/vitepress/components/Layout/SidebarNavBefore.vue +171 -171
  61. package/vitepress/components/Layout/SidebarResizeHandle.vue +319 -319
  62. package/vitepress/components/Layout/Swiper.vue +167 -167
  63. package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -11
  64. package/vitepress/components/Layout/ToggleFullContent.vue +35 -35
  65. package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -11
  66. package/vitepress/components/Layout/ToggleSidebar.vue +26 -26
  67. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -88
  68. package/vitepress/components/Layout/composables/useNoteConfig.ts +119 -119
  69. package/vitepress/components/Layout/composables/useNoteSave.ts +178 -178
  70. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -85
  71. package/vitepress/components/Layout/composables/useRedirect.ts +113 -113
  72. package/vitepress/components/Layout/composables/useSidebarLayout.ts +122 -122
  73. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -85
  74. package/vitepress/components/Layout/homeReadme.data.ts +124 -124
  75. package/vitepress/components/MarkMap/MarkMap.module.scss +159 -159
  76. package/vitepress/components/MarkMap/MarkMap.vue +405 -405
  77. package/vitepress/components/Mermaid/Mermaid.module.scss +275 -275
  78. package/vitepress/components/Mermaid/Mermaid.vue +365 -365
  79. package/vitepress/components/NotesTable/NotesTable.module.scss +77 -77
  80. package/vitepress/components/NotesTable/NotesTable.vue +98 -98
  81. package/vitepress/components/NotesTable/README.md +67 -67
  82. package/vitepress/components/Settings/Settings.module.scss +375 -375
  83. package/vitepress/components/Settings/Settings.vue +334 -334
  84. package/vitepress/components/SidebarCard/MindMapView.vue +484 -484
  85. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -108
  86. package/vitepress/components/SidebarCard/SidebarCard.vue +946 -946
  87. package/vitepress/components/Tooltip/Tooltip.vue +70 -70
  88. package/vitepress/components/constants.ts +109 -109
  89. package/vitepress/components/notesConfig.data.ts +73 -73
  90. package/vitepress/components/sidebar.data.ts +59 -59
  91. package/vitepress/components/tnotes-config.data.ts +21 -21
  92. package/vitepress/components/utils.ts +26 -26
  93. package/vitepress/config/index.ts +169 -169
  94. package/vitepress/configs/constants.ts +26 -26
  95. package/vitepress/configs/head.config.ts +25 -25
  96. package/vitepress/configs/index.ts +9 -9
  97. package/vitepress/configs/markdown-it.d.ts +23 -23
  98. package/vitepress/configs/markdown.config.ts +366 -366
  99. package/vitepress/configs/theme.config.ts +108 -108
  100. package/vitepress/plugins/buildProgressPlugin.ts +434 -434
  101. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -107
  102. package/vitepress/plugins/renameNotePlugin.ts +68 -68
  103. package/vitepress/plugins/sidebarStructurePlugin.ts +260 -260
  104. package/vitepress/plugins/updateConfigPlugin.ts +63 -63
  105. package/vitepress/theme/index.ts +137 -137
  106. package/vitepress/theme/styles/base.scss +64 -59
  107. package/vitepress/theme/styles/components/404.scss +31 -31
  108. package/vitepress/theme/styles/components/collapse.scss +172 -172
  109. package/vitepress/theme/styles/components/markmap.scss +101 -101
  110. package/vitepress/theme/styles/components/swiper.scss +255 -255
  111. package/vitepress/theme/styles/index.scss +25 -25
  112. package/vitepress/theme/styles/layout.scss +78 -78
  113. package/vitepress/theme/styles/utilities.scss +39 -39
  114. package/vitepress/theme/styles/vitepress-override.scss +25 -25
@@ -1,334 +1,334 @@
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="$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>