vuepress-theme-plume
Version:
A Blog&Document Theme for VuePress 2.0
1,512 lines (1,511 loc) • 135 kB
JavaScript
import { LRUCache, attempt, deepMerge, deleteKey, difference, hasOwn, isArray, isBoolean, isEmptyObject, isFunction, isNumber, isPlainObject, isString, kebabCase, objectEntries, objectKeys, omit, sleep, toArray, toTruthy, uniq } from "@pengzhanbo/utils";
import { watch } from "chokidar";
import matter from "gray-matter";
import * as yaml from "js-yaml";
import pMap from "p-map";
import { colors, fs, getDirname, hash, importFileDefault, path, templateRenderer, tinyglobby } from "vuepress/utils";
import fs$1 from "node:fs/promises";
import path$1, { resolve } from "node:path";
import picomatch from "picomatch";
import crypto, { createHash } from "node:crypto";
import { bcrypt } from "hash-wasm";
import { customAlphabet } from "nanoid";
import { Logger, addViteConfig, addViteOptimizeDepsExclude, addViteOptimizeDepsInclude, addViteSsrNoExternal, encodeData, ensureEndingSlash, ensureLeadingSlash, entries, fromEntries, getFullLocaleConfig, isFunction as isFunction$1, isLinkAbsolute, isLinkHttp, isLinkWithProtocol, isPlainObject as isPlainObject$1, removeEndingSlash, removeLeadingSlash } from "@vuepress/helper";
import process from "node:process";
import { isPackageExists } from "local-pkg";
import { ensureEndingSlash as ensureEndingSlash$1, ensureLeadingSlash as ensureLeadingSlash$1, isPlainObject as isPlainObject$2, removeLeadingSlash as removeLeadingSlash$1 } from "vuepress/shared";
import { pathToFileURL } from "node:url";
import { rolldown } from "rolldown";
import EventEmitter from "node:events";
import fs$2, { constants, promises } from "node:fs";
import dayjs from "dayjs";
import { getUserAgent, resolveCommand } from "package-manager-detector";
import { createPage } from "vuepress/core";
import { transformerColorizedBrackets } from "@shikijs/colorized-brackets";
import { transformerRenderIndentGuides } from "@shikijs/transformers";
import { copyCodePlugin } from "@vuepress/plugin-copy-code";
import { shikiPlugin } from "@vuepress/plugin-shiki";
import { createCodeTabIconGetter, getImageOriginalSize, markdownPowerPlugin, resolveImagePath } 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 { generateTOCLink, llmsPlugin } from "@vuepress/plugin-llms";
import { getIconContentCSS, getIconData } from "@iconify/utils";
export * from "../shared/index.js";
//#region src/node/utils/constants.ts
/**
* Theme name constant
*
* 主题名称常量
*/
const THEME_NAME = "vuepress-theme-plume";
//#endregion
//#region src/node/utils/createFsCache.ts
const CACHE_BASE = "markdown";
/**
* Create a file system cache instance
* Provides persistent caching using the file system
*
* 创建文件系统缓存实例
* 使用文件系统提供持久化缓存
*
* @param app - VuePress application instance / VuePress 应用实例
* @param name - Cache file name / 缓存文件名
* @returns File system cache instance / 文件系统缓存实例
*/
function createFsCache(app, name) {
const filepath = app.dir.cache(`${CACHE_BASE}/${name}.json`);
const cache = {
hash: "",
data: null
};
/**
* Read cached data from file
* Loads and parses the cache file if it exists
*
* 从文件读取缓存数据
* 如果存在则加载并解析缓存文件
*/
const read = async () => {
if (!cache.data) try {
const content = await fs$1.readFile(filepath, "utf-8");
if (content) {
const res = JSON.parse(content);
cache.data = res.data ?? null;
cache.hash = hash(res.hash || "");
}
} catch {}
return cache.data;
};
let timer = null;
/**
* Write data to cache file
* Only writes if the data has changed (hash comparison)
*
* 将数据写入缓存文件
* 仅在数据已更改时写入(哈希比较)
*
* @param data - Data to cache / 要缓存的数据
* @param clear - Whether to clear cache after writing / 写入后是否清除缓存
*/
const write = async (data, clear) => {
const currentHash = hash(data);
if (cache.hash && currentHash === cache.hash) return;
cache.data = data;
cache.hash = currentHash;
timer && clearTimeout(timer);
timer = setTimeout(async () => {
await fs$1.mkdir(path$1.dirname(filepath), { recursive: true });
await fs$1.writeFile(filepath, JSON.stringify(cache), "utf-8");
if (clear) {
cache.data = null;
cache.hash = "";
}
}, 300);
};
return {
get hash() {
return cache.hash;
},
get data() {
return cache.data;
},
read,
write
};
}
//#endregion
//#region src/node/utils/createMatcher.ts
/**
* Resolve include and exclude patterns into pattern and ignore arrays.
* Converts various pattern formats into a standardized format for matching.
*
* 将 include 和 exclude 模式解析为 pattern 和 ignore 数组。
* 将各种模式格式转换为用于匹配的标准化格式。
*
* @param include - Patterns to include, can be string or array / 要包含的模式,可以是字符串或数组
* @param exclude - Patterns to exclude, can be string or array / 要排除的模式,可以是字符串或数组
* @returns Object containing pattern and ignore arrays / 包含 pattern 和 ignore 数组的对象
*/
function resolveMatcherPattern(include, exclude) {
const pattern = [];
const ignore = uniq(toArray(exclude));
if (!include || include.length === 0) pattern.push("**/*.md");
else toArray(include).forEach((item) => {
if (item.startsWith("!")) ignore.push(item.slice(1));
else pattern.push(item);
});
return {
pattern,
ignore
};
}
/**
* Create a file matcher function using picomatch.
* Returns a function that tests if a file path matches the given patterns.
*
* 使用 picomatch 创建文件匹配器函数。
* 返回一个测试文件路径是否匹配给定模式的函数。
*
* @param include - Patterns to include / 要包含的模式
* @param exclude - Patterns to exclude / 要排除的模式
* @returns Matcher function that tests file paths / 测试文件路径的匹配器函数
*/
function createMatcher(include, exclude) {
exclude = [
"**/node_modules/**",
"**/.vuepress/**",
...toArray(exclude)
];
const { pattern, ignore } = resolveMatcherPattern(include, exclude);
return picomatch(pattern, { ignore });
}
//#endregion
//#region src/node/utils/encrypt.ts
/**
* Generate encrypted password using bcrypt
* Creates a secure hash with random salt for password storage
*
* 使用 bcrypt 生成加密密码
* 使用随机盐创建安全的哈希值用于密码存储
*
* @param password - Plain text password to encrypt / 要加密的明文密码
* @returns Bcrypt hashed password / Bcrypt 哈希后的密码
*/
async function genEncrypt(password) {
const salt = /* @__PURE__ */ new Uint8Array(16);
crypto.getRandomValues(salt);
return await bcrypt({
password: String(password),
salt,
costFactor: 11,
outputType: "encoded"
});
}
//#endregion
//#region src/node/utils/hash.ts
/**
* Generate MD5 hash of content
*
* 生成内容的 MD5 哈希值
*
* @param content - Content to hash / 要哈希的内容
* @returns MD5 hash string / MD5 哈希字符串
*/
const hash$1 = (content) => createHash("md5").update(content).digest("hex");
/**
* Generate random ID (nanoid)
* Uses alphanumeric characters for URL-safe IDs
*
* 生成随机 ID (nanoid)
* 使用字母数字字符生成 URL 安全的 ID
*
* @param size - Length of the generated ID, defaults to 8 / 生成 ID 的长度,默认为 8
* @returns Random alphanumeric string / 随机字母数字字符串
*/
const nanoid = customAlphabet("0123456789abcdefghijklmnopqrstuvwxyz", 8);
//#endregion
//#region src/node/utils/interopDefault.ts
/**
* Interop default export
*
* 兼容默认导出
*/
async function interopDefault(m) {
const resolved = await m;
return resolved.default || resolved;
}
//#endregion
//#region src/node/utils/logger.ts
/**
* Theme logger instance
* Used for logging messages with the theme name prefix
*
* 主题日志记录器实例
* 用于记录带有主题名称前缀的消息
*/
const logger = new Logger(THEME_NAME);
/**
* Performance monitor class
* Tracks and logs performance metrics for debugging
*
* 性能监控类
* 跟踪和记录性能指标用于调试
*/
var Perf = class {
/** Whether debug mode is enabled / 是否启用调试模式 */
isDebug = false;
/** Collection of performance marks / 性能标记集合 */
collect = {};
/**
* Initialize performance monitor
*
* 初始化性能监控器
*
* @param isDebug - Whether to enable debug mode / 是否启用调试模式
*/
init(isDebug = false) {
this.isDebug = isDebug;
}
/**
* Mark a performance checkpoint
* Records the current timestamp for the given mark
*
* 标记性能检查点
* 记录给定标记的当前时间戳
*
* @param mark - Name of the performance mark / 性能标记的名称
*/
mark(mark) {
this.collect[mark] = performance.now();
}
/**
* Log the time spent since a mark
* Outputs the elapsed time if debug mode is enabled
*
* 记录自标记以来的耗时
* 如果启用调试模式则输出经过的时间
*
* @param mark - Name of the performance mark to log / 要记录的性能标记名称
*/
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`)}`);
}
};
/**
* Global performance monitor instance
*
* 全局性能监控实例
*/
const perf = new Perf();
//#endregion
//#region src/node/utils/path.ts
const __dirname = getDirname(import.meta.url);
/**
* Resolve theme directory path
* Resolves paths relative to the theme's root directory
*
* 解析主题目录路径
* 解析相对于主题根目录的路径
*
* @param args - Path segments to resolve / 要解析的路径段
* @returns Resolved absolute path / 解析后的绝对路径
*/
const resolve$1 = (...args) => path.resolve(__dirname, "../", ...args);
/**
* Resolve template path
* Resolves paths relative to the templates directory
*
* 解析模板路径
* 解析相对于模板目录的路径
*
* @param url - Template file path / 模板文件路径
* @returns Resolved template path / 解析后的模板路径
*/
const templates = (url) => resolve$1("../templates", url);
const RE_SLASH = /(\\|\/)+/g;
/**
* Normalize path separators
* Converts backslashes to forward slashes for cross-platform compatibility
*
* 规范化路径分隔符
* 将反斜杠转换为正斜杠以实现跨平台兼容性
*
* @param path - Path to normalize / 要规范化的路径
* @returns Normalized path with forward slashes / 带有正斜杠的规范化路径
*/
function normalizePath$1(path) {
return path.replace(RE_SLASH, "/");
}
/**
* Normalize link with base path
* Combines base path with link, handling absolute and protocol links
*
* 规范化带基础路径的链接
* 将基础路径与链接组合,处理绝对链接和协议链接
*
* @param base - Base path / 基础路径
* @param link - Link to normalize / 要规范化的链接
* @returns Normalized link / 规范化后的链接
*/
function normalizeLink(base, link = "") {
return isLinkAbsolute(link) || isLinkWithProtocol(link) ? link : ensureLeadingSlash(normalizePath$1(`${base}/${link}/`));
}
/**
* Add base path to path
* Prepends base path to a given path if not already present
*
* 为路径添加基础路径
* 如果给定路径尚未包含基础路径,则在前面添加
*
* @param path - Path to modify / 要修改的路径
* @param base - Base path to prepend / 要添加的基础路径
* @returns Path with base prepended / 添加了基础路径的路径
*/
function withBase(path = "", base = "/") {
path = ensureEndingSlash(ensureLeadingSlash(path));
if (path.startsWith(base)) return normalizePath$1(path);
return normalizePath$1(`${base}${path}`);
}
//#endregion
//#region src/node/utils/package.ts
/**
* Read and parse JSON file asynchronously
*
* 异步读取和解析 JSON 文件
*/
function readJsonFileAsync(filePath) {
const [, data] = attempt(() => {
const content = fs.readFileSync(filePath, "utf-8");
return JSON.parse(content);
});
return data || {};
}
/**
* Get root package.json
*
* 获取根目录的 package.json
*/
function getPackage() {
return readJsonFileAsync(path.join(process.cwd(), "package.json"));
}
/**
* Get theme package.json
*
* 获取主题的 package.json
*/
function getThemePackage() {
return readJsonFileAsync(resolve$1("../package.json"));
}
//#endregion
//#region src/node/utils/pinyin.ts
let _pinyin = null;
/**
* Check if pinyin-pro package is installed.
* Used for Chinese character to pinyin conversion.
*
* 检查是否安装了 pinyin-pro 包。
* 用于中文字符转拼音功能。
*/
const hasPinyin = isPackageExists("pinyin-pro");
const hasPinyinData = isPackageExists("@pinyin-pro/data");
/**
* Get the pinyin conversion function.
* Dynamically imports pinyin-pro and its data if available.
* Caches the result for subsequent calls.
*
* 获取拼音转换函数。
* 动态导入 pinyin-pro 及其数据(如果可用)。
* 缓存结果以供后续调用使用。
*
* @returns Pinyin function or null if not installed / 拼音函数,如果未安装则返回 null
* @example
* const pinyin = await getPinyin()
* if (pinyin) {
* const result = pinyin('中文') // 'zhōng wén'
* }
*/
async function getPinyin() {
if (hasPinyin && !_pinyin) {
const { pinyin, addDict } = await import("pinyin-pro");
_pinyin = pinyin;
if (hasPinyinData) addDict(await interopDefault(import("@pinyin-pro/data/complete")));
}
return _pinyin;
}
//#endregion
//#region src/node/utils/resolveContent.ts
/**
* Resolve content string for writing to temporary files.
* Generates JavaScript module content with HMR support in development mode.
*
* 解析用于写入临时文件的内容字符串。
* 在开发模式下生成带有 HMR 支持的 JavaScript 模块内容。
*
* @param app - VuePress application instance / VuePress 应用实例
* @param options - Content resolution options / 内容解析选项
* @param options.name - Variable name for the exported content / 导出内容的变量名
* @param options.content - Content to be serialized / 要序列化的内容
* @param options.before - Content to prepend before the export / 在导出之前添加的内容
* @param options.after - Content to append after the export / 在导出之后添加的内容
* @returns Resolved content string / 解析后的内容字符串
*/
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
/**
* Simple built-in Chinese/English translation utility.
* Used for log output based on app.lang settings.
*
* 简单的内置中/英翻译转换工具。
* 用于在需要日志输出的场景,根据 app.lang 设置输出语言。
*/
let lang = "en";
/**
* Set the translation language based on the current locale.
* Supports Chinese variants (zh-CN, zh, zh-Hans, zh-Hant) and defaults to English.
*
* 根据当前区域设置翻译语言。
* 支持中文变体(zh-CN、zh、zh-Hans、zh-Hant),默认为英文。
*
* @param current - Current locale string / 当前区域设置字符串
*/
function setTranslateLang(current) {
if ([
"zh-CN",
"zh",
"zh-Hans",
"zh-Hant"
].includes(current)) lang = "zh";
else lang = "en";
}
/**
* Create a translation function with locale support.
* Returns a function that translates keys to localized strings with optional interpolation.
*
* 创建支持本地化的翻译函数。
* 返回一个将键翻译为本地化字符串的函数,支持可选的插值。
*
* @template Data - Data type for interpolation / 插值数据类型
* @template Locale - Locale type for translations / 翻译区域类型
* @param locales - Locale data for each language / 每种语言的区域数据
* @returns Translation function / 翻译函数
* @example
* const t = createTranslate({
* zh: { hello: '你好,{{name}}!' },
* en: { hello: 'Hello, {{name}}!' }
* })
* t('hello', { name: 'World' }) // '你好,World!' or 'Hello, World!'
*/
function createTranslate(locales) {
return function t(key, data) {
const resolved = locales[lang][key];
if (!resolved) return String(key);
if (data && !isEmptyObject(data)) return resolved.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => data[key] || _);
return resolved;
};
}
//#endregion
//#region src/node/utils/writeTemp.ts
/**
* Cache for content hashes to detect changes.
* Maps file paths to their content hashes.
*
* 内容哈希缓存,用于检测变更。
* 将文件路径映射到其内容哈希。
*/
const contentHash$1 = new LRUCache({ maxSize: 64 });
/**
* Write content to a temporary file if it has changed.
* Uses hash comparison to avoid unnecessary writes.
*
* 如果内容已更改,则写入临时文件。
* 使用哈希比较避免不必要的写入。
*
* @param app - VuePress application instance / VuePress 应用实例
* @param filepath - Relative path to the temporary file / 临时文件的相对路径
* @param content - Content to write / 要写入的内容
*/
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/autoFrontmatter/createFilter.ts
const matchers = new LRUCache({ maxSize: 1024 });
/**
* Create Filter from pattern
*/
function createFilter(pattern) {
if (isFunction(pattern)) return pattern;
const key = hash(pattern);
const value = matchers.get(key);
if (value) return value;
if (!isArray(pattern)) {
const matcher = picomatch(pattern);
matchers.set(pattern, matcher);
return matcher;
}
const patterns = [];
const ignorePatterns = [];
for (const p of pattern) if (p.startsWith("!")) ignorePatterns.push(p.slice(1));
else patterns.push(p);
const matcher = patterns.length === 0 ? () => false : picomatch(patterns, { ignore: ignorePatterns });
matchers.set(key, matcher);
return matcher;
}
//#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 importMetaResolveVarName = "__vite_injected_original_import_meta_resolve";
const importMetaResolveRegex = /import\.meta\s*\.\s*resolve/;
const bundle = await rolldown({
input: configPath,
platform: "node",
tsconfig: false,
treeshake: false,
resolve: { mainFields: ["main"] },
transform: { define: {
"__dirname": dirnameVarName,
"__filename": filenameVarName,
"import.meta.url": importMetaUrlVarName,
"import.meta.dirname": dirnameVarName,
"import.meta.filename": filenameVarName,
"import.meta.resolve": importMetaResolveVarName,
"import.meta.main": "false"
} },
plugins: [{
name: "externalize-deps",
resolveId: {
filter: { id: /^[^.#].*/ },
handler(id, importer) {
if (!importer || path.isAbsolute(id)) return;
return {
id,
external: true
};
}
}
}, {
name: "inject-file-scope-variables",
transform: {
filter: { id: /\.[cm]?[jt]s$/ },
handler(code, id) {
let injectValues = `const ${dirnameVarName} = ${JSON.stringify(path.dirname(id))};const ${filenameVarName} = ${JSON.stringify(id)};const ${importMetaUrlVarName} = ${JSON.stringify(pathToFileURL(id).href)};`;
if (importMetaResolveRegex.test(code)) injectValues += `const ${importMetaResolveVarName} = (specifier, importer = ${importMetaUrlVarName}) => import.meta.resolve(specifier, importer);`;
let injectedContents;
if (code.startsWith("#!")) {
let firstLineEndIndex = code.indexOf("\n");
if (firstLineEndIndex < 0) firstLineEndIndex = code.length;
injectedContents = code.slice(0, firstLineEndIndex + 1) + injectValues + code.slice(firstLineEndIndex + 1);
} else injectedContents = injectValues + code;
return {
code: injectedContents,
map: null
};
}
}
}]
});
const result = await bundle.generate({
format: "esm",
sourcemap: "inline",
sourcemapPathTransform(relative) {
return path.resolve(path.dirname(configPath), relative);
},
codeSplitting: false
});
await bundle.close();
const entryChunk = result.output.find((chunk) => chunk.type === "chunk" && chunk.isEntry);
const bundleChunks = Object.fromEntries(result.output.flatMap((c) => c.type === "chunk" ? [[c.fileName, c]] : []));
const userConfigDependencies = [];
const seen = /* @__PURE__ */ new Set();
collectAllModules(bundleChunks, entryChunk.fileName, seen);
for (const modId of seen) if (!modId.startsWith("\0")) userConfigDependencies.push(modId);
const { code: text } = entryChunk;
const tempFilePath = `${configPath}.${hash$1(text)}.mjs`;
let config;
try {
await fs.writeFile(tempFilePath, text);
config = await importFileDefault(tempFilePath);
} finally {
fs.unlink(tempFilePath);
}
return {
config,
dependencies: userConfigDependencies.filter((dep) => dep[0] === ".").map(normalizePath$1)
};
}
function collectAllModules(chunks, fileName, set) {
const chunk = chunks[fileName];
if (!chunk) return;
for (const modId of chunk.moduleIds) if (!set.has(modId)) {
set.add(modId);
for (const importFileName of chunk.imports) collectAllModules(chunks, importFileName, set);
}
}
//#endregion
//#region src/node/config/extendsBundlerOptions.ts
function extendsBundlerOptions(bundlerOptions, app) {
const dynamicImport = [
"artplayer",
"dashjs",
"hls.js",
"mpegts.js",
"shiki",
"pyodide",
"qrcode",
"artalk",
"giscus",
"twikoo",
"@waline",
"photoswipe",
"chart.js",
"echarts",
"flowchart.ts",
"markmap",
"mermaid",
"katex",
"register-service-worker",
"@docsearch"
];
const VUE_REG = /node_modules[\\/](?:@?vue[\\/]|vue-router|floating-vue)/;
addViteConfig(bundlerOptions, app, { build: {
chunkSizeWarningLimit: 2048,
rolldownOptions: { output: { codeSplitting: { groups: [
{
name: "vue",
test: (id) => {
if (id.includes("node_modules")) {
const mod = id.slice(id.indexOf("node_modules") + 13);
return VUE_REG.test(mod);
}
},
priority: 100
},
{
name: "vendor",
test: (id) => {
if (id.includes("node_modules")) {
const mod = id.slice(id.indexOf("node_modules") + 13);
return !dynamicImport.some((item) => mod.includes(item));
}
},
priority: 90,
entriesAware: true,
minModuleSize: 28e3,
entriesAwareMergeThreshold: 1e5
},
{
name: "common",
minShareCount: 3,
minSize: 1e5,
priority: 10
}
] } } }
} });
addViteOptimizeDepsInclude(bundlerOptions, app, [
"@vueuse/core",
"hash-wasm",
"@vuepress/helper/client",
"@iconify/vue",
"@iconify/vue/offline",
"@vuepress/plugin-git/client",
"@vuepress/plugin-markdown-chart/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"]);
}
if (isPackageExists("three")) {
addViteOptimizeDepsInclude(bundlerOptions, app, ["three", "three/src/math/MathUtils.js"]);
addViteSsrNoExternal(bundlerOptions, app, ["three", "three/src/math/MathUtils.js"]);
}
if (isPackageExists("gsap")) {
addViteOptimizeDepsInclude(bundlerOptions, app, ["gsap", "gsap/InertiaPlugin"]);
addViteSsrNoExternal(bundlerOptions, app, ["gsap", "gsap/InertiaPlugin"]);
}
if (isPackageExists("postprocessing")) {
addViteOptimizeDepsInclude(bundlerOptions, app, ["postprocessing"]);
addViteSsrNoExternal(bundlerOptions, app, ["postprocessing"]);
}
if (isPackageExists("ogl")) {
addViteOptimizeDepsInclude(bundlerOptions, app, ["ogl"]);
addViteSsrNoExternal(bundlerOptions, app, ["ogl"]);
}
}
//#endregion
//#region src/node/collections/compat.ts
/**
* 兼容旧的 blog 、 notes 配置,将它们转换为 collections
*/
function compatBlogAndNotesToCollections(options) {
if (!options.collections?.length) {
const collections = options.collections ||= [];
if (options.blog) {
const notes = options.notes || {};
collections.push({
type: "post",
dir: "/",
linkPrefix: options.article,
...options.blog,
exclude: [...toArray(options.blog.exclude), ...notes.notes?.map((note) => removeLeadingSlash$1(path.join(notes.dir, note.dir)))]
});
}
if (options.notes) {
const { dir, link, notes } = options.notes;
collections.push(...notes.map((note) => ({
type: "doc",
dir: path.join(dir, note.dir),
linkPrefix: path.join(link, note.link),
sidebar: note.sidebar,
sidebarScrollbar: options.sidebarScrollbar
})));
}
}
for (const [, opt] of Object.entries(options.locales || {})) {
if (!opt.collections?.length) {
const collections = opt.collections ||= [];
if (options.blog) {
const notes = opt.notes;
collections.push({
type: "post",
dir: "/",
linkPrefix: options.article,
...options.blog,
exclude: [...toArray(options.blog.exclude), ...notes.notes?.map((note) => removeLeadingSlash$1(path.join(notes.dir, note.dir)))]
});
}
if (opt.notes) {
const { dir, link, notes } = opt.notes;
collections.push(...notes.map((note) => ({
type: "doc",
dir: path.join(dir, note.dir),
linkPrefix: path.join(link, note.link),
sidebar: note.sidebar,
sidebarScrollbar: opt.sidebarScrollbar ?? options.sidebarScrollbar
})));
}
}
deleteKey(opt, "notes");
}
deleteKey(options, ["blog", "notes"]);
}
//#endregion
//#region src/node/collections/complete.ts
function completeCollections(options) {
if (options.collections?.length) for (const collection of options.collections) completeCollectionItems(collection);
for (const [, opt] of Object.entries(options.locales || {})) if (opt.collections?.length) for (const collection of opt.collections) completeCollectionItems(collection);
}
function completeCollectionItems(collection) {
collection.title ||= collection.dir.split("/").filter(Boolean).pop();
if (collection.type === "post") {
collection.link ||= normalizePath$1(`/${collection.dir}/`);
collection.linkPrefix ||= collection.link;
collection.tags ??= true;
collection.tags && (collection.tagsLink ||= `${collection.linkPrefix}tags/`);
collection.archives ??= true;
collection.archives && (collection.archivesLink ||= `${collection.linkPrefix}archives/`);
collection.categories ??= true;
collection.categories && (collection.categoriesLink ||= `${collection.linkPrefix}categories/`);
} else collection.linkPrefix ||= normalizePath$1(`/${collection.dir}/`);
}
//#endregion
//#region src/node/collections/findCollection.ts
/**
* 查找当前页面所属的 collection
*/
function findCollection(page) {
const { collections: fallback, locales } = getThemeConfig();
const locale = page.pathLocale;
let collections = locales?.[locale]?.collections;
if (!collections && locale === "/") collections = fallback;
if (!collections || collections.length === 0) return;
const pagePath = page.filePathRelative?.slice(locale.length - 1);
return collections.find((item) => pagePath?.startsWith(removeLeadingSlash(item.dir)));
}
//#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:",
openNewWindowText: "(In neuem Fenster öffnen)",
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",
postsText: "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>" },
copyPageText: "Seite kopieren",
copiedPageText: "Kopieren !",
copingPageText: "Wird kopiert..",
copyTagline: "Seite als Markdown für LLMs kopieren",
viewMarkdown: "Als Markdown anzeigen",
viewMarkdownTagline: "Diese Seite als Nur-Text anzeigen",
askAIText: "In {name} öffnen",
askAITagline: "{name} zu dieser Seite befragen",
askAIMessage: "Lese {link} und beantworte Fragen zum Inhalt."
};
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:",
openNewWindowText: "(Open in new window)",
encryptButtonText: "Confirm",
encryptPlaceholder: "Enter password",
encryptGlobalText: "Only password can access this site",
encryptPageText: "Only password can access this page",
homeText: "Home",
postsText: "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>" },
copyPageText: "Copy page",
copiedPageText: "Copied !",
copingPageText: "Copying..",
copyTagline: "Copy page as Markdown for LLMs",
viewMarkdown: "View as Markdown",
viewMarkdownTagline: "View this page as plain text",
askAIText: "Open in {name}",
askAITagline: "Ask {name} about this page",
askAIMessage: "Read {link} and answer content-related questions."
};
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 :",
openNewWindowText: "(Ouvrir dans une nouvelle fenêtre)",
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",
postsText: "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>" },
copyPageText: "Copier la page",
copiedPageText: "Copie réussie",
copingPageText: "Copie en cours..",
copyTagline: "Copier la page au format Markdown pour une utilisation avec des LLM",
viewMarkdown: "Voir en Markdown",
viewMarkdownTagline: "Voir cette page en texte brut",
askAIText: "Ouvrir dans {name}",
askAITagline: "Interroger {name} sur cette page",
askAIMessage: "Lisez {link} et répondez aux questions concernant son contenu."
};
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: "ライセンス:",
openNewWindowText: "(新しいウィンドウで開く)",
notFound: {
code: "404",
title: "ページが見つかりません",
quote: "しかし、方向を変えずに探し続ければ、最終的には行きたい場所にたどり着くかもしれません。",
linkText: "ホームに戻る"
},
homeText: "ホーム",
postsText: "ブログ",
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> によって提供されています" },
copyPageText: "ページをコピー",
copiedPageText: "コピーしました",
copingPageText: "コピー中..",
copyTagline: "ページをMarkdown形式でコピーしてLLMで使用",
viewMarkdown: "Markdown形式で表示",
viewMarkdownTagline: "このページをプレーンテキストで表示",
askAIText: "{name} で開く",
askAITagline: "このページについて {name} に質問する",
askAIMessage: "{link} を読み、内容に関する質問に答えてください。"
};
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: "홈",
postsText: "블로그",
tagText: "태그",
archiveText: "아카이브",
categoryText: "카테고리",
archiveTotalText: "{count}개의 글",
openNewWindowText: "(새 창에서 열기)",
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>" },
copyPageText: "페이지 복사",
copiedPageText: "복사 완료",
copingPageText: "복사 중..",
copyTagline: "페이지를 마크다운 형식으로 복사하여 LLM에서 사용",
viewMarkdown: "Markdown 형식으로 보기",
viewMarkdownTagline: "이 페이지를 일반 텍스트로 보기",
askAIText: "{name} 에서 열기",
askAITagline: "이 페이지에 대해 {name} 에 질문하기",
askAIMessage: "{link} 을(를) 읽고 내용과 관련된 질문에 답변해 주세요."
};
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: "Лицензия:",
openNewWindowText: "(Открыть в новой вкладке)",
notFound: {
code: "404",
title: "Страница не найдена",
quote: "Но если вы не меняете курс и продолжаете искать, в конечном итоге вы можете добраться до места назначения.",
linkText: "Вернуться на главную"
},
homeText: "Главная",
postsText: "Блог",
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>" },
copyPageText: "Копировать страницу",
copiedPageText: "Скопировано успешно",
copingPageText: "Копируется...",
copyTagline: "Скопировать страницу в формате Markdown для использования в LLM",
viewMarkdown: "Просмотреть в Markdown",
viewMarkdownTagline: "Просмотреть эту страницу в виде простого текста",
askAIText: "Открыть в {name}",
askAITagline: "Спросить {name} об этой странице",
askAIMessage: "Прочитайте {link} и ответьте на вопросы, связанные с содержанием."
};
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: "授權條款:",
openNewWindowText: "(在新窗口打開)",
notFound: {
code: "404",
title: "頁面未找到",
quote: "但是,如果你不改變方向,並且一直尋找,最終可能會到達你要去的地方。",
linkText: "返回首頁"
},
homeText: "首頁",
postsText: "部落格",
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>" },
copyPageText: "複製頁面",
copiedPageText: "複製成功",
copingPageText: "複製中..",
copyTagline: "將頁面以 Markdown 格式複製供 LLMs 使用",
viewMarkdown: "以 Markdown 格式檢視",
viewMarkdownTagline: "以純文字檢視此頁面",
askAIText: "在 {name} 中開啟",
askAITagline: "向 {name} 提問有關此頁面",
askAIMessage: "閱讀 {link} 並回答內容相關的問題。"
};
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: "许可证:",
openNewWindowText: "(在新窗口打开)",
notFound: {
code: "404",
title: "页面未找到",
quote: "但是,如果你不改变方向,并且一直寻找,最终可能会到达你要去的地方。",
linkText: "返回首页"
},
homeText: "首页",
postsText: "博客",
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>" },
copyPageText: "复制页面",
copiedPageText: "复制成功",
copingPageText: "复制中..",
copyTagline: "将页面以 Markdown 格式复制供 LLMs 使用",
viewMarkdown: "以 Markdown 格式查看",
viewMarkdownTagline: "以纯文本查看此页面",
askAIText: "在 {name} 中打开",
askAITagline: "向 {name} 提问有关此页面",
askAIMessage: "阅读 {link} 并回答内容相关的问题。"
};
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 = [
[["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],
[["ko", "ko-KR"], koLocale]
];
const PRESET_LOCALES = [
[["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],
[["ko", "ko-KR"], koPresetLocale]
];
//#endregion
//#region src/node/config/initThemeOptions.ts
const FALLBACK_OPTIONS = {
appearance: true,
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 }) {
const resolvedOptions = {
...mergeOptions(FALLBACK_OPTIONS, options),
locales: getFullLocaleConfig({
app,
name: THEME_NAME,
default: LOCALE_OPTIONS,
config: fromEntries(entries({
"/": {},
...locales
}).map(([locale, opt]) => [locale, mergeOptions(options, opt)]))
})
};
compatBlogAndNotesToCollections(resolvedOptions);
completeCollections(resolvedOptions);
return resolvedOptions;
}
function mergeOptions(target, source) {
const res = {};
const keys = uniq([...objectKeys(target), ...objectKeys(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/config/resolveThemeData.ts
const EXCLUDE_LIST = [
"hostname",
"locales",
"sidebar",
"navbar",
"blog",
"notes",
"collections",
"sidebar",
"article",
"changelog",
"contributors",
"bulletin",
"cache",
"autoFrontmatter",
"comment",
"codeHighlighter",
"markdown",
"configFile",
"encrypt",
"plugins",
"search",
"watermark",
"readingTime",
"copyCode",
"llmstxt"
];
const EXCLUDE_LOCALE_LIST = [
...EXCLUDE_LIST,
"blog",
"appearance"
];
function resolveThemeData(app, options) {
const themeData = { locales: {} };
objectEntries(options).forEach(([key, value]) => {
if (!EXCLUDE_LIST.includes(key)) themeData[key] = value;
});
themeData.contributors = isPlainObject(options.contributors) ? { mode: options.contributors.mode || "inline" } : isBoolean(options.contributors) ? options.contributors : true;
themeData.changelog = !!options.changelog;
if (isPlainObject(options.bulletin)) {
const { enablePage: _, ...opt } = options.bulletin;
themeData.bulletin = opt;
} else if (options.bulletin) themeData.bulletin = options.bulletin;
objectEntries(options.locales || {}).forEach(([locale, opt]) => {
themeData.locales[locale] = {};
objectEntries(opt).forEach(([key, value]) => {
if (!EXCLUDE_LOCALE_LIST.includes(key)) themeData.locales[locale][key] = value;
});
if (isPlainObject(opt.bulletin)) {
const { enablePage: _, ...rest } = opt.bulletin;
themeData.locales[locale].bulletin = rest;
} else if (opt.bulletin) themeData.locales[locale].bulletin = opt.bulletin;
});
objectEntries(options.locales || {}).forEach(([locale, opt]) => {
if (!opt.navbar) {
const navbar = [{
text: opt.homeText || options.homeText || "Home",
link: locale
}];
const collections = opt.collections?.filter((item) => item.type === "post");
if (!collections?.length) return;
const posts = collections[0];
const postsLink = posts.link || posts.dir;
navbar.push({
text: posts.title || removeEndingSlash(posts.dir).split("/").pop() || opt.postsText || options.postsText || "Posts",
link: withBase(postsLink, locale)
});
if (posts.tags !== false) navbar.push({
text: opt.tagText || options.tagText || "Tags",
link: withBase(posts.tagsLink || `${postsLink}/tags/`, locale)
});
if (posts.archives !== false) navbar.push({
text: opt.archiveText || options.archiveText || "Archives",
link: withBase(posts.archivesLink || `${postsLink}/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