-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathshowdown-toc.js
157 lines (136 loc) · 4.92 KB
/
showdown-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
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
150
151
152
153
154
155
156
(function(){
var toc = function(converter) {
return [
{
type: 'output',
filter: function(source) {
var elements = $(source);
var output = [];
var headingLevel = null;
var tocId = null;
for (var i=0; i<elements.length; i++) {
var element = $(elements[i]);
var results = null;
// Does the element consist only of [toc]?
// If so, we can replace this element with out list.
if (element.text().trim()=='[toc]') {
element = $('<ol>',{'class':'showdown-toc'});
headingLevel = null;
tocId = output.length;
}
// Does this item contain a [toc] with other stuff?
// If so, we'll split the element into two
else if (results = element.text().trim().match(/^([\s\S]*?)((?:\\)?\[toc\])([\s\S]*)$/)) {
// If there was a \ before the [toc] they're trying to escape it,
// so return the [toc] string without the \ and carry on. For
// some reason (I'm guessing a bug in showdown) you actually
// appear to need two \ (\\) in order to get this to show up for
// the filter. Leaving this code here anyway for now because it's
// "the right thing to do"(tm).
if (results[2][0]=='\\') {
element.text(results[1]+results[2].substr(1)+results[3]);
}
// Otherwise start building a new table of contents.
else {
var before = null;
var after = null;
// Create two of the same element.
if (element.prop('tagName')) {
if (results[1].trim().length>0) {
before = $('<'+element.prop('tagName')+'>').text(results[1]);
}
if (results[3].trim().length>0) {
after = $('<'+element.prop('tagName')+'>').text(results[3]);
}
}
// Otherwise if there's no tagName assume it's a text node
// and create two of those.
else {
if (results[1].trim().length>0) {
before = document.createTextNode(results[1]);
}
if (results[3].trim().length>0) {
after = document.createTextNode(results[3]);
}
}
// Our new table of contents container.
toc = $('<ol>',{'class':'showdown-toc'});
// If there was text before our [toc], add that in
if (before) {
output.push(before);
}
// Keep track of where our current table is in the elements array.
tocId = output.length;
// If there was text after, push the contents onto the array and
// use the after part as our current element.
if (after) {
output.push(toc);
element = after;
}
// Otherwise use the contents as the current element.
else {
element = toc;
}
// Reset the heading level - we're going to start looking for new
// headings again
headingLevel = null;
}
}
// If we've started a table of contents, but have nothing in it yet,
// look for the first header tag we encounter (after the [toc]).
// That's going to be what we use as contents entries for this table
// of contents.
else if (tocId && !headingLevel && element.prop("tagName")) {
switch (element.prop("tagName")) {
case 'H1':
case 'H2':
case 'H3':
case 'H4':
case 'H5':
case 'H6':
headingLevel = parseInt(element.prop('tagName').substr(1));
break;
}
}
// If we know what header level we're looking for (either we just
// found it above, or we're continuing to look for more) then check to
// see if this heading should be added to the contents.
if (tocId && headingLevel) {
switch (element.prop('tagName')) {
case 'H1':
case 'H2':
case 'H3':
case 'H4':
case 'H5':
case 'H6':
var thisLevel = parseInt(element.prop('tagName').substr(1));
if (thisLevel==headingLevel) {
output[tocId] = $(output[tocId]).append($('<li>').append($('<a>',{href:'#'+element.attr('id'),text:element.text()})));
}
// If we move up in what would be the document tree
// (eg: if we're looking for H2 and we suddenly find an
// H1) then we can probably safely assume that we want
// the table of contents to end for this section.
else if (thisLevel<headingLevel) {
toc = null
tocId = null;
headingLevel = null;
}
break;
}
}
// Push whatever element we've been looking at onto the output array.
output.push(element);
}
// Build some HTML to return
// Return it.
return $('<div>').append(output).html();
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.Showdown && window.Showdown.extensions) { window.Showdown.extensions.toc = toc; }
// Server-side export
if (typeof module !== 'undefined') module.exports = toc;
}());