@sugarat/theme 0.4.3 → 0.4.5
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 +4 -13
- package/node.js +56 -193
- package/package.json +4 -6
- package/src/components/BlogItem.vue +1 -1
- package/src/composables/config/index.ts +5 -15
- package/src/utils/node/index.ts +1 -129
- package/src/utils/node/theme.ts +15 -35
- package/src/utils/node/vitePlugins.ts +42 -16
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;
|
|
@@ -347,7 +338,7 @@ declare namespace Theme {
|
|
|
347
338
|
/**
|
|
348
339
|
* 内置一些主题色
|
|
349
340
|
* @default 'vp-default'
|
|
350
|
-
* 也可以自定义颜色,详见
|
|
341
|
+
* 也可以自定义颜色,详见 https://theme.sugarat.top/config/style.html#%E4%B8%BB%E9%A2%98%E8%89%B2
|
|
351
342
|
*/
|
|
352
343
|
themeColor?: ThemeColor;
|
|
353
344
|
pagesData: PageData[];
|
|
@@ -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,59 +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
|
-
var
|
|
214
|
-
function getDefaultTitle(content) {
|
|
215
|
-
const match = content.match(/^(#+)\s+(.+)/m);
|
|
216
|
-
return match?.[2] || "";
|
|
217
|
-
}
|
|
218
|
-
var cache = /* @__PURE__ */ new Map();
|
|
219
|
-
function getFileBirthTime(url) {
|
|
220
|
-
const cached = cache.get(url);
|
|
221
|
-
if (cached) {
|
|
222
|
-
return cached;
|
|
223
|
-
}
|
|
224
|
-
return new Promise((resolve) => {
|
|
225
|
-
const child = (0, import_node_child_process.spawn)("git", ["log", "-1", '--pretty="%ai"', url]);
|
|
226
|
-
let output = "";
|
|
227
|
-
child.stdout.on("data", (d) => output += String(d));
|
|
228
|
-
child.on("close", async () => {
|
|
229
|
-
let date;
|
|
230
|
-
if (output.trim()) {
|
|
231
|
-
date = new Date(output);
|
|
232
|
-
} else {
|
|
233
|
-
date = await getFileBirthTimeByFs(url);
|
|
234
|
-
}
|
|
235
|
-
cache.set(url, date);
|
|
236
|
-
resolve(date);
|
|
237
|
-
});
|
|
238
|
-
child.on("error", async () => {
|
|
239
|
-
const fsDate = await getFileBirthTimeByFs(url);
|
|
240
|
-
resolve(fsDate);
|
|
241
|
-
});
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
async function getFileBirthTimeByFs(url) {
|
|
245
|
-
try {
|
|
246
|
-
const fsStat = await import_node_fs.default.promises.stat(url);
|
|
247
|
-
return fsStat.birthtime;
|
|
248
|
-
} catch {
|
|
249
|
-
return void 0;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function getTextSummary(text, count = 100) {
|
|
253
|
-
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);
|
|
254
|
-
}
|
|
212
|
+
var import_theme_shared = require("@sugarat/theme-shared");
|
|
255
213
|
function aliasObjectToArray(obj) {
|
|
256
214
|
return Object.entries(obj).map(([find, replacement]) => ({
|
|
257
215
|
find,
|
|
258
216
|
replacement
|
|
259
217
|
}));
|
|
260
218
|
}
|
|
261
|
-
function joinPath(base, path4) {
|
|
262
|
-
return `${base}${path4}`.replace(/\/+/g, "/");
|
|
263
|
-
}
|
|
264
219
|
function isBase64ImageURL(url) {
|
|
265
220
|
const regex = /^data:image\/[a-z]+;base64,/;
|
|
266
221
|
return regex.test(url);
|
|
@@ -275,10 +230,10 @@ function getFirstImagURLFromMD(content, route) {
|
|
|
275
230
|
if (isHTTPSource || isBase64ImageURL(url)) {
|
|
276
231
|
return url;
|
|
277
232
|
}
|
|
278
|
-
const paths = joinPath("/", route).split("/");
|
|
233
|
+
const paths = (0, import_theme_shared.joinPath)("/", route).split("/");
|
|
279
234
|
paths.splice(paths.length - 1, 1);
|
|
280
235
|
const relativePath = url.startsWith("/") ? url : import_node_path.default.join(paths.join("/") || "", url);
|
|
281
|
-
return joinPath("/", relativePath);
|
|
236
|
+
return (0, import_theme_shared.joinPath)("/", relativePath);
|
|
282
237
|
}
|
|
283
238
|
function debounce(func, delay = 1e3) {
|
|
284
239
|
let timeoutId;
|
|
@@ -373,115 +328,11 @@ function patchOptimizeDeps(config) {
|
|
|
373
328
|
}
|
|
374
329
|
|
|
375
330
|
// src/utils/node/theme.ts
|
|
376
|
-
var
|
|
331
|
+
var import_node_fs = __toESM(require("fs"));
|
|
377
332
|
var import_node_path2 = __toESM(require("path"));
|
|
378
333
|
var import_node_process = __toESM(require("process"));
|
|
379
|
-
var import_node_os = __toESM(require("os"));
|
|
380
334
|
var import_fast_glob = __toESM(require("fast-glob"));
|
|
381
|
-
var
|
|
382
|
-
|
|
383
|
-
// ../../node_modules/.pnpm/yocto-queue@1.0.0/node_modules/yocto-queue/index.js
|
|
384
|
-
var Node = class {
|
|
385
|
-
value;
|
|
386
|
-
next;
|
|
387
|
-
constructor(value) {
|
|
388
|
-
this.value = value;
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
var Queue = class {
|
|
392
|
-
#head;
|
|
393
|
-
#tail;
|
|
394
|
-
#size;
|
|
395
|
-
constructor() {
|
|
396
|
-
this.clear();
|
|
397
|
-
}
|
|
398
|
-
enqueue(value) {
|
|
399
|
-
const node = new Node(value);
|
|
400
|
-
if (this.#head) {
|
|
401
|
-
this.#tail.next = node;
|
|
402
|
-
this.#tail = node;
|
|
403
|
-
} else {
|
|
404
|
-
this.#head = node;
|
|
405
|
-
this.#tail = node;
|
|
406
|
-
}
|
|
407
|
-
this.#size++;
|
|
408
|
-
}
|
|
409
|
-
dequeue() {
|
|
410
|
-
const current = this.#head;
|
|
411
|
-
if (!current) {
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
this.#head = this.#head.next;
|
|
415
|
-
this.#size--;
|
|
416
|
-
return current.value;
|
|
417
|
-
}
|
|
418
|
-
clear() {
|
|
419
|
-
this.#head = void 0;
|
|
420
|
-
this.#tail = void 0;
|
|
421
|
-
this.#size = 0;
|
|
422
|
-
}
|
|
423
|
-
get size() {
|
|
424
|
-
return this.#size;
|
|
425
|
-
}
|
|
426
|
-
*[Symbol.iterator]() {
|
|
427
|
-
let current = this.#head;
|
|
428
|
-
while (current) {
|
|
429
|
-
yield current.value;
|
|
430
|
-
current = current.next;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
// ../../node_modules/.pnpm/p-limit@4.0.0/node_modules/p-limit/index.js
|
|
436
|
-
function pLimit(concurrency) {
|
|
437
|
-
if (!((Number.isInteger(concurrency) || concurrency === Number.POSITIVE_INFINITY) && concurrency > 0)) {
|
|
438
|
-
throw new TypeError("Expected `concurrency` to be a number from 1 and up");
|
|
439
|
-
}
|
|
440
|
-
const queue = new Queue();
|
|
441
|
-
let activeCount = 0;
|
|
442
|
-
const next = () => {
|
|
443
|
-
activeCount--;
|
|
444
|
-
if (queue.size > 0) {
|
|
445
|
-
queue.dequeue()();
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
const run = async (fn, resolve, args) => {
|
|
449
|
-
activeCount++;
|
|
450
|
-
const result = (async () => fn(...args))();
|
|
451
|
-
resolve(result);
|
|
452
|
-
try {
|
|
453
|
-
await result;
|
|
454
|
-
} catch {
|
|
455
|
-
}
|
|
456
|
-
next();
|
|
457
|
-
};
|
|
458
|
-
const enqueue = (fn, resolve, args) => {
|
|
459
|
-
queue.enqueue(run.bind(void 0, fn, resolve, args));
|
|
460
|
-
(async () => {
|
|
461
|
-
await Promise.resolve();
|
|
462
|
-
if (activeCount < concurrency && queue.size > 0) {
|
|
463
|
-
queue.dequeue()();
|
|
464
|
-
}
|
|
465
|
-
})();
|
|
466
|
-
};
|
|
467
|
-
const generator = (fn, ...args) => new Promise((resolve) => {
|
|
468
|
-
enqueue(fn, resolve, args);
|
|
469
|
-
});
|
|
470
|
-
Object.defineProperties(generator, {
|
|
471
|
-
activeCount: {
|
|
472
|
-
get: () => activeCount
|
|
473
|
-
},
|
|
474
|
-
pendingCount: {
|
|
475
|
-
get: () => queue.size
|
|
476
|
-
},
|
|
477
|
-
clearQueue: {
|
|
478
|
-
value: () => {
|
|
479
|
-
queue.clear();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
return generator;
|
|
484
|
-
}
|
|
335
|
+
var import_theme_shared2 = require("@sugarat/theme-shared");
|
|
485
336
|
|
|
486
337
|
// src/utils/client/index.ts
|
|
487
338
|
function formatDate(d, fmt = "yyyy-MM-dd hh:mm:ss") {
|
|
@@ -532,44 +383,29 @@ function patchDefaultThemeSideBar(cfg) {
|
|
|
532
383
|
} : void 0;
|
|
533
384
|
}
|
|
534
385
|
function getPageRoute(filepath, srcDir) {
|
|
535
|
-
|
|
536
|
-
if (route.startsWith("./")) {
|
|
537
|
-
route = route.replace(
|
|
538
|
-
new RegExp(
|
|
539
|
-
`^\\.\\/${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
|
|
540
|
-
),
|
|
541
|
-
""
|
|
542
|
-
);
|
|
543
|
-
} else {
|
|
544
|
-
route = route.replace(
|
|
545
|
-
new RegExp(
|
|
546
|
-
`^${import_node_path2.default.join(srcDir, "/").replace(new RegExp(`\\${import_node_path2.default.sep}`, "g"), "/")}`
|
|
547
|
-
),
|
|
548
|
-
""
|
|
549
|
-
);
|
|
550
|
-
}
|
|
386
|
+
const route = (0, import_theme_shared2.normalizePath)(import_node_path2.default.relative(srcDir, filepath)).replace(/\.md$/, "");
|
|
551
387
|
return `/${route}`;
|
|
552
388
|
}
|
|
553
389
|
var defaultTimeZoneOffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() / -60;
|
|
554
390
|
async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset) {
|
|
555
|
-
const fileContent = await
|
|
556
|
-
const { data: frontmatter, excerpt, content } = (0,
|
|
391
|
+
const fileContent = await import_node_fs.default.promises.readFile(filepath, "utf-8");
|
|
392
|
+
const { data: frontmatter, excerpt, content } = (0, import_theme_shared2.grayMatter)(fileContent, {
|
|
557
393
|
excerpt: true
|
|
558
394
|
});
|
|
559
395
|
const meta = {
|
|
560
396
|
...frontmatter
|
|
561
397
|
};
|
|
562
398
|
if (!meta.title) {
|
|
563
|
-
meta.title = getDefaultTitle(content);
|
|
399
|
+
meta.title = (0, import_theme_shared2.getDefaultTitle)(content);
|
|
564
400
|
}
|
|
565
|
-
const date = await (meta.date && /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`) ||
|
|
401
|
+
const date = await (meta.date && /* @__PURE__ */ new Date(`${new Date(meta.date).toUTCString()}+${timeZone}`) || (0, import_theme_shared2.getFileLastModifyTime)(filepath));
|
|
566
402
|
meta.date = formatDate(date || /* @__PURE__ */ new Date());
|
|
567
403
|
meta.categories = typeof meta.categories === "string" ? [meta.categories] : meta.categories;
|
|
568
404
|
meta.tags = typeof meta.tags === "string" ? [meta.tags] : meta.tags;
|
|
569
405
|
meta.tag = [meta.tag || []].flat().concat([
|
|
570
406
|
.../* @__PURE__ */ new Set([...meta.categories || [], ...meta.tags || []])
|
|
571
407
|
]);
|
|
572
|
-
meta.description = meta.description || getTextSummary(content, 100) || excerpt;
|
|
408
|
+
meta.description = meta.description || (0, import_theme_shared2.getTextSummary)(content, 100) || excerpt;
|
|
573
409
|
meta.cover = meta.cover ?? getFirstImagURLFromMD(fileContent, route);
|
|
574
410
|
if (meta.publish === false) {
|
|
575
411
|
meta.hidden = true;
|
|
@@ -577,13 +413,12 @@ async function getArticleMeta(filepath, route, timeZone = defaultTimeZoneOffset)
|
|
|
577
413
|
}
|
|
578
414
|
return meta;
|
|
579
415
|
}
|
|
580
|
-
async function getArticles(cfg) {
|
|
581
|
-
const srcDir = cfg?.srcDir || import_node_process.default.argv.slice(2)?.[1] || ".";
|
|
582
|
-
const files = import_fast_glob.default.sync(`${srcDir}/**/*.md`, { ignore: ["node_modules"] });
|
|
583
|
-
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 });
|
|
584
419
|
const metaResults = files.reduce((prev, curr) => {
|
|
585
|
-
const route = getPageRoute(curr, srcDir);
|
|
586
|
-
const metaPromise =
|
|
420
|
+
const route = getPageRoute(curr, vpConfig.srcDir);
|
|
421
|
+
const metaPromise = getArticleMeta(curr, route, cfg?.timeZone);
|
|
587
422
|
prev[curr] = {
|
|
588
423
|
route,
|
|
589
424
|
metaPromise
|
|
@@ -623,10 +458,11 @@ function checkConfig(cfg) {
|
|
|
623
458
|
|
|
624
459
|
// src/utils/node/vitePlugins.ts
|
|
625
460
|
var import_node_path3 = __toESM(require("path"));
|
|
626
|
-
var
|
|
461
|
+
var import_node_fs2 = require("fs");
|
|
627
462
|
var import_node_buffer = require("buffer");
|
|
628
463
|
var import_vitepress_plugin_pagefind = require("vitepress-plugin-pagefind");
|
|
629
464
|
var import_vitepress_plugin_rss = require("vitepress-plugin-rss");
|
|
465
|
+
var import_theme_shared3 = require("@sugarat/theme-shared");
|
|
630
466
|
|
|
631
467
|
// src/utils/node/hot-reload-plugin.ts
|
|
632
468
|
function themeReloadPlugin() {
|
|
@@ -679,20 +515,19 @@ function themeReloadPlugin() {
|
|
|
679
515
|
}
|
|
680
516
|
|
|
681
517
|
// src/utils/node/vitePlugins.ts
|
|
682
|
-
function getVitePlugins(cfg) {
|
|
518
|
+
function getVitePlugins(cfg = {}) {
|
|
683
519
|
const plugins = [];
|
|
684
520
|
plugins.push(coverImgTransform());
|
|
521
|
+
if (cfg.themeColor) {
|
|
522
|
+
plugins.push(setThemeScript(cfg.themeColor));
|
|
523
|
+
}
|
|
685
524
|
plugins.push(themeReloadPlugin());
|
|
686
525
|
plugins.push(providePageData(cfg));
|
|
687
526
|
if (cfg && cfg.search !== false) {
|
|
688
527
|
const ops = cfg.search instanceof Object ? cfg.search : {};
|
|
689
528
|
plugins.push(
|
|
690
529
|
(0, import_vitepress_plugin_pagefind.pagefindPlugin)({
|
|
691
|
-
...ops
|
|
692
|
-
customSearchQuery: import_vitepress_plugin_pagefind.chineseSearchOptimize,
|
|
693
|
-
filter(searchItem) {
|
|
694
|
-
return searchItem.meta.publish !== false;
|
|
695
|
-
}
|
|
530
|
+
...ops
|
|
696
531
|
})
|
|
697
532
|
);
|
|
698
533
|
}
|
|
@@ -702,7 +537,8 @@ function getVitePlugins(cfg) {
|
|
|
702
537
|
plugins.push(MermaidPlugin(cfg?.mermaid === true ? {} : cfg?.mermaid ?? {}));
|
|
703
538
|
}
|
|
704
539
|
if (cfg?.RSS) {
|
|
705
|
-
|
|
540
|
+
;
|
|
541
|
+
[cfg?.RSS].flat().forEach((rssConfig) => plugins.push((0, import_vitepress_plugin_rss.RssPlugin)(rssConfig)));
|
|
706
542
|
}
|
|
707
543
|
return plugins;
|
|
708
544
|
}
|
|
@@ -749,13 +585,13 @@ function coverImgTransform() {
|
|
|
749
585
|
}
|
|
750
586
|
try {
|
|
751
587
|
const realPath = import_node_path3.default.join(vitepressConfig.root, cover);
|
|
752
|
-
if (!(0,
|
|
588
|
+
if (!(0, import_node_fs2.existsSync)(realPath)) {
|
|
753
589
|
continue;
|
|
754
590
|
}
|
|
755
|
-
const fileBuffer = (0,
|
|
591
|
+
const fileBuffer = (0, import_node_fs2.readFileSync)(realPath);
|
|
756
592
|
const matchAsset = assetsMap.find((v) => import_node_buffer.Buffer.compare(fileBuffer, v.source) === 0);
|
|
757
593
|
if (matchAsset) {
|
|
758
|
-
page.meta.cover = joinPath("/", matchAsset.fileName);
|
|
594
|
+
page.meta.cover = (0, import_theme_shared3.joinPath)("/", matchAsset.fileName);
|
|
759
595
|
}
|
|
760
596
|
} catch (e) {
|
|
761
597
|
vitepressConfig.logger.warn(e?.message);
|
|
@@ -768,11 +604,38 @@ function providePageData(cfg) {
|
|
|
768
604
|
return {
|
|
769
605
|
name: "@sugarat/theme-plugin-provide-page-data",
|
|
770
606
|
async config(config) {
|
|
771
|
-
const pagesData = await getArticles(cfg);
|
|
607
|
+
const pagesData = await getArticles(cfg, config.vitepress);
|
|
772
608
|
config.vitepress.site.themeConfig.blog.pagesData = pagesData;
|
|
773
609
|
}
|
|
774
610
|
};
|
|
775
611
|
}
|
|
612
|
+
function setThemeScript(themeColor) {
|
|
613
|
+
let resolveConfig;
|
|
614
|
+
const pluginOps = {
|
|
615
|
+
name: "@sugarat/theme-plugin-theme-color-script",
|
|
616
|
+
enforce: "pre",
|
|
617
|
+
configResolved(config) {
|
|
618
|
+
if (resolveConfig) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
resolveConfig = config;
|
|
622
|
+
const vitepressConfig = config.vitepress;
|
|
623
|
+
if (!vitepressConfig) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
const selfTransformHead = vitepressConfig.transformHead;
|
|
627
|
+
vitepressConfig.transformHead = async (ctx) => {
|
|
628
|
+
const selfHead = await Promise.resolve(selfTransformHead?.(ctx)) || [];
|
|
629
|
+
return selfHead.concat([
|
|
630
|
+
["script", { type: "text/javascript" }, `;(function() {
|
|
631
|
+
document.documentElement.setAttribute("theme", "${themeColor}");
|
|
632
|
+
})()`]
|
|
633
|
+
]);
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
return pluginOps;
|
|
638
|
+
}
|
|
776
639
|
|
|
777
640
|
// src/node.ts
|
|
778
641
|
function getThemeConfig(cfg = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sugarat/theme",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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
|
-
"
|
|
56
|
-
"vitepress-plugin-rss": "0.2.
|
|
53
|
+
"@sugarat/theme-shared": "0.0.1",
|
|
54
|
+
"vitepress-plugin-rss": "0.2.7",
|
|
55
|
+
"vitepress-plugin-pagefind": "0.4.2"
|
|
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",
|
|
@@ -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
|
|
@@ -381,7 +371,7 @@ export namespace Theme {
|
|
|
381
371
|
/**
|
|
382
372
|
* 内置一些主题色
|
|
383
373
|
* @default 'vp-default'
|
|
384
|
-
* 也可以自定义颜色,详见
|
|
374
|
+
* 也可以自定义颜色,详见 https://theme.sugarat.top/config/style.html#%E4%B8%BB%E9%A2%98%E8%89%B2
|
|
385
375
|
*/
|
|
386
376
|
themeColor?: ThemeColor
|
|
387
377
|
pagesData: PageData[]
|
|
@@ -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,118 +1,5 @@
|
|
|
1
|
-
/* eslint-disable global-require */
|
|
2
|
-
/* eslint-disable prefer-rest-params */
|
|
3
|
-
import { spawn } from 'node:child_process'
|
|
4
1
|
import path from 'node:path'
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
export function clearMatterContent(content: string) {
|
|
8
|
-
let first___: unknown
|
|
9
|
-
let second___: unknown
|
|
10
|
-
|
|
11
|
-
const lines = content.split('\n').reduce<string[]>((pre, line) => {
|
|
12
|
-
// 移除开头的空白行
|
|
13
|
-
if (!line.trim() && pre.length === 0) {
|
|
14
|
-
return pre
|
|
15
|
-
}
|
|
16
|
-
if (line.trim() === '---') {
|
|
17
|
-
if (first___ === undefined) {
|
|
18
|
-
first___ = pre.length
|
|
19
|
-
}
|
|
20
|
-
else if (second___ === undefined) {
|
|
21
|
-
second___ = pre.length
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
pre.push(line)
|
|
25
|
-
return pre
|
|
26
|
-
}, [])
|
|
27
|
-
return (
|
|
28
|
-
lines
|
|
29
|
-
// 剔除---之间的内容
|
|
30
|
-
.slice((second___ as number) || 0)
|
|
31
|
-
.join('\n')
|
|
32
|
-
)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function getDefaultTitle(content: string) {
|
|
36
|
-
const match = content.match(/^(#+)\s+(.+)/m)
|
|
37
|
-
return match?.[2] || ''
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const cache = new Map<string, Date | undefined>()
|
|
41
|
-
export function getFileBirthTime(url: string): Promise<Date | undefined> | Date {
|
|
42
|
-
const cached = cache.get(url)
|
|
43
|
-
if (cached) {
|
|
44
|
-
return cached
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return new Promise((resolve) => {
|
|
48
|
-
// 使用异步回调
|
|
49
|
-
const child = spawn('git', ['log', '-1', '--pretty="%ai"', url])
|
|
50
|
-
let output = ''
|
|
51
|
-
child.stdout.on('data', d => (output += String(d)))
|
|
52
|
-
child.on('close', async () => {
|
|
53
|
-
let date: Date | undefined
|
|
54
|
-
if (output.trim()) {
|
|
55
|
-
date = new Date(output)
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
date = await getFileBirthTimeByFs(url)
|
|
59
|
-
}
|
|
60
|
-
cache.set(url, date)
|
|
61
|
-
resolve(date)
|
|
62
|
-
})
|
|
63
|
-
child.on('error', async () => {
|
|
64
|
-
const fsDate = await getFileBirthTimeByFs(url)
|
|
65
|
-
resolve(fsDate)
|
|
66
|
-
})
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function getFileBirthTimeByFs(url: string) {
|
|
71
|
-
try {
|
|
72
|
-
const fsStat = await fs.promises.stat(url)
|
|
73
|
-
return fsStat.birthtime
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return undefined
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function getGitTimestamp(file: string) {
|
|
81
|
-
return new Promise((resolve, reject) => {
|
|
82
|
-
const child = spawn('git', ['log', '-1', '--pretty="%ci"', file])
|
|
83
|
-
let output = ''
|
|
84
|
-
child.stdout.on('data', (d) => {
|
|
85
|
-
output += String(d)
|
|
86
|
-
})
|
|
87
|
-
child.on('close', () => {
|
|
88
|
-
resolve(+new Date(output))
|
|
89
|
-
})
|
|
90
|
-
child.on('error', reject)
|
|
91
|
-
})
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function getTextSummary(text: string, count = 100) {
|
|
95
|
-
return (
|
|
96
|
-
text
|
|
97
|
-
// 首个标题
|
|
98
|
-
?.replace(/^#+\s+.*/, '')
|
|
99
|
-
// 除去标题
|
|
100
|
-
?.replace(/#/g, '')
|
|
101
|
-
// 除去图片
|
|
102
|
-
?.replace(/!\[.*?\]\(.*?\)/g, '')
|
|
103
|
-
// 除去链接
|
|
104
|
-
?.replace(/\[(.*?)\]\(.*?\)/g, '$1')
|
|
105
|
-
// 除去加粗
|
|
106
|
-
?.replace(/\*\*(.*?)\*\*/g, '$1')
|
|
107
|
-
?.split('\n')
|
|
108
|
-
?.filter(v => !!v)
|
|
109
|
-
?.join('\n')
|
|
110
|
-
?.replace(/>(.*)/, '')
|
|
111
|
-
?.replace(/</g, '<').replace(/>/g, '>')
|
|
112
|
-
?.trim()
|
|
113
|
-
?.slice(0, count)
|
|
114
|
-
)
|
|
115
|
-
}
|
|
2
|
+
import { joinPath } from '@sugarat/theme-shared'
|
|
116
3
|
|
|
117
4
|
export function aliasObjectToArray(obj: Record<string, string>) {
|
|
118
5
|
return Object.entries(obj).map(([find, replacement]) => ({
|
|
@@ -121,21 +8,6 @@ export function aliasObjectToArray(obj: Record<string, string>) {
|
|
|
121
8
|
}))
|
|
122
9
|
}
|
|
123
10
|
|
|
124
|
-
export const EXTERNAL_URL_RE = /^[a-z]+:/i
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Join two paths by resolving the slash collision.
|
|
128
|
-
*/
|
|
129
|
-
export function joinPath(base: string, path: string): string {
|
|
130
|
-
return `${base}${path}`.replace(/\/+/g, '/')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function withBase(base: string, path: string) {
|
|
134
|
-
return EXTERNAL_URL_RE.test(path) || path.startsWith('.')
|
|
135
|
-
? path
|
|
136
|
-
: joinPath(base, path)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
11
|
function isBase64ImageURL(url: string) {
|
|
140
12
|
// Base64 图片链接的格式为 data:image/[image format];base64,[Base64 编码的数据]
|
|
141
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,7 +46,7 @@ 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
51
|
// 无法获取时兜底当前时间
|
|
74
52
|
meta.date = formatDate(date || new Date())
|
|
@@ -102,14 +80,16 @@ export async function getArticleMeta(filepath: string, route: string, timeZone =
|
|
|
102
80
|
}
|
|
103
81
|
return meta as Theme.PageMeta
|
|
104
82
|
}
|
|
105
|
-
export async function getArticles(cfg
|
|
106
|
-
const srcDir
|
|
107
|
-
|
|
108
|
-
|
|
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 })
|
|
109
89
|
|
|
110
90
|
const metaResults = files.reduce((prev, curr) => {
|
|
111
|
-
const route = getPageRoute(curr, srcDir)
|
|
112
|
-
const metaPromise =
|
|
91
|
+
const route = getPageRoute(curr, vpConfig.srcDir)
|
|
92
|
+
const metaPromise = getArticleMeta(curr, route, cfg?.timeZone)
|
|
113
93
|
|
|
114
94
|
// 提前获取,有缓存取缓存
|
|
115
95
|
prev[curr] = {
|
|
@@ -1,30 +1,28 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs'
|
|
3
3
|
import { Buffer } from 'node:buffer'
|
|
4
|
-
import type { SiteConfig } from 'vitepress'
|
|
5
|
-
|
|
4
|
+
import type { HeadConfig, SiteConfig } from 'vitepress'
|
|
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
|
-
// const buildEndFn: any[] = []
|
|
22
|
-
// Build完后运行的一系列列方法,执行自定义的 buildEnd 钩子
|
|
23
|
-
// plugins.push(inlineBuildEndPlugin(buildEndFn))
|
|
24
|
-
|
|
25
19
|
// 处理cover image的路径(暂只支持自动识别的文章首图)
|
|
26
20
|
plugins.push(coverImgTransform())
|
|
27
21
|
|
|
22
|
+
// 处理自定义主题色
|
|
23
|
+
if (cfg.themeColor) {
|
|
24
|
+
plugins.push(setThemeScript(cfg.themeColor))
|
|
25
|
+
}
|
|
28
26
|
// 自动重载首页
|
|
29
27
|
plugins.push(themeReloadPlugin())
|
|
30
28
|
|
|
@@ -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,12 +147,44 @@ 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
|
|
164
158
|
}
|
|
159
|
+
|
|
160
|
+
export function setThemeScript(
|
|
161
|
+
themeColor: Theme.ThemeColor
|
|
162
|
+
) {
|
|
163
|
+
let resolveConfig: any
|
|
164
|
+
const pluginOps: PluginOption = {
|
|
165
|
+
name: '@sugarat/theme-plugin-theme-color-script',
|
|
166
|
+
enforce: 'pre',
|
|
167
|
+
configResolved(config: any) {
|
|
168
|
+
if (resolveConfig) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
resolveConfig = config
|
|
172
|
+
|
|
173
|
+
const vitepressConfig: SiteConfig = config.vitepress
|
|
174
|
+
if (!vitepressConfig) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
// 通过 head 添加额外的脚本注入
|
|
178
|
+
const selfTransformHead = vitepressConfig.transformHead
|
|
179
|
+
vitepressConfig.transformHead = async (ctx) => {
|
|
180
|
+
const selfHead = (await Promise.resolve(selfTransformHead?.(ctx))) || []
|
|
181
|
+
return selfHead.concat([
|
|
182
|
+
['script', { type: 'text/javascript' }, `;(function() {
|
|
183
|
+
document.documentElement.setAttribute("theme", "${themeColor}");
|
|
184
|
+
})()`]
|
|
185
|
+
] as HeadConfig[])
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return pluginOps
|
|
190
|
+
}
|