@jet-w/astro-blog 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.
Files changed (140) hide show
  1. package/dist/chunk-FXPGR372.js +0 -0
  2. package/dist/chunk-GYLSY3OJ.js +173 -0
  3. package/dist/config/index.d.ts +166 -0
  4. package/dist/config/index.js +38 -0
  5. package/dist/index.d.ts +34 -0
  6. package/dist/index.js +59 -0
  7. package/dist/types/index.d.ts +75 -0
  8. package/dist/types/index.js +1 -0
  9. package/package.json +84 -0
  10. package/src/components/EChartsCard.vue +118 -0
  11. package/src/components/Mermaid.vue +73 -0
  12. package/src/components/about/ContentCard.astro +27 -0
  13. package/src/components/about/IconCard.astro +77 -0
  14. package/src/components/about/SocialLinks.astro +54 -0
  15. package/src/components/about/TagCard.astro +65 -0
  16. package/src/components/about/TagGroup.astro +33 -0
  17. package/src/components/about/TimelineCard.astro +52 -0
  18. package/src/components/blog/FloatingToc.vue +198 -0
  19. package/src/components/blog/Hero.astro +147 -0
  20. package/src/components/blog/NavigationTabs.vue +245 -0
  21. package/src/components/blog/PostCard.astro +161 -0
  22. package/src/components/blog/PostNavigation.astro +106 -0
  23. package/src/components/blog/RelatedPosts.astro +175 -0
  24. package/src/components/blog/TableOfContents.astro +153 -0
  25. package/src/components/blog/TagCloud.astro +91 -0
  26. package/src/components/home/FeaturedPostsSection.astro +54 -0
  27. package/src/components/home/QuickNavSection.astro +81 -0
  28. package/src/components/home/RecentPostsSection.astro +52 -0
  29. package/src/components/home/StatsSection.astro +44 -0
  30. package/src/components/layout/Footer.astro +103 -0
  31. package/src/components/layout/Header.astro +68 -0
  32. package/src/components/layout/Sidebar.astro +594 -0
  33. package/src/components/media/Bilibili.astro +114 -0
  34. package/src/components/media/Slides.astro +313 -0
  35. package/src/components/media/Video.astro +111 -0
  36. package/src/components/media/VideoPlayer.astro +89 -0
  37. package/src/components/media/YouTube.astro +92 -0
  38. package/src/components/pte/StudyCalendar.vue +1348 -0
  39. package/src/components/ui/Icon.astro +187 -0
  40. package/src/components/ui/MobileMenu.vue +201 -0
  41. package/src/components/ui/Pagination.astro +143 -0
  42. package/src/components/ui/SearchBox.vue +179 -0
  43. package/src/components/ui/SearchInterface.vue +409 -0
  44. package/src/components/ui/SidebarToggle.vue +57 -0
  45. package/src/components/ui/ThemeToggle.vue +90 -0
  46. package/src/layouts/AboutLayout.astro +18 -0
  47. package/src/layouts/BaseLayout.astro +362 -0
  48. package/src/layouts/PageLayout.astro +217 -0
  49. package/src/layouts/SlidesLayout.astro +320 -0
  50. package/src/plugins/rehype-clean-containers.mjs +24 -0
  51. package/src/plugins/rehype-relative-links.mjs +43 -0
  52. package/src/plugins/rehype-tabs.mjs +116 -0
  53. package/src/plugins/remark-containers.mjs +407 -0
  54. package/src/plugins/remark-mermaid.mjs +46 -0
  55. package/src/styles/global.css +870 -0
  56. package/src/styles/slides.css +220 -0
  57. package/src/utils/sidebar.ts +492 -0
  58. package/templates/default/astro.config.mjs +51 -0
  59. package/templates/default/content/pages/about.mdx +93 -0
  60. package/templates/default/content/pages/index.mdx +20 -0
  61. package/templates/default/content/posts/blog_docs/01-quick-start.md +162 -0
  62. package/templates/default/content/posts/blog_docs/02-frontmatter.md +277 -0
  63. package/templates/default/content/posts/blog_docs/03-markdown-basic.md +350 -0
  64. package/templates/default/content/posts/blog_docs/04-containers.md +331 -0
  65. package/templates/default/content/posts/blog_docs/05-code-blocks.md +388 -0
  66. package/templates/default/content/posts/blog_docs/06-mermaid.md +431 -0
  67. package/templates/default/content/posts/blog_docs/07-video.md +243 -0
  68. package/templates/default/content/posts/blog_docs/08-latex.md +382 -0
  69. package/templates/default/content/posts/blog_docs/09-icons.md +326 -0
  70. package/templates/default/content/posts/blog_docs/10-sidebar.md +445 -0
  71. package/templates/default/content/posts/blog_docs/11-config.md +334 -0
  72. package/templates/default/content/posts/blog_docs/12-slides.mdx +552 -0
  73. package/templates/default/content/posts/blog_docs/README.md +151 -0
  74. package/templates/default/content/slides/demo.md +146 -0
  75. package/templates/default/content/slides/docs/basic-demo.md +35 -0
  76. package/templates/default/content/slides/docs/code-demo.md +62 -0
  77. package/templates/default/content/slides/docs/echarts-demo.md +139 -0
  78. package/templates/default/content/slides/docs/fragment-demo.md +35 -0
  79. package/templates/default/content/slides/docs/math-demo.md +48 -0
  80. package/templates/default/content/slides/docs/mermaid-demo.md +105 -0
  81. package/templates/default/content/slides/docs/theme-demo.md +38 -0
  82. package/templates/default/content/slides/docs/vertical-demo.md +50 -0
  83. package/templates/default/package.json +31 -0
  84. package/templates/default/public/favicon-bak.svg +4 -0
  85. package/templates/default/public/images/avatar.jpg +0 -0
  86. package/templates/default/public/images/avatar.svg +142 -0
  87. package/templates/default/public/js/mermaid-container.js +402 -0
  88. package/templates/default/public/js/mermaid-init.js +131 -0
  89. package/templates/default/public/js/mermaid-render.js +98 -0
  90. package/templates/default/public/js/mermaid-simple.js +95 -0
  91. package/templates/default/public/js/tabs-init.js +86 -0
  92. package/templates/default/public/media/individual_portfolio/INDIVIDUAL PORTFOLIO.png +0 -0
  93. package/templates/default/public/slides/plugin/highlight/highlight.js +5 -0
  94. package/templates/default/public/slides/plugin/highlight/monokai.css +71 -0
  95. package/templates/default/public/slides/plugin/markdown/markdown.js +7 -0
  96. package/templates/default/public/slides/plugin/math/math.js +1 -0
  97. package/templates/default/public/slides/plugin/notes/notes.js +1 -0
  98. package/templates/default/public/slides/reveal.css +9 -0
  99. package/templates/default/public/slides/reveal.js +9 -0
  100. package/templates/default/public/slides/theme/beige.css +366 -0
  101. package/templates/default/public/slides/theme/black-contrast.css +362 -0
  102. package/templates/default/public/slides/theme/black.css +359 -0
  103. package/templates/default/public/slides/theme/blood.css +392 -0
  104. package/templates/default/public/slides/theme/dracula.css +385 -0
  105. package/templates/default/public/slides/theme/league.css +368 -0
  106. package/templates/default/public/slides/theme/moon.css +362 -0
  107. package/templates/default/public/slides/theme/night.css +360 -0
  108. package/templates/default/public/slides/theme/serif.css +363 -0
  109. package/templates/default/public/slides/theme/simple.css +362 -0
  110. package/templates/default/public/slides/theme/sky.css +370 -0
  111. package/templates/default/public/slides/theme/solarized.css +363 -0
  112. package/templates/default/public/slides/theme/white-contrast.css +362 -0
  113. package/templates/default/public/slides/theme/white.css +359 -0
  114. package/templates/default/public/slides/theme/white_contrast_compact_verbatim_headers.css +360 -0
  115. package/templates/default/public/test-complete.html +43 -0
  116. package/templates/default/public/test-mermaid.html +124 -0
  117. package/templates/default/src/config/index.ts +114 -0
  118. package/templates/default/src/content.config.ts +96 -0
  119. package/templates/default/src/pages/[...slug].astro +27 -0
  120. package/templates/default/src/pages/archives/[year]/[month]/page/[page].astro +176 -0
  121. package/templates/default/src/pages/archives/[year]/[month].astro +158 -0
  122. package/templates/default/src/pages/archives/index.astro +210 -0
  123. package/templates/default/src/pages/categories/[category]/page/[page].astro +218 -0
  124. package/templates/default/src/pages/categories/[category].astro +198 -0
  125. package/templates/default/src/pages/categories/index.astro +190 -0
  126. package/templates/default/src/pages/container-test.astro +79 -0
  127. package/templates/default/src/pages/mermaid-direct.html +78 -0
  128. package/templates/default/src/pages/posts/[...slug].astro +335 -0
  129. package/templates/default/src/pages/posts/index.astro +541 -0
  130. package/templates/default/src/pages/posts/page/[page].astro +146 -0
  131. package/templates/default/src/pages/rss.xml.ts +28 -0
  132. package/templates/default/src/pages/search-index.json.ts +21 -0
  133. package/templates/default/src/pages/search.astro +50 -0
  134. package/templates/default/src/pages/slides/[...slug].astro +54 -0
  135. package/templates/default/src/pages/slides/index.astro +135 -0
  136. package/templates/default/src/pages/tags/[tag]/page/[page].astro +211 -0
  137. package/templates/default/src/pages/tags/[tag].astro +191 -0
  138. package/templates/default/src/pages/tags/index.astro +167 -0
  139. package/templates/default/tailwind.config.mjs +78 -0
  140. package/templates/default/tsconfig.json +9 -0
@@ -0,0 +1,594 @@
1
+ ---
2
+ import { getCollection } from 'astro:content';
3
+ import Icon from '../../components/ui/Icon.astro';
4
+ import { sidebarConfig } from '../../config/sidebar';
5
+ import {
6
+ processSidebarConfig,
7
+ getRecentPosts,
8
+ getPopularTags,
9
+ getArchives,
10
+ filterGroupsByPath,
11
+ } from '../../utils/sidebar';
12
+
13
+ interface Props {
14
+ currentPath?: string;
15
+ }
16
+
17
+ const { currentPath = Astro.url.pathname } = Astro.props;
18
+
19
+ // 获取所有文章
20
+ const allPosts = await getCollection('posts', ({ data }) => !data.draft);
21
+
22
+ // 根据当前路径过滤侧边栏组
23
+ const filteredGroups = filterGroupsByPath(sidebarConfig.groups, currentPath);
24
+
25
+ // 创建过滤后的配置
26
+ const filteredConfig = { ...sidebarConfig, groups: filteredGroups };
27
+
28
+ // 处理侧边栏配置
29
+ const processedGroups = await processSidebarConfig(filteredConfig, allPosts);
30
+
31
+ // 获取其他数据
32
+ const recentPosts = sidebarConfig.showRecentPosts
33
+ ? getRecentPosts(allPosts, sidebarConfig.recentPostsCount)
34
+ : [];
35
+
36
+ const popularTags = sidebarConfig.showPopularTags
37
+ ? getPopularTags(allPosts, sidebarConfig.popularTagsCount)
38
+ : [];
39
+
40
+ const archives = sidebarConfig.showArchives
41
+ ? getArchives(allPosts, sidebarConfig.archivesCount)
42
+ : [];
43
+ ---
44
+
45
+ <div class="space-y-8 p-6">
46
+ {/* 渲染配置的侧边栏组 */}
47
+ {processedGroups.map((group) => (
48
+ group.type === 'divider' ? (
49
+ <div class="sidebar-divider">
50
+ {group.title && (
51
+ <span class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wider">
52
+ {group.title}
53
+ </span>
54
+ )}
55
+ <div class="border-b border-slate-200 dark:border-slate-700 mt-2"></div>
56
+ </div>
57
+ ) : (
58
+ <section class="doc-tree-section">
59
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2 flex items-center gap-2">
60
+ {group.icon ? (
61
+ <Icon icon={group.icon} size="1.25em" class="text-primary-500" />
62
+ ) : (
63
+ <svg class="w-5 h-5 text-primary-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
64
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
65
+ </svg>
66
+ )}
67
+ {group.title}
68
+ </h3>
69
+ <nav class="doc-tree text-sm" data-collapsed={group.collapsed}>
70
+ <ul class="space-y-1">
71
+ {group.tree?.map((node) => (
72
+ <li class="tree-node">
73
+ {node.isFolder ? (
74
+ <div class="tree-folder">
75
+ <button
76
+ class="tree-folder-toggle w-full flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-left"
77
+ data-folder-name={node.name}
78
+ data-default-collapsed={node.collapsed}
79
+ >
80
+ <svg class="tree-chevron w-4 h-4 text-slate-400 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
81
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
82
+ </svg>
83
+ {node.icon ? (
84
+ <Icon icon={node.icon} size="1.1em" class="flex-shrink-0" />
85
+ ) : (
86
+ <svg class="tree-folder-icon w-4 h-4 text-amber-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
87
+ <path d="M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" />
88
+ </svg>
89
+ )}
90
+ <span class="text-slate-700 dark:text-slate-300 font-medium truncate">
91
+ {node.displayName || node.name}
92
+ </span>
93
+ {node.badge && (
94
+ <span class={`ml-1 px-1.5 py-0.5 text-xs rounded ${
95
+ node.badgeType === 'error' ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' :
96
+ node.badgeType === 'warning' ? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400' :
97
+ node.badgeType === 'success' ? 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' :
98
+ 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
99
+ }`}>
100
+ {node.badge}
101
+ </span>
102
+ )}
103
+ <span class="text-xs text-slate-400 ml-auto">{node.children.length}</span>
104
+ </button>
105
+ <ul class="tree-children ml-4 mt-1 space-y-1 hidden">
106
+ {node.children.map((child) => (
107
+ <li class="tree-node">
108
+ {child.isFolder ? (
109
+ <div class="tree-folder">
110
+ <button
111
+ class="tree-folder-toggle w-full flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-left"
112
+ data-folder-name={child.name}
113
+ data-default-collapsed={child.collapsed}
114
+ >
115
+ <svg class="tree-chevron w-4 h-4 text-slate-400 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
116
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
117
+ </svg>
118
+ {child.icon ? (
119
+ <Icon icon={child.icon} size="1.1em" class="flex-shrink-0" />
120
+ ) : (
121
+ <svg class="tree-folder-icon w-4 h-4 text-amber-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
122
+ <path d="M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" />
123
+ </svg>
124
+ )}
125
+ <span class="text-slate-700 dark:text-slate-300 font-medium truncate">
126
+ {child.displayName || child.name}
127
+ </span>
128
+ {child.badge && (
129
+ <span class={`ml-1 px-1.5 py-0.5 text-xs rounded ${
130
+ child.badgeType === 'error' ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' :
131
+ child.badgeType === 'warning' ? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400' :
132
+ child.badgeType === 'success' ? 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' :
133
+ 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
134
+ }`}>
135
+ {child.badge}
136
+ </span>
137
+ )}
138
+ <span class="text-xs text-slate-400 ml-auto">{child.children.length}</span>
139
+ </button>
140
+ <ul class="tree-children ml-4 mt-1 space-y-1 hidden">
141
+ {child.children.map((grandChild) => (
142
+ <li class="tree-node">
143
+ {grandChild.isFolder ? (
144
+ <div class="tree-folder">
145
+ <button
146
+ class="tree-folder-toggle w-full flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors text-left"
147
+ data-folder-name={grandChild.name}
148
+ >
149
+ <svg class="tree-chevron w-4 h-4 text-slate-400 transition-transform duration-200" fill="none" stroke="currentColor" viewBox="0 0 24 24">
150
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
151
+ </svg>
152
+ {grandChild.icon ? (
153
+ <Icon icon={grandChild.icon} size="1.1em" class="flex-shrink-0" />
154
+ ) : (
155
+ <svg class="tree-folder-icon w-4 h-4 text-amber-500 flex-shrink-0" fill="currentColor" viewBox="0 0 24 24">
156
+ <path d="M10 4H4a2 2 0 00-2 2v12a2 2 0 002 2h16a2 2 0 002-2V8a2 2 0 00-2-2h-8l-2-2z" />
157
+ </svg>
158
+ )}
159
+ <span class="text-slate-700 dark:text-slate-300 font-medium truncate">
160
+ {grandChild.displayName || grandChild.name}
161
+ </span>
162
+ <span class="text-xs text-slate-400 ml-auto">{grandChild.children.length}</span>
163
+ </button>
164
+ <ul class="tree-children ml-4 mt-1 space-y-1 hidden">
165
+ {grandChild.children.map((item) => (
166
+ <li class="tree-node">
167
+ {item.link ? (
168
+ <a
169
+ href={item.link}
170
+ target="_blank"
171
+ rel="noopener noreferrer"
172
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
173
+ title={item.title}
174
+ >
175
+ {item.icon ? (
176
+ <Icon icon={item.icon} size="1em" class="flex-shrink-0" />
177
+ ) : (
178
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
179
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
180
+ </svg>
181
+ )}
182
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
183
+ {item.title || item.name}
184
+ </span>
185
+ </a>
186
+ ) : (
187
+ <a
188
+ href={`/posts/${item.slug?.toLowerCase()}`}
189
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
190
+ title={item.title}
191
+ >
192
+ {item.icon ? (
193
+ <Icon icon={item.icon} size="1em" class="flex-shrink-0" />
194
+ ) : (
195
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
196
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
197
+ </svg>
198
+ )}
199
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
200
+ {item.title || item.name}
201
+ </span>
202
+ </a>
203
+ )}
204
+ </li>
205
+ ))}
206
+ </ul>
207
+ </div>
208
+ ) : grandChild.link ? (
209
+ <a
210
+ href={grandChild.link}
211
+ target="_blank"
212
+ rel="noopener noreferrer"
213
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
214
+ title={grandChild.title}
215
+ >
216
+ {grandChild.icon ? (
217
+ <Icon icon={grandChild.icon} size="1em" class="flex-shrink-0" />
218
+ ) : (
219
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
220
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
221
+ </svg>
222
+ )}
223
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
224
+ {grandChild.title || grandChild.name}
225
+ </span>
226
+ </a>
227
+ ) : (
228
+ <a
229
+ href={`/posts/${grandChild.slug?.toLowerCase()}`}
230
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
231
+ title={grandChild.title}
232
+ >
233
+ {grandChild.icon ? (
234
+ <Icon icon={grandChild.icon} size="1em" class="flex-shrink-0" />
235
+ ) : (
236
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
237
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
238
+ </svg>
239
+ )}
240
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
241
+ {grandChild.title || grandChild.name}
242
+ </span>
243
+ {grandChild.badge && (
244
+ <span class={`ml-1 px-1.5 py-0.5 text-xs rounded ${
245
+ grandChild.badgeType === 'error' ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' :
246
+ grandChild.badgeType === 'warning' ? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400' :
247
+ grandChild.badgeType === 'success' ? 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' :
248
+ 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
249
+ }`}>
250
+ {grandChild.badge}
251
+ </span>
252
+ )}
253
+ </a>
254
+ )}
255
+ </li>
256
+ ))}
257
+ </ul>
258
+ </div>
259
+ ) : child.link ? (
260
+ <a
261
+ href={child.link}
262
+ target="_blank"
263
+ rel="noopener noreferrer"
264
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
265
+ title={child.title}
266
+ >
267
+ {child.icon ? (
268
+ <Icon icon={child.icon} size="1em" class="flex-shrink-0" />
269
+ ) : (
270
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
271
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
272
+ </svg>
273
+ )}
274
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
275
+ {child.title || child.name}
276
+ </span>
277
+ </a>
278
+ ) : (
279
+ <a
280
+ href={`/posts/${child.slug?.toLowerCase()}`}
281
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
282
+ title={child.title}
283
+ >
284
+ {child.icon ? (
285
+ <Icon icon={child.icon} size="1em" class="flex-shrink-0" />
286
+ ) : (
287
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
288
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
289
+ </svg>
290
+ )}
291
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
292
+ {child.title || child.name}
293
+ </span>
294
+ {child.badge && (
295
+ <span class={`ml-1 px-1.5 py-0.5 text-xs rounded ${
296
+ child.badgeType === 'error' ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' :
297
+ child.badgeType === 'warning' ? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400' :
298
+ child.badgeType === 'success' ? 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' :
299
+ 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
300
+ }`}>
301
+ {child.badge}
302
+ </span>
303
+ )}
304
+ </a>
305
+ )}
306
+ </li>
307
+ ))}
308
+ </ul>
309
+ </div>
310
+ ) : node.link ? (
311
+ <a
312
+ href={node.link}
313
+ target="_blank"
314
+ rel="noopener noreferrer"
315
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
316
+ title={node.title}
317
+ >
318
+ {node.icon ? (
319
+ <Icon icon={node.icon} size="1em" class="flex-shrink-0" />
320
+ ) : (
321
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
322
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
323
+ </svg>
324
+ )}
325
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
326
+ {node.title || node.name}
327
+ </span>
328
+ </a>
329
+ ) : (
330
+ <a
331
+ href={`/posts/${node.slug?.toLowerCase()}`}
332
+ class="tree-file flex items-center gap-2 px-2 py-1.5 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors"
333
+ title={node.title}
334
+ >
335
+ {node.icon ? (
336
+ <Icon icon={node.icon} size="1em" class="flex-shrink-0" />
337
+ ) : (
338
+ <svg class="w-4 h-4 text-slate-400 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
339
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
340
+ </svg>
341
+ )}
342
+ <span class="text-slate-600 dark:text-slate-400 truncate hover:text-primary-500">
343
+ {node.title || node.name}
344
+ </span>
345
+ {node.badge && (
346
+ <span class={`ml-1 px-1.5 py-0.5 text-xs rounded ${
347
+ node.badgeType === 'error' ? 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' :
348
+ node.badgeType === 'warning' ? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900/30 dark:text-yellow-400' :
349
+ node.badgeType === 'success' ? 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' :
350
+ 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400'
351
+ }`}>
352
+ {node.badge}
353
+ </span>
354
+ )}
355
+ </a>
356
+ )}
357
+ </li>
358
+ ))}
359
+ </ul>
360
+ </nav>
361
+ </section>
362
+ )
363
+ ))}
364
+
365
+ {/* 最新文章 */}
366
+ {sidebarConfig.showRecentPosts && recentPosts.length > 0 && (
367
+ <section>
368
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
369
+ 最新文章
370
+ </h3>
371
+ <div class="space-y-3">
372
+ {recentPosts.map((post) => (
373
+ <article class="group">
374
+ <a
375
+ href={`/posts/${post.id.toLowerCase()}`}
376
+ class="block p-3 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors"
377
+ >
378
+ <h4 class="text-sm font-medium text-slate-900 dark:text-slate-100 group-hover:text-primary-500 line-clamp-2 mb-1">
379
+ {post.data.title}
380
+ </h4>
381
+ {post.data.pubDate && (
382
+ <time class="text-xs text-slate-500 dark:text-slate-400">
383
+ {new Date(post.data.pubDate).toLocaleDateString('zh-CN')}
384
+ </time>
385
+ )}
386
+ </a>
387
+ </article>
388
+ ))}
389
+ </div>
390
+ </section>
391
+ )}
392
+
393
+ {/* 热门标签 */}
394
+ {sidebarConfig.showPopularTags && popularTags.length > 0 && (
395
+ <section>
396
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
397
+ 热门标签
398
+ </h3>
399
+ <div class="flex flex-wrap gap-2">
400
+ {popularTags.map((tag) => (
401
+ <a
402
+ href={`/tags/${tag.slug}`}
403
+ class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-primary-100 dark:bg-primary-900/30 text-primary-700 dark:text-primary-300 hover:bg-primary-200 dark:hover:bg-primary-900/50 transition-colors"
404
+ style={`font-size: ${Math.min(14, 10 + tag.count * 0.5)}px`}
405
+ >
406
+ {tag.name}
407
+ <span class="ml-1 text-primary-500">({tag.count})</span>
408
+ </a>
409
+ ))}
410
+ </div>
411
+ </section>
412
+ )}
413
+
414
+ {/* 归档 */}
415
+ {sidebarConfig.showArchives && archives.length > 0 && (
416
+ <section>
417
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
418
+ 文章归档
419
+ </h3>
420
+ <div class="space-y-2">
421
+ {archives.map((archive) => (
422
+ <a
423
+ href={`/archives/${archive.year}/${String(archive.month).padStart(2, '0')}`}
424
+ class="flex items-center justify-between p-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors group"
425
+ >
426
+ <span class="text-sm text-slate-700 dark:text-slate-300 group-hover:text-primary-500">
427
+ {archive.year}年{archive.month}月
428
+ </span>
429
+ <span class="text-xs text-slate-500 dark:text-slate-400 bg-slate-100 dark:bg-slate-700 px-2 py-1 rounded-full">
430
+ {archive.count}
431
+ </span>
432
+ </a>
433
+ ))}
434
+ </div>
435
+ </section>
436
+ )}
437
+
438
+ {/* 友情链接 */}
439
+ {sidebarConfig.showFriendLinks && sidebarConfig.friendLinks && sidebarConfig.friendLinks.length > 0 && (
440
+ <section>
441
+ <h3 class="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-4 border-b border-slate-200 dark:border-slate-700 pb-2">
442
+ 友情链接
443
+ </h3>
444
+ <div class="space-y-2">
445
+ {sidebarConfig.friendLinks.map((link) => (
446
+ <a
447
+ href={link.url}
448
+ target="_blank"
449
+ rel="noopener noreferrer"
450
+ class="flex items-center gap-2 p-2 rounded-lg hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors group"
451
+ >
452
+ {link.icon && <Icon icon={link.icon} size="1em" class="text-slate-500 group-hover:text-primary-500" />}
453
+ <span class="text-sm text-slate-700 dark:text-slate-300 group-hover:text-primary-500">
454
+ {link.title}
455
+ </span>
456
+ </a>
457
+ ))}
458
+ </div>
459
+ </section>
460
+ )}
461
+ </div>
462
+
463
+ <style>
464
+ .line-clamp-2 {
465
+ display: -webkit-box;
466
+ -webkit-line-clamp: 2;
467
+ -webkit-box-orient: vertical;
468
+ overflow: hidden;
469
+ }
470
+
471
+ /* 树形结构样式 */
472
+ .doc-tree {
473
+ max-height: 400px;
474
+ overflow-y: auto;
475
+ }
476
+
477
+ .doc-tree::-webkit-scrollbar {
478
+ width: 4px;
479
+ }
480
+
481
+ .doc-tree::-webkit-scrollbar-thumb {
482
+ @apply bg-slate-300 dark:bg-slate-600 rounded;
483
+ }
484
+
485
+ .tree-folder-toggle.expanded .tree-chevron {
486
+ transform: rotate(90deg);
487
+ }
488
+
489
+ .tree-children.show {
490
+ display: block !important;
491
+ }
492
+
493
+ .tree-file:hover span,
494
+ .tree-folder-toggle:hover span {
495
+ @apply text-primary-500;
496
+ }
497
+
498
+ /* 当前页面高亮 */
499
+ .tree-file.active {
500
+ @apply bg-primary-50 dark:bg-primary-900/20;
501
+ }
502
+
503
+ .tree-file.active span {
504
+ @apply text-primary-600 dark:text-primary-400 font-medium;
505
+ }
506
+
507
+ /* 分隔符样式 */
508
+ .sidebar-divider {
509
+ @apply py-2;
510
+ }
511
+ </style>
512
+
513
+ <script>
514
+ // 文档树交互功能
515
+ function initDocTree() {
516
+ const toggleButtons = document.querySelectorAll('.tree-folder-toggle');
517
+
518
+ toggleButtons.forEach(btn => {
519
+ btn.addEventListener('click', () => {
520
+ const children = btn.nextElementSibling;
521
+ const isExpanded = btn.classList.toggle('expanded');
522
+
523
+ if (children && children.classList.contains('tree-children')) {
524
+ children.classList.toggle('show', isExpanded);
525
+ }
526
+ });
527
+ });
528
+
529
+ // 高亮当前页面
530
+ const currentPath = window.location.pathname.replace(/\/$/, ''); // 移除尾部斜杠
531
+ const treeLinks = document.querySelectorAll('.tree-file');
532
+
533
+ treeLinks.forEach(link => {
534
+ const href = link.getAttribute('href')?.replace(/\/$/, ''); // 移除尾部斜杠
535
+ // 精确匹配:当前路径必须等于链接路径
536
+ if (href && currentPath === href) {
537
+ link.classList.add('active');
538
+
539
+ // 展开父级文件夹
540
+ let parent: Element | null = link.closest('.tree-children');
541
+ while (parent) {
542
+ parent.classList.add('show');
543
+ const toggle = parent.previousElementSibling;
544
+ if (toggle && toggle.classList.contains('tree-folder-toggle')) {
545
+ toggle.classList.add('expanded');
546
+ }
547
+ parent = parent.parentElement?.closest('.tree-children') || null;
548
+ }
549
+ }
550
+ });
551
+
552
+ // 处理默认折叠/展开状态
553
+ const docTrees = document.querySelectorAll('.doc-tree');
554
+ docTrees.forEach(tree => {
555
+ const isTreeCollapsed = tree.getAttribute('data-collapsed') === 'true';
556
+
557
+ // 处理所有文件夹的折叠状态
558
+ const allToggles = tree.querySelectorAll('.tree-folder-toggle');
559
+ allToggles.forEach(toggle => {
560
+ const shouldCollapse = toggle.getAttribute('data-default-collapsed') === 'true';
561
+ const isFirstLevel = toggle.closest('.tree-children') === null ||
562
+ toggle.closest('ul')?.parentElement?.classList.contains('doc-tree');
563
+
564
+ // 跳过已经被高亮展开的
565
+ if (toggle.classList.contains('expanded')) return;
566
+
567
+ // 如果整个树设置为折叠,或者该文件夹设置为折叠,则不展开
568
+ if (isTreeCollapsed || shouldCollapse) {
569
+ // 保持折叠状态(默认就是折叠的)
570
+ return;
571
+ }
572
+
573
+ // 只自动展开第一层
574
+ if (isFirstLevel) {
575
+ const children = toggle.nextElementSibling;
576
+ if (children && children.classList.contains('tree-children')) {
577
+ children.classList.add('show');
578
+ toggle.classList.add('expanded');
579
+ }
580
+ }
581
+ });
582
+ });
583
+ }
584
+
585
+ // DOM 加载完成后初始化
586
+ if (document.readyState === 'loading') {
587
+ document.addEventListener('DOMContentLoaded', initDocTree);
588
+ } else {
589
+ initDocTree();
590
+ }
591
+
592
+ // 支持 Astro 页面切换时重新初始化
593
+ document.addEventListener('astro:page-load', initDocTree);
594
+ </script>