154 lines
5.7 KiB
JavaScript
154 lines
5.7 KiB
JavaScript
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 [];
|
|
};
|