2905 lines
96 KiB
JavaScript
2905 lines
96 KiB
JavaScript
import { deepMerge, hasOwn, isArray, isEmptyObject, isNumber, isPlainObject, isString, promiseParallel, random, sleep, toArray, uniq } from "@pengzhanbo/utils";
|
||
import chokidar, { watch } from "chokidar";
|
||
import { createFilter } from "create-filter";
|
||
import grayMatter from "gray-matter";
|
||
import yaml from "js-yaml";
|
||
import { colors, fs, getDirname, hash, importFileDefault, path, templateRenderer } from "vuepress/utils";
|
||
import fs$1, { constants, promises } from "node:fs";
|
||
import path$1, { resolve } from "node:path";
|
||
import process from "node:process";
|
||
import { pathToFileURL } from "node:url";
|
||
import { build } from "esbuild";
|
||
import fs$2 from "node:fs/promises";
|
||
import { genSaltSync, hashSync } from "bcrypt-ts";
|
||
import { createHash } from "node:crypto";
|
||
import { customAlphabet } from "nanoid";
|
||
import { Logger, addViteConfig, addViteOptimizeDepsExclude, addViteOptimizeDepsInclude, addViteSsrNoExternal, ensureEndingSlash, ensureLeadingSlash, entries, fromEntries, getFullLocaleConfig, isArray as isArray$1, isBoolean, isFunction, isLinkAbsolute, isLinkHttp, isLinkWithProtocol, isPlainObject as isPlainObject$1, removeLeadingSlash } from "@vuepress/helper";
|
||
import fg from "fast-glob";
|
||
import { isPlainObject as isPlainObject$2, resolveLocalePath } from "vuepress/shared";
|
||
import { isPackageExists } from "local-pkg";
|
||
import dayjs from "dayjs";
|
||
import { getUserAgent, resolveCommand } from "package-manager-detector";
|
||
import { createPage } from "vuepress/core";
|
||
import { copyCodePlugin } from "@vuepress/plugin-copy-code";
|
||
import { shikiPlugin } from "@vuepress/plugin-shiki";
|
||
import { createCodeTabIconGetter, markdownPowerPlugin, resolveImageSize } from "vuepress-plugin-md-power";
|
||
import { markdownChartPlugin } from "@vuepress/plugin-markdown-chart";
|
||
import { markdownHintPlugin } from "@vuepress/plugin-markdown-hint";
|
||
import { markdownImagePlugin } from "@vuepress/plugin-markdown-image";
|
||
import { markdownIncludePlugin } from "@vuepress/plugin-markdown-include";
|
||
import { markdownMathPlugin } from "@vuepress/plugin-markdown-math";
|
||
import { fontsPlugin } from "@vuepress-plume/plugin-fonts";
|
||
import { searchPlugin } from "@vuepress-plume/plugin-search";
|
||
import { cachePlugin } from "@vuepress/plugin-cache";
|
||
import { commentPlugin } from "@vuepress/plugin-comment";
|
||
import { docsearchPlugin } from "@vuepress/plugin-docsearch";
|
||
import { nprogressPlugin } from "@vuepress/plugin-nprogress";
|
||
import { photoSwipePlugin } from "@vuepress/plugin-photo-swipe";
|
||
import { readingTimePlugin } from "@vuepress/plugin-reading-time";
|
||
import { replaceAssetsPlugin } from "@vuepress/plugin-replace-assets";
|
||
import { seoPlugin } from "@vuepress/plugin-seo";
|
||
import { sitemapPlugin } from "@vuepress/plugin-sitemap";
|
||
import { watermarkPlugin } from "@vuepress/plugin-watermark";
|
||
import { gitPlugin } from "@vuepress/plugin-git";
|
||
import { getIconContentCSS, getIconData } from "@iconify/utils";
|
||
|
||
export * from "../shared/index.js"
|
||
|
||
//#region src/node/utils/constants.ts
|
||
const THEME_NAME = "vuepress-theme-plume";
|
||
|
||
//#endregion
|
||
//#region src/node/utils/createFsCache.ts
|
||
const CACHE_BASE = "markdown";
|
||
function createFsCache(app, name) {
|
||
const filepath = app.dir.cache(`${CACHE_BASE}/${name}.json`);
|
||
const cache$3 = {
|
||
hash: "",
|
||
data: null
|
||
};
|
||
const read = async () => {
|
||
if (!cache$3.data) try {
|
||
const content = await fs$2.readFile(filepath, "utf-8");
|
||
if (content) {
|
||
const res = JSON.parse(content);
|
||
cache$3.data = res.data ?? null;
|
||
cache$3.hash = hash(res.hash || "");
|
||
}
|
||
} catch {}
|
||
return cache$3.data;
|
||
};
|
||
let timer = null;
|
||
const write = async (data) => {
|
||
const currentHash = hash(data);
|
||
if (cache$3.hash && currentHash === cache$3.hash) return;
|
||
cache$3.data = data;
|
||
cache$3.hash = currentHash;
|
||
timer && clearTimeout(timer);
|
||
timer = setTimeout(async () => {
|
||
await fs$2.mkdir(path$1.dirname(filepath), { recursive: true });
|
||
await fs$2.writeFile(filepath, JSON.stringify(cache$3), "utf-8");
|
||
}, 300);
|
||
};
|
||
return {
|
||
get hash() {
|
||
return cache$3.hash;
|
||
},
|
||
get data() {
|
||
return cache$3.data;
|
||
},
|
||
read,
|
||
write
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/encrypt.ts
|
||
function genEncrypt(pwd) {
|
||
return hashSync(String(pwd), genSaltSync(random(8, 16)));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/hash.ts
|
||
const hash$1 = (content) => createHash("md5").update(content).digest("hex");
|
||
const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 8);
|
||
|
||
//#endregion
|
||
//#region src/node/utils/interopDefault.ts
|
||
async function interopDefault(m) {
|
||
const resolved = await m;
|
||
return resolved.default || resolved;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/logger.ts
|
||
const logger = new Logger(THEME_NAME);
|
||
var Perf = class {
|
||
isDebug = false;
|
||
collect = {};
|
||
init(isDebug = false) {
|
||
this.isDebug = isDebug;
|
||
}
|
||
mark(mark) {
|
||
this.collect[mark] = performance.now();
|
||
}
|
||
log(mark) {
|
||
const startTime = this.collect[mark];
|
||
if (!this.isDebug || !startTime) return;
|
||
logger.info("[perf spent time] ", `${colors.green(mark)}: ${colors.cyan(`${(performance.now() - startTime).toFixed(2)}ms`)}`);
|
||
}
|
||
};
|
||
const perf = new Perf();
|
||
|
||
//#endregion
|
||
//#region src/node/utils/path.ts
|
||
const __dirname = getDirname(import.meta.url);
|
||
const resolve$1 = (...args) => path.resolve(__dirname, "../", ...args);
|
||
const templates = (url) => resolve$1("../templates", url);
|
||
const RE_SLASH = /(\\|\/)+/g;
|
||
function normalizePath(path$2) {
|
||
return path$2.replace(RE_SLASH, "/");
|
||
}
|
||
function pathJoin(...args) {
|
||
return normalizePath(path.join(...args));
|
||
}
|
||
function normalizeLink(base, link = "") {
|
||
return isLinkAbsolute(link) || isLinkWithProtocol(link) ? link : ensureLeadingSlash(normalizePath(`${base}/${link}/`));
|
||
}
|
||
const RE_START_END_SLASH = /^\/|\/$/g;
|
||
function getCurrentDirname(basePath, filepath) {
|
||
const dirList = normalizePath(basePath || path.dirname(filepath)).replace(RE_START_END_SLASH, "").split("/");
|
||
return dirList.length > 0 ? dirList[dirList.length - 1] : "";
|
||
}
|
||
function withBase(path$2 = "", base = "/") {
|
||
path$2 = ensureEndingSlash(ensureLeadingSlash(path$2));
|
||
if (path$2.startsWith(base)) return normalizePath(path$2);
|
||
return normalizePath(`${base}${path$2}`);
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/package.ts
|
||
function readJsonFileAsync(filePath) {
|
||
try {
|
||
const content = fs.readFileSync(filePath, "utf-8");
|
||
return JSON.parse(content);
|
||
} catch {}
|
||
return {};
|
||
}
|
||
function getPackage() {
|
||
return readJsonFileAsync(path.join(process.cwd(), "package.json"));
|
||
}
|
||
function getThemePackage() {
|
||
return readJsonFileAsync(resolve$1("../package.json"));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/resolveContent.ts
|
||
function resolveContent(app, { name, content, before, after }) {
|
||
content = `${before ? `${before}\n` : ""}export const ${name} = ${JSON.stringify(content)}${after ? `\n${after}` : ""}`;
|
||
if (app.env.isDev) {
|
||
const func = `update${name[0].toUpperCase()}${name.slice(1)}`;
|
||
content += `\n
|
||
if (import.meta.webpackHot) {
|
||
import.meta.webpackHot.accept()
|
||
if (__VUE_HMR_RUNTIME__.${func}) {
|
||
__VUE_HMR_RUNTIME__.${func}(${name})
|
||
}
|
||
}
|
||
|
||
if (import.meta.hot) {
|
||
import.meta.hot.accept(({ ${name} }) => {
|
||
__VUE_HMR_RUNTIME__.${func}(${name})
|
||
})
|
||
}
|
||
`;
|
||
}
|
||
return content;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/translate.ts
|
||
let lang = "en";
|
||
function setTranslateLang(current) {
|
||
if ([
|
||
"zh-CN",
|
||
"zh",
|
||
"zh-Hans",
|
||
"zh-Hant"
|
||
].includes(current)) lang = "zh";
|
||
else lang = "en";
|
||
}
|
||
function createTranslate(locales) {
|
||
return function t$4(key, data) {
|
||
const resolved = locales[lang][key];
|
||
if (!resolved) return String(key);
|
||
if (data && !isEmptyObject(data)) return resolved.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key$1) => data[key$1] || _);
|
||
return resolved;
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/utils/writeTemp.ts
|
||
const contentHash$1 = /* @__PURE__ */ new Map();
|
||
async function writeTemp(app, filepath, content) {
|
||
const currentHash = hash$1(content);
|
||
if (!contentHash$1.has(filepath) || contentHash$1.get(filepath) !== currentHash) {
|
||
contentHash$1.set(filepath, currentHash);
|
||
await app.writeTemp(filepath, content);
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/loadConfig/compiler.ts
|
||
async function compiler(configPath) {
|
||
if (!configPath) return {
|
||
config: {},
|
||
dependencies: []
|
||
};
|
||
const dirnameVarName = "__vite_injected_original_dirname";
|
||
const filenameVarName = "__vite_injected_original_filename";
|
||
const importMetaUrlVarName = "__vite_injected_original_import_meta_url";
|
||
const result = await build({
|
||
absWorkingDir: process.cwd(),
|
||
entryPoints: [configPath],
|
||
outfile: "out.js",
|
||
write: false,
|
||
target: [`node${process.versions.node}`],
|
||
platform: "node",
|
||
bundle: true,
|
||
format: "esm",
|
||
mainFields: ["main"],
|
||
sourcemap: "inline",
|
||
metafile: true,
|
||
define: {
|
||
"__dirname": dirnameVarName,
|
||
"__filename": filenameVarName,
|
||
"import.meta.url": importMetaUrlVarName,
|
||
"import.meta.dirname": dirnameVarName,
|
||
"import.meta.filename": filenameVarName
|
||
},
|
||
plugins: [{
|
||
name: "externalize-deps",
|
||
setup(build$1) {
|
||
build$1.onResolve({ filter: /.*/ }, ({ path: id }) => {
|
||
if (id[0] !== "." && !path$1.isAbsolute(id)) return { external: true };
|
||
return null;
|
||
});
|
||
}
|
||
}, {
|
||
name: "inject-file-scope-variables",
|
||
setup(build$1) {
|
||
build$1.onLoad({ filter: /\.[cm]?[jt]s$/ }, async (args) => {
|
||
const contents = await promises.readFile(args.path, "utf-8");
|
||
const injectValues = `const ${dirnameVarName} = ${JSON.stringify(path$1.dirname(args.path))};const ${filenameVarName} = ${JSON.stringify(args.path)};const ${importMetaUrlVarName} = ${JSON.stringify(pathToFileURL(args.path).href)};`;
|
||
return {
|
||
loader: args.path.endsWith("ts") ? "ts" : "js",
|
||
contents: injectValues + contents
|
||
};
|
||
});
|
||
}
|
||
}]
|
||
});
|
||
const { text } = result.outputFiles[0];
|
||
const tempFilePath = `${configPath}.${hash$1(text)}.mjs`;
|
||
let config;
|
||
try {
|
||
await promises.writeFile(tempFilePath, text);
|
||
config = await importFileDefault(tempFilePath);
|
||
} finally {
|
||
await promises.rm(tempFilePath);
|
||
}
|
||
return {
|
||
config,
|
||
dependencies: Object.keys(result.metafile?.inputs ?? {})
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/loadConfig/findConfigPath.ts
|
||
const CONFIG_FILE_NAME = "plume.config";
|
||
const extensions = [
|
||
"ts",
|
||
"js",
|
||
"mjs",
|
||
"cjs",
|
||
"mts",
|
||
"cts"
|
||
];
|
||
async function findConfigPath(app, configPath) {
|
||
const cwd = process.cwd();
|
||
const source = app.dir.source(".vuepress");
|
||
const paths = [];
|
||
if (configPath) {
|
||
const path$2 = resolve(cwd, configPath);
|
||
if (existsSync(path$2) && (await promises.stat(path$2)).isFile()) return path$2;
|
||
}
|
||
extensions.forEach((ext) => paths.push(resolve(cwd, `${source}/${CONFIG_FILE_NAME}.${ext}`), resolve(cwd, `./${CONFIG_FILE_NAME}.${ext}`), resolve(cwd, `./.vuepress/${CONFIG_FILE_NAME}.${ext}`)));
|
||
let current;
|
||
for (const path$2 of paths) if (existsSync(path$2) && (await promises.stat(path$2)).isFile()) {
|
||
current = path$2;
|
||
break;
|
||
}
|
||
if (configPath && current) logger.warn(`Can not find config file: ${colors.gray(configPath)}\nUse config file: ${colors.gray(current)}`);
|
||
return current;
|
||
}
|
||
function existsSync(fp) {
|
||
try {
|
||
fs$1.accessSync(fp, constants.R_OK);
|
||
return true;
|
||
} catch {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/locales/de.ts
|
||
const deLocale = {
|
||
selectLanguageName: "Deutsch",
|
||
selectLanguageText: "Sprache auswählen",
|
||
appearanceText: "Erscheinungsbild",
|
||
lightModeSwitchTitle: "Zu hellem Thema wechseln",
|
||
darkModeSwitchTitle: "Zu dunklem Thema wechseln",
|
||
outlineLabel: "Inhalt dieser Seite",
|
||
returnToTopLabel: "Zurück nach oben",
|
||
editLinkText: "Diese Seite bearbeiten",
|
||
contributorsText: "Mitwirkende",
|
||
prevPageLabel: "Vorherige Seite",
|
||
nextPageLabel: "Nächste Seite",
|
||
lastUpdatedText: "Zuletzt aktualisiert am",
|
||
changelogText: "Änderungsprotokoll",
|
||
changelogOnText: "am",
|
||
changelogButtonText: "Alle Änderungen anzeigen",
|
||
copyrightText: "Alle Rechte vorbehalten",
|
||
copyrightAuthorText: "Urheberrecht liegt bei:",
|
||
copyrightCreationOriginalText: "Originalartikel:",
|
||
copyrightCreationTranslateText: "Übersetzt aus:",
|
||
copyrightCreationReprintText: "Nachdruck von:",
|
||
copyrightLicenseText: "Lizenz:",
|
||
notFound: {
|
||
code: "404",
|
||
title: "Seite nicht gefunden",
|
||
quote: "Aber wenn du deine Richtung nicht änderst und weiter suchst, könntest du schließlich dorthin gelangen, wohin du gehen willst.",
|
||
linkText: "Zur Startseite"
|
||
},
|
||
homeText: "Startseite",
|
||
blogText: "Blog",
|
||
tagText: "Tag",
|
||
archiveText: "Archiv",
|
||
categoryText: "Kategorie",
|
||
archiveTotalText: "{count} Beiträge",
|
||
encryptButtonText: "Bestätigen",
|
||
encryptPlaceholder: "Bitte Passwort eingeben",
|
||
encryptGlobalText: "Diese Website ist nur mit Passwort zugänglich",
|
||
encryptPageText: "Diese Seite ist nur mit Passwort zugänglich",
|
||
footer: { message: "Unterstützt von <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const dePresetLocale = {
|
||
"CC0": "CC0 1.0 Universell",
|
||
"CC-BY-4.0": "Namensnennung 4.0 International",
|
||
"CC-BY-NC-4.0": "Namensnennung-Nicht kommerziell 4.0 International",
|
||
"CC-BY-NC-SA-4.0": "Namensnennung-Nicht kommerziell-Weitergabe unter gleichen Bedingungen 4.0 International",
|
||
"CC-BY-NC-ND-4.0": "Namensnennung-Nicht kommerziell-Keine Bearbeitung 4.0 International",
|
||
"CC-BY-ND-4.0": "Namensnennung-Keine Bearbeitung 4.0 International",
|
||
"CC-BY-SA-4.0": "Namensnennung-Weitergabe unter gleichen Bedingungen 4.0 International"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/en.ts
|
||
const enLocale = {
|
||
selectLanguageName: "English",
|
||
selectLanguageText: "Languages",
|
||
appearanceText: "Appearance",
|
||
lightModeSwitchTitle: "Switch to light theme",
|
||
darkModeSwitchTitle: "Switch to dark theme",
|
||
editLinkText: "Edit this page",
|
||
contributorsText: "Contributors",
|
||
lastUpdatedText: "Last Updated",
|
||
changelogText: "Changelog",
|
||
changelogOnText: "On",
|
||
changelogButtonText: "View All Changelog",
|
||
copyrightText: "Copyright",
|
||
copyrightAuthorText: "Copyright Ownership:",
|
||
copyrightCreationOriginalText: "This article link:",
|
||
copyrightCreationTranslateText: "This article is translated from:",
|
||
copyrightCreationReprintText: "This article is reprint from:",
|
||
copyrightLicenseText: "License under:",
|
||
encryptButtonText: "Confirm",
|
||
encryptPlaceholder: "Enter password",
|
||
encryptGlobalText: "Only password can access this site",
|
||
encryptPageText: "Only password can access this page",
|
||
homeText: "Home",
|
||
blogText: "Blog",
|
||
tagText: "Tags",
|
||
archiveText: "Archives",
|
||
categoryText: "Categories",
|
||
archiveTotalText: "{count} articles",
|
||
footer: { message: "Powered by <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const enPresetLocale = {
|
||
"CC0": "CC0 1.0 Universal",
|
||
"CC-BY-4.0": "Attribution 4.0 International",
|
||
"CC-BY-NC-4.0": "Attribution-NonCommercial 4.0 International",
|
||
"CC-BY-NC-SA-4.0": "Attribution-NonCommercial-ShareAlike 4.0 International",
|
||
"CC-BY-NC-ND-4.0": "Attribution-NonCommercial-NoDerivatives 4.0 International",
|
||
"CC-BY-ND-4.0": "Attribution-NoDerivatives 4.0 International",
|
||
"CC-BY-SA-4.0": "Attribution-ShareAlike 4.0 International"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/fr.ts
|
||
const frLocale = {
|
||
selectLanguageName: "Français",
|
||
selectLanguageText: "Choisir la langue",
|
||
appearanceText: "Apparence",
|
||
lightModeSwitchTitle: "Passer au thème clair",
|
||
darkModeSwitchTitle: "Passer au thème sombre",
|
||
outlineLabel: "Contenu de cette page",
|
||
returnToTopLabel: "Retour en haut",
|
||
editLinkText: "Modifier cette page",
|
||
contributorsText: "Contributeurs",
|
||
prevPageLabel: "Page précédente",
|
||
nextPageLabel: "Page suivante",
|
||
lastUpdatedText: "Dernière mise à jour",
|
||
changelogText: "Historique des changements",
|
||
changelogOnText: "le",
|
||
changelogButtonText: "Voir tout l'historique des changements",
|
||
copyrightText: "Tous droits réservés",
|
||
copyrightAuthorText: "Copyright appartenant à :",
|
||
copyrightCreationOriginalText: "Lien de l'article :",
|
||
copyrightCreationTranslateText: "Traduit de :",
|
||
copyrightCreationReprintText: "Reproduit de :",
|
||
copyrightLicenseText: "Licence :",
|
||
notFound: {
|
||
code: "404",
|
||
title: "Page non trouvée",
|
||
quote: "Mais si tu ne changes pas de direction et que tu continues à chercher, tu finiras par arriver à destination.",
|
||
linkText: "Retour à l'accueil"
|
||
},
|
||
homeText: "Accueil",
|
||
blogText: "Blog",
|
||
tagText: "Étiquette",
|
||
archiveText: "Archives",
|
||
categoryText: "Catégorie",
|
||
archiveTotalText: "{count} articles",
|
||
encryptButtonText: "Confirmer",
|
||
encryptPlaceholder: "Veuillez entrer le mot de passe",
|
||
encryptGlobalText: "Ce site n'est accessible qu'avec un mot de passe",
|
||
encryptPageText: "Cette page n'est accessible qu'avec un mot de passe",
|
||
footer: { message: "Propulsé par <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const frPresetLocale = {
|
||
"CC0": "CC0 1.0 Universel",
|
||
"CC-BY-4.0": "Attribution 4.0 International",
|
||
"CC-BY-NC-4.0": "Attribution-Pas d'Utilisation Commerciale 4.0 International",
|
||
"CC-BY-NC-SA-4.0": "Attribution-Pas d'Utilisation Commerciale-Partage dans les Mêmes Conditions 4.0 International",
|
||
"CC-BY-NC-ND-4.0": "Attribution-Pas d'Utilisation Commerciale-Pas de Modification 4.0 International",
|
||
"CC-BY-ND-4.0": "Attribution-Pas de Modification 4.0 International",
|
||
"CC-BY-SA-4.0": "Attribution-Partage dans les Mêmes Conditions 4.0 International"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/ja.ts
|
||
const jaLocale = {
|
||
selectLanguageName: "日本語",
|
||
selectLanguageText: "言語を選択",
|
||
appearanceText: "外観",
|
||
lightModeSwitchTitle: "ライトモードに切り替え",
|
||
darkModeSwitchTitle: "ダークモードに切り替え",
|
||
outlineLabel: "このページの内容",
|
||
returnToTopLabel: "トップに戻る",
|
||
editLinkText: "このページを編集",
|
||
contributorsText: "貢献者",
|
||
prevPageLabel: "前のページ",
|
||
nextPageLabel: "次のページ",
|
||
lastUpdatedText: "最終更新日",
|
||
changelogText: "変更履歴",
|
||
changelogOnText: "に",
|
||
changelogButtonText: "すべての変更履歴を見る",
|
||
copyrightText: "著作権",
|
||
copyrightAuthorText: "著作権者:",
|
||
copyrightCreationOriginalText: "本文リンク:",
|
||
copyrightCreationTranslateText: "本文の翻訳元:",
|
||
copyrightCreationReprintText: "本文の転載元:",
|
||
copyrightLicenseText: "ライセンス:",
|
||
notFound: {
|
||
code: "404",
|
||
title: "ページが見つかりません",
|
||
quote: "しかし、方向を変えずに探し続ければ、最終的には行きたい場所にたどり着くかもしれません。",
|
||
linkText: "ホームに戻る"
|
||
},
|
||
homeText: "ホーム",
|
||
blogText: "ブログ",
|
||
tagText: "タグ",
|
||
archiveText: "アーカイブ",
|
||
categoryText: "カテゴリー",
|
||
archiveTotalText: "{count} 件",
|
||
encryptButtonText: "確認",
|
||
encryptPlaceholder: "パスワードを入力してください",
|
||
encryptGlobalText: "このサイトはパスワードでのみアクセス可能です",
|
||
encryptPageText: "このページはパスワードでのみアクセス可能です",
|
||
footer: { message: "<a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a> によって提供されています" }
|
||
};
|
||
const jaPresetLocale = {
|
||
"CC0": "CC0 1.0 パブリックドメイン",
|
||
"CC-BY-4.0": "表示 4.0 国際",
|
||
"CC-BY-NC-4.0": "表示-非営利 4.0 国際",
|
||
"CC-BY-NC-SA-4.0": "表示-非営利-継承 4.0 国際",
|
||
"CC-BY-NC-ND-4.0": "表示-非営利-改変禁止 4.0 国際",
|
||
"CC-BY-ND-4.0": "表示-改変禁止 4.0 国際",
|
||
"CC-BY-SA-4.0": "表示-継承 4.0 国際"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/ko.ts
|
||
const koLocale = {
|
||
selectLanguageName: "한국어",
|
||
selectLanguageText: "",
|
||
appearanceText: "모양",
|
||
lightModeSwitchTitle: "밝은 테마로 전환",
|
||
darkModeSwitchTitle: "어두운 테마로 전환",
|
||
sidebarMenuLabel: "메뉴",
|
||
returnToTopLabel: "위로 이동",
|
||
outlineLabel: "목차",
|
||
editLinkText: "편집하기",
|
||
contributorsText: "기여자",
|
||
lastUpdatedText: "마지막 업데이트",
|
||
changelogText: "변경 내역",
|
||
changelogOnText: "On",
|
||
changelogButtonText: "변경 내역 모두 보기",
|
||
prevPageLabel: "이전 페이지",
|
||
nextPageLabel: "다음 페이지",
|
||
copyrightText: "Copyright",
|
||
copyrightAuthorText: "저작권 소유자:",
|
||
copyrightCreationOriginalText: "This article link:",
|
||
copyrightCreationTranslateText: "This article is translated from:",
|
||
copyrightCreationReprintText: "This article is reprint from:",
|
||
copyrightLicenseText: "License under:",
|
||
encryptButtonText: "확인",
|
||
encryptPlaceholder: "비밀번호를 입력하세요",
|
||
encryptGlobalText: "이 사이트를 이용하려면 비밀번호가 필요합니다",
|
||
encryptPageText: "이 페이지를 이용하려면 비밀번호가 필요합니다",
|
||
homeText: "홈",
|
||
blogText: "블로그",
|
||
tagText: "태그",
|
||
archiveText: "아카이브",
|
||
categoryText: "카테고리",
|
||
archiveTotalText: "{count}개의 글",
|
||
notFound: {
|
||
code: "404",
|
||
title: "페이지를 찾을 수 없습니다",
|
||
quote: "방향을 잃지 않고 꾸준히 나아가다 보면 결국엔 목적지에 닿을 수 있습니다.",
|
||
linkText: "홈으로"
|
||
},
|
||
footer: { message: "Powered by <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const koPresetLocale = {
|
||
"CC0": "CC0 1.0 Universal",
|
||
"CC-BY-4.0": "Attribution 4.0 International",
|
||
"CC-BY-NC-4.0": "Attribution-NonCommercial 4.0 International",
|
||
"CC-BY-NC-SA-4.0": "Attribution-NonCommercial-ShareAlike 4.0 International",
|
||
"CC-BY-NC-ND-4.0": "Attribution-NonCommercial-NoDerivatives 4.0 International",
|
||
"CC-BY-ND-4.0": "Attribution-NoDerivatives 4.0 International",
|
||
"CC-BY-SA-4.0": "Attribution-ShareAlike 4.0 International"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/ru.ts
|
||
const ruLocale = {
|
||
selectLanguageName: "Русский",
|
||
selectLanguageText: "Выберите язык",
|
||
appearanceText: "Внешний вид",
|
||
lightModeSwitchTitle: "Переключить на светлую тему",
|
||
darkModeSwitchTitle: "Переключить на темную тему",
|
||
outlineLabel: "Содержание страницы",
|
||
returnToTopLabel: "Вернуться наверх",
|
||
editLinkText: "Редактировать страницу",
|
||
contributorsText: "Авторы",
|
||
prevPageLabel: "Предыдущая страница",
|
||
nextPageLabel: "Следующая страница",
|
||
lastUpdatedText: "Последнее обновление",
|
||
changelogText: "История изменений",
|
||
changelogOnText: "от",
|
||
changelogButtonText: "Посмотреть все изменения",
|
||
copyrightText: "Все права защищены",
|
||
copyrightAuthorText: "Авторские права принадлежат:",
|
||
copyrightCreationOriginalText: "Ссылка на статью:",
|
||
copyrightCreationTranslateText: "Перевод статьи:",
|
||
copyrightCreationReprintText: "Перепечатано из:",
|
||
copyrightLicenseText: "Лицензия:",
|
||
notFound: {
|
||
code: "404",
|
||
title: "Страница не найдена",
|
||
quote: "Но если вы не меняете курс и продолжаете искать, в конечном итоге вы можете добраться до места назначения.",
|
||
linkText: "Вернуться на главную"
|
||
},
|
||
homeText: "Главная",
|
||
blogText: "Блог",
|
||
tagText: "Теги",
|
||
archiveText: "Архив",
|
||
categoryText: "Категории",
|
||
archiveTotalText: "{count} статей",
|
||
encryptButtonText: "Подтвердить",
|
||
encryptPlaceholder: "Введите пароль",
|
||
encryptGlobalText: "Доступ к сайту только по паролю",
|
||
encryptPageText: "Доступ к странице только по паролю",
|
||
footer: { message: "Работает на <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const ruPresetLocale = {
|
||
"CC0": "CC0 1.0 Универсальная",
|
||
"CC-BY-4.0": "Атрибуция 4.0 Международный",
|
||
"CC-BY-NC-4.0": "Атрибуция-Некоммерческое 4.0 Международный",
|
||
"CC-BY-NC-SA-4.0": "Атрибуция-Некоммерческое-С сохранением условий 4.0 Международный",
|
||
"CC-BY-NC-ND-4.0": "Атрибуция-Некоммерческое-Без производных 4.0 Международный",
|
||
"CC-BY-ND-4.0": "Атрибуция-Без производных 4.0 Международный",
|
||
"CC-BY-SA-4.0": "Атрибуция-С сохранением условий 4.0 Международный"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/zh-tw.ts
|
||
const zhTwLocale = {
|
||
selectLanguageName: "繁體中文",
|
||
selectLanguageText: "選擇語言",
|
||
appearanceText: "外觀",
|
||
lightModeSwitchTitle: "切換為淺色主題",
|
||
darkModeSwitchTitle: "切換為深色主題",
|
||
outlineLabel: "此頁內容",
|
||
returnToTopLabel: "返回頂部",
|
||
editLinkText: "編輯此頁",
|
||
contributorsText: "貢獻者",
|
||
prevPageLabel: "上一頁",
|
||
nextPageLabel: "下一頁",
|
||
lastUpdatedText: "最後更新於",
|
||
changelogText: "變更歷史",
|
||
changelogOnText: "於",
|
||
changelogButtonText: "查看全部變更歷史",
|
||
copyrightText: "版權所有",
|
||
copyrightAuthorText: "版權歸屬:",
|
||
copyrightCreationOriginalText: "本文連結:",
|
||
copyrightCreationTranslateText: "本文翻譯自:",
|
||
copyrightCreationReprintText: "本文轉載自:",
|
||
copyrightLicenseText: "許可證:",
|
||
notFound: {
|
||
code: "404",
|
||
title: "頁面未找到",
|
||
quote: "但是,如果你不改變方向,並且一直尋找,最終可能會到達你要去的地方。",
|
||
linkText: "返回首頁"
|
||
},
|
||
homeText: "首頁",
|
||
blogText: "博客",
|
||
tagText: "標籤",
|
||
archiveText: "歸檔",
|
||
categoryText: "分類",
|
||
archiveTotalText: "{count} 篇",
|
||
encryptButtonText: "確認",
|
||
encryptPlaceholder: "請輸入密碼",
|
||
encryptGlobalText: "本站只允許密碼訪問",
|
||
encryptPageText: "本頁面只允許密碼訪問",
|
||
footer: { message: "Powered by <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const zhTwPresetLocale = {
|
||
"CC0": "CC0 1.0 通用",
|
||
"CC-BY-4.0": "署名 4.0 國際",
|
||
"CC-BY-NC-4.0": "署名-非商業性 4.0 國際",
|
||
"CC-BY-NC-SA-4.0": "署名-非商業性-相同方式共享 4.0 國際",
|
||
"CC-BY-NC-ND-4.0": "署名-非商業性-禁止演繹 4.0 國際",
|
||
"CC-BY-ND-4.0": "署名-禁止演繹 4.0 國際",
|
||
"CC-BY-SA-4.0": "署名-相同方式共享 4.0 國際"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/zh.ts
|
||
const zhLocale = {
|
||
selectLanguageName: "简体中文",
|
||
selectLanguageText: "选择语言",
|
||
appearanceText: "外观",
|
||
lightModeSwitchTitle: "切换为浅色主题",
|
||
darkModeSwitchTitle: "切换为深色主题",
|
||
outlineLabel: "此页内容",
|
||
returnToTopLabel: "返回顶部",
|
||
editLinkText: "编辑此页",
|
||
contributorsText: "贡献者",
|
||
prevPageLabel: "上一页",
|
||
nextPageLabel: "下一页",
|
||
lastUpdatedText: "最后更新于",
|
||
changelogText: "变更历史",
|
||
changelogOnText: "于",
|
||
changelogButtonText: "查看全部变更历史",
|
||
copyrightText: "版权所有",
|
||
copyrightAuthorText: "版权归属:",
|
||
copyrightCreationOriginalText: "本文链接:",
|
||
copyrightCreationTranslateText: "本文翻译自:",
|
||
copyrightCreationReprintText: "本文转载自:",
|
||
copyrightLicenseText: "许可证:",
|
||
notFound: {
|
||
code: "404",
|
||
title: "页面未找到",
|
||
quote: "但是,如果你不改变方向,并且一直寻找,最终可能会到达你要去的地方。",
|
||
linkText: "返回首页"
|
||
},
|
||
homeText: "首页",
|
||
blogText: "博客",
|
||
tagText: "标签",
|
||
archiveText: "归档",
|
||
categoryText: "分类",
|
||
archiveTotalText: "{count} 篇",
|
||
encryptButtonText: "确认",
|
||
encryptPlaceholder: "请输入密码",
|
||
encryptGlobalText: "本站只允许密码访问",
|
||
encryptPageText: "本页面只允许密码访问",
|
||
footer: { message: "Powered by <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
const zhPresetLocale = {
|
||
"CC0": "CC0 1.0 通用",
|
||
"CC-BY-4.0": "署名 4.0 国际",
|
||
"CC-BY-NC-4.0": "署名-非商业性 4.0 国际",
|
||
"CC-BY-NC-SA-4.0": "署名-非商业性-相同方式共享 4.0 国际",
|
||
"CC-BY-NC-ND-4.0": "署名-非商业性-禁止演绎 4.0 国际",
|
||
"CC-BY-ND-4.0": "署名-禁止演绎 4.0 国际",
|
||
"CC-BY-SA-4.0": "署名-相同方式共享 4.0 国际"
|
||
};
|
||
|
||
//#endregion
|
||
//#region src/node/locales/index.ts
|
||
const LOCALE_OPTIONS = [
|
||
[["ko", "ko-KR"], koLocale],
|
||
[["en", "en-US"], enLocale],
|
||
[[
|
||
"zh",
|
||
"zh-CN",
|
||
"zh-Hans",
|
||
"zh-Hant"
|
||
], zhLocale],
|
||
[["zh-TW"], zhTwLocale],
|
||
[["de", "de-DE"], deLocale],
|
||
[["fr", "fr-FR"], frLocale],
|
||
[["ru", "ru-RU"], ruLocale],
|
||
[["ja", "ja-JP"], jaLocale]
|
||
];
|
||
const PRESET_LOCALES = [
|
||
[["ko", "ko-KR"], koPresetLocale],
|
||
[["en", "en-US"], enPresetLocale],
|
||
[[
|
||
"zh",
|
||
"zh-CN",
|
||
"zh-Hans",
|
||
"zh-Hant"
|
||
], zhPresetLocale],
|
||
[["zh-TW"], zhTwPresetLocale],
|
||
[["de", "de-DE"], dePresetLocale],
|
||
[["fr", "fr-FR"], frPresetLocale],
|
||
[["ru", "ru-RU"], ruPresetLocale],
|
||
[["ja", "ja-JP"], jaPresetLocale]
|
||
];
|
||
|
||
//#endregion
|
||
//#region src/node/config/initThemeOptions.ts
|
||
const FALLBACK_OPTIONS = {
|
||
appearance: true,
|
||
blog: {
|
||
pagination: 15,
|
||
postList: true,
|
||
tags: true,
|
||
archives: true,
|
||
categories: true,
|
||
link: "/blog/",
|
||
tagsLink: "/blog/tags/",
|
||
archivesLink: "/blog/archives/",
|
||
categoriesLink: "/blog/categories/"
|
||
},
|
||
article: "/article/",
|
||
notes: {
|
||
link: "/",
|
||
dir: "/notes/",
|
||
notes: []
|
||
},
|
||
navbarSocialInclude: [
|
||
"github",
|
||
"twitter",
|
||
"discord",
|
||
"facebook"
|
||
],
|
||
aside: true,
|
||
outline: [2, 3],
|
||
externalLinkIcon: true,
|
||
editLink: true,
|
||
contributors: true,
|
||
changelog: false,
|
||
prevPage: true,
|
||
nextPage: true,
|
||
footer: { message: "Power by <a target=\"_blank\" href=\"https://v2.vuepress.vuejs.org/\">VuePress</a> & <a target=\"_blank\" href=\"https://theme-plume.vuejs.press\">vuepress-theme-plume</a>" }
|
||
};
|
||
/**
|
||
* 初始化主题配置,
|
||
* 1. 合并默认配置
|
||
* 2. 合并多语言配置
|
||
*/
|
||
function initThemeOptions(app, { locales,...options }) {
|
||
return {
|
||
...mergeOptions(FALLBACK_OPTIONS, options),
|
||
locales: getFullLocaleConfig({
|
||
app,
|
||
name: THEME_NAME,
|
||
default: LOCALE_OPTIONS,
|
||
config: fromEntries(entries({
|
||
"/": {},
|
||
...locales
|
||
}).map(([locale, opt]) => [locale, mergeOptions(options, opt)]))
|
||
})
|
||
};
|
||
}
|
||
function mergeOptions(target, source) {
|
||
const res = {};
|
||
const keys = uniq([...Object.keys(target), ...Object.keys(source)]);
|
||
for (const key of keys) if (hasOwn(source, key)) {
|
||
const value = source[key];
|
||
const targetValue = target[key];
|
||
if (isPlainObject$1(targetValue) && isPlainObject$1(value)) res[key] = Object.assign({}, targetValue, value);
|
||
else res[key] = value;
|
||
} else res[key] = target[key];
|
||
return res;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/loadConfig/loader.ts
|
||
let loader = null;
|
||
async function initConfigLoader(app, { configFile, onChange, defaultConfig }) {
|
||
perf.mark("load-config");
|
||
loader = {
|
||
configFile,
|
||
dependencies: [],
|
||
load: () => compiler(loader.configFile),
|
||
loaded: false,
|
||
changeEvents: [],
|
||
whenLoaded: [],
|
||
defaultConfig,
|
||
config: initThemeOptions(app, defaultConfig)
|
||
};
|
||
perf.mark("load-config:find");
|
||
loader.configFile = await findConfigPath(app, configFile);
|
||
perf.log("load-config:find");
|
||
if (onChange) loader.changeEvents.push(onChange);
|
||
perf.mark("load-config:loaded");
|
||
const { config, dependencies = [] } = await loader.load();
|
||
perf.log("load-config:loaded");
|
||
loader.loaded = true;
|
||
loader.dependencies = [...dependencies];
|
||
updateResolvedConfig(app, config);
|
||
loader.whenLoaded.forEach((fn) => fn(loader.config));
|
||
loader.whenLoaded = [];
|
||
perf.log("load-config");
|
||
}
|
||
function watchConfigFile(app, watchers, onChange) {
|
||
if (!loader || !loader.configFile) return;
|
||
const watcher = watch(loader.configFile, {
|
||
ignoreInitial: true,
|
||
cwd: process.cwd()
|
||
});
|
||
addDependencies(watcher);
|
||
onConfigChange(onChange);
|
||
watcher.on("change", async () => {
|
||
if (loader) {
|
||
loader.loaded = false;
|
||
const { config, dependencies = [] } = await loader.load();
|
||
loader.loaded = true;
|
||
addDependencies(watcher, dependencies);
|
||
updateResolvedConfig(app, config);
|
||
runChangeEvents();
|
||
}
|
||
});
|
||
watcher.on("unlink", async () => {
|
||
updateResolvedConfig(app);
|
||
runChangeEvents();
|
||
});
|
||
watchers.push(watcher);
|
||
}
|
||
async function onConfigChange(onChange) {
|
||
if (loader && !loader.changeEvents.includes(onChange)) {
|
||
loader.changeEvents.push(onChange);
|
||
if (loader.loaded) await onChange(loader.config);
|
||
}
|
||
}
|
||
function waitForConfigLoaded() {
|
||
return new Promise((resolve$2) => {
|
||
if (loader?.loaded) resolve$2(loader.config);
|
||
else loader?.whenLoaded.push(resolve$2);
|
||
});
|
||
}
|
||
function getThemeConfig() {
|
||
return loader.config;
|
||
}
|
||
function updateResolvedConfig(app, userConfig = {}) {
|
||
if (loader) {
|
||
const config = deepMerge({}, loader.defaultConfig, userConfig);
|
||
loader.config = initThemeOptions(app, config);
|
||
}
|
||
}
|
||
async function runChangeEvents() {
|
||
if (loader) await Promise.all(loader.changeEvents.map((fn) => fn(loader.config)));
|
||
}
|
||
function addDependencies(watcher, dependencies) {
|
||
if (!loader) return;
|
||
if (dependencies?.length) {
|
||
const deps = dependencies.filter((dep) => !loader.dependencies.includes(dep) && dep[0] === ".");
|
||
loader.dependencies.push(...deps);
|
||
watcher.add(deps);
|
||
} else watcher.add(loader.dependencies);
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/autoFrontmatter/readFile.ts
|
||
async function readMarkdownList(app, { globFilter, checkCache }) {
|
||
const source = app.dir.source();
|
||
const files = await fg(["**/*.md"], {
|
||
cwd: source,
|
||
ignore: ["node_modules", ".vuepress"]
|
||
});
|
||
return await Promise.all(files.filter((id) => {
|
||
if (!globFilter(id)) return false;
|
||
return checkCache(path.join(source, id));
|
||
}).map((file) => readMarkdown(source, file)));
|
||
}
|
||
async function readMarkdown(sourceDir, relativePath) {
|
||
const filepath = path.join(sourceDir, relativePath);
|
||
const stats = await fs.promises.stat(filepath);
|
||
return {
|
||
filepath,
|
||
relativePath: normalizePath(relativePath),
|
||
content: await fs.promises.readFile(filepath, "utf-8"),
|
||
createTime: getFileCreateTime(stats),
|
||
stats
|
||
};
|
||
}
|
||
function getFileCreateTime(stats) {
|
||
return stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/extendsBundlerOptions.ts
|
||
function extendsBundlerOptions(bundlerOptions, app) {
|
||
addViteConfig(bundlerOptions, app, { build: { chunkSizeWarningLimit: 2048 } });
|
||
addViteOptimizeDepsInclude(bundlerOptions, app, [
|
||
"@vueuse/core",
|
||
"bcrypt-ts/browser",
|
||
"@vuepress/helper/client",
|
||
"@iconify/vue",
|
||
"@iconify/vue/offline",
|
||
"@vuepress/plugin-git/client"
|
||
]);
|
||
addViteOptimizeDepsExclude(bundlerOptions, app, "@theme");
|
||
addViteSsrNoExternal(bundlerOptions, app, [
|
||
"@vuepress/helper",
|
||
"@vuepress/plugin-reading-time",
|
||
"@vuepress/plugin-watermark"
|
||
]);
|
||
if (isPackageExists("swiper")) {
|
||
addViteOptimizeDepsInclude(bundlerOptions, app, ["swiper/modules", "swiper/vue"]);
|
||
addViteSsrNoExternal(bundlerOptions, app, ["swiper"]);
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/resolveNotesOptions.ts
|
||
function resolveNotesLinkList(options) {
|
||
const locales = options.locales || {};
|
||
const notesLinks = [];
|
||
for (const [locale, opt] of entries(locales)) {
|
||
const config = locale === "/" ? opt.notes || options.notes : opt.notes;
|
||
if (config && config.notes?.length) {
|
||
const prefix = config.link || "";
|
||
notesLinks.push(...config.notes.map((note) => withBase(`${prefix}/${note.link || ""}`, locale)));
|
||
}
|
||
}
|
||
return uniq(notesLinks);
|
||
}
|
||
function resolveNotesOptions(options) {
|
||
const locales = options.locales || {};
|
||
const notesOptionsList = [];
|
||
for (const [locale, opt] of entries(locales)) {
|
||
const current = locale === "/" ? opt.notes || options.notes : opt.notes;
|
||
if (current) {
|
||
current.dir = withBase(current.dir, locale);
|
||
notesOptionsList.push(current);
|
||
}
|
||
}
|
||
return notesOptionsList;
|
||
}
|
||
function resolveNotesDirs(options) {
|
||
const notesList = resolveNotesOptions(options);
|
||
return uniq(notesList.flatMap(({ notes, dir }) => notes.map((note) => removeLeadingSlash(normalizePath(`${dir}/${note.dir || ""}/`)))));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/resolveThemeData.ts
|
||
const EXCLUDE_LIST = [
|
||
"hostname",
|
||
"locales",
|
||
"sidebar",
|
||
"navbar",
|
||
"notes",
|
||
"sidebar",
|
||
"article",
|
||
"changelog",
|
||
"contributors",
|
||
"bulletin",
|
||
"cache",
|
||
"autoFrontmatter",
|
||
"comment",
|
||
"codeHighlighter",
|
||
"markdown",
|
||
"configFile",
|
||
"encrypt",
|
||
"plugins",
|
||
"search",
|
||
"watermark",
|
||
"readingTime",
|
||
"copyCode"
|
||
];
|
||
const EXCLUDE_LOCALE_LIST = [
|
||
...EXCLUDE_LIST,
|
||
"blog",
|
||
"appearance"
|
||
];
|
||
function resolveThemeData(app, options) {
|
||
const themeData = { locales: {} };
|
||
entries(options).forEach(([key, value]) => {
|
||
if (!EXCLUDE_LIST.includes(key)) themeData[key] = value;
|
||
});
|
||
themeData.contributors = isPlainObject$1(options.contributors) ? { mode: options.contributors.mode || "inline" } : isBoolean(options.contributors) ? options.contributors : true;
|
||
themeData.changelog = !!options.changelog;
|
||
if (isPlainObject$1(options.bulletin)) {
|
||
const { enablePage: _,...opt } = options.bulletin;
|
||
themeData.bulletin = opt;
|
||
} else if (options.bulletin) themeData.bulletin = options.bulletin;
|
||
if (isPlainObject$1(options.blog)) {
|
||
const { categoriesTransform, include, exclude,...blog } = options.blog;
|
||
themeData.blog = blog;
|
||
} else themeData.blog = options.blog;
|
||
entries(options.locales || {}).forEach(([locale, opt]) => {
|
||
themeData.locales[locale] = {};
|
||
entries(opt).forEach(([key, value]) => {
|
||
if (!EXCLUDE_LOCALE_LIST.includes(key)) themeData.locales[locale][key] = value;
|
||
});
|
||
if (isPlainObject$1(opt.bulletin)) {
|
||
const { enablePage: _,...rest } = opt.bulletin;
|
||
themeData.locales[locale].bulletin = rest;
|
||
} else if (opt.bulletin) themeData.locales[locale].bulletin = opt.bulletin;
|
||
});
|
||
entries(options.locales || {}).forEach(([locale, opt]) => {
|
||
if (opt.navbar !== false && (!opt.navbar || opt.navbar.length === 0)) {
|
||
const navbar = [{
|
||
text: opt.homeText || options.homeText || "Home",
|
||
link: locale
|
||
}];
|
||
if (options.blog !== false) {
|
||
const blog = options.blog || {};
|
||
const blogLink = blog.link || "/blog/";
|
||
navbar.push({
|
||
text: opt.blogText || options.blogText || "Blog",
|
||
link: withBase(blogLink, locale)
|
||
});
|
||
if (blog.tags !== false) navbar.push({
|
||
text: opt.tagText || options.tagText || "Tags",
|
||
link: withBase(blog.tagsLink || `${blogLink}/tags/`, locale)
|
||
});
|
||
if (blog.archives !== false) navbar.push({
|
||
text: opt.archiveText || options.archiveText || "Archives",
|
||
link: withBase(blog.archivesLink || `${blogLink}/archives/`, locale)
|
||
});
|
||
}
|
||
themeData.locales[locale].navbar = navbar;
|
||
} else themeData.locales[locale].navbar = opt.navbar;
|
||
});
|
||
return themeData;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/setupAlias.ts
|
||
function setupAlias() {
|
||
return { ...Object.fromEntries(fs.readdirSync(resolve$1("client/components"), {
|
||
encoding: "utf-8",
|
||
recursive: true
|
||
}).filter((file) => file.endsWith(".vue")).map((file) => [path.join("@theme", file), resolve$1("client/components", file)])) };
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/setupProvideData.ts
|
||
function setupProvideData(app, plugins) {
|
||
const watermark = getThemeConfig().watermark ?? plugins.watermark;
|
||
return {
|
||
__PLUME_WM_FP__: isPlainObject$1(watermark) ? watermark.fullPage !== false : true,
|
||
__PLUME_PRESET_LOCALE__: getFullLocaleConfig({
|
||
app,
|
||
name: "vuepress-theme-plume/preset-locales",
|
||
default: PRESET_LOCALES
|
||
})
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/config/templateBuildRenderer.ts
|
||
function templateBuildRenderer(template, context) {
|
||
const options = getThemeConfig();
|
||
const pkg = getThemePackage();
|
||
template = template.replace("{{ themeVersion }}", pkg.version || "").replace(/^\s+|\s+$/gm, "").replace(/\n/g, "");
|
||
if (options.appearance ?? true) {
|
||
const appearance = typeof options.appearance === "string" ? options.appearance : "auto";
|
||
const script = appearance === "force-dark" ? `document.documentElement.dataset.theme = 'dark'` : `;(function () {
|
||
const um= localStorage.getItem('vuepress-theme-appearance') || '${appearance}';
|
||
const sm = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||
const isDark = um === 'dark' || (um !== 'light' && sm);
|
||
document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
|
||
})();`.replace(/^\s+|\s+$/gm, "").replace(/\n/g, "");
|
||
template = template.replace("<!--vuepress-theme-plume-appearance-->", `<script id="check-dark-mode">${script}<\/script>`);
|
||
} else template = template.replace("<!--vuepress-theme-plume-appearance-->", "");
|
||
return templateRenderer(template, context);
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/autoFrontmatter/baseFrontmatter.ts
|
||
function createBaseFrontmatter(autoFrontmatter) {
|
||
const res = {};
|
||
if (autoFrontmatter.createTime !== false) res.createTime = (formatTime, { createTime }, data) => {
|
||
if (formatTime) return formatTime;
|
||
if (data.friends || data.pageLayout === "friends") return;
|
||
return dayjs(new Date(createTime)).format("YYYY/MM/DD HH:mm:ss");
|
||
};
|
||
return res;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/autoFrontmatter/resolveLinkBySidebar.ts
|
||
function resolveLinkBySidebar(sidebar, _prefix) {
|
||
const res = {};
|
||
if (sidebar === "auto") return res;
|
||
for (const item of sidebar) if (typeof item !== "string") {
|
||
const { prefix, dir = "", link = "/", items, text = "" } = item;
|
||
getSidebarLink(items, link, text, pathJoin(_prefix, prefix || dir), res);
|
||
}
|
||
return res;
|
||
}
|
||
function getSidebarLink(items, link, text, dir = "", res = {}) {
|
||
if (items === "auto") return;
|
||
if (!items) {
|
||
res[pathJoin(dir, `${text}.md`)] = link;
|
||
return;
|
||
}
|
||
for (const item of items) if (typeof item === "string") {
|
||
if (!link) continue;
|
||
if (item) res[pathJoin(dir, `${item}.md`)] = link;
|
||
else {
|
||
res[pathJoin(dir, "README.md")] = link;
|
||
res[pathJoin(dir, "index.md")] = link;
|
||
res[pathJoin(dir, "readme.md")] = link;
|
||
}
|
||
res[dir] = link;
|
||
} else {
|
||
const { prefix, dir: subDir = "", link: subLink = "/", items: subItems, text: subText = "" } = item;
|
||
getSidebarLink(subItems, pathJoin(link, subLink), subText, pathJoin(prefix || dir, subDir), res);
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/autoFrontmatter/resolveOptions.ts
|
||
function resolveOptions(options, autoFrontmatter) {
|
||
const resolveLocale = (relativeFilepath) => resolveLocalePath(options.locales, ensureLeadingSlash(relativeFilepath));
|
||
const findNotesByLocale = (locale) => {
|
||
const notes = options.locales?.[locale]?.notes;
|
||
return notes === false ? void 0 : notes;
|
||
};
|
||
const findNote = (relativeFilepath) => {
|
||
const locale = resolveLocale(relativeFilepath);
|
||
const filepath = ensureLeadingSlash(relativeFilepath);
|
||
const notes = findNotesByLocale(locale);
|
||
if (!notes) return void 0;
|
||
const notesList = notes?.notes || [];
|
||
const notesDir = notes?.dir || "";
|
||
return notesList.find((note) => filepath.startsWith(normalizePath(`${notesDir}/${note.dir}`)));
|
||
};
|
||
const baseFrontmatter = createBaseFrontmatter(autoFrontmatter);
|
||
const localesNotesDirs = resolveNotesDirs(options);
|
||
const configs = [];
|
||
if (localesNotesDirs.length) {
|
||
configs.push({
|
||
include: localesNotesDirs.map((dir) => pathJoin(dir, "/{readme,README,index}.md")),
|
||
frontmatter: {
|
||
title(title, { relativePath }) {
|
||
if (title) return title;
|
||
if (autoFrontmatter.title === false) return;
|
||
return findNote(relativePath)?.text || getCurrentDirname("", relativePath);
|
||
},
|
||
...baseFrontmatter,
|
||
permalink(permalink, { relativePath }, data) {
|
||
if (permalink) return permalink;
|
||
if (autoFrontmatter.permalink === false || data.friends || data.pageLayout === "friends") return;
|
||
const locale = resolveLocale(relativePath);
|
||
const prefix = findNotesByLocale(locale)?.link || "";
|
||
const note = findNote(relativePath);
|
||
return pathJoin(prefix.startsWith(locale) ? "/" : locale, prefix, note?.link || getCurrentDirname(note?.dir, relativePath), "/");
|
||
}
|
||
}
|
||
});
|
||
configs.push({
|
||
include: localesNotesDirs.map((dir) => `${dir}**/**.md`),
|
||
frontmatter: {
|
||
title(title, { relativePath }) {
|
||
if (title) return title;
|
||
if (autoFrontmatter.title === false) return;
|
||
return path.basename(relativePath, ".md").replace(/^\d+\./, "");
|
||
},
|
||
...baseFrontmatter,
|
||
permalink(permalink, { relativePath }, data) {
|
||
if (permalink) return permalink;
|
||
if (autoFrontmatter.permalink === false) return;
|
||
if (data.friends || data.pageLayout === "friends") return;
|
||
const locale = resolveLocale(relativePath);
|
||
const notes = findNotesByLocale(locale);
|
||
const note = findNote(relativePath);
|
||
const prefix = notes?.link || "";
|
||
const args = [
|
||
prefix.startsWith(locale) ? "/" : locale,
|
||
prefix,
|
||
note?.link || ""
|
||
];
|
||
const sidebar = note?.sidebar;
|
||
if (note && sidebar && sidebar !== "auto") {
|
||
const res = resolveLinkBySidebar(sidebar, pathJoin(notes?.dir || "", note.dir || ""));
|
||
const file = ensureLeadingSlash(relativePath);
|
||
if (res[file]) args.push(res[file]);
|
||
else if (res[path.dirname(file)]) args.push(res[path.dirname(file)]);
|
||
}
|
||
return pathJoin(...args, nanoid(), "/");
|
||
}
|
||
}
|
||
});
|
||
}
|
||
configs.push({
|
||
include: "**/{readme,README,index}.md",
|
||
frontmatter: {}
|
||
});
|
||
if (options.blog !== false) configs.push({
|
||
include: options.blog?.include ?? ["**/*.md"],
|
||
frontmatter: {
|
||
title(title, { relativePath }) {
|
||
if (title) return title;
|
||
if (autoFrontmatter.title === false) return;
|
||
return path.basename(relativePath || "", ".md");
|
||
},
|
||
...baseFrontmatter,
|
||
permalink(permalink, { relativePath }) {
|
||
if (permalink) return permalink;
|
||
if (autoFrontmatter.permalink === false) return;
|
||
const locale = resolveLocale(relativePath);
|
||
const prefix = withBase(options.article || "/article/", locale);
|
||
return normalizePath(`${prefix}/${nanoid()}/`);
|
||
}
|
||
}
|
||
});
|
||
configs.push({
|
||
include: "*",
|
||
frontmatter: {
|
||
title(title, { relativePath }) {
|
||
if (title) return title;
|
||
if (autoFrontmatter.title === false) return;
|
||
return path.basename(relativePath || "", ".md");
|
||
},
|
||
...baseFrontmatter,
|
||
permalink(permalink, { relativePath }) {
|
||
if (permalink) return permalink;
|
||
if (autoFrontmatter.permalink === false) return;
|
||
return ensureLeadingSlash(normalizePath(relativePath.replace(/\.md$/, "/")));
|
||
}
|
||
}
|
||
});
|
||
return {
|
||
include: autoFrontmatter?.include ?? ["**/*.md"],
|
||
exclude: uniq([
|
||
".vuepress/**/*",
|
||
"node_modules",
|
||
...autoFrontmatter?.exclude ?? []
|
||
]),
|
||
frontmatter: configs
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/autoFrontmatter/generator.ts
|
||
const CACHE_FILE = "markdown/auto-frontmatter.json";
|
||
let generate = null;
|
||
function initAutoFrontmatter() {
|
||
const { autoFrontmatter = {},...options } = getThemeConfig();
|
||
if (autoFrontmatter === false) return;
|
||
const { include, exclude, frontmatter = {} } = resolveOptions(options, autoFrontmatter);
|
||
const globFilter = createFilter(include, exclude, { resolve: false });
|
||
const userConfig = isArray(frontmatter) ? frontmatter : [{
|
||
include: "*",
|
||
frontmatter
|
||
}];
|
||
const globalConfig = userConfig.find(({ include: include$1 }) => include$1 === "*")?.frontmatter || {};
|
||
const rules = userConfig.filter(({ include: include$1 }) => include$1 !== "*").map(({ include: include$1, frontmatter: frontmatter$1 }) => {
|
||
return {
|
||
include: include$1,
|
||
filter: createFilter(toArray(include$1), void 0, { resolve: false }),
|
||
frontmatter: frontmatter$1
|
||
};
|
||
});
|
||
const cache$3 = {};
|
||
function checkCache(filepath) {
|
||
const stats = fs.statSync(filepath);
|
||
if (cache$3[filepath] && cache$3[filepath] === stats.mtimeMs.toString()) return false;
|
||
cache$3[filepath] = stats.mtimeMs.toString();
|
||
return true;
|
||
}
|
||
async function updateCache(app) {
|
||
if (!isEmptyObject(cache$3)) {
|
||
await fs.mkdir(path.dirname(app.dir.cache(CACHE_FILE)), { recursive: true });
|
||
await fs.writeFile(app.dir.cache(CACHE_FILE), JSON.stringify(cache$3), "utf-8");
|
||
}
|
||
}
|
||
generate = {
|
||
globFilter,
|
||
global: globalConfig,
|
||
rules,
|
||
cache: cache$3,
|
||
checkCache,
|
||
updateCache
|
||
};
|
||
}
|
||
async function generateAutoFrontmatter(app) {
|
||
perf.mark("generate:frontmatter");
|
||
if (!generate) return;
|
||
const cachePath = app.dir.cache(CACHE_FILE);
|
||
if (fs.existsSync(cachePath)) try {
|
||
generate.cache = JSON.parse(await fs.readFile(cachePath, "utf-8"));
|
||
} catch {
|
||
generate.cache = {};
|
||
}
|
||
const markdownList = await readMarkdownList(app, generate);
|
||
await promiseParallel(markdownList.map((file) => () => generator(file)), 64);
|
||
await generate.updateCache(app);
|
||
perf.log("generate:frontmatter");
|
||
}
|
||
async function watchAutoFrontmatter(app, watchers) {
|
||
if (!generate) return;
|
||
const watcher = chokidar.watch(".", {
|
||
cwd: app.dir.source(),
|
||
ignoreInitial: true,
|
||
ignored: (filepath, stats) => {
|
||
if (filepath.includes("node_modules")) return true;
|
||
if (filepath.includes(".vuepress")) return true;
|
||
return Boolean(stats?.isFile()) && !filepath.endsWith(".md");
|
||
}
|
||
});
|
||
watcher.on("add", async (relativePath) => {
|
||
const enabled = getThemeConfig().autoFrontmatter !== false;
|
||
if (!generate.globFilter(relativePath) || !enabled) return;
|
||
const file = await readMarkdown(app.dir.source(), relativePath);
|
||
await generator(file);
|
||
});
|
||
watcher.on("change", async (relativePath) => {
|
||
const enabled = getThemeConfig().autoFrontmatter !== false;
|
||
if (!generate.globFilter(relativePath) || !enabled) return;
|
||
if (generate.checkCache(path.join(app.dir.source(), relativePath))) await generate.updateCache(app);
|
||
});
|
||
watchers.push(watcher);
|
||
}
|
||
async function generator(file) {
|
||
if (!generate) return;
|
||
const { filepath, relativePath } = file;
|
||
const formatter = generate.rules.find(({ filter }) => filter(relativePath))?.frontmatter || generate.global;
|
||
const { data, content } = grayMatter(file.content);
|
||
const beforeHash = hash(data);
|
||
for (const key in formatter) {
|
||
const value = await formatter[key](data[key], file, data) ?? data[key];
|
||
if (typeof value !== "undefined") data[key] = value;
|
||
else delete data[key];
|
||
}
|
||
if (beforeHash === hash(data)) return;
|
||
try {
|
||
const formatted = isEmptyObject(data) ? "" : yaml.dump(data);
|
||
const newContent = formatted ? `---\n${formatted}---\n${content}` : content;
|
||
await fs.promises.writeFile(filepath, newContent, "utf-8");
|
||
generate.checkCache(filepath);
|
||
} catch (e) {
|
||
console.error(colors.red("[vuepress-theme-plume:auto-frontmatter] "), `error in: ${colors.cyan(relativePath)}\n`, e);
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/detector/fields.ts
|
||
const PLUGINS_SUPPORTED_FIELDS = [
|
||
"search",
|
||
"docsearch",
|
||
"copyCode",
|
||
"shiki",
|
||
"git",
|
||
"nprogress",
|
||
"photoSwipe",
|
||
"markdownChart",
|
||
"markdownPower",
|
||
"markdownImage",
|
||
"markdownMath",
|
||
"markdownInclude",
|
||
"comment",
|
||
"sitemap",
|
||
"seo",
|
||
"cache",
|
||
"readingTime",
|
||
"watermark",
|
||
"replaceAssets"
|
||
];
|
||
const MARKDOWN_CHART_FIELDS = [
|
||
"chartjs",
|
||
"echarts",
|
||
"mermaid",
|
||
"markmap",
|
||
"plantuml",
|
||
"flowchart"
|
||
];
|
||
const MARKDOWN_POWER_FIELDS = [
|
||
"abbr",
|
||
"acfun",
|
||
"annotation",
|
||
"artPlayer",
|
||
"audioReader",
|
||
"bilibili",
|
||
"caniuse",
|
||
"codeSandbox",
|
||
"codeTabs",
|
||
"codeTree",
|
||
"codepen",
|
||
"demo",
|
||
"fileTree",
|
||
"field",
|
||
"icons",
|
||
"icon",
|
||
"imageSize",
|
||
"jsfiddle",
|
||
"npmTo",
|
||
"pdf",
|
||
"plot",
|
||
"repl",
|
||
"replit",
|
||
"table",
|
||
"timeline",
|
||
"collapse",
|
||
"chat",
|
||
"youtube"
|
||
];
|
||
const MARKDOWN_SUPPORT_FIELDS = [
|
||
...MARKDOWN_CHART_FIELDS,
|
||
...MARKDOWN_POWER_FIELDS,
|
||
"image",
|
||
"math",
|
||
"include",
|
||
"hint",
|
||
"alert"
|
||
];
|
||
|
||
//#endregion
|
||
//#region src/node/detector/dependency.ts
|
||
const DEPENDENCIES = {
|
||
twoslash: ["@vuepress/shiki-twoslash"],
|
||
pythonRepl: ["pyodide"],
|
||
chartjs: ["chart.js"],
|
||
echarts: ["echarts"],
|
||
markmap: [
|
||
"markmap-lib",
|
||
"markmap-toolbar",
|
||
"markmap-view"
|
||
],
|
||
mermaid: ["mermaid"],
|
||
flowchart: ["flowchart.ts"],
|
||
artPlayer: ["artplayer"],
|
||
mathjax: ["mathjax-full"]
|
||
};
|
||
const t$3 = createTranslate({
|
||
en: {
|
||
notFoundDeps: "Enabling features such as {{ features }} requires the installation of the following dependencies: {{ dependencies }}",
|
||
install: "Run the command to install: {{ command }}"
|
||
},
|
||
zh: {
|
||
notFoundDeps: "启用 {{ features }} 等功能需要安装以下依赖: {{ dependencies }}",
|
||
install: "运行安装命令: {{ command }}"
|
||
}
|
||
});
|
||
/**
|
||
* 部分功能需要手动安装依赖,
|
||
* 检查环境中是否缺少依赖
|
||
*/
|
||
function detectDependencies(options, plugins) {
|
||
const shouldInstall = {};
|
||
const markdown = options.markdown || {};
|
||
const mdPower = isPlainObject(plugins.markdownPower) ? plugins.markdownPower : {};
|
||
const mdChart = isPlainObject(plugins.markdownChart) ? plugins.markdownChart : {};
|
||
const add = (name) => {
|
||
const list = DEPENDENCIES[name].filter((dep) => !isPackageExists(dep));
|
||
if (list.length) shouldInstall[name] = list;
|
||
};
|
||
if (options.codeHighlighter && options.codeHighlighter.twoslash) add("twoslash");
|
||
if (markdown.repl && markdown.repl.python) add("pythonRepl");
|
||
[
|
||
"chartjs",
|
||
"echarts",
|
||
"markmap",
|
||
"mermaid",
|
||
"flowchart"
|
||
].forEach((dep) => {
|
||
if (markdown[dep] || mdChart[dep]) add(dep);
|
||
});
|
||
const math = markdown.math || plugins.markdownMath;
|
||
if (math && math.type === "mathjax") add("mathjax");
|
||
if (markdown.artPlayer || mdPower.artPlayer) add("artPlayer");
|
||
if (isEmptyObject(shouldInstall)) return;
|
||
const features = Object.keys(shouldInstall);
|
||
const dependencies = Object.values(shouldInstall).flat();
|
||
logger.error(t$3("notFoundDeps", {
|
||
features: features.map((feat) => colors.green(feat)).join(", "),
|
||
dependencies: dependencies.map((dep) => colors.magenta(dep)).join(", ")
|
||
}));
|
||
const agent = getUserAgent();
|
||
if (agent) {
|
||
const { command = "", args = [] } = resolveCommand(agent, "add", dependencies) || {};
|
||
logger.info(t$3("install", { command: colors.cyan(`${command} ${args.join(" ").replace(DEPENDENCIES.twoslash[0], `${DEPENDENCIES.twoslash[0]}@next`)}`) }));
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/detector/markdown.ts
|
||
const t$2 = createTranslate({
|
||
en: { message: "{{ markdown }} unsupported fields: {{ unsupported }}, please check your config." },
|
||
zh: { message: "{{ markdown }} 不支持以下字段: {{ unsupported }}, 请检查你的配置。" }
|
||
});
|
||
function detectMarkdown(options) {
|
||
const { markdown } = options;
|
||
if (!markdown) return;
|
||
const unsupported = Object.keys(markdown).filter((key) => !MARKDOWN_SUPPORT_FIELDS.includes(key));
|
||
if (unsupported.length) logger.warn(t$2("message", {
|
||
markdown: colors.green("markdown"),
|
||
unsupported: unsupported.map((field) => colors.magenta(`"${field}"`)).join(", ")
|
||
}));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/detector/plugins.ts
|
||
const t$1 = createTranslate({
|
||
en: { message: "{{ plugins }} unsupported fields: {{ unsupported }}, please check your config." },
|
||
zh: { message: "{{ plugins }} 不支持以下字段: {{ unsupported }}, 请检查你的配置。" }
|
||
});
|
||
function detectPlugins(plugins) {
|
||
if (Array.isArray(plugins)) logger.warn(`${colors.green("plugins")} only accept object config, please check your config.`);
|
||
const unsupportedPluginsFields = Object.keys(plugins).filter((field) => !PLUGINS_SUPPORTED_FIELDS.includes(field));
|
||
if (unsupportedPluginsFields.length) logger.warn(t$1("message", {
|
||
plugins: colors.green("plugins"),
|
||
unsupported: unsupportedPluginsFields.map((field) => colors.magenta(`"${field}"`)).join(", ")
|
||
}));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/detector/options.ts
|
||
/**
|
||
* 检测主题选项
|
||
*/
|
||
function detectThemeOptions({ plugins = {}, configFile,...themeOptions }) {
|
||
detectDependencies(themeOptions, plugins);
|
||
detectMarkdown(themeOptions);
|
||
detectPlugins(plugins);
|
||
return {
|
||
configFile,
|
||
plugins,
|
||
themeOptions
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/detector/versions.ts
|
||
const t = createTranslate({
|
||
en: {
|
||
title: "The following dependencies have version mismatches:",
|
||
footer: "Please update the dependencies to the correct versions."
|
||
},
|
||
zh: {
|
||
title: "以下依赖版本不匹配:",
|
||
footer: "请更新依赖至正确的版本。"
|
||
}
|
||
});
|
||
function detectVersions(app) {
|
||
detectVuepressVersion();
|
||
detectThemeVersion(app);
|
||
}
|
||
/**
|
||
* 检查 vuepress 相关依赖,
|
||
* 当依赖不匹配时,可能会导致 vuepress 无法正常运行
|
||
* 比如 某些插件依赖了不同版本的 `@vuepress/helper` ,会导致在浏览器中无法正常运行
|
||
*/
|
||
function detectVuepressVersion() {
|
||
const themePackage = getThemePackage();
|
||
const userPackage = getPackage();
|
||
const vuepressDeps = Object.entries({
|
||
"vuepress-theme-plume": themePackage.version,
|
||
"@vuepress/bundler-vite": themePackage.peerDependencies?.vuepress,
|
||
"@vuepress/bundler-webpack": themePackage.peerDependencies?.vuepress,
|
||
...themePackage.dependencies,
|
||
...themePackage.peerDependencies
|
||
}).reduce((deps, [name, version]) => {
|
||
if (name.includes("vuepress") && version && version !== "workspace:*") deps[name] = version;
|
||
return deps;
|
||
}, {});
|
||
/**
|
||
* 检查依赖是否匹配
|
||
* TODO: 检查 pnpm catalog
|
||
*/
|
||
const detect = (deps) => {
|
||
const results = [];
|
||
if (!deps || isEmptyObject(deps)) return results;
|
||
for (const [name, version] of Object.entries(deps)) {
|
||
const resolved = resolveVersion(version);
|
||
if (resolved && vuepressDeps[name] && vuepressDeps[name] !== resolved) results.push({
|
||
name,
|
||
expected: vuepressDeps[name],
|
||
current: version
|
||
});
|
||
}
|
||
return results;
|
||
};
|
||
const devResults = detect(userPackage.devDependencies);
|
||
const prodResults = detect(userPackage.dependencies);
|
||
if (devResults.length || prodResults.length) {
|
||
const output = (deps) => deps.map((dep) => ` ${colors.green(dep.name)}: ${colors.gray(dep.current)} -> ${colors.cyan(dep.expected)}`).join(" \n");
|
||
logger.warn(`${t("title")}
|
||
${devResults.length ? `\ndevDependencies:\n${output(devResults)}\n` : ""}${prodResults.length ? `\ndependencies:\n${output(prodResults)}\n` : ""}
|
||
${t("footer")}
|
||
`);
|
||
}
|
||
}
|
||
/**
|
||
* 检查用户是否升级主题版本,
|
||
* 如果升级了主题版本,则清空缓存
|
||
*/
|
||
function detectThemeVersion(app) {
|
||
if (app.env.isBuild) return;
|
||
try {
|
||
const versionCache = app.dir.cache(".theme-plume-version");
|
||
const current = getThemePackage().version;
|
||
const updateCache = () => {
|
||
fs$1.mkdirSync(path$1.dirname(versionCache), { recursive: true });
|
||
fs$1.writeFileSync(versionCache, current, "utf-8");
|
||
};
|
||
if (!fs$1.existsSync(versionCache)) {
|
||
updateCache();
|
||
return;
|
||
}
|
||
if ((fs$1.readFileSync(versionCache, "utf-8") || "") === current) return;
|
||
/**
|
||
* 当主题版本有更新时,清空缓存,
|
||
* 避免由于缓存问题,导致主题的更新内容无法生效
|
||
*/
|
||
fs$1.rmSync(app.dir.cache(), { recursive: true });
|
||
fs$1.rmSync(app.dir.temp(), { recursive: true });
|
||
updateCache();
|
||
} catch {}
|
||
}
|
||
const RE_FLAG = /^[\^~<>=]+/;
|
||
function resolveVersion(version) {
|
||
if (RE_FLAG.test(version)) return version.replace(RE_FLAG, "");
|
||
if (/^\d/.test(version)) return version;
|
||
return "";
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/pages/createPages.ts
|
||
function getRootLang(app) {
|
||
const siteLocales = app.siteData.locales;
|
||
if (siteLocales["/"]?.lang) return siteLocales["/"].lang;
|
||
return app.siteData.lang;
|
||
}
|
||
async function createPages(app) {
|
||
const options = getThemeConfig();
|
||
if (options.blog === false) return;
|
||
perf.mark("create:blog-pages");
|
||
const pageList = [];
|
||
const locales = options.locales || {};
|
||
const rootLang = getRootLang(app);
|
||
const blog = options.blog || {};
|
||
const link = blog.link || "/blog/";
|
||
for (const localePath of Object.keys(locales)) {
|
||
const lang$1 = app.siteData.locales?.[localePath]?.lang || rootLang;
|
||
const opt = locales[localePath];
|
||
if (blog.postList !== false) pageList.push(createPage(app, {
|
||
path: withBase(link, localePath),
|
||
frontmatter: {
|
||
lang: lang$1,
|
||
_pageLayout: "blog",
|
||
title: opt.blogText || options.blogText || "Blog"
|
||
}
|
||
}));
|
||
if (blog.tags !== false) pageList.push(createPage(app, {
|
||
path: withBase(blog.tagsLink || `${link}/tags/`, localePath),
|
||
frontmatter: {
|
||
lang: lang$1,
|
||
_pageLayout: "blog-tags",
|
||
title: opt.tagText || options.tagText || "Tags"
|
||
}
|
||
}));
|
||
if (blog.archives !== false) pageList.push(createPage(app, {
|
||
path: withBase(blog.archivesLink || `${link}/archives/`, localePath),
|
||
frontmatter: {
|
||
lang: lang$1,
|
||
_pageLayout: "blog-archives",
|
||
title: opt.archiveText || options.archiveText || "Archives"
|
||
}
|
||
}));
|
||
if (blog.categories !== false) pageList.push(createPage(app, {
|
||
path: withBase(blog.categoriesLink || `${link}/categories/`, localePath),
|
||
frontmatter: {
|
||
lang: lang$1,
|
||
_pageLayout: "blog-categories",
|
||
title: opt.categoryText || options.categoryText || "Categories"
|
||
}
|
||
}));
|
||
}
|
||
app.pages.push(...await Promise.all(pageList));
|
||
perf.log("create:blog-pages");
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/pages/autoCategory.ts
|
||
let uuid = 1e4;
|
||
const cache$2 = {};
|
||
const RE_CATEGORY = /^(?:(\d+)\.)?([\s\S]+)$/;
|
||
let LOCALE_RE;
|
||
function autoCategory(page, options) {
|
||
const pagePath = page.filePathRelative;
|
||
const blog = isPlainObject$1(options.blog) ? options.blog : {};
|
||
const enabled = blog.categories !== false;
|
||
if (page.data.type || !pagePath || !enabled) return;
|
||
if (resolveNotesLinkList(options).some((link) => page.path.startsWith(link))) return;
|
||
LOCALE_RE ??= /* @__PURE__ */ new RegExp(`^(${Object.keys(options.locales || {}).filter((l) => l !== "/").join("|")})`);
|
||
const list = ensureLeadingSlash(pagePath).replace(LOCALE_RE, "").replace(/^\//, "").split("/").slice(0, -1);
|
||
const categoryList = list.map((category, index) => {
|
||
const match = category.match(RE_CATEGORY) || [];
|
||
if (!cache$2[match[2]] && !match[1]) cache$2[match[2]] = uuid++;
|
||
return {
|
||
id: hash$1(list.slice(0, index + 1).join("-")).slice(0, 6),
|
||
sort: Number(match[1] || cache$2[match[2]]),
|
||
name: match[2]
|
||
};
|
||
});
|
||
page.data.categoryList = blog.categoriesTransform?.(categoryList) || categoryList;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/pages/encryptPage.ts
|
||
function encryptPage(page) {
|
||
const password = toArray(page.frontmatter.password);
|
||
if (password.length) page.data._e = password.map((pwd) => genEncrypt(pwd)).join(":");
|
||
delete page.frontmatter.password;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/pages/pageBulletin.ts
|
||
function enableBulletin(page, options) {
|
||
if (isPlainObject$1(options.bulletin)) {
|
||
const enablePage = options.bulletin.enablePage;
|
||
page.data.bulletin = (isFunction(enablePage) ? enablePage(page) : enablePage) ?? true;
|
||
}
|
||
if (options.locales?.[page.pathLocale]) {
|
||
const bulletin = options.locales?.[page.pathLocale].bulletin;
|
||
if (isPlainObject$1(bulletin)) {
|
||
const enablePage = bulletin.enablePage;
|
||
page.data.bulletin = (isFunction(enablePage) ? enablePage(page) : enablePage) ?? true;
|
||
}
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/pages/extendsPage.ts
|
||
function extendsPageData(page) {
|
||
const options = getThemeConfig();
|
||
cleanPageData(page);
|
||
encryptPage(page);
|
||
autoCategory(page, options);
|
||
enableBulletin(page, options);
|
||
}
|
||
function cleanPageData(page) {
|
||
page.data.filePathRelative = page.filePathRelative;
|
||
page.routeMeta.title = page.frontmatter.title || page.title;
|
||
if (page.frontmatter.icon) page.routeMeta.icon = page.frontmatter.icon;
|
||
if (page.frontmatter.badge) page.routeMeta.badge = page.frontmatter.badge;
|
||
if (page.frontmatter.home) {
|
||
page.frontmatter.pageLayout = "home";
|
||
delete page.frontmatter.home;
|
||
}
|
||
if (page.headers) page.data.headers = [];
|
||
if (page.frontmatter.friends) {
|
||
page.frontmatter.draft = true;
|
||
page.data.type = "friends";
|
||
page.permalink = page.permalink ?? "/friends/";
|
||
page.frontmatter.pageLayout = "friends";
|
||
delete page.frontmatter.friends;
|
||
}
|
||
const pageType = page.frontmatter._pageLayout;
|
||
if (pageType) {
|
||
page.frontmatter.draft = true;
|
||
page.data.type = pageType;
|
||
delete page.frontmatter._pageLayout;
|
||
}
|
||
if (page.frontmatter.pageLayout === "blog") {
|
||
page.frontmatter.draft = true;
|
||
page.data.type = "blog";
|
||
}
|
||
if ("externalLink" in page.frontmatter) {
|
||
page.frontmatter.externalLinkIcon = page.frontmatter.externalLink;
|
||
delete page.frontmatter.externalLink;
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/plugins/code.ts
|
||
function codePlugins(pluginOptions) {
|
||
const options = getThemeConfig();
|
||
const plugins = [];
|
||
const copyCode = options.copyCode ?? pluginOptions.copyCode;
|
||
if (copyCode !== false) {
|
||
const { ignoreSelector = [],...copyCodeOptions } = copyCode || {};
|
||
plugins.push(copyCodePlugin({
|
||
ignoreSelector: uniq([
|
||
".vp-copy-ignore",
|
||
".diff.remove",
|
||
...ignoreSelector
|
||
]),
|
||
...copyCodeOptions
|
||
}));
|
||
}
|
||
const shikiOptions = options.codeHighlighter ?? pluginOptions.shiki;
|
||
if (shikiOptions !== false) {
|
||
const { twoslash, langs = [], codeBlockTitle: _,...restShikiOptions } = isPlainObject$1(shikiOptions) ? shikiOptions : {};
|
||
const twoslashOptions = twoslash === true ? {} : twoslash;
|
||
const mdPower = isPlainObject$1(pluginOptions.markdownPower) ? pluginOptions.markdownPower : {};
|
||
const getIcon = createCodeTabIconGetter(options.markdown?.codeTabs ?? mdPower.codeTabs);
|
||
plugins.push(shikiPlugin({
|
||
notationDiff: true,
|
||
notationErrorLevel: true,
|
||
notationFocus: true,
|
||
notationHighlight: true,
|
||
notationWordHighlight: true,
|
||
highlightLines: true,
|
||
collapsedLines: false,
|
||
langs: uniq([...twoslash ? [
|
||
"ts",
|
||
"js",
|
||
"vue",
|
||
"json",
|
||
"bash",
|
||
"sh"
|
||
] : [], ...langs]),
|
||
codeBlockTitle: (title, code) => {
|
||
const icon = getIcon(title);
|
||
return `<div class="code-block-title" data-title="${title}"><div class="code-block-title-bar"><span class="title">${icon ? `<VPIcon provider="iconify" name="${icon}"/>` : ""}${title}</span></div>${code}</div>`;
|
||
},
|
||
twoslash: isPlainObject$1(twoslashOptions) ? {
|
||
...twoslashOptions,
|
||
floatingVue: {
|
||
classMarkdown: "vp-doc",
|
||
...twoslashOptions.floatingVue
|
||
}
|
||
} : twoslashOptions,
|
||
..."theme" in restShikiOptions ? {} : { themes: {
|
||
light: "vitesse-light",
|
||
dark: "vitesse-dark"
|
||
} },
|
||
...restShikiOptions
|
||
}));
|
||
}
|
||
return plugins;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/plugins/markdown.ts
|
||
function markdownPlugins(pluginOptions) {
|
||
const options = getThemeConfig();
|
||
const plugins = [];
|
||
let { hint, image, include, math, mdChart, mdPower } = splitMarkdownOptions(options.markdown ?? {});
|
||
plugins.push(markdownHintPlugin({
|
||
hint: hint.hint ?? true,
|
||
alert: hint.alert ?? true,
|
||
injectStyles: false
|
||
}));
|
||
if (pluginOptions.markdownPower !== false) {
|
||
const shikiOptions = options.codeHighlighter ?? pluginOptions.shiki;
|
||
const shikiTheme = shikiOptions && "theme" in shikiOptions ? shikiOptions.theme : shikiOptions && "themes" in shikiOptions ? shikiOptions.themes : {
|
||
light: "vitesse-light",
|
||
dark: "vitesse-dark"
|
||
};
|
||
const repl = mdPower?.repl ?? pluginOptions.markdownPower?.repl;
|
||
plugins.push(markdownPowerPlugin({
|
||
fileTree: true,
|
||
plot: true,
|
||
icons: true,
|
||
...pluginOptions.markdownPower || {},
|
||
...mdPower,
|
||
repl: repl ? {
|
||
theme: shikiTheme,
|
||
...repl
|
||
} : repl
|
||
}));
|
||
}
|
||
mdChart ??= pluginOptions.markdownChart;
|
||
if (mdChart) plugins.push(markdownChartPlugin(mdChart));
|
||
math ??= pluginOptions.markdownMath;
|
||
if (math !== false) plugins.push(markdownMathPlugin(math ?? { type: "katex" }));
|
||
image ??= pluginOptions.markdownImage;
|
||
if (image) plugins.push(markdownImagePlugin(image));
|
||
include ??= pluginOptions.markdownInclude;
|
||
if (include !== false) plugins.push(markdownIncludePlugin(isPlainObject$1(include) ? include : {}));
|
||
return plugins;
|
||
}
|
||
function splitMarkdownOptions(options) {
|
||
const { hint, alert, oldDemo, image, include, math,...restOptions } = options;
|
||
const mdChart = {};
|
||
const mdPower = {};
|
||
for (const key in restOptions) if (MARKDOWN_CHART_FIELDS.includes(key)) mdChart[key] = restOptions[key];
|
||
else if (MARKDOWN_POWER_FIELDS.includes(key)) mdPower[key] = restOptions[key];
|
||
const mdChartKeys = Object.keys(mdChart);
|
||
return {
|
||
hint: {
|
||
hint,
|
||
alert
|
||
},
|
||
image,
|
||
include,
|
||
math,
|
||
mdChart: mdChartKeys.length && mdChartKeys.some((key) => mdChart[key] !== false) ? mdChart : false,
|
||
mdPower
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/plugins/git.ts
|
||
function gitPlugin$1(app, pluginOptions) {
|
||
const options = getThemeConfig();
|
||
if (!(pluginOptions.git ?? app.env.isBuild)) return [gitPlugin({
|
||
createdTime: false,
|
||
updatedTime: false,
|
||
contributors: false,
|
||
changelog: false
|
||
})];
|
||
const excludes = [
|
||
"home",
|
||
"friends",
|
||
"page",
|
||
"custom",
|
||
false
|
||
];
|
||
const changelogOptions = isPlainObject$1(options.changelog) ? options.changelog : {};
|
||
return [gitPlugin({
|
||
updatedTime: options.lastUpdated !== false,
|
||
contributors: isPlainObject$1(options.contributors) || options.contributors === true ? {
|
||
avatar: true,
|
||
...options.contributors === true ? {} : options.contributors
|
||
} : false,
|
||
changelog: options.changelog && options.docsRepo ? {
|
||
repoUrl: options.docsRepo,
|
||
...changelogOptions
|
||
} : options.changelog,
|
||
filter(page) {
|
||
if (page.frontmatter.home || excludes.includes(page.frontmatter.pageLayout)) return false;
|
||
return true;
|
||
}
|
||
})];
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/plugins/setupPlugins.ts
|
||
function setupPlugins(app, pluginOptions) {
|
||
const isProd = app.env.isBuild;
|
||
const options = getThemeConfig();
|
||
const hostname = options.hostname;
|
||
const plugins = [
|
||
fontsPlugin(),
|
||
...codePlugins(pluginOptions),
|
||
...markdownPlugins(pluginOptions)
|
||
];
|
||
if (pluginOptions.nprogress !== false) plugins.push(nprogressPlugin());
|
||
const readingTime = options.readingTime ?? pluginOptions.readingTime;
|
||
if (readingTime !== false) plugins.push(readingTimePlugin({
|
||
locales: { "/zh/": {
|
||
word: "$word 字",
|
||
less1Minute: "小于 1 分钟",
|
||
time: "约 $time 分钟"
|
||
} },
|
||
...readingTime
|
||
}));
|
||
if (pluginOptions.photoSwipe !== false) plugins.push(photoSwipePlugin({
|
||
selector: ".vp-doc :not(a) > img:not([no-view],.no-view,.ignore)",
|
||
...pluginOptions.photoSwipe
|
||
}));
|
||
/**
|
||
* 内容水印
|
||
*/
|
||
const watermark = options.watermark ?? pluginOptions.watermark;
|
||
if (watermark) plugins.push(watermarkPlugin({
|
||
enabled: true,
|
||
...isPlainObject$1(watermark) ? watermark : {}
|
||
}));
|
||
/**
|
||
* 文章评论
|
||
*/
|
||
const comment = options.comment ?? pluginOptions.comment;
|
||
if (comment) plugins.push(commentPlugin(comment));
|
||
if (options.search !== false) {
|
||
const search = (options.search === true ? { provider: "local" } : options.search) ?? (pluginOptions.docsearch ? {
|
||
provider: "algolia",
|
||
...pluginOptions.docsearch
|
||
} : {
|
||
provider: "local",
|
||
...isPlainObject$1(pluginOptions.search) ? pluginOptions.search : {}
|
||
});
|
||
const { provider,...searchOptions } = search;
|
||
if (provider === "algolia") if (search.appId && search.apiKey) plugins.push(docsearchPlugin(searchOptions));
|
||
else console.error("docsearch plugin: appId and apiKey are both required");
|
||
else plugins.push(searchPlugin(searchOptions));
|
||
}
|
||
/**
|
||
* git 插件配置
|
||
* 1. 最后更新时间
|
||
* 2. 贡献者列表
|
||
* 3. 更新日志
|
||
*/
|
||
plugins.push(...gitPlugin$1(app, pluginOptions));
|
||
/**
|
||
* 资源替换
|
||
*/
|
||
const replaceAssets = options.replaceAssets ?? pluginOptions.replaceAssets;
|
||
if (replaceAssets) plugins.push(replaceAssetsPlugin(replaceAssets));
|
||
/**
|
||
* 站点地图,仅在生产构建时,且 hostname 存在时生效
|
||
*/
|
||
if (pluginOptions.sitemap !== false && isProd) {
|
||
const sitemapOptions = isPlainObject$1(pluginOptions.sitemap) ? pluginOptions.sitemap : {};
|
||
sitemapOptions.hostname ||= hostname;
|
||
if (sitemapOptions.hostname) plugins.push(sitemapPlugin(sitemapOptions));
|
||
}
|
||
/**
|
||
* SEO,仅在生产构建时,且 hostname 存在时生效
|
||
*/
|
||
if (pluginOptions.seo !== false && hostname && isProd) {
|
||
const seoOptions = isPlainObject$1(pluginOptions.seo) ? pluginOptions.seo : {};
|
||
seoOptions.hostname ||= hostname;
|
||
if (seoOptions.hostname) plugins.push(seoPlugin(seoOptions));
|
||
}
|
||
/**
|
||
* 编译缓存,默认使用文件缓存
|
||
*/
|
||
if (options.cache !== false) plugins.push(cachePlugin({
|
||
...isPlainObject$1(pluginOptions.cache) ? pluginOptions.cache : {},
|
||
type: options.cache || "filesystem"
|
||
}));
|
||
return plugins;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareArticleTagColor.ts
|
||
const PRESET = [
|
||
[
|
||
"#6aa1b7",
|
||
"#5086a1",
|
||
"rgba(131, 208, 218, 0.314)"
|
||
],
|
||
[
|
||
"#299764",
|
||
"#18794e",
|
||
"rgba(16, 185, 129, 0.14)"
|
||
],
|
||
[
|
||
"#946300",
|
||
"#915930",
|
||
"rgba(234, 179, 8, 0.14)"
|
||
],
|
||
[
|
||
"#d5393e",
|
||
"#b8272c",
|
||
"rgba(244, 63, 94, 0.14)"
|
||
],
|
||
[
|
||
"#7e4cc9",
|
||
"#6f42c1",
|
||
"rgba(159, 122, 234, 0.14)"
|
||
],
|
||
[
|
||
"#3a5ccc",
|
||
"#3451b2",
|
||
"rgba(100, 108, 255, 0.14)"
|
||
],
|
||
[
|
||
"#fab10f",
|
||
"#f39c12",
|
||
"rgba(255, 213, 0, 0.14)"
|
||
],
|
||
[
|
||
"#cc6699",
|
||
"#be3f7f",
|
||
"rgba(161, 54, 107, 0.14)"
|
||
],
|
||
[
|
||
"#55aaee",
|
||
"#2391e9",
|
||
"rgba(21, 123, 206, 0.1333)"
|
||
],
|
||
[
|
||
"#9933cc",
|
||
"#aa56d5",
|
||
"rgba(179, 102, 217, 0.2)"
|
||
],
|
||
[
|
||
"#cc3366",
|
||
"#d55680",
|
||
"rgba(217, 102, 140, 0.2)"
|
||
],
|
||
[
|
||
"#cc9933",
|
||
"#be7f3f",
|
||
"rgba(161, 107, 54, 0.2)"
|
||
],
|
||
[
|
||
"#9966cc",
|
||
"#7171b8",
|
||
"rgba(83, 83, 167, 0.14)"
|
||
],
|
||
[
|
||
"#66cccc",
|
||
"#3fbebe",
|
||
"rgba(54, 161, 161, 0.14)"
|
||
],
|
||
[
|
||
"#3366cc",
|
||
"#5680d5",
|
||
"rgba(102, 140, 217, 0.14)"
|
||
],
|
||
[
|
||
"#339999",
|
||
"#41c0c0",
|
||
"rgba(83, 198, 198, 0.2)"
|
||
],
|
||
[
|
||
"#a6623b",
|
||
"#c17950",
|
||
"rgba(199, 134, 97, 0.2411)"
|
||
],
|
||
[
|
||
"#8ecaef",
|
||
"#55afe7",
|
||
"rgba(42, 155, 225, 0.147)"
|
||
]
|
||
];
|
||
const cache$1 = {};
|
||
async function prepareArticleTagColors(app) {
|
||
perf.mark("prepare:tag-colors");
|
||
const options = getThemeConfig();
|
||
const blog = isPlainObject$2(options.blog) ? options.blog : {};
|
||
const { js, css } = genCode(app, blog.tagsTheme ?? "colored");
|
||
await writeTemp(app, "internal/articleTagColors.css", css);
|
||
await writeTemp(app, "internal/articleTagColors.js", js);
|
||
perf.log("prepare:tag-colors");
|
||
}
|
||
function genCode(app, theme) {
|
||
const articleTagColors = {};
|
||
const tagList = /* @__PURE__ */ new Set();
|
||
if (theme !== "colored") return {
|
||
js: resolveContent(app, {
|
||
name: "articleTagColors",
|
||
content: articleTagColors
|
||
}),
|
||
css: ""
|
||
};
|
||
app.pages.forEach((page) => {
|
||
const { frontmatter: { tags } } = page;
|
||
if (tags) toArray(tags).forEach((tag) => {
|
||
if (tag) tagList.add(tag);
|
||
});
|
||
});
|
||
tagList.forEach((tag) => {
|
||
const code = getTagCode(tag);
|
||
if (!cache$1[code]) cache$1[code] = nanoid(4);
|
||
if (!articleTagColors[tag]) articleTagColors[tag] = cache$1[code];
|
||
});
|
||
const js = resolveContent(app, {
|
||
name: "articleTagColors",
|
||
content: articleTagColors,
|
||
before: `import './articleTagColors.css'`
|
||
});
|
||
const css = genCSS();
|
||
return {
|
||
js,
|
||
css
|
||
};
|
||
}
|
||
function getTagCode(tag) {
|
||
tag = tag.toLowerCase();
|
||
let code = 0;
|
||
for (let i = 0; i < tag.length; i++) code += tag.charCodeAt(i);
|
||
return code % PRESET.length;
|
||
}
|
||
function genCSS() {
|
||
let css = "";
|
||
for (const [code, className] of Object.entries(cache$1)) {
|
||
const [color, hoverColor, backgroundColor] = PRESET[Number(code)];
|
||
css += `\
|
||
.vp-tag-${className} {
|
||
--vp-tag-color: ${color};
|
||
--vp-tag-hover-color: ${hoverColor};
|
||
--vp-tag-bg: ${backgroundColor};
|
||
}
|
||
`;
|
||
}
|
||
return css;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareEncrypt.ts
|
||
const isStringLike = (value) => isString(value) || isNumber(value);
|
||
const separator = ":";
|
||
let contentHash = "";
|
||
let fsCache$1 = null;
|
||
async function prepareEncrypt(app) {
|
||
perf.mark("prepare:encrypt");
|
||
const { encrypt } = getThemeConfig();
|
||
if (!fsCache$1 && app.env.isDev) {
|
||
fsCache$1 = createFsCache(app, "encrypt");
|
||
await fsCache$1.read();
|
||
}
|
||
contentHash = fsCache$1?.data?.[0] ?? "";
|
||
let resolvedEncrypt = fsCache$1?.data?.[1];
|
||
const currentHash = encrypt ? hash$1(JSON.stringify(encrypt)) : "";
|
||
if (!contentHash || contentHash !== currentHash || !resolvedEncrypt) {
|
||
contentHash = currentHash;
|
||
resolvedEncrypt = resolveEncrypt(encrypt);
|
||
}
|
||
await writeTemp(app, "internal/encrypt.js", resolveContent(app, {
|
||
name: "encrypt",
|
||
content: resolvedEncrypt
|
||
}));
|
||
fsCache$1?.write([currentHash, resolvedEncrypt]);
|
||
perf.log("prepare:encrypt");
|
||
}
|
||
function resolveEncrypt(encrypt) {
|
||
const admin = encrypt?.admin ? toArray(encrypt.admin).filter(isStringLike).map((item) => genEncrypt(item)).join(separator) : "";
|
||
const rules = {};
|
||
const keys = Object.keys(encrypt?.rules ?? {});
|
||
if (encrypt?.rules) Object.keys(encrypt.rules).forEach((key) => {
|
||
const index = keys.indexOf(key);
|
||
rules[String(index)] = toArray(encrypt.rules[key]).filter(isStringLike).map((item) => genEncrypt(item)).join(separator);
|
||
});
|
||
return [
|
||
encrypt?.global ?? false,
|
||
separator,
|
||
admin,
|
||
keys,
|
||
rules
|
||
];
|
||
}
|
||
function isEncryptPage(page, encrypt) {
|
||
if (!encrypt) return false;
|
||
if (page.data._e) return true;
|
||
const rules = encrypt.rules ?? {};
|
||
return Object.keys(rules).some((match) => {
|
||
const relativePath = page.data.filePathRelative || "";
|
||
if (match[0] === "^") {
|
||
const regex = new RegExp(match);
|
||
return regex.test(page.path) || relativePath && regex.test(relativePath);
|
||
}
|
||
if (match.endsWith(".md")) return relativePath && relativePath.endsWith(match);
|
||
return page.path.startsWith(match) || relativePath.startsWith(match);
|
||
});
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareBlogData.ts
|
||
const HEADING_RE = /<h(\d)[^>]*>.*?<\/h\1>/gi;
|
||
const EXCERPT_SPLIT = "<!-- more -->";
|
||
function getTimestamp(time) {
|
||
return new Date(time).getTime();
|
||
}
|
||
async function preparedBlogData(app) {
|
||
const isBuild = app.env.isBuild;
|
||
const options = getThemeConfig();
|
||
const encrypt = options.encrypt;
|
||
if (options.blog === false) {
|
||
const content$1 = resolveContent(app, {
|
||
name: "blogPostData",
|
||
content: []
|
||
});
|
||
await writeTemp(app, "internal/blogData.js", content$1);
|
||
return;
|
||
}
|
||
perf.mark("prepare:blog-data");
|
||
const blog = options.blog || {};
|
||
const notesDirList = resolveNotesOptions(options).map((notes) => removeLeadingSlash(normalizePath(`${notes.dir}/**`))).filter(Boolean);
|
||
const filter = createFilter(blog.include ?? ["**/*.md"], [
|
||
"**/{README,readme,index}.md",
|
||
"**/.vuepress/**",
|
||
"**/node_modules/**",
|
||
...blog.exclude ?? [],
|
||
...notesDirList
|
||
].filter(Boolean), { resolve: false });
|
||
const blogData = app.pages.filter((page) => page.filePathRelative && filter(page.filePathRelative) && page.frontmatter.article !== false && (page.frontmatter.draft === true ? !isBuild : true)).sort((prev, next) => getTimestamp(prev.frontmatter.createTime || prev.date) < getTimestamp(next.frontmatter.createTime || next.date) ? 1 : -1).map((page) => {
|
||
const tags = page.frontmatter.tags;
|
||
const date = page.frontmatter.createTime || page.frontmatter.date || (page.date === "0000-00-00" ? fs$1.statSync(page.filePath).birthtime : page.date);
|
||
const data = {
|
||
path: page.path,
|
||
title: page.title,
|
||
categoryList: page.data.categoryList,
|
||
tags,
|
||
sticky: page.frontmatter.sticky,
|
||
createTime: dayjs(new Date(date)).format("YYYY/MM/DD HH:mm:ss"),
|
||
lang: page.lang,
|
||
excerpt: "",
|
||
cover: page.frontmatter.cover,
|
||
coverStyle: page.frontmatter.coverStyle
|
||
};
|
||
if (typeof data.cover === "object") logger.warn(`cover should be a path string, please use string instead. (${page.filePathRelative})`);
|
||
if (isEncryptPage(page, encrypt)) data.encrypt = true;
|
||
if (page.frontmatter.draft && !isBuild) data.draft = true;
|
||
const fmExcerpt = page.frontmatter.excerpt;
|
||
if (fmExcerpt !== false) {
|
||
if (typeof fmExcerpt === "string") data.excerpt = fmExcerpt;
|
||
else if (page.contentRendered.includes(EXCERPT_SPLIT)) {
|
||
let excerpt = page.contentRendered.split(EXCERPT_SPLIT)[0];
|
||
excerpt = excerpt.replace(HEADING_RE, "");
|
||
data.excerpt = excerpt;
|
||
}
|
||
}
|
||
return data;
|
||
});
|
||
const content = resolveContent(app, {
|
||
name: "blogPostData",
|
||
content: blogData
|
||
});
|
||
await writeTemp(app, "internal/blogData.js", content);
|
||
perf.log("prepare:blog-data");
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareIcons.ts
|
||
const ICON_REGEXP = /<(?:VP)?(Icon|Card|LinkCard|Button)([^>]*)>/g;
|
||
const ICON_NAME_REGEXP = /(?:name|icon|suffix-icon)="([^"]+)"/;
|
||
const URL_CONTENT_REGEXP = /(url\([\s\S]+\))/;
|
||
const ICONIFY_NAME = /^[\w-]+:[\w-]+$/;
|
||
const JS_FILENAME = "internal/iconify.js";
|
||
const CSS_FILENAME = "internal/iconify.css";
|
||
const isInstalled = isPackageExists("@iconify/json");
|
||
let locate;
|
||
let fsCache = null;
|
||
const cache = {};
|
||
async function prepareIcons(app) {
|
||
perf.mark("prepare:icons:total");
|
||
const options = getThemeConfig();
|
||
if (!isInstalled) {
|
||
await writeTemp(app, JS_FILENAME, resolveContent(app, {
|
||
name: "icons",
|
||
content: "{}"
|
||
}));
|
||
return;
|
||
}
|
||
if (!fsCache && app.env.isDev) {
|
||
fsCache = createFsCache(app, "iconify");
|
||
await fsCache.read();
|
||
}
|
||
perf.mark("prepare:pages:icons");
|
||
const iconOptions = options.markdown?.icon || {};
|
||
const iconList = [];
|
||
app.pages.forEach((page) => iconList.push(...getIconsWithPage(page, iconOptions)));
|
||
iconList.push(...getIconWithThemeConfig(options, iconOptions));
|
||
const collectMap = {};
|
||
uniq(iconList).filter((icon) => {
|
||
if (fsCache?.data?.[icon] && !cache[icon]) cache[icon] = fsCache.data[icon];
|
||
return !cache[icon];
|
||
}).forEach((iconName) => {
|
||
const [collect, name] = iconName.split(":");
|
||
if (!collectMap[collect]) collectMap[collect] = [];
|
||
collectMap[collect].push(name);
|
||
});
|
||
perf.log("prepare:pages:icons");
|
||
perf.mark("prepare:icons:imports");
|
||
if (!locate) locate = (await interopDefault(import("@iconify/json"))).locate;
|
||
const unknownList = (await Promise.all(entries(collectMap).map(([collect, names]) => resolveCollect(collect, names)))).flat();
|
||
if (unknownList.length) logger.warn(`[iconify] Unknown icons: ${unknownList.join(", ")}`);
|
||
perf.log("prepare:icons:imports");
|
||
let cssCode = "";
|
||
const map = {};
|
||
for (const [iconName, { className, content, background }] of entries(cache)) {
|
||
map[iconName] = `${className}${background ? " bg" : ""}`;
|
||
cssCode += `.${className} {\n --icon: ${content};\n}\n`;
|
||
}
|
||
await Promise.all([writeTemp(app, CSS_FILENAME, cssCode), writeTemp(app, JS_FILENAME, resolveContent(app, {
|
||
name: "icons",
|
||
content: map,
|
||
before: `import './iconify.css'`
|
||
}))]);
|
||
fsCache?.write(cache);
|
||
perf.log("prepare:icons:total");
|
||
}
|
||
function isIconify(icon) {
|
||
if (!icon || typeof icon !== "string" || isLinkAbsolute(icon) || isLinkHttp(icon)) return false;
|
||
return icon[0] !== "{" && ICONIFY_NAME.test(icon);
|
||
}
|
||
function withPrefix(icon, prefix) {
|
||
if (!prefix) return icon;
|
||
return icon.includes(":") ? icon : `${prefix}:${icon}`;
|
||
}
|
||
function getIconsWithPage(page, { provider = "iconify", prefix }) {
|
||
const list = [];
|
||
const matches = page.contentRendered.match(ICON_REGEXP) || [];
|
||
for (const matched of matches) if (provider === "iconify" || matched.includes("provider=\"iconify\"")) {
|
||
const icon = matched.match(ICON_NAME_REGEXP)?.[1];
|
||
if (isIconify(icon)) list.push(withPrefix(icon, prefix));
|
||
}
|
||
const addIcon = (icon) => {
|
||
if (isIconify(icon) && (provider === "iconify" || icon.startsWith("iconify"))) list.push(withPrefix(icon.replace(/^iconify /, ""), prefix));
|
||
};
|
||
const fm = page.frontmatter;
|
||
addIcon(fm.icon);
|
||
if ((fm.home || fm.pageLayout === "home") && fm.config?.length) for (const config of fm.config) {
|
||
if (config.type === "features" && config.features.length) for (const feature of config.features) addIcon(feature.icon);
|
||
if (config.type === "hero" && config.hero?.actions?.length) for (const action of config.hero.actions) {
|
||
addIcon(action.icon);
|
||
addIcon(action.suffixIcon);
|
||
}
|
||
}
|
||
return list;
|
||
}
|
||
function getIconWithThemeConfig(options, { provider = "iconify", prefix }) {
|
||
const list = [];
|
||
const locales = options.locales || {};
|
||
entries(locales).forEach(([, { navbar, sidebar, notes }]) => {
|
||
if (navbar) list.push(...getIconWithNavbar(navbar));
|
||
const sidebarList = Object.values(sidebar || {});
|
||
if (notes) notes.notes.forEach((note) => {
|
||
if (note.sidebar) sidebarList.push(note.sidebar);
|
||
});
|
||
sidebarList.forEach((sidebar$1) => list.push(...getIconWithSidebar(sidebar$1)));
|
||
});
|
||
const addIcon = (icon) => {
|
||
if (isIconify(icon) && (provider === "iconify" || icon.startsWith("iconify"))) return withPrefix(icon.replace(/^iconify /, ""), prefix);
|
||
};
|
||
return list.map(addIcon).filter(Boolean);
|
||
}
|
||
function getIconWithNavbar(navbar) {
|
||
const list = [];
|
||
navbar.forEach((item) => {
|
||
if (typeof item !== "string") {
|
||
if (isIconify(item.icon)) list.push(item.icon);
|
||
if (item.items?.length) list.push(...getIconWithNavbar(item.items));
|
||
}
|
||
});
|
||
return list;
|
||
}
|
||
function getIconWithSidebar(sidebar) {
|
||
const list = [];
|
||
if (isArray(sidebar)) sidebar.forEach((item) => {
|
||
if (typeof item !== "string") {
|
||
if (isIconify(item.icon)) list.push(item.icon);
|
||
if (item.items?.length) list.push(...getIconWithSidebar(item.items));
|
||
}
|
||
});
|
||
else if (isPlainObject$1(sidebar)) entries(sidebar).forEach(([, item]) => {
|
||
if (typeof item !== "string") {
|
||
if (isArray(item)) list.push(...getIconWithSidebar(item));
|
||
else if (item.items?.length) list.push(...getIconWithSidebar(item.items));
|
||
}
|
||
});
|
||
return list;
|
||
}
|
||
async function resolveCollect(collect, names) {
|
||
const filepath = locate(collect);
|
||
const config = await readJSON(filepath);
|
||
if (!config) {
|
||
logger.warn(`[iconify] Can not find icon collect: ${collect}!`);
|
||
return [];
|
||
}
|
||
const unknownList = [];
|
||
for (const name of names) {
|
||
const data = getIconData(config, name);
|
||
const icon = `${collect}:${name}`;
|
||
if (!data) unknownList.push(icon);
|
||
else if (!cache[icon]) {
|
||
const matched = getIconContentCSS(data, { height: data.height || 24 }).match(URL_CONTENT_REGEXP)?.[1] ?? "";
|
||
/**
|
||
* @see - https://iconify.design/docs/libraries/utils/get-icon-css.html#options
|
||
*/
|
||
const background = !data.body.includes("currentColor");
|
||
cache[icon] = {
|
||
className: `vpi-${nanoid()}`,
|
||
background,
|
||
content: matched
|
||
};
|
||
}
|
||
}
|
||
return unknownList;
|
||
}
|
||
async function readJSON(filepath) {
|
||
try {
|
||
return await fs.readJSON(filepath, "utf-8");
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareSidebar.ts
|
||
async function prepareSidebar(app) {
|
||
perf.mark("prepare:sidebar");
|
||
const options = getThemeConfig();
|
||
const sidebar = getAllSidebar(options);
|
||
const { resolved, autoHome } = getSidebarData(app, sidebar);
|
||
sidebar.__auto__ = resolved;
|
||
sidebar.__home__ = autoHome;
|
||
await writeTemp(app, "internal/sidebar.js", resolveContent(app, {
|
||
name: "sidebar",
|
||
content: sidebar
|
||
}));
|
||
perf.log("prepare:sidebar");
|
||
}
|
||
function getSidebarData(app, locales) {
|
||
const autoDirList = [];
|
||
const resolved = {};
|
||
entries(locales).forEach(([localePath, sidebar]) => {
|
||
if (!sidebar) return;
|
||
if (isArray$1(sidebar)) autoDirList.push(...findAutoDirList(sidebar));
|
||
else if (isPlainObject$1(sidebar)) entries(sidebar).forEach(([dirname, config]) => {
|
||
const prefix = normalizeLink(localePath, dirname);
|
||
if (config === "auto") autoDirList.push(prefix);
|
||
else if (isArray$1(config)) autoDirList.push(...findAutoDirList(config, prefix));
|
||
else if (config.items === "auto") autoDirList.push(normalizeLink(prefix, config.prefix));
|
||
else autoDirList.push(...findAutoDirList(config.items || [], normalizeLink(prefix, config.prefix)));
|
||
});
|
||
else if (sidebar === "auto") autoDirList.push(localePath);
|
||
});
|
||
const autoHome = {};
|
||
autoDirList.forEach((localePath) => {
|
||
const { link, sidebar } = getAutoDirSidebar(app, localePath);
|
||
resolved[localePath] = sidebar;
|
||
if (link) autoHome[localePath] = link;
|
||
});
|
||
return {
|
||
resolved,
|
||
autoHome
|
||
};
|
||
}
|
||
const MD_RE = /\.md$/;
|
||
const NUMBER_RE = /^\d+\./;
|
||
function resolveTitle(dirname) {
|
||
return dirname.replace(MD_RE, "").replace(NUMBER_RE, "");
|
||
}
|
||
const RE_FILE_SORTING = /(?:(\d+)\.)?(?=[^/]+$)/;
|
||
function fileSorting(filepath) {
|
||
if (!filepath) return false;
|
||
const matched = filepath.match(RE_FILE_SORTING);
|
||
const sorted = matched ? Number(matched[1]) : 0;
|
||
if (Number.isNaN(sorted)) return Number.MAX_SAFE_INTEGER;
|
||
return sorted;
|
||
}
|
||
function getAutoDirSidebar(app, localePath) {
|
||
const locale = removeLeadingSlash(localePath);
|
||
let pages = app.pages.filter((page) => page.data.filePathRelative?.startsWith(locale)).map((page) => {
|
||
return {
|
||
...page,
|
||
splitPath: page.data.filePathRelative?.split("/") || []
|
||
};
|
||
});
|
||
let nowIndex = Math.max(...pages.map((page) => page.splitPath.length)) - 1;
|
||
while (nowIndex >= 0) {
|
||
pages = pages.sort((prev, next) => {
|
||
const pi = fileSorting(prev.splitPath?.[nowIndex]);
|
||
const ni = fileSorting(next.splitPath?.[nowIndex]);
|
||
if (pi === false || ni === false) return 0;
|
||
if (pi === ni) return 0;
|
||
return pi < ni ? -1 : 1;
|
||
});
|
||
nowIndex--;
|
||
}
|
||
const RE_INDEX = [
|
||
"index.md",
|
||
"README.md",
|
||
"readme.md"
|
||
];
|
||
const sidebar = [];
|
||
let rootLink = "";
|
||
for (const page of pages) {
|
||
const { data, title, path: path$2, frontmatter } = page;
|
||
const paths = (data.filePathRelative || "").slice(localePath.replace(/^\/|\/$/g, "").length + 1).split("/");
|
||
let index = 0;
|
||
let dir;
|
||
let items = sidebar;
|
||
let parent;
|
||
while (dir = paths[index]) {
|
||
const text = resolveTitle(dir);
|
||
const isHome = RE_INDEX.includes(dir);
|
||
let current = items.find((item) => item.text === text);
|
||
if (!current) {
|
||
current = {
|
||
text,
|
||
link: void 0,
|
||
items: []
|
||
};
|
||
if (!isHome) items.push(current);
|
||
}
|
||
if (dir.endsWith(".md")) if (isHome) if (parent) parent.link = path$2;
|
||
else rootLink = path$2;
|
||
else {
|
||
current.link = path$2;
|
||
current.text = title;
|
||
}
|
||
if (frontmatter.icon) current.icon = frontmatter.icon;
|
||
if (parent?.items?.length) parent.collapsed = false;
|
||
parent = current;
|
||
items = current.items;
|
||
index++;
|
||
}
|
||
}
|
||
return {
|
||
link: rootLink,
|
||
sidebar: cleanSidebar(sidebar)
|
||
};
|
||
}
|
||
function cleanSidebar(sidebar) {
|
||
for (const item of sidebar) if (isPlainObject$1(item)) {
|
||
if (isArray$1(item.items)) if (item.items.length === 0) delete item.items;
|
||
else cleanSidebar(item.items);
|
||
}
|
||
return sidebar;
|
||
}
|
||
function findAutoDirList(sidebar, prefix = "") {
|
||
const list = [];
|
||
if (!sidebar.length) return list;
|
||
sidebar.forEach((item) => {
|
||
if (isPlainObject$1(item)) {
|
||
const nextPrefix = normalizeLink(prefix, item.prefix || item.dir);
|
||
if (item.items === "auto") list.push(nextPrefix);
|
||
else if (item.items?.length) list.push(...findAutoDirList(item.items, nextPrefix));
|
||
}
|
||
});
|
||
return list;
|
||
}
|
||
function getAllSidebar(options) {
|
||
const locales = {};
|
||
for (const [locale, opt] of entries(options.locales || {})) {
|
||
const notes = locale === "/" ? opt.notes || options.notes : opt.notes;
|
||
const sidebar = locale === "/" ? opt.sidebar || options.sidebar : opt.sidebar;
|
||
locales[locale] = {};
|
||
for (const [key, value] of entries(sidebar || {})) locales[locale][normalizeLink(key)] = value;
|
||
if (notes && notes.notes?.length) {
|
||
const prefix = notes.link || "";
|
||
for (const note of notes.notes) if (note.sidebar) locales[locale][normalizeLink(prefix, note.link || "/")] = {
|
||
items: note.sidebar,
|
||
prefix: normalizeLink(notes.dir, note.dir)
|
||
};
|
||
}
|
||
}
|
||
return locales;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/index.ts
|
||
async function prepareData(app) {
|
||
perf.mark("prepare:data");
|
||
await Promise.all([
|
||
prepareArticleTagColors(app),
|
||
preparedBlogData(app),
|
||
prepareSidebar(app),
|
||
prepareEncrypt(app),
|
||
prepareIcons(app)
|
||
]);
|
||
perf.log("prepare:data");
|
||
}
|
||
function watchPrepare(app, watchers) {
|
||
const pagesWatcher = watch("pages", {
|
||
cwd: app.dir.temp(),
|
||
ignoreInitial: true,
|
||
ignored: (filepath, stats) => Boolean(stats?.isFile()) && !filepath.endsWith(".js")
|
||
});
|
||
watchers.push(pagesWatcher);
|
||
pagesWatcher.on("change", () => prepareData(app));
|
||
pagesWatcher.on("add", () => prepareData(app));
|
||
pagesWatcher.on("unlink", () => prepareData(app));
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/prepare/prepareThemeData.ts
|
||
let bulletinFileWatcher = null;
|
||
const bulletinFiles = {};
|
||
process.on("exit", () => bulletinFileWatcher?.close());
|
||
async function prepareThemeData(app, plugins) {
|
||
perf.mark("prepare:theme-data");
|
||
const options = getThemeConfig();
|
||
const resolvedThemeData = resolveThemeData(app, options);
|
||
await processProfileImageSize(app, resolvedThemeData, plugins);
|
||
if (bulletinFileWatcher) {
|
||
bulletinFileWatcher.close();
|
||
bulletinFileWatcher = null;
|
||
}
|
||
await resolveBulletin(app, resolvedThemeData);
|
||
await updateThemeData(app, resolvedThemeData);
|
||
perf.log("prepare:theme-data");
|
||
}
|
||
async function updateThemeData(app, themeData) {
|
||
const content = resolveContent(app, {
|
||
name: "themeData",
|
||
content: themeData
|
||
});
|
||
await writeTemp(app, "internal/themePlumeData.js", content);
|
||
}
|
||
async function resolveBulletin(app, themeData) {
|
||
if (themeData.bulletin === true) themeData.bulletin = {};
|
||
if (themeData.bulletin) themeData.bulletin.id ||= hash(themeData.bulletin);
|
||
if (themeData.bulletin) {
|
||
if (bulletinFiles.root || themeData.bulletin.contentFile) {
|
||
bulletinFiles.root = themeData.bulletin.contentFile || bulletinFiles.root;
|
||
delete themeData.bulletin.contentFile;
|
||
themeData.bulletin.content = await readBulletinFile(app, bulletinFiles.root);
|
||
} else if (themeData.bulletin.content) {
|
||
const type = themeData.bulletin.contentType ?? "text";
|
||
themeData.bulletin.content = type === "markdown" ? app.markdown.render(themeData.bulletin.content, {
|
||
filepath: app.dir.source(`/_bulletin.md`),
|
||
filePathRelative: `_bulletin.md`
|
||
}) : themeData.bulletin.content;
|
||
}
|
||
}
|
||
if (themeData.locales) for (const locale of Object.keys(themeData.locales)) {
|
||
if (themeData.locales[locale].bulletin === true) themeData.locales[locale].bulletin = {};
|
||
if (themeData.locales[locale].bulletin) themeData.locales[locale].bulletin.id ||= hash(themeData.locales[locale].bulletin);
|
||
if (!themeData.locales[locale].bulletin) continue;
|
||
if (bulletinFiles[locale] || themeData.locales[locale].bulletin.contentFile) {
|
||
bulletinFiles[locale] = themeData.locales[locale].bulletin?.contentFile || bulletinFiles[locale];
|
||
delete themeData.locales[locale].bulletin.contentFile;
|
||
themeData.locales[locale].bulletin.content = await readBulletinFile(app, bulletinFiles[locale], locale);
|
||
} else if (themeData.locales[locale].bulletin.content) {
|
||
const type = themeData.locales[locale].bulletin.contentType ?? "text";
|
||
themeData.locales[locale].bulletin.content = type === "markdown" ? app.markdown.render(themeData.locales[locale].bulletin.content, {
|
||
filepath: app.dir.source(`${locale}_bulletin.md`),
|
||
filePathRelative: `${locale.slice(1)}_bulletin.md`
|
||
}) : themeData.locales[locale].bulletin.content;
|
||
}
|
||
}
|
||
const files = Array.from(new Set(Object.values(bulletinFiles)));
|
||
if (app.env.isDev && files.length) if (!bulletinFileWatcher) {
|
||
bulletinFileWatcher = watch(files, { ignoreInitial: true });
|
||
bulletinFileWatcher.on("change", async () => {
|
||
await resolveBulletin(app, themeData);
|
||
await updateThemeData(app, themeData);
|
||
});
|
||
} else files.forEach((file) => bulletinFileWatcher?.add(file));
|
||
}
|
||
async function readBulletinFile(app, filepath, locale = "/") {
|
||
try {
|
||
const content = await fs$2.readFile(filepath, "utf-8");
|
||
if (filepath.endsWith(".md")) return app.markdown.render(content, {
|
||
filepath: app.dir.source(`${locale}_bulletin.md`),
|
||
filePathRelative: `${locale.slice(1)}_bulletin.md`
|
||
});
|
||
return content;
|
||
} catch {}
|
||
return "";
|
||
}
|
||
async function processProfileImageSize(app, themeData, plugins) {
|
||
const imageSize = getThemeConfig().markdown?.imageSize ?? (typeof plugins.markdownPower === "boolean" ? false : plugins.markdownPower?.imageSize);
|
||
if (!app.env.isBuild || !imageSize) return;
|
||
const remote = imageSize === "all";
|
||
if (themeData.profile?.avatar) {
|
||
const { width, height } = await resolveImageSize(app, themeData.profile.avatar, remote);
|
||
if (width && height) themeData.profile = {
|
||
...themeData.profile,
|
||
originalWidth: width,
|
||
originalHeight: height
|
||
};
|
||
}
|
||
if (themeData.locales) {
|
||
for (const locale of Object.keys(themeData.locales)) if (themeData.locales[locale].profile?.avatar) {
|
||
const { width, height } = await resolveImageSize(app, themeData.locales[locale].profile.avatar, remote);
|
||
if (width && height) themeData.locales[locale].profile = {
|
||
...themeData.locales[locale].profile,
|
||
originalWidth: width,
|
||
originalHeight: height
|
||
};
|
||
}
|
||
}
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/theme.ts
|
||
/**
|
||
* VuePress Theme Plume
|
||
* @param options 主题配置
|
||
* @example
|
||
* ```ts
|
||
* import { defineUserConfig } from 'vuepress'
|
||
* import { plumeTheme } from 'vuepress-theme-plume'
|
||
*
|
||
* export default defineUserConfig({
|
||
* theme: plumeTheme({
|
||
* // ...options
|
||
* })
|
||
* })
|
||
* ```
|
||
*/
|
||
function plumeTheme(options = {}) {
|
||
return (app) => {
|
||
setTranslateLang(app.options.lang);
|
||
perf.init(app.env.isDebug);
|
||
detectVersions(app);
|
||
const { configFile, plugins, themeOptions } = detectThemeOptions(options);
|
||
initConfigLoader(app, {
|
||
configFile,
|
||
defaultConfig: themeOptions,
|
||
onChange: initAutoFrontmatter
|
||
});
|
||
return {
|
||
name: THEME_NAME,
|
||
define: setupProvideData(app, plugins),
|
||
templateBuild: templates("build.html"),
|
||
clientConfigFile: resolve$1("client/config.js"),
|
||
alias: setupAlias(),
|
||
plugins: setupPlugins(app, plugins),
|
||
extendsBundlerOptions,
|
||
templateBuildRenderer,
|
||
extendsMarkdown: async (_, app$1) => {
|
||
const { autoFrontmatter } = await waitForConfigLoaded();
|
||
if (autoFrontmatter !== false) {
|
||
initAutoFrontmatter();
|
||
await generateAutoFrontmatter(app$1);
|
||
await sleep(100);
|
||
}
|
||
},
|
||
extendsPage: (page) => extendsPageData(page),
|
||
onInitialized: async (app$1) => await createPages(app$1),
|
||
onPrepared: async (app$1) => {
|
||
await prepareThemeData(app$1, plugins);
|
||
await prepareData(app$1);
|
||
},
|
||
onWatched: (app$1, watchers) => {
|
||
watchConfigFile(app$1, watchers, async () => {
|
||
await prepareThemeData(app$1, plugins);
|
||
await prepareData(app$1);
|
||
});
|
||
watchAutoFrontmatter(app$1, watchers);
|
||
watchPrepare(app$1, watchers);
|
||
}
|
||
};
|
||
};
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/defineConfig.ts
|
||
/**
|
||
* 主题配置,在单独的 `plume.config.ts` 中使用的类型帮助函数
|
||
*/
|
||
function defineThemeConfig(config) {
|
||
return config;
|
||
}
|
||
/**
|
||
* 主题导航栏配置帮助函数
|
||
*/
|
||
function defineNavbarConfig(navbar) {
|
||
return navbar;
|
||
}
|
||
/**
|
||
* 主题 notes 配置帮助函数
|
||
*/
|
||
function defineNotesConfig(notes) {
|
||
return notes;
|
||
}
|
||
/**
|
||
* 主题 notes item 配置帮助函数
|
||
*/
|
||
function defineNoteConfig(note) {
|
||
return note;
|
||
}
|
||
|
||
//#endregion
|
||
//#region src/node/index.ts
|
||
/**
|
||
* @deprecated 请使用 具名导出 替代 默认导出
|
||
*/
|
||
var node_default = plumeTheme;
|
||
|
||
//#endregion
|
||
export { node_default as default, defineNavbarConfig, defineNoteConfig, defineNotesConfig, defineThemeConfig, plumeTheme }; |