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

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

@ -1,5 +1,11 @@
import { resolveHeaders } from 'client/theme-default/composables/outline' import { resolveHeaders } from 'client/theme-default/composables/outline'
const element = {
classList: {
contains: () => false
}
} as unknown as HTMLHeadElement
describe('client/theme-default/composables/outline', () => { describe('client/theme-default/composables/outline', () => {
describe('resolveHeader', () => { describe('resolveHeader', () => {
test('levels range', () => { test('levels range', () => {
@ -9,12 +15,14 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 2, level: 2,
title: 'h2 - 1', title: 'h2 - 1',
link: '#h2-1' link: '#h2-1',
element
}, },
{ {
level: 3, level: 3,
title: 'h3 - 1', title: 'h3 - 1',
link: '#h3-1' link: '#h3-1',
element
} }
], ],
[2, 3] [2, 3]
@ -28,9 +36,12 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 3, level: 3,
title: 'h3 - 1', title: 'h3 - 1',
link: '#h3-1' link: '#h3-1',
children: [],
element
} }
] ],
element
} }
]) ])
}) })
@ -42,12 +53,14 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 2, level: 2,
title: 'h2 - 1', title: 'h2 - 1',
link: '#h2-1' link: '#h2-1',
element
}, },
{ {
level: 3, level: 3,
title: 'h3 - 1', title: 'h3 - 1',
link: '#h3-1' link: '#h3-1',
element
} }
], ],
2 2
@ -56,7 +69,9 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 2, level: 2,
title: 'h2 - 1', title: 'h2 - 1',
link: '#h2-1' link: '#h2-1',
children: [],
element
} }
]) ])
}) })
@ -68,42 +83,50 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 2, level: 2,
title: 'h2 - 1', title: 'h2 - 1',
link: '#h2-1' link: '#h2-1',
element
}, },
{ {
level: 3, level: 3,
title: 'h3 - 1', title: 'h3 - 1',
link: '#h3-1' link: '#h3-1',
element
}, },
{ {
level: 4, level: 4,
title: 'h4 - 1', title: 'h4 - 1',
link: '#h4-1' link: '#h4-1',
element
}, },
{ {
level: 3, level: 3,
title: 'h3 - 2', title: 'h3 - 2',
link: '#h3-2' link: '#h3-2',
element
}, },
{ {
level: 4, level: 4,
title: 'h4 - 2', title: 'h4 - 2',
link: '#h4-2' link: '#h4-2',
element
}, },
{ {
level: 2, level: 2,
title: 'h2 - 2', title: 'h2 - 2',
link: '#h2-2' link: '#h2-2',
element
}, },
{ {
level: 3, level: 3,
title: 'h3 - 3', title: 'h3 - 3',
link: '#h3-3' link: '#h3-3',
element
}, },
{ {
level: 4, level: 4,
title: 'h4 - 3', title: 'h4 - 3',
link: '#h4-3' link: '#h4-3',
element
} }
], ],
'deep' 'deep'
@ -122,9 +145,12 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 4, level: 4,
title: 'h4 - 1', title: 'h4 - 1',
link: '#h4-1' link: '#h4-1',
children: [],
element
} }
] ],
element
}, },
{ {
level: 3, level: 3,
@ -134,11 +160,15 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 4, level: 4,
title: 'h4 - 2', title: 'h4 - 2',
link: '#h4-2' link: '#h4-2',
children: [],
element
} }
] ],
element
} }
] ],
element
}, },
{ {
level: 2, level: 2,
@ -153,11 +183,15 @@ describe('client/theme-default/composables/outline', () => {
{ {
level: 4, level: 4,
title: 'h4 - 3', 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[] children?: MenuItem[]
} }
export function resolveTitle(theme: DefaultTheme.Config) { export function resolveTitle(theme: DefaultTheme.Config): string {
return ( return (
(typeof theme.outline === 'object' && (typeof theme.outline === 'object' &&
!Array.isArray(theme.outline) && !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 = [ const headers = [
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)') ...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
] ]
@ -80,38 +80,13 @@ export function resolveHeaders(
? [2, 6] ? [2, 6]
: levelsRange : levelsRange
headers = headers.filter((h) => h.level >= high && h.level <= low) return buildTree(headers, high, 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
} }
export function useActiveAnchor( export function useActiveAnchor(
container: Ref<HTMLElement>, container: Ref<HTMLElement>,
marker: Ref<HTMLElement> marker: Ref<HTMLElement>
) { ): void {
const { isAsideEnabled } = useAside() const { isAsideEnabled } = useAside()
const onScroll = throttleAndDebounce(setActiveLink, 100) const onScroll = throttleAndDebounce(setActiveLink, 100)
@ -221,3 +196,38 @@ function getAbsoluteTop(element: HTMLElement): number {
} }
return offsetTop 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