@sugarat/theme 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/node.d.ts +19 -2
- package/node.js +179 -54
- package/package.json +6 -4
- package/src/components/BlogFriendLink.vue +3 -18
- package/src/components/BlogHomeTags.vue +11 -22
- package/src/components/BlogHotArticle.vue +2 -2
- package/src/components/BlogItem.vue +1 -1
- package/src/composables/config/index.ts +20 -2
- package/src/constants/svg.ts +41 -0
- package/src/index.ts +6 -8
- package/src/node.ts +11 -5
- package/src/utils/node/hot-reload-plugin.ts +4 -4
- package/src/utils/node/index.ts +19 -16
- package/src/utils/node/theme.ts +41 -21
- package/src/utils/node/vitePlugins.ts +28 -42
package/README.md
CHANGED
package/node.d.ts
CHANGED
|
@@ -137,6 +137,10 @@ declare namespace Theme {
|
|
|
137
137
|
server: string;
|
|
138
138
|
}
|
|
139
139
|
interface HotArticle {
|
|
140
|
+
/**
|
|
141
|
+
* 自定义标题,支持SVG + 文字
|
|
142
|
+
* @default '🔥 精选文章'
|
|
143
|
+
*/
|
|
140
144
|
title?: string;
|
|
141
145
|
pageSize?: number;
|
|
142
146
|
nextText?: string;
|
|
@@ -275,6 +279,11 @@ declare namespace Theme {
|
|
|
275
279
|
* @default "动态计算"
|
|
276
280
|
*/
|
|
277
281
|
scrollSpeed?: number;
|
|
282
|
+
/**
|
|
283
|
+
* 自定义展示标题,支持SVG + 文字
|
|
284
|
+
* @default '🤝 友情链接'
|
|
285
|
+
*/
|
|
286
|
+
title?: string;
|
|
278
287
|
}
|
|
279
288
|
interface UserWork {
|
|
280
289
|
title: string;
|
|
@@ -375,7 +384,8 @@ declare namespace Theme {
|
|
|
375
384
|
mermaid?: any;
|
|
376
385
|
/**
|
|
377
386
|
* 设置解析 frontmatter 里 date 的时区
|
|
378
|
-
* @default
|
|
387
|
+
* @default new Date().getTimezoneOffset() / -60
|
|
388
|
+
* @example 8 => 'UTC+8'
|
|
379
389
|
*/
|
|
380
390
|
timeZone?: number;
|
|
381
391
|
/**
|
|
@@ -418,7 +428,7 @@ declare namespace Theme {
|
|
|
418
428
|
* 详见 https://oml2d.com/options/Options.html
|
|
419
429
|
*/
|
|
420
430
|
oml2d?: Options;
|
|
421
|
-
homeTags?: boolean;
|
|
431
|
+
homeTags?: boolean | HomeTagsConfig;
|
|
422
432
|
buttonAfterArticle?: ButtonAfterArticleConfig | false;
|
|
423
433
|
/**
|
|
424
434
|
* 是否开启深色模式过渡动画
|
|
@@ -545,6 +555,13 @@ declare namespace Theme {
|
|
|
545
555
|
*/
|
|
546
556
|
coverPreview?: ReplaceRule | ReplaceRule[];
|
|
547
557
|
}
|
|
558
|
+
interface HomeTagsConfig {
|
|
559
|
+
/**
|
|
560
|
+
* 自定义标题,支持SVG + 文字
|
|
561
|
+
* @default '🏷 标签'
|
|
562
|
+
*/
|
|
563
|
+
title?: string;
|
|
564
|
+
}
|
|
548
565
|
}
|
|
549
566
|
|
|
550
567
|
/**
|
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.2.
|
|
43
|
+
// ../../node_modules/.pnpm/vitepress-plugin-tabs@0.2.0_vitepress@1.2.3_@algolia+client-search@4.19.1_@types+node@20.6.3__tasys46ln23ubdv2to2chczqqi/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) => {
|
|
@@ -214,17 +214,25 @@ function getDefaultTitle(content) {
|
|
|
214
214
|
const match = content.match(/^(#+)\s+(.+)/m);
|
|
215
215
|
return match?.[2] || "";
|
|
216
216
|
}
|
|
217
|
+
var cache = /* @__PURE__ */ new Map();
|
|
217
218
|
function getFileBirthTime(url) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
219
|
+
const cached = cache.get(url);
|
|
220
|
+
if (cached) {
|
|
221
|
+
return cached;
|
|
222
|
+
}
|
|
223
|
+
return new Promise((resolve) => {
|
|
224
|
+
const child = (0, import_node_child_process.spawn)("git", ["log", "-1", '--pretty="%ai"', url]);
|
|
225
|
+
let output = "";
|
|
226
|
+
child.stdout.on("data", (d) => output += String(d));
|
|
227
|
+
child.on("close", () => {
|
|
228
|
+
const date = new Date(output);
|
|
229
|
+
cache.set(url, date);
|
|
230
|
+
resolve(date);
|
|
231
|
+
});
|
|
232
|
+
child.on("error", () => {
|
|
233
|
+
resolve(void 0);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
228
236
|
}
|
|
229
237
|
function getTextSummary(text, count = 100) {
|
|
230
238
|
return text?.replace(/^#+\s+.*/, "")?.replace(/#/g, "")?.replace(/!\[.*?\]\(.*?\)/g, "")?.replace(/\[(.*?)\]\(.*?\)/g, "$1")?.replace(/\*\*(.*?)\*\*/g, "$1")?.split("\n")?.filter((v) => !!v)?.join("\n")?.replace(/>(.*)/, "")?.replace(/</g, "<").replace(/>/g, ">")?.trim()?.slice(0, count);
|
|
@@ -353,9 +361,113 @@ function patchOptimizeDeps(config) {
|
|
|
353
361
|
var import_node_fs = __toESM(require("fs"));
|
|
354
362
|
var import_node_path2 = __toESM(require("path"));
|
|
355
363
|
var import_node_process = __toESM(require("process"));
|
|
364
|
+
var import_node_os = __toESM(require("os"));
|
|
356
365
|
var import_fast_glob = __toESM(require("fast-glob"));
|
|
357
366
|
var import_gray_matter = __toESM(require("gray-matter"));
|
|
358
367
|
|
|
368
|
+
// ../../node_modules/.pnpm/yocto-queue@1.0.0/node_modules/yocto-queue/index.js
|
|
369
|
+
var Node = class {
|
|
370
|
+
value;
|
|
371
|
+
next;
|
|
372
|
+
constructor(value) {
|
|
373
|
+
this.value = value;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
var Queue = class {
|
|
377
|
+
#head;
|
|
378
|
+
#tail;
|
|
379
|
+
#size;
|
|
380
|
+
constructor() {
|
|
381
|
+
this.clear();
|
|
382
|
+
}
|
|
383
|
+
enqueue(value) {
|
|
384
|
+
const node = new Node(value);
|
|
385
|
+
if (this.#head) {
|
|
386
|
+
this.#tail.next = node;
|
|
387
|
+
this.#tail = node;
|
|
388
|
+
} else {
|
|
389
|
+
this.#head = node;
|
|
390
|
+
this.#tail = node;
|
|
391
|
+
}
|
|
392
|
+
this.#size++;
|
|
393
|
+
}
|
|
394
|
+
dequeue() {
|
|
395
|
+
const current = this.#head;
|
|
396
|
+
if (!current) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
this.#head = this.#head.next;
|
|
400
|
+
this.#size--;
|
|
401
|
+
return current.value;
|
|
402
|
+
}
|
|
403
|
+
clear() {
|
|
404
|
+
this.#head = void 0;
|
|
405
|
+
this.#tail = void 0;
|
|
406
|
+
this.#size = 0;
|
|
407
|
+
}
|
|
408
|
+
get size() {
|
|
409
|
+
return this.#size;
|
|
410
|
+
}
|
|
411
|
+
*[Symbol.iterator]() {
|
|
412
|
+
let current = this.#head;
|
|
413
|
+
while (current) {
|
|
414
|
+
yield current.value;
|
|
415
|
+
current = current.next;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// ../../node_modules/.pnpm/p-limit@4.0.0/node_modules/p-limit/index.js
|
|
421
|
+
function pLimit(concurrency) {
|
|
422
|
+
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
423
|
+
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
424
|
+
}
|
|
425
|
+
const queue = new Queue();
|
|
426
|
+
let activeCount = 0;
|
|
427
|
+
const next = () => {
|
|
428
|
+
activeCount--;
|
|
429
|
+
if (queue.size > 0) {
|
|
430
|
+
queue.dequeue()();
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const run = async (fn, resolve, args) => {
|
|
434
|
+
activeCount++;
|
|
435
|
+
const result = (async () => fn(...args))();
|
|
436
|
+
resolve(result);
|
|
437
|
+
try {
|
|
438
|
+
await result;
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
next();
|
|
442
|
+
};
|
|
443
|
+
const enqueue = (fn, resolve, args) => {
|
|
444
|
+
queue.enqueue(run.bind(void 0, fn, resolve, args));
|
|
445
|
+
(async () => {
|
|
446
|
+
await Promise.resolve();
|
|
447
|
+
if (activeCount < concurrency && queue.size > 0) {
|
|
448
|
+
queue.dequeue()();
|
|
449
|
+
}
|
|
450
|
+
})();
|
|
451
|
+
};
|
|
452
|
+
const generator = (fn, ...args) => new Promise((resolve) => {
|
|
453
|
+
enqueue(fn, resolve, args);
|
|
454
|
+
});
|
|
455
|
+
Object.defineProperties(generator, {
|
|
456
|
+
activeCount: {
|
|
457
|
+
get: () => activeCount
|
|
458
|
+
},
|
|
459
|
+
pendingCount: {
|
|
460
|
+
get: () => queue.size
|
|
461
|
+
},
|
|
462
|
+
clearQueue: {
|
|
463
|
+
value: () => {
|
|
464
|
+
queue.clear();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
return generator;
|
|
469
|
+
}
|
|
470
|
+
|
|
359
471
|
// src/utils/client/index.ts
|
|
360
472
|
function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
|
|
361
473
|
if (!(d instanceof Date)) {
|
|
@@ -424,8 +536,8 @@ function getPageRoute(filepath, srcDir) {
|
|
|
424
536
|
return `/${route}`;
|
|
425
537
|
}
|
|
426
538
|
var defaultTimeZoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() / -60;
|
|
427
|
-
function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
428
|
-
const fileContent = import_node_fs.default.
|
|
539
|
+
async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
540
|
+
const fileContent = await import_node_fs.default.promises.readFile(filepath, "utf-8");
|
|
429
541
|
const { data: frontmatter, excerpt, content } = (0, import_gray_matter.default)(fileContent, {
|
|
430
542
|
excerpt: true
|
|
431
543
|
});
|
|
@@ -435,12 +547,9 @@ function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
|
435
547
|
if (!meta.title) {
|
|
436
548
|
meta.title = getDefaultTitle(content);
|
|
437
549
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
meta.date = formatDate(
|
|
442
|
-
/* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`)
|
|
443
|
-
);
|
|
550
|
+
const date = await (meta.date && /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`) || getFileBirthTime(filepath));
|
|
551
|
+
if (date) {
|
|
552
|
+
meta.date = formatDate(date);
|
|
444
553
|
}
|
|
445
554
|
meta.categories = typeof meta.categories === "string" ? [meta.categories] : meta.categories;
|
|
446
555
|
meta.tags = typeof meta.tags === "string" ? [meta.tags] : meta.tags;
|
|
@@ -455,17 +564,31 @@ function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
|
455
564
|
}
|
|
456
565
|
return meta;
|
|
457
566
|
}
|
|
458
|
-
function getArticles(cfg) {
|
|
567
|
+
async function getArticles(cfg) {
|
|
459
568
|
const srcDir = cfg?.srcDir || import_node_process.default.argv.slice(2)?.[1] || ".";
|
|
460
569
|
const files = import_fast_glob.default.sync(`${srcDir}/**/*.md`, { ignore: ["node_modules"] });
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
const
|
|
464
|
-
|
|
570
|
+
const limit = pLimit(+(import_node_process.default.env.P_LIMT_MAX || import_node_os.default.cpus().length));
|
|
571
|
+
const metaResults = files.reduce((prev, curr) => {
|
|
572
|
+
const route = getPageRoute(curr, srcDir);
|
|
573
|
+
const metaPromise = limit(() => getArticleMeta(curr, route, cfg?.timeZone));
|
|
574
|
+
prev[curr] = {
|
|
465
575
|
route,
|
|
466
|
-
|
|
576
|
+
metaPromise
|
|
467
577
|
};
|
|
468
|
-
|
|
578
|
+
return prev;
|
|
579
|
+
}, {});
|
|
580
|
+
const pageData = [];
|
|
581
|
+
for (const file of files) {
|
|
582
|
+
const { route, metaPromise } = metaResults[file];
|
|
583
|
+
const meta = await metaPromise;
|
|
584
|
+
if (meta.layout === "home") {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
pageData.push({
|
|
588
|
+
route,
|
|
589
|
+
meta
|
|
590
|
+
});
|
|
591
|
+
}
|
|
469
592
|
return pageData;
|
|
470
593
|
}
|
|
471
594
|
function patchVPConfig(vpConfig, cfg) {
|
|
@@ -487,8 +610,6 @@ function checkConfig(cfg) {
|
|
|
487
610
|
|
|
488
611
|
// src/utils/node/vitePlugins.ts
|
|
489
612
|
var import_node_path3 = __toESM(require("path"));
|
|
490
|
-
var import_node_child_process2 = require("child_process");
|
|
491
|
-
var import_node_process2 = __toESM(require("process"));
|
|
492
613
|
var import_node_fs2 = require("fs");
|
|
493
614
|
var import_node_buffer = require("buffer");
|
|
494
615
|
var import_vitepress_plugin_pagefind = require("vitepress-plugin-pagefind");
|
|
@@ -509,18 +630,18 @@ function themeReloadPlugin() {
|
|
|
509
630
|
const restart = debounce(() => {
|
|
510
631
|
server.restart();
|
|
511
632
|
}, 500);
|
|
512
|
-
server.watcher.on("add", (path4) => {
|
|
633
|
+
server.watcher.on("add", async (path4) => {
|
|
513
634
|
const route = generateRoute(path4);
|
|
514
|
-
const meta = getArticleMeta(path4, route, blogConfig?.timeZone);
|
|
635
|
+
const meta = await getArticleMeta(path4, route, blogConfig?.timeZone);
|
|
515
636
|
blogConfig.pagesData.push({
|
|
516
637
|
route,
|
|
517
638
|
meta
|
|
518
639
|
});
|
|
519
640
|
restart();
|
|
520
641
|
});
|
|
521
|
-
server.watcher.on("change", (path4) => {
|
|
642
|
+
server.watcher.on("change", async (path4) => {
|
|
522
643
|
const route = generateRoute(path4);
|
|
523
|
-
const meta = getArticleMeta(path4, route, blogConfig?.timeZone);
|
|
644
|
+
const meta = await getArticleMeta(path4, route, blogConfig?.timeZone);
|
|
524
645
|
const matched = blogConfig.pagesData.find((v) => v.route === route);
|
|
525
646
|
if (matched && !isEqual(matched.meta, meta, ["date", "description"])) {
|
|
526
647
|
matched.meta = meta;
|
|
@@ -547,10 +668,9 @@ function themeReloadPlugin() {
|
|
|
547
668
|
// src/utils/node/vitePlugins.ts
|
|
548
669
|
function getVitePlugins(cfg) {
|
|
549
670
|
const plugins = [];
|
|
550
|
-
const buildEndFn = [];
|
|
551
|
-
plugins.push(inlineBuildEndPlugin(buildEndFn));
|
|
552
671
|
plugins.push(coverImgTransform());
|
|
553
672
|
plugins.push(themeReloadPlugin());
|
|
673
|
+
plugins.push(providePageData(cfg));
|
|
554
674
|
if (cfg && cfg.search !== false) {
|
|
555
675
|
const ops = cfg.search instanceof Object ? cfg.search : {};
|
|
556
676
|
plugins.push(
|
|
@@ -565,6 +685,7 @@ function getVitePlugins(cfg) {
|
|
|
565
685
|
}
|
|
566
686
|
if (cfg?.mermaid !== false) {
|
|
567
687
|
const { MermaidPlugin } = _require("vitepress-plugin-mermaid");
|
|
688
|
+
plugins.push(inlineInjectMermaidClient());
|
|
568
689
|
plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : cfg?.mermaid ?? {}));
|
|
569
690
|
}
|
|
570
691
|
if (cfg?.RSS) {
|
|
@@ -577,25 +698,15 @@ function registerVitePlugins(vpCfg, plugins) {
|
|
|
577
698
|
plugins
|
|
578
699
|
};
|
|
579
700
|
}
|
|
580
|
-
function
|
|
581
|
-
let rewrite = false;
|
|
701
|
+
function inlineInjectMermaidClient() {
|
|
582
702
|
return {
|
|
583
|
-
name: "@
|
|
703
|
+
name: "@sugarat/theme-plugin-inline-inject-mermaid-client",
|
|
584
704
|
enforce: "pre",
|
|
585
|
-
|
|
586
|
-
if (
|
|
587
|
-
return;
|
|
705
|
+
transform(code, id) {
|
|
706
|
+
if (id.endsWith("src/index.ts") && code.startsWith("// @sugarat/theme index")) {
|
|
707
|
+
return code.replace("// replace-mermaid-import-code", "import Mermaid from 'vitepress-plugin-mermaid/Mermaid.vue'").replace("// replace-mermaid-mounted-code", "if (!ctx.app.component('Mermaid')) { ctx.app.component('Mermaid', Mermaid as any) }");
|
|
588
708
|
}
|
|
589
|
-
|
|
590
|
-
if (!vitepressConfig) {
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
rewrite = true;
|
|
594
|
-
const selfBuildEnd = vitepressConfig.buildEnd;
|
|
595
|
-
vitepressConfig.buildEnd = (siteCfg) => {
|
|
596
|
-
selfBuildEnd?.(siteCfg);
|
|
597
|
-
buildEndFn.filter((fn) => typeof fn === "function").forEach((fn) => fn(siteCfg));
|
|
598
|
-
};
|
|
709
|
+
return code;
|
|
599
710
|
}
|
|
600
711
|
};
|
|
601
712
|
}
|
|
@@ -640,17 +751,31 @@ function coverImgTransform() {
|
|
|
640
751
|
}
|
|
641
752
|
};
|
|
642
753
|
}
|
|
754
|
+
function providePageData(cfg) {
|
|
755
|
+
return {
|
|
756
|
+
name: "@sugarat/theme-plugin-provide-page-data",
|
|
757
|
+
async config(config) {
|
|
758
|
+
const pagesData = await getArticles(cfg);
|
|
759
|
+
config.vitepress.site.themeConfig.blog.pagesData = pagesData;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
643
763
|
|
|
644
764
|
// src/node.ts
|
|
645
|
-
function getThemeConfig(cfg) {
|
|
765
|
+
function getThemeConfig(cfg = {}) {
|
|
646
766
|
checkConfig(cfg);
|
|
647
|
-
const pagesData =
|
|
648
|
-
const extraVPConfig = {
|
|
767
|
+
const pagesData = [];
|
|
768
|
+
const extraVPConfig = {
|
|
769
|
+
vite: {}
|
|
770
|
+
};
|
|
649
771
|
const vitePlugins = getVitePlugins(cfg);
|
|
650
772
|
registerVitePlugins(extraVPConfig, vitePlugins);
|
|
651
773
|
const markdownPlugin = getMarkdownPlugins(cfg);
|
|
652
774
|
registerMdPlugins(extraVPConfig, markdownPlugin);
|
|
653
|
-
|
|
775
|
+
cfg.mermaid = cfg.mermaid ?? false;
|
|
776
|
+
if (cfg?.mermaid !== false) {
|
|
777
|
+
patchMermaidPluginCfg(extraVPConfig);
|
|
778
|
+
}
|
|
654
779
|
patchOptimizeDeps(extraVPConfig);
|
|
655
780
|
patchVPConfig(extraVPConfig, cfg);
|
|
656
781
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
|
|
5
5
|
"author": "sugar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@mermaid-js/mermaid-mindmap": "^9.3.0",
|
|
43
43
|
"@vue/shared": "^3.4.26",
|
|
44
44
|
"@vueuse/core": "^9.13.0",
|
|
45
|
+
"cross-spawn": "^7.0.3",
|
|
45
46
|
"fast-glob": "^3.3.2",
|
|
46
47
|
"gray-matter": "^4.0.3",
|
|
47
48
|
"markdown-it-task-checkbox": "^1.0.6",
|
|
@@ -51,24 +52,25 @@
|
|
|
51
52
|
"vitepress-markdown-timeline": "^1.2.1",
|
|
52
53
|
"vitepress-plugin-mermaid": "2.0.13",
|
|
53
54
|
"vitepress-plugin-tabs": "0.2.0",
|
|
54
|
-
"vitepress-plugin-pagefind": "0.
|
|
55
|
+
"vitepress-plugin-pagefind": "0.4.1",
|
|
55
56
|
"vitepress-plugin-rss": "0.2.6"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@element-plus/icons-vue": "^2.3.1",
|
|
59
60
|
"artalk": "^2.8.5",
|
|
60
61
|
"element-plus": "^2.7.2",
|
|
62
|
+
"p-limit": "4",
|
|
61
63
|
"pagefind": "^1.1.0",
|
|
62
64
|
"sass": "^1.76.0",
|
|
63
65
|
"typescript": "^5.4.5",
|
|
64
66
|
"vite": "^5.2.11",
|
|
65
|
-
"vitepress": "1.2.
|
|
67
|
+
"vitepress": "1.2.3",
|
|
66
68
|
"vue": "^3.4.26"
|
|
67
69
|
},
|
|
68
70
|
"scripts": {
|
|
69
71
|
"dev": "npm run build:node && npm run dev:docs",
|
|
70
72
|
"dev:docs": "vitepress dev docs",
|
|
71
|
-
"dev:node": "npx tsup",
|
|
73
|
+
"dev:node": "npx tsup --watch",
|
|
72
74
|
"build": "npm run build:node && npm run build:docs",
|
|
73
75
|
"build:docs": "vitepress build docs",
|
|
74
76
|
"build:node": "npx tsup",
|
|
@@ -6,6 +6,7 @@ import Swiper from 'swiper'
|
|
|
6
6
|
import { useBlogConfig } from '../composables/config/blog'
|
|
7
7
|
import { getImageUrl, shuffleArray } from '../utils/client'
|
|
8
8
|
import type { Theme } from '../'
|
|
9
|
+
import { friendLinkSvgStr } from '../constants/svg'
|
|
9
10
|
|
|
10
11
|
const isDark = useDark({
|
|
11
12
|
storageKey: 'vitepress-theme-appearance'
|
|
@@ -16,6 +17,7 @@ const friendConfig = computed<Theme.FriendConfig>(() => ({
|
|
|
16
17
|
list: [],
|
|
17
18
|
random: false,
|
|
18
19
|
limit: Number.MAX_SAFE_INTEGER,
|
|
20
|
+
title: `${friendLinkSvgStr}友情链接`,
|
|
19
21
|
...(Array.isArray(friend) ? { list: friend } : friend)
|
|
20
22
|
}))
|
|
21
23
|
|
|
@@ -96,24 +98,7 @@ onUnmounted(() => {
|
|
|
96
98
|
<div v-if="friendList?.length" class="card friend-wrapper">
|
|
97
99
|
<!-- 头部 -->
|
|
98
100
|
<div class="card-header">
|
|
99
|
-
<span class="title svg-icon"
|
|
100
|
-
<path
|
|
101
|
-
fill="#EF9645"
|
|
102
|
-
d="M16.428 30.331a2.31 2.31 0 0 0 3.217-.568a.798.798 0 0 0-.197-1.114l-1.85-1.949l4.222 2.955a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-3.596-3.305l5.375 3.763a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-4.766-4.073l5.864 4.105a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089L4.733 11.194l-3.467 5.521c-.389.6-.283 1.413.276 1.891l7.786 6.671c.355.304.724.591 1.107.859l5.993 4.195z"
|
|
103
|
-
/>
|
|
104
|
-
<path
|
|
105
|
-
fill="#FFDC5D"
|
|
106
|
-
d="M29.802 21.752L18.5 13.601l-.059-.08l.053-.08l.053-.053l.854.469c.958.62 3.147 1.536 4.806 1.536c1.135 0 1.815-.425 2.018-1.257a1.409 1.409 0 0 0-1.152-1.622a6.788 6.788 0 0 1-2.801-1.091l-.555-.373c-.624-.421-1.331-.898-1.853-1.206c-.65-.394-1.357-.585-2.163-.585c-1.196 0-2.411.422-3.585.83l-1.266.436a5.18 5.18 0 0 1-1.696.271c-1.544 0-3.055-.586-4.516-1.152l-.147-.058a1.389 1.389 0 0 0-1.674.56L1.35 15.669a1.357 1.357 0 0 0 .257 1.761l7.785 6.672c.352.301.722.588 1.1.852l6.165 4.316a2 2 0 0 0 2.786-.491a.803.803 0 0 0-.196-1.115l-1.833-1.283a.424.424 0 0 1-.082-.618a.422.422 0 0 1 .567-.075l3.979 2.785a1.4 1.4 0 0 0 1.606-2.294l-3.724-2.606a.424.424 0 0 1-.082-.618a.423.423 0 0 1 .567-.075l5.132 3.593a1.4 1.4 0 0 0 1.606-2.294l-4.868-3.407a.42.42 0 0 1-.081-.618a.377.377 0 0 1 .506-.066l5.656 3.959a1.4 1.4 0 0 0 1.606-2.295z"
|
|
107
|
-
/>
|
|
108
|
-
<path
|
|
109
|
-
fill="#EF9645"
|
|
110
|
-
d="M16.536 27.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 23.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.495 1.495 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 23.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 25.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm16.555-4.137l.627-.542l-6.913-10.85l-12.27 1.985a1.507 1.507 0 0 0-1.235 1.737c.658 2.695 6.003.693 8.355-.601l11.436 8.271z"
|
|
111
|
-
/>
|
|
112
|
-
<path
|
|
113
|
-
fill="#FFCC4D"
|
|
114
|
-
d="M16.536 26.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 22.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.497 1.497 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 22.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 24.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm21.557-7.456a1.45 1.45 0 0 0 .269-1.885l-.003-.005l-3.467-6.521a1.488 1.488 0 0 0-1.794-.6c-1.992.771-4.174 1.657-6.292.937l-1.098-.377c-1.948-.675-4.066-1.466-6-.294c-.695.409-1.738 1.133-2.411 1.58a6.873 6.873 0 0 1-2.762 1.076a1.502 1.502 0 0 0-1.235 1.737c.613 2.512 5.3.908 7.838-.369a.968.968 0 0 1 1.002.081l11.584 8.416l4.369-3.776z"
|
|
115
|
-
/>
|
|
116
|
-
</svg> 友情链接</span>
|
|
101
|
+
<span class="title svg-icon" v-html="friendConfig.title" />
|
|
117
102
|
</div>
|
|
118
103
|
<!-- 友链列表 -->
|
|
119
104
|
<div
|
|
@@ -9,10 +9,16 @@ import {
|
|
|
9
9
|
useConfig,
|
|
10
10
|
useCurrentPageNum,
|
|
11
11
|
} from '../composables/config/blog'
|
|
12
|
+
import { tagsSvgStr } from '../constants/svg'
|
|
12
13
|
|
|
13
14
|
const route = useRoute()
|
|
14
15
|
const docs = useArticles()
|
|
15
|
-
const
|
|
16
|
+
const homeTagsConfig = useConfig()?.config?.blog?.homeTags
|
|
17
|
+
const showTags = computed(() => !!(homeTagsConfig ?? true))
|
|
18
|
+
const title = computed(() => (typeof homeTagsConfig === 'boolean' || !homeTagsConfig?.title)
|
|
19
|
+
? `${tagsSvgStr}标签`
|
|
20
|
+
: homeTagsConfig?.title
|
|
21
|
+
)
|
|
16
22
|
const tags = computed(() => {
|
|
17
23
|
return [...new Set(docs.value.map(v => v.meta.tag || []).flat(3))]
|
|
18
24
|
})
|
|
@@ -81,28 +87,11 @@ watch(
|
|
|
81
87
|
<div v-if="showTags && tags.length" class="card tags" data-pagefind-ignore="all">
|
|
82
88
|
<!-- 头部 -->
|
|
83
89
|
<div class="card-header">
|
|
84
|
-
<span class="title svg-icon"
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
<span class="title svg-icon" v-html="title" />
|
|
91
|
+
<ElTag
|
|
92
|
+
v-if="activeTag.label" :type="activeTag.type || 'primary'" :effect="colorMode" closable
|
|
93
|
+
@close="handleCloseTag"
|
|
87
94
|
>
|
|
88
|
-
<path
|
|
89
|
-
d="M810.88 245.888a118.432 118.432 0 1 0 0 236.864 118.432 118.432 0 0 0 0-236.864z m-151.008 118.432a151.008 151.008 0 1 1 302.016 0 151.008 151.008 0 0 1-302.016 0z"
|
|
90
|
-
fill="#D3D3D3" p-id="4291"
|
|
91
|
-
/>
|
|
92
|
-
<path
|
|
93
|
-
d="M774.08 565.6l61.76-160.64c6.4-16.64 2.56-35.84-10.24-48.64l-151.04-151.04c-12.8-12.8-31.68-16.64-48.64-10.24l-160.64 61.76c-12.16 4.8-23.36 11.84-32.64 21.12l-355.2 355.2c-17.92 17.92-17.92 46.72 0 64.32l256 256c17.92 17.92 46.72 17.92 64.32 0l355.2-355.2c9.28-9.28 16.32-20.16 21.12-32.64z m-159.36-149.12c-22.08-22.08-22.08-57.6 0-79.68 22.08-22.08 57.6-22.08 79.68 0 22.08 22.08 22.08 57.6 0 79.68-22.08 21.76-57.92 21.76-79.68 0z"
|
|
94
|
-
fill="#FCD53F" p-id="4292"
|
|
95
|
-
/>
|
|
96
|
-
<path
|
|
97
|
-
d="M654.4 320.48c14.4 0 28.8 5.44 39.68 16.64 22.08 22.08 22.08 57.6 0 79.68-10.88 10.88-25.28 16.64-39.68 16.64-14.4 0-28.8-5.44-39.68-16.64-22.08-22.08-22.08-57.6 0-79.68 10.88-11.2 25.28-16.64 39.68-16.64z m0-30.08c-23.04 0-44.8 8.96-61.12 25.28a86.72 86.72 0 0 0 0 122.24c16.32 16.32 38.08 25.28 61.12 25.28s44.8-8.96 61.12-25.28a86.72 86.72 0 0 0 0-122.24c-16.32-16.32-38.08-25.28-61.12-25.28z"
|
|
98
|
-
fill="#F8312F" p-id="4293"
|
|
99
|
-
/>
|
|
100
|
-
<path
|
|
101
|
-
d="M676.16 348.032c8.992 0 16.288 7.296 16.288 16.288a118.144 118.144 0 0 0 64.288 105.44h0.064c22.24 11.296 47.36 15.264 71.68 11.84a16.288 16.288 0 0 1 4.48 32.32 154.24 154.24 0 0 1-90.848-15.04 150.72 150.72 0 0 1-82.24-134.56c0-8.992 7.296-16.288 16.288-16.288z"
|
|
102
|
-
fill="#D3D3D3" p-id="4294"
|
|
103
|
-
/>
|
|
104
|
-
</svg> 标签</span>
|
|
105
|
-
<ElTag v-if="activeTag.label" :type="activeTag.type || 'primary'" :effect="colorMode" closable @close="handleCloseTag">
|
|
106
95
|
{{ activeTag.label }}
|
|
107
96
|
</ElTag>
|
|
108
97
|
</div>
|
|
@@ -12,7 +12,7 @@ const hotArticle = computed(() =>
|
|
|
12
12
|
_hotArticle === false ? undefined : _hotArticle
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
-
const title = computed(() => hotArticle.value?.title ||
|
|
15
|
+
const title = computed(() => hotArticle.value?.title || `${fireSVG}精选文章`)
|
|
16
16
|
const nextText = computed(() => hotArticle.value?.nextText || '换一组')
|
|
17
17
|
const pageSize = computed(() => hotArticle.value?.pageSize || 9)
|
|
18
18
|
const empty = computed(() => hotArticle.value?.empty ?? '暂无精选内容')
|
|
@@ -59,7 +59,7 @@ const showChangeBtn = computed(() => {
|
|
|
59
59
|
>
|
|
60
60
|
<!-- 头部 -->
|
|
61
61
|
<div class="card-header">
|
|
62
|
-
<span class="title" v-html="title" />
|
|
62
|
+
<span class="title svg-icon" v-html="title" />
|
|
63
63
|
<ElButton v-if="showChangeBtn" size="small" type="primary" text @click="changePage">
|
|
64
64
|
{{ nextText }}
|
|
65
65
|
</ElButton>
|
|
@@ -92,7 +92,7 @@ const resultCover = computed(() => {
|
|
|
92
92
|
<div class="badge-list pc-visible">
|
|
93
93
|
<span v-show="author" class="split">{{ author }}</span>
|
|
94
94
|
<span class="split">{{ showTime }}</span>
|
|
95
|
-
<span v-
|
|
95
|
+
<span v-if="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
|
|
96
96
|
</div>
|
|
97
97
|
</div>
|
|
98
98
|
<!-- 右侧封面图 -->
|
|
@@ -146,6 +146,10 @@ export namespace Theme {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
export interface HotArticle {
|
|
149
|
+
/**
|
|
150
|
+
* 自定义标题,支持SVG + 文字
|
|
151
|
+
* @default '🔥 精选文章'
|
|
152
|
+
*/
|
|
149
153
|
title?: string
|
|
150
154
|
pageSize?: number
|
|
151
155
|
nextText?: string
|
|
@@ -288,6 +292,11 @@ export namespace Theme {
|
|
|
288
292
|
* @default "动态计算"
|
|
289
293
|
*/
|
|
290
294
|
scrollSpeed?: number
|
|
295
|
+
/**
|
|
296
|
+
* 自定义展示标题,支持SVG + 文字
|
|
297
|
+
* @default '🤝 友情链接'
|
|
298
|
+
*/
|
|
299
|
+
title?: string
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
export interface UserWork {
|
|
@@ -408,7 +417,8 @@ export namespace Theme {
|
|
|
408
417
|
mermaid?: any
|
|
409
418
|
/**
|
|
410
419
|
* 设置解析 frontmatter 里 date 的时区
|
|
411
|
-
* @default
|
|
420
|
+
* @default new Date().getTimezoneOffset() / -60
|
|
421
|
+
* @example 8 => 'UTC+8'
|
|
412
422
|
*/
|
|
413
423
|
timeZone?: number
|
|
414
424
|
/**
|
|
@@ -452,7 +462,7 @@ export namespace Theme {
|
|
|
452
462
|
* 详见 https://oml2d.com/options/Options.html
|
|
453
463
|
*/
|
|
454
464
|
oml2d?: Oml2dOptions
|
|
455
|
-
homeTags?: boolean
|
|
465
|
+
homeTags?: boolean | HomeTagsConfig
|
|
456
466
|
buttonAfterArticle?: ButtonAfterArticleConfig | false
|
|
457
467
|
/**
|
|
458
468
|
* 是否开启深色模式过渡动画
|
|
@@ -586,4 +596,12 @@ export namespace Theme {
|
|
|
586
596
|
*/
|
|
587
597
|
coverPreview?: ReplaceRule | ReplaceRule[]
|
|
588
598
|
}
|
|
599
|
+
|
|
600
|
+
export interface HomeTagsConfig {
|
|
601
|
+
/**
|
|
602
|
+
* 自定义标题,支持SVG + 文字
|
|
603
|
+
* @default '🏷 标签'
|
|
604
|
+
*/
|
|
605
|
+
title?: string
|
|
606
|
+
}
|
|
589
607
|
}
|
package/src/constants/svg.ts
CHANGED
|
@@ -75,3 +75,44 @@ export const weChatPaySVG = `
|
|
|
75
75
|
/>
|
|
76
76
|
</svg>
|
|
77
77
|
`
|
|
78
|
+
|
|
79
|
+
export const friendLinkSvgStr = `<svg width="512" height="512" viewBox="0 0 36 36" xmlns="http://www.w3.org/2000/svg">
|
|
80
|
+
<path
|
|
81
|
+
fill="#EF9645"
|
|
82
|
+
d="M16.428 30.331a2.31 2.31 0 0 0 3.217-.568a.798.798 0 0 0-.197-1.114l-1.85-1.949l4.222 2.955a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-3.596-3.305l5.375 3.763a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089l-4.766-4.073l5.864 4.105a1.497 1.497 0 0 0 2.089-.369a1.5 1.5 0 0 0-.369-2.089L4.733 11.194l-3.467 5.521c-.389.6-.283 1.413.276 1.891l7.786 6.671c.355.304.724.591 1.107.859l5.993 4.195z"
|
|
83
|
+
/>
|
|
84
|
+
<path
|
|
85
|
+
fill="#FFDC5D"
|
|
86
|
+
d="M29.802 21.752L18.5 13.601l-.059-.08l.053-.08l.053-.053l.854.469c.958.62 3.147 1.536 4.806 1.536c1.135 0 1.815-.425 2.018-1.257a1.409 1.409 0 0 0-1.152-1.622a6.788 6.788 0 0 1-2.801-1.091l-.555-.373c-.624-.421-1.331-.898-1.853-1.206c-.65-.394-1.357-.585-2.163-.585c-1.196 0-2.411.422-3.585.83l-1.266.436a5.18 5.18 0 0 1-1.696.271c-1.544 0-3.055-.586-4.516-1.152l-.147-.058a1.389 1.389 0 0 0-1.674.56L1.35 15.669a1.357 1.357 0 0 0 .257 1.761l7.785 6.672c.352.301.722.588 1.1.852l6.165 4.316a2 2 0 0 0 2.786-.491a.803.803 0 0 0-.196-1.115l-1.833-1.283a.424.424 0 0 1-.082-.618a.422.422 0 0 1 .567-.075l3.979 2.785a1.4 1.4 0 0 0 1.606-2.294l-3.724-2.606a.424.424 0 0 1-.082-.618a.423.423 0 0 1 .567-.075l5.132 3.593a1.4 1.4 0 0 0 1.606-2.294l-4.868-3.407a.42.42 0 0 1-.081-.618a.377.377 0 0 1 .506-.066l5.656 3.959a1.4 1.4 0 0 0 1.606-2.295z"
|
|
87
|
+
/>
|
|
88
|
+
<path
|
|
89
|
+
fill="#EF9645"
|
|
90
|
+
d="M16.536 27.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 23.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.495 1.495 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 23.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 25.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm16.555-4.137l.627-.542l-6.913-10.85l-12.27 1.985a1.507 1.507 0 0 0-1.235 1.737c.658 2.695 6.003.693 8.355-.601l11.436 8.271z"
|
|
91
|
+
/>
|
|
92
|
+
<path
|
|
93
|
+
fill="#FFCC4D"
|
|
94
|
+
d="M16.536 26.929c-.07.267-.207.498-.389.681l-1.004.996a1.494 1.494 0 0 1-1.437.396a1.5 1.5 0 0 1-.683-2.512l1.004-.996a1.494 1.494 0 0 1 1.437-.396a1.502 1.502 0 0 1 1.072 1.831zM5.992 22.008l1.503-1.497a1.5 1.5 0 0 0-.444-2.429a1.497 1.497 0 0 0-1.674.31l-1.503 1.497a1.5 1.5 0 0 0 .445 2.429a1.496 1.496 0 0 0 1.673-.31zm5.204.052a1.5 1.5 0 1 0-2.122-2.118L6.072 22.94a1.5 1.5 0 1 0 2.122 2.118l3.002-2.998zm2.25 3a1.5 1.5 0 0 0-.945-2.555a1.489 1.489 0 0 0-1.173.44L9.323 24.94a1.5 1.5 0 0 0 .945 2.556c.455.036.874-.141 1.173-.44l2.005-1.996zm21.557-7.456a1.45 1.45 0 0 0 .269-1.885l-.003-.005l-3.467-6.521a1.488 1.488 0 0 0-1.794-.6c-1.992.771-4.174 1.657-6.292.937l-1.098-.377c-1.948-.675-4.066-1.466-6-.294c-.695.409-1.738 1.133-2.411 1.58a6.873 6.873 0 0 1-2.762 1.076a1.502 1.502 0 0 0-1.235 1.737c.613 2.512 5.3.908 7.838-.369a.968.968 0 0 1 1.002.081l11.584 8.416l4.369-3.776z"
|
|
95
|
+
/>
|
|
96
|
+
</svg>`
|
|
97
|
+
|
|
98
|
+
export const tagsSvgStr = `<svg
|
|
99
|
+
t="1695048840129" class="icon" viewBox="0 0 1024 1024" version="1.1"
|
|
100
|
+
xmlns="http://www.w3.org/2000/svg" p-id="4290" width="200" height="200"
|
|
101
|
+
>
|
|
102
|
+
<path
|
|
103
|
+
d="M810.88 245.888a118.432 118.432 0 1 0 0 236.864 118.432 118.432 0 0 0 0-236.864z m-151.008 118.432a151.008 151.008 0 1 1 302.016 0 151.008 151.008 0 0 1-302.016 0z"
|
|
104
|
+
fill="#D3D3D3" p-id="4291"
|
|
105
|
+
/>
|
|
106
|
+
<path
|
|
107
|
+
d="M774.08 565.6l61.76-160.64c6.4-16.64 2.56-35.84-10.24-48.64l-151.04-151.04c-12.8-12.8-31.68-16.64-48.64-10.24l-160.64 61.76c-12.16 4.8-23.36 11.84-32.64 21.12l-355.2 355.2c-17.92 17.92-17.92 46.72 0 64.32l256 256c17.92 17.92 46.72 17.92 64.32 0l355.2-355.2c9.28-9.28 16.32-20.16 21.12-32.64z m-159.36-149.12c-22.08-22.08-22.08-57.6 0-79.68 22.08-22.08 57.6-22.08 79.68 0 22.08 22.08 22.08 57.6 0 79.68-22.08 21.76-57.92 21.76-79.68 0z"
|
|
108
|
+
fill="#FCD53F" p-id="4292"
|
|
109
|
+
/>
|
|
110
|
+
<path
|
|
111
|
+
d="M654.4 320.48c14.4 0 28.8 5.44 39.68 16.64 22.08 22.08 22.08 57.6 0 79.68-10.88 10.88-25.28 16.64-39.68 16.64-14.4 0-28.8-5.44-39.68-16.64-22.08-22.08-22.08-57.6 0-79.68 10.88-11.2 25.28-16.64 39.68-16.64z m0-30.08c-23.04 0-44.8 8.96-61.12 25.28a86.72 86.72 0 0 0 0 122.24c16.32 16.32 38.08 25.28 61.12 25.28s44.8-8.96 61.12-25.28a86.72 86.72 0 0 0 0-122.24c-16.32-16.32-38.08-25.28-61.12-25.28z"
|
|
112
|
+
fill="#F8312F" p-id="4293"
|
|
113
|
+
/>
|
|
114
|
+
<path
|
|
115
|
+
d="M676.16 348.032c8.992 0 16.288 7.296 16.288 16.288a118.144 118.144 0 0 0 64.288 105.44h0.064c22.24 11.296 47.36 15.264 71.68 11.84a16.288 16.288 0 0 1 4.48 32.32 154.24 154.24 0 0 1-90.848-15.04 150.72 150.72 0 0 1-82.24-134.56c0-8.992 7.296-16.288 16.288-16.288z"
|
|
116
|
+
fill="#D3D3D3" p-id="4294"
|
|
117
|
+
/>
|
|
118
|
+
</svg>`
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// @sugarat/theme index
|
|
1
2
|
// override style
|
|
2
3
|
import './styles/index.scss'
|
|
3
4
|
|
|
@@ -23,13 +24,12 @@ import DefaultTheme from 'vitepress/theme'
|
|
|
23
24
|
import { enhanceAppWithTabs } from 'vitepress-plugin-tabs/client'
|
|
24
25
|
|
|
25
26
|
// 图表渲染组件
|
|
26
|
-
//
|
|
27
|
-
import Mermaid from 'vitepress-plugin-mermaid/Mermaid.vue'
|
|
27
|
+
// replace-mermaid-import-code
|
|
28
|
+
// import Mermaid from 'vitepress-plugin-mermaid/Mermaid.vue'
|
|
28
29
|
import BlogApp from './components/BlogApp.vue'
|
|
29
30
|
import { withConfigProvider } from './composables/config/blog'
|
|
30
31
|
|
|
31
32
|
// page
|
|
32
|
-
import TimelinePage from './components/TimelinePage.vue'
|
|
33
33
|
import UserWorksPage from './components/UserWorks.vue'
|
|
34
34
|
|
|
35
35
|
// 内置一些特殊的主题色
|
|
@@ -41,11 +41,9 @@ export const BlogTheme: Theme = {
|
|
|
41
41
|
enhanceApp(ctx) {
|
|
42
42
|
enhanceAppWithTabs(ctx.app as any)
|
|
43
43
|
DefaultTheme.enhanceApp(ctx)
|
|
44
|
-
ctx.app.component('
|
|
45
|
-
|
|
46
|
-
if (!ctx.app.component('Mermaid')) {
|
|
47
|
-
ctx.app.component('Mermaid', Mermaid as any)
|
|
48
|
-
}
|
|
44
|
+
ctx.app.component('UserWorksPage', UserWorksPage as any)
|
|
45
|
+
// replace-mermaid-mounted-code
|
|
46
|
+
// if (!ctx.app.component('Mermaid')) { ctx.app.component('Mermaid', Mermaid as any) }
|
|
49
47
|
}
|
|
50
48
|
}
|
|
51
49
|
|
package/src/node.ts
CHANGED
|
@@ -6,20 +6,22 @@ import {
|
|
|
6
6
|
patchOptimizeDeps,
|
|
7
7
|
registerMdPlugins,
|
|
8
8
|
} from './utils/node/mdPlugins'
|
|
9
|
-
import { checkConfig,
|
|
9
|
+
import { checkConfig, patchVPConfig, patchVPThemeConfig } from './utils/node/theme'
|
|
10
10
|
import { getVitePlugins, registerVitePlugins } from './utils/node/vitePlugins'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* 获取主题的配置
|
|
14
14
|
* @param cfg 主题配置
|
|
15
15
|
*/
|
|
16
|
-
export function getThemeConfig(cfg
|
|
16
|
+
export function getThemeConfig(cfg: Partial<Theme.BlogConfig> = {}) {
|
|
17
17
|
// 配置校验
|
|
18
18
|
checkConfig(cfg)
|
|
19
19
|
|
|
20
20
|
// 文章数据
|
|
21
|
-
const pagesData =
|
|
22
|
-
const extraVPConfig: any = {
|
|
21
|
+
const pagesData: Theme.PageData[] = []
|
|
22
|
+
const extraVPConfig: any = {
|
|
23
|
+
vite: {}
|
|
24
|
+
}
|
|
23
25
|
|
|
24
26
|
// 获取要加载的vite插件
|
|
25
27
|
const vitePlugins = getVitePlugins(cfg)
|
|
@@ -32,7 +34,11 @@ export function getThemeConfig(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
32
34
|
registerMdPlugins(extraVPConfig, markdownPlugin)
|
|
33
35
|
|
|
34
36
|
// patch extraVPConfig
|
|
35
|
-
|
|
37
|
+
// 默认不开启 markdown 图表,会明显影响构建速度
|
|
38
|
+
cfg.mermaid = cfg.mermaid ?? false
|
|
39
|
+
if (cfg?.mermaid !== false) {
|
|
40
|
+
patchMermaidPluginCfg(extraVPConfig)
|
|
41
|
+
}
|
|
36
42
|
patchOptimizeDeps(extraVPConfig)
|
|
37
43
|
|
|
38
44
|
patchVPConfig(extraVPConfig, cfg)
|
|
@@ -20,9 +20,9 @@ export function themeReloadPlugin() {
|
|
|
20
20
|
const restart = debounce(() => {
|
|
21
21
|
server.restart()
|
|
22
22
|
}, 500)
|
|
23
|
-
server.watcher.on('add', (path) => {
|
|
23
|
+
server.watcher.on('add', async (path) => {
|
|
24
24
|
const route = generateRoute(path)
|
|
25
|
-
const meta = getArticleMeta(path, route, blogConfig?.timeZone)
|
|
25
|
+
const meta = await getArticleMeta(path, route, blogConfig?.timeZone)
|
|
26
26
|
blogConfig.pagesData.push({
|
|
27
27
|
route,
|
|
28
28
|
meta
|
|
@@ -30,9 +30,9 @@ export function themeReloadPlugin() {
|
|
|
30
30
|
restart()
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
server.watcher.on('change', (path: string) => {
|
|
33
|
+
server.watcher.on('change', async (path: string) => {
|
|
34
34
|
const route = generateRoute(path)
|
|
35
|
-
const meta = getArticleMeta(path, route, blogConfig?.timeZone)
|
|
35
|
+
const meta = await getArticleMeta(path, route, blogConfig?.timeZone)
|
|
36
36
|
const matched = blogConfig.pagesData.find(v => v.route === route)
|
|
37
37
|
|
|
38
38
|
if (matched && !isEqual(matched.meta, meta, ['date', 'description'])) {
|
package/src/utils/node/index.ts
CHANGED
|
@@ -36,24 +36,27 @@ export function getDefaultTitle(content: string) {
|
|
|
36
36
|
return match?.[2] || ''
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const infoStr = spawnSync('git', ['log', '-1', '--pretty="%ci"', url])
|
|
45
|
-
.stdout?.toString()
|
|
46
|
-
.replace(/["']/g, '')
|
|
47
|
-
.trim()
|
|
48
|
-
if (infoStr) {
|
|
49
|
-
date = new Date(infoStr)
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
return date
|
|
39
|
+
const cache = new Map<string, Date>()
|
|
40
|
+
export function getFileBirthTime(url: string): Promise<Date | undefined> | Date {
|
|
41
|
+
const cached = cache.get(url)
|
|
42
|
+
if (cached) {
|
|
43
|
+
return cached
|
|
54
44
|
}
|
|
55
45
|
|
|
56
|
-
return
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
// 使用异步回调
|
|
48
|
+
const child = spawn('git', ['log', '-1', '--pretty="%ai"', url])
|
|
49
|
+
let output = ''
|
|
50
|
+
child.stdout.on('data', d => (output += String(d)))
|
|
51
|
+
child.on('close', () => {
|
|
52
|
+
const date = new Date(output)
|
|
53
|
+
cache.set(url, date)
|
|
54
|
+
resolve(date)
|
|
55
|
+
})
|
|
56
|
+
child.on('error', () => {
|
|
57
|
+
resolve(undefined)
|
|
58
|
+
})
|
|
59
|
+
})
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export function getGitTimestamp(file: string) {
|
package/src/utils/node/theme.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import process from 'node:process'
|
|
5
|
+
import os from 'node:os'
|
|
5
6
|
import glob from 'fast-glob'
|
|
6
7
|
import matter from 'gray-matter'
|
|
8
|
+
import pLimit from 'p-limit'
|
|
7
9
|
import type { Theme } from '../../composables/config/index'
|
|
8
10
|
import { formatDate } from '../client'
|
|
9
11
|
import { getDefaultTitle, getFileBirthTime, getFirstImagURLFromMD, getTextSummary } from './index'
|
|
@@ -49,8 +51,8 @@ export function getPageRoute(filepath: string, srcDir: string) {
|
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
const defaultTimeZoneOffset = new Date().getTimezoneOffset() / -60
|
|
52
|
-
export function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset) {
|
|
53
|
-
const fileContent = fs.
|
|
54
|
+
export async function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset) {
|
|
55
|
+
const fileContent = await fs.promises.readFile(filepath, 'utf-8')
|
|
54
56
|
|
|
55
57
|
const { data: frontmatter, excerpt, content } = matter(fileContent, {
|
|
56
58
|
excerpt: true,
|
|
@@ -63,13 +65,13 @@ export function getArticleMeta(filepath: string, route: string, timeZone = defau
|
|
|
63
65
|
if (!meta.title) {
|
|
64
66
|
meta.title = getDefaultTitle(content)
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
meta.date
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
)
|
|
68
|
+
const date = await (
|
|
69
|
+
(meta.date
|
|
70
|
+
&& new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`))
|
|
71
|
+
|| getFileBirthTime(filepath)
|
|
72
|
+
)
|
|
73
|
+
if (date) {
|
|
74
|
+
meta.date = formatDate(date)
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// 处理tags和categories,兼容历史文章
|
|
@@ -101,22 +103,40 @@ export function getArticleMeta(filepath: string, route: string, timeZone = defau
|
|
|
101
103
|
}
|
|
102
104
|
return meta as Theme.PageMeta
|
|
103
105
|
}
|
|
104
|
-
export function getArticles(cfg?: Partial<Theme.BlogConfig>) {
|
|
106
|
+
export async function getArticles(cfg?: Partial<Theme.BlogConfig>) {
|
|
105
107
|
const srcDir = cfg?.srcDir || process.argv.slice(2)?.[1] || '.'
|
|
106
108
|
const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'] })
|
|
109
|
+
const limit = pLimit(+(process.env.P_LIMT_MAX || os.cpus().length))
|
|
107
110
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
const metaResults = files.reduce((prev, curr) => {
|
|
112
|
+
const route = getPageRoute(curr, srcDir)
|
|
113
|
+
const metaPromise = limit(() => getArticleMeta(curr, route, cfg?.timeZone))
|
|
114
|
+
|
|
115
|
+
// 提前获取,有缓存取缓存
|
|
116
|
+
prev[curr] = {
|
|
117
|
+
route,
|
|
118
|
+
metaPromise
|
|
119
|
+
}
|
|
120
|
+
return prev
|
|
121
|
+
}, {} as Record<string, {
|
|
122
|
+
route: string
|
|
123
|
+
metaPromise: Promise<Theme.PageMeta>
|
|
124
|
+
}>)
|
|
125
|
+
|
|
126
|
+
const pageData: Theme.PageData[] = []
|
|
127
|
+
|
|
128
|
+
for (const file of files) {
|
|
129
|
+
const { route, metaPromise } = metaResults[file]
|
|
130
|
+
const meta = await metaPromise
|
|
131
|
+
if (meta.layout === 'home') {
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
pageData.push({
|
|
135
|
+
route,
|
|
136
|
+
meta
|
|
117
137
|
})
|
|
118
|
-
|
|
119
|
-
return pageData
|
|
138
|
+
}
|
|
139
|
+
return pageData
|
|
120
140
|
}
|
|
121
141
|
|
|
122
142
|
export function patchVPConfig(vpConfig: any, cfg?: Partial<Theme.BlogConfig>) {
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
|
-
import { execSync } from 'node:child_process'
|
|
3
|
-
import process from 'node:process'
|
|
4
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
5
3
|
import { Buffer } from 'node:buffer'
|
|
6
4
|
import type { SiteConfig } from 'vitepress'
|
|
@@ -10,19 +8,19 @@ import {
|
|
|
10
8
|
pagefindPlugin
|
|
11
9
|
} from 'vitepress-plugin-pagefind'
|
|
12
10
|
import { RssPlugin } from 'vitepress-plugin-rss'
|
|
11
|
+
import type { PluginOption } from 'vite'
|
|
13
12
|
import type { Theme } from '../../composables/config/index'
|
|
14
13
|
import { _require } from './mdPlugins'
|
|
15
14
|
import { themeReloadPlugin } from './hot-reload-plugin'
|
|
15
|
+
import { getArticles } from './theme'
|
|
16
16
|
import { joinPath } from './index'
|
|
17
17
|
|
|
18
18
|
export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
|
|
19
19
|
const plugins: any[] = []
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// 执行自定义的 buildEnd 钩子
|
|
25
|
-
plugins.push(inlineBuildEndPlugin(buildEndFn))
|
|
21
|
+
// const buildEndFn: any[] = []
|
|
22
|
+
// Build完后运行的一系列列方法,执行自定义的 buildEnd 钩子
|
|
23
|
+
// plugins.push(inlineBuildEndPlugin(buildEndFn))
|
|
26
24
|
|
|
27
25
|
// 处理cover image的路径(暂只支持自动识别的文章首图)
|
|
28
26
|
plugins.push(coverImgTransform())
|
|
@@ -30,6 +28,9 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
30
28
|
// 自动重载首页
|
|
31
29
|
plugins.push(themeReloadPlugin())
|
|
32
30
|
|
|
31
|
+
// 主题pageData生成
|
|
32
|
+
plugins.push(providePageData(cfg))
|
|
33
|
+
|
|
33
34
|
// 内置简化版的pagefind
|
|
34
35
|
if (cfg && cfg.search !== false) {
|
|
35
36
|
const ops = cfg.search instanceof Object ? cfg.search : {}
|
|
@@ -47,6 +48,7 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
47
48
|
// 内置支持Mermaid
|
|
48
49
|
if (cfg?.mermaid !== false) {
|
|
49
50
|
const { MermaidPlugin } = _require('vitepress-plugin-mermaid')
|
|
51
|
+
plugins.push(inlineInjectMermaidClient())
|
|
50
52
|
plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : (cfg?.mermaid ?? {})))
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -63,45 +65,19 @@ export function registerVitePlugins(vpCfg: any, plugins: any[]) {
|
|
|
63
65
|
}
|
|
64
66
|
}
|
|
65
67
|
|
|
66
|
-
export function
|
|
67
|
-
buildEndFn.push(() => {
|
|
68
|
-
// 调用pagefind
|
|
69
|
-
const ignore: string[] = [
|
|
70
|
-
// 侧边栏内容
|
|
71
|
-
'div.aside',
|
|
72
|
-
// 标题锚点
|
|
73
|
-
'a.header-anchor'
|
|
74
|
-
]
|
|
75
|
-
const { log } = console
|
|
76
|
-
log()
|
|
77
|
-
log('=== pagefind: https://pagefind.app/ ===')
|
|
78
|
-
const siteDir = path.join(
|
|
79
|
-
process.argv.slice(2)?.[1] || '.',
|
|
80
|
-
'.vitepress/dist'
|
|
81
|
-
)
|
|
82
|
-
let command = `npx pagefind --site ${siteDir} --output-subdir "_pagefind"`
|
|
83
|
-
|
|
84
|
-
if (ignore.length) {
|
|
85
|
-
command += ` --exclude-selectors "${ignore.join(', ')}"`
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
log(command)
|
|
89
|
-
log()
|
|
90
|
-
execSync(command, {
|
|
91
|
-
stdio: 'inherit'
|
|
92
|
-
})
|
|
93
|
-
})
|
|
68
|
+
export function inlineInjectMermaidClient() {
|
|
94
69
|
return {
|
|
95
|
-
name: '@
|
|
70
|
+
name: '@sugarat/theme-plugin-inline-inject-mermaid-client',
|
|
96
71
|
enforce: 'pre',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
72
|
+
transform(code, id) {
|
|
73
|
+
if (id.endsWith('src/index.ts') && code.startsWith('// @sugarat/theme index')) {
|
|
74
|
+
return code
|
|
75
|
+
.replace('// replace-mermaid-import-code', 'import Mermaid from \'vitepress-plugin-mermaid/Mermaid.vue\'')
|
|
76
|
+
.replace('// replace-mermaid-mounted-code', 'if (!ctx.app.component(\'Mermaid\')) { ctx.app.component(\'Mermaid\', Mermaid as any) }')
|
|
101
77
|
}
|
|
102
78
|
return code
|
|
103
|
-
}
|
|
104
|
-
}
|
|
79
|
+
},
|
|
80
|
+
} as PluginOption
|
|
105
81
|
}
|
|
106
82
|
|
|
107
83
|
export function inlineBuildEndPlugin(buildEndFn: any[]) {
|
|
@@ -176,3 +152,13 @@ export function coverImgTransform() {
|
|
|
176
152
|
}
|
|
177
153
|
}
|
|
178
154
|
}
|
|
155
|
+
|
|
156
|
+
export function providePageData(cfg?: Partial<Theme.BlogConfig>) {
|
|
157
|
+
return {
|
|
158
|
+
name: '@sugarat/theme-plugin-provide-page-data',
|
|
159
|
+
async config(config: any) {
|
|
160
|
+
const pagesData = await getArticles(cfg)
|
|
161
|
+
config.vitepress.site.themeConfig.blog.pagesData = pagesData
|
|
162
|
+
},
|
|
163
|
+
} as PluginOption
|
|
164
|
+
}
|