UNPKG

@vuepress/plugin-catalog

Version:
218 lines (217 loc) 9.85 kB
import { endsWith, ensureEndingSlash, ensureLeadingSlash, entries, isNumber, isPlainObject, isString, keys, startsWith, useLocaleConfig, } from '@vuepress/helper/client'; import { computed, defineComponent, h, shallowRef } from 'vue'; import { RouteLink, usePageData, useRoutes, useSiteData } from 'vuepress/client'; import { useCatalogInfoGetter } from '../helpers/index.js'; import '../styles/catalog.css'; export default defineComponent({ name: 'Catalog', props: { /** * Catalog Base * * 目录的基础路径 * * @default current route base */ base: { type: String, default: '', }, /** * Max level of catalog * * @description only 1,2,3 are supported * * Catalog 的最大层级 * * @description 目前仅支持 1,2,3 * * @default 3 */ level: { type: Number, default: 3, }, /** * Whether show index for catalog * * 目录是否显示索引 */ index: Boolean, /** * Whether hide `Category` title * * 是否隐藏 `目录` 标题 * * @default false */ hideHeading: Boolean, }, setup(props) { const catalogInfoGetter = useCatalogInfoGetter(); const locale = useLocaleConfig(__CATALOG_LOCALES__); const page = usePageData(); const routes = useRoutes(); const siteData = useSiteData(); const getCatalogData = () => entries(routes.value) .map(([path, { meta }]) => { const info = catalogInfoGetter(meta); if (!info) return null; const level = path.split('/').length; return { level: endsWith(path, '/') ? level - 2 : level - 1, base: path.replace(/\/[^/]+\/?$/, '/'), path, ...info, }; }) .filter((item) => isPlainObject(item) && isString(item.title)); const catalogInfo = shallowRef(getCatalogData()); const catalogData = computed(() => { const base = props.base ? ensureLeadingSlash(ensureEndingSlash(props.base)) : page.value.path.replace(/\/[^/]+$/, '/'); const baseDepth = base.split('/').length - 2; const result = []; catalogInfo.value .filter(({ level, path }) => { // filter those under current base if (!startsWith(path, base) || path === base) return false; if (base === '/') { const otherLocales = keys(siteData.value.locales).filter((item) => item !== '/'); // exclude 404 page and other locales if (path === '/404.html' || otherLocales.some((localePath) => startsWith(path, localePath))) return false; } return ( // level is less than or equal to max level level - baseDepth <= props.level); }) .sort(({ title: titleA, level: levelA, order: orderA }, { title: titleB, level: levelB, order: orderB }) => { const level = levelA - levelB; if (level) return level; // infoA order is absent if (!isNumber(orderA)) { // infoB order is absent if (!isNumber(orderB)) // compare title return titleA.localeCompare(titleB); // infoB order is present return orderB; } // infoB order is absent if (!isNumber(orderB)) return orderA; // now we are sure both order exist // infoA order is positive if (orderA > 0) { if (orderB > 0) return orderA - orderB; return -1; } // both order are negative if (orderB < 0) return orderA - orderB; return 1; }) .forEach((info) => { const { base, level } = info; switch (level - baseDepth) { case 1: { result.push(info); break; } case 2: { const parent = result.find((item) => item.path === base); if (parent) (parent.children ??= []).push(info); break; } default: { const grandParent = result.find((item) => item.path === base.replace(/\/[^/]+\/$/, '/')); if (grandParent) { const parent = grandParent.children?.find((item) => item.path === base); if (parent) (parent.children ??= []).push(info); } } } }); return result; }); return () => { const isDeep = catalogData.value.some((item) => item.children); return h('div', { class: ['vp-catalog-wrapper', { index: props.index }] }, [ props.hideHeading ? null : h('h2', { class: 'vp-catalog-main-title' }, locale.value.title), catalogData.value.length ? h(props.index ? 'ol' : 'ul', { class: ['vp-catalogs', { deep: isDeep }] }, catalogData.value.map(({ children = [], title, path, content }) => { const childLink = h(RouteLink, { class: 'vp-catalog-title', to: path }, () => (content ? h(content) : title)); return h('li', { class: 'vp-catalog' }, isDeep ? [ h('h3', { id: title, class: [ 'vp-catalog-child-title', { 'has-children': children.length }, ], }, [ h('a', { 'href': `#${title}`, 'class': 'vp-catalog-header-anchor', 'aria-hidden': true, }, '#'), childLink, ]), children.length ? h(props.index ? 'ol' : 'ul', { class: 'vp-child-catalogs' }, children.map(({ children = [], content, path, title }) => h('li', { class: 'vp-child-catalog' }, [ h('div', { class: [ 'vp-catalog-sub-title', { 'has-children': children.length, }, ], }, [ h('a', { href: `#${title}`, class: 'vp-catalog-header-anchor', }, '#'), h(RouteLink, { class: 'vp-catalog-title', to: path, }, () => content ? h(content) : title), ]), children.length ? h(props.index ? 'ol' : 'div', { class: props.index ? 'vp-sub-catalogs' : 'vp-sub-catalogs-wrapper', }, children.map(({ content, path, title }) => props.index ? h('li', { class: 'vp-sub-catalog', }, h(RouteLink, { to: path }, () => content ? h(content) : title)) : h(RouteLink, { class: 'vp-sub-catalog-link', to: path, }, () => content ? h(content) : title))) : null, ]))) : null, ] : h('div', { class: 'vp-catalog-child-title' }, childLink)); })) : h('p', { class: 'vp-empty-catalog' }, locale.value.empty), ]); }; }, });