@sugarat/theme 0.3.0 → 0.3.2

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
@@ -406,6 +406,12 @@ declare namespace Theme {
406
406
  oml2d?: Options;
407
407
  homeTags?: boolean;
408
408
  buttonAfterArticle?: ButtonAfterArticleConfig | false;
409
+ /**
410
+ * 是否开启深色模式过渡动画
411
+ * @reference https://vitepress.dev/zh/guide/extending-default-theme#on-appearance-toggle
412
+ * @default true
413
+ */
414
+ darkTransition?: boolean;
409
415
  }
410
416
  interface BackToTop {
411
417
  /**
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.2.0_vitepress@1.0.2_vue@3.4.21/node_modules/vitepress-plugin-tabs/dist/index.js
43
+ // ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.1.4_@algolia+client-search@4.19.1_@types+node@20.6.3__ee2wap4ljxfukruj47sfuqlmqq/node_modules/vitepress-plugin-tabs/dist/index.js
44
44
  var tabsMarker = "=tabs";
45
45
  var tabsMarkerLen = tabsMarker.length;
46
46
  var ruleBlockTabs = (state, startLine, endLine, silent) => {
@@ -210,69 +210,9 @@ var import_vitepress_markdown_timeline = __toESM(require("vitepress-markdown-tim
210
210
  // src/utils/node/index.ts
211
211
  var import_node_child_process = require("child_process");
212
212
  var import_node_path = __toESM(require("path"));
213
-
214
- // src/utils/client/index.ts
215
- function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
216
- if (!(d instanceof Date)) {
217
- d = new Date(d);
218
- }
219
- const o = {
220
- "M+": d.getMonth() + 1,
221
- // 月份
222
- "d+": d.getDate(),
223
- // 日
224
- "h+": d.getHours(),
225
- // 小时
226
- "m+": d.getMinutes(),
227
- // 分
228
- "s+": d.getSeconds(),
229
- // 秒
230
- "q+": Math.floor((d.getMonth() + 3) / 3),
231
- // 季度
232
- "S": d.getMilliseconds()
233
- // 毫秒
234
- };
235
- if (/(y+)/.test(fmt)) {
236
- fmt = fmt.replace(
237
- RegExp.$1,
238
- `${d.getFullYear()}`.substr(4 - RegExp.$1.length)
239
- );
240
- }
241
- for (const k in o) {
242
- if (new RegExp(`(${k})`).test(fmt))
243
- fmt = fmt.replace(
244
- RegExp.$1,
245
- RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length)
246
- );
247
- }
248
- return fmt;
249
- }
250
-
251
- // src/utils/node/index.ts
252
- function clearMatterContent(content) {
253
- let first___;
254
- let second___;
255
- const lines = content.split("\n").reduce((pre, line) => {
256
- if (!line.trim() && pre.length === 0) {
257
- return pre;
258
- }
259
- if (line.trim() === "---") {
260
- if (first___ === void 0) {
261
- first___ = pre.length;
262
- } else if (second___ === void 0) {
263
- second___ = pre.length;
264
- }
265
- }
266
- pre.push(line);
267
- return pre;
268
- }, []);
269
- return lines.slice(second___ || 0).join("\n");
270
- }
271
213
  function getDefaultTitle(content) {
272
- const title = clearMatterContent(content).split("\n")?.find((str) => {
273
- return str.startsWith("# ");
274
- })?.slice(2).replace(/^\s+|\s+$/g, "") || "";
275
- return title;
214
+ const match = content.match(/^(#+)\s+(.+)/m);
215
+ return match?.[2] || "";
276
216
  }
277
217
  function getFileBirthTime(url) {
278
218
  let date = /* @__PURE__ */ new Date();
@@ -282,12 +222,12 @@ function getFileBirthTime(url) {
282
222
  date = new Date(infoStr);
283
223
  }
284
224
  } catch (error) {
285
- return formatDate(date);
225
+ return date;
286
226
  }
287
- return formatDate(date);
227
+ return date;
288
228
  }
289
229
  function getTextSummary(text, count = 100) {
290
- return clearMatterContent(text).match(/^# ([\s\S]+)/m)?.[1]?.replace(/#/g, "")?.replace(/!\[.*?\]\(.*?\)/g, "")?.replace(/\[(.*?)\]\(.*?\)/g, "$1")?.replace(/\*\*(.*?)\*\*/g, "$1")?.split("\n")?.filter((v) => !!v)?.slice(1)?.join("\n")?.replace(/>(.*)/, "")?.slice(0, count);
230
+ return text?.replace(/^#+\s+.*/, "")?.replace(/#/g, "")?.replace(/!\[.*?\]\(.*?\)/g, "")?.replace(/\[(.*?)\]\(.*?\)/g, "$1")?.replace(/\*\*(.*?)\*\*/g, "$1")?.split("\n")?.filter((v) => !!v)?.join("\n")?.replace(/>(.*)/, "")?.replace(/</g, "&lt;").replace(/>/g, "&gt;")?.trim()?.slice(0, count);
291
231
  }
292
232
  function aliasObjectToArray(obj) {
293
233
  return Object.entries(obj).map(([find, replacement]) => ({
@@ -317,6 +257,37 @@ function getFirstImagURLFromMD(content, route) {
317
257
  const relativePath = url.startsWith("/") ? url : import_node_path.default.join(paths.join("/") || "", url);
318
258
  return joinPath("/", relativePath);
319
259
  }
260
+ function debounce(func, delay = 1e3) {
261
+ let timeoutId;
262
+ return (...rest) => {
263
+ clearTimeout(timeoutId);
264
+ timeoutId = setTimeout(() => {
265
+ func(...rest);
266
+ }, delay);
267
+ };
268
+ }
269
+ function isEqual(obj1, obj2, excludeKeys = []) {
270
+ const keys1 = Object.keys(obj1).filter((key) => !excludeKeys.includes(key));
271
+ const keys2 = Object.keys(obj2).filter((key) => !excludeKeys.includes(key));
272
+ if (keys1.length !== keys2.length) {
273
+ return false;
274
+ }
275
+ for (const key of keys1) {
276
+ if (!keys2.includes(key)) {
277
+ return false;
278
+ }
279
+ const val1 = obj1[key];
280
+ const val2 = obj2[key];
281
+ const areObjects = isObject(val1) && isObject(val2);
282
+ if (areObjects && !isEqual(val1, val2, excludeKeys) || !areObjects && val1 !== val2) {
283
+ return false;
284
+ }
285
+ }
286
+ return true;
287
+ }
288
+ function isObject(obj) {
289
+ return obj != null && typeof obj === "object";
290
+ }
320
291
 
321
292
  // src/utils/node/mdPlugins.ts
322
293
  var import_meta = {};
@@ -332,7 +303,7 @@ function getMarkdownPlugins(cfg) {
332
303
  const { MermaidMarkdown } = _require("vitepress-plugin-mermaid");
333
304
  markdownPlugin.push(MermaidMarkdown);
334
305
  }
335
- if (cfg.taskCheckbox !== false) {
306
+ if (cfg?.taskCheckbox !== false) {
336
307
  markdownPlugin.push(taskCheckboxPlugin(typeof cfg?.taskCheckbox === "boolean" ? {} : cfg?.taskCheckbox));
337
308
  }
338
309
  if (cfg?.timeline !== false) {
@@ -384,6 +355,45 @@ var import_node_path2 = __toESM(require("path"));
384
355
  var import_node_process = __toESM(require("process"));
385
356
  var import_fast_glob = __toESM(require("fast-glob"));
386
357
  var import_gray_matter = __toESM(require("gray-matter"));
358
+
359
+ // src/utils/client/index.ts
360
+ function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
361
+ if (!(d instanceof Date)) {
362
+ d = new Date(d);
363
+ }
364
+ const o = {
365
+ "M+": d.getMonth() + 1,
366
+ // 月份
367
+ "d+": d.getDate(),
368
+ // 日
369
+ "h+": d.getHours(),
370
+ // 小时
371
+ "m+": d.getMinutes(),
372
+ // 分
373
+ "s+": d.getSeconds(),
374
+ // 秒
375
+ "q+": Math.floor((d.getMonth() + 3) / 3),
376
+ // 季度
377
+ "S": d.getMilliseconds()
378
+ // 毫秒
379
+ };
380
+ if (/(y+)/.test(fmt)) {
381
+ fmt = fmt.replace(
382
+ RegExp.$1,
383
+ `${d.getFullYear()}`.substr(4 - RegExp.$1.length)
384
+ );
385
+ }
386
+ for (const k in o) {
387
+ if (new RegExp(`(${k})`).test(fmt))
388
+ fmt = fmt.replace(
389
+ RegExp.$1,
390
+ RegExp.$1.length === 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length)
391
+ );
392
+ }
393
+ return fmt;
394
+ }
395
+
396
+ // src/utils/node/theme.ts
387
397
  function patchDefaultThemeSideBar(cfg) {
388
398
  return cfg?.blog !== false && cfg?.recommend !== false ? {
389
399
  sidebar: [
@@ -394,64 +404,69 @@ function patchDefaultThemeSideBar(cfg) {
394
404
  ]
395
405
  } : void 0;
396
406
  }
397
- var pageMap = /* @__PURE__ */ new Map();
407
+ function getPageRoute(filepath, srcDir) {
408
+ let route = filepath.replace(".md", "");
409
+ if (route.startsWith("./")) {
410
+ route = route.replace(
411
+ new RegExp(
412
+ `^\\.\\/${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
413
+ ),
414
+ ""
415
+ );
416
+ } else {
417
+ route = route.replace(
418
+ new RegExp(
419
+ `^${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
420
+ ),
421
+ ""
422
+ );
423
+ }
424
+ return `/${route}`;
425
+ }
426
+ var defaultTimeZoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() / -60;
427
+ function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
428
+ const fileContent = import_node_fs.default.readFileSync(filepath, "utf-8");
429
+ const { data: frontmatter, excerpt, content } = (0, import_gray_matter.default)(fileContent, {
430
+ excerpt: true
431
+ });
432
+ const meta = {
433
+ ...frontmatter
434
+ };
435
+ if (!meta.title) {
436
+ meta.title = getDefaultTitle(content);
437
+ }
438
+ if (!meta.date) {
439
+ meta.date = formatDate(getFileBirthTime(filepath));
440
+ } else {
441
+ meta.date = formatDate(
442
+ /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`)
443
+ );
444
+ }
445
+ meta.categories = typeof meta.categories === "string" ? [meta.categories] : meta.categories;
446
+ meta.tags = typeof meta.tags === "string" ? [meta.tags] : meta.tags;
447
+ meta.tag = [meta.tag || []].flat().concat([
448
+ .../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
449
+ ]);
450
+ meta.description = meta.description || getTextSummary(content, 100) || excerpt;
451
+ meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, route);
452
+ if (meta.publish === false) {
453
+ meta.hidden = true;
454
+ meta.recommend = false;
455
+ }
456
+ return meta;
457
+ }
398
458
  function getArticles(cfg) {
399
459
  const srcDir = cfg?.srcDir || import_node_process.default.argv.slice(2)?.[1] || ".";
400
460
  const files = import_fast_glob.default.sync(`${srcDir}/**/*.md`, { ignore: ["node_modules"] });
401
- const data = files.map((v) => {
402
- let route = v.replace(".md", "");
403
- if (route.startsWith("./")) {
404
- route = route.replace(
405
- new RegExp(
406
- `^\\.\\/${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
407
- ),
408
- ""
409
- );
410
- } else {
411
- route = route.replace(
412
- new RegExp(
413
- `^${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
414
- ),
415
- ""
416
- );
417
- }
418
- pageMap.set(`/${route}`, v);
419
- const fileContent = import_node_fs.default.readFileSync(v, "utf-8");
420
- const { data: frontmatter, excerpt } = (0, import_gray_matter.default)(fileContent, {
421
- excerpt: true
422
- });
423
- const meta = {
424
- ...frontmatter
425
- };
426
- if (!meta.title) {
427
- meta.title = getDefaultTitle(fileContent);
428
- }
429
- if (!meta.date) {
430
- meta.date = getFileBirthTime(v);
431
- } else {
432
- const timeZone = cfg?.timeZone ?? 8;
433
- meta.date = formatDate(
434
- /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`)
435
- );
436
- }
437
- meta.categories = typeof meta.categories === "string" ? [meta.categories] : meta.categories;
438
- meta.tags = typeof meta.tags === "string" ? [meta.tags] : meta.tags;
439
- meta.tag = [meta.tag || []].flat().concat([
440
- .../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
441
- ]);
442
- const wordCount = 100;
443
- meta.description = meta.description || getTextSummary(fileContent, wordCount);
444
- meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, `/${route}`);
445
- if (meta.publish === false) {
446
- meta.hidden = true;
447
- meta.recommend = false;
448
- }
461
+ const pageData = files.map((filepath) => {
462
+ const route = getPageRoute(filepath, srcDir);
463
+ const meta = getArticleMeta(filepath, route, cfg?.timeZone);
449
464
  return {
450
- route: `/${route}`,
465
+ route,
451
466
  meta
452
467
  };
453
468
  }).filter((v) => v.meta.layout !== "home");
454
- return data;
469
+ return pageData;
455
470
  }
456
471
  function patchVPConfig(vpConfig, cfg) {
457
472
  vpConfig.head = vpConfig.head || [];
@@ -478,11 +493,64 @@ var import_node_fs2 = require("fs");
478
493
  var import_node_buffer = require("buffer");
479
494
  var import_vitepress_plugin_pagefind = require("vitepress-plugin-pagefind");
480
495
  var import_vitepress_plugin_rss = require("vitepress-plugin-rss");
496
+
497
+ // src/utils/node/hot-reload-plugin.ts
498
+ function themeReloadPlugin() {
499
+ let blogConfig;
500
+ let vitepressConfig;
501
+ let docsDir;
502
+ const generateRoute = (filepath) => {
503
+ return filepath.replace(docsDir, "").replace(".md", "");
504
+ };
505
+ return {
506
+ name: "@sugarat/theme-reload",
507
+ apply: "serve",
508
+ configureServer(server) {
509
+ const restart = debounce(() => {
510
+ server.restart();
511
+ }, 500);
512
+ server.watcher.on("add", (path4) => {
513
+ const route = generateRoute(path4);
514
+ const meta = getArticleMeta(path4, route, blogConfig?.timeZone);
515
+ blogConfig.pagesData.push({
516
+ route,
517
+ meta
518
+ });
519
+ restart();
520
+ });
521
+ server.watcher.on("change", (path4) => {
522
+ const route = generateRoute(path4);
523
+ const meta = getArticleMeta(path4, route, blogConfig?.timeZone);
524
+ const matched = blogConfig.pagesData.find((v) => v.route === route);
525
+ if (matched && !isEqual(matched.meta, meta, ["date", "description"])) {
526
+ matched.meta = meta;
527
+ restart();
528
+ }
529
+ });
530
+ server.watcher.on("unlink", (path4) => {
531
+ const route = generateRoute(path4);
532
+ const idx = blogConfig.pagesData.findIndex((v) => v.route === route);
533
+ if (idx >= 0) {
534
+ blogConfig.pagesData.splice(idx, 1);
535
+ restart();
536
+ }
537
+ });
538
+ },
539
+ configResolved(config) {
540
+ vitepressConfig = config.vitepress;
541
+ docsDir = vitepressConfig.srcDir;
542
+ blogConfig = config.vitepress.site.themeConfig.blog;
543
+ }
544
+ };
545
+ }
546
+
547
+ // src/utils/node/vitePlugins.ts
481
548
  function getVitePlugins(cfg) {
482
549
  const plugins = [];
483
550
  const buildEndFn = [];
484
551
  plugins.push(inlineBuildEndPlugin(buildEndFn));
485
552
  plugins.push(coverImgTransform());
553
+ plugins.push(themeReloadPlugin());
486
554
  if (cfg && cfg.search !== false) {
487
555
  const ops = cfg.search instanceof Object ? cfg.search : {};
488
556
  plugins.push(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sugarat/theme",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
5
5
  "author": "sugar",
6
6
  "license": "MIT",
@@ -58,7 +58,8 @@
58
58
  "pagefind": "1.0.3",
59
59
  "sass": "^1.56.1",
60
60
  "typescript": "^4.8.2",
61
- "vitepress": "1.0.2",
61
+ "vite": "^5",
62
+ "vitepress": "1.1.4",
62
63
  "vue": "^3.4.21"
63
64
  },
64
65
  "scripts": {
@@ -2,8 +2,9 @@
2
2
  import Theme from 'vitepress/theme'
3
3
  import { useData } from 'vitepress'
4
4
  import { computed } from 'vue'
5
+ import { useDarkTransition } from '../hooks/useDarkTransition'
5
6
  import { useOml2d } from '../hooks/useOml2d'
6
- import { useBlogThemeMode } from '../composables/config/blog'
7
+ import { useBlogThemeMode, useDarkTransitionConfig } from '../composables/config/blog'
7
8
  import BlogHomeInfo from './BlogHomeInfo.vue'
8
9
  import BlogHomeBanner from './BlogHomeBanner.vue'
9
10
  import BlogList from './BlogList.vue'
@@ -28,14 +29,20 @@ const { Layout } = Theme
28
29
 
29
30
  // oh-my-live2d 扩展
30
31
  useOml2d()
32
+ // 切换深色模式过渡
33
+ // https://vitepress.dev/zh/guide/extending-default-theme#on-appearance-toggle
34
+ useDarkTransition()
35
+ const openTransition = useDarkTransitionConfig()
31
36
  </script>
32
37
 
33
38
  <template>
34
- <Layout>
39
+ <Layout :class="{ 'blog-theme-layout': openTransition }">
35
40
  <template #layout-top>
36
41
  <slot name="layout-top" />
37
- <BlogAlert />
38
- <BlogPopover />
42
+ <ClientOnly>
43
+ <BlogAlert />
44
+ <BlogPopover />
45
+ </ClientOnly>
39
46
  </template>
40
47
 
41
48
  <template #doc-before>
@@ -224,3 +231,7 @@ useOml2d()
224
231
  }
225
232
  }
226
233
  </style>
234
+
235
+ <style>
236
+ @import url(./../styles/dark-transition.css);
237
+ </style>
@@ -126,7 +126,7 @@ h1 {
126
126
 
127
127
  .inspiring-wrapper {
128
128
  margin: 16px 0;
129
- height: 24px;
129
+ height: 32px;
130
130
  width: auto;
131
131
 
132
132
  h2 {
@@ -1,12 +1,8 @@
1
1
  <script setup lang="ts">
2
2
  import { useData, withBase } from 'vitepress'
3
3
  import { computed } from 'vue'
4
- import { useWindowSize } from '@vueuse/core'
5
4
  import { useBlogConfig } from '../composables/config/blog'
6
5
 
7
- const { width } = useWindowSize()
8
- const inMiniScreen = computed(() => width.value <= 767)
9
-
10
6
  const { home } = useBlogConfig()
11
7
  const { frontmatter, site } = useData()
12
8
  const logo = computed(() =>
@@ -19,7 +15,7 @@ const alwaysHide = computed(() => frontmatter.value.blog?.minScreenAvatar === fa
19
15
  </script>
20
16
 
21
17
  <template>
22
- <div v-show="inMiniScreen && !alwaysHide" class="blog-home-header-avatar">
18
+ <div v-show="!alwaysHide" class="blog-home-header-avatar">
23
19
  <img :src="withBase(logo)" alt="avatar">
24
20
  </div>
25
21
  </template>
@@ -47,4 +43,10 @@ const alwaysHide = computed(() => frontmatter.value.blog?.minScreenAvatar === fa
47
43
  transition-timing-function: cubic-bezier(.34, 0, .84, 1)
48
44
  }
49
45
  }
46
+
47
+ @media screen and (min-width: 768px) {
48
+ .blog-home-header-avatar{
49
+ display: none;
50
+ }
51
+ }
50
52
  </style>
@@ -34,7 +34,7 @@ import BlogFriendLink from './BlogFriendLink.vue'
34
34
 
35
35
  @media screen and (min-width: 767px) {
36
36
  .blog-info {
37
- max-width: 300px;
37
+ width: 280px;
38
38
  }
39
39
  }
40
40
  </style>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import { computed, ref } from 'vue'
3
- import { ElButton, ElLink } from 'element-plus'
4
- import { withBase } from 'vitepress'
3
+ import { ElButton } from 'element-plus'
4
+ import { useRouter, withBase } from 'vitepress'
5
5
  import { useArticles, useBlogConfig, useCleanUrls } from '../composables/config/blog'
6
6
  import { formatShowDate, wrapperCleanUrls } from '../utils/client'
7
7
  import { fireSVG } from '../constants/svg'
@@ -26,6 +26,11 @@ const recommendList = computed(() => {
26
26
  })
27
27
 
28
28
  const currentPage = ref(1)
29
+
30
+ const router = useRouter()
31
+ function handleLinkClick(link: string) {
32
+ router.go(link)
33
+ }
29
34
  function changePage() {
30
35
  const newIdx
31
36
  = currentPage.value % Math.ceil(recommendList.value.length / pageSize.value)
@@ -48,7 +53,10 @@ const showChangeBtn = computed(() => {
48
53
  </script>
49
54
 
50
55
  <template>
51
- <div v-if="_hotArticle !== false && (recommendList.length || empty) " class="card recommend" data-pagefind-ignore="all">
56
+ <div
57
+ v-if="_hotArticle !== false && (recommendList.length || empty)" class="card recommend"
58
+ data-pagefind-ignore="all"
59
+ >
52
60
  <!-- 头部 -->
53
61
  <div class="card-header">
54
62
  <span class="title" v-html="title" />
@@ -64,11 +72,19 @@ const showChangeBtn = computed(() => {
64
72
  <!-- 简介 -->
65
73
  <div class="des">
66
74
  <!-- title -->
67
- <ElLink type="info" class="title" :href="withBase(v.route)">
68
- {{
69
- v.meta.title
70
- }}
71
- </ElLink>
75
+ <a
76
+ :href="withBase(v.route)"
77
+ class="title" @click="(e) => {
78
+ e.preventDefault()
79
+ handleLinkClick(withBase(v.route))
80
+ }"
81
+ >
82
+ <span>
83
+ {{
84
+ v.meta.title
85
+ }}
86
+ </span>
87
+ </a>
72
88
  <!-- 描述信息 -->
73
89
  <div class="suffix">
74
90
  <!-- 日期 -->
@@ -172,6 +188,19 @@ const showChangeBtn = computed(() => {
172
188
  .title {
173
189
  font-size: 14px;
174
190
  color: var(--vp-c-text-1);
191
+ font-weight: 500;
192
+ position: relative;
193
+ cursor: pointer;
194
+ }
195
+
196
+ .title:hover::after {
197
+ content: "";
198
+ position: absolute;
199
+ left: 0;
200
+ right: 0;
201
+ height: 0;
202
+ bottom: -3px;
203
+ border-bottom: 1px solid #b1b3b8;
175
204
  }
176
205
 
177
206
  .suffix {
@@ -1,5 +1,5 @@
1
1
  <script lang="ts" setup>
2
- import { withBase } from 'vitepress'
2
+ import { useRouter, withBase } from 'vitepress'
3
3
  import { computed } from 'vue'
4
4
  import { formatShowDate, wrapperCleanUrls } from '../utils/client'
5
5
  import { useCleanUrls } from '../composables/config/blog'
@@ -19,24 +19,36 @@ const props = defineProps<{
19
19
  const showTime = computed(() => {
20
20
  return formatShowDate(props.date)
21
21
  })
22
-
23
22
  const cleanUrls = useCleanUrls()
24
23
  const link = computed(() => withBase(wrapperCleanUrls(!!cleanUrls, props.route)))
24
+
25
+ const router = useRouter()
26
+ function handleSkipDoc() {
27
+ router.go(link.value)
28
+ }
25
29
  </script>
26
30
 
27
31
  <template>
28
- <!-- TODO: 响应式优化使用纯 CSS -->
29
- <a class="blog-item" :href="link">
30
- <i v-if="!!pin" class="pin" />
32
+ <a
33
+ class="blog-item" :href="link" @click="(e) => {
34
+ e.preventDefault()
35
+ handleSkipDoc()
36
+ }"
37
+ >
38
+ <i v-show="!!pin" class="pin" />
31
39
  <!-- 标题 -->
32
- <p class="title mobile-visible">{{ title }}</p>
40
+ <p class="title mobile-visible">
41
+ {{ title }}
42
+ </p>
33
43
  <div class="info-container">
34
44
  <!-- 左侧信息 -->
35
45
  <div class="info-part">
36
46
  <!-- 标题 -->
37
- <p class="title pc-visible">{{ title }}</p>
47
+ <p class="title pc-visible">
48
+ {{ title }}
49
+ </p>
38
50
  <!-- 简短描述 -->
39
- <p v-if="!descriptionHTML && !!description" class="description">
51
+ <p v-show="!descriptionHTML && !!description" class="description">
40
52
  {{ description }}
41
53
  </p>
42
54
  <template v-if="descriptionHTML">
@@ -44,19 +56,19 @@ const link = computed(() => withBase(wrapperCleanUrls(!!cleanUrls, props.route))
44
56
  </template>
45
57
  <!-- 底部补充描述 -->
46
58
  <div class="badge-list pc-visible">
47
- <span v-if="author" class="split">{{ author }}</span>
59
+ <span v-show="author" class="split">{{ author }}</span>
48
60
  <span class="split">{{ showTime }}</span>
49
- <span v-if="tag?.length" class="split">{{ tag.join(' · ') }}</span>
61
+ <span v-show="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
50
62
  </div>
51
63
  </div>
52
64
  <!-- 右侧封面图 -->
53
- <div v-if="cover" class="cover-img" :style="`background-image: url(${withBase(`${cover}`)});`" />
65
+ <div v-show="cover" class="cover-img" :style="`background-image: url(${withBase(`${cover}`)});`" />
54
66
  </div>
55
67
  <!-- 底部补充描述 -->
56
68
  <div class="badge-list mobile-visible">
57
- <span v-if="author" class="split">{{ author }}</span>
69
+ <span v-show="author" class="split">{{ author }}</span>
58
70
  <span class="split">{{ showTime }}</span>
59
- <span v-if="tag?.length" class="split">{{ tag.join(' · ') }}</span>
71
+ <span v-show="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
60
72
  </div>
61
73
  </a>
62
74
  </template>
@@ -239,6 +239,10 @@ function PopoverValue(props: { key: number; item: BlogPopover.Value },
239
239
 
240
240
  img {
241
241
  width: 100%;
242
+ // TODO: 未来优化,自动预获取图片高度填充
243
+ height: 100px;
244
+ object-fit: contain;
245
+ margin: 0 auto;
242
246
  }
243
247
  }
244
248
 
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import { computed, onMounted, ref } from 'vue'
3
- import { useRoute, withBase } from 'vitepress'
4
- import { ElButton, ElLink } from 'element-plus'
3
+ import { useRoute, useRouter, withBase } from 'vitepress'
4
+ import { ElButton } from 'element-plus'
5
5
  import { formatShowDate, wrapperCleanUrls } from '../utils/client'
6
6
  import { useArticles, useBlogConfig, useCleanUrls } from '../composables/config/blog'
7
7
  import { recommendSVG } from '../constants/svg'
@@ -147,6 +147,11 @@ onMounted(() => {
147
147
  })
148
148
 
149
149
  const cleanUrls = useCleanUrls()
150
+
151
+ const router = useRouter()
152
+ function handleLinkClick(link: string) {
153
+ router.go(link)
154
+ }
150
155
  </script>
151
156
 
152
157
  <template>
@@ -169,13 +174,18 @@ const cleanUrls = useCleanUrls()
169
174
  <!-- 简介 -->
170
175
  <div class="des">
171
176
  <!-- title -->
172
- <ElLink
173
- type="info" class="title" :class="{
177
+ <a
178
+ class="title" :class="{
174
179
  current: isCurrentDoc(v.route),
175
- }" :href="wrapperCleanUrls(cleanUrls, v.route)"
180
+ }"
181
+ :href="wrapperCleanUrls(cleanUrls, v.route)"
182
+ @click="(e) => {
183
+ e.preventDefault()
184
+ handleLinkClick(wrapperCleanUrls(cleanUrls, v.route))
185
+ }"
176
186
  >
177
- {{ v.meta.title }}
178
- </ElLink>
187
+ <span>{{ v.meta.title }}</span>
188
+ </a>
179
189
  <!-- 描述信息 -->
180
190
  <div class="suffix">
181
191
  <!-- 日期 -->
@@ -248,12 +258,25 @@ const cleanUrls = useCleanUrls()
248
258
  color: var(--vp-c-text-1);
249
259
  word-break: break-all;
250
260
  white-space: break-spaces;
261
+ font-weight: 500;
262
+ position: relative;
263
+ cursor: pointer;
251
264
 
252
265
  &.current {
253
266
  color: var(--vp-c-brand-1);
254
267
  }
255
268
  }
256
269
 
270
+ .title:hover::after {
271
+ content: "";
272
+ position: absolute;
273
+ left: 0;
274
+ right: 0;
275
+ height: 0;
276
+ bottom: -3px;
277
+ border-bottom: 1px solid #b1b3b8;
278
+ }
279
+
257
280
  .suffix {
258
281
  font-size: 12px;
259
282
  color: var(--vp-c-text-2);
@@ -104,6 +104,10 @@ export function useOml2dOptions() {
104
104
  return inject(configSymbol)!.value.blog?.oml2d
105
105
  }
106
106
 
107
+ export function useDarkTransitionConfig() {
108
+ return inject(configSymbol)!.value.blog?.darkTransition ?? true
109
+ }
110
+
107
111
  export function useBlogThemeMode() {
108
112
  return inject(configSymbol)!.value?.blog?.blog ?? true
109
113
  }
@@ -440,6 +440,12 @@ export namespace Theme {
440
440
  oml2d?: Oml2dOptions
441
441
  homeTags?: boolean
442
442
  buttonAfterArticle?: ButtonAfterArticleConfig | false
443
+ /**
444
+ * 是否开启深色模式过渡动画
445
+ * @reference https://vitepress.dev/zh/guide/extending-default-theme#on-appearance-toggle
446
+ * @default true
447
+ */
448
+ darkTransition?: boolean
443
449
  }
444
450
 
445
451
  export interface BackToTop {
@@ -0,0 +1,46 @@
1
+ import { useData } from 'vitepress'
2
+ import { nextTick, provide } from 'vue'
3
+ import { useDarkTransitionConfig } from '../composables/config/blog'
4
+
5
+ export function useDarkTransition() {
6
+ const { isDark } = useData()
7
+
8
+ const isOpenDarkTransition = useDarkTransitionConfig()
9
+
10
+ if (!isOpenDarkTransition) {
11
+ return
12
+ }
13
+ const enableTransitions = () =>
14
+ 'startViewTransition' in document
15
+ && window.matchMedia('(prefers-reduced-motion: no-preference)').matches
16
+
17
+ provide('toggle-appearance', async ({ clientX: x, clientY: y }: MouseEvent) => {
18
+ if (!enableTransitions()) {
19
+ isDark.value = !isDark.value
20
+ return
21
+ }
22
+
23
+ const clipPath = [
24
+ `circle(0px at ${x}px ${y}px)`,
25
+ `circle(${Math.hypot(
26
+ Math.max(x, innerWidth - x),
27
+ Math.max(y, innerHeight - y)
28
+ )}px at ${x}px ${y}px)`
29
+ ]
30
+
31
+ // @ts-expect-error
32
+ await document.startViewTransition(async () => {
33
+ isDark.value = !isDark.value
34
+ await nextTick()
35
+ }).ready
36
+
37
+ document.documentElement.animate(
38
+ { clipPath: isDark.value ? clipPath.reverse() : clipPath },
39
+ {
40
+ duration: 300,
41
+ easing: 'ease-in',
42
+ pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`
43
+ }
44
+ )
45
+ })
46
+ }
@@ -59,15 +59,21 @@ export function useOml2d() {
59
59
  ...defaultOptions.tips,
60
60
  ...oml2dOptions.tips,
61
61
  style: {
62
+ // @ts-expect-error
62
63
  ...defaultOptions?.tips?.style,
64
+ // @ts-expect-error
63
65
  ...oml2dOptions?.tips?.style
64
66
  },
65
67
  mobileStyle: {
68
+ // @ts-expect-error
66
69
  ...defaultOptions?.tips?.mobileStyle,
70
+ // @ts-expect-error
67
71
  ...oml2dOptions?.tips?.mobileStyle
68
72
  },
69
73
  copyTips: {
74
+ // @ts-expect-error
70
75
  ...defaultOptions?.tips?.copyTips,
76
+ // @ts-expect-error
71
77
  ...oml2dOptions?.tips?.copyTips
72
78
  }
73
79
  }
@@ -0,0 +1,23 @@
1
+ ::view-transition-old(root),
2
+ ::view-transition-new(root) {
3
+ animation: none;
4
+ mix-blend-mode: normal;
5
+ }
6
+
7
+ ::view-transition-old(root),
8
+ .dark::view-transition-new(root) {
9
+ z-index: 1;
10
+ }
11
+
12
+ ::view-transition-new(root),
13
+ .dark::view-transition-old(root) {
14
+ z-index: 9999;
15
+ }
16
+
17
+ .blog-theme-layout .VPSwitchAppearance {
18
+ width: 22px !important;
19
+ }
20
+
21
+ .blog-theme-layout .VPSwitchAppearance .check {
22
+ transform: none !important;
23
+ }
@@ -0,0 +1,59 @@
1
+ import type { PluginOption } from 'vite'
2
+ import type { SiteConfig } from 'vitepress'
3
+ import type { Theme } from '../../composables/config/index'
4
+ import { getArticleMeta } from './theme'
5
+ import { debounce, isEqual } from './index'
6
+
7
+ export function themeReloadPlugin() {
8
+ let blogConfig: Theme.BlogConfig
9
+ let vitepressConfig: SiteConfig
10
+ let docsDir: string
11
+
12
+ const generateRoute = (filepath: string) => {
13
+ return filepath.replace(docsDir, '').replace('.md', '')
14
+ }
15
+
16
+ return {
17
+ name: '@sugarat/theme-reload',
18
+ apply: 'serve',
19
+ configureServer(server) {
20
+ const restart = debounce(() => {
21
+ server.restart()
22
+ }, 500)
23
+ server.watcher.on('add', (path) => {
24
+ const route = generateRoute(path)
25
+ const meta = getArticleMeta(path, route, blogConfig?.timeZone)
26
+ blogConfig.pagesData.push({
27
+ route,
28
+ meta
29
+ })
30
+ restart()
31
+ })
32
+
33
+ server.watcher.on('change', (path: string) => {
34
+ const route = generateRoute(path)
35
+ const meta = getArticleMeta(path, route, blogConfig?.timeZone)
36
+ const matched = blogConfig.pagesData.find(v => v.route === route)
37
+
38
+ if (matched && !isEqual(matched.meta, meta, ['date', 'description'])) {
39
+ matched.meta = meta
40
+ restart()
41
+ }
42
+ })
43
+
44
+ server.watcher.on('unlink', (path) => {
45
+ const route = generateRoute(path)
46
+ const idx = blogConfig.pagesData.findIndex(v => v.route === route)
47
+ if (idx >= 0) {
48
+ blogConfig.pagesData.splice(idx, 1)
49
+ restart()
50
+ }
51
+ })
52
+ },
53
+ configResolved(config: any) {
54
+ vitepressConfig = config.vitepress
55
+ docsDir = vitepressConfig.srcDir
56
+ blogConfig = config.vitepress.site.themeConfig.blog
57
+ },
58
+ } as PluginOption
59
+ }
@@ -2,7 +2,6 @@
2
2
  /* eslint-disable prefer-rest-params */
3
3
  import { spawn, spawnSync } from 'node:child_process'
4
4
  import path from 'node:path'
5
- import { formatDate } from '../client'
6
5
 
7
6
  export function clearMatterContent(content: string) {
8
7
  let first___: unknown
@@ -31,16 +30,10 @@ export function clearMatterContent(content: string) {
31
30
  .join('\n')
32
31
  )
33
32
  }
33
+
34
34
  export function getDefaultTitle(content: string) {
35
- const title
36
- = clearMatterContent(content)
37
- .split('\n')
38
- ?.find((str) => {
39
- return str.startsWith('# ')
40
- })
41
- ?.slice(2)
42
- .replace(/^\s+|\s+$/g, '') || ''
43
- return title
35
+ const match = content.match(/^(#+)\s+(.+)/m)
36
+ return match?.[2] || ''
44
37
  }
45
38
 
46
39
  export function getFileBirthTime(url: string) {
@@ -57,10 +50,10 @@ export function getFileBirthTime(url: string) {
57
50
  }
58
51
  }
59
52
  catch (error) {
60
- return formatDate(date)
53
+ return date
61
54
  }
62
55
 
63
- return formatDate(date)
56
+ return date
64
57
  }
65
58
 
66
59
  export function getGitTimestamp(file: string) {
@@ -79,8 +72,9 @@ export function getGitTimestamp(file: string) {
79
72
 
80
73
  export function getTextSummary(text: string, count = 100) {
81
74
  return (
82
- clearMatterContent(text)
83
- .match(/^# ([\s\S]+)/m)?.[1]
75
+ text
76
+ // 首个标题
77
+ ?.replace(/^#+\s+.*/, '')
84
78
  // 除去标题
85
79
  ?.replace(/#/g, '')
86
80
  // 除去图片
@@ -91,9 +85,10 @@ export function getTextSummary(text: string, count = 100) {
91
85
  ?.replace(/\*\*(.*?)\*\*/g, '$1')
92
86
  ?.split('\n')
93
87
  ?.filter(v => !!v)
94
- ?.slice(1)
95
88
  ?.join('\n')
96
89
  ?.replace(/>(.*)/, '')
90
+ ?.replace(/</g, '&lt;').replace(/>/g, '&gt;')
91
+ ?.trim()
97
92
  ?.slice(0, count)
98
93
  )
99
94
  }
@@ -151,3 +146,43 @@ export function getFirstImagURLFromMD(content: string, route: string) {
151
146
 
152
147
  return joinPath('/', relativePath)
153
148
  }
149
+
150
+ export function debounce(func: any, delay = 1000) {
151
+ let timeoutId: any
152
+ return (...rest: any[]) => {
153
+ clearTimeout(timeoutId)
154
+ timeoutId = setTimeout(() => {
155
+ func(...rest)
156
+ }, delay)
157
+ }
158
+ }
159
+
160
+ export function isEqual(obj1: any, obj2: any, excludeKeys: string[] = []) {
161
+ const keys1 = Object.keys(obj1).filter(key => !excludeKeys.includes(key))
162
+ const keys2 = Object.keys(obj2).filter(key => !excludeKeys.includes(key))
163
+
164
+ if (keys1.length !== keys2.length) {
165
+ return false
166
+ }
167
+
168
+ for (const key of keys1) {
169
+ if (!keys2.includes(key)) {
170
+ return false
171
+ }
172
+ const val1 = obj1[key]
173
+ const val2 = obj2[key]
174
+ const areObjects = isObject(val1) && isObject(val2)
175
+ if (
176
+ (areObjects && !isEqual(val1, val2, excludeKeys))
177
+ || (!areObjects && val1 !== val2)
178
+ ) {
179
+ return false
180
+ }
181
+ }
182
+
183
+ return true
184
+ }
185
+
186
+ export function isObject(obj: any) {
187
+ return obj != null && typeof obj === 'object'
188
+ }
@@ -23,7 +23,7 @@ export function getMarkdownPlugins(cfg?: Partial<Theme.BlogConfig>) {
23
23
  markdownPlugin.push(MermaidMarkdown)
24
24
  }
25
25
 
26
- if (cfg.taskCheckbox !== false) {
26
+ if (cfg?.taskCheckbox !== false) {
27
27
  markdownPlugin.push(taskCheckboxPlugin(typeof cfg?.taskCheckbox === 'boolean' ? {} : cfg?.taskCheckbox))
28
28
  }
29
29
 
@@ -21,107 +21,102 @@ export function patchDefaultThemeSideBar(cfg?: Partial<Theme.BlogConfig>) {
21
21
  : undefined
22
22
  }
23
23
 
24
- // hack:RSS用
25
- export const pageMap = new Map<string, string>()
26
-
27
- export function getArticles(cfg?: Partial<Theme.BlogConfig>) {
28
- const srcDir = cfg?.srcDir || process.argv.slice(2)?.[1] || '.'
29
- const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'] })
30
-
31
- // 文章数据
32
- const data = files
33
- .map((v) => {
34
- let route = v
35
- // 处理文件后缀名
36
- .replace('.md', '')
24
+ export function getPageRoute(filepath: string, srcDir: string) {
25
+ let route = filepath.replace('.md', '')
26
+ // 去除 srcDir 处理目录名
27
+ // TODO:优化 路径处理,同VitePress 内部一致
28
+ if (route.startsWith('./')) {
29
+ route = route.replace(
30
+ new RegExp(
31
+ `^\\.\\/${path
32
+ .join(srcDir, '/')
33
+ .replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
34
+ ),
35
+ ''
36
+ )
37
+ }
38
+ else {
39
+ route = route.replace(
40
+ new RegExp(
41
+ `^${path
42
+ .join(srcDir, '/')
43
+ .replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
44
+ ),
45
+ ''
46
+ )
47
+ }
48
+ return `/${route}`
49
+ }
37
50
 
38
- // 去除 srcDir 处理目录名
39
- if (route.startsWith('./')) {
40
- route = route.replace(
41
- new RegExp(
42
- `^\\.\\/${path
43
- .join(srcDir, '/')
44
- .replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
45
- ),
46
- ''
47
- )
48
- }
49
- else {
50
- route = route.replace(
51
- new RegExp(
52
- `^${path
53
- .join(srcDir, '/')
54
- .replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
55
- ),
56
- ''
57
- )
58
- }
59
- // hack:RSS使用
60
- pageMap.set(`/${route}`, v)
51
+ const defaultTimeZoneOffset = new Date().getTimezoneOffset() / -60
52
+ export function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset) {
53
+ const fileContent = fs.readFileSync(filepath, 'utf-8')
61
54
 
62
- const fileContent = fs.readFileSync(v, 'utf-8')
63
- // TODO:摘要生成优化
64
- // TODO: 用上内容content
65
- const { data: frontmatter, excerpt } = matter(fileContent, {
66
- excerpt: true
67
- })
55
+ const { data: frontmatter, excerpt, content } = matter(fileContent, {
56
+ excerpt: true,
57
+ })
68
58
 
69
- const meta: Partial<Theme.PageMeta> = {
70
- ...frontmatter
71
- }
59
+ const meta: Partial<Theme.PageMeta> = {
60
+ ...frontmatter
61
+ }
72
62
 
73
- if (!meta.title) {
74
- // TODO:优化标题的采集
75
- meta.title = getDefaultTitle(fileContent)
76
- }
77
- if (!meta.date) {
78
- // getGitTimestamp(v).then((v) => {
79
- // meta.date = formatDate(v)
80
- // })
81
- meta.date = getFileBirthTime(v)
82
- }
83
- else {
84
- const timeZone = cfg?.timeZone ?? 8
85
- meta.date = formatDate(
86
- new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`)
87
- )
88
- }
63
+ if (!meta.title) {
64
+ meta.title = getDefaultTitle(content)
65
+ }
66
+ if (!meta.date) {
67
+ meta.date = formatDate(getFileBirthTime(filepath))
68
+ }
69
+ else {
70
+ meta.date = formatDate(
71
+ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`)
72
+ )
73
+ }
89
74
 
90
- // 处理tags和categories,兼容历史文章
91
- meta.categories
75
+ // 处理tags和categories,兼容历史文章
76
+ meta.categories
92
77
  = typeof meta.categories === 'string'
93
- ? [meta.categories]
94
- : meta.categories
95
- meta.tags = typeof meta.tags === 'string' ? [meta.tags] : meta.tags
96
- meta.tag = [meta.tag || []]
97
- .flat()
98
- .concat([
99
- ...new Set([...(meta.categories || []), ...(meta.tags || [])])
100
- ])
101
-
102
- // 获取摘要信息
103
- const wordCount = 100
104
- meta.description
105
- = meta.description || getTextSummary(fileContent, wordCount)
106
-
107
- // 获取封面图
108
- meta.cover
78
+ ? [meta.categories]
79
+ : meta.categories
80
+ meta.tags = typeof meta.tags === 'string' ? [meta.tags] : meta.tags
81
+ meta.tag = [meta.tag || []]
82
+ .flat()
83
+ .concat([
84
+ ...new Set([...(meta.categories || []), ...(meta.tags || [])])
85
+ ])
86
+
87
+ // 获取摘要信息
88
+ // TODO:摘要生成优化
89
+ meta.description
90
+ = meta.description || getTextSummary(content, 100) || excerpt
91
+
92
+ // 获取封面图
93
+ meta.cover
109
94
  = meta.cover
110
- ?? (getFirstImagURLFromMD(fileContent, `/${route}`))
95
+ ?? (getFirstImagURLFromMD(fileContent, route))
111
96
 
112
- // 是否发布 默认发布
113
- if (meta.publish === false) {
114
- meta.hidden = true
115
- meta.recommend = false
116
- }
97
+ // 是否发布 默认发布
98
+ if (meta.publish === false) {
99
+ meta.hidden = true
100
+ meta.recommend = false
101
+ }
102
+ return meta as Theme.PageMeta
103
+ }
104
+ export function getArticles(cfg?: Partial<Theme.BlogConfig>) {
105
+ const srcDir = cfg?.srcDir || process.argv.slice(2)?.[1] || '.'
106
+ const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'] })
117
107
 
108
+ // 文章数据
109
+ const pageData = files
110
+ .map((filepath) => {
111
+ const route = getPageRoute(filepath, srcDir)
112
+ const meta = getArticleMeta(filepath, route, cfg?.timeZone)
118
113
  return {
119
- route: `/${route}`,
114
+ route,
120
115
  meta
121
116
  }
122
117
  })
123
118
  .filter(v => v.meta.layout !== 'home')
124
- return data as Theme.PageData[]
119
+ return pageData as Theme.PageData[]
125
120
  }
126
121
 
127
122
  export function patchVPConfig(vpConfig: any, cfg?: Partial<Theme.BlogConfig>) {
@@ -12,6 +12,7 @@ import {
12
12
  import { RssPlugin } from 'vitepress-plugin-rss'
13
13
  import type { Theme } from '../../composables/config/index'
14
14
  import { _require } from './mdPlugins'
15
+ import { themeReloadPlugin } from './hot-reload-plugin'
15
16
  import { joinPath } from './index'
16
17
 
17
18
  export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
@@ -19,10 +20,16 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
19
20
 
20
21
  // Build完后运行的一系列列方法
21
22
  const buildEndFn: any[] = []
23
+
22
24
  // 执行自定义的 buildEnd 钩子
23
25
  plugins.push(inlineBuildEndPlugin(buildEndFn))
26
+
24
27
  // 处理cover image的路径(暂只支持自动识别的文章首图)
25
28
  plugins.push(coverImgTransform())
29
+
30
+ // 自动重载首页
31
+ plugins.push(themeReloadPlugin())
32
+
26
33
  // 内置简化版的pagefind
27
34
  if (cfg && cfg.search !== false) {
28
35
  const ops = cfg.search instanceof Object ? cfg.search : {}
@@ -47,13 +54,6 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
47
54
  if (cfg?.RSS) {
48
55
  plugins.push(RssPlugin(cfg.RSS))
49
56
  }
50
- // 未来移除使用
51
- // if (cfg && cfg.search !== undefined) {
52
- // console.log(
53
- // '已从内部移除 pagefind 支持,请单独安装 vitepress-plugin-pagefind 插件使用'
54
- // )
55
- // }
56
-
57
57
  return plugins
58
58
  }
59
59