feat: allow ignoring certain headers and their subtrees completely in outline

closes #4171
pull/4177/head
Divyansh Singh 3 months ago
parent bc7271d258
commit 3e11b6abf5

@ -1,5 +1,11 @@
import { resolveHeaders } from 'client/theme-default/composables/outline'
const element = {
classList: {
contains: () => false
}
} as unknown as HTMLHeadElement
describe('client/theme-default/composables/outline', () => {
describe('resolveHeader', () => {
test('levels range', () => {
@ -9,12 +15,14 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
link: '#h2-1',
element
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
link: '#h3-1',
element
}
],
[2, 3]
@ -28,9 +36,12 @@ describe('client/theme-default/composables/outline', () => {
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
link: '#h3-1',
children: [],
element
}
]
],
element
}
])
})
@ -42,12 +53,14 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
link: '#h2-1',
element
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
link: '#h3-1',
element
}
],
2
@ -56,7 +69,9 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
link: '#h2-1',
children: [],
element
}
])
})
@ -68,42 +83,50 @@ describe('client/theme-default/composables/outline', () => {
{
level: 2,
title: 'h2 - 1',
link: '#h2-1'
link: '#h2-1',
element
},
{
level: 3,
title: 'h3 - 1',
link: '#h3-1'
link: '#h3-1',
element
},
{
level: 4,
title: 'h4 - 1',
link: '#h4-1'
link: '#h4-1',
element
},
{
level: 3,
title: 'h3 - 2',
link: '#h3-2'
link: '#h3-2',
element
},
{
level: 4,
title: 'h4 - 2',
link: '#h4-2'
link: '#h4-2',
element
},
{
level: 2,
title: 'h2 - 2',
link: '#h2-2'
link: '#h2-2',
element
},
{
level: 3,
title: 'h3 - 3',
link: '#h3-3'
link: '#h3-3',
element
},
{
level: 4,
title: 'h4 - 3',
link: '#h4-3'
link: '#h4-3',
element
}
],
'deep'
@ -122,9 +145,12 @@ describe('client/theme-default/composables/outline', () => {
{
level: 4,
title: 'h4 - 1',
link: '#h4-1'
link: '#h4-1',
children: [],
element
}
]
],
element
},
{
level: 3,
@ -134,11 +160,15 @@ describe('client/theme-default/composables/outline', () => {
{
level: 4,
title: 'h4 - 2',
link: '#h4-2'
link: '#h4-2',
children: [],
element
}
]
],
element
}
]
],
element
},
{
level: 2,
@ -153,11 +183,15 @@ describe('client/theme-default/composables/outline', () => {
{
level: 4,
title: 'h4 - 3',
link: '#h4-3'
link: '#h4-3',
children: [],
element
}
]
],
element
}
]
],
element
}
])
})

@ -13,7 +13,7 @@ export type MenuItem = Omit<Header, 'slug' | 'children'> & {
children?: MenuItem[]
}
export function resolveTitle(theme: DefaultTheme.Config) {
export function resolveTitle(theme: DefaultTheme.Config): string {
return (
(typeof theme.outline === 'object' &&
!Array.isArray(theme.outline) &&
@ -23,7 +23,7 @@ export function resolveTitle(theme: DefaultTheme.Config) {
)
}
export function getHeaders(range: DefaultTheme.Config['outline']) {
export function getHeaders(range: DefaultTheme.Config['outline']): MenuItem[] {
const headers = [
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
]
@ -80,38 +80,13 @@ export function resolveHeaders(
? [2, 6]
: levelsRange
headers = headers.filter((h) => h.level >= high && h.level <= low)
// clear previous caches
resolvedHeaders.length = 0
// update global header list for active link rendering
for (const { element, link } of headers) {
resolvedHeaders.push({ element, link })
}
const ret: MenuItem[] = []
outer: for (let i = 0; i < headers.length; i++) {
const cur = headers[i]
if (i === 0) {
ret.push(cur)
} else {
for (let j = i - 1; j >= 0; j--) {
const prev = headers[j]
if (prev.level < cur.level) {
;(prev.children || (prev.children = [])).push(cur)
continue outer
}
}
ret.push(cur)
}
}
return ret
return buildTree(headers, high, low)
}
export function useActiveAnchor(
container: Ref<HTMLElement>,
marker: Ref<HTMLElement>
) {
): void {
const { isAsideEnabled } = useAside()
const onScroll = throttleAndDebounce(setActiveLink, 100)
@ -221,3 +196,38 @@ function getAbsoluteTop(element: HTMLElement): number {
}
return offsetTop
}
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
}

Loading…
Cancel
Save