Skip to content

Commit 3e11b6a

Browse files
committed
feat: allow ignoring certain headers and their subtrees completely in outline
closes vuejs#4171
1 parent bc7271d commit 3e11b6a

File tree

2 files changed

+96
-52
lines changed

2 files changed

+96
-52
lines changed

__tests__/unit/client/theme-default/composables/outline.test.ts

+57-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { resolveHeaders } from 'client/theme-default/composables/outline'
22

3+
const element = {
4+
classList: {
5+
contains: () => false
6+
}
7+
} as unknown as HTMLHeadElement
8+
39
describe('client/theme-default/composables/outline', () => {
410
describe('resolveHeader', () => {
511
test('levels range', () => {
@@ -9,12 +15,14 @@ describe('client/theme-default/composables/outline', () => {
915
{
1016
level: 2,
1117
title: 'h2 - 1',
12-
link: '#h2-1'
18+
link: '#h2-1',
19+
element
1320
},
1421
{
1522
level: 3,
1623
title: 'h3 - 1',
17-
link: '#h3-1'
24+
link: '#h3-1',
25+
element
1826
}
1927
],
2028
[2, 3]
@@ -28,9 +36,12 @@ describe('client/theme-default/composables/outline', () => {
2836
{
2937
level: 3,
3038
title: 'h3 - 1',
31-
link: '#h3-1'
39+
link: '#h3-1',
40+
children: [],
41+
element
3242
}
33-
]
43+
],
44+
element
3445
}
3546
])
3647
})
@@ -42,12 +53,14 @@ describe('client/theme-default/composables/outline', () => {
4253
{
4354
level: 2,
4455
title: 'h2 - 1',
45-
link: '#h2-1'
56+
link: '#h2-1',
57+
element
4658
},
4759
{
4860
level: 3,
4961
title: 'h3 - 1',
50-
link: '#h3-1'
62+
link: '#h3-1',
63+
element
5164
}
5265
],
5366
2
@@ -56,7 +69,9 @@ describe('client/theme-default/composables/outline', () => {
5669
{
5770
level: 2,
5871
title: 'h2 - 1',
59-
link: '#h2-1'
72+
link: '#h2-1',
73+
children: [],
74+
element
6075
}
6176
])
6277
})
@@ -68,42 +83,50 @@ describe('client/theme-default/composables/outline', () => {
6883
{
6984
level: 2,
7085
title: 'h2 - 1',
71-
link: '#h2-1'
86+
link: '#h2-1',
87+
element
7288
},
7389
{
7490
level: 3,
7591
title: 'h3 - 1',
76-
link: '#h3-1'
92+
link: '#h3-1',
93+
element
7794
},
7895
{
7996
level: 4,
8097
title: 'h4 - 1',
81-
link: '#h4-1'
98+
link: '#h4-1',
99+
element
82100
},
83101
{
84102
level: 3,
85103
title: 'h3 - 2',
86-
link: '#h3-2'
104+
link: '#h3-2',
105+
element
87106
},
88107
{
89108
level: 4,
90109
title: 'h4 - 2',
91-
link: '#h4-2'
110+
link: '#h4-2',
111+
element
92112
},
93113
{
94114
level: 2,
95115
title: 'h2 - 2',
96-
link: '#h2-2'
116+
link: '#h2-2',
117+
element
97118
},
98119
{
99120
level: 3,
100121
title: 'h3 - 3',
101-
link: '#h3-3'
122+
link: '#h3-3',
123+
element
102124
},
103125
{
104126
level: 4,
105127
title: 'h4 - 3',
106-
link: '#h4-3'
128+
link: '#h4-3',
129+
element
107130
}
108131
],
109132
'deep'
@@ -122,9 +145,12 @@ describe('client/theme-default/composables/outline', () => {
122145
{
123146
level: 4,
124147
title: 'h4 - 1',
125-
link: '#h4-1'
148+
link: '#h4-1',
149+
children: [],
150+
element
126151
}
127-
]
152+
],
153+
element
128154
},
129155
{
130156
level: 3,
@@ -134,11 +160,15 @@ describe('client/theme-default/composables/outline', () => {
134160
{
135161
level: 4,
136162
title: 'h4 - 2',
137-
link: '#h4-2'
163+
link: '#h4-2',
164+
children: [],
165+
element
138166
}
139-
]
167+
],
168+
element
140169
}
141-
]
170+
],
171+
element
142172
},
143173
{
144174
level: 2,
@@ -153,11 +183,15 @@ describe('client/theme-default/composables/outline', () => {
153183
{
154184
level: 4,
155185
title: 'h4 - 3',
156-
link: '#h4-3'
186+
link: '#h4-3',
187+
children: [],
188+
element
157189
}
158-
]
190+
],
191+
element
159192
}
160-
]
193+
],
194+
element
161195
}
162196
])
163197
})

src/client/theme-default/composables/outline.ts

+39-29
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type MenuItem = Omit<Header, 'slug' | 'children'> & {
1313
children?: MenuItem[]
1414
}
1515

16-
export function resolveTitle(theme: DefaultTheme.Config) {
16+
export function resolveTitle(theme: DefaultTheme.Config): string {
1717
return (
1818
(typeof theme.outline === 'object' &&
1919
!Array.isArray(theme.outline) &&
@@ -23,7 +23,7 @@ export function resolveTitle(theme: DefaultTheme.Config) {
2323
)
2424
}
2525

26-
export function getHeaders(range: DefaultTheme.Config['outline']) {
26+
export function getHeaders(range: DefaultTheme.Config['outline']): MenuItem[] {
2727
const headers = [
2828
...document.querySelectorAll('.VPDoc :where(h1,h2,h3,h4,h5,h6)')
2929
]
@@ -80,38 +80,13 @@ export function resolveHeaders(
8080
? [2, 6]
8181
: levelsRange
8282

83-
headers = headers.filter((h) => h.level >= high && h.level <= low)
84-
// clear previous caches
85-
resolvedHeaders.length = 0
86-
// update global header list for active link rendering
87-
for (const { element, link } of headers) {
88-
resolvedHeaders.push({ element, link })
89-
}
90-
91-
const ret: MenuItem[] = []
92-
outer: for (let i = 0; i < headers.length; i++) {
93-
const cur = headers[i]
94-
if (i === 0) {
95-
ret.push(cur)
96-
} else {
97-
for (let j = i - 1; j >= 0; j--) {
98-
const prev = headers[j]
99-
if (prev.level < cur.level) {
100-
;(prev.children || (prev.children = [])).push(cur)
101-
continue outer
102-
}
103-
}
104-
ret.push(cur)
105-
}
106-
}
107-
108-
return ret
83+
return buildTree(headers, high, low)
10984
}
11085

11186
export function useActiveAnchor(
11287
container: Ref<HTMLElement>,
11388
marker: Ref<HTMLElement>
114-
) {
89+
): void {
11590
const { isAsideEnabled } = useAside()
11691

11792
const onScroll = throttleAndDebounce(setActiveLink, 100)
@@ -221,3 +196,38 @@ function getAbsoluteTop(element: HTMLElement): number {
221196
}
222197
return offsetTop
223198
}
199+
200+
function buildTree(data: MenuItem[], min: number, max: number): MenuItem[] {
201+
resolvedHeaders.length = 0
202+
203+
const result: MenuItem[] = []
204+
const stack: (MenuItem | { level: number; shouldIgnore: true })[] = []
205+
206+
data.forEach((item) => {
207+
const node = { ...item, children: [] }
208+
let parent = stack[stack.length - 1]
209+
210+
while (parent && parent.level >= node.level) {
211+
stack.pop()
212+
parent = stack[stack.length - 1]
213+
}
214+
215+
if (
216+
node.element.classList.contains('ignore-header') ||
217+
(parent && 'shouldIgnore' in parent)
218+
) {
219+
stack.push({ level: node.level, shouldIgnore: true })
220+
return
221+
}
222+
223+
if (node.level > max || node.level < min) return
224+
resolvedHeaders.push({ element: node.element, link: node.link })
225+
226+
if (parent) parent.children!.push(node)
227+
else result.push(node)
228+
229+
stack.push(node)
230+
})
231+
232+
return result
233+
}

0 commit comments

Comments
 (0)