fuc-new/doc/node_modules/vuepress-theme-plume/lib/node/index.js
hellcat 6adbc04d4b 1
2025-10-03 17:17:08 +08:00

2905 lines
96 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 };