UNPKG

vuepress-theme-plume

Version:

A Blog&Document Theme for VuePress 2.0

1,512 lines (1,511 loc) 135 kB
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