@sugarat/theme 0.3.0 → 0.3.1
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 +6 -0
- package/node.js +185 -117
- package/package.json +3 -2
- package/src/components/BlogApp.vue +15 -4
- package/src/components/BlogHomeBanner.vue +1 -1
- package/src/components/BlogHomeHeaderAvatar.vue +7 -5
- package/src/components/BlogHomeInfo.vue +1 -1
- package/src/components/BlogHotArticle.vue +37 -8
- package/src/components/BlogItem.vue +25 -13
- package/src/components/BlogPopover.vue +4 -0
- package/src/components/BlogRecommendArticle.vue +30 -7
- package/src/composables/config/blog.ts +4 -0
- package/src/composables/config/index.ts +6 -0
- package/src/hooks/useDarkTransition.ts +46 -0
- package/src/hooks/useOml2d.ts +6 -0
- package/src/styles/dark-transition.css +23 -0
- package/src/utils/node/hot-reload-plugin.ts +59 -0
- package/src/utils/node/index.ts +49 -15
- package/src/utils/node/theme.ts +82 -87
- package/src/utils/node/vitePlugins.ts +7 -7
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.
|
|
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
|
|
273
|
-
|
|
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
|
|
225
|
+
return date;
|
|
286
226
|
}
|
|
287
|
-
return
|
|
227
|
+
return date;
|
|
288
228
|
}
|
|
289
229
|
function getTextSummary(text, count = 100) {
|
|
290
|
-
return
|
|
230
|
+
return text?.replace(/^#+\s+.*/, "")?.replace(/#/g, "")?.replace(/!\[.*?\]\(.*?\)/g, "")?.replace(/\[(.*?)\]\(.*?\)/g, "$1")?.replace(/\*\*(.*?)\*\*/g, "$1")?.split("\n")?.filter((v) => !!v)?.join("\n")?.replace(/>(.*)/, "")?.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 = {};
|
|
@@ -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
|
-
|
|
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
|
|
402
|
-
|
|
403
|
-
|
|
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
|
|
465
|
+
route,
|
|
451
466
|
meta
|
|
452
467
|
};
|
|
453
468
|
}).filter((v) => v.meta.layout !== "home");
|
|
454
|
-
return
|
|
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.
|
|
3
|
+
"version": "0.3.1",
|
|
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
|
-
"
|
|
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
|
-
<
|
|
38
|
-
|
|
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>
|
|
@@ -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="
|
|
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>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { computed, ref } from 'vue'
|
|
3
|
-
import { ElButton
|
|
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
|
|
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
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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">
|
|
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">
|
|
47
|
+
<p class="title pc-visible">
|
|
48
|
+
{{ title }}
|
|
49
|
+
</p>
|
|
38
50
|
<!-- 简短描述 -->
|
|
39
|
-
<p v-
|
|
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-
|
|
59
|
+
<span v-show="author" class="split">{{ author }}</span>
|
|
48
60
|
<span class="split">{{ showTime }}</span>
|
|
49
|
-
<span v-
|
|
61
|
+
<span v-show="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
|
|
50
62
|
</div>
|
|
51
63
|
</div>
|
|
52
64
|
<!-- 右侧封面图 -->
|
|
53
|
-
<div v-
|
|
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-
|
|
69
|
+
<span v-show="author" class="split">{{ author }}</span>
|
|
58
70
|
<span class="split">{{ showTime }}</span>
|
|
59
|
-
<span v-
|
|
71
|
+
<span v-show="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
|
|
60
72
|
</div>
|
|
61
73
|
</a>
|
|
62
74
|
</template>
|
|
@@ -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
|
|
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
|
-
<
|
|
173
|
-
|
|
177
|
+
<a
|
|
178
|
+
class="title" :class="{
|
|
174
179
|
current: isCurrentDoc(v.route),
|
|
175
|
-
}"
|
|
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
|
-
</
|
|
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
|
+
}
|
package/src/hooks/useOml2d.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/node/index.ts
CHANGED
|
@@ -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
|
|
36
|
-
|
|
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
|
|
53
|
+
return date
|
|
61
54
|
}
|
|
62
55
|
|
|
63
|
-
return
|
|
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
|
-
|
|
83
|
-
|
|
75
|
+
text
|
|
76
|
+
// 首个标题
|
|
77
|
+
?.replace(/^#+\s+.*/, '')
|
|
84
78
|
// 除去标题
|
|
85
79
|
?.replace(/#/g, '')
|
|
86
80
|
// 除去图片
|
|
@@ -91,9 +85,9 @@ 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
|
+
?.trim()
|
|
97
91
|
?.slice(0, count)
|
|
98
92
|
)
|
|
99
93
|
}
|
|
@@ -151,3 +145,43 @@ export function getFirstImagURLFromMD(content: string, route: string) {
|
|
|
151
145
|
|
|
152
146
|
return joinPath('/', relativePath)
|
|
153
147
|
}
|
|
148
|
+
|
|
149
|
+
export function debounce(func: any, delay = 1000) {
|
|
150
|
+
let timeoutId: any
|
|
151
|
+
return (...rest: any[]) => {
|
|
152
|
+
clearTimeout(timeoutId)
|
|
153
|
+
timeoutId = setTimeout(() => {
|
|
154
|
+
func(...rest)
|
|
155
|
+
}, delay)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isEqual(obj1: any, obj2: any, excludeKeys: string[] = []) {
|
|
160
|
+
const keys1 = Object.keys(obj1).filter(key => !excludeKeys.includes(key))
|
|
161
|
+
const keys2 = Object.keys(obj2).filter(key => !excludeKeys.includes(key))
|
|
162
|
+
|
|
163
|
+
if (keys1.length !== keys2.length) {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const key of keys1) {
|
|
168
|
+
if (!keys2.includes(key)) {
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
const val1 = obj1[key]
|
|
172
|
+
const val2 = obj2[key]
|
|
173
|
+
const areObjects = isObject(val1) && isObject(val2)
|
|
174
|
+
if (
|
|
175
|
+
(areObjects && !isEqual(val1, val2, excludeKeys))
|
|
176
|
+
|| (!areObjects && val1 !== val2)
|
|
177
|
+
) {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function isObject(obj: any) {
|
|
186
|
+
return obj != null && typeof obj === 'object'
|
|
187
|
+
}
|
package/src/utils/node/theme.ts
CHANGED
|
@@ -21,107 +21,102 @@ export function patchDefaultThemeSideBar(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
21
21
|
: undefined
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
59
|
+
const meta: Partial<Theme.PageMeta> = {
|
|
60
|
+
...frontmatter
|
|
61
|
+
}
|
|
72
62
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
91
|
-
|
|
75
|
+
// 处理tags和categories,兼容历史文章
|
|
76
|
+
meta.categories
|
|
92
77
|
= typeof meta.categories === 'string'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
= meta.description || getTextSummary(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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,
|
|
95
|
+
?? (getFirstImagURLFromMD(fileContent, route))
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
114
|
+
route,
|
|
120
115
|
meta
|
|
121
116
|
}
|
|
122
117
|
})
|
|
123
118
|
.filter(v => v.meta.layout !== 'home')
|
|
124
|
-
return
|
|
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
|
|