UNPKG

@valaxyjs/utils

Version:

A utility library for Valaxy

147 lines (124 loc) 3.82 kB
import type { MenuItem } from '../types' /** * @ref vitepress src/client/theme-default/composables/outline.ts */ // cached list of anchor elements from resolveHeaders export const resolvedHeaders: { element: HTMLHeadElement, link: string }[] = [] export interface GetHeadersOptions { range?: number | [number, number] | 'deep' | { level: [number, number] } selector?: string filter?: (el: Element) => boolean } export function buildTree(data: MenuItem[], min: number, max: number): MenuItem[] { resolvedHeaders.length = 0 const result: MenuItem[] = [] const stack: (MenuItem | { level: number, shouldIgnore: true })[] = [] data.forEach((item) => { const node = { ...item, children: [] } let parent = stack[stack.length - 1] while (parent && parent.level >= node.level) { stack.pop() parent = stack[stack.length - 1] } if ( node.element.classList.contains('ignore-header') || (parent && 'shouldIgnore' in parent) ) { stack.push({ level: node.level, shouldIgnore: true }) return } if (node.level > max || node.level < min) return resolvedHeaders.push({ element: node.element, link: node.link }) if (parent) parent.children!.push(node) else result.push(node) stack.push(node) }) return result } export function addToParent( currIndex: number, headers: MenuItem[], levelsRange: [number, number], ) { if (currIndex === 0) return true const currentHeader = headers[currIndex] for (let index = currIndex - 1; index >= 0; index--) { const header = headers[index] if ( header.level < currentHeader.level && header.level >= levelsRange[0] && header.level <= levelsRange[1] ) { if (header.children == null) header.children = [] header.children.push(currentHeader) return false } } return true } export function resolveHeaders( headers: MenuItem[], range: GetHeadersOptions['range'] = [2, 4], ) { const levelsRange = (typeof range === 'object' && !Array.isArray(range) ? range.level : range) || 2 const [high, low]: [number, number] = typeof levelsRange === 'number' ? [levelsRange, levelsRange] : levelsRange === 'deep' ? [2, 6] : levelsRange return buildTree(headers, high, low) } export function serializeHeader(h: Element): string { let ret = '' for (const node of Array.from(h.childNodes)) { if (node.nodeType === 1) { if ( (node as Element).classList.contains('VABadge') || (node as Element).classList.contains('header-anchor') ) { continue } ret += node.textContent } else if (node.nodeType === 3) { ret += node.textContent } } return ret.trim() } // el => el.id && el.hasChildNodes() /** * get headers from document directly */ export function getHeaders(options: GetHeadersOptions = { range: [2, 4], selector: '.markdown-body', }) { const mdBodySelector = options.selector || '.markdown-body' // when transition, the markdown-body will be two // the first is the old one, the last is the new one const markdownBodyElements = document.querySelectorAll(mdBodySelector) as NodeListOf<HTMLElement> const markdownBody = markdownBodyElements[markdownBodyElements.length - 1] const headers = Array.from(markdownBody?.querySelectorAll(`${mdBodySelector} :where(h1,h2,h3,h4,h5,h6)`) || []) .filter(el => options.filter ? options.filter(el) : true) .map((el) => { const level = Number(el.tagName[1]) return { element: el as HTMLHeadElement, title: serializeHeader(el), link: `#${el.id}`, level, // @ts-expect-error lang lang: el.lang, } }) return resolveHeaders(headers, options.range) }