UNPKG

vuepress-theme-plume

Version:

A Blog&Document Theme for VuePress 2.0

2,464 lines 80.6 kB
import { computedAsync, tryOnScopeDispose, useDark, useEventListener, useLocalStorage, useSessionStorage, useThrottleFn, watchDebounced } from "@vueuse/core";
import { computed, customRef, inject, nextTick, onMounted, onUnmounted, onUpdated, provide, readonly, ref, shallowRef, toValue, watch, watchEffect } from "vue";
import { clientDataSymbol, onContentUpdated, resolveRoute, resolveRouteFullPath, usePageData, usePageFrontmatter, usePageLang, useRoute, useRouteLocale, useRouter, useSiteLocaleData } from "vuepress/client";
import { collections } from "@internal/collectionsData";
import { ensureEndingSlash, isPlainObject, isString, removeEndingSlash, removeLeadingSlash } from "vuepress/shared";
import { inBrowser, isActive, normalizeLink, normalizePrefix, resolveEditLink, resolveNavLink, toArray } from "../utils/index.js";
import { themeData as themeData$1 } from "@internal/themePlumeData";
import { useContributors as useContributors$1 } from "@vuepress/plugin-git/client";
import { encrypt as encrypt$1 } from "@internal/encrypt";
import { decodeData, ensureLeadingSlash, isArray, isLinkAbsolute, isLinkExternal, isLinkWithProtocol, isPlainObject as isPlainObject$1, isString as isString$1, removeLeadingSlash as removeLeadingSlash$1 } from "@vuepress/helper/client";
import { bcryptVerify, md5 } from "hash-wasm";
import { icons } from "@internal/iconify";
import { sidebar } from "@internal/sidebar";
import { postsData as postsData$1 } from "@internal/postsData";
import { articleTagColors } from "@internal/articleTagColors";
import { defineWatermarkConfig } from "@vuepress/plugin-watermark/client";
//#region src/client/composables/collections.ts
/**
* Global reference to all collections data.
*
* 所有集合数据的全局引用。
*/
const collectionsRef = ref(collections);
/**
* Global reference to the current collection item.
*
* 当前集合项的全局引用。
*/
const collectionItemRef = ref();
const forceCollection = ref();
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateCollections = (data) => {
	collectionsRef.value = data;
};
/**
* Use collections data.
* Returns the global collections reference.
*
* 获取集合数据。
* 返回全局集合引用。
*
* @returns Collections data reference / 集合数据引用
*/
const useCollections = () => collectionsRef;
/**
* Use current collection item.
* Returns the current collection based on the page context.
*
* 获取当前集合项。
* 根据页面上下文返回当前集合。
*
* @template T - Collection type / 集合类型
* @returns Current collection item reference / 当前集合项引用
*/
const useCollection = () => collectionItemRef;
/**
* Force update the current collection.
* Used to programmatically switch the active collection.
*
* 强制更新当前集合。
* 用于以编程方式切换活动集合。
*
* @param dir - Collection directory or true for first posts collection / 集合目录或 true 表示第一个文章集合
*/
function forceUpdateCollection(dir) {
	forceCollection.value = dir;
}
/**
* Setup collection tracking.
* Automatically determines the current collection based on route and page path.
*
* 设置集合跟踪。
* 根据路由和页面路径自动确定当前集合。
*/
function setupCollection() {
	const routeLocale = useRouteLocale();
	const { page } = useData();
	const startWith = (link) => link ? page.value.path.startsWith(normalizeLink(routeLocale.value, removeLeadingSlash(link))) : false;
	watchEffect(() => {
		const locale = collectionsRef.value[routeLocale.value];
		collectionItemRef.value = locale?.find((item) => {
			if (forceCollection.value) {
				if (forceCollection.value === true) return item.type === "post";
				return item.dir === forceCollection.value;
			}
			if (page.value.filePathRelative) return page.value.filePathRelative?.startsWith(normalizeLink(routeLocale.value, item.dir).slice(1));
			else {
				const { link, linkPrefix, dir, tagsLink, categoriesLink, archivesLink } = item;
				return startWith(link) || startWith(linkPrefix) || startWith(dir) || startWith(tagsLink) || startWith(categoriesLink) || startWith(archivesLink);
			}
		});
	});
}
//#endregion
//#region src/client/composables/theme-data.ts
/**
* Injection key for theme locale data
*
* 主题本地化数据的注入键
*/
const themeLocaleDataSymbol = Symbol(__VUEPRESS_DEV__ ? "themeLocaleData" : "");
/**
* Theme data ref
* Global reference to the theme configuration data
*
* 主题数据引用
* 主题配置数据的全局引用
*/
const themeData = ref(themeData$1);
/**
* Use theme data
* Returns the global theme data reference
*
* 获取主题数据
* 返回全局主题数据引用
*
* @returns Theme data reference / 主题数据引用
*/
function useThemeData() {
	return themeData;
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateThemeData = (data) => {
	themeData.value = data;
};
/**
* Use theme locale data
* Returns the theme data for the current route's locale
*
* 获取当前路由对应的语言环境的主题数据
*
* @returns Theme locale data computed reference / 主题本地化数据计算引用
* @throws Error if called without provider / 如果没有提供者则抛出错误
*/
function useThemeLocaleData() {
	const themeLocaleData = inject(themeLocaleDataSymbol);
	if (!themeLocaleData) throw new Error("useThemeLocaleData() is called without provider.");
	return themeLocaleData;
}
/**
* Merge the locales fields to the root fields according to the route path
* Combines base theme options with locale-specific options
*
* 根据路由路径将本地化字段合并到根字段
* 将基础主题选项与特定语言环境的选项合并
*
* @param theme - Base theme data / 基础主题数据
* @param routeLocale - Current route locale / 当前路由的语言环境
* @returns Merged theme data for the locale / 合并后的语言环境主题数据
*/
function resolveThemeLocaleData(theme, routeLocale) {
	const { locales, ...baseOptions } = theme;
	return {
		...baseOptions,
		...locales?.[routeLocale]
	};
}
/**
* Setup theme data for the Vue app
* Provides theme data and theme locale data to the application
*
* 为 Vue 应用设置主题数据
* 向应用程序提供主题数据和主题本地化数据
*
* @param app - Vue application instance / Vue 应用实例
*/
function setupThemeData(app) {
	const themeData = useThemeData();
	const clientData = app._context.provides[clientDataSymbol];
	const themeLocaleData = computed(() => resolveThemeLocaleData(themeData.value, clientData.routeLocale.value));
	app.provide(themeLocaleDataSymbol, themeLocaleData);
	Object.defineProperties(app.config.globalProperties, {
		$theme: { get() {
			return themeData.value;
		} },
		$themeLocale: { get() {
			return themeLocaleData.value;
		} }
	});
}
//#endregion
//#region src/client/composables/dark-mode.ts
/**
* Injection key for dark mode
*
* 暗黑模式的注入键
*/
const darkModeSymbol = Symbol(__VUEPRESS_DEV__ ? "darkMode" : "");
/**
* Check if view transitions are enabled and supported
* Considers prefers-reduced-motion preference
*
* 检查视图过渡是否启用且受支持
* 考虑 prefers-reduced-motion 偏好设置
*
* @returns Whether transitions are enabled / 过渡是否启用
*/
function enableTransitions() {
	if (typeof document === "undefined") return false;
	return "startViewTransition" in document && window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
}
/**
* Setup dark mode for the Vue application
* Configures dark mode based on theme settings and user preferences
*
* 为 Vue 应用设置暗黑模式
* 根据主题设置和用户偏好配置暗黑模式
*
* @param app - Vue application instance / Vue 应用实例
*/
function setupDarkMode(app) {
	const theme = useThemeData();
	const transition = theme.value.transition;
	const disableTransition = enableTransitions() || (typeof transition === "object" ? transition.appearance === false : transition === false);
	const appearance = theme.value.appearance;
	const isDark = appearance === "force-dark" ? ref(true) : appearance ? useDark({
		storageKey: "vuepress-theme-appearance",
		attribute: "data-theme",
		valueLight: "light",
		valueDark: "dark",
		disableTransition,
		initialValue: () => typeof appearance === "string" ? appearance : "auto",
		...typeof appearance === "object" ? appearance : {}
	}) : ref(false);
	app.provide(darkModeSymbol, isDark);
	if (__VUEPRESS_DEV__ && appearance === "force-dark" && typeof document !== "undefined") document.documentElement.dataset.theme = "dark";
	Object.defineProperty(app.config.globalProperties, "$isDark", { get: () => isDark });
	useEventListener("beforeprint", () => {
		if (isDark.value) document.documentElement.dataset.theme = "light";
	});
	useEventListener("afterprint", () => {
		if (isDark.value) document.documentElement.dataset.theme = "dark";
	});
}
/**
* Use dark mode
* Returns the dark mode reactive reference
*
* 获取暗黑模式状态
*
* @returns Dark mode reference / 暗黑模式引用
* @throws Error if called without provider / 如果没有提供者则抛出错误
*/
function useDarkMode() {
	const isDarkMode = inject(darkModeSymbol);
	if (!isDarkMode) throw new Error("useDarkMode() is called without provider.");
	return isDarkMode;
}
//#endregion
//#region src/client/composables/data.ts
/**
* Use data
*
* 获取页面数据,包括主题配置、页面数据、frontmatter、语言、站点信息、暗黑模式和集合信息
*/
function useData() {
	const theme = useThemeLocaleData();
	const page = usePageData();
	const frontmatter = usePageFrontmatter();
	const site = useSiteLocaleData();
	const isDark = useDarkMode();
	return {
		theme,
		page,
		frontmatter,
		lang: usePageLang(),
		site,
		isDark,
		collection: useCollection()
	};
}
//#endregion
//#region src/client/composables/bulletin.ts
const showBulletin = ref(false);
function useBulletin() {
	const { theme } = useData();
	return computed(() => theme.value.bulletin === true ? {} : theme.value.bulletin);
}
function useBulletinControl() {
	const session = useSessionStorage("plume:bulletin", "");
	const local = useLocalStorage("plume:bulletin", "");
	const { page } = useData();
	const bulletin = useBulletin();
	const enableBulletin = computed(() => page.value.bulletin ?? true);
	watch(() => bulletin.value?.lifetime, (lifetime) => {
		const id = bulletin.value?.id;
		if (lifetime === "session") showBulletin.value = session.value !== id;
		else if (lifetime === "once") showBulletin.value = local.value !== id;
		else showBulletin.value = true;
	}, { immediate: true });
	function close() {
		showBulletin.value = false;
		const lifetime = bulletin.value?.lifetime;
		const id = bulletin.value?.id;
		if (lifetime === "session") session.value = id;
		else if (lifetime === "once") local.value = id;
	}
	return {
		bulletin,
		enableBulletin,
		showBulletin,
		close
	};
}
//#endregion
//#region src/client/composables/contributors.ts
function useContributors() {
	const { frontmatter } = useData();
	const list = useContributors$1();
	const theme = useThemeData();
	const mode = computed(() => {
		const config = theme.value.contributors;
		if (isPlainObject(config)) return config.mode || "inline";
		return "inline";
	});
	const contributors = computed(() => {
		if ((frontmatter.value.contributors ?? !!theme.value.contributors) === false) return [];
		return list.value;
	});
	return {
		mode,
		contributors,
		hasContributors: computed(() => contributors.value.length > 0)
	};
}
//#endregion
//#region src/client/composables/preset-locales.ts
const presetLocales = __PLUME_PRESET_LOCALE__;
function getPresetLocaleData(locale, name) {
	return presetLocales[locale]?.[name] || presetLocales["/"][name];
}
//#endregion
//#region src/client/composables/copyright.ts
const LICENSE_URL = {
	"CC0": {
		url: "https://creativecommons.org/publicdomain/zero/1.0/",
		icons: ["zero"]
	},
	"CC-BY-4.0": {
		url: "https://creativecommons.org/licenses/by/4.0/",
		icons: ["cc", "by"]
	},
	"CC-BY-NC-4.0": {
		url: "https://creativecommons.org/licenses/by-nc/4.0/",
		icons: [
			"cc",
			"by",
			"nc"
		]
	},
	"CC-BY-NC-SA-4.0": {
		url: "https://creativecommons.org/licenses/by-nc-sa/4.0/",
		icons: [
			"cc",
			"by",
			"nc",
			"sa"
		]
	},
	"CC-BY-NC-ND-4.0": {
		url: "https://creativecommons.org/licenses/by-nc-nd/4.0/",
		icons: [
			"cc",
			"by",
			"nc",
			"nd"
		]
	},
	"CC-BY-ND-4.0": {
		url: "https://creativecommons.org/licenses/by-nd/4.0/",
		icons: [
			"cc",
			"by",
			"nd"
		]
	},
	"CC-BY-SA-4.0": {
		url: "https://creativecommons.org/licenses/by-sa/4.0/",
		icons: [
			"cc",
			"by",
			"sa"
		]
	}
};
function useCopyright(copyright) {
	const { theme } = useData();
	const routeLocale = useRouteLocale();
	const { contributors } = useContributors();
	const hasCopyright = computed(() => Boolean(copyright.value));
	const creation = computed(() => copyright.value.creation || "original");
	const license = computed(() => resolveLicense(copyright.value.license, routeLocale.value));
	const author = computed(() => resolveAuthor(copyright.value.author, creation.value, contributors.value));
	const sourceUrl = computed(() => {
		if (creation.value === "original") {
			if (__VUEPRESS_SSR__) return "";
			const url = new URL(location.href.split("#")[0]);
			url.searchParams.delete("giscus");
			return url.toString();
		}
		return copyright.value.source;
	});
	return {
		license,
		author,
		hasCopyright,
		creation,
		creationText: computed(() => {
			const creation = copyright.value.creation;
			if (creation === "translate") return theme.value.copyrightCreationTranslateText || "This article is translated from";
			else if (creation === "reprint") return theme.value.copyrightCreationReprintText || "This article is reprint from";
			return theme.value.copyrightCreationOriginalText || "This article link: ";
		}),
		sourceUrl
	};
}
function resolveLicense(license = "CC-BY-4.0", locale) {
	const result = typeof license === "string" ? { name: license } : { ...license };
	const fallback = LICENSE_URL[result.name];
	const name = getPresetLocaleData(locale, result.name);
	if (name) result.name = `${name} (${result.name})`;
	result.url ||= fallback?.url;
	result.icons = fallback?.icons;
	return result;
}
function resolveAuthor(author, creation, contributors) {
	const contributor = contributors[0];
	if (!author && contributor && creation === "original") return contributor;
	const options = typeof author === "string" ? { name: author } : author;
	if (options && !options.url) {
		const contributor = contributors.find((c) => c.name === options.name);
		if (contributor) options.url = contributor.url;
	}
	return options;
}
//#endregion
//#region src/client/composables/css-var.ts
/**
* Get css variable
* @param prop css variable name
* @param initialValue
*/
function useCssVar(prop, initialValue = "") {
	const isDark = useDarkMode();
	const variable = shallowRef(initialValue);
	function updateCssVar() {
		const _window = typeof window !== "undefined" ? window : null;
		const target = _window?.document?.documentElement;
		const key = toValue(prop);
		if (target && key) {
			const value = _window.getComputedStyle(target).getPropertyValue(key)?.trim();
			variable.value = value || variable.value || initialValue;
		}
	}
	watch([isDark, () => toValue(prop)], () => {
		updateCssVar();
	}, {
		immediate: true,
		flush: "post"
	});
	return computed(() => variable.value);
}
//#endregion
//#region src/client/composables/edit-link.ts
function useEditLink() {
	const { theme, page, frontmatter } = useData();
	const themeData = useThemeData();
	return computed(() => {
		if (!(frontmatter.value.editLink ?? themeData.value.editLink ?? true)) return null;
		const { docsRepo, docsBranch = "main", docsDir = "" } = themeData.value;
		const { editLinkText } = theme.value;
		if (!docsRepo) return null;
		const editLink = resolveEditLink({
			docsRepo,
			docsBranch,
			docsDir,
			filePathRelative: page.value.filePathRelative,
			editLinkPattern: frontmatter.value.editLinkPattern ?? theme.value.editLinkPattern
		});
		if (!editLink) return null;
		return {
			text: editLinkText ?? "Edit this page",
			link: editLink
		};
	});
}
//#endregion
//#region src/client/composables/encrypt-data.ts
/**
* Global encrypt data reference
*
* 全局加密数据引用
*/
const encrypt = ref(resolveEncryptData(encrypt$1));
/**
* Use encrypt data
* Returns the global encrypt data reference
*
* 获取加密数据
* 返回全局加密数据引用
*
* @returns Encrypt data reference / 加密数据引用
*/
function useEncryptData() {
	return encrypt;
}
/**
* Resolve encrypt data from raw configuration
* Decodes and parses the raw encrypt configuration
*
* 从原始配置解析加密数据
* 解码并解析原始加密配置
* @param data - Raw encrypt configuration / 原始加密配置
* @param data."0" rawKeys - Encoded keys string / 编码的密钥字符串
* @param data."1" rawRules - Encoded rules string / 编码的规则字符串
* @param data."2" global - Global encryption flag / 全局加密标志
* @param data."3" admin - Admin passwords string / 管理员密码字符串
* @returns Parsed encrypt data / 解析后的加密数据
*/
function resolveEncryptData([rawKeys, rawRules, global, admin]) {
	const keys = unwrapData(rawKeys).map((key) => decodeData(key));
	const rules = unwrapData(rawRules);
	const separator = ":";
	return {
		global: !!global,
		matches: keys,
		admins: admin.split(separator),
		ruleList: Object.keys(rules).map((key) => ({
			key,
			match: keys[key],
			rules: rules[key].split(separator)
		}))
	};
}
/**
* Unwrap and decode data from raw string
*
* 从原始字符串解包和解码数据
*
* @param raw - Raw encoded string / 原始编码字符串
* @returns Parsed data / 解析后的数据
*/
function unwrapData(raw) {
	return JSON.parse(decodeData(raw));
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateEncrypt = (data) => {
	encrypt.value = resolveEncryptData(data);
};
//#endregion
//#region src/client/composables/encrypt.ts
/**
* Injection key for encrypt functionality
*
* 加密功能的注入键
*/
const EncryptSymbol = Symbol(__VUEPRESS_DEV__ ? "Encrypt" : "");
/**
* Session storage for encryption state
* Stores global and page decryption states
*
* 加密状态的会话存储
* 存储全局和页面解密状态
*/
const storage = useSessionStorage("2a0a3d6afb2fdf1f", () => {
	if (__VUEPRESS_SSR__) return {
		g: "",
		p: []
	};
	return {
		g: "",
		p: []
	};
});
/**
* Cache for password comparison results
* Improves performance by caching bcrypt verification results
*
* 密码比较结果的缓存
* 通过缓存 bcrypt 验证结果提高性能
*/
const compareCache = /* @__PURE__ */ new Map();
const separator = ":";
/**
* Compare password with hash using bcrypt
* Caches results to avoid repeated computations
*
* 使用 bcrypt 比较密码和哈希
* 缓存结果以避免重复计算
*
* @param content - Password to verify / 要验证的密码
* @param hash - Bcrypt hash to compare against / 要比较的 bcrypt 哈希
* @returns Whether the password matches / 密码是否匹配
*/
async function compareDecrypt(content, hash) {
	const key = [content, hash].join(separator);
	if (compareCache.has(key)) return compareCache.get(key);
	try {
		const result = await bcryptVerify({
			password: content,
			hash
		});
		compareCache.set(key, result);
		return result;
	} catch {
		compareCache.set(key, false);
		return false;
	}
}
/**
* Cache for regex patterns
* Improves performance by caching compiled regexes
*
* 正则表达式模式的缓存
* 通过缓存编译后的正则表达式提高性能
*/
const matchCache = /* @__PURE__ */ new Map();
/**
* Create or retrieve cached regex pattern
*
* 创建或获取缓存的正则表达式模式
*
* @param match - Pattern string / 模式字符串
* @returns Compiled regex / 编译后的正则表达式
*/
function createMatchRegex(match) {
	if (matchCache.has(match)) return matchCache.get(match);
	const regex = new RegExp(match);
	matchCache.set(match, regex);
	return regex;
}
/**
* Check if a match pattern applies to the current page
* Supports regex patterns (starting with ^) and path patterns
*
* 检查匹配模式是否适用于当前页面
* 支持正则表达式模式(以 ^ 开头)和路径模式
*
* @param match - Match pattern / 匹配模式
* @param pagePath - Current page path / 当前页面路径
* @param filePathRelative - Relative file path / 相对文件路径
* @returns Whether the pattern matches / 模式是否匹配
*/
function toMatch(match, pagePath, filePathRelative) {
	const relativePath = filePathRelative || "";
	if (match[0] === "^") {
		const regex = createMatchRegex(match);
		return regex.test(pagePath) || regex.test(relativePath);
	}
	if (match.endsWith(".md")) return relativePath && relativePath.endsWith(match);
	return pagePath.startsWith(match) || relativePath.startsWith(removeLeadingSlash(match));
}
/**
* Setup encrypt functionality for the application
* Initializes encryption state and provides it to child components
*
* 为应用程序设置加密功能
* 初始化加密状态并提供给子组件
*/
function setupEncrypt() {
	const { page } = useData();
	const route = useRoute();
	const encrypt = useEncryptData();
	/**
	* Whether the current page has encryption enabled
	* Checks page-specific encryption and rule-based encryption
	*/
	const hasPageEncrypt = computed(() => {
		const pagePath = route.path;
		const filePathRelative = page.value.filePathRelative;
		if (page.value._e) return true;
		return encrypt.value.ruleList.length ? encrypt.value.matches.some((match) => toMatch(match, pagePath, filePathRelative)) : false;
	});
	/**
	* Whether global encryption is decrypted
	* Checks if any admin password hash matches the stored hash
	*/
	const isGlobalDecrypted = computedAsync(async () => {
		const hash = storage.value.g;
		if (!encrypt.value.global) return true;
		for (const admin of encrypt.value.admins) if (hash && hash === await md5(admin)) return true;
		return false;
	}, !encrypt.value.global);
	/**
	* List of encryption rules applicable to the current page
	* Includes page-specific rules and matching pattern rules
	*/
	const hashList = computed(() => {
		const pagePath = route.path;
		const filePathRelative = page.value.filePathRelative;
		const passwords = typeof page.value._e === "string" ? page.value._e.split(":") : [];
		return [passwords.length ? {
			key: pagePath.replace(/\//g, "").replace(/\.html$/, ""),
			match: pagePath,
			rules: passwords
		} : void 0, ...encrypt.value.ruleList.length ? encrypt.value.ruleList.filter((item) => toMatch(item.match, pagePath, filePathRelative)) : []].filter(Boolean);
	});
	/**
	* Whether the current page is decrypted
	* Checks admin passwords and page-specific passwords
	*/
	const isPageDecrypted = computedAsync(async () => {
		if (!hasPageEncrypt.value) return true;
		const hash = storage.value.g;
		for (const admin of encrypt.value.admins) if (hash && hash === await md5(admin)) return true;
		for (const { key, rules } of hashList.value) {
			const hash = storage.value.p[key];
			for (const rule of rules) if (hash && hash === await md5(rule)) return true;
		}
		return false;
	}, !hasPageEncrypt.value);
	provide(EncryptSymbol, {
		hasPageEncrypt,
		isGlobalDecrypted,
		isPageDecrypted,
		hashList
	});
}
/**
* Use encrypt
* Returns the encryption state and properties
*
* 获取加密功能的状态和方法,包括全局解密、页面解密、密码验证等
*
* @returns Encrypt state and properties / 加密状态和属性
* @throws Error if called without setup / 如果没有设置则抛出错误
*/
function useEncrypt() {
	const result = inject(EncryptSymbol);
	if (!result) throw new Error("useEncrypt() is called without setup");
	return result;
}
/**
* Use encrypt compare
* Provides password verification functions for global and page encryption
*
* 密码比较功能,提供全局密码和页面密码的验证方法
*
* @returns Object with compareGlobal and comparePage functions / 包含 compareGlobal 和 comparePage 函数的对象
*/
function useEncryptCompare() {
	const encrypt = useEncryptData();
	const { page } = useData();
	const route = useRoute();
	const { hashList } = useEncrypt();
	/**
	* Compare global password
	* Verifies against admin passwords
	*
	* @param password - Password to verify / 要验证的密码
	* @returns Whether the password is valid / 密码是否有效
	*/
	async function compareGlobal(password) {
		if (!password) return false;
		for (const admin of encrypt.value.admins) if (await compareDecrypt(password, admin)) {
			storage.value.g = await md5(admin);
			return true;
		}
		return false;
	}
	/**
	* Compare page password
	* Verifies against page-specific rules and falls back to global password
	*
	* @param password - Password to verify / 要验证的密码
	* @returns Whether the password is valid / 密码是否有效
	*/
	async function comparePage(password) {
		if (!password) return false;
		const pagePath = route.path;
		const filePathRelative = page.value.filePathRelative;
		let decrypted = false;
		for (const { match, key, rules } of hashList.value) if (toMatch(match, pagePath, filePathRelative)) {
			for (const rule of rules) if (await compareDecrypt(password, rule)) {
				decrypted = true;
				storage.value.p[key] = await md5(rule);
				break;
			}
			if (decrypted) break;
		}
		if (!decrypted) decrypted = await compareGlobal(password);
		return decrypted;
	}
	return {
		compareGlobal,
		comparePage
	};
}
//#endregion
//#region src/client/composables/flyout.ts
/**
* Currently focused element reference.
* Shared across all flyout instances.
*
* 当前聚焦元素的引用。
* 在所有弹出框实例之间共享。
*/
const focusedElement = ref();
let active = false;
let listeners = 0;
/**
* Use flyout focus tracking.
* Tracks focus state for dropdown menus and flyout components.
*
* 弹出框焦点跟踪。
* 跟踪下拉菜单和弹出框组件的焦点状态。
*
* @param options - Flyout options / 弹出框选项
* @returns Readonly reference to focus state / 焦点状态的只读引用
*/
function useFlyout(options) {
	const focus = ref(false);
	if (inBrowser) {
		if (!active) activateFocusTracking();
		listeners++;
		const unwatch = watch(focusedElement, (el) => {
			if (el === options.el.value || options.el.value?.contains(el)) {
				focus.value = true;
				options.onFocus?.();
			} else {
				focus.value = false;
				options.onBlur?.();
			}
		});
		onUnmounted(() => {
			unwatch();
			listeners--;
			if (!listeners) deactivateFocusTracking();
		});
	}
	return readonly(focus);
}
/**
* Activate global focus tracking.
* Adds focusin event listener to document.
*
* 激活全局焦点跟踪。
* 向文档添加 focusin 事件监听器。
*/
function activateFocusTracking() {
	document.addEventListener("focusin", handleFocusIn);
	active = true;
	focusedElement.value = document.activeElement;
}
/**
* Deactivate global focus tracking.
* Removes focusin event listener from document.
*
* 停用全局焦点跟踪。
* 从文档移除 focusin 事件监听器。
*/
function deactivateFocusTracking() {
	document.removeEventListener("focusin", handleFocusIn);
}
/**
* Handle focusin event.
* Updates the focused element reference.
*
* 处理 focusin 事件。
* 更新聚焦元素引用。
*/
function handleFocusIn() {
	focusedElement.value = document.activeElement;
}
//#endregion
//#region src/client/composables/icons.ts
const iconsData = ref(resolveIconsData(icons));
/**
* Use icons data.
* Returns the processed icons data reference.
*
* 获取图标数据。
* 返回处理后的图标数据引用。
*
* @returns Icons data reference / 图标数据引用
*/
const useIconsData = () => iconsData;
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateIcons = (data) => {
	iconsData.value = resolveIconsData(data);
};
/**
* Fallback mappings for old icon aliases.
* Maps legacy icon names to their current equivalents.
*
* 旧版本内置图标别名的后备映射。
* 将旧图标名称映射到当前等效名称。
*/
const socialFallbacks = {
	twitter: "x",
	weibo: "sinaweibo"
};
/**
* Resolve raw icons data to processed format.
* Converts indexed data to flat icon lists.
*
* 将原始图标数据解析为处理后的格式。
* 将索引数据转换为平面图标列表。
*
* @param data - Raw icons data / 原始图标数据
* @param data.co - Collection names / 集合名称
* @param data.bg - Background icons by collection index / 按集合索引的背景图标
* @param data.mask - Mask icons by collection index / 按集合索引的遮罩图标
* @returns Processed icons data / 处理后的图标数据
*/
function resolveIconsData({ co, bg, mask }) {
	return {
		bg: processIcons(co, bg),
		mask: processIcons(co, mask)
	};
}
/**
* Normalize icon to CSS class name.
* Returns the appropriate class name based on icon type.
*
* 将图标规范化为 CSS 类名。
* 根据图标类型返回适当的类名。
*
* @param icon - Icon name in format "collection:name" / 格式为 "collection:name" 的图标名称
* @returns CSS class name or empty string if not found / CSS 类名,如果未找到则返回空字符串
*/
function normalizeIconClassname(icon) {
	const [collect, name] = icon.split(":");
	const iconName = `vpi-${collect}-${name}`;
	if (iconsData.value.bg.includes(icon)) return `${iconName} bg`;
	if (iconsData.value.mask.includes(icon)) return iconName;
	return "";
}
/**
* Process indexed icons into flat list.
* Converts collection-indexed data to "collection:icon" format.
*
* 将索引图标处理为平面列表。
* 将集合索引数据转换为 "collection:icon" 格式。
*
* @param collects - Array of collection names / 集合名称数组
* @param raw - Indexed icon data / 索引图标数据
* @returns Flat array of icon identifiers / 图标标识符的平面数组
*/
function processIcons(collects, raw) {
	const data = [];
	for (const [key, list] of Object.entries(raw)) {
		const collect = collects[Number(key)];
		if (collect) data.push(...list.map((icon) => `${collect}:${icon}`));
	}
	return data;
}
//#endregion
//#region src/client/utils/resolveNavLink.ts
/**
* Normalize a link by combining base and link
* Handles absolute links and protocol links correctly
*
* 通过组合 base 和 link 来规范化链接
* 正确处理绝对链接和协议链接
*
* @param base - Base URL / 基础 URL
* @param link - Link to normalize / 要规范化的链接
* @returns Normalized link / 规范化后的链接
*/
function normalizeLink$1(base = "", link = "") {
	return isLinkAbsolute(link) || isLinkWithProtocol(link) ? link : ensureLeadingSlash(`${base}/${link}`.replace(/\/+/g, "/"));
}
//#endregion
//#region src/client/composables/internal-link.ts
function useInternalLink() {
	const { collection, theme } = useData();
	const themeData = useThemeData();
	const routeLocale = useRouteLocale();
	function resolveLink(link, fallback) {
		link = link ? removeLeadingSlash(link) : "";
		return ensureEndingSlash(normalizeLink$1(routeLocale.value, link || fallback));
	}
	const postCollection = computed(() => collection.value?.type === "post" ? collection.value : void 0);
	const home = computed(() => ({
		link: normalizeLink$1(routeLocale.value),
		text: theme.value.homeText || themeData.value.homeText || "Home"
	}));
	const postsLink = computed(() => normalizeLink$1(routeLocale.value, resolveLink(postCollection.value?.link || postCollection.value?.dir, "posts/")));
	return {
		home,
		posts: computed(() => postCollection.value?.postList !== false ? {
			text: postCollection.value?.title || removeEndingSlash(postCollection.value?.dir || "").split("/").pop() || theme.value.postsText,
			link: postsLink.value
		} : void 0),
		tags: computed(() => postCollection.value?.tags !== false ? {
			text: postCollection.value?.tagsText || theme.value.tagText || themeData.value.tagText || "Tags",
			link: resolveLink(postCollection.value?.tagsLink, "tags/")
		} : void 0),
		archive: computed(() => postCollection.value?.archives !== false ? {
			text: postCollection.value?.archivesText || theme.value.archiveText || themeData.value.archiveText || "Archives",
			link: resolveLink(postCollection.value?.archivesLink, "archives/")
		} : void 0),
		categories: computed(() => postCollection.value?.categories !== false ? {
			text: postCollection.value?.categoriesText || theme.value.categoryText || themeData.value.categoryText || "Categories",
			link: resolveLink(postCollection.value?.categoriesLink, "categories/")
		} : void 0)
	};
}
//#endregion
//#region src/client/composables/page.ts
/**
* Use posts page data
*
* 获取文章页面数据,判断当前页面是否为文章类型或文章列表布局
*/
function usePostsPageData() {
	const { collection, page } = useData();
	return {
		isPosts: computed(() => collection.value?.type === "post"),
		isPostsLayout: computed(() => {
			const type = page.value.type;
			return type === "posts" || type === "posts-archives" || type === "posts-tags" || type === "posts-categories";
		})
	};
}
//#endregion
//#region src/client/composables/langs.ts
function useLangs({ removeCurrent = true } = {}) {
	const theme = useThemeData();
	const { page, collection } = useData();
	const routeLocale = useRouteLocale();
	const { isPosts } = usePostsPageData();
	const currentLang = computed(() => {
		const link = routeLocale.value;
		return {
			text: theme.value.locales?.[link]?.selectLanguageName || link,
			link
		};
	});
	const resolvePath = (locale, url) => {
		const targetPath = normalizeLink(locale, url.slice(routeLocale.value.length));
		const { notFound, path } = resolveRoute(targetPath);
		return notFound ? void 0 : path;
	};
	const getPageLink = (locale) => {
		let path;
		if (page.value.filePathRelative) path = resolvePath(locale, `/${page.value.filePathRelative}`);
		path ??= resolvePath(locale, page.value.path);
		if (path) return path;
		if (isPosts.value && collection.value) {
			const col = collection.value;
			return normalizeLink(locale, removeLeadingSlash(col.link || col.dir));
		}
		const home = theme.value.home || "/";
		const fallbackResolve = resolveRoute(locale);
		return fallbackResolve.notFound ? home : fallbackResolve.path;
	};
	return {
		localeLinks: computed(() => Object.entries(theme.value.locales || {}).flatMap(([key, locale]) => removeCurrent && currentLang.value.text === locale.selectLanguageName ? [] : {
			text: locale.selectLanguageName || key,
			link: getPageLink(key)
		})),
		currentLang
	};
}
//#endregion
//#region src/client/composables/latest-updated.ts
function useLastUpdated() {
	const { theme, page, frontmatter } = useData();
	const themeData = useThemeData();
	const lang = usePageLang();
	const date = computed(() => page.value.git?.updatedTime ? new Date(page.value.git.updatedTime) : null);
	const isoDatetime = computed(() => date.value?.toISOString());
	const datetime = ref("");
	const lastUpdatedText = computed(() => {
		if (themeData.value.lastUpdated === false) return "";
		return theme.value.lastUpdatedText || "Last updated";
	});
	onMounted(() => {
		watchEffect(() => {
			if (frontmatter.value.lastUpdated === false || themeData.value.lastUpdated === false) return;
			datetime.value = date.value ? new Intl.DateTimeFormat(themeData.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : void 0, themeData.value.lastUpdated?.formatOptions ?? {
				dateStyle: "short",
				timeStyle: "short"
			}).format(date.value) : "";
		});
	});
	return {
		datetime,
		isoDatetime,
		lastUpdatedText
	};
}
//#endregion
//#region src/client/composables/outline.ts
const resolvedHeaders = [];
const headers = ref([]);
/**
* Setup headers for the current page
* Initializes header extraction based on frontmatter and theme configuration
*
* 为当前页面设置标题
* 根据 frontmatter 和主题配置初始化标题提取
*
* @returns Reference to the headers array / 标题数组的引用
*/
function setupHeaders() {
	const { frontmatter, theme } = useData();
	onContentUpdated(() => {
		headers.value = getHeaders(frontmatter.value.outline ?? theme.value.outline);
	});
	return headers;
}
/**
* Use headers
* Returns the reactive headers reference for the current page
*
* 获取当前页面的标题列表
*
* @returns Reactive reference to menu items / 菜单项的响应式引用
*/
function useHeaders() {
	return headers;
}
const IGNORE_WRAPPERS = [".vp-bulletin", ".vp-demo-wrapper"];
/**
* Get headers from the page content
* Extracts and filters headings based on the outline configuration
*
* 从页面内容获取标题
* 根据目录配置提取和过滤标题
*
* @param range - Outline configuration for header levels / 标题级别的目录配置
* @returns Array of menu items representing headers / 表示标题的菜单项数组
*/
function getHeaders(range) {
	const heading = [
		"h1",
		"h2",
		"h3",
		"h4",
		"h5",
		"h6"
	];
	const ignores = Array.from(document.querySelectorAll(heading.map((h) => IGNORE_WRAPPERS.map((w) => `${w} ${h}`)).flat().join(",")));
	const headers = Array.from(document.querySelectorAll(heading.map((h) => `.vp-doc ${h}`).join(","))).filter((el) => !ignores.includes(el) && el.id && el.hasChildNodes()).map((el) => {
		const level = Number(el.tagName[1]);
		return {
			element: el,
			title: serializeHeader(el),
			link: `#${el.id}`,
			level,
			lowLevel: getLowLevel(el, level)
		};
	});
	if (range === false) return [];
	const [high, low] = getRange(range);
	return resolveSubRangeHeader(resolveHeaders(headers, high), low);
}
/**
* Get the header level range from outline configuration
*
* 从目录配置获取标题级别范围
*
* @param range - Outline configuration / 目录配置
* @returns Tuple of [high, low] levels / [高, 低] 级别元组
*/
function getRange(range) {
	const levelsRange = range || 2;
	return typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
}
/**
* Get the lowest outline level for a specific header
* Checks for data-outline or outline attributes
*
* 获取特定标题的最低目录级别
* 检查 data-outline 或 outline 属性
*
* @param el - Header element / 标题元素
* @param level - Current header level / 当前标题级别
* @returns Lowest level or undefined / 最低级别或未定义
*/
function getLowLevel(el, level) {
	if (!el.hasAttribute("data-outline") && !el.hasAttribute("outline")) return;
	const str = (el.getAttribute("data-outline") || el.getAttribute("outline"))?.trim();
	if (!str) return;
	const num = Number(str);
	if (!Number.isNaN(num) && num >= level) return num;
}
/**
* Serialize a header element to text
* Extracts text content while ignoring badges and ignored elements
*
* 将标题元素序列化为文本
* 提取文本内容,同时忽略徽章和被忽略的元素
*
* @param h - Header element / 标题元素
* @returns Serialized header text / 序列化的标题文本
*/
function serializeHeader(h) {
	const anchor = h.firstChild;
	const el = anchor?.firstChild;
	let ret = "";
	for (const node of Array.from(el?.childNodes ?? [])) if (node.nodeType === 1) {
		if (node.classList.contains("vp-badge") || node.classList.contains("ignore-header")) continue;
		const clone = node.cloneNode(true);
		clearHeaderNodeList(Array.from(clone.childNodes));
		ret += clone.textContent;
	} else if (node.nodeType === 3) ret += node.textContent;
	let next = anchor?.nextSibling;
	while (next) {
		if (next.nodeType === 1 || next.nodeType === 3) ret += next.textContent;
		next = next.nextSibling;
	}
	return ret.trim();
}
/**
* Clear ignored nodes from a list of child nodes
* Recursively removes elements with 'ignore-header' class
*
* 从子节点列表中清除被忽略的节点
* 递归移除带有 'ignore-header' 类的元素
*
* @param list - Array of child nodes / 子节点数组
*/
function clearHeaderNodeList(list) {
	if (list?.length) {
		for (const node of list) if (node.nodeType === 1) {
			if (node.classList.contains("ignore-header")) node.remove();
			else clearHeaderNodeList(Array.from(node.childNodes));
		}
	}
}
/**
* Resolve headers into a hierarchical structure
* Organizes flat headers into nested menu items based on levels
*
* 将标题解析为层次结构
* 根据级别将平面标题组织为嵌套菜单项
*
* @param headers - Flat array of menu items / 平面菜单项数组
* @param high - Minimum header level to include / 要包含的最小标题级别
* @returns Hierarchical array of menu items / 层次化的菜单项数组
*/
function resolveHeaders(headers, high) {
	headers = headers.filter((h) => h.level >= high);
	resolvedHeaders.length = 0;
	for (const { element, link } of headers) resolvedHeaders.push({
		element,
		link
	});
	const ret = [];
	outer: for (let i = 0; i < headers.length; i++) {
		const cur = headers[i];
		if (i === 0) {
			ret.push(cur);
			continue;
		} else {
			for (let j = i - 1; j >= 0; j--) {
				const prev = headers[j];
				if (prev.level < cur.level) {
					(prev.children || (prev.children = [])).push(cur);
					continue outer;
				}
			}
			ret.push(cur);
		}
	}
	return ret;
}
/**
* Resolve sub range header
* Filters children headers based on the lowest level
*
* 解析子范围标题
* 根据最低级别过滤子标题
*
* @param headers - Array of menu items / 菜单项数组
* @param low - Lowest level to include / 要包含的最低级别
* @returns Filtered menu items / 过滤后的菜单项
*/
function resolveSubRangeHeader(headers, low) {
	return headers.map((header) => {
		if (header.children?.length) {
			const current = header.lowLevel ? Math.max(header.lowLevel, low) : low;
			header.children = resolveSubRangeHeader(header.children.filter(({ level }) => level <= current), header.lowLevel || low);
		}
		return header;
	});
}
/**
* Use active anchor
* Tracks scroll position and updates the active outline item
*
* 活跃锚点处理,监听滚动并更新目录中高亮的锚点
*
* @param container - Reference to the outline container / 目录容器的引用
* @param marker - Reference to the active marker element / 活动标记元素的引用
*/
function useActiveAnchor(container, marker) {
	const { isAsideEnabled } = useLayout();
	const router = useRouter();
	const routeHash = ref(router.currentRoute.value.hash);
	let prevActiveLink = null;
	/**
	* Set the active link based on scroll position
	* Determines which header is currently in view
	*/
	const setActiveLink = () => {
		if (!isAsideEnabled.value) return;
		const scrollY = Math.round(window.scrollY);
		const innerHeight = window.innerHeight;
		const offsetHeight = document.body.offsetHeight;
		const isBottom = Math.abs(scrollY + innerHeight - offsetHeight) < 1;
		const headers = resolvedHeaders.map(({ element, link }) => ({
			link,
			top: getAbsoluteTop(element)
		})).filter(({ top }) => !Number.isNaN(top)).sort((a, b) => a.top - b.top);
		if (!headers.length) {
			activateLink(null);
			return;
		}
		if (scrollY < 1) {
			activateLink(null);
			return;
		}
		if (isBottom) {
			activateLink(headers[headers.length - 1].link);
			return;
		}
		let activeLink = null;
		for (const { link, top } of headers) {
			if (top > scrollY + 80) break;
			activeLink = link;
		}
		activateLink(activeLink);
	};
	/**
	* Activate a specific link in the outline
	* Updates visual indicators and marker position
	*
	* @param hash - Hash of the link to activate / 要激活的链接哈希
	*/
	function activateLink(hash) {
		routeHash.value = hash || "";
		if (prevActiveLink) prevActiveLink.classList.remove("active");
		if (hash == null) prevActiveLink = null;
		else prevActiveLink = container.value?.querySelector(`a[href="${decodeURIComponent(hash)}"]`) ?? null;
		const activeLink = prevActiveLink;
		if (activeLink) {
			activeLink.classList.add("active");
			if (marker.value) {
				marker.value.style.top = `${activeLink.offsetTop + 39}px`;
				marker.value.style.opacity = "1";
			}
		} else if (marker.value) {
			marker.value.style.top = "33px";
			marker.value.style.opacity = "0";
		}
	}
	const onScroll = useThrottleFn(setActiveLink, 100);
	watchDebounced(routeHash, () => {
		updateHash(router, routeHash.value);
	}, { debounce: 500 });
	onMounted(() => {
		setTimeout(() => {
			setActiveLink();
			window.addEventListener("scroll", onScroll);
		}, 1e3);
	});
	onUpdated(() => {
		activateLink(location.hash);
	});
	onUnmounted(() => {
		window.removeEventListener("scroll", onScroll);
	});
}
/**
* Get the absolute top position of an element
* Accounts for fixed positioned ancestors
*
* 获取元素的绝对顶部位置
* 考虑固定定位的祖先元素
*
* @param element - Element to measure / 要测量的元素
* @returns Absolute top position or NaN / 绝对顶部位置或 NaN
*/
function getAbsoluteTop(element) {
	let offsetTop = 0;
	while (element && element !== document.body) {
		if (window.getComputedStyle(element).position === "fixed") return element.offsetTop;
		offsetTop += element.offsetTop;
		element = element.offsetParent;
	}
	return element ? offsetTop : NaN;
}
/**
* Update current hash without triggering scrollBehavior
* Temporarily disables scroll behavior during hash update
*
* 更新当前哈希而不触发 scrollBehavior
* 在哈希更新期间临时禁用滚动行为
*
* @param router - Vue Router instance / Vue Router 实例
* @param hash - New hash value / 新的哈希值
*/
async function updateHash(router, hash) {
	const { path, query } = router.currentRoute.value;
	const { scrollBehavior } = router.options;
	router.options.scrollBehavior = void 0;
	await router.replace({
		path,
		query,
		hash
	});
	router.options.scrollBehavior = scrollBehavior;
}
//#endregion
//#region src/client/composables/sidebar-data.ts
const { __auto__, __home__, ...items } = sidebar;
/**
* Global sidebar data reference.
*
* 全局侧边栏数据引用。
*/
const sidebarData = ref(items);
/**
* Auto-generated directory sidebar reference.
*
* 自动生成目录侧边栏引用。
*/
const autoDirSidebar = ref(__auto__);
const autoHomeData = ref(__home__);
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateSidebar = (data) => {
	const { __auto__, __home__, ...items } = data;
	sidebarData.value = items;
	autoDirSidebar.value = __auto__;
	autoHomeData.value = __home__;
};
const sidebar$1 = ref([]);
/**
* Setup sidebar tracking.
* Automatically updates sidebar based on route and frontmatter changes.
*
* 设置侧边栏跟踪。
* 根据路由和 frontmatter 变化自动更新侧边栏。
*/
function setupSidebar() {
	const { page, frontmatter } = useData();
	const routeLocale = useRouteLocale();
	const hasSidebar = computed(() => {
		return frontmatter.value.pageLayout !== "home" && frontmatter.value.pageLayout !== "friends" && frontmatter.value.sidebar !== false && frontmatter.value.layout !== "NotFound";
	});
	watch([
		hasSidebar,
		routeLocale,
		() => frontmatter.value.sidebar,
		() => page.value.path
	], () => {
		sidebar$1.value = hasSidebar.value ? getSidebar(typeof frontmatter.value.sidebar === "string" ? frontmatter.value.sidebar : page.value.path, routeLocale.value) : [];
	}, { immediate: true });
}
/**
* Use sidebar data.
* Returns the resolved sidebar items for the current page.
*
* 获取侧边栏数据。
* 返回当前页面的解析侧边栏项目。
*
* @returns Resolved sidebar items reference / 解析侧边栏项目引用
*/
function useSidebarData() {
	return sidebar$1;
}
/**
* Get the sidebar configuration from sidebar options.
* Ensures correct sidebar config from MultiSideBarConfig with various path combinations.
* Returns empty array if no matching config is found.
*
* 从侧边栏选项获取侧边栏配置。
* 确保从 MultiSideBarConfig 获取正确的侧边栏配置,支持各种路径组合。
* 如果未找到匹配的配置,则返回空数组。
*
* @param routePath - Current route path / 当前路由路径
* @param routeLocal - Current route locale / 当前路由语言环境
* @returns Resolved sidebar items / 解析的侧边栏项目
*/
function getSidebar(routePath, routeLocal) {
	const _sidebar = sidebarData.value[routeLocal];
	if (_sidebar === "auto") return resolveSidebarItems(autoDirSidebar.value[routeLocal]);
	else if (isArray(_sidebar)) return resolveSidebarItems(_sidebar, routeLocal);
	else if (isPlainObject$1(_sidebar)) {
		routePath = decodeURIComponent(routePath);
		const dir = Object.keys(_sidebar).sort((a, b) => b.split("/").length - a.split("/").length).find((dir) => {
			return routePath.startsWith(`${routeLocal}${removeLeadingSlash$1(dir)}`);
		}) || "";
		const sidebar = dir ? _sidebar[dir] : void 0;
		if (sidebar === "auto") return resolveSidebarItems(dir ? autoDirSidebar.value[dir] : [], routeLocal);
		else if (isArray(sidebar)) return resolveSidebarItems(sidebar, dir);
		else if (isPlainObject$1(sidebar)) {
			const prefix = normalizePrefix(routeLocal, sidebar.prefix);
			return resolveSidebarItems(sidebar.items === "auto" ? autoDirSidebar.value[prefix] : sidebar.items, prefix);
		}
	}
	return [];
}
/**
* Resolve sidebar items from raw configuration.
* Converts string items and nested structures to resolved format.
*
* 从原始配置解析侧边栏项目。
* 将字符串项目和嵌套结构转换为解析格式。
*
* @param sidebarItems - Raw sidebar items / 原始侧边栏项目
* @param _prefix - URL prefix for nested items / 嵌套项目的 URL 前缀
* @returns Resolved sidebar items / 解析的侧边栏项目
*/
function resolveSidebarItems(sidebarItems, _prefix = "") {
	const resolved = [];
	sidebarItems.forEach((item) => {
		if (isString$1(item)) resolved.push(resolveNavLink(normalizeLink(_prefix, item)));
		else {
			const { link, items, prefix, dir, ...args } = item;
			const navLink = { ...args };
			if (link) {
				navLink.link = link.startsWith("---") ? link : normalizeLink(_prefix, link);
				const nav = resolveNavLink(navLink.link);
				navLink.icon = nav.icon || navLink.icon;
				navLink.badge = nav.badge || navLink.badge;
			}
			const nextPrefix = normalizePrefix(_prefix, prefix || dir);
			if (items === "auto") {
				navLink.items = resolveSidebarItems(autoDirSidebar.value[nextPrefix], nextPrefix);
				if (!navLink.link && autoHomeData.value[nextPrefix]) {
					navLink.link = normalizeLink(autoHomeData.value[nextPrefix]);
					const nav = resolveNavLink(navLink.link);
					navLink.icon = nav.icon || navLink.icon;
					navLink.badge = nav.badge || navLink.badge;
				}
			} else navLink.items = items?.length ? resolveSidebarItems(items, nextPrefix) : void 0;
			resolved.push(navLink);
		}
	});
	return resolved;
}
/**
* Get or generate sidebar groups from the given sidebar items.
* Groups consecutive items without children into a single group.
*
* 从给定的侧边栏项目获取或生成侧边栏分组。
* 将没有子项目的连续项目分组到单个组中。
*
* @param sidebar - Flat array of sidebar items / 平面侧边栏项目数组
* @returns Grouped sidebar items / 分组的侧边栏项目
*/
function getSidebarGroups(sidebar) {
	const groups = [];
	let lastGroupIndex = 0;
	for (const index in sidebar) {
		const item = sidebar[index];
		if (item.items) {
			lastGroupIndex = groups.push(item);
			continue;
		}
		if (!groups[lastGroupIndex]) groups.push({ items: [] });
		groups[lastGroupIndex].items.push(item);
	}
	return groups;
}
/**
* Get the first link from sidebar items.
* Recursively searches through nested items to find the first link.
*
* 从侧边栏项目获取第一个链接。
* 递归搜索嵌套项目以找到第一个链接。
*
* @param sidebar - Sidebar items to search / 要搜索的侧边栏项目
* @returns First link found or empty string / 找到的第一个链接或空字符串
*/
function getSidebarFirstLink(sidebar) {
	for (const item of sidebar) {
		if (item.link) return item.link;
		if (item.items) return getSidebarFirstLink(item.items);
	}
	return "";
}
//#endregion
//#region src/client/composables/sidebar.ts
/**
* Check if the given sidebar item contains any active link.
*
* 检查给定的侧边栏项是否包含活动链接
*/
function hasActiveLink(path, items) {
	if (Array.isArray(items)) return items.some((item) => hasActiveLink(path, item));
	return isActive(path, items.link ? resolveRouteFullPath(items.link) : void 0) ? true : items.items ? hasActiveLink(path, items.items) : false;
}
const containsActiveLink = hasActiveLink;
const isSidebarEnabled = ref(false);
const isSidebarCollapsed = ref(false);
/**
* Use sidebar control
*
* 侧边栏控制,提供启用/禁用侧边栏和折叠/展开的控制函数
*/
function useSidebarControl() {
	const enableSidebar = () => {
		isSidebarEnabled.value = true;
	};
	const disableSidebar = () => {
		isSidebarEnabled.value = false;
	};
	const toggleSidebarEnabled = () => {
		if (isSidebarEnabled.value) disableSidebar();
		else enableSidebar();
	};
	function toggleSidebarCollapse(collapse) {
		isSidebarCollapsed.value = collapse ?? !isSidebarCollapsed.value;
	}
	return {
		isSidebarEnabled,
		enableSidebar,
		disableSidebar,
		toggleSidebarEnabled,
		isSidebarCollapsed,
		toggleSidebarCollapse
	};
}
/**
* Use sidebar
*
* 侧边栏数据,获取当前路由的侧边栏项目和分组
*/
function useSidebar() {
	const { page } = useData();
	const routeLocal = useRouteLocale();
	const { hasSidebar } = useLayout();
	const sidebar = useSidebarData();
	const sidebarGroups = computed(() => {
		return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
	});
	return {
		sidebar,
		sidebarKey: computed(() => {
			const _sidebar = sidebarData.value[routeLocal.value];
			if (!_sidebar || _sidebar === "auto" || isArray(_sidebar)) return routeLocal.value;
			return Object.keys(_sidebar).sort((a, b) => b.split("/").length - a.split("/").length).find((dir) => {
				return page.value.path.startsWith(ensureLeadingSlash(dir));
			}) || "";
		}),
		sidebarGroups
	};
}
/**
* Use close sidebar on escape
*
* a11y: 缓存打开侧边栏的元素(菜单按钮),当使用 Escape 关闭菜单时重新聚焦该按钮
*/
function useCloseSidebarOnEscape() {
	const { disableSidebar } = useSidebarControl();
	let triggerElement;
	watchEffect(() => {
		triggerElement = isSidebarEnabled.value ? document.activeElement : void 0;
	});
	onMounted(() => {
		window.addEventListener("keyup", onEscape);
	});
	onUnmounted(() => {
		window.removeEventListener("keyup", onEscape);
	});
	function onEscape(e) {
		if (e.key === "Escape" && isSidebarEnabled.value) {
			disableSidebar();
			triggerElement?.focus();
		}
	}
}
/**
* Use sidebar item control
*
* 侧边栏项目控制,管理单个侧边栏项目的折叠状态、激活状态等
*/
function useSidebarItemControl(item) {
	const { page } = useData();
	const route = useRoute();
	const collapsed = ref(false);
	const collapsible = computed(() => {
		return item.value.collapsed != null;
	});
	const isLink = computed(() => {
		return !!item.value.link;
	});
	const isActiveLink = ref(false);
	const updateIsActiveLink = () => {
		isActiveLink.value = isActive(page.value.path, item.value.link ? resolveRouteFullPath(item.value.link) : void 0);
	};
	watch([
		() => page.value.path,
		item,
		() => route.hash
	], updateIsActiveLink);
	onMounted(updateIsActiveLink);
	const hasActiveLink = computed(() => {
		if (isActiveLink.value) return true;
		return item.value.items ? containsActiveLink(page.value.path, item.value.items) : false;
	});
	const hasChildren = computed(() => {
		return !!(item.value.items && item.value.items.length);
	});
	watch(() => [collapsible.value, item.value.collapsed], (n, o) => {
		if (n[0] !== o?.[0] || n[1] !== o?.[1]) collapsed.value = !!(collapsible.value && item.value.collapsed);
	}, { immediate: true });
	watch(() => [
		page.value.path,
		isActiveLink.value,
		hasActiveLink.value
	], () => {
		if (isActiveLink.value || hasActiveLink.value) collapsed.value = false;
	}, {
		immediate: true,
		flush: "post"
	});
	const toggle = () => {
		if (collapsible.value) collapsed.value = !collapsed.value;
	};
	return {
		collapsed,
		collapsible,
		isLink,
		isActiveLink,
		hasActiveLink,
		hasChildren,
		toggle
	};
}
//#endregion
//#region src/client/composables/layout.ts
const is960 = shallowRef(false);
const is1280 = shallowRef(false);
/**
* Use layout
*
* 获取当前页面布局相关信息,包括是否首页、是否有侧边栏、是否显示目录等
*/
function useLayout() {
	const { frontmatter, theme } = useData();
	const { isPageDecrypted } = useEncrypt();
	const sidebar = useSidebarData();
	const headers = useHeaders();
	const isHome = computed(() => frontmatter.value.home ?? frontmatter.value.pageLayout === "home");
	const hasSidebar = computed(() => {
		return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.pageLayout !== "home";
	});
	const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
	const hasAside = computed(() => {
		if (frontmatter.value.pageLayout === "home" || frontmatter.value.home) return false;
		if (frontmatter.value.pageLayout === "friends" || frontmatter.value.friends) return false;
		if (!isPageDecrypted.value) return false;
		if (frontmatter.value.aside != null) return !!frontmatter.value.aside;
		return theme.value.aside !== false;
	});
	return {
		isHome,
		hasAside,
		hasSidebar,
		leftAside: computed(() => {
			if (hasAside.value) return frontmatter.value.aside == null ? theme.value.aside === "left" : frontmatter.value.aside === "left";
			return false;
		}),
		hasLocalNav: computed(() => headers.value.length > 0),
		isSidebarEnabled,
		isAsideEnabled: computed(() => {
			if (!is1280.value && !is960.value) return false;
			return hasSidebar.value ? is1280.value : is960.value;
		}),
		is960,
		is1280
	};
}
function registerWatchers() {
	if (inBrowser) {
		is960.value = window.innerWidth >= 960;
		is1280.value = window.innerWidth >= 1280;
		window.addEventListener("resize", () => {
			is960.value = window.innerWidth >= 960;
			is1280.value = window.innerWidth >= 1280;
		}, { passive: true });
	}
	const { lang } = useData();
	const route = useRoute();
	const { disableSidebar, toggleSidebarCollapse } = useSidebarControl();
	watch(() => route.path, () => {
		disableSidebar();
		toggleSidebarCollapse(false);
	});
	if (inBrowser) watch(lang, (newLang) => {
		document.documentElement.lang = newLang;
	}, { immediate: true });
	useCloseSidebarOnEscape();
}
//#endregion
//#region src/client/composables/link.ts
/**
* Use link
* Resolves and processes a link URL with smart handling of internal/external links
*
* 使用链接
* 解析并处理链接 URL,智能处理内部/外部链接
*
* @param href - Link URL or reference / 链接 URL 或引用
* @param target - Link target or reference / 链接目标或引用
* @returns Link resolution result / 链接解析结果
*/
function useLink(href, target) {
	const route = useRoute();
	const { page } = useData();
	const maybeIsExternal = computed(() => {
		const link = toValue(href);
		const rawTarget = toValue(target);
		if (!link || link[0] === "#") return false;
		if (rawTarget === "_blank" || isLinkExternal(link)) return true;
		return false;
	});
	const preProcessLink = computed(() => {
		const link = toValue(href);
		if (!link || maybeIsExternal.value) return link;
		if (link[0] === "#") return page.value.path + link;
		const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : void 0;
		const path = resolveRouteFullPath(link, currentPath);
		if (path.includes("#")) {
			if (path.slice(0, path.indexOf("#")) === route.path) return path.slice(path.indexOf("#"));
		}
		return path;
	});
	const isExternal = computed(() => {
		const link = preProcessLink.value;
		if (maybeIsExternal.value) return true;
		if (!link || link[0] === "#") return false;
		const routePath = link.split(/[?#]/)[0];
		const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : void 0;
		const { notFound } = resolveRoute(routePath, currentPath);
		if (__VUEPRESS_DEV__) notFound && console.warn(`[VuePress Dead Link] "${toValue(href)}" is not found in (${page.value.filePathRelative || page.value.path})`);
		return notFound;
	});
	const link = computed(() => {
		if (isExternal.value) return toValue(href);
		return preProcessLink.value;
	});
	return {
		isExternal,
		isExternalProtocol: computed(() => {
			if (!link.value || link.value[0] === "#") return false;
			return isLinkWithProtocol(link.value);
		}),
		link
	};
}
//#endregion
//#region src/client/composables/nav.ts
/**
* Use navbar data
* Returns the resolved navbar items based on theme configuration
*
* 获取导航栏数据
* 根据主题配置返回解析后的导航栏项目
*
* @returns Reactive reference to resolved navbar items / 解析后的导航栏项目的响应式引用
*/
function useNavbarData() {
	const { theme } = useData();
	return computed(() => resolveNavbar(theme.value.navbar || []));
}
/**
* Resolve navbar configuration to resolved items
* Recursively processes navbar items and resolves links
*
* 将导航栏配置解析为解析后的项目
* 递归处理导航栏项目并解析链接
*
* @param navbar - Raw navbar configuration / 原始导航栏配置
* @param _prefix - URL prefix for nested items / 嵌套项目的 URL 前缀
* @returns Array of resolved navbar items / 解析后的导航栏项目数组
*/
function resolveNavbar(navbar, _prefix = "") {
	const resolved = [];
	navbar.forEach((item) => {
		if (typeof item === "string") resolved.push(resolveNavLink(normalizeLink(_prefix, item)));
		else {
			const { items, prefix, ...args } = item;
			const res = { ...args };
			if ("link" in res) res.link = normalizeLink(_prefix, res.link);
			if (items?.length) res.items = resolveNavbar(items, normalizeLink(_prefix, prefix));
			resolved.push(res);
		}
	});
	return resolved;
}
/**
* Use nav
* Provides mobile navigation menu control functionality
*
* 导航栏状态控制,提供移动端导航菜单的打开、关闭和切换功能
*
* @returns Navigation control state and methods / 导航控制状态和方法
*/
function useNav() {
	const isScreenOpen = ref(false);
	/**
	* Open the mobile navigation screen
	* Adds resize listener to auto-close on larger screens
	*/
	function openScreen() {
		isScreenOpen.value = true;
		window.addEventListener("resize", closeScreenOnTabletWindow);
	}
	/**
	* Close the mobile navigation screen
	* Removes resize listener
	*/
	function closeScreen() {
		isScreenOpen.value = false;
		window.removeEventListener("resize", closeScreenOnTabletWindow);
	}
	/**
	* Toggle the mobile navigation screen
	*/
	function toggleScreen() {
		if (isScreenOpen.value) closeScreen();
		else openScreen();
	}
	/**
	* Close screen when the user resizes the window wider than tablet size.
	* Automatically closes the mobile menu on larger screens.
	*/
	function closeScreenOnTabletWindow() {
		if (window.outerWidth >= 768) closeScreen();
	}
	const route = useRoute();
	watch(() => route.path, closeScreen);
	return {
		isScreenOpen,
		openScreen,
		closeScreen,
		toggleScreen
	};
}
//#endregion
//#region src/client/composables/posts-data.ts
/**
* Posts data ref
*
* 文章数据引用,包含所有语言环境的文章列表
*/
const postsData = ref(postsData$1);
/**
* Use posts data
*
* 获取文章数据
*/
function usePostsData() {
	return postsData;
}
/**
* Use locale post list
*
* 获取当前语言环境的文章列表
*/
function useLocalePostList() {
	const collection = useCollection();
	const routeLocale = useRouteLocale();
	return computed(() => {
		if (collection.value) return postsData.value[normalizeLink$1(routeLocale.value, ensureEndingSlash(removeLeadingSlash(collection.value.dir)))] || [];
		return [];
	});
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updatePostsData = (data) => {
	postsData.value = data;
};
//#endregion
//#region src/client/composables/posts-archives.ts
function useArchives() {
	const themeData = useThemeData();
	const list = useLocalePostList();
	const { theme } = useData();
	return { archives: computed(() => {
		const archives = [];
		const countLocale = theme.value.archiveTotalText || themeData.value.archiveTotalText;
		list.value.forEach((item) => {
			const createTime = item.createTime?.split(/\s|T/)[0] || "";
			const year = createTime.split("/")[0];
			let current = archives.find((archive) => archive.title === year);
			if (!current) {
				current = {
					title: year,
					list: [],
					label: ""
				};
				archives.push(current);
			}
			current.list.push({
				title: item.title,
				path: item.path,
				createTime: createTime.slice(year.length + 1).replace(/\//g, "-")
			});
		});
		archives.forEach((item) => {
			item.label = countLocale?.replace("{count}", item.list.length.toString()) || "";
		});
		return archives;
	}) };
}
//#endregion
//#region src/client/composables/posts-category.ts
function usePostsCategory() {
	const postList = useLocalePostList();
	return { categories: computed(() => {
		const list = [];
		postList.value.forEach((item) => {
			const categoryList = item.categoryList;
			if (!categoryList || categoryList.length === 0) list.push({
				type: "post",
				title: item.title,
				path: item.path
			});
			else {
				let cate = list;
				let i = 0;
				while (i < categoryList.length) {
					const { id, name, sort } = categoryList[i];
					const current = cate.find((item) => item.type === "category" && item.id === id);
					if (!current) {
						const items = [];
						cate.push({
							type: "category",
							title: name,
							id,
							sort,
							items
						});
						cate = items;
					} else cate = current.items;
					i++;
				}
				cate.push({
					type: "post",
					title: item.title,
					path: item.path
				});
			}
		});
		return sortCategory(list);
	}) };
}
function sortCategory(items) {
	for (const item of items) if (item.type === "category" && item.items.length) item.items = sortCategory(item.items);
	return items.sort((a, b) => {
		if (a.type === "category" && b.type === "category") return a.sort < b.sort ? -1 : 1;
		if (a.type === "category" && b.type === "post") return -1;
		if (a.type === "post" && b.type === "category") return 1;
		return 0;
	});
}
//#endregion
//#region src/client/composables/route-query.ts
const _queue = /* @__PURE__ */ new WeakMap();
function useRouteQuery(name, defaultValue, options = {}) {
	const { mode = "replace", route = useRoute(), router = useRouter(), transform = (value) => value } = options;
	if (!_queue.has(router)) _queue.set(router, /* @__PURE__ */ new Map());
	const _queriesQueue = _queue.get(router);
	let query = route.query[name];
	tryOnScopeDispose(() => {
		query = void 0;
	});
	let _trigger;
	const proxy = customRef((track, trigger) => {
		_trigger = trigger;
		return {
			get() {
				track();
				return transform(query !== void 0 ? query : toValue(defaultValue));
			},
			set(v) {
				if (query === v) return;
				query = v;
				_queriesQueue.set(name, v);
				trigger();
				nextTick(() => {
					if (_queriesQueue.size === 0) return;
					const newQueries = Object.fromEntries(_queriesQueue.entries());
					_queriesQueue.clear();
					const { query, hash, path } = route;
					router[toValue(mode)]({
						path,
						query: {
							...query,
							...newQueries
						},
						hash
					});
				});
			}
		};
	});
	watch(() => route.query[name], (v) => {
		query = v;
		_trigger();
	}, { flush: "sync" });
	return proxy;
}
//#endregion
//#region src/client/composables/tag-colors.ts
const tagColorsRef = ref(articleTagColors);
const useTagColors = () => tagColorsRef;
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) __VUE_HMR_RUNTIME__.updateArticleTagColors = (data) => {
	tagColorsRef.value = data;
};
//#endregion
//#region src/client/composables/posts-tags.ts
/**
* Use tags
*
* 获取标签列表和当前选中的标签,处理标签相关逻辑
*/
function useTags() {
	const { collection } = useData();
	const list = useLocalePostList();
	const colors = useTagColors();
	const postCollection = computed(() => {
		if (collection.value?.type === "post") return collection.value;
	});
	const tags = computed(() => {
		const tagTheme = postCollection.value?.tagsTheme ?? "colored";
		const tagMap = {};
		list.value.forEach((item) => {
			if (item.tags) toArray(item.tags).forEach((tag) => {
				if (tagMap[tag]) tagMap[tag] += 1;
				else tagMap[tag] = 1;
			});
		});
		return Object.keys(tagMap).map((tag) => ({
			name: tag,
			count: tagMap[tag] > 99 ? "99+" : tagMap[tag],
			className: colors.value[tag] ? `vp-tag-${colors.value[tag]}` : `tag-${tagTheme}`
		}));
	});
	const currentTag = useRouteQuery("tag");
	const postList = computed(() => {
		if (!currentTag.value) return [];
		return list.value.filter((item) => {
			if (item.tags) return toArray(item.tags).includes(currentTag.value);
			return false;
		}).map((item) => ({
			title: item.title,
			path: item.path,
			createTime: item.createTime.split(" ")[0].replace(/\//g, "-")
		}));
	});
	const handleTagClick = (tag) => {
		currentTag.value = tag;
	};
	return {
		tags,
		currentTag,
		postList,
		handleTagClick
	};
}
//#endregion
//#region src/client/composables/posts-extract.ts
function usePostsExtract() {
	const { collection } = useData();
	const postList = useLocalePostList();
	const { tags: tagsList } = useTags();
	const { categories: categoryList } = usePostsCategory();
	const links = useInternalLink();
	return {
		hasPostsExtract: computed(() => collection.value?.type === "post" && (collection.value.archives !== false || collection.value.tags !== false || collection.value.categories !== false)),
		tags: computed(() => ({
			link: links.tags.value?.link,
			text: links.tags.value?.text,
			total: tagsList.value.length
		})),
		archives: computed(() => ({
			link: links.archive.value?.link,
			text: links.archive.value?.text,
			total: postList.value.length
		})),
		categories: computed(() => ({
			link: links.categories.value?.link,
			text: links.categories.value?.text,
			total: getCategoriesTotal(categoryList.value)
		}))
	};
}
function getCategoriesTotal(categories) {
	let total = 0;
	for (const category of categories) if (category.type === "category") {
		total += 1;
		if (category.items.length) total += getCategoriesTotal(category.items);
	}
	return total;
}
//#endregion
//#region src/client/composables/posts-post-list.ts
const DEFAULT_PER_PAGE = 15;
/**
* Use post list control
*
* 文章列表控制,管理分页逻辑和文章列表数据
*/
function usePostListControl(homePage) {
	const { collection } = useData();
	const list = useLocalePostList();
	const { is960 } = useLayout();
	const postCollection = computed(() => {
		if (collection.value?.type === "post") return collection.value;
	});
	const postList = computed(() => {
		const stickyList = list.value.filter((item) => item.sticky === true || typeof item.sticky === "number");
		const otherList = list.value.filter((item) => item.sticky === void 0 || item.sticky === false);
		return [...stickyList.sort((prev, next) => {
			if (next.sticky === true && prev.sticky === true) return 0;
			return next.sticky > prev.sticky ? 1 : -1;
		}), ...otherList];
	});
	const page = useRouteQuery("p", 1, {
		mode: "push",
		transform(val) {
			const page = Number(val);
			if (!Number.isNaN(page) && page > 0) return page;
			return 1;
		}
	});
	const perPage = computed(() => {
		if (postCollection.value?.pagination === false) return 0;
		if (typeof postCollection.value?.pagination === "number") return postCollection.value.pagination;
		return postCollection.value?.pagination?.perPage || DEFAULT_PER_PAGE;
	});
	const totalPage = computed(() => {
		if (postCollection.value?.pagination === false) return 0;
		return Math.ceil(postList.value.length / perPage.value);
	});
	const isLastPage = computed(() => page.value >= totalPage.value);
	const isFirstPage = computed(() => page.value <= 1);
	const isPaginationEnabled = computed(() => postCollection.value?.pagination !== false && totalPage.value > 1);
	const finalList = computed(() => {
		if (postCollection.value?.pagination === false) return postList.value;
		if (postList.value.length <= perPage.value) return postList.value;
		return postList.value.slice((page.value - 1) * perPage.value, page.value * perPage.value);
	});
	const pageRange = computed(() => {
		let range = [];
		const total = totalPage.value;
		const _page = page.value;
		const per = is960.value ? 4 : 5;
		if (total <= 0) return range;
		if (total <= 10) range = Array.from({ length: total }, (_, i) => ({ value: i + 1 }));
		else {
			let i = 1;
			let hasMore = false;
			while (i <= total) {
				if (_page <= per && i <= per || _page >= total - (per - 1) && i >= total - (per - 1)) {
					hasMore = false;
					range.push({ value: i });
				} else if (i <= 2 || i >= total - 1) {
					hasMore = false;
					range.push({ value: i });
				} else if ((_page > per + 1 || _page < total - (per + 1)) && _page - i < per - 2 && i - _page < per - 2) {
					hasMore = false;
					range.push({ value: i });
				} else if (!hasMore) {
					hasMore = true;
					range.push({
						value: i,
						more: true
					});
				}
				i++;
			}
		}
		return range;
	});
	const changePage = (current) => {
		if (page.value === current) return;
		page.value = current;
		setTimeout(() => {
			let top = 0;
			if (homePage.value) {
				top = document.querySelector(".vp-posts")?.getBoundingClientRect().top || 0;
				top += window.scrollY - 64;
			}
			window.scrollTo({
				top,
				behavior: "instant"
			});
		}, 0);
	};
	return {
		postList: finalList,
		page,
		totalPage,
		pageRange,
		isLastPage,
		isFirstPage,
		isPaginationEnabled,
		changePage
	};
}
//#endregion
//#region src/client/composables/prev-next.ts
const SEPARATOR_RE = /^-{3,}$/;
/**
* Use prev next
*
* 获取上一页和下一页链接
*/
function usePrevNext() {
	const route = useRoute();
	const { frontmatter, theme } = useData();
	const { sidebar } = useSidebar();
	const postList = useLocalePostList();
	const locale = usePageLang();
	const { isPosts } = usePostsPageData();
	return {
		prev: computed(() => {
			if (theme.value.prevPage === false) return null;
			const prevConfig = resolveFromFrontmatterConfig(frontmatter.value.prev);
			if (prevConfig !== false) return prevConfig;
			if (isPosts.value) return resolveFromPostsData(postList.value.filter((item) => item.lang === locale.value), route.path, -1);
			else return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, -1);
		}),
		next: computed(() => {
			if (theme.value.nextPage === false) return null;
			const nextConfig = resolveFromFrontmatterConfig(frontmatter.value.next);
			if (nextConfig !== false) return nextConfig;
			if (isPosts.value) return resolveFromPostsData(postList.value.filter((item) => item.lang === locale.value), route.path, 1);
			else return resolveFromSidebarItems(flatSidebar(sidebar.value), route.path, 1);
		})
	};
}
/**
* Resolve `prev` or `next` config from frontmatter
*/
function resolveFromFrontmatterConfig(conf) {
	if (conf === false) return null;
	if (isString(conf)) return resolveNavLink(conf);
	if (isPlainObject(conf)) return conf;
	return false;
}
function flatSidebar(sidebar, res = []) {
	for (const item of sidebar) {
		if (item.link) res.push({
			link: item.link,
			text: item.text || "",
			icon: item.icon
		});
		if (Array.isArray(item.items) && item.items.length) flatSidebar(item.items, res);
	}
	return res;
}
/**
* Resolve `prev` or `next` config from sidebar items
*/
function resolveFromSidebarItems(sidebarItems, currentPath, offset) {
	let index = sidebarItems.findIndex((item) => resolveRouteFullPath(item.link) === currentPath);
	if (index === -1) return null;
	while ((index += offset) >= 0 && index < sidebarItems.length) {
		const targetItem = sidebarItems[index];
		if (targetItem?.link && !SEPARATOR_RE.test(targetItem.link)) return targetItem;
	}
	return null;
}
function resolveFromPostsData(postList, currentPath, offset) {
	const index = postList.findIndex((item) => item.path === currentPath);
	if (index !== -1) {
		const targetItem = postList[index + offset];
		if (!targetItem?.path) return null;
		return {
			link: targetItem.path,
			text: targetItem.title
		};
	}
	return null;
}
//#endregion
//#region src/client/composables/scroll-promise.ts
let promise = null;
let promiseResolve = null;
const scrollPromise = {
	wait: () => promise,
	pending: () => {
		promise = new Promise((resolve) => promiseResolve = resolve);
	},
	resolve: () => {
		promiseResolve?.();
		promise = null;
		promiseResolve = null;
	}
};
const useScrollPromise = () => scrollPromise;
//#endregion
//#region src/client/composables/scroll-behavior.ts
function enhanceScrollBehavior(router) {
	router.options.scrollBehavior = async (to, from, savedPosition) => {
		await useScrollPromise().wait();
		if (savedPosition) return savedPosition;
		if (to.hash) return {
			el: to.hash,
			top: 64
		};
		return { top: 0 };
	};
	router.beforeEach((to, from) => {
		if (inBrowser) {
			if (from.path !== to.path) document.documentElement.classList.remove("smooth");
		}
	});
	router.afterEach(() => nextTick(() => {
		if (inBrowser) setTimeout(() => {
			document.documentElement.classList.add("smooth");
		}, 1e3);
	}));
}
//#endregion
//#region src/client/composables/view-transition.ts
const strategy = {
	"fade": (reverse) => ({
		opacity: reverse(["0", "1"]),
		duration: 300
	}),
	"circle-clip": (reverse, { x, y }) => ({
		clipPath: reverse([`circle(0px at ${x}px ${y}px)`, `circle(${Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y))}px at ${x}px ${y}px)`]),
		duration: 650
	}),
	"horizontal-clip": (reverse) => ({ clipPath: reverse([`inset(0px ${innerWidth}px 0px 0px)`, `inset(0px 0px 0px 0px)`]) }),
	"vertical-clip": (reverse) => ({ clipPath: reverse([`inset(0px 0px ${innerHeight}px 0px)`, `inset(0px 0px 0px 0px)`]) }),
	"skew-clip": (reverse) => ({ clipPath: reverse(["polygon(0px 0px, 0px 0px, 0px 0px)", `polygon(0px 0px, ${innerWidth * 2}px 0px, 0px ${innerHeight * 2}px)`]) }),
	"blinds-vertical": (reverse) => ({ clipPath: reverse(["inset(50% 0% 50% 0%)", "inset(0 0 0 0)"]) }),
	"blinds-horizontal": (reverse) => ({ clipPath: reverse(["polygon(50% 0, 50% 100%, 50% 100%, 50% 0)", "polygon(0 0, 0 100%, 100% 100%, 100% 0)"]) }),
	"soft-blur-fade": (reverse) => ({
		opacity: reverse(["0", "1"]),
		filter: reverse(["blur(10px)", "blur(0px)"]),
		duration: 380
	}),
	"diamond-reveal": (reverse) => ({
		clipPath: reverse([`polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)`, `polygon(50% -50%, 150% 50%, 50% 150%, -50% 50%)`]),
		duration: 500
	})
};
function resolveTransitionKeyframes(x, y, mode, isDark) {
	if (!mode || !strategy[mode]) mode = "fade";
	const reverse = (effect) => {
		return isDark ? effect.reverse() : effect;
	};
	const { duration = 400, ...keyframes } = strategy[mode](reverse, {
		x,
		y,
		isDark
	});
	return {
		keyframes,
		duration
	};
}
//#endregion
//#region src/client/composables/watermark.ts
const FP = __PLUME_WM_FP__;
function setupWatermark() {
	const { frontmatter } = useData();
	defineWatermarkConfig(computed(() => {
		const disableFullPage = typeof frontmatter.value.watermark === "object" && frontmatter.value.watermark.fullPage === false;
		return { parent: !FP || disableFullPage ? ".vp-doc" : "body" };
	}));
}
//#endregion
export { EncryptSymbol, autoDirSidebar, collectionItemRef, collectionsRef, darkModeSymbol, enableTransitions, encrypt, enhanceScrollBehavior, focusedElement, forceUpdateCollection, getHeaders, getPresetLocaleData, getSidebar, getSidebarFirstLink, getSidebarGroups, hasActiveLink, normalizeIconClassname, postsData, presetLocales, registerWatchers, resolveHeaders, resolveIconsData, resolveTransitionKeyframes, setupCollection, setupDarkMode, setupEncrypt, setupHeaders, setupSidebar, setupThemeData, setupWatermark, sidebarData, socialFallbacks, themeData, themeLocaleDataSymbol, useActiveAnchor, useArchives, useBulletin, useBulletinControl, useCloseSidebarOnEscape, useCollection, useCollections, useContributors, useCopyright, useCssVar, useDarkMode, useData, useEditLink, useEncrypt, useEncryptCompare, useEncryptData, useFlyout, useHeaders, useIconsData, useInternalLink, useLangs, useLastUpdated, useLayout, useLink, useLocalePostList, useNav, useNavbarData, usePostListControl, usePostsCategory, usePostsData, usePostsExtract, usePostsPageData, usePrevNext, useRouteQuery, useScrollPromise, useSidebar, useSidebarControl, useSidebarData, useSidebarItemControl, useTagColors, useTags, useThemeData, useThemeLocaleData };