generated from JS-DevTools/template-node-typescript
-
Notifications
You must be signed in to change notification settings - Fork 18
/
Copy pathcreate-toc.ts
149 lines (134 loc) · 3.96 KB
/
create-toc.ts
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import { getInnerText } from "./get-inner-text";
import { buildClass, NormalizedOptions } from "./options";
import { HeadingNode, HtmlElementNode, ListItemNode, ListNode } from "./types";
interface TocLevel {
depth: number;
headingNumber: number;
list: ListNode;
}
/**
* Creates a `<nav>` and/or `<ol>` element containing the table of contents.
*/
export function createTOC(headings: HeadingNode[], options: NormalizedOptions): HtmlElementNode {
let list = createTocList(headings, options);
if (options.nav) {
return {
type: "element",
tagName: "nav",
properties: {
className: options.cssClasses.toc || undefined,
},
children: [list],
};
}
else {
list.properties.className =
[options.cssClasses.toc, list.properties.className].filter(Boolean).join(" ") || undefined;
return list;
}
}
/**
* Creates an `<ol>` element containing the table of contents.
*/
function createTocList(headings: HeadingNode[], options: NormalizedOptions): HtmlElementNode {
let levels: TocLevel[] = [];
let currentLevel: TocLevel = {
depth: 0,
headingNumber: 0,
list: undefined as unknown as ListNode,
};
for (let heading of headings) {
let headingNumber = parseInt(heading.tagName.slice(-1), 10);
if (headingNumber > currentLevel.headingNumber) {
// This is a higher heading number, so start a new level
let depth = currentLevel.depth + 1;
let level = {
depth,
headingNumber,
list: createList(heading, depth, options),
};
// Add the new list to the previous level's list
if (currentLevel.list) {
let lastItem = currentLevel.list.children.slice(-1)[0];
lastItem.children.push(level.list);
}
levels.push(level);
currentLevel = level;
}
else {
if (headingNumber < currentLevel.headingNumber) {
// This is a lower heading number, so we need to go up to a previous level
for (let i = levels.length - 2; i >= 0; i--) {
let level = levels[i];
if (level.headingNumber === headingNumber) {
// We found the previous level that matches this heading
levels = levels.slice(0, i + 1);
currentLevel = level;
break;
}
}
// If headings are in an incorrect order, then we may need to adjust the headingNumber
currentLevel.headingNumber = Math.min(currentLevel.headingNumber, headingNumber);
}
// This heading is the same level as the previous heading,
// so just add another <li> to the same <ol>
let listItem = createListItem(heading, options);
currentLevel.list.children.push(listItem);
}
}
if (levels.length === 0) {
return createList(undefined, 1, options);
}
else {
return levels[0].list;
}
}
/**
* Creates an `<ol>` and `<li>` element for the given heading
*/
function createList(heading: HeadingNode | undefined, depth: number, options: NormalizedOptions): ListNode {
let list: ListNode = {
type: "element",
tagName: "ol",
properties: {
className: buildClass(options.cssClasses.list, depth),
},
children: [],
};
if (heading) {
let listItem = createListItem(heading, options);
list.children.push(listItem);
}
return list;
}
/**
* Creates an `<li>` element for the given heading
*/
function createListItem(heading: HeadingNode, options: NormalizedOptions): ListItemNode {
return {
type: "element",
tagName: "li",
data: {
hookArgs: [heading],
},
properties: {
className: buildClass(options.cssClasses.listItem, heading.tagName),
},
children: [
{
type: "element",
tagName: "a",
properties: {
className: buildClass(options.cssClasses.link, heading.tagName),
href: `#${heading.properties.id || ""}`,
},
children: [
{
type: "text",
value: getInnerText(heading),
}
]
}
],
};
}