vuepress-theme-plume
Version:
A Blog&Document Theme for VuePress 2.0
1,583 lines (1,545 loc) • 61.8 kB
JavaScript
// src/client/composables/aside.ts
import { useMediaQuery as useMediaQuery2 } from "@vueuse/core";
import { computed as computed5 } from "vue";
// src/client/composables/sidebar.ts
import { sidebar as sidebarRaw } from "@internal/sidebar";
import {
ensureLeadingSlash,
isArray,
isPlainObject,
isString,
removeLeadingSlash
} from "@vuepress/helper/client";
import { useMediaQuery } from "@vueuse/core";
import {
computed as computed4,
inject as inject4,
onMounted,
onUnmounted,
provide as provide2,
ref as ref4,
watch,
watchEffect
} from "vue";
import { resolveRouteFullPath, useRoute as useRoute2, useRouteLocale } from "vuepress/client";
import { isActive, normalizeLink, normalizePrefix, resolveNavLink } from "../utils/index.js";
// src/client/composables/data.ts
import { computed as computed2 } from "vue";
import {
usePageData,
usePageFrontmatter,
usePageLang,
useSiteLocaleData
} from "vuepress/client";
// src/client/composables/dark-mode.ts
import { useDark, useEventListener } from "@vueuse/core";
import { inject as inject2, ref as ref2 } from "vue";
// src/client/composables/theme-data.ts
import { themeData as themeDataRaw } from "@internal/themePlumeData";
import { computed, inject, ref } from "vue";
import { clientDataSymbol } from "vuepress/client";
var themeLocaleDataSymbol = Symbol(
__VUEPRESS_DEV__ ? "themeLocaleData" : ""
);
var themeData = ref(themeDataRaw);
function useThemeData() {
return themeData;
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateThemeData = (data) => {
themeData.value = data;
};
}
function useThemeLocaleData() {
const themeLocaleData = inject(themeLocaleDataSymbol);
if (!themeLocaleData) {
throw new Error("useThemeLocaleData() is called without provider.");
}
return themeLocaleData;
}
function resolveThemeLocaleData(theme, routeLocale) {
const { locales, ...baseOptions } = theme;
return {
...baseOptions,
...locales?.[routeLocale]
};
}
function setupThemeData(app) {
const themeData2 = useThemeData();
const clientData = app._context.provides[clientDataSymbol];
const themeLocaleData = computed(
() => resolveThemeLocaleData(themeData2.value, clientData.routeLocale.value)
);
app.provide(themeLocaleDataSymbol, themeLocaleData);
Object.defineProperties(app.config.globalProperties, {
$theme: {
get() {
return themeData2.value;
}
},
$themeLocale: {
get() {
return themeLocaleData.value;
}
}
});
}
// src/client/composables/dark-mode.ts
var darkModeSymbol = Symbol(
__VUEPRESS_DEV__ ? "darkMode" : ""
);
function enableTransitions() {
if (typeof document === "undefined")
return false;
return "startViewTransition" in document && window.matchMedia("(prefers-reduced-motion: no-preference)").matches;
}
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" ? ref2(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 : {}
}) : ref2(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";
});
}
function useDarkMode() {
const isDarkMode = inject2(darkModeSymbol);
if (!isDarkMode)
throw new Error("useDarkMode() is called without provider.");
return isDarkMode;
}
// src/client/composables/data.ts
function useData() {
const themeData2 = useThemeData();
const theme = useThemeLocaleData();
const page = usePageData();
const frontmatter = usePageFrontmatter();
const site = useSiteLocaleData();
const isDark = useDarkMode();
const lang = usePageLang();
const blog = computed2(() => themeData2.value.blog || {});
return { theme, page, frontmatter, lang, site, isDark, blog };
}
// src/client/composables/encrypt.ts
import { hasOwn, useSessionStorage } from "@vueuse/core";
import { compare, genSaltSync } from "bcrypt-ts/browser";
import { computed as computed3, inject as inject3, provide } from "vue";
import { useRoute } from "vuepress/client";
// src/client/composables/encrypt-data.ts
import { encrypt as rawEncrypt } from "@internal/encrypt";
import { ref as ref3 } from "vue";
var encrypt = ref3(resolveEncryptData(rawEncrypt));
function useEncryptData() {
return encrypt;
}
function resolveEncryptData([global, separator, admin, matches, rules]) {
return {
global,
separator,
matches,
admins: admin.split(separator),
ruleList: Object.keys(rules).map((key) => ({
key,
match: matches[key],
rules: rules[key].split(separator)
}))
};
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateEncrypt = (data) => {
encrypt.value = resolveEncryptData(data);
};
}
// src/client/composables/encrypt.ts
var EncryptSymbol = Symbol(
__VUEPRESS_DEV__ ? "Encrypt" : ""
);
var storage = useSessionStorage("2a0a3d6afb2fdf1f", () => {
if (__VUEPRESS_SSR__) {
return { s: ["", ""], g: "", p: {} };
}
return {
s: [genSaltSync(10), genSaltSync(10)],
g: "",
p: {}
};
});
function mergeHash(hash) {
const [left, right] = storage.value.s;
return left + hash + right;
}
function splitHash(hash) {
const [left, right] = storage.value.s;
if (!hash.startsWith(left) || !hash.endsWith(right))
return "";
return hash.slice(left.length, hash.length - right.length);
}
var compareCache = /* @__PURE__ */ new Map();
async function compareDecrypt(content, hash, separator = ":") {
const key = [content, hash].join(separator);
if (compareCache.has(key))
return compareCache.get(key);
try {
const result = await compare(content, hash);
compareCache.set(key, result);
return result;
} catch {
compareCache.set(key, false);
return false;
}
}
var matchCache = /* @__PURE__ */ new Map();
function createMatchRegex(match) {
if (matchCache.has(match))
return matchCache.get(match);
const regex = new RegExp(match);
matchCache.set(match, regex);
return regex;
}
function toMatch(match, pagePath, filePathRelative) {
const relativePath = filePathRelative || "";
if (match[0] === "^") {
const regex = createMatchRegex(match);
return regex.test(pagePath) || relativePath && regex.test(relativePath);
}
if (match.endsWith(".md"))
return relativePath && relativePath.endsWith(match);
return pagePath.startsWith(match) || relativePath.startsWith(match);
}
function setupEncrypt() {
const { page } = useData();
const route = useRoute();
const encrypt2 = useEncryptData();
const hasPageEncrypt = computed3(() => {
const pagePath = route.path;
const filePathRelative = page.value.filePathRelative;
return encrypt2.value.ruleList.length ? encrypt2.value.matches.some((match) => toMatch(match, pagePath, filePathRelative)) : false;
});
const isGlobalDecrypted = computed3(() => {
if (!encrypt2.value.global)
return true;
const hash = splitHash(storage.value.g);
return !!hash && encrypt2.value.admins.includes(hash);
});
const hashList = computed3(() => {
const pagePath = route.path;
const filePathRelative = page.value.filePathRelative;
return encrypt2.value.ruleList.length ? encrypt2.value.ruleList.filter((item) => toMatch(item.match, pagePath, filePathRelative)) : [];
});
const isPageDecrypted = computed3(() => {
if (!hasPageEncrypt.value)
return true;
const hash = splitHash(storage.value.p.__GLOBAL__ || "");
if (hash && encrypt2.value.admins.includes(hash))
return true;
for (const { key, rules } of hashList.value) {
if (hasOwn(storage.value.p, key)) {
const hash2 = splitHash(storage.value.p[key]);
if (hash2 && rules.includes(hash2))
return true;
}
}
return false;
});
provide(EncryptSymbol, {
hasPageEncrypt,
isGlobalDecrypted,
isPageDecrypted,
hashList
});
}
function useEncrypt() {
const result = inject3(EncryptSymbol);
if (!result)
throw new Error("useEncrypt() is called without setup");
return result;
}
function useEncryptCompare() {
const encrypt2 = useEncryptData();
const { page } = useData();
const route = useRoute();
const { hashList } = useEncrypt();
async function compareGlobal(password) {
if (!password)
return false;
for (const admin of encrypt2.value.admins) {
if (await compareDecrypt(password, admin, encrypt2.value.separator)) {
storage.value.g = mergeHash(admin);
return true;
}
}
return false;
}
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, encrypt2.value.separator)) {
decrypted = true;
storage.value.p = {
...storage.value.p,
[key]: mergeHash(rule)
};
break;
}
}
if (decrypted)
break;
}
}
if (!decrypted) {
decrypted = await compareGlobal(password);
}
return decrypted;
}
return { compareGlobal, comparePage };
}
// src/client/composables/sidebar.ts
var { __auto__, __home__, ...items } = sidebarRaw;
var sidebarData = ref4(items);
var autoDirSidebar = ref4(__auto__);
var autoHomeData = ref4(__home__);
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateSidebar = (data) => {
const { __auto__: __auto__2, __home__: __home__2, ...items2 } = data;
sidebarData.value = items2;
autoDirSidebar.value = __auto__2;
autoHomeData.value = __home__2;
};
}
var sidebarSymbol = Symbol(
__VUEPRESS_DEV__ ? "sidebar" : ""
);
function setupSidebar() {
const { page, frontmatter } = useData();
const routeLocale = useRouteLocale();
const hasSidebar = computed4(() => {
return frontmatter.value.pageLayout !== "home" && frontmatter.value.pageLayout !== "friends" && frontmatter.value.sidebar !== false && frontmatter.value.layout !== "NotFound";
});
const sidebarData2 = computed4(() => {
return hasSidebar.value ? getSidebar(typeof frontmatter.value.sidebar === "string" ? frontmatter.value.sidebar : page.value.path, routeLocale.value) : [];
});
provide2(sidebarSymbol, sidebarData2);
}
function useSidebarData() {
const sidebarData2 = inject4(sidebarSymbol);
if (!sidebarData2) {
throw new Error("useSidebarData() is called without provider.");
}
return sidebarData2;
}
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(_sidebar)) {
routePath = decodeURIComponent(routePath);
const dir = Object.keys(_sidebar).sort((a, b) => b.split("/").length - a.split("/").length).find((dir2) => {
return routePath.startsWith(`${routeLocal}${removeLeadingSlash(dir2)}`);
}) || "";
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(sidebar)) {
const prefix = normalizePrefix(dir, sidebar.prefix);
return resolveSidebarItems(
sidebar.items === "auto" ? autoDirSidebar.value[prefix] : sidebar.items,
prefix
);
}
}
return [];
}
function resolveSidebarItems(sidebarItems, _prefix = "") {
const resolved = [];
sidebarItems.forEach((item) => {
if (isString(item)) {
resolved.push(resolveNavLink(normalizeLink(_prefix, item)));
} else {
const { link, items: items2, 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;
}
const nextPrefix = normalizePrefix(_prefix, prefix || dir);
if (items2 === "auto") {
navLink.items = autoDirSidebar.value[nextPrefix];
if (!navLink.link && autoHomeData.value[nextPrefix]) {
navLink.link = normalizeLink(autoHomeData.value[nextPrefix]);
const nav = resolveNavLink(navLink.link);
navLink.icon = nav.icon || navLink.icon;
}
} else {
navLink.items = items2?.length ? resolveSidebarItems(items2, nextPrefix) : void 0;
}
resolved.push(navLink);
}
});
return resolved;
}
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;
}
function hasActiveLink(path, items2) {
if (Array.isArray(items2)) {
return items2.some((item) => hasActiveLink(path, item));
}
return isActive(
path,
items2.link ? resolveRouteFullPath(items2.link) : void 0
) ? true : items2.items ? hasActiveLink(path, items2.items) : false;
}
var containsActiveLink = hasActiveLink;
function useSidebar() {
const { theme, frontmatter, page } = useData();
const routeLocal = useRouteLocale();
const is960 = useMediaQuery("(min-width: 960px)");
const { isPageDecrypted } = useEncrypt();
const isOpen = ref4(false);
const sidebarKey = computed4(() => {
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));
}) || "";
});
const sidebar = useSidebarData();
const hasSidebar = computed4(() => {
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.pageLayout !== "home";
});
const hasAside = computed4(() => {
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;
});
const leftAside = computed4(() => {
if (hasAside.value) {
return frontmatter.value.aside == null ? theme.value.aside === "left" : frontmatter.value.aside === "left";
}
return false;
});
const isSidebarEnabled = computed4(() => hasSidebar.value && is960.value);
const sidebarGroups = computed4(() => {
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
});
const open = () => {
isOpen.value = true;
};
const close = () => {
isOpen.value = false;
};
const toggle = () => {
if (isOpen.value) {
close();
} else {
open();
}
};
return {
isOpen,
sidebar,
sidebarKey,
sidebarGroups,
hasSidebar,
hasAside,
leftAside,
isSidebarEnabled,
open,
close,
toggle
};
}
function useCloseSidebarOnEscape(isOpen, close) {
let triggerElement;
watchEffect(() => {
triggerElement = isOpen.value ? document.activeElement : void 0;
});
onMounted(() => {
window.addEventListener("keyup", onEscape);
});
onUnmounted(() => {
window.removeEventListener("keyup", onEscape);
});
function onEscape(e) {
if (e.key === "Escape" && isOpen.value) {
close();
triggerElement?.focus();
}
}
}
function useSidebarControl(item) {
const { page } = useData();
const route = useRoute2();
const collapsed = ref4(false);
const collapsible = computed4(() => {
return item.value.collapsed != null;
});
const isLink = computed4(() => {
return !!item.value.link;
});
const isActiveLink = ref4(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 hasActiveLink2 = computed4(() => {
if (isActiveLink.value) {
return true;
}
return item.value.items ? containsActiveLink(page.value.path, item.value.items) : false;
});
const hasChildren = computed4(() => {
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, hasActiveLink2.value], () => {
if (isActiveLink.value || hasActiveLink2.value) {
collapsed.value = false;
}
}, { immediate: true, flush: "post" });
const toggle = () => {
if (collapsible.value) {
collapsed.value = !collapsed.value;
}
};
return {
collapsed,
collapsible,
isLink,
isActiveLink,
hasActiveLink: hasActiveLink2,
hasChildren,
toggle
};
}
function getSidebarFirstLink(sidebar) {
for (const item of sidebar) {
if (item.link)
return item.link;
if (item.items)
return getSidebarFirstLink(item.items);
}
return "";
}
// src/client/composables/aside.ts
function useAside() {
const { hasSidebar } = useSidebar();
const is960 = useMediaQuery2("(min-width: 960px)");
const is1280 = useMediaQuery2("(min-width: 1280px)");
const isAsideEnabled = computed5(() => {
if (!is1280.value && !is960.value)
return false;
return hasSidebar.value ? is1280.value : is960.value;
});
return {
isAsideEnabled
};
}
// src/client/composables/blog-archives.ts
import { computed as computed7 } from "vue";
// src/client/composables/blog-data.ts
import {
blogPostData as blogPostDataRaw
} from "@internal/blogData";
import { computed as computed6, ref as ref5 } from "vue";
import { usePageLang as usePageLang2 } from "vuepress/client";
var blogPostData = ref5(blogPostDataRaw);
function usePostList() {
return blogPostData;
}
function useLocalePostList() {
const locale = usePageLang2();
return computed6(() => blogPostData.value.filter((item) => item.lang === locale.value));
}
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateBlogPostData = (data) => {
blogPostData.value = data;
};
}
// src/client/composables/blog-archives.ts
function useArchives() {
const themeData2 = useThemeData();
const list = useLocalePostList();
const { theme } = useData();
const archives = computed7(() => {
const archives2 = [];
const countLocale = theme.value.archiveTotalText || themeData2.value.archiveTotalText;
list.value.forEach((item) => {
const createTime = item.createTime?.split(/\s|T/)[0] || "";
const year = createTime.split("/")[0];
let current = archives2.find((archive) => archive.title === year);
if (!current) {
current = { title: year, list: [], label: "" };
archives2.push(current);
}
current.list.push({
title: item.title,
path: item.path,
createTime: createTime.slice(year.length + 1).replace(/\//g, "-")
});
});
archives2.forEach((item) => {
item.label = countLocale?.replace("{count}", item.list.length.toString()) || "";
});
return archives2;
});
return { archives };
}
// src/client/composables/blog-category.ts
import { computed as computed8 } from "vue";
function useBlogCategory() {
const postList = useLocalePostList();
const categories = computed8(() => {
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((item2) => item2.type === "category" && item2.id === id);
if (!current) {
const items2 = [];
cate.push({ type: "category", title: name, id, sort, items: items2 });
cate = items2;
} else {
cate = current.items;
}
i++;
}
cate.push({ type: "post", title: item.title, path: item.path });
}
});
return sortCategory(list);
});
return { categories };
}
function sortCategory(items2) {
for (const item of items2) {
if (item.type === "category" && item.items.length) {
item.items = sortCategory(item.items);
}
}
return items2.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;
});
}
// src/client/composables/blog-extract.ts
import { computed as computed11 } from "vue";
// src/client/composables/blog-tags.ts
import { computed as computed9 } from "vue";
import { toArray } from "../utils/index.js";
// src/client/composables/route-query.ts
import { tryOnScopeDispose } from "@vueuse/core";
import { customRef, nextTick, toValue, watch as watch2 } from "vue";
import { useRoute as useRoute3, useRouter } from "vuepress/client";
var _queue = /* @__PURE__ */ new WeakMap();
function useRouteQuery(name, defaultValue, options = {}) {
const {
mode = "replace",
route = useRoute3(),
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: query2, hash, path } = route;
router[toValue(mode)]({
path,
query: { ...query2, ...newQueries },
hash
});
});
}
};
});
watch2(
() => route.query[name],
(v) => {
query = v;
_trigger();
},
{ flush: "sync" }
);
return proxy;
}
// src/client/composables/tag-colors.ts
import { articleTagColors } from "@internal/articleTagColors";
import { ref as ref6 } from "vue";
var tagColorsRef = ref6(articleTagColors);
var useTagColors = () => tagColorsRef;
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateArticleTagColors = (data) => {
tagColorsRef.value = data;
};
}
// src/client/composables/blog-tags.ts
function useTags() {
const { blog } = useData();
const list = useLocalePostList();
const colors = useTagColors();
const tags = computed9(() => {
const tagTheme = blog.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 = computed9(() => {
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
};
}
// src/client/composables/internal-link.ts
import { computed as computed10 } from "vue";
import { useRouteLocale as useRouteLocale2 } from "vuepress/client";
function useInternalLink() {
const { blog, theme } = useData();
const themeData2 = useThemeData();
const routeLocale = useRouteLocale2();
function resolveLink(name, link) {
return {
link: (routeLocale.value + link).replace(/\/+/g, "/"),
text: theme.value[`${name}Text`] || themeData2.value[`${name}Text`]
};
}
const home = computed10(() => resolveLink("home", "/"));
const blogLink = computed10(() => blog.value.postList !== false ? resolveLink("blog", blog.value.link || "blog/") : home.value);
const tags = computed10(() => blog.value.tags !== false ? resolveLink("tag", blog.value.tagsLink || "blog/tags/") : void 0);
const archive = computed10(() => blog.value.archives !== false ? resolveLink("archive", blog.value.archivesLink || "blog/archives/") : void 0);
const categories = computed10(() => blog.value.categories !== false ? resolveLink("category", blog.value.categoriesLink || "blog/categories/") : void 0);
return {
home,
blog: blogLink,
tags,
archive,
categories
};
}
// src/client/composables/blog-extract.ts
function useBlogExtract() {
const { blog } = useData();
const postList = useLocalePostList();
const { tags: tagsList } = useTags();
const { categories: categoryList } = useBlogCategory();
const links = useInternalLink();
const hasBlogExtract = computed11(
() => blog.value.archives !== false || blog.value.tags !== false || blog.value.categories !== false
);
const tags = computed11(() => ({
link: links.tags.value?.link,
text: links.tags.value?.text,
total: tagsList.value.length
}));
const archives = computed11(() => ({
link: links.archive.value?.link,
text: links.archive.value?.text,
total: postList.value.length
}));
const categories = computed11(() => ({
link: links.categories.value?.link,
text: links.categories.value?.text,
total: getCategoriesTotal(categoryList.value)
}));
return {
hasBlogExtract,
tags,
archives,
categories
};
}
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;
}
// src/client/composables/blog-post-list.ts
import { useMediaQuery as useMediaQuery3 } from "@vueuse/core";
import { computed as computed12 } from "vue";
var DEFAULT_PER_PAGE = 15;
function usePostListControl(homePage) {
const { blog } = useData();
const list = useLocalePostList();
const is960 = useMediaQuery3("(max-width: 960px)");
const postList = computed12(() => {
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 page2 = Number(val);
if (!Number.isNaN(page2) && page2 > 0)
return page2;
return 1;
}
});
const perPage = computed12(() => {
if (blog.value.pagination === false)
return 0;
if (typeof blog.value.pagination === "number")
return blog.value.pagination;
return blog.value.pagination?.perPage || DEFAULT_PER_PAGE;
});
const totalPage = computed12(() => {
if (blog.value.pagination === false)
return 0;
return Math.ceil(postList.value.length / perPage.value);
});
const isLastPage = computed12(() => page.value >= totalPage.value);
const isFirstPage = computed12(() => page.value <= 1);
const isPaginationEnabled = computed12(() => blog.value.pagination !== false && totalPage.value > 1);
const finalList = computed12(() => {
if (blog.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 = computed12(() => {
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-blog")?.getBoundingClientRect().top || 0;
top += window.scrollY - 64;
}
window.scrollTo({ top, behavior: "instant" });
}, 0);
};
return {
postList: finalList,
page,
totalPage,
pageRange,
isLastPage,
isFirstPage,
isPaginationEnabled,
changePage
};
}
// src/client/composables/bulletin.ts
import { useLocalStorage, useSessionStorage as useSessionStorage2 } from "@vueuse/core";
import { computed as computed13, ref as ref7, watch as watch3 } from "vue";
var showBulletin = ref7(false);
function useBulletin() {
const { theme } = useData();
const bulletin = computed13(() => theme.value.bulletin === true ? {} : theme.value.bulletin);
return bulletin;
}
function useBulletinControl() {
const session = useSessionStorage2("plume:bulletin", "");
const local = useLocalStorage("plume:bulletin", "");
const { page } = useData();
const bulletin = useBulletin();
const enableBulletin = computed13(() => page.value.bulletin ?? true);
watch3(() => 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
};
}
// src/client/composables/contributors.ts
import { useContributors as _useContributors } from "@vuepress/plugin-git/client";
import { computed as computed14 } from "vue";
import { isPlainObject as isPlainObject2 } from "vuepress/shared";
function useContributors() {
const { frontmatter } = useData();
const list = _useContributors();
const theme = useThemeData();
const mode = computed14(() => {
const config = theme.value.contributors;
if (isPlainObject2(config))
return config.mode || "inline";
return "inline";
});
const contributors = computed14(() => {
const config = frontmatter.value.contributors ?? !!theme.value.contributors;
if (config === false)
return [];
return list.value;
});
const hasContributors = computed14(() => contributors.value.length > 0);
return { mode, contributors, hasContributors };
}
// src/client/composables/copyright.ts
import { computed as computed15 } from "vue";
import { useRouteLocale as useRouteLocale3 } from "vuepress/client";
// src/client/composables/preset-locales.ts
var presetLocales = __PLUME_PRESET_LOCALE__;
function getPresetLocaleData(locale, name) {
return presetLocales[locale]?.[name] || presetLocales["/"][name];
}
// src/client/composables/copyright.ts
var 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 = useRouteLocale3();
const { contributors } = useContributors();
const hasCopyright = computed15(() => Boolean(copyright.value));
const creation = computed15(() => copyright.value.creation || "original");
const license = computed15(() => resolveLicense(copyright.value.license, routeLocale.value));
const author = computed15(() => resolveAuthor(copyright.value.author, creation.value, contributors.value));
const sourceUrl = computed15(() => {
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;
});
const creationText = computed15(() => {
const creation2 = copyright.value.creation;
if (creation2 === "translate") {
return theme.value.copyrightCreationTranslateText || "This article is translated from";
} else if (creation2 === "reprint") {
return theme.value.copyrightCreationReprintText || "This article is reprint from";
}
return theme.value.copyrightCreationOriginalText || "This article link: ";
});
return { license, author, hasCopyright, creation, creationText, 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 contributor2 = contributors.find((c) => c.name === options.name);
if (contributor2)
options.url = contributor2.url;
}
return options;
}
// src/client/composables/edit-link.ts
import { computed as computed16 } from "vue";
import { resolveEditLink } from "../utils/index.js";
function useEditLink() {
const { theme, page, frontmatter } = useData();
const themeData2 = useThemeData();
return computed16(() => {
const showEditLink = frontmatter.value.editLink ?? themeData2.value.editLink ?? true;
if (!showEditLink)
return null;
const {
docsRepo,
docsBranch = "main",
docsDir = ""
} = themeData2.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
};
});
}
// src/client/composables/flyout.ts
import { onUnmounted as onUnmounted2, readonly, ref as ref8, watch as watch4 } from "vue";
import { inBrowser } from "../utils/index.js";
var focusedElement = ref8();
var active = false;
var listeners = 0;
function useFlyout(options) {
const focus = ref8(false);
if (inBrowser) {
if (!active) {
activateFocusTracking();
}
listeners++;
const unwatch = watch4(focusedElement, (el) => {
if (el === options.el.value || options.el.value?.contains(el)) {
focus.value = true;
options.onFocus?.();
} else {
focus.value = false;
options.onBlur?.();
}
});
onUnmounted2(() => {
unwatch();
listeners--;
if (!listeners)
deactivateFocusTracking();
});
}
return readonly(focus);
}
function activateFocusTracking() {
document.addEventListener("focusin", handleFocusIn);
active = true;
focusedElement.value = document.activeElement;
}
function deactivateFocusTracking() {
document.removeEventListener("focusin", handleFocusIn);
}
function handleFocusIn() {
focusedElement.value = document.activeElement;
}
// src/client/composables/home.ts
import { computed as computed17, onMounted as onMounted2, onUnmounted as onUnmounted3 } from "vue";
var lightTint = {
r: { value: 200, offset: 36 },
g: { value: 200, offset: 36 },
b: { value: 200, offset: 36 }
};
var darkTint = {
r: { value: 32, offset: 36 },
g: { value: 32, offset: 36 },
b: { value: 32, offset: 36 }
};
function useHomeHeroTintPlate(canvas, enable, tintPlate) {
const isDark = useDarkMode();
let ctx = null;
let t = 0;
let timer;
const plate = computed17(() => {
const defaultTint = isDark.value ? darkTint : lightTint;
if (!tintPlate.value)
return defaultTint;
const plate2 = tintPlate.value;
if (typeof plate2 === "string" || typeof plate2 === "number") {
if (isDark.value)
return darkTint;
const values = toPlate(plate2);
return values.length !== 3 ? lightTint : toTint(values);
}
if (typeof plate2 === "object") {
if ("r" in plate2) {
if (isDark.value)
return darkTint;
return toNumber({ ...lightTint, ...plate2 });
}
const key = isDark.value ? "dark" : "light";
if (key in plate2) {
const _plate = plate2[key];
if (typeof _plate === "string" || typeof _plate === "number") {
const values = toPlate(_plate);
return values.length !== 3 ? lightTint : toTint(values);
}
return toNumber({ ...defaultTint, ...plate2 });
}
}
return defaultTint;
});
onMounted2(() => {
if (canvas.value && enable.value) {
ctx = canvas.value.getContext("2d");
if (timer) {
window.cancelAnimationFrame(timer);
}
run();
}
});
onUnmounted3(() => {
if (timer) {
window.cancelAnimationFrame(timer);
}
});
function run() {
for (let x = 0; x <= 35; x++) {
for (let y = 0; y <= 35; y++)
col(x, y, R(x, y, t), G(x, y, t), B(x, y, t));
}
t = t + 0.02;
timer = window.requestAnimationFrame(run);
}
function col(x, y, r, g, b) {
if (!ctx)
return;
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.fillRect(x, y, 1, 1);
}
function R(x, y, t2) {
const r = plate.value.r;
return Math.floor(r.value + r.offset * Math.cos((x * x - y * y) / 300 + t2));
}
function G(x, y, t2) {
const g = plate.value.g;
return Math.floor(g.value + g.offset * Math.sin((x * x * Math.cos(t2 / 4) + y * y * Math.sin(t2 / 3)) / 300));
}
function B(x, y, t2) {
const b = plate.value.b;
return Math.floor(b.value + b.offset * Math.sin(5 * Math.sin(t2 / 9) + ((x - 100) * (x - 100) + (y - 100) * (y - 100)) / 1100));
}
}
function toPlate(plate) {
return typeof plate === "number" || Number(plate) === Number.parseInt(plate) ? [plate, plate, plate].map((n) => Number(n)) : plate.includes(",") ? plate.replace(/\s/g, "").split(",").map((n) => Number(n)) : [];
}
function toTint([r, g, b]) {
return { r: toColor(r), g: toColor(g), b: toColor(b) };
}
function toColor(num) {
const offset = 256 - num;
return { value: num, offset: offset > 64 ? 64 : offset };
}
function toNumber(tint) {
Object.keys(tint).forEach((key) => {
const p = tint[key];
p.value = Number(p.value);
p.offset = Number(p.offset);
});
return tint;
}
// src/client/composables/icons.ts
import { icons } from "@internal/iconify";
import { ref as ref9 } from "vue";
var iconsData = ref9(icons);
var useIconsData = () => iconsData;
if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) {
__VUE_HMR_RUNTIME__.updateIcons = (data) => {
iconsData.value = data;
};
}
// src/client/composables/langs.ts
import { computed as computed19 } from "vue";
import { resolveRoute, useRouteLocale as useRouteLocale4 } from "vuepress/client";
import { removeLeadingSlash as removeLeadingSlash2 } from "vuepress/shared";
import { normalizeLink as normalizeLink2 } from "../utils/index.js";
// src/client/composables/page.ts
import { computed as computed18 } from "vue";
function useBlogPageData() {
const { page } = useData();
const postList = usePostList();
const isBlogPost = computed18(() => {
return postList.value.some((item) => item.path === page.value.path);
});
const isBlogLayout = computed18(() => {
const type = page.value.type;
return type === "blog" || type === "blog-archives" || type === "blog-tags" || type === "blog-categories";
});
return {
isBlogPost,
isBlogLayout
};
}
// src/client/composables/langs.ts
function useLangs({
removeCurrent = true
} = {}) {
const theme = useThemeData();
const { page } = useData();
const routeLocale = useRouteLocale4();
const { isBlogPost } = useBlogPageData();
const currentLang = computed19(() => {
const link = routeLocale.value;
return {
label: theme.value.locales?.[link]?.selectLanguageName,
link
};
});
const resolvePath = (locale, url) => {
const targetPath = normalizeLink2(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;
const blog = theme.value.blog;
if (isBlogPost.value && blog !== false)
return normalizeLink2(locale, removeLeadingSlash2(blog?.link || "blog/"));
const home = theme.value.home || "/";
const fallbackResolve = resolveRoute(locale);
return fallbackResolve.notFound ? home : fallbackResolve.path;
};
const localeLinks = computed19(
() => Object.entries(theme.value.locales || {}).flatMap(
([key, locale]) => removeCurrent && currentLang.value.label === locale.selectLanguageName ? [] : {
text: locale.selectLanguageName,
link: getPageLink(key)
}
)
);
return { localeLinks, currentLang };
}
// src/client/composables/latest-updated.ts
import { computed as computed20, onMounted as onMounted3, ref as ref10, watchEffect as watchEffect2 } from "vue";
import { usePageLang as usePageLang3 } from "vuepress/client";
function useLastUpdated() {
const { theme, page, frontmatter } = useData();
const themeData2 = useThemeData();
const lang = usePageLang3();
const date = computed20(() => page.value.git?.updatedTime ? new Date(page.value.git.updatedTime) : null);
const isoDatetime = computed20(() => date.value?.toISOString());
const datetime = ref10("");
const lastUpdatedText = computed20(() => {
if (themeData2.value.lastUpdated === false)
return "";
return theme.value.lastUpdatedText || "Last updated";
});
onMounted3(() => {
watchEffect2(() => {
if (frontmatter.value.lastUpdated === false || themeData2.value.lastUpdated === false)
return;
datetime.value = date.value ? new Intl.DateTimeFormat(
themeData2.value.lastUpdated?.formatOptions?.forceLocale ? lang.value : void 0,
themeData2.value.lastUpdated?.formatOptions ?? {
dateStyle: "short",
timeStyle: "short"
}
).format(date.value) : "";
});
});
return {
datetime,
isoDatetime,
lastUpdatedText
};
}
// src/client/composables/link.ts
import { isLinkExternal } from "@vuepress/helper/client";
import { computed as computed21, toValue as toValue2 } from "vue";
import { resolveRouteFullPath as resolveRouteFullPath2, useRoute as useRoute4 } from "vuepress/client";
function useLink(href, target) {
const route = useRoute4();
const { page } = useData();
const isExternal = computed21(() => {
const link2 = toValue2(href);
const rawTarget = toValue2(target);
if (!link2)
return false;
if (rawTarget === "_blank" || isLinkExternal(link2))
return true;
const filename = link2.split(/[#?]/)[0]?.split("/").pop() || "";
if (filename === "" || filename.endsWith(".html") || filename.endsWith(".md"))
return false;
return filename.includes(".");
});
const link = computed21(() => {
const link2 = toValue2(href);
if (!link2)
return void 0;
if (isExternal.value)
return link2;
const currentPath = page.value.filePathRelative ? `/${page.value.filePathRelative}` : void 0;
const path = resolveRouteFullPath2(link2, currentPath);
if (path.includes("#")) {
if (path.slice(0, path.indexOf("#")) === route.path) {
return path.slice(path.indexOf("#"));
}
}
return path;
});
return { isExternal, link };
}
// src/client/composables/nav.ts
import { computed as computed22, ref as ref11, watch as watch5 } from "vue";
import { useRoute as useRoute5 } from "vuepress/client";
import { normalizeLink as normalizeLink3, resolveNavLink as resolveNavLink2 } from "../utils/index.js";
function useNavbarData() {
const { theme } = useData();
return computed22(() => resolveNavbar(theme.value.navbar || []));
}
function resolveNavbar(navbar, _prefix = "") {
const resolved = [];
navbar.forEach((item) => {
if (typeof item === "string") {
resolved.push(resolveNavLink2(normalizeLink3(_prefix, item)));
} else {
const { items: items2, prefix, ...args } = item;
const res = { ...args };
if ("link" in res) {
res.link = normalizeLink3(_prefix, res.link);
}
if (items2?.length) {
res.items = resolveNavbar(
items2,
normalizeLink3(_prefix, prefix)
);
}
resolved.push(res);
}
});
return resolved;
}
function useNav() {
const isScreenOpen = ref11(false);
function openScreen() {
isScreenOpen.value = true;
window.addEventListener("resize", closeScreenOnTabletWindow);
}
function closeScreen() {
isScreenOpen.value = false;
window.removeEventListener("resize", closeScreenOnTabletWindow);
}
function toggleScreen() {
if (isScreenOpen.value) {
closeScreen();
} else {
openScreen();
}
}
function closeScreenOnTabletWindow() {
if (window.outerWidth >= 768) {
closeScreen();
}
}
const route = useRoute5();
watch5(() => route.path, closeScreen);
return {
isScreenOpen,
openScreen,
cl