forked from jdsteinbach/eleventy-plugin-toc
-
Notifications
You must be signed in to change notification settings - Fork 5
/
toc.js
115 lines (95 loc) · 2.83 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
111
112
113
114
115
const cheerio = require('cheerio');
/** Attribute which if found on a heading means the heading is excluded */
const ignoreAttribute = 'data-toc-exclude';
const defaults = {
tags: ['h2', 'h3', 'h4'],
ignoredElements: [],
wrapper: 'nav',
wrapperClass: 'toc',
headingText: '',
headingTag: 'h2'
};
function getParent(prev, current) {
if (current.level > prev.level) {
//child heading
return prev;
} else if (current.level === prev.level) {
//sibling of previous
return prev.parent;
} else {
//above the previous
return getParent(prev.parent, current);
}
}
class Item {
constructor($el) {
if ($el) {
this.slug = $el.attr('id');
this.text = $el.text().trim();
this.level = +$el.get(0).tagName.slice(1);
} else {
this.level = 0;
}
this.children = [];
}
html() {
let markup = '';
if (this.slug && this.text) {
markup += `
<li><a href="#${this.slug}">${this.text}</a>
`;
}
if (this.children.length > 0) {
markup += `
<ol>
${this.children.map(item => item.html()).join('\n')}
</ol>
`;
}
if (this.slug && this.text) {
markup += '\t\t</li>'
}
return markup;
}
}
class Toc {
constructor(htmlstring = '', options = defaults) {
this.options = {...defaults, ...options};
const selector = this.options.tags.join(',');
this.root = new Item();
this.root.parent = this.root;
const $ = cheerio.load(htmlstring);
const headings = $(selector)
.filter('[id]')
.filter(`:not([${ignoreAttribute}])`);
const ignoredElementsSelector = this.options.ignoredElements.join(',')
headings.find(ignoredElementsSelector).remove()
if (headings.length) {
let previous = this.root;
headings.each((index, heading) => {
const current = new Item($(heading));
const parent = getParent(previous, current);
current.parent = parent;
parent.children.push(current);
previous = current;
})
}
}
get() {
return this.root;
}
html() {
const {wrapper, wrapperClass, headingText, headingTag} = this.options;
const root = this.get();
let html = '';
if (root.children.length) {
if (headingText) {
html += `<${headingTag}>${headingText}</${headingTag}>\n`;
}
html += `<${wrapper} class="${wrapperClass}">${root.html()}</${wrapper}>`;
}
return html;
}
}
module.exports = Toc;
module.exports.Item = Item;