@sugarat/theme 0.5.13 → 0.5.15

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.
package/node.d.ts CHANGED
@@ -4,6 +4,7 @@ import { Repo, Mapping } from '@giscus/vue';
4
4
  import { Options } from 'oh-my-live2d';
5
5
  import { PagefindConfig } from 'vitepress-plugin-pagefind';
6
6
  import { AnnouncementOptions } from 'vitepress-plugin-announcement';
7
+ import { ImagePreviewOptions } from 'vitepress-plugin-image-preview';
7
8
 
8
9
  type RSSPluginOptions = RSSOptions;
9
10
  type ThemeableImage = string | {
@@ -392,6 +393,10 @@ declare namespace Theme {
392
393
  */
393
394
  alert?: Alert;
394
395
  popover?: AnnouncementOptions;
396
+ /**
397
+ * 图片预览插件配置
398
+ */
399
+ imagePreview?: ImagePreviewOptions;
395
400
  friend?: FriendLink[] | FriendConfig;
396
401
  authorList?: Omit<FriendLink, 'avatar'>[];
397
402
  /**
@@ -617,6 +622,11 @@ declare namespace Theme {
617
622
  * @default '🏷 标签'
618
623
  */
619
624
  title?: string;
625
+ /**
626
+ * 首页标签默认展示数量,超出部分折叠
627
+ * @default 999
628
+ */
629
+ limit?: number;
620
630
  }
621
631
  }
622
632
 
package/node.js CHANGED
@@ -40,7 +40,7 @@ module.exports = __toCommonJS(node_exports);
40
40
  // src/utils/node/mdPlugins.ts
41
41
  var import_module = require("module");
42
42
 
43
- // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.7.3_vitepress@2.0.0-alpha.15_@types+node@24.5.2_async-validator_f00dcfd3cc5e72074cf41a606f1d799c/node_modules/vitepress-plugin-tabs/dist/node/index.js
43
+ // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.7.3_vitepress@2.0.0-alpha.16_@types+node@24.5.2_async-validator@4.2.5_wszfouv2xxx6bizfwwcmbwut3e/node_modules/vitepress-plugin-tabs/dist/node/index.js
44
44
  function container_plugin(md, name, options) {
45
45
  function validateDefault(params) {
46
46
  return params.trim().split(" ", 2)[0] === name;
@@ -422,7 +422,8 @@ async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset,
422
422
  meta.tag = [meta.tag || []].flat().concat([
423
423
  .../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
424
424
  ]);
425
- meta.description = meta.description || (0, import_theme_shared2.getTextSummary)(content, 100) || excerpt;
425
+ const contentWithoutHeadings = content.replace(/^#+\s+.*$/gm, "");
426
+ meta.description = meta.description || (0, import_theme_shared2.getTextSummary)(contentWithoutHeadings, 100) || excerpt;
426
427
  meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, route);
427
428
  if (meta.publish === false) {
428
429
  meta.hidden = true;
@@ -487,6 +488,7 @@ var import_vitepress_plugin_rss = require("vitepress-plugin-rss");
487
488
  var import_theme_shared4 = require("@sugarat/theme-shared");
488
489
  var import_vitepress_plugin_announcement = require("vitepress-plugin-announcement");
489
490
  var import_vitepress_plugin_group_icons2 = require("vitepress-plugin-group-icons");
491
+ var import_vitepress_plugin_image_preview = require("vitepress-plugin-image-preview");
490
492
 
491
493
  // src/utils/node/hot-reload-plugin.ts
492
494
  var import_fs = __toESM(require("fs"));
@@ -569,6 +571,7 @@ function themeReloadPlugin() {
569
571
  function getVitePlugins(cfg = {}) {
570
572
  const plugins = [];
571
573
  plugins.push(cacheAllGitTimestampsPlugin());
574
+ plugins.push((0, import_vitepress_plugin_image_preview.ImagePreviewPlugin)(cfg?.imagePreview));
572
575
  if (cfg.themeColor) {
573
576
  plugins.push(setThemeScript(cfg.themeColor));
574
577
  }
package/node.mjs CHANGED
@@ -9,7 +9,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
9
9
  // src/utils/node/mdPlugins.ts
10
10
  import { createRequire } from "module";
11
11
 
12
- // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.7.3_vitepress@2.0.0-alpha.15_@types+node@24.5.2_async-validator_f00dcfd3cc5e72074cf41a606f1d799c/node_modules/vitepress-plugin-tabs/dist/node/index.js
12
+ // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.7.3_vitepress@2.0.0-alpha.16_@types+node@24.5.2_async-validator@4.2.5_wszfouv2xxx6bizfwwcmbwut3e/node_modules/vitepress-plugin-tabs/dist/node/index.js
13
13
  function container_plugin(md, name, options) {
14
14
  function validateDefault(params) {
15
15
  return params.trim().split(" ", 2)[0] === name;
@@ -389,7 +389,8 @@ async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset,
389
389
  meta.tag = [meta.tag || []].flat().concat([
390
390
  .../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
391
391
  ]);
392
- meta.description = meta.description || getTextSummary(content, 100) || excerpt;
392
+ const contentWithoutHeadings = content.replace(/^#+\s+.*$/gm, "");
393
+ meta.description = meta.description || getTextSummary(contentWithoutHeadings, 100) || excerpt;
393
394
  meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, route);
394
395
  if (meta.publish === false) {
395
396
  meta.hidden = true;
@@ -456,6 +457,7 @@ import { RssPlugin } from "vitepress-plugin-rss";
456
457
  import { cacheAllGitTimestamps, joinPath as joinPath2 } from "@sugarat/theme-shared";
457
458
  import { AnnouncementPlugin } from "vitepress-plugin-announcement";
458
459
  import { groupIconVitePlugin } from "vitepress-plugin-group-icons";
460
+ import { ImagePreviewPlugin } from "vitepress-plugin-image-preview";
459
461
 
460
462
  // src/utils/node/hot-reload-plugin.ts
461
463
  import fs2 from "fs";
@@ -538,6 +540,7 @@ function themeReloadPlugin() {
538
540
  function getVitePlugins(cfg = {}) {
539
541
  const plugins = [];
540
542
  plugins.push(cacheAllGitTimestampsPlugin());
543
+ plugins.push(ImagePreviewPlugin(cfg?.imagePreview));
541
544
  if (cfg.themeColor) {
542
545
  plugins.push(setThemeScript(cfg.themeColor));
543
546
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sugarat/theme",
3
- "version": "0.5.13",
3
+ "version": "0.5.15",
4
4
  "description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
5
5
  "author": "sugar",
6
6
  "license": "MIT",
@@ -52,9 +52,10 @@
52
52
  "vitepress-plugin-mermaid": "2.0.13",
53
53
  "vitepress-plugin-tabs": "0.7.3",
54
54
  "@sugarat/theme-shared": "0.0.7",
55
- "vitepress-plugin-pagefind": "0.4.18",
56
- "vitepress-plugin-rss": "0.4.2",
57
- "vitepress-plugin-announcement": "0.1.7"
55
+ "vitepress-plugin-announcement": "0.1.7",
56
+ "vitepress-plugin-image-preview": "0.1.1",
57
+ "vitepress-plugin-pagefind": "0.4.19",
58
+ "vitepress-plugin-rss": "0.4.3"
58
59
  },
59
60
  "devDependencies": {
60
61
  "artalk": "^2.8.5",
@@ -62,7 +63,7 @@
62
63
  "pagefind": "^1.3.0",
63
64
  "typescript": "^5.4.5",
64
65
  "vite": "^5.4.9",
65
- "vitepress": "2.0.0-alpha.15",
66
+ "vitepress": "2.0.0-alpha.16",
66
67
  "vue": "^3.5.24",
67
68
  "vitepress-plugin-51la": "0.1.1"
68
69
  },
@@ -8,7 +8,7 @@ import BlogHomeInfo from './BlogHomeInfo.vue'
8
8
  import BlogHomeBanner from './BlogHomeBanner.vue'
9
9
  import BlogList from './BlogList.vue'
10
10
  import BlogSidebar from './BlogSidebar.vue'
11
- import BlogImagePreview from './BlogImagePreview.vue'
11
+
12
12
  import BlogArticleAnalyze from './BlogArticleAnalyze.vue'
13
13
  import BlogAlert from './BlogAlert.vue'
14
14
  import BlogFooter from './BlogFooter.vue'
@@ -49,8 +49,6 @@ const openTransition = useDarkTransitionConfig()
49
49
  <!-- 阅读时间分析 -->
50
50
  <ClientOnly>
51
51
  <BlogArticleAnalyze />
52
- <!-- 图片预览 -->
53
- <BlogImagePreview />
54
52
  </ClientOnly>
55
53
  </template>
56
54
 
@@ -128,10 +126,6 @@ const openTransition = useDarkTransitionConfig()
128
126
  <!-- content -->
129
127
  <template #page-top>
130
128
  <slot name="page-top" />
131
- <ClientOnly>
132
- <!-- 图片预览 -->
133
- <BlogImagePreview />
134
- </ClientOnly>
135
129
  </template>
136
130
  <template #page-bottom>
137
131
  <slot name="page-bottom" />
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import { computed, watch } from 'vue'
2
+ import { computed, ref, watch } from 'vue'
3
3
  import { useBrowserLocation, useUrlSearchParams } from '@vueuse/core'
4
4
  import { useRoute, useRouter } from 'vitepress'
5
5
  import {
@@ -19,9 +19,42 @@ const title = computed(() => (typeof homeTagsConfig.value === 'boolean' || !home
19
19
  ? `${tagsSvgStr}标签`
20
20
  : homeTagsConfig.value?.title
21
21
  )
22
- const tags = computed(() => {
23
- return [...new Set(docs.value.map(v => v.meta.tag || []).flat(3))]
22
+
23
+ // 统计每个标签的文章数量,并按数量降序排序
24
+ const tagsWithCount = computed(() => {
25
+ const tagCountMap = new Map<string, number>()
26
+ docs.value.forEach((v) => {
27
+ const articleTags = v.meta.tag || []
28
+ const flatTags = Array.isArray(articleTags) ? articleTags.flat(3) : [articleTags]
29
+ flatTags.forEach((tag: string) => {
30
+ tagCountMap.set(tag, (tagCountMap.get(tag) || 0) + 1)
31
+ })
32
+ })
33
+ // 按文章数量降序排序
34
+ return [...tagCountMap.entries()]
35
+ .sort((a, b) => b[1] - a[1])
36
+ .map(([tag, count]) => ({ tag, count }))
37
+ })
38
+
39
+ // 折叠/展开功能,从配置中读取 limit,默认 10
40
+ const showCount = computed(() => {
41
+ if (typeof homeTagsConfig.value === 'object' && homeTagsConfig.value?.limit) {
42
+ return homeTagsConfig.value.limit
43
+ }
44
+ return 999
45
+ })
46
+ const isExpanded = ref(false)
47
+ const displayTags = computed(() => {
48
+ if (isExpanded.value || tagsWithCount.value.length <= showCount.value) {
49
+ return tagsWithCount.value
50
+ }
51
+ return tagsWithCount.value.slice(0, showCount.value)
24
52
  })
53
+ const hasMore = computed(() => tagsWithCount.value.length > showCount.value)
54
+
55
+ function toggleExpand() {
56
+ isExpanded.value = !isExpanded.value
57
+ }
25
58
 
26
59
  const activeTag = useActiveTag()
27
60
 
@@ -78,7 +111,7 @@ watch(
78
111
  </script>
79
112
 
80
113
  <template>
81
- <div v-if="showTags && tags.length" class="card tags" data-pagefind-ignore="all">
114
+ <div v-if="showTags && tagsWithCount.length" class="card tags" data-pagefind-ignore="all">
82
115
  <!-- 头部 -->
83
116
  <div class="card-header">
84
117
  <span class="title svg-icon" v-html="title" />
@@ -91,15 +124,33 @@ watch(
91
124
  </div>
92
125
  <!-- 标签列表 -->
93
126
  <ul class="tag-list">
94
- <li v-for="(tag, idx) in tags" :key="tag">
127
+ <li v-for="(item, idx) in displayTags" :key="item.tag">
95
128
  <Tag
96
129
  :type="tagType[idx % tagType.length] || 'primary'"
97
- @click="handleTagClick(tag, tagType[idx % tagType.length])"
130
+ @click="handleTagClick(item.tag, tagType[idx % tagType.length])"
98
131
  >
99
- {{ tag }}
132
+ {{ item.tag }}
133
+ <span class="tag-count">{{ item.count }}</span>
100
134
  </Tag>
101
135
  </li>
102
136
  </ul>
137
+ <!-- 展开/收起按钮 -->
138
+ <div v-if="hasMore" class="expand-btn" @click="toggleExpand">
139
+ <span>{{ isExpanded ? '收起' : `展开全部 ${tagsWithCount.length} 个标签` }}</span>
140
+ <svg
141
+ class="expand-icon"
142
+ :class="{ 'is-expanded': isExpanded }"
143
+ viewBox="0 0 1024 1024"
144
+ xmlns="http://www.w3.org/2000/svg"
145
+ width="1em"
146
+ height="1em"
147
+ >
148
+ <path
149
+ fill="currentColor"
150
+ d="M488.832 344.32l-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0z"
151
+ />
152
+ </svg>
153
+ </div>
103
154
  </div>
104
155
  </template>
105
156
 
@@ -147,4 +198,43 @@ watch(
147
198
  margin-bottom: 10px;
148
199
  cursor: pointer;
149
200
  }
201
+
202
+ .tag-count {
203
+ display: inline-flex;
204
+ align-items: center;
205
+ justify-content: center;
206
+ margin-left: 4px;
207
+ min-width: 16px;
208
+ height: 16px;
209
+ padding: 0 4px;
210
+ font-size: 10px;
211
+ line-height: 1;
212
+ border-radius: 8px;
213
+ background-color: rgba(255, 255, 255, 0.3);
214
+ }
215
+
216
+ .expand-btn {
217
+ display: flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ gap: 4px;
221
+ margin-top: 6px;
222
+ padding: 4px 0;
223
+ font-size: 12px;
224
+ color: var(--vp-c-text-2);
225
+ cursor: pointer;
226
+ transition: color 0.3s;
227
+ user-select: none;
228
+ }
229
+ .expand-btn:hover {
230
+ color: var(--vp-c-brand);
231
+ }
232
+
233
+ .expand-icon {
234
+ transition: transform 0.3s;
235
+ transform: rotate(180deg);
236
+ }
237
+ .expand-icon.is-expanded {
238
+ transform: rotate(0deg);
239
+ }
150
240
  </style>
@@ -5,6 +5,7 @@ import type { Mapping, Repo } from '@giscus/vue'
5
5
  import type { Options as Oml2dOptions } from 'oh-my-live2d'
6
6
  import type { PagefindConfig } from 'vitepress-plugin-pagefind'
7
7
  import type { AnnouncementOptions } from 'vitepress-plugin-announcement'
8
+ import type { ImagePreviewOptions } from 'vitepress-plugin-image-preview'
8
9
 
9
10
  type RSSPluginOptions = RSSOptions
10
11
 
@@ -420,6 +421,10 @@ export namespace Theme {
420
421
  */
421
422
  alert?: Alert
422
423
  popover?: AnnouncementOptions
424
+ /**
425
+ * 图片预览插件配置
426
+ */
427
+ imagePreview?: ImagePreviewOptions
423
428
  friend?: FriendLink[] | FriendConfig
424
429
  authorList?: Omit<FriendLink, 'avatar'>[]
425
430
  /**
@@ -654,5 +659,10 @@ export namespace Theme {
654
659
  * @default '🏷 标签'
655
660
  */
656
661
  title?: string
662
+ /**
663
+ * 首页标签默认展示数量,超出部分折叠
664
+ * @default 999
665
+ */
666
+ limit?: number
657
667
  }
658
668
  }
@@ -66,8 +66,10 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
66
66
 
67
67
  // 获取摘要信息
68
68
  // TODO:摘要生成优化,支持AI?
69
+ // 先去除 Markdown 中的标题行(# 开头),避免 description 中重复出现标题文字
70
+ const contentWithoutHeadings = content.replace(/^#+\s+.*$/gm, '')
69
71
  meta.description
70
- = meta.description || getTextSummary(content, 100) || excerpt
72
+ = meta.description || getTextSummary(contentWithoutHeadings, 100) || excerpt
71
73
 
72
74
  // 获取封面图
73
75
  // TODO: 耦合信息优化
@@ -7,6 +7,7 @@ import type { PluginOption } from 'vite'
7
7
  import { cacheAllGitTimestamps, joinPath } from '@sugarat/theme-shared'
8
8
  import { AnnouncementPlugin } from 'vitepress-plugin-announcement'
9
9
  import { groupIconVitePlugin } from 'vitepress-plugin-group-icons'
10
+ import { ImagePreviewPlugin } from 'vitepress-plugin-image-preview'
10
11
  import type { Theme } from '../../composables/config/index'
11
12
  import { _require } from './mdPlugins'
12
13
  import { themeReloadPlugin } from './hot-reload-plugin'
@@ -18,6 +19,9 @@ export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
18
19
  // 缓存所有文章的 git 提交时间
19
20
  plugins.push(cacheAllGitTimestampsPlugin())
20
21
 
22
+ // 图片预览
23
+ plugins.push(ImagePreviewPlugin(cfg?.imagePreview))
24
+
21
25
  // 处理自定义主题色
22
26
  if (cfg.themeColor) {
23
27
  plugins.push(setThemeScript(cfg.themeColor))