forked from davidflanagan/jstdg7
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TOC.js
110 lines (98 loc) · 4.44 KB
/
TOC.js
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
/**
* TOC.js: create a table of contents for a document.
*
* This script runs when the DOMContentLoaded event is fired and
* automatically generates a table of contents for the document.
* It does not define any global symbols so it should not conflict
* with other scripts.
*
* When this script runs, it first looks for a document element with
* an id of "TOC". If there is no such element it creates one at the
* start of the document. Next, the function finds all <h2> through
* <h6> tags, treats them as section titles, and creates a table of
* contents within the TOC element. The function adds section numbers
* to each section heading and wraps the headings in named anchors so
* that the TOC can link to them. The generated anchors have names
* that begin with "TOC", so you should avoid this prefix in your own
* HTML.
*
* The entries in the generated TOC can be styled with CSS. All
* entries have a class "TOCEntry". Entries also have a class that
* corresponds to the level of the section heading. <h1> tags generate
* entries of class "TOCLevel1", <h2> tags generate entries of class
* "TOCLevel2", and so on. Section numbers inserted into headings have
* class "TOCSectNum".
*
* You might use this script with a stylesheet like this:
*
* #TOC { border: solid black 1px; margin: 10px; padding: 10px; }
* .TOCEntry { margin: 5px 0px; }
* .TOCEntry a { text-decoration: none; }
* .TOCLevel1 { font-size: 16pt; font-weight: bold; }
* .TOCLevel2 { font-size: 14pt; margin-left: .25in; }
* .TOCLevel3 { font-size: 12pt; margin-left: .5in; }
* .TOCSectNum:after { content: ": "; }
*
* To hide the section numbers, use this:
*
* .TOCSectNum { display: none }
**/
document.addEventListener("DOMContentLoaded", () => {
// Find the TOC container element.
// If there isn't one, create one at the start of the document.
let toc = document.querySelector("#TOC");
if (!toc) {
toc = document.createElement("div");
toc.id = "TOC";
document.body.prepend(toc);
}
// Find all section heading elements. We're assuming here that the
// document title uses <h1> and that sections within the document are
// marked with <h2> through <h6>.
let headings = document.querySelectorAll("h2,h3,h4,h5,h6");
// Initialize an array that keeps track of section numbers.
let sectionNumbers = [0,0,0,0,0];
// Now loop through the section header elements we found.
for(let heading of headings) {
// Skip the heading if it is inside the TOC container.
if (heading.parentNode === toc) {
continue;
}
// Figure out what level heading it is.
// Subtract 1 because <h2> is a level-1 heading.
let level = parseInt(heading.tagName.charAt(1)) - 1;
// Increment the section number for this heading level
// and reset all lower heading level numbers to zero.
sectionNumbers[level-1]++;
for(let i = level; i < sectionNumbers.length; i++) {
sectionNumbers[i] = 0;
}
// Now combine section numbers for all heading levels
// to produce a section number like 2.3.1.
let sectionNumber = sectionNumbers.slice(0, level).join(".");
// Add the section number to the section header title.
// We place the number in a <span> to make it styleable.
let span = document.createElement("span");
span.className = "TOCSectNum";
span.textContent = sectionNumber;
heading.prepend(span);
// Wrap the heading in a named anchor so we can link to it.
let anchor = document.createElement("a");
let fragmentName = `TOC${sectionNumber}`;
anchor.name = fragmentName;
heading.before(anchor); // Insert anchor before heading
anchor.append(heading); // and move heading inside anchor
// Now create a link to this section.
let link = document.createElement("a");
link.href = `#${fragmentName}`; // Link destination
// Copy the heading text into the link. This is a safe use of
// innerHTML because we are not inserting any untrusted strings.
link.innerHTML = heading.innerHTML;
// Place the link in a div that is styleable based on the level.
let entry = document.createElement("div");
entry.classList.add("TOCEntry", `TOCLevel${level}`);
entry.append(link);
// And add the div to the TOC container.
toc.append(entry);
}
});