forked from cmaas/markdown-it-table-of-contents
-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
123 lines (109 loc) · 3.37 KB
/
index.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
"use strict";
var string = require("string");
var assign = require("lodash.assign");
var defaults = {
includeLevel: [ 1, 2 ],
containerClass: "table-of-contents",
slugify: function(str) {
return string(str).slugify().toString();
}
};
module.exports = function(md, options) {
var options = assign({}, defaults, options);
var tocRegexp = /^\[\[toc\]\]/im;
var gstate;
function toc(state, silent) {
var token;
var match;
while (state.src.indexOf("\n") >= 0 && state.src.indexOf("\n") < state.src.indexOf("[[toc]]")) {
if (state.tokens.slice(-1)[0].type === "softbreak") {
state.src = state.src.split("\n").slice(1).join("\n");
state.pos = 0;
}
}
// Reject if the token does not start with [
if (state.src.charCodeAt(state.pos) !== 0x5B /* [ */ ) {
return false;
}
// Don't run any pairs in validation mode
if (silent) {
return false;
}
// Detect TOC markdown
match = tocRegexp.exec(state.src);
match = !match ? [] : match.filter(function(m) { return m; });
if (match.length < 1) {
return false;
}
// Build content
token = state.push("toc_open", "toc", 1);
token.markup = "[[toc]]";
token = state.push("toc_body", "", 0);
token = state.push("toc_close", "toc", -1);
// Update pos so the parser can continue
var newline = state.src.indexOf("\n");
if (newline !== -1) {
state.pos = state.pos + newline;
} else {
state.pos = state.pos + state.posMax + 1;
}
return true;
}
md.renderer.rules.toc_open = function(tokens, index) {
return "<div class=\"table-of-contents\">";
};
md.renderer.rules.toc_close = function(tokens, index) {
return "</div>";
};
md.renderer.rules.toc_body = function(tokens, index) {
return renderChildsTokens(0, gstate.tokens)[1];
};
function renderChildsTokens(pos, tokens) {
var headings = [],
buffer = '',
currentLevel,
subHeadings,
size = tokens.length,
i = pos;
while(i < size) {
var token = tokens[i];
var heading = tokens[i - 1];
var level = parseInt(token.tag.substr(1, 1));
if (token.type !== "heading_close" || options.includeLevel.indexOf(level) == -1 || heading.type !== "inline") {
i++; continue; // Skip if not matching criteria
}
if (!currentLevel) {
currentLevel = level;// We init with the first found level
} else {
if (level > currentLevel) {
subHeadings = renderChildsTokens(i, tokens);
buffer += subHeadings[1];
i = subHeadings[0];
continue;
}
if (level < currentLevel) {
// Finishing the sub headings
buffer += "</li>";
headings.push(buffer);
return [i, "<ul>" + headings.join("") + "</ul>"];
}
if (level == currentLevel) {
// Finishing the sub headings
buffer += "</li>";
headings.push(buffer);
}
}
buffer = "<li><a href=\"#" + options.slugify(heading.content) + "\">" + heading.content + "</a>";
i++;
}
buffer += "</li>";
headings.push(buffer);
return [i, "<ul>" + headings.join("") + "</ul>"];
}
// Catch all the tokens for iteration later
md.core.ruler.push("grab_state", function(state) {
gstate = state;
});
// Insert TOC
md.inline.ruler.after("emphasis", "toc", toc);
};