@sugarat/theme 0.4.2 → 0.4.4
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 +3 -12
- package/node.js +22 -176
- package/package.json +4 -6
- package/src/components/BlogArticleAnalyze.vue +6 -2
- package/src/components/BlogItem.vue +1 -1
- package/src/composables/config/index.ts +4 -14
- package/src/utils/node/index.ts +1 -111
- package/src/utils/node/theme.ts +17 -38
- package/src/utils/node/vitePlugins.ts +5 -11
package/node.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { RSSOptions } from 'vitepress-plugin-rss';
|
|
|
4
4
|
import { Repo, Mapping } from '@giscus/vue';
|
|
5
5
|
import { Options } from 'oh-my-live2d';
|
|
6
6
|
import { Ref } from 'vue';
|
|
7
|
+
import { PagefindConfig } from 'vitepress-plugin-pagefind';
|
|
7
8
|
export { tabsMarkdownPlugin } from 'vitepress-plugin-tabs';
|
|
8
9
|
|
|
9
10
|
type RSSPluginOptions = RSSOptions;
|
|
@@ -324,17 +325,7 @@ declare namespace Theme {
|
|
|
324
325
|
tags?: string[];
|
|
325
326
|
top?: number;
|
|
326
327
|
}
|
|
327
|
-
type SearchConfig =
|
|
328
|
-
btnPlaceholder?: string;
|
|
329
|
-
placeholder?: string;
|
|
330
|
-
emptyText?: string;
|
|
331
|
-
/**
|
|
332
|
-
* @example
|
|
333
|
-
* 'Total: {{searchResult}} search results.'
|
|
334
|
-
*/
|
|
335
|
-
heading?: string;
|
|
336
|
-
mode?: boolean | 'pagefind';
|
|
337
|
-
};
|
|
328
|
+
type SearchConfig = false | PagefindConfig;
|
|
338
329
|
interface UserWorks {
|
|
339
330
|
title: string;
|
|
340
331
|
description?: string;
|
|
@@ -400,7 +391,7 @@ declare namespace Theme {
|
|
|
400
391
|
/**
|
|
401
392
|
* 启用RSS配置
|
|
402
393
|
*/
|
|
403
|
-
RSS?: RSSOptions;
|
|
394
|
+
RSS?: RSSOptions | RSSOptions[];
|
|
404
395
|
/**
|
|
405
396
|
* 首页页脚
|
|
406
397
|
*/
|
package/node.js
CHANGED
|
@@ -208,44 +208,14 @@ var tabsPlugin = (md) => {
|
|
|
208
208
|
var import_vitepress_markdown_timeline = __toESM(require("vitepress-markdown-timeline"));
|
|
209
209
|
|
|
210
210
|
// src/utils/node/index.ts
|
|
211
|
-
var import_node_child_process = require("child_process");
|
|
212
211
|
var import_node_path = __toESM(require("path"));
|
|
213
|
-
|
|
214
|
-
const match = content.match(/^(#+)\s+(.+)/m);
|
|
215
|
-
return match?.[2] || "";
|
|
216
|
-
}
|
|
217
|
-
var cache = /* @__PURE__ */ new Map();
|
|
218
|
-
function getFileBirthTime(url) {
|
|
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
|
-
});
|
|
236
|
-
}
|
|
237
|
-
function getTextSummary(text, count = 100) {
|
|
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);
|
|
239
|
-
}
|
|
212
|
+
var import_theme_shared = require("@sugarat/theme-shared");
|
|
240
213
|
function aliasObjectToArray(obj) {
|
|
241
214
|
return Object.entries(obj).map(([find, replacement]) => ({
|
|
242
215
|
find,
|
|
243
216
|
replacement
|
|
244
217
|
}));
|
|
245
218
|
}
|
|
246
|
-
function joinPath(base, path4) {
|
|
247
|
-
return `${base}${path4}`.replace(/\/+/g, "/");
|
|
248
|
-
}
|
|
249
219
|
function isBase64ImageURL(url) {
|
|
250
220
|
const regex = /^data:image\/[a-z]+;base64,/;
|
|
251
221
|
return regex.test(url);
|
|
@@ -260,10 +230,10 @@ function getFirstImagURLFromMD(content, route) {
|
|
|
260
230
|
if (isHTTPSource || isBase64ImageURL(url)) {
|
|
261
231
|
return url;
|
|
262
232
|
}
|
|
263
|
-
const paths = joinPath("/", route).split("/");
|
|
233
|
+
const paths = (0, import_theme_shared.joinPath)("/", route).split("/");
|
|
264
234
|
paths.splice(paths.length - 1, 1);
|
|
265
235
|
const relativePath = url.startsWith("/") ? url : import_node_path.default.join(paths.join("/") || "", url);
|
|
266
|
-
return joinPath("/", relativePath);
|
|
236
|
+
return (0, import_theme_shared.joinPath)("/", relativePath);
|
|
267
237
|
}
|
|
268
238
|
function debounce(func, delay = 1e3) {
|
|
269
239
|
let timeoutId;
|
|
@@ -361,112 +331,8 @@ function patchOptimizeDeps(config) {
|
|
|
361
331
|
var import_node_fs = __toESM(require("fs"));
|
|
362
332
|
var import_node_path2 = __toESM(require("path"));
|
|
363
333
|
var import_node_process = __toESM(require("process"));
|
|
364
|
-
var import_node_os = __toESM(require("os"));
|
|
365
334
|
var import_fast_glob = __toESM(require("fast-glob"));
|
|
366
|
-
var
|
|
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
|
-
}
|
|
335
|
+
var import_theme_shared2 = require("@sugarat/theme-shared");
|
|
470
336
|
|
|
471
337
|
// src/utils/client/index.ts
|
|
472
338
|
function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
|
|
@@ -517,46 +383,29 @@ function patchDefaultThemeSideBar(cfg) {
|
|
|
517
383
|
} : void 0;
|
|
518
384
|
}
|
|
519
385
|
function getPageRoute(filepath, srcDir) {
|
|
520
|
-
|
|
521
|
-
if (route.startsWith("./")) {
|
|
522
|
-
route = route.replace(
|
|
523
|
-
new RegExp(
|
|
524
|
-
`^\\.\\/${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
|
|
525
|
-
),
|
|
526
|
-
""
|
|
527
|
-
);
|
|
528
|
-
} else {
|
|
529
|
-
route = route.replace(
|
|
530
|
-
new RegExp(
|
|
531
|
-
`^${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
|
|
532
|
-
),
|
|
533
|
-
""
|
|
534
|
-
);
|
|
535
|
-
}
|
|
386
|
+
const route = (0, import_theme_shared2.normalizePath)(import_node_path2.default.relative(srcDir, filepath)).replace(/\.md$/, "");
|
|
536
387
|
return `/${route}`;
|
|
537
388
|
}
|
|
538
389
|
var defaultTimeZoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() / -60;
|
|
539
390
|
async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
540
391
|
const fileContent = await import_node_fs.default.promises.readFile(filepath, "utf-8");
|
|
541
|
-
const { data: frontmatter, excerpt, content } = (0,
|
|
392
|
+
const { data: frontmatter, excerpt, content } = (0, import_theme_shared2.grayMatter)(fileContent, {
|
|
542
393
|
excerpt: true
|
|
543
394
|
});
|
|
544
395
|
const meta = {
|
|
545
396
|
...frontmatter
|
|
546
397
|
};
|
|
547
398
|
if (!meta.title) {
|
|
548
|
-
meta.title = getDefaultTitle(content);
|
|
549
|
-
}
|
|
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);
|
|
399
|
+
meta.title = (0, import_theme_shared2.getDefaultTitle)(content);
|
|
553
400
|
}
|
|
401
|
+
const date = await (meta.date && /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`) || (0, import_theme_shared2.getFileLastModifyTime)(filepath));
|
|
402
|
+
meta.date = formatDate(date || /* @__PURE__ */ new Date());
|
|
554
403
|
meta.categories = typeof meta.categories === "string" ? [meta.categories] : meta.categories;
|
|
555
404
|
meta.tags = typeof meta.tags === "string" ? [meta.tags] : meta.tags;
|
|
556
405
|
meta.tag = [meta.tag || []].flat().concat([
|
|
557
406
|
.../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
|
|
558
407
|
]);
|
|
559
|
-
meta.description = meta.description || getTextSummary(content, 100) || excerpt;
|
|
408
|
+
meta.description = meta.description || (0, import_theme_shared2.getTextSummary)(content, 100) || excerpt;
|
|
560
409
|
meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, route);
|
|
561
410
|
if (meta.publish === false) {
|
|
562
411
|
meta.hidden = true;
|
|
@@ -564,13 +413,12 @@ async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset)
|
|
|
564
413
|
}
|
|
565
414
|
return meta;
|
|
566
415
|
}
|
|
567
|
-
async function getArticles(cfg) {
|
|
568
|
-
const srcDir = cfg?.srcDir || import_node_process.default.argv.slice(2)?.[1] || ".";
|
|
569
|
-
const files = import_fast_glob.default.sync(`${srcDir}/**/*.md`, { ignore: ["node_modules"] });
|
|
570
|
-
const limit = pLimit(+(import_node_process.default.env.P_LIMT_MAX || import_node_os.default.cpus().length));
|
|
416
|
+
async function getArticles(cfg, vpConfig) {
|
|
417
|
+
const srcDir = cfg?.srcDir || vpConfig.srcDir.replace(vpConfig.root, "").replace(/^\//, "") || import_node_process.default.argv.slice(2)?.[1] || ".";
|
|
418
|
+
const files = import_fast_glob.default.sync(`${srcDir}/**/*.md`, { ignore: ["node_modules"], absolute: true });
|
|
571
419
|
const metaResults = files.reduce((prev, curr) => {
|
|
572
|
-
const route = getPageRoute(curr, srcDir);
|
|
573
|
-
const metaPromise =
|
|
420
|
+
const route = getPageRoute(curr, vpConfig.srcDir);
|
|
421
|
+
const metaPromise = getArticleMeta(curr, route, cfg?.timeZone);
|
|
574
422
|
prev[curr] = {
|
|
575
423
|
route,
|
|
576
424
|
metaPromise
|
|
@@ -614,6 +462,7 @@ var import_node_fs2 = require("fs");
|
|
|
614
462
|
var import_node_buffer = require("buffer");
|
|
615
463
|
var import_vitepress_plugin_pagefind = require("vitepress-plugin-pagefind");
|
|
616
464
|
var import_vitepress_plugin_rss = require("vitepress-plugin-rss");
|
|
465
|
+
var import_theme_shared3 = require("@sugarat/theme-shared");
|
|
617
466
|
|
|
618
467
|
// src/utils/node/hot-reload-plugin.ts
|
|
619
468
|
function themeReloadPlugin() {
|
|
@@ -666,7 +515,7 @@ function themeReloadPlugin() {
|
|
|
666
515
|
}
|
|
667
516
|
|
|
668
517
|
// src/utils/node/vitePlugins.ts
|
|
669
|
-
function getVitePlugins(cfg) {
|
|
518
|
+
function getVitePlugins(cfg = {}) {
|
|
670
519
|
const plugins = [];
|
|
671
520
|
plugins.push(coverImgTransform());
|
|
672
521
|
plugins.push(themeReloadPlugin());
|
|
@@ -675,11 +524,7 @@ function getVitePlugins(cfg) {
|
|
|
675
524
|
const ops = cfg.search instanceof Object ? cfg.search : {};
|
|
676
525
|
plugins.push(
|
|
677
526
|
(0, import_vitepress_plugin_pagefind.pagefindPlugin)({
|
|
678
|
-
...ops
|
|
679
|
-
customSearchQuery: import_vitepress_plugin_pagefind.chineseSearchOptimize,
|
|
680
|
-
filter(searchItem) {
|
|
681
|
-
return searchItem.meta.publish !== false;
|
|
682
|
-
}
|
|
527
|
+
...ops
|
|
683
528
|
})
|
|
684
529
|
);
|
|
685
530
|
}
|
|
@@ -689,7 +534,8 @@ function getVitePlugins(cfg) {
|
|
|
689
534
|
plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : cfg?.mermaid ?? {}));
|
|
690
535
|
}
|
|
691
536
|
if (cfg?.RSS) {
|
|
692
|
-
|
|
537
|
+
;
|
|
538
|
+
[cfg?.RSS].flat().forEach((rssConfig) => plugins.push((0, import_vitepress_plugin_rss.RssPlugin)(rssConfig)));
|
|
693
539
|
}
|
|
694
540
|
return plugins;
|
|
695
541
|
}
|
|
@@ -742,7 +588,7 @@ function coverImgTransform() {
|
|
|
742
588
|
const fileBuffer = (0, import_node_fs2.readFileSync)(realPath);
|
|
743
589
|
const matchAsset = assetsMap.find((v) => import_node_buffer.Buffer.compare(fileBuffer, v.source) === 0);
|
|
744
590
|
if (matchAsset) {
|
|
745
|
-
page.meta.cover = joinPath("/", matchAsset.fileName);
|
|
591
|
+
page.meta.cover = (0, import_theme_shared3.joinPath)("/", matchAsset.fileName);
|
|
746
592
|
}
|
|
747
593
|
} catch (e) {
|
|
748
594
|
vitepressConfig.logger.warn(e?.message);
|
|
@@ -755,7 +601,7 @@ function providePageData(cfg) {
|
|
|
755
601
|
return {
|
|
756
602
|
name: "@sugarat/theme-plugin-provide-page-data",
|
|
757
603
|
async config(config) {
|
|
758
|
-
const pagesData = await getArticles(cfg);
|
|
604
|
+
const pagesData = await getArticles(cfg, config.vitepress);
|
|
759
605
|
config.vitepress.site.themeConfig.blog.pagesData = pagesData;
|
|
760
606
|
}
|
|
761
607
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "简约风的 Vitepress 博客主题,sugarat vitepress blog theme",
|
|
5
5
|
"author": "sugar",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,9 +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",
|
|
46
45
|
"fast-glob": "^3.3.2",
|
|
47
|
-
"gray-matter": "^4.0.3",
|
|
48
46
|
"markdown-it-task-checkbox": "^1.0.6",
|
|
49
47
|
"mermaid": "^10.9.0",
|
|
50
48
|
"oh-my-live2d": "^0.19.3",
|
|
@@ -52,14 +50,14 @@
|
|
|
52
50
|
"vitepress-markdown-timeline": "^1.2.1",
|
|
53
51
|
"vitepress-plugin-mermaid": "2.0.13",
|
|
54
52
|
"vitepress-plugin-tabs": "0.2.0",
|
|
55
|
-
"vitepress-plugin-
|
|
56
|
-
"vitepress-plugin-
|
|
53
|
+
"vitepress-plugin-rss": "0.2.7",
|
|
54
|
+
"vitepress-plugin-pagefind": "0.4.2",
|
|
55
|
+
"@sugarat/theme-shared": "0.0.1"
|
|
57
56
|
},
|
|
58
57
|
"devDependencies": {
|
|
59
58
|
"@element-plus/icons-vue": "^2.3.1",
|
|
60
59
|
"artalk": "^2.8.5",
|
|
61
60
|
"element-plus": "^2.7.2",
|
|
62
|
-
"p-limit": "4",
|
|
63
61
|
"pagefind": "^1.1.0",
|
|
64
62
|
"sass": "^1.76.0",
|
|
65
63
|
"typescript": "^5.4.5",
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
UserFilled
|
|
13
13
|
} from '@element-plus/icons-vue'
|
|
14
14
|
import { useBlogConfig, useCurrentArticle, useDocMetaInsertPosition, useDocMetaInsertSelector } from '../composables/config/blog'
|
|
15
|
-
import countWord, { formatShowDate } from '../utils/client'
|
|
15
|
+
import countWord, { formatDate, formatShowDate } from '../utils/client'
|
|
16
16
|
import type { Theme } from '../composables/config'
|
|
17
17
|
import BlogDocCover from './BlogDocCover.vue'
|
|
18
18
|
|
|
@@ -108,6 +108,10 @@ const publishDate = computed(() => {
|
|
|
108
108
|
return formatShowDate(currentArticle.value?.meta?.date || '')
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
+
const hoverDate = computed(() => {
|
|
112
|
+
return currentArticle.value?.meta?.date ? `: ${formatDate(currentArticle.value?.meta?.date)}` : ''
|
|
113
|
+
})
|
|
114
|
+
|
|
111
115
|
const timeTitle = computed(() =>
|
|
112
116
|
frontmatter.value.date ? '发布时间' : '最近修改时间'
|
|
113
117
|
)
|
|
@@ -164,7 +168,7 @@ watch(
|
|
|
164
168
|
{{ author }}
|
|
165
169
|
</template>
|
|
166
170
|
</span>
|
|
167
|
-
<span v-if="publishDate && !hiddenTime" class="publishDate" :title="timeTitle">
|
|
171
|
+
<span v-if="publishDate && !hiddenTime" class="publishDate" :title="timeTitle + hoverDate">
|
|
168
172
|
<ElIcon><Clock /></ElIcon>
|
|
169
173
|
{{ publishDate }}
|
|
170
174
|
</span>
|
|
@@ -102,7 +102,7 @@ const resultCover = computed(() => {
|
|
|
102
102
|
<div class="badge-list mobile-visible">
|
|
103
103
|
<span v-show="author" class="split">{{ author }}</span>
|
|
104
104
|
<span class="split">{{ showTime }}</span>
|
|
105
|
-
<span v-
|
|
105
|
+
<span v-if="tag?.length" class="split">{{ tag?.join(' · ') }}</span>
|
|
106
106
|
</div>
|
|
107
107
|
</a>
|
|
108
108
|
</template>
|
|
@@ -5,6 +5,7 @@ import type { RSSOptions } from 'vitepress-plugin-rss'
|
|
|
5
5
|
import type { Mapping, Repo } from '@giscus/vue'
|
|
6
6
|
import type { Options as Oml2dOptions } from 'oh-my-live2d'
|
|
7
7
|
import type { Ref } from 'vue'
|
|
8
|
+
import type { PagefindConfig } from 'vitepress-plugin-pagefind'
|
|
8
9
|
|
|
9
10
|
type RSSPluginOptions = RSSOptions
|
|
10
11
|
|
|
@@ -347,19 +348,8 @@ export namespace Theme {
|
|
|
347
348
|
top?: number
|
|
348
349
|
}
|
|
349
350
|
export type SearchConfig =
|
|
350
|
-
|
|
|
351
|
-
|
|
|
352
|
-
| {
|
|
353
|
-
btnPlaceholder?: string
|
|
354
|
-
placeholder?: string
|
|
355
|
-
emptyText?: string
|
|
356
|
-
/**
|
|
357
|
-
* @example
|
|
358
|
-
* 'Total: {{searchResult}} search results.'
|
|
359
|
-
*/
|
|
360
|
-
heading?: string
|
|
361
|
-
mode?: boolean | 'pagefind'
|
|
362
|
-
}
|
|
351
|
+
| false
|
|
352
|
+
| PagefindConfig
|
|
363
353
|
|
|
364
354
|
export interface UserWorks {
|
|
365
355
|
title: string
|
|
@@ -434,7 +424,7 @@ export namespace Theme {
|
|
|
434
424
|
/**
|
|
435
425
|
* 启用RSS配置
|
|
436
426
|
*/
|
|
437
|
-
RSS?: RSSOptions
|
|
427
|
+
RSS?: RSSOptions | RSSOptions[]
|
|
438
428
|
/**
|
|
439
429
|
* 首页页脚
|
|
440
430
|
*/
|
package/src/utils/node/index.ts
CHANGED
|
@@ -1,100 +1,5 @@
|
|
|
1
|
-
/* eslint-disable global-require */
|
|
2
|
-
/* eslint-disable prefer-rest-params */
|
|
3
|
-
import { spawn, spawnSync } from 'node:child_process'
|
|
4
1
|
import path from 'node:path'
|
|
5
|
-
|
|
6
|
-
export function clearMatterContent(content: string) {
|
|
7
|
-
let first___: unknown
|
|
8
|
-
let second___: unknown
|
|
9
|
-
|
|
10
|
-
const lines = content.split('\n').reduce<string[]>((pre, line) => {
|
|
11
|
-
// 移除开头的空白行
|
|
12
|
-
if (!line.trim() && pre.length === 0) {
|
|
13
|
-
return pre
|
|
14
|
-
}
|
|
15
|
-
if (line.trim() === '---') {
|
|
16
|
-
if (first___ === undefined) {
|
|
17
|
-
first___ = pre.length
|
|
18
|
-
}
|
|
19
|
-
else if (second___ === undefined) {
|
|
20
|
-
second___ = pre.length
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
pre.push(line)
|
|
24
|
-
return pre
|
|
25
|
-
}, [])
|
|
26
|
-
return (
|
|
27
|
-
lines
|
|
28
|
-
// 剔除---之间的内容
|
|
29
|
-
.slice((second___ as number) || 0)
|
|
30
|
-
.join('\n')
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function getDefaultTitle(content: string) {
|
|
35
|
-
const match = content.match(/^(#+)\s+(.+)/m)
|
|
36
|
-
return match?.[2] || ''
|
|
37
|
-
}
|
|
38
|
-
|
|
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
|
|
44
|
-
}
|
|
45
|
-
|
|
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
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getGitTimestamp(file: string) {
|
|
63
|
-
return new Promise((resolve, reject) => {
|
|
64
|
-
const child = spawn('git', ['log', '-1', '--pretty="%ci"', file])
|
|
65
|
-
let output = ''
|
|
66
|
-
child.stdout.on('data', (d) => {
|
|
67
|
-
output += String(d)
|
|
68
|
-
})
|
|
69
|
-
child.on('close', () => {
|
|
70
|
-
resolve(+new Date(output))
|
|
71
|
-
})
|
|
72
|
-
child.on('error', reject)
|
|
73
|
-
})
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function getTextSummary(text: string, count = 100) {
|
|
77
|
-
return (
|
|
78
|
-
text
|
|
79
|
-
// 首个标题
|
|
80
|
-
?.replace(/^#+\s+.*/, '')
|
|
81
|
-
// 除去标题
|
|
82
|
-
?.replace(/#/g, '')
|
|
83
|
-
// 除去图片
|
|
84
|
-
?.replace(/!\[.*?\]\(.*?\)/g, '')
|
|
85
|
-
// 除去链接
|
|
86
|
-
?.replace(/\[(.*?)\]\(.*?\)/g, '$1')
|
|
87
|
-
// 除去加粗
|
|
88
|
-
?.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
89
|
-
?.split('\n')
|
|
90
|
-
?.filter(v => !!v)
|
|
91
|
-
?.join('\n')
|
|
92
|
-
?.replace(/>(.*)/, '')
|
|
93
|
-
?.replace(/</g, '<').replace(/>/g, '>')
|
|
94
|
-
?.trim()
|
|
95
|
-
?.slice(0, count)
|
|
96
|
-
)
|
|
97
|
-
}
|
|
2
|
+
import { joinPath } from '@sugarat/theme-shared'
|
|
98
3
|
|
|
99
4
|
export function aliasObjectToArray(obj: Record<string, string>) {
|
|
100
5
|
return Object.entries(obj).map(([find, replacement]) => ({
|
|
@@ -103,21 +8,6 @@ export function aliasObjectToArray(obj: Record<string, string>) {
|
|
|
103
8
|
}))
|
|
104
9
|
}
|
|
105
10
|
|
|
106
|
-
export const EXTERNAL_URL_RE = /^[a-z]+:/i
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Join two paths by resolving the slash collision.
|
|
110
|
-
*/
|
|
111
|
-
export function joinPath(base: string, path: string): string {
|
|
112
|
-
return `${base}${path}`.replace(/\/+/g, '/')
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function withBase(base: string, path: string) {
|
|
116
|
-
return EXTERNAL_URL_RE.test(path) || path.startsWith('.')
|
|
117
|
-
? path
|
|
118
|
-
: joinPath(base, path)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
11
|
function isBase64ImageURL(url: string) {
|
|
122
12
|
// Base64 图片链接的格式为 data:image/[image format];base64,[Base64 编码的数据]
|
|
123
13
|
const regex = /^data:image\/[a-z]+;base64,/
|
package/src/utils/node/theme.ts
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
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'
|
|
6
5
|
import glob from 'fast-glob'
|
|
7
|
-
import
|
|
8
|
-
import
|
|
6
|
+
import { getDefaultTitle, getFileLastModifyTime, getTextSummary, grayMatter, normalizePath } from '@sugarat/theme-shared'
|
|
7
|
+
import type { SiteConfig } from 'vitepress'
|
|
9
8
|
import type { Theme } from '../../composables/config/index'
|
|
10
9
|
import { formatDate } from '../client'
|
|
11
|
-
import {
|
|
10
|
+
import { getFirstImagURLFromMD } from './index'
|
|
12
11
|
|
|
13
12
|
export function patchDefaultThemeSideBar(cfg?: Partial<Theme.BlogConfig>) {
|
|
14
13
|
return cfg?.blog !== false && cfg?.recommend !== false
|
|
@@ -24,29 +23,8 @@ export function patchDefaultThemeSideBar(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
24
23
|
}
|
|
25
24
|
|
|
26
25
|
export function getPageRoute(filepath: string, srcDir: string) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
// TODO:优化 路径处理,同VitePress 内部一致
|
|
30
|
-
if (route.startsWith('./')) {
|
|
31
|
-
route = route.replace(
|
|
32
|
-
new RegExp(
|
|
33
|
-
`^\\.\\/${path
|
|
34
|
-
.join(srcDir, '/')
|
|
35
|
-
.replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
|
|
36
|
-
),
|
|
37
|
-
''
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
route = route.replace(
|
|
42
|
-
new RegExp(
|
|
43
|
-
`^${path
|
|
44
|
-
.join(srcDir, '/')
|
|
45
|
-
.replace(new RegExp(`\\${path.sep}`, 'g'), '/')}`
|
|
46
|
-
),
|
|
47
|
-
''
|
|
48
|
-
)
|
|
49
|
-
}
|
|
26
|
+
const route = normalizePath(path.relative(srcDir, filepath))
|
|
27
|
+
.replace(/\.md$/, '')
|
|
50
28
|
return `/${route}`
|
|
51
29
|
}
|
|
52
30
|
|
|
@@ -54,7 +32,7 @@ const defaultTimeZoneOffset = new Date().getTimezoneOffset() / -60
|
|
|
54
32
|
export async function getArticleMeta(filepath: string, route: string, timeZone = defaultTimeZoneOffset) {
|
|
55
33
|
const fileContent = await fs.promises.readFile(filepath, 'utf-8')
|
|
56
34
|
|
|
57
|
-
const { data: frontmatter, excerpt, content } =
|
|
35
|
+
const { data: frontmatter, excerpt, content } = grayMatter(fileContent, {
|
|
58
36
|
excerpt: true,
|
|
59
37
|
})
|
|
60
38
|
|
|
@@ -68,11 +46,10 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
|
|
|
68
46
|
const date = await (
|
|
69
47
|
(meta.date
|
|
70
48
|
&& new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`))
|
|
71
|
-
||
|
|
49
|
+
|| getFileLastModifyTime(filepath)
|
|
72
50
|
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
51
|
+
// 无法获取时兜底当前时间
|
|
52
|
+
meta.date = formatDate(date || new Date())
|
|
76
53
|
|
|
77
54
|
// 处理tags和categories,兼容历史文章
|
|
78
55
|
meta.categories
|
|
@@ -103,14 +80,16 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
|
|
|
103
80
|
}
|
|
104
81
|
return meta as Theme.PageMeta
|
|
105
82
|
}
|
|
106
|
-
export async function getArticles(cfg
|
|
107
|
-
const srcDir
|
|
108
|
-
|
|
109
|
-
|
|
83
|
+
export async function getArticles(cfg: Partial<Theme.BlogConfig>, vpConfig: SiteConfig) {
|
|
84
|
+
const srcDir
|
|
85
|
+
= cfg?.srcDir || vpConfig.srcDir.replace(vpConfig.root, '').replace(/^\//, '')
|
|
86
|
+
|| process.argv.slice(2)?.[1]
|
|
87
|
+
|| '.'
|
|
88
|
+
const files = glob.sync(`${srcDir}/**/*.md`, { ignore: ['node_modules'], absolute: true })
|
|
110
89
|
|
|
111
90
|
const metaResults = files.reduce((prev, curr) => {
|
|
112
|
-
const route = getPageRoute(curr, srcDir)
|
|
113
|
-
const metaPromise =
|
|
91
|
+
const route = getPageRoute(curr, vpConfig.srcDir)
|
|
92
|
+
const metaPromise = getArticleMeta(curr, route, cfg?.timeZone)
|
|
114
93
|
|
|
115
94
|
// 提前获取,有缓存取缓存
|
|
116
95
|
prev[curr] = {
|
|
@@ -2,20 +2,18 @@ import path from 'node:path'
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { Buffer } from 'node:buffer'
|
|
4
4
|
import type { SiteConfig } from 'vitepress'
|
|
5
|
-
|
|
6
5
|
import {
|
|
7
|
-
chineseSearchOptimize,
|
|
8
6
|
pagefindPlugin
|
|
9
7
|
} from 'vitepress-plugin-pagefind'
|
|
10
8
|
import { RssPlugin } from 'vitepress-plugin-rss'
|
|
11
9
|
import type { PluginOption } from 'vite'
|
|
10
|
+
import { joinPath } from '@sugarat/theme-shared'
|
|
12
11
|
import type { Theme } from '../../composables/config/index'
|
|
13
12
|
import { _require } from './mdPlugins'
|
|
14
13
|
import { themeReloadPlugin } from './hot-reload-plugin'
|
|
15
14
|
import { getArticles } from './theme'
|
|
16
|
-
import { joinPath } from './index'
|
|
17
15
|
|
|
18
|
-
export function getVitePlugins(cfg
|
|
16
|
+
export function getVitePlugins(cfg: Partial<Theme.BlogConfig> = {}) {
|
|
19
17
|
const plugins: any[] = []
|
|
20
18
|
|
|
21
19
|
// const buildEndFn: any[] = []
|
|
@@ -37,10 +35,6 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
37
35
|
plugins.push(
|
|
38
36
|
pagefindPlugin({
|
|
39
37
|
...ops,
|
|
40
|
-
customSearchQuery: chineseSearchOptimize,
|
|
41
|
-
filter(searchItem) {
|
|
42
|
-
return searchItem.meta.publish !== false
|
|
43
|
-
}
|
|
44
38
|
})
|
|
45
39
|
)
|
|
46
40
|
}
|
|
@@ -54,7 +48,7 @@ export function getVitePlugins(cfg?: Partial<Theme.BlogConfig>) {
|
|
|
54
48
|
|
|
55
49
|
// 内置支持RSS
|
|
56
50
|
if (cfg?.RSS) {
|
|
57
|
-
plugins.push(RssPlugin(
|
|
51
|
+
;[cfg?.RSS].flat().forEach(rssConfig => plugins.push(RssPlugin(rssConfig)))
|
|
58
52
|
}
|
|
59
53
|
return plugins
|
|
60
54
|
}
|
|
@@ -153,11 +147,11 @@ export function coverImgTransform() {
|
|
|
153
147
|
}
|
|
154
148
|
}
|
|
155
149
|
|
|
156
|
-
export function providePageData(cfg
|
|
150
|
+
export function providePageData(cfg: Partial<Theme.BlogConfig>) {
|
|
157
151
|
return {
|
|
158
152
|
name: '@sugarat/theme-plugin-provide-page-data',
|
|
159
153
|
async config(config: any) {
|
|
160
|
-
const pagesData = await getArticles(cfg)
|
|
154
|
+
const pagesData = await getArticles(cfg, config.vitepress)
|
|
161
155
|
config.vitepress.site.themeConfig.blog.pagesData = pagesData
|
|
162
156
|
},
|
|
163
157
|
} as PluginOption
|