@tnotesjs/core 0.1.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.

Potentially problematic release.


This version of @tnotesjs/core might be problematic. Click here for more details.

Files changed (117) hide show
  1. package/README.md +105 -0
  2. package/dist/chunk-K3X5OP3N.js +1532 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.js +4199 -0
  5. package/dist/index.d.ts +138 -0
  6. package/dist/index.js +9 -0
  7. package/package.json +74 -0
  8. package/types/config.ts +61 -0
  9. package/types/index.ts +11 -0
  10. package/types/note.ts +33 -0
  11. package/vitepress/assets/icons/icon__check.svg +3 -0
  12. package/vitepress/assets/icons/icon__clipboard.svg +8 -0
  13. package/vitepress/assets/icons/icon__close.svg +1 -0
  14. package/vitepress/assets/icons/icon__collapse.svg +1 -0
  15. package/vitepress/assets/icons/icon__confirm.svg +1 -0
  16. package/vitepress/assets/icons/icon__copy.svg +4 -0
  17. package/vitepress/assets/icons/icon__focus.svg +1 -0
  18. package/vitepress/assets/icons/icon__fold.svg +3 -0
  19. package/vitepress/assets/icons/icon__folder.svg +1 -0
  20. package/vitepress/assets/icons/icon__fullscreen.svg +1 -0
  21. package/vitepress/assets/icons/icon__fullscreen_exit.svg +1 -0
  22. package/vitepress/assets/icons/icon__github.svg +4 -0
  23. package/vitepress/assets/icons/icon__mindmap.svg +1 -0
  24. package/vitepress/assets/icons/icon__next.svg +1 -0
  25. package/vitepress/assets/icons/icon__number_gray.svg +1 -0
  26. package/vitepress/assets/icons/icon__number_purple.svg +1 -0
  27. package/vitepress/assets/icons/icon__prev.svg +1 -0
  28. package/vitepress/assets/icons/icon__restore.svg +1 -0
  29. package/vitepress/assets/icons/icon__rotate.svg +4 -0
  30. package/vitepress/assets/icons/icon__search.svg +1 -0
  31. package/vitepress/assets/icons/icon__sidebar_collapsed.svg +1 -0
  32. package/vitepress/assets/icons/icon__sidebar_opened.svg +1 -0
  33. package/vitepress/assets/icons/icon__totop.svg +6 -0
  34. package/vitepress/assets/icons/icon__vscode.svg +6 -0
  35. package/vitepress/assets/icons/icon__zoom_fit.svg +1 -0
  36. package/vitepress/assets/icons/icon__zoom_in.svg +1 -0
  37. package/vitepress/assets/icons/icon__zoom_out.svg +1 -0
  38. package/vitepress/assets/icons/icon__zoom_reset.svg +1 -0
  39. package/vitepress/assets/icons/index.ts +38 -0
  40. package/vitepress/components/BilibiliOutsidePlayer/BilibiliOutsidePlayer.vue +20 -0
  41. package/vitepress/components/CodeBlockFullscreen/CodeBlockFullscreen.vue +373 -0
  42. package/vitepress/components/CodeBlockFullscreen/index.ts +115 -0
  43. package/vitepress/components/CodeBlockFullscreen/styles.css +64 -0
  44. package/vitepress/components/Discussions/Discussions.module.scss +32 -0
  45. package/vitepress/components/Discussions/Discussions.vue +211 -0
  46. package/vitepress/components/EnWordList/EnWordList.module.scss +124 -0
  47. package/vitepress/components/EnWordList/EnWordList.vue +543 -0
  48. package/vitepress/components/EnWordList/RightClickMenu.module.scss +22 -0
  49. package/vitepress/components/EnWordList/RightClickMenu.vue +66 -0
  50. package/vitepress/components/Footprints/Footprints.module.scss +93 -0
  51. package/vitepress/components/Footprints/Footprints.vue +377 -0
  52. package/vitepress/components/Layout/AboutModal.module.scss +233 -0
  53. package/vitepress/components/Layout/AboutModal.vue +105 -0
  54. package/vitepress/components/Layout/AboutPanel.vue +266 -0
  55. package/vitepress/components/Layout/ContentCollapse.vue +603 -0
  56. package/vitepress/components/Layout/CustomSidebar.vue +605 -0
  57. package/vitepress/components/Layout/DocBeforeControls.vue +139 -0
  58. package/vitepress/components/Layout/DocFooter.vue +225 -0
  59. package/vitepress/components/Layout/ImagePreview.module.scss +201 -0
  60. package/vitepress/components/Layout/ImagePreview.vue +281 -0
  61. package/vitepress/components/Layout/Layout.module.scss +661 -0
  62. package/vitepress/components/Layout/Layout.vue +542 -0
  63. package/vitepress/components/Layout/NoteStatus.vue +140 -0
  64. package/vitepress/components/Layout/SidebarItems.vue +263 -0
  65. package/vitepress/components/Layout/SidebarNavBefore.vue +92 -0
  66. package/vitepress/components/Layout/Swiper.vue +167 -0
  67. package/vitepress/components/Layout/ToggleFullContent.module.scss +11 -0
  68. package/vitepress/components/Layout/ToggleFullContent.vue +34 -0
  69. package/vitepress/components/Layout/ToggleSidebar.module.scss +11 -0
  70. package/vitepress/components/Layout/ToggleSidebar.vue +35 -0
  71. package/vitepress/components/Layout/composables/useCollapseControl.ts +88 -0
  72. package/vitepress/components/Layout/composables/useNoteConfig.ts +121 -0
  73. package/vitepress/components/Layout/composables/useNoteSave.ts +173 -0
  74. package/vitepress/components/Layout/composables/useNoteValidation.ts +85 -0
  75. package/vitepress/components/Layout/composables/useRedirect.ts +110 -0
  76. package/vitepress/components/Layout/composables/useVSCodeIntegration.ts +85 -0
  77. package/vitepress/components/Layout/homeReadme.data.ts +124 -0
  78. package/vitepress/components/LoadingPage/LoadingPage.vue +192 -0
  79. package/vitepress/components/MarkMap/MarkMap.module.scss +159 -0
  80. package/vitepress/components/MarkMap/MarkMap.vue +404 -0
  81. package/vitepress/components/Mermaid/Mermaid.module.scss +275 -0
  82. package/vitepress/components/Mermaid/Mermaid.vue +364 -0
  83. package/vitepress/components/NotesTable/NotesTable.module.scss +77 -0
  84. package/vitepress/components/NotesTable/NotesTable.vue +98 -0
  85. package/vitepress/components/NotesTable/README.md +67 -0
  86. package/vitepress/components/Settings/Settings.module.scss +433 -0
  87. package/vitepress/components/Settings/Settings.vue +306 -0
  88. package/vitepress/components/SidebarCard/MindMapView.vue +483 -0
  89. package/vitepress/components/SidebarCard/NotesTrendChart.vue +108 -0
  90. package/vitepress/components/SidebarCard/SidebarCard.vue +948 -0
  91. package/vitepress/components/Tooltip/Tooltip.vue +70 -0
  92. package/vitepress/components/constants.ts +91 -0
  93. package/vitepress/components/notesConfig.data.ts +73 -0
  94. package/vitepress/components/sidebar.data.ts +59 -0
  95. package/vitepress/components/tnotes-config.data.ts +21 -0
  96. package/vitepress/components/utils.ts +26 -0
  97. package/vitepress/config/index.ts +126 -0
  98. package/vitepress/configs/constants.ts +26 -0
  99. package/vitepress/configs/head.config.ts +25 -0
  100. package/vitepress/configs/index.ts +9 -0
  101. package/vitepress/configs/markdown-it.d.ts +23 -0
  102. package/vitepress/configs/markdown.config.ts +366 -0
  103. package/vitepress/configs/theme.config.ts +108 -0
  104. package/vitepress/plugins/buildProgressPlugin.ts +390 -0
  105. package/vitepress/plugins/getNoteByConfigIdPlugin.ts +107 -0
  106. package/vitepress/plugins/renameNotePlugin.ts +60 -0
  107. package/vitepress/plugins/updateConfigPlugin.ts +63 -0
  108. package/vitepress/theme/index.ts +95 -0
  109. package/vitepress/theme/styles/base.scss +50 -0
  110. package/vitepress/theme/styles/components/404.scss +31 -0
  111. package/vitepress/theme/styles/components/collapse.scss +175 -0
  112. package/vitepress/theme/styles/components/markmap.scss +101 -0
  113. package/vitepress/theme/styles/components/swiper.scss +255 -0
  114. package/vitepress/theme/styles/index.scss +25 -0
  115. package/vitepress/theme/styles/layout.scss +62 -0
  116. package/vitepress/theme/styles/utilities.scss +39 -0
  117. package/vitepress/theme/styles/vitepress-override.scss +25 -0
@@ -0,0 +1,948 @@
1
+ <script setup>
2
+ import { useData } from 'vitepress'
3
+ import { formatDate } from '../utils.ts'
4
+ import { ref, computed, watch } from 'vue'
5
+ import { useRouter } from 'vitepress'
6
+ // @ts-expect-error - VitePress Data Loader
7
+ import { data as sidebarConfig } from '../sidebar.data'
8
+
9
+ import { NOTES_DIR_KEY, REPO_NAME, AUTHOR, ROOT_ITEM } from '../constants.ts'
10
+
11
+ import MindMapView from './MindMapView.vue'
12
+ import NotesTrendChart from './NotesTrendChart.vue'
13
+
14
+ import {
15
+ icon__fold,
16
+ icon__github,
17
+ icon__vscode,
18
+ icon__folder,
19
+ icon__search,
20
+ icon__mindmap,
21
+ icon__number_gray,
22
+ icon__number_purple,
23
+ } from '../../assets/icons'
24
+
25
+ // #region props
26
+ const props = defineProps({
27
+ pending: {
28
+ type: Boolean,
29
+ default: false,
30
+ },
31
+ done: {
32
+ type: Boolean,
33
+ default: true,
34
+ },
35
+ })
36
+ // #endregion
37
+
38
+ // #region data
39
+ const viewMode = ref('folder')
40
+ const expandedGroupsFolder = ref(new Set())
41
+ const searchQuery = ref('')
42
+ const debouncedQuery = ref('')
43
+ const showAbout = ref(false)
44
+ const showNumber = ref(false)
45
+ let debounceTimer = null
46
+ // #endregion
47
+
48
+ // #region computed
49
+ const searchResults = computed(() => {
50
+ const query = debouncedQuery.value.trim().toLowerCase()
51
+ if (!query) return []
52
+ return articles.filter((article) => {
53
+ const fullTitle = `${article.realNumber}. ${article.text}`.toLowerCase()
54
+ return fullTitle.includes(query)
55
+ })
56
+ })
57
+
58
+ const folderViewData = computed(() => {
59
+ const result = {}
60
+
61
+ // 按完整分组路径构建层级结构(平铺模式)
62
+ articles.forEach((article) => {
63
+ // 将所有层级铺出来,路径信息作为分组名
64
+ // eg. 1. 第一层级/第一个第二层级
65
+ // article 1
66
+ // article 2
67
+ // ……
68
+ // 1. 第一层级/第二个第二层级
69
+ // article 1
70
+ // article 2
71
+ // ……
72
+ // 1. 第一层级/第三个第二层级
73
+ // article 1
74
+ // article 2
75
+ // ……
76
+ // const group = article.group
77
+ // if (!result[group]) {
78
+ // result[group] = {
79
+ // name: group,
80
+ // articles: [],
81
+ // fullPath: group,
82
+ // }
83
+ // }
84
+ // result[group].articles.push(article)
85
+
86
+ // 只铺出第一层级
87
+ const firstLevelCategory = article.firstLevelCategory
88
+ if (!result[firstLevelCategory]) {
89
+ result[firstLevelCategory] = {
90
+ name: firstLevelCategory,
91
+ articles: [],
92
+ fullPath: firstLevelCategory,
93
+ }
94
+ }
95
+ result[firstLevelCategory].articles.push(article)
96
+ })
97
+
98
+ return result
99
+ })
100
+ // #endregion
101
+
102
+ const router = useRouter()
103
+ const { site } = useData()
104
+ const baseUrl = site.value.base.replace(/\/$/, '')
105
+
106
+ // 使用 VitePress Data Loader 读取的 sidebar 数据
107
+ const sidebarData = computed(() => {
108
+ return sidebarConfig && sidebarConfig['/notes/']
109
+ ? sidebarConfig['/notes/']
110
+ : []
111
+ })
112
+
113
+ const { articles, groups } = extractArticlesWithGroups(
114
+ sidebarData.value,
115
+ props.pending,
116
+ props.done,
117
+ )
118
+
119
+ // 根据笔记数量动态计算防抖时间
120
+ // ≤100: 无需防抖,过滤几乎无开销
121
+ // ≤500: 100ms,轻量防抖避免频繁 DOM 更新
122
+ // ≤2000: 150ms
123
+ // ≤5000: 200ms,3k+ 笔记场景(如 LeetCode 知识库)
124
+ // >5000: 300ms,接近万篇上限时适当增加
125
+ const searchDebounceDelay = (() => {
126
+ const count = articles.length
127
+ if (count <= 100) return 0
128
+ if (count <= 500) return 100
129
+ if (count <= 2000) return 150
130
+ if (count <= 5000) return 200
131
+ return 300
132
+ })()
133
+
134
+ watch(searchQuery, (val) => {
135
+ if (debounceTimer) clearTimeout(debounceTimer)
136
+ if (searchDebounceDelay === 0) {
137
+ debouncedQuery.value = val
138
+ return
139
+ }
140
+ debounceTimer = setTimeout(() => {
141
+ debouncedQuery.value = val
142
+ }, searchDebounceDelay)
143
+ })
144
+
145
+ function extractArticlesWithGroups(sidebar, showPending, showDone) {
146
+ const articles = []
147
+ const groups = {}
148
+
149
+ function traverse(items, groupPath = []) {
150
+ for (let i = 0; i < items.length; i++) {
151
+ const item = items[i]
152
+
153
+ if (item.items && Array.isArray(item.items)) {
154
+ const newGroupPath = [...groupPath, item.text]
155
+ traverse(item.items, newGroupPath)
156
+ } else if (item.text) {
157
+ let shouldInclude = false
158
+ let status = ''
159
+ let cleanText = ''
160
+
161
+ if (item.text.startsWith('✅') && showDone) {
162
+ shouldInclude = true
163
+ status = 'done'
164
+ cleanText = item.text.replace('✅ ', '')
165
+ } else if (item.text.startsWith('⏰') && showPending) {
166
+ shouldInclude = true
167
+ status = 'pending'
168
+ cleanText = item.text.replace('⏰ ', '')
169
+ }
170
+
171
+ if (shouldInclude) {
172
+ const group = groupPath.join(' / ')
173
+ const firstLevelCategory = groupPath[0] || '未分类'
174
+
175
+ const realNumber = extractRealNumberFromLink(item.link)
176
+ const title = extractTitleFromText(cleanText)
177
+ const fullLink = baseUrl ? baseUrl + item.link : item.link
178
+
179
+ // 添加 relativePath 字段
180
+ const article = {
181
+ text: title,
182
+ realNumber,
183
+ link: fullLink,
184
+ relativePath: item.link, // 存储相对路径
185
+ group: group || '未分类',
186
+ status,
187
+ firstLevelCategory,
188
+ }
189
+
190
+ articles.push(article)
191
+
192
+ if (!groups[firstLevelCategory]) {
193
+ groups[firstLevelCategory] = []
194
+ }
195
+ groups[firstLevelCategory].push(article)
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ traverse(sidebar)
202
+ return { articles, groups }
203
+ }
204
+
205
+ // 从 link 中提取真实编号
206
+ function extractRealNumberFromLink(link) {
207
+ if (!link) return '0000'
208
+
209
+ const match = link.match(/\/notes\/(\d{4})/)
210
+ return match ? match[1] : '0000'
211
+ }
212
+
213
+ // 从文本中提取标题(去除开头的编号部分)
214
+ function extractTitleFromText(text) {
215
+ if (!text) return ''
216
+
217
+ return text.replace(/^\d{4}\.?\s/, '')
218
+ }
219
+
220
+ function handleCardClick(link) {
221
+ // open in new tab
222
+ if (link) {
223
+ window.open(link, '_blank')
224
+ }
225
+
226
+ // open in current tab
227
+ // if (link) {
228
+ // window.location.href = link
229
+ // }
230
+ }
231
+
232
+ // 在文件夹视图中切换分组展开状态
233
+ function toggleGroupFolder(groupName) {
234
+ if (expandedGroupsFolder.value.has(groupName)) {
235
+ expandedGroupsFolder.value.delete(groupName)
236
+ } else {
237
+ expandedGroupsFolder.value.add(groupName)
238
+ }
239
+ }
240
+
241
+ // 切换所有折叠状态
242
+ function toggleAllFold() {
243
+ const isAllExpanded =
244
+ expandedGroupsFolder.value.size === Object.keys(folderViewData.value).length
245
+
246
+ if (isAllExpanded) {
247
+ expandedGroupsFolder.value.clear()
248
+ } else {
249
+ Object.keys(folderViewData.value).forEach((folderName) => {
250
+ expandedGroupsFolder.value.add(folderViewData.value[folderName].fullPath)
251
+ })
252
+ }
253
+ }
254
+
255
+ // 打开 GitHub 仓库
256
+ function openGithubRepo() {
257
+ window.open(`https://github.com/${AUTHOR}/${REPO_NAME}`, '_blank')
258
+ }
259
+
260
+ // 打开 VS Code 知识库
261
+ function openVSCodeRepo() {
262
+ const notesDir = localStorage.getItem(NOTES_DIR_KEY)
263
+
264
+ if (!notesDir) {
265
+ const shouldRedirect = confirm(
266
+ '请先配置本地知识库所在位置,点击确定跳转到设置页面',
267
+ )
268
+ if (shouldRedirect) {
269
+ router.go(`${REPO_NAME}/Settings`)
270
+ }
271
+ return
272
+ }
273
+
274
+ window.open('vscode://file/' + notesDir, '_blank')
275
+ }
276
+
277
+ // 打开 VS Code 中的笔记目录
278
+ function openVSCodeArticle(article) {
279
+ const notesDir = localStorage.getItem(NOTES_DIR_KEY)
280
+
281
+ if (!notesDir) {
282
+ const shouldRedirect = confirm(
283
+ '请先配置本地知识库所在位置,点击确定跳转到设置页面',
284
+ )
285
+ if (shouldRedirect) {
286
+ router.go(`${REPO_NAME}/Settings`)
287
+ }
288
+ return
289
+ }
290
+
291
+ // 构建笔记目录路径
292
+ const notePath = `${notesDir}/${article.relativePath.replace('/README', '')}`
293
+ // console.log(notePath, encodeURI(notePath))
294
+ window.open('vscode://file/' + encodeURI(notePath), '_blank')
295
+ }
296
+ </script>
297
+
298
+ <template>
299
+ <div class="sidebar-view-container">
300
+ <!-- 控制栏区域 -->
301
+ <div class="control-bar">
302
+ <!-- 左侧视图切换按钮 -->
303
+ <div class="view-toggle">
304
+ <button
305
+ :class="{ active: viewMode === 'folder' }"
306
+ @click="viewMode = 'folder'"
307
+ >
308
+ <img :src="icon__folder" alt="文件夹视图" />
309
+ </button>
310
+ <button
311
+ :class="{ active: viewMode === 'search' }"
312
+ @click="viewMode = 'search'"
313
+ >
314
+ <img :src="icon__search" alt="搜索视图" />
315
+ </button>
316
+ <button
317
+ :class="{ active: viewMode === 'mindmap' }"
318
+ @click="viewMode = 'mindmap'"
319
+ >
320
+ <img :src="icon__mindmap" alt="思维导图" />
321
+ </button>
322
+ </div>
323
+ <!-- 右侧控制按钮 -->
324
+ <div class="actions">
325
+ <!-- 编号显示切换按钮 -->
326
+ <button
327
+ class="number-toggle"
328
+ v-show="viewMode === 'folder'"
329
+ @click="showNumber = !showNumber"
330
+ >
331
+ <img
332
+ :src="showNumber ? icon__number_purple : icon__number_gray"
333
+ alt="切换编号显示"
334
+ />
335
+ </button>
336
+ <!-- 折叠/展开按钮 -->
337
+ <button
338
+ class="fold-toggle"
339
+ v-show="viewMode === 'folder'"
340
+ @click="toggleAllFold"
341
+ >
342
+ <img :src="icon__fold" alt="折叠/展开" />
343
+ </button>
344
+
345
+ <!-- GitHub 按钮 -->
346
+ <button class="github-link" @click="openGithubRepo">
347
+ <img :src="icon__github" alt="GitHub仓库" />
348
+ </button>
349
+
350
+ <!-- VS Code 按钮 -->
351
+ <button class="vscode-open" @click="openVSCodeRepo">
352
+ <img :src="icon__vscode" alt="使用 VS Code 打开本地知识库" />
353
+ </button>
354
+
355
+ <!-- 关于按钮 -->
356
+ <button class="about-toggle" @click="showAbout = !showAbout">!</button>
357
+ </div>
358
+ </div>
359
+
360
+ <!-- 关于弹窗 -->
361
+ <div class="about-overlay" v-if="showAbout" @click.self="showAbout = false">
362
+ <div class="about-dialog">
363
+ <div class="about-header">
364
+ <span>关于知识库</span>
365
+ <button class="about-close" @click="showAbout = false">✕</button>
366
+ </div>
367
+ <div class="about-body">
368
+ <div class="about-item">
369
+ <span class="about-label">GitHub</span>
370
+ <a
371
+ class="about-value about-link"
372
+ :href="`https://github.com/${AUTHOR}/${REPO_NAME}`"
373
+ target="_blank"
374
+ >
375
+ {{ AUTHOR }}/{{ REPO_NAME }}
376
+ </a>
377
+ </div>
378
+ <div class="about-item">
379
+ <span class="about-label">📅 创建时间</span>
380
+ <span class="about-value">{{
381
+ formatDate(ROOT_ITEM.created_at)
382
+ }}</span>
383
+ </div>
384
+ <div class="about-item">
385
+ <span class="about-label">♻️ 最近更新</span>
386
+ <span class="about-value">{{
387
+ formatDate(ROOT_ITEM.updated_at)
388
+ }}</span>
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ <!-- 搜索视图 -->
394
+ <div class="search-view" v-if="viewMode === 'search'">
395
+ <div class="search-box">
396
+ <input
397
+ v-model="searchQuery"
398
+ type="text"
399
+ placeholder="搜索笔记标题…"
400
+ class="search-input"
401
+ />
402
+ </div>
403
+ <div class="search-summary">找到 {{ searchResults.length }} 条结果</div>
404
+ <div class="folder-tree" v-show="searchResults.length > 0">
405
+ <div
406
+ v-for="article in searchResults"
407
+ :key="article.link"
408
+ class="folder-article"
409
+ >
410
+ <div class="article-info" @click="handleCardClick(article.link)">
411
+ <span class="article-status" :class="`status-${article.status}`">
412
+ {{
413
+ article.status === 'done'
414
+ ? '✅'
415
+ : article.status === 'pending'
416
+ ? '⏰'
417
+ : '📄'
418
+ }}
419
+ </span>
420
+ <span class="article-title">{{
421
+ `${article.realNumber}. ${article.text}`
422
+ }}</span>
423
+ </div>
424
+ <button
425
+ class="vscode-article"
426
+ @click.stop="openVSCodeArticle(article)"
427
+ title="在 VS Code 中打开笔记目录"
428
+ >
429
+ <img :src="icon__vscode" alt="打开笔记目录" />
430
+ </button>
431
+ </div>
432
+ </div>
433
+ </div>
434
+ <!-- 思维导图视图 -->
435
+ <MindMapView v-if="viewMode === 'mindmap'" :sidebarData="sidebarData" />
436
+ <!-- 文件夹视图 -->
437
+ <div class="folder-view" v-if="viewMode === 'folder'">
438
+ <NotesTrendChart
439
+ v-if="ROOT_ITEM.completed_notes_count"
440
+ :completedNotesCount="ROOT_ITEM.completed_notes_count"
441
+ />
442
+ <div class="folder-tree">
443
+ <div
444
+ v-for="(folder, folderName) in folderViewData"
445
+ :key="folderName"
446
+ class="folder-group"
447
+ >
448
+ <!-- 文件夹组 -->
449
+ <div
450
+ class="folder-header"
451
+ @click="toggleGroupFolder(folder.fullPath)"
452
+ >
453
+ <span class="folder-icon">
454
+ {{ expandedGroupsFolder.has(folder.fullPath) ? '📂' : '📁' }}
455
+ </span>
456
+ <span class="folder-name">{{ folderName }}</span>
457
+ <span class="folder-count">{{ folder.articles.length }}</span>
458
+ </div>
459
+
460
+ <!-- 展开的文章 -->
461
+ <div
462
+ class="folder-content"
463
+ v-if="expandedGroupsFolder.has(folder.fullPath)"
464
+ >
465
+ <!-- 当前文件夹中的文章 -->
466
+ <div
467
+ v-for="article in folder.articles"
468
+ :key="article.link"
469
+ class="folder-article"
470
+ >
471
+ <div class="article-info" @click="handleCardClick(article.link)">
472
+ <span
473
+ class="article-status"
474
+ :class="`status-${article.status}`"
475
+ >
476
+ {{
477
+ article.status === 'done'
478
+ ? '✅'
479
+ : article.status === 'pending'
480
+ ? '⏰'
481
+ : '📄'
482
+ }}
483
+ </span>
484
+ <!-- <span class="article-number">{{ article.realNumber }}</span> -->
485
+ <span class="article-title">{{
486
+ showNumber
487
+ ? `${article.realNumber}. ${article.text}`
488
+ : article.text
489
+ }}</span>
490
+ </div>
491
+
492
+ <button
493
+ class="vscode-article"
494
+ @click.stop="openVSCodeArticle(article)"
495
+ title="在 VS Code 中打开笔记目录"
496
+ >
497
+ <img :src="icon__vscode" alt="打开笔记目录" />
498
+ </button>
499
+ </div>
500
+ </div>
501
+ </div>
502
+ </div>
503
+ </div>
504
+ </div>
505
+ </template>
506
+
507
+ <style scoped lang="scss">
508
+ /* 关于弹窗样式 */
509
+ .about-overlay {
510
+ position: fixed;
511
+ top: 0;
512
+ left: 0;
513
+ right: 0;
514
+ bottom: 0;
515
+ background-color: rgba(0, 0, 0, 0.5);
516
+ display: flex;
517
+ align-items: center;
518
+ justify-content: center;
519
+ z-index: 100;
520
+ }
521
+
522
+ .about-dialog {
523
+ background-color: var(--vp-c-bg);
524
+ border: 1px solid var(--vp-c-divider);
525
+ border-radius: 12px;
526
+ width: 90%;
527
+ max-width: 400px;
528
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
529
+
530
+ .about-header {
531
+ display: flex;
532
+ justify-content: space-between;
533
+ align-items: center;
534
+ padding: 0.75rem 1rem;
535
+ border-bottom: 1px solid var(--vp-c-divider);
536
+ font-weight: 600;
537
+ font-size: 0.95rem;
538
+ }
539
+
540
+ .about-close {
541
+ background: none;
542
+ border: none;
543
+ cursor: pointer;
544
+ color: var(--vp-c-text-2);
545
+ font-size: 1rem;
546
+ padding: 0.25rem 0.5rem;
547
+ border-radius: 4px;
548
+ transition: background-color 0.2s;
549
+
550
+ &:hover {
551
+ background-color: var(--vp-c-bg-soft);
552
+ color: var(--vp-c-text-1);
553
+ }
554
+ }
555
+
556
+ .about-body {
557
+ padding: 0.75rem 1rem;
558
+ }
559
+
560
+ .about-item {
561
+ display: flex;
562
+ justify-content: space-between;
563
+ align-items: center;
564
+ padding: 0.5rem 0;
565
+
566
+ &:not(:last-child) {
567
+ border-bottom: 1px solid var(--vp-c-divider);
568
+ }
569
+ }
570
+
571
+ .about-label {
572
+ color: var(--vp-c-text-2);
573
+ font-size: 0.85rem;
574
+ }
575
+
576
+ .about-value {
577
+ color: var(--vp-c-text-1);
578
+ font-size: 0.85rem;
579
+ }
580
+
581
+ .about-link {
582
+ color: var(--vp-c-brand-1);
583
+ text-decoration: none;
584
+
585
+ &:hover {
586
+ text-decoration: underline;
587
+ }
588
+ }
589
+ }
590
+
591
+ .about-toggle {
592
+ background: none;
593
+ border: none;
594
+ padding: 0.5rem;
595
+ cursor: pointer;
596
+ border-radius: 4px;
597
+ transition: background-color 0.3s ease;
598
+ color: #646cff;
599
+ font-size: 0.85rem;
600
+ line-height: 1;
601
+
602
+ &:hover {
603
+ background-color: var(--vp-c-bg-soft);
604
+ }
605
+ }
606
+
607
+ /* 文件夹视图样式 */
608
+ .folder-view {
609
+ width: 100%;
610
+ padding: 1rem 0;
611
+
612
+ .folder-tree {
613
+ // background-color: var(--vp-c-bg-soft);
614
+ border-radius: 8px;
615
+ padding: 1rem;
616
+
617
+ .folder-group {
618
+ margin-bottom: 1rem;
619
+ border: 1px solid var(--vp-c-divider);
620
+ border-radius: 8px;
621
+ overflow: hidden;
622
+
623
+ .folder-header {
624
+ display: flex;
625
+ align-items: center;
626
+ padding: 0.75rem 1rem;
627
+ // background-color: var(--vp-c-bg-soft);
628
+ cursor: pointer;
629
+ transition: background-color 0.3s;
630
+
631
+ // &:hover {
632
+ // background-color: var(--vp-c-bg-elv);
633
+ // }
634
+
635
+ .folder-icon {
636
+ margin-right: 0.75rem;
637
+ font-size: 1.1rem;
638
+ }
639
+
640
+ .folder-name {
641
+ flex: 1;
642
+ font-weight: 500;
643
+ font-size: 0.95rem;
644
+ }
645
+
646
+ .folder-count {
647
+ background-color: var(--vp-c-default-soft);
648
+ color: var(--vp-c-text-2);
649
+ font-size: 0.8rem;
650
+ padding: 0.2rem 0.5rem;
651
+ border-radius: 4px;
652
+ }
653
+ }
654
+
655
+ .folder-content {
656
+ padding: 0.5rem 1rem;
657
+ background-color: var(--vp-c-bg);
658
+
659
+ .folder-article {
660
+ display: flex;
661
+ align-items: center;
662
+ justify-content: space-between;
663
+ padding: 0.6rem 0.8rem;
664
+ margin: 0.4rem 0;
665
+ border-radius: 6px;
666
+ transition: background-color 0.3s;
667
+
668
+ &:hover {
669
+ background-color: var(--vp-c-bg-soft);
670
+ }
671
+
672
+ .article-info {
673
+ display: flex;
674
+ align-items: center;
675
+ flex: 1;
676
+ cursor: pointer;
677
+ overflow: hidden;
678
+
679
+ .article-status {
680
+ margin-right: 0.8rem;
681
+ font-size: 0.9rem;
682
+ width: 1.2rem;
683
+ text-align: center;
684
+ flex-shrink: 0;
685
+
686
+ &.status-done {
687
+ color: var(--vp-c-green-1);
688
+ }
689
+
690
+ &.status-pending {
691
+ color: var(--vp-c-yellow-1);
692
+ }
693
+ }
694
+
695
+ .article-number {
696
+ background-color: var(--vp-c-default-soft);
697
+ color: var(--vp-c-text-2);
698
+ font-size: 0.75rem;
699
+ padding: 0.2rem 0.5rem;
700
+ border-radius: 4px;
701
+ font-family: var(--vp-font-family-mono);
702
+ flex-shrink: 0;
703
+ margin-right: 0.8rem;
704
+ }
705
+
706
+ .article-title {
707
+ font-size: 0.9rem;
708
+ overflow: hidden;
709
+ text-overflow: ellipsis;
710
+ white-space: nowrap;
711
+ }
712
+ }
713
+
714
+ .vscode-article {
715
+ background: none;
716
+ border: none;
717
+ padding: 0.3rem;
718
+ cursor: pointer;
719
+ border-radius: 4px;
720
+ flex-shrink: 0;
721
+ opacity: 0.5;
722
+ transition: opacity 0.3s ease;
723
+
724
+ &:hover {
725
+ opacity: 1;
726
+ background-color: var(--vp-c-bg-soft-down);
727
+ }
728
+
729
+ img {
730
+ display: block;
731
+ height: 1rem;
732
+ width: 1rem;
733
+ }
734
+ }
735
+ }
736
+ }
737
+ }
738
+ }
739
+
740
+ /* 响应式调整 */
741
+ @media (max-width: 768px) {
742
+ .folder-content {
743
+ padding-left: 1rem !important;
744
+ padding-right: 1rem !important;
745
+ }
746
+ }
747
+ }
748
+
749
+ /* 搜索视图 */
750
+ .search-view {
751
+ .search-box {
752
+ margin-bottom: 0.75rem;
753
+
754
+ .search-input {
755
+ width: 100%;
756
+ padding: 0.5rem 0.75rem;
757
+ border: 1px solid var(--vp-c-divider);
758
+ border-radius: 6px;
759
+ background-color: var(--vp-c-bg);
760
+ color: var(--vp-c-text-1);
761
+ font-size: 0.85rem;
762
+ outline: none;
763
+ transition: border-color 0.3s;
764
+ box-sizing: border-box;
765
+
766
+ &::placeholder {
767
+ color: var(--vp-c-text-3);
768
+ }
769
+
770
+ &:focus {
771
+ border-color: var(--vp-c-brand-1);
772
+ }
773
+ }
774
+ }
775
+
776
+ width: 100%;
777
+ padding: 1rem 0;
778
+
779
+ .search-summary {
780
+ font-size: 0.85rem;
781
+ color: var(--vp-c-text-2);
782
+ margin-bottom: 0.75rem;
783
+ }
784
+
785
+ .folder-tree {
786
+ border-radius: 8px;
787
+ padding: 0.5rem 1rem;
788
+ border: 1px solid var(--vp-c-divider);
789
+ }
790
+
791
+ .folder-article {
792
+ display: flex;
793
+ align-items: center;
794
+ justify-content: space-between;
795
+ padding: 0.6rem 0.8rem;
796
+ margin: 0.4rem 0;
797
+ border-radius: 6px;
798
+ transition: background-color 0.3s;
799
+
800
+ &:hover {
801
+ background-color: var(--vp-c-bg-soft);
802
+ }
803
+
804
+ .article-info {
805
+ display: flex;
806
+ align-items: center;
807
+ flex: 1;
808
+ cursor: pointer;
809
+ overflow: hidden;
810
+
811
+ .article-status {
812
+ margin-right: 0.8rem;
813
+ font-size: 0.9rem;
814
+ width: 1.2rem;
815
+ text-align: center;
816
+ flex-shrink: 0;
817
+
818
+ &.status-done {
819
+ color: var(--vp-c-green-1);
820
+ }
821
+
822
+ &.status-pending {
823
+ color: var(--vp-c-yellow-1);
824
+ }
825
+ }
826
+
827
+ .article-title {
828
+ font-size: 0.9rem;
829
+ overflow: hidden;
830
+ text-overflow: ellipsis;
831
+ white-space: nowrap;
832
+ }
833
+ }
834
+
835
+ .vscode-article {
836
+ background: none;
837
+ border: none;
838
+ padding: 0.3rem;
839
+ cursor: pointer;
840
+ border-radius: 4px;
841
+ flex-shrink: 0;
842
+ opacity: 0.5;
843
+ transition: opacity 0.3s ease;
844
+
845
+ &:hover {
846
+ opacity: 1;
847
+ background-color: var(--vp-c-bg-soft-down);
848
+ }
849
+
850
+ img {
851
+ display: block;
852
+ height: 1rem;
853
+ width: 1rem;
854
+ }
855
+ }
856
+ }
857
+ }
858
+
859
+ /* 控制栏样式 */
860
+ .control-bar {
861
+ display: flex;
862
+ justify-content: space-between;
863
+ align-items: center;
864
+ margin-bottom: 1.5rem;
865
+ padding-bottom: 0.5rem;
866
+ border-bottom: 1px solid var(--vp-c-divider);
867
+ }
868
+
869
+ .view-toggle {
870
+ display: flex;
871
+
872
+ button {
873
+ background: none;
874
+ border: none;
875
+ padding: 0.5rem;
876
+ cursor: pointer;
877
+ position: relative;
878
+ border-radius: 4px;
879
+ transition: background-color 0.3s ease;
880
+ font-size: 0.85rem;
881
+ line-height: 1;
882
+
883
+ &.active {
884
+ background-color: var(--vp-c-bg-soft);
885
+
886
+ &::after {
887
+ content: '';
888
+ position: absolute;
889
+ bottom: -0.5rem;
890
+ left: 0;
891
+ right: 0;
892
+ height: 2px;
893
+ background-color: var(--vp-c-brand);
894
+ }
895
+ }
896
+
897
+ &:hover:not(.active) {
898
+ background-color: var(--vp-c-bg-soft);
899
+ }
900
+
901
+ img {
902
+ display: block;
903
+ height: 1rem;
904
+ width: 1rem;
905
+ opacity: 0.7;
906
+ transition: opacity 0.3s ease;
907
+
908
+ &:hover {
909
+ opacity: 1;
910
+ }
911
+ }
912
+ }
913
+ }
914
+
915
+ .actions {
916
+ display: flex;
917
+ gap: 0.5rem;
918
+ }
919
+
920
+ /* 折叠/展开按钮和GitHub按钮样式 */
921
+ .fold-toggle,
922
+ .github-link,
923
+ .vscode-open,
924
+ .number-toggle {
925
+ background: none;
926
+ border: none;
927
+ padding: 0.5rem;
928
+ cursor: pointer;
929
+ border-radius: 4px;
930
+ transition: background-color 0.3s ease;
931
+
932
+ &:hover {
933
+ background-color: var(--vp-c-bg-soft);
934
+ }
935
+
936
+ img {
937
+ display: block;
938
+ height: 1rem;
939
+ width: 1rem;
940
+ opacity: 0.7;
941
+ transition: opacity 0.3s ease;
942
+
943
+ &:hover {
944
+ opacity: 1;
945
+ }
946
+ }
947
+ }
948
+ </style>