forked from nushell/nushell.github.io
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathReleaseToc.vue
More file actions
95 lines (76 loc) · 2.53 KB
/
ReleaseToc.vue
File metadata and controls
95 lines (76 loc) · 2.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<template>
<NestedList :items="items"></NestedList>
</template>
<script setup lang="ts">
import NestedList, { Item } from './NestedList.vue';
import { ref, onMounted, nextTick } from 'vue';
import { onContentUpdated } from 'vuepress/client';
const items = ref([] as Item[]);
function getHeaders(): NodeListOf<HTMLHeadingElement> {
return document.querySelectorAll('h1, h2, h3');
}
type OrganizedHeader = {
element?: HTMLHeadingElement;
children: OrganizedHeader[];
};
function organizeHeaders(
headers: HTMLHeadingElement[],
index: [number] = [0],
level: number = 1,
): OrganizedHeader {
const node: OrganizedHeader = { children: [] };
while (index[0] < headers.length) {
const header = headers[index[0]];
const headerLevel = Number(header.tagName.slice(1)); // "H2" -> 2
// if we hit a header above our current level, we are done here
if (headerLevel < level) break;
// if we hit a header deeper than expected, let the caller handle it as children
if (headerLevel > level) break;
// headerLevel === level: consume this header and attach its children.
index[0]++;
const entry: OrganizedHeader = { element: header, children: [] };
// children are the following headers with level+1 (and their descendants)
const childrenTree = organizeHeaders(headers, index, level + 1);
entry.children = childrenTree.children;
node.children.push(entry);
}
return node;
}
function filterHeaders(root: OrganizedHeader): OrganizedHeader {
const wanted = [
'Highlights and themes of this release',
'Changes',
'Notes for plugin developers',
'Hall of fame',
'Full changelog',
].map((section) => section.toLowerCase());
return {
...root,
children: root.children.filter((section) =>
wanted.some((wanted) =>
section.element!.innerText.toLowerCase().startsWith(wanted),
),
),
};
}
function generateItem(header: OrganizedHeader): Item {
let slug = '#' + header.element?.id;
let content = header.element?.querySelector('span')?.innerHTML;
const i = content?.lastIndexOf('[');
if (i !== -1) content = content?.slice(0, i).trim();
return {
label: `<em><a href="${slug}">${content}</a></em>`,
children: header.children.map(generateItem),
};
}
async function refresh() {
await nextTick();
const headers = Array.from(getHeaders());
const organized = organizeHeaders(headers);
const filtered = filterHeaders(organized);
const item = generateItem(filtered);
items.value = item.children;
}
onMounted(refresh);
onContentUpdated(refresh);
</script>