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 VuePress & vuepress-theme-plume" } }; 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 VuePress & vuepress-theme-plume" } }; 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 VuePress & vuepress-theme-plume" } }; 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: "VuePress & vuepress-theme-plume によって提供されています" } }; 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 VuePress & vuepress-theme-plume" } }; 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: "Работает на VuePress & vuepress-theme-plume" } }; 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 VuePress & vuepress-theme-plume" } }; 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 VuePress & vuepress-theme-plume" } }; 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 VuePress & vuepress-theme-plume" } }; /** * 初始化主题配置, * 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("", `