import { useThemeLocaleData } from '@theme/useThemeData'; import { getHeaders, keys, startsWith } from '@vuepress/helper/client'; import { computed, inject, onMounted, provide, ref, watch } from 'vue'; import { usePageData, usePageFrontmatter, useRoute, useRouteLocale, useRouter, } from 'vuepress/client'; import { isPlainObject, isString } from 'vuepress/shared'; import { getAutoLink, isLinkInternal, resolvePrefix } from '../utils/index.js'; export const headersRef = ref([]); export const setupHeaders = () => { const router = useRouter(); const themeLocale = useThemeLocaleData(); const frontmatter = usePageFrontmatter(); const levels = computed(() => frontmatter.value.sidebarDepth ?? themeLocale.value.sidebarDepth ?? 2); router.beforeEach((to, from) => { if (to.path !== from.path) { headersRef.value = []; } }); const updateHeaders = () => { if (levels.value <= 0) { headersRef.value = []; return; } headersRef.value = getHeaders({ selector: [...new Array(6)] .map((_, i) => `.theme-default-content h${i + 1}`) .join(','), levels: [2, levels.value + 1], ignore: ['.vp-badge'], }); }; watch(levels, updateHeaders); onMounted(updateHeaders); }; export const useHeaders = () => headersRef; export const sidebarItemsSymbol = Symbol('sidebarItems'); /** * Inject sidebar items global computed */ export const useSidebarItems = () => { const sidebarItems = inject(sidebarItemsSymbol); if (!sidebarItems) { throw new Error('useSidebarItems() is called without provider.'); } return sidebarItems; }; /** * Create sidebar items ref and provide as global computed in setup */ export const setupSidebarItems = () => { const themeLocale = useThemeLocaleData(); const frontmatter = usePageFrontmatter(); const page = usePageData(); const route = useRoute(); const routeLocale = useRouteLocale(); const headers = useHeaders(); const sidebarConfig = computed(() => frontmatter.value.home ? false : (frontmatter.value.sidebar ?? themeLocale.value.sidebar ?? 'heading')); const sidebarItems = computed(() => resolveSidebarItems(sidebarConfig.value, page.value, route.path, routeLocale.value, headers.value)); provide(sidebarItemsSymbol, sidebarItems); }; /** * Resolve sidebar items global computed * * It should only be resolved and provided once */ export const resolveSidebarItems = (sidebarConfig, page, path, routeLocale, headers) => { // resolve sidebar items according to the config if (sidebarConfig === false) { return []; } if (sidebarConfig === 'heading') { return resolveSidebarHeadingItem(page, headers); } if (Array.isArray(sidebarConfig)) { return resolveArraySidebarItems(sidebarConfig, headers, path, routeLocale); } if (isPlainObject(sidebarConfig)) { return resolveMultiSidebarItems(sidebarConfig, page, headers, path); } return []; }; /** * Util to transform page header to sidebar item */ export const resolveSidebarHeaderItem = (header) => ({ text: header.title, link: header.link, children: resolveSidebarHeaderItems(header.children), }); export const resolveSidebarHeaderItems = (headers) => headers ? headers.map((header) => resolveSidebarHeaderItem(header)) : []; /** * Resolve current page and its header to sidebar items if the config is `heading` */ export const resolveSidebarHeadingItem = (page, headers) => [ { text: page.title, children: resolveSidebarHeaderItems(headers), }, ]; /** * Resolve sidebar items if the config is an array */ export const resolveArraySidebarItems = (sidebarConfig, headers, path, prefix = '') => { const handleChildItem = (item, pathPrefix) => { const childItem = isString(item) ? getAutoLink(resolvePrefix(pathPrefix, item)) : isString(item.link) ? { ...item, link: isLinkInternal(item.link) ? getAutoLink(resolvePrefix(pathPrefix, item.link)).link : item.link, } : item; if ('children' in childItem) { return { ...childItem, children: childItem.children.map((item) => handleChildItem(item, resolvePrefix(pathPrefix, childItem.prefix))), }; } // if the sidebar item is current page and children is not set // use headers of current page as children if (childItem.link === path) { // skip h1 header const currentHeaders = headers[0]?.level === 1 ? headers[0].children : headers; return { ...childItem, children: resolveSidebarHeaderItems(currentHeaders), }; } return childItem; }; return sidebarConfig.map((item) => handleChildItem(item, prefix)); }; /** * Resolve sidebar items if the config is a key -> value (path-prefix -> array) object */ export const resolveMultiSidebarItems = (sidebarConfig, page, headers, path) => { const sidebarRoutes = keys(sidebarConfig).sort((x, y) => y.length - x.length); // Find matching config for (const base of sidebarRoutes) if (startsWith(decodeURI(path), base)) { const matched = sidebarConfig[base]; return matched ? matched === 'heading' ? resolveSidebarHeadingItem(page, headers) : resolveArraySidebarItems(matched, headers, path, base) : []; } console.warn(`${decodeURI(path)} is missing sidebar config.`); return []; };