UNPKG

vuepress-theme-plume

Version:

A Blog&Document Theme for VuePress 2.0

3,836 lines 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((file) => file.endsWith(".vue")).map((file) => [path.join("@theme", file), resolve$1("client/components", file)])) };
}
//#endregion
//#region src/node/config/setupProvideData.ts
function setupProvideData(app, plugins) {
	const watermark = getThemeConfig().watermark ?? plugins.watermark;
	return {
		__PLUME_WM_FP__: isPlainObject$1(watermark) ? watermark.fullPage !== false : true,
		__PLUME_PRESET_LOCALE__: getFullLocaleConfig({
			app,
			name: "vuepress-theme-plume/preset-locales",
			default: PRESET_LOCALES
		})
	};
}
//#endregion
//#region src/node/config/templateBuildRenderer.ts
function templateBuildRenderer(template, context) {
	const options = getThemeConfig();
	const pkg = getThemePackage();
	template = template.replace("{{ themeVersion }}", pkg.version || "").replace(/^\s+|\s+$/gm, "").replace(/\n/g, "");
	if (options.appearance ?? true) {
		const appearance = typeof options.appearance === "string" ? options.appearance : "auto";
		const script = appearance === "force-dark" ? `document.documentElement.dataset.theme = 'dark'` : `;(function () {
    const um= localStorage.getItem('vuepress-theme-appearance') || '${appearance}';
    const sm = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    const isDark = um === 'dark' || (um !== 'light' && sm);
    document.documentElement.dataset.theme = isDark ? 'dark' : 'light';
  })();`.replace(/^\s+|\s+$/gm, "").replace(/\n/g, "");
		template = template.replace("<!--vuepress-theme-plume-appearance-->", `<script id="check-dark-mode">${script}<\/script>`);
	} else template = template.replace("<!--vuepress-theme-plume-appearance-->", "");
	return templateRenderer(template, context);
}
//#endregion
//#region src/node/loadConfig/findConfigPath.ts
const CONFIG_FILE_NAME = "plume.config";
const extensions = [
	"ts",
	"js",
	"mjs",
	"cjs",
	"mts",
	"cts"
];
async function findConfigPath(app, configPath) {
	const cwd = process.cwd();
	if (configPath) {
		const path = resolve(cwd, configPath);
		if (existsSync(path) && (await promises.stat(path)).isFile()) return path;
	}
	const source = app.dir.source(".vuepress");
	const paths = [];
	extensions.forEach((ext) => paths.push(resolve(cwd, `${source}/${CONFIG_FILE_NAME}.${ext}`), resolve(cwd, `./${CONFIG_FILE_NAME}.${ext}`), resolve(cwd, `./.vuepress/${CONFIG_FILE_NAME}.${ext}`)));
	let current;
	for (const path of paths) if (existsSync(path) && (await promises.stat(path)).isFile()) {
		current = path;
		break;
	}
	if (configPath && current) logger.warn(`Can not find config file: ${colors.gray(configPath)}\nUse config file: ${colors.gray(current)}`);
	return current;
}
function existsSync(fp) {
	try {
		fs$2.accessSync(fp, constants.R_OK);
		return true;
	} catch {
		return false;
	}
}
//#endregion
//#region src/node/loadConfig/ConfigLoader.ts
var ConfigLoader = class extends EventEmitter {
	app;
	dependencies = [];
	loaded = false;
	configFile;
	defaultConfig;
	config = {};
	async init(app, defaultConfig, configFile) {
		this.removeAllListeners("change");
		this.app = app;
		this.defaultConfig = defaultConfig;
		this.config = initThemeOptions(app, defaultConfig);
		perf.mark("config-loader:find-config");
		this.configFile = await findConfigPath(app, configFile);
		perf.log("config-loader:find-config");
		perf.mark("config-loader:loaded");
		const dependencies = await this.load();
		this.dependencies = [...dependencies];
		perf.log("config-loader:loaded");
		this.emit("loaded", this.config);
		this.removeAllListeners("loaded");
	}
	watch(watchers) {
		if (!this.configFile) return;
		const cwd = process.cwd();
		const watcher = watch([this.configFile, ...this.dependencies], {
			ignoreInitial: true,
			cwd,
			ignored: (filepath, stats) => {
				return Boolean(stats?.isFile()) && filepath.includes("node_modules");
			}
		});
		watcher.on("change", async (filepath) => {
			const dependencies = await this.load();
			watcher.add(difference(dependencies, this.dependencies));
			this.dependencies = [...dependencies];
			this.emit("change", this.config);
			logger.info(`${colors.gray("theme config")} ${colors.magenta(normalizePath$1(filepath))} ${colors.gray("is modified.")}`);
		});
		watchers.push(watcher);
	}
	async waiting() {
		if (this.loaded) return;
		return new Promise((resolve) => {
			this.once("loaded", resolve);
		});
	}
	async load() {
		this.loaded = false;
		const { config, dependencies = [] } = await compiler(this.configFile);
		this.updateConfig(config);
		this.loaded = true;
		return dependencies;
	}
	updateConfig(userConfig) {
		const config = deepMerge({}, this.defaultConfig, userConfig);
		this.config = initThemeOptions(this.app, config);
	}
};
const configLoader = new ConfigLoader();
function getThemeConfig() {
	return configLoader.config;
}
//#endregion
//#region src/node/autoFrontmatter/helper.ts
const EXCLUDE = ["!**/.vuepress/", "!**/node_modules/"];
const NUMBER_RE$1 = /^\d+\./;
function isReadme(filepath) {
	return filepath.endsWith("README.md") || filepath.endsWith("index.md") || filepath.endsWith("readme.md");
}
function normalizeTitle(title) {
	return title.replace(NUMBER_RE$1, "").trim();
}
function getFileCreateTime(filepath) {
	const stats = fs.statSync(filepath);
	const time = stats.birthtime.getFullYear() !== 1970 ? stats.birthtime : stats.atime;
	return dayjs(new Date(time)).format("YYYY/MM/DD HH:mm:ss");
}
function getCurrentName(filepath) {
	if (isReadme(filepath)) return normalizeTitle(path.dirname(filepath).slice(-1).split("/").pop() || "Home");
	return normalizeTitle(path.basename(filepath, ".md"));
}
async function getPermalinkByFilepath(filepath, base = "/") {
	const relative = removeLeadingSlash$1(ensureLeadingSlash$1(filepath).replace(ensureLeadingSlash$1(base), ""));
	const dirs = path.dirname(relative).split("/").map(normalizeTitle);
	const basename = normalizeTitle(path.basename(relative, ".md"));
	if (hasPinyin) {
		const pinyin = await getPinyin();
		return path.join(...dirs.map((dir) => slugify(pinyin?.(dir, {
			toneType: "none",
			nonZh: "consecutive"
		}) || dir)), slugify(pinyin?.(basename, {
			toneType: "none",
			nonZh: "consecutive"
		}) || basename));
	}
	return path.join(...dirs.map(slugify), slugify(basename));
}
function slugify(str) {
	return kebabCase(str.trim());
}
//#endregion
//#region src/node/autoFrontmatter/resolveLinkBySidebar.ts
function resolveLinkBySidebar(sidebar, _prefix) {
	const res = {};
	if (sidebar === "auto") return res;
	for (const item of sidebar) if (!isString(item)) {
		const { prefix, dir = "", link = "/", items } = item;
		getSidebarLink(items, link, path.join(_prefix, prefix || dir), res);
	}
	return res;
}
function getSidebarLink(items, link, dir = "", res = {}) {
	if (items === "auto" || !items) return;
	for (const item of items) if (isString(item)) res[ensureEndingSlash$1(dir)] = link;
	else {
		const { prefix = "", dir: subDir = "", link: subLink = "/", items: subItems } = item;
		getSidebarLink(subItems, path.join(link, subLink), path.join(prefix[0] === "/" ? prefix : `/${dir}/${prefix || subDir}`), res);
	}
}
//#endregion
//#region src/node/autoFrontmatter/rules.ts
const rules = [];
function getRules() {
	return rules;
}
function genAutoFrontmatterRules() {
	const options = getThemeConfig();
	const remainExclude = [...EXCLUDE];
	rules.length = 0;
	const autoFrontmatter = options.autoFrontmatter ?? {};
	for (const [locale, { collections }] of objectEntries(options.locales || {})) {
		if (!collections?.length) continue;
		for (const collection of collections) {
			const source = removeLeadingSlash$1(path.join(locale, collection.dir, "**/*.md"));
			remainExclude.push(source);
			if ((collection.autoFrontmatter ?? autoFrontmatter) === false) continue;
			if (collection.type === "post") rules.push({
				filter: [
					source,
					...toArray(collection.include),
					...EXCLUDE,
					...toArray(collection.exclude).map((s) => `!${s}`)
				],
				handle: (data, context) => generateWithPost(data, context, collection, autoFrontmatter, locale)
			});
			else rules.push({
				filter: [source, ...EXCLUDE],
				handle: (data, context) => generateWithDoc(data, context, collection, autoFrontmatter, locale)
			});
		}
		if (locale !== "/") {
			const source = removeLeadingSlash$1(path.join(locale, "**/*.md"));
			rules.push({
				filter: [source, ...remainExclude.map((s) => `!${s}`)],
				handle: (data, context) => generateWithRemain(data, context, autoFrontmatter, locale)
			});
			remainExclude.push(source);
		}
	}
	rules.push({
		filter: ["**/*.md", ...remainExclude.map((s) => `!${s}`)],
		handle: (data, context) => generateWithRemain(data, context, autoFrontmatter, "/")
	});
}
async function generateWithPost(data, context, collection, fm, locale) {
	if ((collection.autoFrontmatter ?? fm) === false) return data;
	const { title: et = true, createTime: ec = true, permalink: ep = true } = {
		...fm,
		...collection.autoFrontmatter
	};
	const transform = (collection.autoFrontmatter || {}).transform;
	const isRoot = context.filepath.endsWith(path.join(locale, collection.dir, "README.md"));
	if (et && !hasOwn(data, "title")) data.title = isRoot ? collection.title : getCurrentName(context.relativePath);
	if (ec && !hasOwn(data, "createTime")) data.createTime = getFileCreateTime(context.filepath);
	if (ep && !hasOwn(data, "permalink")) data.permalink = path.join(locale, collection.linkPrefix || collection.link || collection.dir, ep === "filepath" ? await getPermalinkByFilepath(context.relativePath, path.join(locale, collection.dir)) : nanoid(), "/");
	data = await transform?.(data, context, locale) ?? data;
	return data;
}
async function generateWithDoc(data, context, collection, fm, locale) {
	if ((collection.autoFrontmatter ?? fm) === false) return data;
	const { title: et = true, createTime: ec = true, permalink: ep = true } = {
		...fm,
		...collection.autoFrontmatter
	};
	const transform = (collection.autoFrontmatter || {}).transform;
	const isRoot = context.filepath.endsWith(path.join(locale, collection.dir, "README.md"));
	if (et && !hasOwn(data, "title")) data.title = isRoot ? collection.title : getCurrentName(context.relativePath);
	if (ec && !hasOwn(data, "createTime")) data.createTime = getFileCreateTime(context.filepath);
	if (ep && !hasOwn(data, "permalink")) {
		if (isRoot) data.permalink = path.join(locale, collection.linkPrefix, "/");
		else if (collection.sidebar && collection.sidebar !== "auto") {
			const res = resolveLinkBySidebar(collection.sidebar, ensureLeadingSlash$1(collection.dir));
			const file = path.dirname(context.relativePath);
			const link = res[ensureLeadingSlash$1(ensureEndingSlash$1(file))] || "/";
			data.permalink = path.join(locale, collection.linkPrefix, link, isReadme(context.relativePath) ? "" : ep === "filepath" ? await getPermalinkByFilepath(link === "/" ? context.relativePath : path.basename(context.relativePath), link === "/" ? path.join(locale, collection.dir) : "") : nanoid(8), "/");
		} else data.permalink = path.join(locale, collection.linkPrefix, ep === "filepath" ? await getPermalinkByFilepath(context.relativePath, path.join(locale, collection.dir)) : nanoid(8), "/");
	}
	data = await transform?.(data, context, locale) ?? data;
	return data;
}
async function generateWithRemain(data, context, fm, locale) {
	if (fm === false) return data;
	const { title: et = true, createTime: ec = true, permalink: ep = true, transform } = fm;
	const isRoot = context.filepath.endsWith(path.join(locale, "README.md"));
	if (isRoot) data.pageLayout = "home";
	if (et && !hasOwn(data, "title")) data.title = isRoot ? "Home" : getCurrentName(context.relativePath);
	if (ec && !hasOwn(data, "createTime") && !isRoot) data.createTime = getFileCreateTime(context.filepath);
	if (ep && !hasOwn(data, "permalink") && !isRoot) data.permalink = path.join(locale, ep === "filepath" ? await getPermalinkByFilepath(context.relativePath, locale) : nanoid(8), "/");
	data = await transform?.(data, context, locale) ?? data;
	return data;
}
//#endregion
//#region src/node/autoFrontmatter/generate.ts
/**
* Get markdown info
*/
async function getMarkdownInfo(relativePath, cwd) {
	const filepath = path.join(cwd, relativePath);
	const raw = await fs.promises.readFile(filepath, "utf-8");
	const { data, content } = matter(raw);
	return {
		data,
		context: {
			filepath,
			relativePath,
			content
		}
	};
}
/**
* Find rule by filepath, Only return the first
*/
function findRule(rules, filepath) {
	return rules.find(({ filter }) => createFilter(filter)(filepath));
}
/**
* Generate frontmatter for a single Markdown file
*/
async function generateFileFrontmatter(filepath, cwd, handle) {
	try {
		const { data, context } = await getMarkdownInfo(filepath, cwd);
		const beforeHash = hash(data);
		const result = await handle(data, context);
		if (beforeHash === hash(result)) return;
		const formatted = objectKeys(result).length === 0 ? "" : yaml.dump(result);
		await fs.promises.writeFile(context.filepath, formatted ? `---\n${formatted}---\n${context.content}` : context.content, "utf-8");
	} catch (e) {
		logger.error(`Failed to generate frontmatter for ${filepath}`, e);
	}
}
/**
* Generate frontmatter for all Markdown files
*/
async function generateFileListFrontmatter(app) {
	const { pagePatterns = [
		"**/*.md",
		"!.vuepress",
		"!node_modules"
	] } = app.options;
	const cwd = app.dir.source();
	genAutoFrontmatterRules();
	const rules = getRules();
	const tasks = [];
	const fileList = await tinyglobby.glob(pagePatterns, { cwd });
	for (const filepath of fileList) {
		const rule = findRule(rules, filepath);
		if (rule) tasks.push([filepath, rule.handle]);
	}
	if (tasks.length === 0) return;
	await pMap(tasks, async ([filepath, handle]) => await generateFileFrontmatter(filepath, cwd, handle), { concurrency: 64 });
	await sleep(100);
}
function watchAutoFrontmatter(app, watchers) {
	const { pagePatterns = [
		"**/*.md",
		"!.vuepress",
		"!node_modules"
	] } = app.options;
	const cwd = app.dir.source();
	const filter = createFilter(pagePatterns);
	const watcher = watch(".", {
		cwd,
		ignoreInitial: true,
		ignored: (filepath, stats) => {
			const isFile = Boolean(stats?.isFile());
			if (filepath.includes(".vuepress") || isFile && !filepath.endsWith(".md")) return true;
			return isFile && !filter(path.relative(cwd, filepath));
		}
	});
	/**
	* Only need to focus on the newly added files
	* 只需要关注新增的文件
	*/
	watcher.on("add", (filepath) => {
		const relativePath = path.join(filepath);
		const rule = findRule(getRules(), relativePath);
		if (rule) generateFileFrontmatter(relativePath, cwd, rule.handle);
	});
	watchers.push(watcher);
}
//#endregion
//#region src/node/detector/breakingChange.ts
const t$5 = createTranslate({
	en: {
		blog: `${colors.gray("blog")} configuration has been removed and migrated to collections. Please refer to the migration documentation: ${colors.cyan("https://theme-plume.vuejs.press/blog/dk58a4t2/")}`,
		notes: `${colors.gray("notes")} configuration has been removed and migrated to collections. Please refer to the migration documentation: ${colors.cyan("https://theme-plume.vuejs.press/blog/dk58a4t2/")}`
	},
	zh: {
		blog: `${colors.gray("blog")} 配置已移除,迁移到集合中,请查看迁移文档:${colors.cyan("https://theme-plume.vuejs.press/blog/dk58a4t2/")}`,
		notes: `${colors.gray("notes")} 配置已移除,迁移到集合中,请查看迁移文档:${colors.cyan("https://theme-plume.vuejs.press/blog/dk58a4t2/")}`
	}
});
function detectBreakingChange(options) {
	withBlogAndNotesHaveBeenDelete(options);
}
/**
* @since `v1.0.0-rc.165`
* @description 博客和笔记已经被删除,迁移到 collections 实现
*/
function withBlogAndNotesHaveBeenDelete(options) {
	if (hasOwn(options, "blog") && (!options.collections || options.collections.length === 0)) logger.warn(t$5("blog"));
	let shouldMigrateNotes = false;
	if (options.notes?.length && (!options.collections || options.collections.length === 0)) shouldMigrateNotes = true;
	for (const locale of Object.values(options.locales || {})) if (locale.notes?.length && (!locale.collections || locale.collections.length === 0)) shouldMigrateNotes = true;
	if (shouldMigrateNotes) logger.warn(t$5("notes"));
}
//#endregion
//#region src/node/detector/fields.ts
const PLUGINS_SUPPORTED_FIELDS = [
	"search",
	"docsearch",
	"copyCode",
	"shiki",
	"git",
	"nprogress",
	"photoSwipe",
	"markdownChart",
	"markdownPower",
	"markdownImage",
	"markdownMath",
	"markdownInclude",
	"comment",
	"sitemap",
	"seo",
	"cache",
	"readingTime",
	"watermark",
	"replaceAssets",
	"llmstxt"
];
const MARKDOWN_CHART_FIELDS = [
	"chartjs",
	"echarts",
	"mermaid",
	"markmap",
	"plantuml",
	"flowchart"
];
const MARKDOWN_POWER_FIELDS = [
	"abbr",
	"acfun",
	"annotation",
	"artPlayer",
	"audioReader",
	"bilibili",
	"caniuse",
	"codeSandbox",
	"codeTabs",
	"codeTree",
	"codepen",
	"demo",
	"env",
	"fileTree",
	"field",
	"icons",
	"icon",
	"imageSize",
	"jsfiddle",
	"mark",
	"npmTo",
	"pdf",
	"plot",
	"repl",
	"replit",
	"table",
	"timeline",
	"collapse",
	"chat",
	"youtube",
	"qrcode",
	"encrypt",
	"obsidian",
	"locales"
];
const MARKDOWN_SUPPORT_FIELDS = [
	...MARKDOWN_CHART_FIELDS,
	...MARKDOWN_POWER_FIELDS,
	"image",
	"math",
	"include",
	"hint",
	"alert"
];
//#endregion
//#region src/node/detector/dependency.ts
const DEPENDENCIES = {
	twoslash: ["@vuepress/shiki-twoslash"],
	pythonRepl: ["pyodide"],
	chartjs: ["chart.js"],
	echarts: ["echarts"],
	markmap: [
		"markmap-lib",
		"markmap-toolbar",
		"markmap-view"
	],
	mermaid: ["mermaid"],
	flowchart: ["flowchart.ts"],
	artPlayer: ["artplayer"],
	mathjax: ["mathjax-full"]
};
const t$4 = createTranslate({
	en: {
		notFoundDeps: "Enabling features such as {{ features }} requires the installation of the following dependencies: {{ dependencies }}",
		install: "Run the command to install:  {{ command }}"
	},
	zh: {
		notFoundDeps: "启用 {{ features }} 等功能需要安装以下依赖: {{ dependencies }}",
		install: "运行安装命令:  {{ command }}"
	}
});
/**
* 部分功能需要手动安装依赖,
* 检查环境中是否缺少依赖
*/
function detectDependencies(options, plugins) {
	const shouldInstall = {};
	const markdown = options.markdown || {};
	const mdPower = isPlainObject(plugins.markdownPower) ? plugins.markdownPower : {};
	const mdChart = isPlainObject(plugins.markdownChart) ? plugins.markdownChart : {};
	const add = (name) => {
		const list = DEPENDENCIES[name].filter((dep) => !isPackageExists(dep));
		if (list.length) shouldInstall[name] = list;
	};
	if (options.codeHighlighter && options.codeHighlighter.twoslash) add("twoslash");
	if (markdown.repl && markdown.repl.python) add("pythonRepl");
	[
		"chartjs",
		"echarts",
		"markmap",
		"mermaid",
		"flowchart"
	].forEach((dep) => {
		if (markdown[dep] || mdChart[dep]) add(dep);
	});
	const math = markdown.math || plugins.markdownMath;
	if (math && math.type === "mathjax") add("mathjax");
	if (markdown.artPlayer || mdPower.artPlayer) add("artPlayer");
	if (isEmptyObject(shouldInstall)) return;
	const features = objectKeys(shouldInstall);
	const dependencies = Object.values(shouldInstall).flat();
	logger.error(t$4("notFoundDeps", {
		features: features.map((feat) => colors.green(feat)).join(", "),
		dependencies: dependencies.map((dep) => colors.magenta(dep)).join(", ")
	}));
	const agent = getUserAgent();
	if (agent) {
		const { command = "", args = [] } = resolveCommand(agent, "add", dependencies) || {};
		logger.info(t$4("install", { command: colors.cyan(`${command} ${args.join(" ").replace(DEPENDENCIES.twoslash[0], `${DEPENDENCIES.twoslash[0]}@next`)}`) }));
	}
}
//#endregion
//#region src/node/detector/markdown.ts
const t$3 = createTranslate({
	en: { message: "{{ markdown }} unsupported fields: {{ unsupported }}, please check your config." },
	zh: { message: "{{ markdown }} 不支持以下字段: {{ unsupported }}, 请检查你的配置。" }
});
function detectMarkdown(options) {
	const { markdown } = options;
	if (!markdown) return;
	const unsupported = objectKeys(markdown).filter((key) => !MARKDOWN_SUPPORT_FIELDS.includes(key));
	if (unsupported.length) logger.warn(t$3("message", {
		markdown: colors.green("markdown"),
		unsupported: unsupported.map((field) => colors.magenta(`"${field}"`)).join(", ")
	}));
}
//#endregion
//#region src/node/detector/plugins.ts
const t$2 = createTranslate({
	en: { message: "{{ plugins }} unsupported fields: {{ unsupported }}, please check your config." },
	zh: { message: "{{ plugins }} 不支持以下字段: {{ unsupported }}, 请检查你的配置。" }
});
function detectPlugins(plugins) {
	if (isArray(plugins)) logger.warn(`${colors.green("plugins")} only accept object config, please check your config.`);
	const unsupportedPluginsFields = objectKeys(plugins).filter((field) => !PLUGINS_SUPPORTED_FIELDS.includes(field));
	if (unsupportedPluginsFields.length) logger.warn(t$2("message", {
		plugins: colors.green("plugins"),
		unsupported: unsupportedPluginsFields.map((field) => colors.magenta(`"${field}"`)).join(", ")
	}));
}
//#endregion
//#region src/node/detector/options.ts
/**
* Detect theme options
*
* 检测主题选项
*/
function detectThemeOptions({ plugins = {}, configFile, ...themeOptions }) {
	detectDependencies(themeOptions, plugins);
	detectMarkdown(themeOptions);
	detectPlugins(plugins);
	detectBreakingChange(themeOptions);
	return {
		configFile,
		plugins,
		themeOptions
	};
}
//#endregion
//#region src/node/detector/versions.ts
const t$1 = createTranslate({
	en: {
		title: "The following dependencies have version mismatches:",
		footer: "Please update the dependencies to the correct versions."
	},
	zh: {
		title: "以下依赖版本不匹配:",
		footer: "请更新依赖至正确的版本。"
	}
});
/**
* Detect version compatibility
*
* 检测版本兼容性
*/
function detectVersions(app) {
	detectVuepressVersion();
	detectThemeVersion(app);
}
/**
* 检查 vuepress 相关依赖,
* 当依赖不匹配时,可能会导致 vuepress 无法正常运行
* 比如 某些插件依赖了不同版本的 `@vuepress/helper` ,会导致在浏览器中无法正常运行
*/
function detectVuepressVersion() {
	const themePackage = getThemePackage();
	const userPackage = getPackage();
	const vuepressDeps = objectEntries({
		"vuepress-theme-plume": themePackage.version,
		"@vuepress/bundler-vite": themePackage.peerDependencies?.vuepress,
		"@vuepress/bundler-webpack": themePackage.peerDependencies?.vuepress,
		...themePackage.dependencies,
		...themePackage.peerDependencies
	}).reduce((deps, [name, version]) => {
		if (name.includes("vuepress") && version && version !== "workspace:*") deps[name] = version;
		return deps;
	}, {});
	/**
	* 检查依赖是否匹配
	* TODO: 检查 pnpm catalog
	*/
	const detect = (deps) => {
		const results = [];
		if (!deps || isEmptyObject(deps)) return results;
		for (const [name, version] of objectEntries(deps)) {
			const resolved = resolveVersion(version);
			if (resolved && vuepressDeps[name] && vuepressDeps[name] !== resolved) results.push({
				name,
				expected: vuepressDeps[name],
				current: version
			});
		}
		return results;
	};
	const devResults = detect(userPackage.devDependencies);
	const prodResults = detect(userPackage.dependencies);
	if (devResults.length || prodResults.length) {
		const output = (deps) => deps.map((dep) => `  ${colors.green(dep.name)}: ${colors.gray(dep.current)} -> ${colors.cyan(dep.expected)}`).join("  \n");
		logger.warn(`${t$1("title")}
${devResults.length ? `\ndevDependencies:\n${output(devResults)}\n` : ""}${prodResults.length ? `\ndependencies:\n${output(prodResults)}\n` : ""}
${t$1("footer")}
`);
	}
}
/**
* 检查用户是否升级主题版本,
* 如果升级了主题版本,则清空缓存
*/
function detectThemeVersion(app) {
	if (app.env.isBuild) return;
	try {
		const versionCache = app.dir.cache(".theme-plume-version");
		const current = getThemePackage().version;
		const updateCache = () => {
			fs$2.mkdirSync(path$1.dirname(versionCache), { recursive: true });
			fs$2.writeFileSync(versionCache, current, "utf-8");
		};
		if (!fs$2.existsSync(versionCache)) {
			updateCache();
			return;
		}
		if ((fs$2.readFileSync(versionCache, "utf-8") || "") === current) return;
		/**
		* 当主题版本有更新时,清空缓存,
		* 避免由于缓存问题,导致主题的更新内容无法生效
		*/
		fs$2.rmSync(app.dir.cache(), { recursive: true });
		fs$2.rmSync(app.dir.temp(), { recursive: true });
		updateCache();
	} catch {}
}
const RE_FLAG = /^[\^~<>=]+/;
function resolveVersion(version) {
	if (RE_FLAG.test(version)) return version.replace(RE_FLAG, "");
	if (/^\d/.test(version)) return version;
	return "";
}
//#endregion
//#region src/node/pages/createPages.ts
function getRootLang(app) {
	const siteLocales = app.siteData.locales;
	if (siteLocales["/"]?.lang) return siteLocales["/"].lang;
	return app.siteData.lang;
}
/**
* Create additional pages
*
* 创建额外页面,根据集合配置生成文章列表页、标签页、分类页、归档页等
*/
async function createPages(app) {
	const options = getThemeConfig();
	perf.mark("create:post-pages");
	const pageList = [];
	const locales = options.locales || {};
	const rootLang = getRootLang(app);
	for (const localePath of objectKeys(locales)) {
		const lang = app.siteData.locales?.[localePath]?.lang || rootLang;
		const opt = locales[localePath];
		const collections = opt.collections?.filter((item) => item.type === "post");
		if (!collections?.length) continue;
		for (const post of collections) {
			const link = withBase(post.link || post.dir, localePath);
			if (post.postList !== false) pageList.push(createPage(app, {
				path: link,
				frontmatter: {
					lang,
					_pageLayout: "posts",
					title: post.title || opt.postsText || options.postsText || "Posts"
				}
			}));
			if (post.tags !== false) pageList.push(createPage(app, {
				path: withBase(post.tagsLink || `${link}/tags/`, localePath),
				frontmatter: {
					lang,
					_pageLayout: "posts-tags",
					title: opt.tagText || options.tagText || "Tags"
				}
			}));
			if (post.archives !== false) pageList.push(createPage(app, {
				path: withBase(post.archivesLink || `${link}/archives/`, localePath),
				frontmatter: {
					lang,
					_pageLayout: "posts-archives",
					title: opt.archiveText || options.archiveText || "Archives"
				}
			}));
			if (post.categories !== false) pageList.push(createPage(app, {
				path: withBase(post.categoriesLink || `${link}/categories/`, localePath),
				frontmatter: {
					lang,
					_pageLayout: "posts-categories",
					title: opt.categoryText || options.categoryText || "Categories"
				}
			}));
		}
	}
	app.pages.push(...await Promise.all(pageList));
	perf.log("create:post-pages");
}
//#endregion
//#region src/node/pages/autoCategory.ts
let uuid = 1e4;
const cache$2 = {};
const RE_CATEGORY = /^(?:(\d+)\.)?([\s\S]+)$/;
/**
* Auto category for page
*
* 自动为页面生成分类信息,根据文件路径和集合配置自动确定页面所属的分类
*/
function autoCategory(page) {
	const collection = findCollection(page);
	if (collection?.type !== "post") return;
	const pagePath = page.filePathRelative;
	if (page.data.type || !pagePath || collection.categories === false) return;
	const collectionDir = ensureEndingSlash(path.join(page.pathLocale, collection.dir));
	const list = ensureLeadingSlash(pagePath).slice(collectionDir.length).split("/").slice(0, -1);
	const categoryList = list.map((category, index) => {
		const match = category.match(RE_CATEGORY) || [];
		if (!cache$2[match[2]] && !match[1]) cache$2[match[2]] = uuid++;
		return {
			id: hash$1(list.slice(0, index + 1).join("-")).slice(0, 6),
			sort: Number(match[1] || cache$2[match[2]]),
			name: match[2]
		};
	});
	page.data.categoryList = collection.categoriesTransform?.(categoryList) || categoryList;
}
//#endregion
//#region src/node/pages/encryptPage.ts
/**
* Encrypt page
*
* 加密页面,将页面的密码转换为加密后的哈希值并存储在页面数据中
*/
async function encryptPage(page) {
	const password = toArray(page.frontmatter.password);
	if (password.length) page.data._e = (await pMap(password, (item) => genEncrypt(item))).join(":");
	deleteKey(page.frontmatter, "password");
}
//#endregion
//#region src/node/pages/pageBulletin.ts
/**
* Enable bulletin for page
*
* 为页面启用公告栏,根据全局或语言环境配置决定是否显示公告栏
*/
function enableBulletin(page) {
	const options = getThemeConfig();
	if (isPlainObject$1(options.bulletin)) {
		const enablePage = options.bulletin.enablePage;
		page.data.bulletin = (isFunction$1(enablePage) ? enablePage(page) : enablePage) ?? true;
	}
	if (options.locales?.[page.pathLocale]) {
		const bulletin = options.locales?.[page.pathLocale].bulletin;
		if (isPlainObject$1(bulletin)) {
			const enablePage = bulletin.enablePage;
			page.data.bulletin = (isFunction$1(enablePage) ? enablePage(page) : enablePage) ?? true;
		}
	}
}
//#endregion
//#region src/node/pages/extendsPage.ts
/**
* Extend page data
*
* 扩展页面数据,清理页面数据、自动分类、启用公告栏、加密页面
*/
async function extendsPageData(page) {
	cleanPageData(page);
	autoCategory(page);
	enableBulletin(page);
	await encryptPage(page);
}
function cleanPageData(page) {
	page.data.filePathRelative = page.filePathRelative;
	page.routeMeta.title = page.frontmatter.title || page.title;
	if (page.frontmatter.icon) page.routeMeta.icon = page.frontmatter.icon;
	if (page.frontmatter.badge) page.routeMeta.badge = page.frontmatter.badge;
	if (page.frontmatter.home) {
		page.frontmatter.pageLayout = "home";
		deleteKey(page.frontmatter, "home");
	}
	if (page.headers) page.data.headers = [];
	if (page.frontmatter.friends) {
		page.frontmatter.draft = true;
		page.data.type = "friends";
		page.permalink = page.permalink ?? "/friends/";
		page.frontmatter.pageLayout = "friends";
		deleteKey(page.frontmatter, "friends");
	}
	const pageType = page.frontmatter._pageLayout;
	if (pageType) {
		page.frontmatter.draft = true;
		page.data.type = pageType;
		deleteKey(page.frontmatter, "_pageLayout");
	}
	if (page.frontmatter.pageLayout === "blog" || page.frontmatter.pageLayout === "posts") {
		page.frontmatter.draft = true;
		page.data.type = "posts";
	}
	if ("externalLink" in page.frontmatter) {
		page.frontmatter.externalLinkIcon = page.frontmatter.externalLink;
		deleteKey(page.frontmatter, "externalLink");
	}
	if (page.data.filePathRelative?.endsWith(".md")) {
		if (!page._rawTitle) page._rawTitle = page.frontmatter.title || page.data.title || page.title;
		const title = page._rawTitle;
		const collection = findCollection(page);
		if (collection) {
			const newTitle = `${title} | ${collection.title}`;
			page.data.title = newTitle;
		}
	}
}
//#endregion
//#region src/node/plugins/code.ts
/**
* Setup code-related plugins
*
* 设置代码相关插件,包括代码复制和代码高亮
*/
function codePlugins(pluginOptions) {
	const options = getThemeConfig();
	const plugins = [];
	const copyCode = options.copyCode ?? pluginOptions.copyCode;
	if (copyCode !== false) {
		const { ignoreSelector = [], ...copyCodeOptions } = copyCode || {};
		plugins.push(copyCodePlugin({
			ignoreSelector: uniq([
				".vp-copy-ignore",
				".diff.remove",
				...ignoreSelector
			]),
			...copyCodeOptions
		}));
	}
	const shikiOptions = options.codeHighlighter ?? pluginOptions.shiki;
	if (shikiOptions !== false) {
		const { twoslash, langs = [], renderIndentGuides = false, colorizedBrackets = false, codeBlockTitle: _, transformers, ...restShikiOptions } = isPlainObject$1(shikiOptions) ? shikiOptions : {};
		const twoslashOptions = twoslash === true ? {} : twoslash;
		const mdPower = isPlainObject$1(pluginOptions.markdownPower) ? pluginOptions.markdownPower : {};
		const getIcon = createCodeTabIconGetter(options.markdown?.codeTabs ?? mdPower.codeTabs);
		plugins.push(shikiPlugin({
			notationDiff: true,
			notationErrorLevel: true,
			notationFocus: true,
			notationHighlight: true,
			notationWordHighlight: true,
			highlightLines: true,
			collapsedLines: false,
			transformers: [
				colorizedBrackets ? transformerColorizedBrackets(isPlainObject$1(colorizedBrackets) ? colorizedBrackets : {}) : void 0,
				renderIndentGuides ? transformerRenderIndentGuides(isPlainObject$1(renderIndentGuides) ? renderIndentGuides : {}) : void 0,
				...transformers || []
			].filter(toTruthy),
			langs: uniq([...twoslash ? [
				"ts",
				"js",
				"vue",
				"json",
				"bash",
				"sh"
			] : [], ...langs]),
			codeBlockTitle: (title, code) => {
				const icon = getIcon(title);
				return `<div class="code-block-title" data-title="${title}"><div class="code-block-title-bar"><span class="title">${icon ? `<VPIcon provider="iconify" name="${icon}"/>` : ""}${title}</span></div>${code}</div>`;
			},
			twoslash: isPlainObject$1(twoslashOptions) ? {
				...twoslashOptions,
				floatingVue: {
					classMarkdown: "vp-doc",
					...twoslashOptions.floatingVue
				}
			} : twoslashOptions,
			..."theme" in restShikiOptions ? {} : { themes: {
				light: "vitesse-light",
				dark: "vitesse-dark"
			} },
			...restShikiOptions
		}));
	}
	return plugins;
}
//#endregion
//#region src/node/plugins/markdown.ts
/**
* Setup markdown plugins
*
* 设置 Markdown 增强插件,包括提示、图像、数学公式、图表等功能
*/
function markdownPlugins(pluginOptions) {
	const options = getThemeConfig();
	const plugins = [];
	let { hint, image, include, math, mdChart, mdPower } = splitMarkdownOptions(options.markdown ?? {});
	const obsidian = isPlainObject(mdPower.obsidian) ? mdPower.obsidian : {};
	plugins.push(markdownHintPlugin({
		hint: hint.hint ?? true,
		alert: mdPower.obsidian === false ? hint.alert ?? true : obsidian.callout === false,
		injectStyles: false
	}));
	if (pluginOptions.markdownPower !== false) {
		const shikiOptions = options.codeHighlighter ?? pluginOptions.shiki;
		const shikiTheme = shikiOptions && "theme" in shikiOptions ? shikiOptions.theme : shikiOptions && "themes" in shikiOptions ? shikiOptions.themes : {
			light: "vitesse-light",
			dark: "vitesse-dark"
		};
		const repl = mdPower?.repl ?? pluginOptions.markdownPower?.repl;
		plugins.push(markdownPowerPlugin({
			fileTree: true,
			plot: true,
			icons: true,
			...pluginOptions.markdownPower || {},
			...mdPower,
			repl: repl ? {
				theme: shikiTheme,
				...repl
			} : repl
		}));
	}
	mdChart ??= pluginOptions.markdownChart;
	if (mdChart) plugins.push(markdownChartPlugin({
		DANGEROUS_ALLOW_SCRIPT_EXECUTION: true,
		DANGEROUS_SCRIPT_EXECUTION_ALLOWLIST: "*",
		...mdChart
	}));
	math ??= pluginOptions.markdownMath;
	if (math !== false) plugins.push(markdownMathPlugin(math ?? { type: "katex" }));
	image ??= pluginOptions.markdownImage;
	if (image) plugins.push(markdownImagePlugin(image));
	include ??= pluginOptions.markdownInclude;
	if (include !== false) plugins.push(markdownIncludePlugin(isPlainObject(include) ? include : {}));
	return plugins;
}
function splitMarkdownOptions(options) {
	const { hint, alert, oldDemo, image, include, math, ...restOptions } = options;
	const mdChart = {};
	const mdPower = {};
	for (const key in restOptions) if (MARKDOWN_CHART_FIELDS.includes(key)) mdChart[key] = restOptions[key];
	else if (MARKDOWN_POWER_FIELDS.includes(key)) mdPower[key] = restOptions[key];
	const mdChartKeys = objectKeys(mdChart);
	return {
		hint: {
			hint,
			alert
		},
		image,
		include,
		math,
		mdChart: mdChartKeys.length && mdChartKeys.some((key) => mdChart[key] !== false) ? mdChart : false,
		mdPower
	};
}
//#endregion
//#region src/node/plugins/git.ts
/**
* Setup git plugin
*
* 设置 Git 插件,用于显示文章的最后更新时间、贡献者和变更历史
*/
function gitPlugin$1(app, pluginOptions) {
	const options = getThemeConfig();
	if (!(pluginOptions.git ?? app.env.isBuild)) return [gitPlugin({
		createdTime: false,
		updatedTime: false,
		contributors: false,
		changelog: false
	})];
	const excludes = [
		"home",
		"friends",
		"page",
		"custom",
		false
	];
	const changelogOptions = isPlainObject$1(options.changelog) ? options.changelog : {};
	return [gitPlugin({
		updatedTime: options.lastUpdated !== false,
		contributors: isPlainObject$1(options.contributors) || options.contributors === true ? {
			avatar: true,
			...options.contributors === true ? {} : options.contributors
		} : false,
		changelog: options.changelog && options.docsRepo ? {
			repoUrl: options.docsRepo,
			...changelogOptions
		} : options.changelog,
		filter(page) {
			if (page.frontmatter.home || excludes.includes(page.frontmatter.pageLayout)) return false;
			return true;
		}
	})];
}
//#endregion
//#region src/node/prepare/prepareEncrypt.ts
const isStringLike = (value) => isString(value) || isNumber(value);
const separator = ":";
let contentHash = "";
let fsCache$1 = null;
/**
* Prepare encryption configuration
*
* 准备加密配置,处理主题的加密选项并生成加密相关的临时文件
*/
async function prepareEncrypt(app) {
	perf.mark("prepare:encrypt");
	const { encrypt } = getThemeConfig();
	if (!fsCache$1 && app.env.isDev) {
		fsCache$1 = createFsCache(app, "encrypt");
		await fsCache$1.read();
	}
	contentHash = fsCache$1?.data?.[0] ?? "";
	let resolvedEncrypt = fsCache$1?.data?.[1];
	const currentHash = encrypt ? hash$1(JSON.stringify(encrypt)) : "";
	if (!contentHash || contentHash !== currentHash || !resolvedEncrypt) {
		contentHash = currentHash;
		resolvedEncrypt = await resolveEncrypt(encrypt);
	}
	await writeTemp(app, "internal/encrypt.js", resolveContent(app, {
		name: "encrypt",
		content: resolvedEncrypt
	}));
	fsCache$1?.write([currentHash, resolvedEncrypt], app.env.isBuild);
	perf.log("prepare:encrypt");
}
async function resolveEncrypt(encrypt) {
	const admin = encrypt?.admin ? (await pMap(toArray(encrypt.admin).filter(isStringLike), (item) => genEncrypt(item))).join(separator) : "";
	const encryptRules = objectKeys(encrypt?.rules ?? {}).reduce((acc, key) => {
		acc[encodeData(key)] = encrypt.rules[key];
		return acc;
	}, {});
	const rules = {};
	const keys = objectKeys(encryptRules);
	if (!isEmptyObject(encryptRules)) for (const key of keys) {
		const index = keys.indexOf(key);
		rules[String(index)] = (await pMap(toArray(encryptRules[key]).filter(isStringLike), (item) => genEncrypt(item))).join(separator);
	}
	return [
		encodeData(JSON.stringify(keys)),
		encodeData(JSON.stringify(rules)),
		encrypt?.global ? 1 : 0,
		admin
	];
}
/**
* Check if a page is encrypted
*
* 检查页面是否需要加密,根据页面的路径或文件相对路径匹配加密规则
*/
function isEncryptPage(page, encrypt) {
	if (!encrypt) return false;
	if (page.data._e) return true;
	const rules = encrypt.rules ?? {};
	return objectKeys(rules).some((match) => {
		const relativePath = page.data.filePathRelative || "";
		if (match[0] === "^") {
			const regex = new RegExp(match);
			return regex.test(page.path) || regex.test(relativePath);
		}
		if (match.endsWith(".md")) return relativePath.endsWith(match);
		return page.path.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match));
	});
}
//#endregion
//#region src/node/plugins/llms.ts
const CODE_BLOCK_RE = /(?:^|\n)(?<marker>\s*`{3,})([\s\w])[\s\S]*?\n\k<marker>(?:\n|$)/g;
const ENCRYPT_CONTAINER_RE = /(?:^|\n)(?<marker>\s*:{3,})\s*encrypt\b[\s\S]*?\n\k<marker>(?:\n|$)/g;
const RESTORE_RE = /<!-- llms-code-block:(\w+) -->/g;
/**
* Setup LLMs plugin
*
* 设置 LLM 插件,用于生成 LLM 友好的网站内容,支持自定义 Markdown 转换和模板
*/
function llmsPlugin$1(app, userOptions) {
	if (!app.env.isBuild) return [];
	const { llmsTxtTemplateGetter, transformMarkdown, ...userLLMsTxt } = isPlainObject$2(userOptions) ? userOptions : {};
	function tocGetter(llmPages, llmState) {
		const options = getThemeConfig();
		const { currentLocale } = llmState;
		const collections = options.locales?.[currentLocale]?.collections || [];
		if (!collections.length) return "";
		let tableOfContent = "";
		const usagePages = [];
		collections.filter((item) => item.type === "post").forEach(({ title, linkPrefix, link }) => {
			tableOfContent += `### ${title}\n\n`;
			const withLinkPrefix = genStarsWith(linkPrefix, currentLocale);
			const withLink = genStarsWith(link, currentLocale);
			const withFallback = genStarsWith("/article/", currentLocale);
			const list = [];
			llmPages.forEach((page) => {
				if (withLinkPrefix(page.path) || withLink(page.path) || withFallback(page.path)) {
					usagePages.push(page);
					list.push(generateTOCLink(page, llmState));
				}
			});
			tableOfContent += `${list.filter(Boolean).join("")}\n`;
		});
		const generateTOCLink$1 = (path) => {
			const filepath = path.endsWith("/") ? `${path}README.md` : path.endsWith(".md") ? path : `${path || "README"}.md`;
			const link = path.endsWith("/") ? `${path}index.html` : `${path}.html`;
			const page = llmPages.find((item) => {
				return ensureLeadingSlash$1(item.filePathRelative || "") === filepath || link === item.path;
			});
			if (page) {
				usagePages.push(page);
				return generateTOCLink(page, llmState);
			}
			return "";
		};
		const processAutoSidebar = (prefix) => {
			const list = [];
			llmPages.forEach((page) => {
				if (ensureLeadingSlash$1(page.filePathRelative || "").startsWith(prefix)) {
					usagePages.push(page);
					list.push(generateTOCLink(page, llmState));
				}
			});
			return list.filter(Boolean);
		};
		const processSidebar = (items, prefix) => {
			const result = [];
			items.forEach((item) => {
				if (typeof item === "string") result.push(generateTOCLink$1(normalizePath(prefix, item)));
				else {
					if (item.link) result.push(generateTOCLink$1(normalizePath(prefix, item.link)));
					if (item.items === "auto") result.push(...processAutoSidebar(normalizePath(prefix, item.prefix)));
					else if (item.items?.length) result.push(...processSidebar(item.items, normalizePath(prefix, item.prefix)));
				}
			});
			return result;
		};
		collections.filter((collection) => collection.type === "doc").forEach(({ dir, title, sidebar = [] }) => {
			tableOfContent += `### ${title}\n\n`;
			const prefix = normalizePath(ensureLeadingSlash$1(withBase(dir, currentLocale)));
			if (sidebar === "auto") tableOfContent += `${processAutoSidebar(prefix).join("")}\n`;
			else if (sidebar.length) {
				const home = generateTOCLink$1(ensureEndingSlash$1(prefix));
				const list = processSidebar(sidebar, prefix);
				if (home && !list.includes(home)) list.unshift(home);
				tableOfContent += `${list.join("")}\n`;
			}
		});
		const unUsagePages = llmPages.filter((page) => !usagePages.includes(page));
		if (unUsagePages.length) {
			tableOfContent += "### Others\n\n";
			tableOfContent += unUsagePages.map((page) => generateTOCLink(page, llmState)).join("");
		}
		return tableOfContent;
	}
	return [llmsPlugin({
		filter: (page) => {
			const options = getThemeConfig();
			return options.encrypt?.global ? false : !isEncryptPage(page, options.encrypt);
		},
		locale: "/",
		...userLLMsTxt,
		transformMarkdown(markdown, page) {
			let rematches = {};
			markdown = markdown.replaceAll(CODE_BLOCK_RE, (content) => {
				const contentHash = hash$1(content);
				rematches[contentHash] = content;
				return `<!-- llms-code-block:${contentHash} -->`;
			});
			markdown = markdown.replaceAll(ENCRYPT_CONTAINER_RE, "");
			markdown = markdown.replaceAll(RESTORE_RE, (_, hash) => {
				return rematches[hash] || "";
			});
			rematches = {};
			return transformMarkdown?.(markdown, page) ?? markdown;
		},
		llmsTxtTemplateGetter: {
			toc: tocGetter,
			...llmsTxtTemplateGetter
		}
	})];
}
function genStarsWith(stars, locale) {
	return (url) => {
		if (!stars) return false;
		return url.startsWith(withBase(stars, locale));
	};
}
function normalizePath(prefix, path = "") {
	if (path.startsWith("/")) return path;
	return `${ensureEndingSlash$1(prefix)}${path}`;
}
//#endregion
//#region src/node/plugins/setupPlugins.ts
/**
* Setup theme plugins
*
* 设置主题插件,根据配置初始化搜索、评论、SEO、水印等插件
*/
function setupPlugins(app, pluginOptions) {
	const isProd = app.env.isBuild;
	const options = getThemeConfig();
	const hostname = options.hostname;
	const plugins = [
		fontsPlugin(),
		...codePlugins(pluginOptions),
		...markdownPlugins(pluginOptions)
	];
	if (pluginOptions.nprogress !== false) plugins.push(nprogressPlugin());
	const readingTime = options.readingTime ?? pluginOptions.readingTime;
	if (readingTime !== false) plugins.push(readingTimePlugin({
		locales: { "/zh/": {
			word: "$word 字",
			less1Minute: "小于 1 分钟",
			time: "约 $time 分钟"
		} },
		...readingTime
	}));
	if (pluginOptions.photoSwipe !== false) plugins.push(photoSwipePlugin({
		selector: ".vp-doc :not(a) > img:not([no-view],.no-view,.ignore)",
		...pluginOptions.photoSwipe
	}));
	/**
	* 内容水印
	*/
	const watermark = options.watermark ?? pluginOptions.watermark;
	if (watermark) plugins.push(watermarkPlugin({
		enabled: true,
		...isPlainObject$1(watermark) ? watermark : {}
	}));
	/**
	* 文章评论
	*/
	const comment = options.comment ?? pluginOptions.comment;
	if (comment) plugins.push(commentPlugin(comment));
	if (options.search !== false) {
		const search = (options.search === true ? { provider: "local" } : options.search) ?? (pluginOptions.docsearch ? {
			provider: "algolia",
			...pluginOptions.docsearch
		} : {
			provider: "local",
			...isPlainObject$1(pluginOptions.search) ? pluginOptions.search : {}
		});
		const { provider, ...searchOptions } = search;
		if (provider === "algolia") {
			if (search.appId && search.apiKey) plugins.push(docsearchPlugin(searchOptions));
			else console.error("docsearch plugin: appId and apiKey are both required");
		} else plugins.push(searchPlugin(searchOptions));
	}
	/**
	* git 插件配置
	* 1. 最后更新时间
	* 2. 贡献者列表
	* 3. 更新日志
	*/
	plugins.push(...gitPlugin$1(app, pluginOptions));
	/**
	* 资源替换
	*/
	const replaceAssets = options.replaceAssets ?? pluginOptions.replaceAssets;
	if (replaceAssets) plugins.push(replaceAssetsPlugin(replaceAssets));
	const llmstxt = options.llmstxt ?? pluginOptions.llmstxt;
	if (llmstxt) plugins.push(...llmsPlugin$1(app, llmstxt));
	/**
	* 站点地图,仅在生产构建时,且 hostname 存在时生效
	*/
	if (pluginOptions.sitemap !== false && isProd) {
		const sitemapOptions = isPlainObject$1(pluginOptions.sitemap) ? pluginOptions.sitemap : {};
		sitemapOptions.hostname ||= hostname;
		if (sitemapOptions.hostname) plugins.push(sitemapPlugin(sitemapOptions));
	}
	/**
	* SEO,仅在生产构建时,且 hostname 存在时生效
	*/
	if (pluginOptions.seo !== false && hostname && isProd) {
		const seoOptions = isPlainObject$1(pluginOptions.seo) ? pluginOptions.seo : {};
		seoOptions.hostname ||= hostname;
		if (seoOptions.hostname) plugins.push(seoPlugin(seoOptions));
	}
	/**
	* 编译缓存,默认使用文件缓存
	*/
	if (options.cache !== false) plugins.push(cachePlugin({
		...isPlainObject$1(pluginOptions.cache) ? pluginOptions.cache : {},
		type: options.cache || "filesystem"
	}));
	return plugins;
}
//#endregion
//#region src/node/prepare/prepareArticleTagColor.ts
const PRESET = [
	[
		"#6aa1b7",
		"#5086a1",
		"rgba(131, 208, 218, 0.314)"
	],
	[
		"#299764",
		"#18794e",
		"rgba(16, 185, 129, 0.14)"
	],
	[
		"#946300",
		"#915930",
		"rgba(234, 179, 8, 0.14)"
	],
	[
		"#d5393e",
		"#b8272c",
		"rgba(244, 63, 94, 0.14)"
	],
	[
		"#7e4cc9",
		"#6f42c1",
		"rgba(159, 122, 234, 0.14)"
	],
	[
		"#3a5ccc",
		"#3451b2",
		"rgba(100, 108, 255, 0.14)"
	],
	[
		"#fab10f",
		"#f39c12",
		"rgba(255, 213, 0, 0.14)"
	],
	[
		"#cc6699",
		"#be3f7f",
		"rgba(161, 54, 107, 0.14)"
	],
	[
		"#55aaee",
		"#2391e9",
		"rgba(21, 123, 206, 0.1333)"
	],
	[
		"#9933cc",
		"#aa56d5",
		"rgba(179, 102, 217, 0.2)"
	],
	[
		"#cc3366",
		"#d55680",
		"rgba(217, 102, 140, 0.2)"
	],
	[
		"#cc9933",
		"#be7f3f",
		"rgba(161, 107, 54, 0.2)"
	],
	[
		"#9966cc",
		"#7171b8",
		"rgba(83, 83, 167, 0.14)"
	],
	[
		"#66cccc",
		"#3fbebe",
		"rgba(54, 161, 161, 0.14)"
	],
	[
		"#3366cc",
		"#5680d5",
		"rgba(102, 140, 217, 0.14)"
	],
	[
		"#339999",
		"#41c0c0",
		"rgba(83, 198, 198, 0.2)"
	],
	[
		"#a6623b",
		"#c17950",
		"rgba(199, 134, 97, 0.2411)"
	],
	[
		"#8ecaef",
		"#55afe7",
		"rgba(42, 155, 225, 0.147)"
	]
];
let cache$1 = {};
/**
* Prepare article tag colors
*
* 准备文章标签颜色,收集页面中使用的标签并生成对应的 CSS 和 JS 文件
*/
async function prepareArticleTagColors(app) {
	perf.mark("prepare:tag-colors");
	const { js, css } = genCode(app);
	await writeTemp(app, "internal/articleTagColors.css", css);
	await writeTemp(app, "internal/articleTagColors.js", js);
	if (app.env.isBuild) cache$1 = {};
	perf.log("prepare:tag-colors");
}
/**
* Generate tag color CSS and JS code
*
* 生成标签颜色的 CSS 和 JS 代码,遍历所有页面收集标签并生成对应的样式和脚本
*/
function genCode(app) {
	const articleTagColors = {};
	const tagList = /* @__PURE__ */ new Set();
	app.pages.forEach((page) => {
		const { frontmatter: { tags } } = page;
		if (tags) toArray(tags).forEach((tag) => {
			if (tag) tagList.add(tag);
		});
	});
	tagList.forEach((tag) => {
		const code = getTagCode(tag);
		if (!cache$1[code]) cache$1[code] = nanoid(4);
		if (!articleTagColors[tag]) articleTagColors[tag] = cache$1[code];
	});
	return {
		js: resolveContent(app, {
			name: "articleTagColors",
			content: articleTagColors,
			before: `import './articleTagColors.css'`
		}),
		css: genCSS()
	};
}
function getTagCode(tag) {
	tag = tag.toLowerCase();
	let code = 0;
	for (let i = 0; i < tag.length; i++) code += tag.charCodeAt(i);
	return code % PRESET.length;
}
function genCSS() {
	let css = "";
	for (const [code, className] of objectEntries(cache$1)) {
		const index = Number(code);
		const [color, hoverColor, backgroundColor] = PRESET[index];
		css += `\
.vp-tag-${className} {
  --vp-tag-color: ${color};
  --vp-tag-hover-color: ${hoverColor};
  --vp-tag-bg: ${backgroundColor};
}
`;
	}
	return css;
}
//#endregion
//#region src/node/prepare/prepareCollections.ts
/**
* Prepare collections data
*
* 准备集合数据,为每个语言环境处理集合配置并生成临时文件
*/
async function prepareCollections(app) {
	perf.mark("prepare:collections");
	const { collections: fallback, locales } = getThemeConfig();
	let data = {};
	for (const [locale, opt] of entries(locales || {})) {
		let collections = opt.collections;
		if (locale === "/" && !collections?.length) collections = fallback;
		if (!collections?.length) continue;
		data[locale] = collections?.map((item) => {
			if (item.type === "post") return omit(item, [
				"include",
				"exclude",
				"autoFrontmatter"
			]);
			else return omit(item, ["sidebar", "autoFrontmatter"]);
		});
	}
	await writeTemp(app, "internal/collectionsData.js", resolveContent(app, {
		name: "collections",
		content: data
	}));
	if (app.env.isBuild) data = {};
	perf.log("prepare:collections");
}
//#endregion
//#region src/node/prepare/prepareHomeHeroEffects.ts
const effectDeps = {
	"prism": ["ogl"],
	"pixel-blast": ["three", "postprocessing"],
	"hyper-speed": ["three", "postprocessing"],
	"liquid-ether": ["three"],
	"dot-grid": ["gsap"],
	"iridescence": ["ogl"],
	"orb": ["ogl"],
	"beams": ["three"],
	"dark-veil": ["ogl"]
};
const effectMapping = {
	"tint-plate": "TintPlate",
	"prism": "Prism",
	"pixel-blast": "PixelBlast",
	"hyper-speed": "HyperSpeed",
	"liquid-ether": "LiquidEther",
	"dot-grid": "DotGrid",
	"iridescence": "Iridescence",
	"orb": "Orb",
	"beams": "Beams",
	"lightning": "Lightning",
	"dark-veil": "DarkVeil"
};
const allEffects = objectKeys(effectMapping);
const t = createTranslate({
	en: {
		unknown: `[Home hero background effect] Unknown effect: {{ effect }}`,
		uninstall: `[Home hero background effect] The following effect is missing necessary dependencies: {{ deps }}`,
		install: `Run the installation command: {{ command }}`
	},
	zh: {
		unknown: `[首页 hero 背景效果] 未知的 effect: {{ effect }}`,
		uninstall: `[首页 hero 背景效果] 以下效果缺少必要依赖: {{ deps }}`,
		install: `运行安装命令:  {{ command }}`
	}
});
/**
* Prepare home page hero effects
*
* 准备首页 Hero 动画效果,从 frontmatter 收集效果配置并写入临时文件,同时检测缺失的依赖
*/
async function prepareHomeHeroEffects(app) {
	perf.mark("prepare:home-hero-effects");
	const { effects, unknownEffects } = getEffectsByFrontmatter(app);
	if (unknownEffects.length) logger.warn(t("unknown", { effect: colors.cyan(unknownEffects.join(", ")) }));
	await writeToInternalTemp(app, effects);
	detectMissingDeps(effects);
	perf.log("prepare:home-hero-effects");
}
async function writeToInternalTemp(app, effects) {
	let imports = "";
	let exports = "export const effectComponents = {\n";
	for (const effect of effects) {
		const component = effectMapping[effect];
		imports += `import ${component} from '@theme/background/${component}.vue'\n`;
		exports += `  '${effect}': ${component},\n`;
	}
	exports += "}\n\nexport const effects = Object.keys(effectComponents)\n";
	await writeTemp(app, "internal/home-hero-effects.js", `${imports}\n${exports}`);
}
function getEffectsByFrontmatter(app) {
	const effects = [];
	const unknownEffects = [];
	for (const page of app.pages) {
		const fm = page.frontmatter;
		const config = fm.config;
		if (!(fm.home || fm.pageLayout === "home") || !config?.length) continue;
		for (const item of config) if (item.type === "hero") {
			const effect = item.effect;
			if (effect) {
				if (allEffects.includes(effect)) effects.push(effect);
				else unknownEffects.push(effect);
			}
			if (item.background) {
				if (allEffects.includes(item.background)) effects.push(item.background);
			}
		}
	}
	return {
		effects: uniq(effects),
		unknownEffects: uniq(unknownEffects)
	};
}
function detectMissingDeps(effects) {
	const missingDeps = {};
	for (const effect of effects) {
		const deps = effectDeps[effect];
		if (deps?.length) {
			const uninstall = deps.filter((dep) => !isPackageExists(dep));
			if (uninstall.length) missingDeps[effect] = uninstall;
		}
	}
	if (isEmptyObject(missingDeps)) return;
	const dependencies = uniq(Object.values(missingDeps).flat());
	logger.warn(t("uninstall", { deps: colors.bold(JSON.stringify(missingDeps)) }));
	const agent = getUserAgent();
	if (agent) {
		const { command = "", args = [] } = resolveCommand(agent, "add", dependencies) || {};
		logger.info(t("install", { command: colors.cyan(`${command} ${args.join(" ")}`) }));
	}
}
//#endregion
//#region src/node/prepare/prepareIcons.ts
const ICON_REGEXP = /<(?:VP)?(Icon|Card|LinkCard|Button)([^>]*)>/g;
const ICON_NAME_REGEXP = /(?:name|icon|suffix-icon)="([^"]+)"/;
const URL_CONTENT_REGEXP = /(url\([\s\S]+\))/;
const ICONIFY_NAME = /^(?:iconify\s+)?[\w-]+:[\w-]+$/;
const JS_FILENAME = "internal/iconify.js";
const CSS_FILENAME = "internal/iconify.css";
const isInstalled = isPackageExists("@iconify/json");
let locate;
let fsCache = null;
let cache = {};
const socialFallbacks = {
	twitter: "x",
	weibo: "sinaweibo"
};
/**
* Prepare icon data for theme
*
* 准备主题图标数据,收集页面中使用的图标并生成对应的 CSS 和 JS 文件
*/
async function prepareIcons(app) {
	perf.mark("prepare:icons:total");
	const options = getThemeConfig();
	const icons = {
		co: [],
		bg: {},
		mask: {}
	};
	if (!isInstalled) {
		await writeTemp(app, JS_FILENAME, resolveContent(app, {
			name: "icons",
			content: icons
		}));
		return;
	}
	if (!fsCache && app.env.isDev) {
		fsCache = createFsCache(app, "iconify");
		await fsCache.read();
	}
	perf.mark("prepare:pages:icons");
	const iconOptions = options.markdown?.icon || {};
	const iconList = [];
	const preload = iconOptions.preload;
	if (isArray(preload)) iconList.push(...preload);
	else if (isPlainObject(preload)) {
		const { preflight = [], ...rest } = preload;
		iconList.push(...preflight);
		for (const [collect, names] of objectEntries(rest)) iconList.push(...names.map((name) => `${collect}:${name}`));
	}
	app.pages.forEach((page) => iconList.push(...getIconsWithPage(page, iconOptions)));
	iconList.push(...getIconWithThemeConfig(options, iconOptions));
	const collectMap = {};
	uniq(iconList).filter((icon) => {
		if (fsCache?.data?.[icon] && !cache[icon]) cache[icon] = fsCache.data[icon];
		return !cache[icon];
	}).forEach((iconName) => {
		const [collect, name] = iconName.split(":");
		if (!collectMap[collect]) collectMap[collect] = [];
		collectMap[collect].push(name);
	});
	perf.log("prepare:pages:icons");
	perf.mark("prepare:icons:imports");
	if (!locate) locate = (await interopDefault(import("@iconify/json"))).locate;
	const unknownList = (await Promise.all(objectEntries(collectMap).map(([collect, names]) => resolveCollect(collect, names)))).flat();
	if (unknownList.length) logger.warn(`[iconify] Unknown icons: ${unknownList.join(", ")}`);
	perf.log("prepare:icons:imports");
	let cssCode = "";
	for (const [, { className, content, background, collect, name }] of objectEntries(cache)) {
		if (!icons.co.includes(collect)) icons.co.push(collect);
		const index = icons.co.indexOf(collect);
		const key = background ? "bg" : "mask";
		icons[key][index] ??= [];
		icons[key][index].push(name);
		cssCode += `.${className} {\n  --icon: ${content};\n}\n`;
	}
	await Promise.all([writeTemp(app, CSS_FILENAME, cssCode), writeTemp(app, JS_FILENAME, resolveContent(app, {
		name: "icons",
		content: icons,
		before: `import './iconify.css'`
	}))]);
	fsCache?.write(cache, app.env.isBuild);
	if (app.env.isBuild) cache = {};
	perf.log("prepare:icons:total");
}
function isIconify(icon) {
	if (!icon || !isString(icon) || isLinkAbsolute(icon) || isLinkHttp(icon)) return false;
	const ic = icon.trim();
	return ic[0] !== "{" && ICONIFY_NAME.test(ic);
}
function withPrefix(icon, prefix) {
	icon = icon.replace(/^iconify /, "");
	if (!prefix) return icon;
	return icon.includes(":") ? icon : `${prefix}:${icon}`;
}
function getIconsWithPage(page, { provider = "iconify", prefix }) {
	const list = [];
	const matches = page.contentRendered.match(ICON_REGEXP) || [];
	for (const matched of matches) if (provider === "iconify" || matched.includes("provider=\"iconify\"")) {
		const icon = matched.match(ICON_NAME_REGEXP)?.[1];
		if (isIconify(icon)) list.push(withPrefix(icon, prefix));
	}
	const addIcon = (icon) => {
		if (icon && isIconify(icon) && (provider === "iconify" || icon.startsWith("iconify"))) list.push(withPrefix(icon, prefix));
	};
	const fm = page.frontmatter;
	addIcon(fm.icon);
	if ((fm.home || fm.pageLayout === "home") && fm.config?.length) for (const config of fm.config) {
		if (config.type === "features" && config.features.length) for (const feature of config.features) addIcon(feature.icon);
		if (config.type === "hero" && config.hero?.actions?.length) for (const action of config.hero.actions) {
			addIcon(action.icon);
			addIcon(action.suffixIcon);
		}
	}
	if (fm.pageLayout === "friends") {
		const socialList = [];
		if (fm.list?.length) for (const { socials } of fm.list) socialList.push(...socials || []);
		if (fm.groups?.length) for (const { list } of fm.groups) {
			if (!list?.length) continue;
			for (const { socials } of list) socialList.push(...socials || []);
		}
		socialList.forEach((social) => addIcon(getIconWithSocial(social)));
	}
	return list;
}
function getIconWithThemeConfig(options, { provider = "iconify", prefix }) {
	const list = [];
	const locales = options.locales || {};
	objectEntries(locales).forEach(([, { navbar, sidebar, collections, social }]) => {
		if (navbar) list.push(...getIconWithNavbar(navbar));
		const socialList = social ? [...social] : [];
		const sidebarList = Object.values(sidebar || {});
		if (collections?.length) collections.forEach((collection) => {
			if (collection.type === "doc" && collection.sidebar) sidebarList.push(collection.sidebar);
			if (collection.type === "post" && collection.social) socialList.push(...collection.social);
		});
		sidebarList.forEach((sidebar) => list.push(...getIconWithSidebar(sidebar)));
		socialList.forEach((social) => list.push(getIconWithSocial(social)));
	});
	const addIcon = (icon) => {
		if (icon && isIconify(icon) && (provider === "iconify" || icon.startsWith("iconify"))) return withPrefix(icon, prefix);
	};
	return list.map(addIcon).filter(Boolean);
}
function getIconWithNavbar(navbar) {
	const list = [];
	navbar.forEach((item) => {
		if (!isString(item)) {
			if (isIconify(item.icon)) list.push(item.icon);
			if (item.items?.length) list.push(...getIconWithNavbar(item.items));
		}
	});
	return list;
}
function getIconWithSidebar(sidebar) {
	const list = [];
	if (isArray(sidebar)) sidebar.forEach((item) => {
		if (!isString(item)) {
			if (isIconify(item.icon)) list.push(item.icon);
			if (item.items?.length) list.push(...getIconWithSidebar(item.items));
		}
	});
	else if (isPlainObject(sidebar)) objectEntries(sidebar).forEach(([, item]) => {
		if (!isString(item)) {
			if (isArray(item)) list.push(...getIconWithSidebar(item));
			else if (item.items?.length) list.push(...getIconWithSidebar(item.items));
		}
	});
	return list;
}
function getIconWithSocial({ icon }) {
	if (!icon || typeof icon !== "string") return "";
	const name = socialFallbacks[icon] || icon;
	if (name.includes(":")) return name;
	return `simple-icons:${name}`;
}
async function resolveCollect(collect, names) {
	const config = await readJSON(locate(collect));
	if (!config) {
		logger.warn(`[iconify] Can not find icon collect: ${collect}!`);
		return [];
	}
	const unknownList = [];
	for (const name of names) {
		const data = getIconData(config, name);
		const icon = `${collect}:${name}`;
		if (!data) unknownList.push(icon);
		else if (!cache[icon]) {
			const matched = getIconContentCSS(data, { height: data.height || 24 }).match(URL_CONTENT_REGEXP)?.[1] ?? "";
			/**
			* @see - https://iconify.design/docs/libraries/utils/get-icon-css.html#options
			*/
			const background = !data.body.includes("currentColor");
			cache[icon] = {
				className: normalizeClassname(icon),
				background,
				content: matched,
				collect,
				name
			};
		}
	}
	return unknownList;
}
function normalizeClassname(icon) {
	const [collect, name] = icon.split(":");
	return `vpi-${collect}-${name}`;
}
async function readJSON(filepath) {
	try {
		return await fs.readJSON(filepath, "utf-8");
	} catch {
		return null;
	}
}
//#endregion
//#region src/node/prepare/preparePostsData.ts
const HEADING_RE = /<h(\d)[^>]*>.*?<\/h\1>/gi;
const EXCERPT_SPLIT = "<!-- more -->";
function getTimestamp(time) {
	return new Date(time).getTime();
}
function sortPage(prev, next) {
	return getTimestamp(prev.frontmatter.createTime || prev.date) < getTimestamp(next.frontmatter.createTime || next.date) ? 1 : -1;
}
function processPostData(page, isBuild, encrypt) {
	const tags = page.frontmatter.tags;
	const date = page.frontmatter.createTime || page.frontmatter.date || (page.date === "0000-00-00" ? fs.statSync(page.filePath).birthtime : page.date);
	const data = {
		path: page.path,
		title: page.title,
		categoryList: page.data.categoryList,
		tags,
		sticky: page.frontmatter.sticky,
		createTime: dayjs(new Date(date)).format("YYYY/MM/DD HH:mm:ss"),
		lang: page.lang,
		excerpt: "",
		cover: page.frontmatter.cover,
		coverStyle: page.frontmatter.coverStyle,
		readingTime: page.data.readingTime
	};
	if (typeof data.cover === "object") logger.warn(`cover should be a path string, please use string instead. (${page.filePathRelative})`);
	if (isEncryptPage(page, encrypt)) data.encrypt = true;
	if (page.frontmatter.draft && !isBuild) data.draft = true;
	const fmExcerpt = page.frontmatter.excerpt;
	if (fmExcerpt !== false) {
		if (typeof fmExcerpt === "string") data.excerpt = fmExcerpt;
		else if (page.contentRendered.includes(EXCERPT_SPLIT)) {
			let excerpt = page.contentRendered.split(EXCERPT_SPLIT)[0];
			excerpt = excerpt.replace(HEADING_RE, "");
			data.excerpt = excerpt;
		}
	}
	return data;
}
/**
* Prepare posts data
*
* 准备文章数据,过滤非草稿文章并为每个集合和语言环境生成文章列表数据
*/
async function preparedPostsData(app) {
	const isBuild = app.env.isBuild;
	const { encrypt, locales } = getThemeConfig();
	perf.mark("prepare:posts-data");
	const postsData = {};
	const pages = app.pages.filter((page) => page.filePathRelative && page.filePath && page.frontmatter.article !== false && (page.frontmatter.draft === true ? !isBuild : true));
	for (const [locale, { collections }] of Object.entries(locales || {})) {
		if (!collections) continue;
		for (const { include, exclude, dir } of collections.filter((item) => item.type === "post")) {
			const source = app.dir.source(removeLeadingSlash(withBase(dir, locale)));
			const isMatched = createMatcher(include, exclude);
			postsData[withBase(dir, locale)] = pages.filter(({ filePath }) => {
				return filePath?.startsWith(source) && isMatched(path.relative(source, filePath));
			}).sort(sortPage).map((page) => processPostData(page, isBuild, encrypt));
		}
	}
	await writeTemp(app, "internal/postsData.js", resolveContent(app, {
		name: "postsData",
		content: postsData
	}));
	perf.log("prepare:posts-data");
}
//#endregion
//#region src/node/prepare/prepareSidebar.ts
/**
* Prepare sidebar data
*
* 准备侧边栏数据,处理所有语言环境的侧边栏配置并生成临时文件
*/
async function prepareSidebar(app) {
	perf.mark("prepare:sidebar");
	const sidebar = getAllSidebar();
	const { resolved, autoHome } = getSidebarData(app, sidebar);
	sidebar.__auto__ = resolved;
	sidebar.__home__ = autoHome;
	await writeTemp(app, "internal/sidebar.js", resolveContent(app, {
		name: "sidebar",
		content: sidebar
	}));
	perf.log("prepare:sidebar");
}
function getSidebarData(app, locales) {
	const autoDirList = [];
	const resolved = {};
	objectEntries(locales).forEach(([localePath, sidebar]) => {
		if (!sidebar) return;
		if (isArray(sidebar)) autoDirList.push(...findAutoDirList(sidebar));
		else if (isPlainObject(sidebar)) objectEntries(sidebar).forEach(([dirname, config]) => {
			const prefix = normalizeLink(localePath, removeLeadingSlash(dirname));
			if (config === "auto") autoDirList.push(prefix);
			else if (isArray(config)) autoDirList.push(...findAutoDirList(config, prefix));
			else if (config.items === "auto") autoDirList.push(normalizeLink(prefix, config.prefix));
			else autoDirList.push(...findAutoDirList(config.items || [], normalizeLink(prefix, config.prefix)));
		});
		else if (sidebar === "auto") autoDirList.push(localePath);
	});
	const autoHome = {};
	autoDirList.forEach((localePath) => {
		const { link, sidebar } = getAutoDirSidebar(app, localePath);
		resolved[localePath] = sidebar;
		if (link) autoHome[localePath] = link;
	});
	return {
		resolved,
		autoHome
	};
}
const MD_RE = /\.md$/;
const NUMBER_RE = /^\d+\./;
function resolveTitle(dirname) {
	return dirname.replace(MD_RE, "").replace(NUMBER_RE, "");
}
const RE_FILE_SORTING = /(?:(\d+)\.)?(?=[^/]+$)/;
function fileSorting(filepath) {
	if (!filepath) return false;
	const matched = filepath.match(RE_FILE_SORTING);
	const sorted = matched ? Number(matched[1]) : 0;
	if (Number.isNaN(sorted)) return Number.MAX_SAFE_INTEGER;
	return sorted;
}
function getAutoDirSidebar(app, prefix) {
	const rootPath = removeLeadingSlash(prefix);
	let pages = app.pages.filter((page) => page.data.filePathRelative?.startsWith(rootPath)).map((page) => {
		return {
			...page,
			splitPath: page.data.filePathRelative?.split("/") || []
		};
	});
	let nowIndex = Math.max(...pages.map((page) => page.splitPath.length)) - 1;
	while (nowIndex >= 0) {
		pages = pages.sort((prev, next) => {
			const pi = fileSorting(prev.splitPath?.[nowIndex]);
			const ni = fileSorting(next.splitPath?.[nowIndex]);
			if (pi === false || ni === false) return 0;
			if (pi === ni) return 0;
			return pi < ni ? -1 : 1;
		});
		nowIndex--;
	}
	const RE_INDEX = [
		"index.md",
		"README.md",
		"readme.md"
	];
	const sidebar = [];
	let rootLink = "";
	for (const page of pages) {
		const { data, title, path, frontmatter } = page;
		const paths = (data.filePathRelative || "").slice(rootPath.replace(/^\/|\/$/g, "").length + 1).split("/");
		const collection = findCollection(page);
		let index = 0;
		let dir;
		let items = sidebar;
		let parent;
		while (dir = paths[index]) {
			const text = resolveTitle(dir);
			const isHome = RE_INDEX.includes(dir);
			let current = items.find((item) => item.text === text);
			if (!current) {
				current = {
					text,
					link: void 0,
					items: [],
					collapsed: collection?.sidebarCollapsed
				};
				if (!isHome) items.push(current);
			}
			if (dir.endsWith(".md")) {
				if (isHome) {
					if (parent) parent.link = path;
					else rootLink = path;
				} else {
					current.link = path;
					current.text = title;
				}
			}
			if (frontmatter.icon && dir.endsWith(".md")) current.icon = frontmatter.icon;
			if (parent?.items?.length) parent.collapsed ??= false;
			parent = current;
			items = current.items;
			index++;
		}
	}
	return {
		link: rootLink,
		sidebar: cleanSidebar(sidebar)
	};
}
function cleanSidebar(sidebar) {
	for (const item of sidebar) if (isPlainObject(item)) {
		if (isArray(item.items)) {
			if (item.items.length === 0) deleteKey(item, ["items", "collapsed"]);
			else cleanSidebar(item.items);
		} else if (!("items" in item)) deleteKey(item, "collapsed");
	}
	return sidebar;
}
function findAutoDirList(sidebar, prefix = "") {
	const list = [];
	if (!sidebar.length) return list;
	sidebar.forEach((item) => {
		if (isPlainObject(item)) {
			const nextPrefix = normalizeLink(prefix, item.prefix || item.dir);
			if (item.items === "auto") list.push(nextPrefix);
			else if (item.items?.length) list.push(...findAutoDirList(item.items, nextPrefix));
		}
	});
	return list;
}
function getAllSidebar() {
	const options = getThemeConfig();
	const locales = {};
	for (const [locale, opt] of objectEntries(options.locales || {})) {
		const rawCollections = locale === "/" ? opt.collections || options.collections : opt.collections;
		const sidebar = locale === "/" ? opt.sidebar || options.sidebar : opt.sidebar;
		locales[locale] = {};
		for (const [key, value] of objectEntries(sidebar || {})) locales[locale][ensureLeadingSlash(key)] = isPlainObject(value) && "items" in value ? {
			...value,
			prefix: value.prefix?.startsWith("/") ? value.prefix : normalizeLink(locale, removeLeadingSlash(key))
		} : {
			items: value,
			prefix: normalizeLink(locale, removeLeadingSlash(key))
		};
		const collections = rawCollections?.filter((item) => item.type === "doc");
		if (collections?.length) {
			for (const collection of collections) if (collection.sidebar) locales[locale][normalizeLink(collection.linkPrefix || collection.dir)] = {
				items: collection.sidebar,
				prefix: normalizeLink(locale, removeLeadingSlash(collection.dir))
			};
		}
	}
	return locales;
}
//#endregion
//#region src/node/prepare/index.ts
/**
* Prepare all theme data
*
* 准备所有主题数据,包括文章标签颜色、文章列表、侧边栏、集合、加密、图标、Hero 动画效果等
*/
async function prepareData(app) {
	perf.mark("prepare:data");
	await Promise.all([
		prepareArticleTagColors(app),
		preparedPostsData(app),
		prepareSidebar(app),
		prepareCollections(app),
		prepareEncrypt(app),
		prepareIcons(app),
		prepareHomeHeroEffects(app)
	]);
	perf.log("prepare:data");
}
//#endregion
//#region src/node/prepare/prepareThemeData.ts
let bulletinFileWatcher = null;
const bulletinFiles = {};
process.on("exit", () => bulletinFileWatcher?.close());
/**
* Prepare theme data
*
* 准备主题数据,解析主题配置、处理头像尺寸、解析公告栏并更新主题数据
*/
async function prepareThemeData(app, plugins) {
	perf.mark("prepare:theme-data");
	const resolvedThemeData = resolveThemeData(app, getThemeConfig());
	await processProfileImageSize(app, resolvedThemeData, plugins);
	if (bulletinFileWatcher) {
		bulletinFileWatcher.close();
		bulletinFileWatcher = null;
	}
	await resolveBulletin(app, resolvedThemeData);
	await updateThemeData(app, resolvedThemeData);
	perf.log("prepare:theme-data");
}
async function updateThemeData(app, themeData) {
	await writeTemp(app, "internal/themePlumeData.js", resolveContent(app, {
		name: "themeData",
		content: themeData
	}));
}
async function resolveBulletin(app, themeData) {
	if (themeData.bulletin === true) themeData.bulletin = {};
	if (themeData.bulletin) themeData.bulletin.id ||= hash(themeData.bulletin);
	if (themeData.bulletin) {
		if (bulletinFiles.root || themeData.bulletin.contentFile) {
			bulletinFiles.root = themeData.bulletin.contentFile || bulletinFiles.root;
			deleteKey(themeData.bulletin, "contentFile");
			themeData.bulletin.content = await readBulletinFile(app, bulletinFiles.root);
		} else if (themeData.bulletin.content) {
			const type = themeData.bulletin.contentType ?? "text";
			themeData.bulletin.content = type === "markdown" ? app.markdown.render(themeData.bulletin.content, {
				filepath: app.dir.source(`/_bulletin.md`),
				filePathRelative: `_bulletin.md`
			}) : themeData.bulletin.content;
		}
	}
	if (themeData.locales) for (const locale of Object.keys(themeData.locales)) {
		if (themeData.locales[locale].bulletin === true) themeData.locales[locale].bulletin = {};
		if (themeData.locales[locale].bulletin) themeData.locales[locale].bulletin.id ||= hash(themeData.locales[locale].bulletin);
		if (!themeData.locales[locale].bulletin) continue;
		if (bulletinFiles[locale] || themeData.locales[locale].bulletin.contentFile) {
			bulletinFiles[locale] = themeData.locales[locale].bulletin?.contentFile || bulletinFiles[locale];
			deleteKey(themeData.locales[locale].bulletin, "contentFile");
			themeData.locales[locale].bulletin.content = await readBulletinFile(app, bulletinFiles[locale], locale);
		} else if (themeData.locales[locale].bulletin.content) {
			const type = themeData.locales[locale].bulletin.contentType ?? "text";
			themeData.locales[locale].bulletin.content = type === "markdown" ? app.markdown.render(themeData.locales[locale].bulletin.content, {
				filepath: app.dir.source(`${locale}_bulletin.md`),
				filePathRelative: `${locale.slice(1)}_bulletin.md`
			}) : themeData.locales[locale].bulletin.content;
		}
	}
	const files = Array.from(new Set(Object.values(bulletinFiles)));
	if (app.env.isDev && files.length) {
		if (!bulletinFileWatcher) {
			bulletinFileWatcher = watch(files, { ignoreInitial: true });
			bulletinFileWatcher.on("change", async () => {
				await resolveBulletin(app, themeData);
				await updateThemeData(app, themeData);
			});
		} else files.forEach((file) => bulletinFileWatcher?.add(file));
	}
}
async function readBulletinFile(app, filepath, locale = "/") {
	try {
		const content = await fs$1.readFile(filepath, "utf-8");
		if (filepath.endsWith(".md")) return app.markdown.render(content, {
			filepath: app.dir.source(`${locale}_bulletin.md`),
			filePathRelative: `${locale.slice(1)}_bulletin.md`
		});
		return content;
	} catch {}
	return "";
}
async function processProfileImageSize(app, themeData, plugins) {
	const imageSize = getThemeConfig().markdown?.imageSize ?? (typeof plugins.markdownPower === "boolean" ? false : plugins.markdownPower?.imageSize);
	if (!app.env.isBuild || !imageSize) return;
	const remote = imageSize === "all";
	if (themeData.profile?.avatar) {
		const size = await getImageOriginalSize(resolveImagePath(app, themeData.profile.avatar), remote);
		if (size) themeData.profile = {
			...themeData.profile,
			originalWidth: size.width,
			originalHeight: size.height
		};
	}
	if (themeData.locales) {
		for (const locale of Object.keys(themeData.locales)) if (themeData.locales[locale].profile?.avatar) {
			const size = await getImageOriginalSize(resolveImagePath(app, themeData.locales[locale].profile.avatar), remote);
			if (size) themeData.locales[locale].profile = {
				...themeData.locales[locale].profile,
				originalWidth: size.width,
				originalHeight: size.height
			};
		}
	}
}
//#endregion
//#region src/node/theme.ts
/**
* VuePress Theme Plume
*
* VuePress 主题 Plume
*
* @param options Theme options / 主题配置
* @example
* ```ts
* import { defineUserConfig } from 'vuepress'
* import { plumeTheme } from 'vuepress-theme-plume'
*
* export default defineUserConfig({
*   theme: plumeTheme({
*     // ...options
*   })
* })
* ```
*/
function plumeTheme(options = {}) {
	return (app) => {
		setTranslateLang(app.options.lang);
		perf.init(app.env.isDebug);
		detectVersions(app);
		const { configFile, plugins, themeOptions } = detectThemeOptions(options);
		configLoader.init(app, themeOptions, configFile);
		configLoader.on("change", async () => {
			genAutoFrontmatterRules();
			await prepareThemeData(app, plugins);
			await prepareData(app);
		});
		return {
			name: THEME_NAME,
			define: setupProvideData(app, plugins),
			templateBuild: templates("build.html"),
			clientConfigFile: resolve$1("client/config.js"),
			alias: setupAlias(),
			plugins: setupPlugins(app, plugins),
			extendsMarkdownOptions: async (_, app) => {
				await configLoader.waiting();
				await generateFileListFrontmatter(app);
			},
			extendsBundlerOptions,
			templateBuildRenderer,
			extendsPage: async (page) => await extendsPageData(page),
			onInitialized: async (app) => await createPages(app),
			onPrepared: async (app) => {
				await prepareThemeData(app, plugins);
				await prepareData(app);
			},
			onPageUpdated: async (app) => {
				await prepareData(app);
			},
			onWatched: async (app, watchers) => {
				configLoader.watch(watchers);
				watchAutoFrontmatter(app, watchers);
			}
		};
	};
}
//#endregion
//#region src/node/defineConfig.ts
/**
* Theme configuration helper function, used in separate `plume.config.ts`
*
* 主题配置,在单独的 `plume.config.ts` 中使用的类型帮助函数
*/
function defineThemeConfig(config) {
	return config;
}
/**
* Theme navbar configuration helper function
*
* 主题导航栏配置帮助函数
*/
function defineNavbarConfig(navbar) {
	return navbar;
}
/**
* Theme notes configuration helper function
*
* 主题 notes 配置帮助函数
* @deprecated 使用 `defineCollections` 代替
*/
function defineNotesConfig(notes) {
	return notes;
}
/**
* Theme note item configuration helper function
*
* 主题 notes item 配置帮助函数
* @deprecated 使用 `defineCollection` 代替
*/
function defineNoteConfig(note) {
	return note;
}
/**
* Theme collections configuration helper function
*
* 主题 collections 配置帮助函数
*/
function defineCollections(collections) {
	return collections;
}
/**
* Theme collection item configuration helper function
*
* 主题 collection item 配置帮助函数
*/
function defineCollection(collection) {
	return collection;
}
//#endregion
//#region src/node/index.ts
/**
* @deprecated 请使用 具名导出 替代 默认导出
*
* @deprecated Please use named exports instead of default export
*/
var node_default = plumeTheme;
//#endregion
export { node_default as default, defineCollection, defineCollections, defineNavbarConfig, defineNoteConfig, defineNotesConfig, defineThemeConfig, plumeTheme };