1 |
|
2 |
|
3 | 'use strict';
|
4 |
|
5 |
|
6 |
|
7 |
|
8 | function render_footnote_anchor_name(tokens, idx, options, env/*, slf*/) {
|
9 | var n = Number(tokens[idx].meta.id + 1).toString();
|
10 |
|
11 | if (tokens[idx].meta.subId > 0) {
|
12 | n += ':' + tokens[idx].meta.subId;
|
13 | }
|
14 |
|
15 | var prefix = '';
|
16 |
|
17 | if (typeof env.docId === 'string') {
|
18 | prefix = '-' + env.docId + '-';
|
19 | }
|
20 |
|
21 | return prefix + n;
|
22 | }
|
23 |
|
24 | function render_footnote_caption(tokens, idx/*, options, env, slf*/) {
|
25 | var n = Number(tokens[idx].meta.id + 1).toString();
|
26 |
|
27 | if (tokens[idx].meta.subId > 0) {
|
28 | n += ':' + tokens[idx].meta.subId;
|
29 | }
|
30 |
|
31 | return '[' + n + ']';
|
32 | }
|
33 |
|
34 | function render_footnote_ref(tokens, idx, options, env, slf) {
|
35 | var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf);
|
36 | var caption = slf.rules.footnote_caption(tokens, idx, options, env, slf);
|
37 |
|
38 | return '<sup class="footnote-ref"><a href="#fn' + id + '" id="fnref' + id + '">' + caption + '</a></sup>';
|
39 | }
|
40 |
|
41 | function render_footnote_block_open(tokens, idx, options) {
|
42 | return (options.xhtmlOut ? '<hr class="footnotes-sep" />\n' : '<hr class="footnotes-sep">\n') +
|
43 | '<section class="footnotes">\n' +
|
44 | '<ol class="footnotes-list">\n';
|
45 | }
|
46 |
|
47 | function render_footnote_block_close() {
|
48 | return '</ol>\n</section>\n';
|
49 | }
|
50 |
|
51 | function render_footnote_open(tokens, idx, options, env, slf) {
|
52 | var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf);
|
53 |
|
54 | return '<li id="fn' + id + '" class="footnote-item">';
|
55 | }
|
56 |
|
57 | function render_footnote_close() {
|
58 | return '</li>\n';
|
59 | }
|
60 |
|
61 | function render_footnote_anchor(tokens, idx, options, env, slf) {
|
62 | var id = slf.rules.footnote_anchor_name(tokens, idx, options, env, slf);
|
63 |
|
64 |
|
65 | return ' <a href="#fnref' + id + '" class="footnote-backref">\u21a9\uFE0E</a>';
|
66 | }
|
67 |
|
68 |
|
69 | module.exports = function footnote_plugin(md) {
|
70 | var parseLinkLabel = md.helpers.parseLinkLabel,
|
71 | isSpace = md.utils.isSpace;
|
72 |
|
73 | md.renderer.rules.footnote_ref = render_footnote_ref;
|
74 | md.renderer.rules.footnote_block_open = render_footnote_block_open;
|
75 | md.renderer.rules.footnote_block_close = render_footnote_block_close;
|
76 | md.renderer.rules.footnote_open = render_footnote_open;
|
77 | md.renderer.rules.footnote_close = render_footnote_close;
|
78 | md.renderer.rules.footnote_anchor = render_footnote_anchor;
|
79 |
|
80 |
|
81 | md.renderer.rules.footnote_caption = render_footnote_caption;
|
82 | md.renderer.rules.footnote_anchor_name = render_footnote_anchor_name;
|
83 |
|
84 |
|
85 | function footnote_def(state, startLine, endLine, silent) {
|
86 | var oldBMark, oldTShift, oldSCount, oldParentType, pos, label, token,
|
87 | initial, offset, ch, posAfterColon,
|
88 | start = state.bMarks[startLine] + state.tShift[startLine],
|
89 | max = state.eMarks[startLine];
|
90 |
|
91 |
|
92 | if (start + 4 > max) { return false; }
|
93 |
|
94 | if (state.src.charCodeAt(start) !== 0x5B) { return false; }
|
95 | if (state.src.charCodeAt(start + 1) !== 0x5E) { return false; }
|
96 |
|
97 | for (pos = start + 2; pos < max; pos++) {
|
98 | if (state.src.charCodeAt(pos) === 0x20) { return false; }
|
99 | if (state.src.charCodeAt(pos) === 0x5D ) {
|
100 | break;
|
101 | }
|
102 | }
|
103 |
|
104 | if (pos === start + 2) { return false; }
|
105 | if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A ) { return false; }
|
106 | if (silent) { return true; }
|
107 | pos++;
|
108 |
|
109 | if (!state.env.footnotes) { state.env.footnotes = {}; }
|
110 | if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; }
|
111 | label = state.src.slice(start + 2, pos - 2);
|
112 | state.env.footnotes.refs[':' + label] = -1;
|
113 |
|
114 | token = new state.Token('footnote_reference_open', '', 1);
|
115 | token.meta = { label: label };
|
116 | token.level = state.level++;
|
117 | state.tokens.push(token);
|
118 |
|
119 | oldBMark = state.bMarks[startLine];
|
120 | oldTShift = state.tShift[startLine];
|
121 | oldSCount = state.sCount[startLine];
|
122 | oldParentType = state.parentType;
|
123 |
|
124 | posAfterColon = pos;
|
125 | initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]);
|
126 |
|
127 | while (pos < max) {
|
128 | ch = state.src.charCodeAt(pos);
|
129 |
|
130 | if (isSpace(ch)) {
|
131 | if (ch === 0x09) {
|
132 | offset += 4 - offset % 4;
|
133 | } else {
|
134 | offset++;
|
135 | }
|
136 | } else {
|
137 | break;
|
138 | }
|
139 |
|
140 | pos++;
|
141 | }
|
142 |
|
143 | state.tShift[startLine] = pos - posAfterColon;
|
144 | state.sCount[startLine] = offset - initial;
|
145 |
|
146 | state.bMarks[startLine] = posAfterColon;
|
147 | state.blkIndent += 4;
|
148 | state.parentType = 'footnote';
|
149 |
|
150 | if (state.sCount[startLine] < state.blkIndent) {
|
151 | state.sCount[startLine] += state.blkIndent;
|
152 | }
|
153 |
|
154 | state.md.block.tokenize(state, startLine, endLine, true);
|
155 |
|
156 | state.parentType = oldParentType;
|
157 | state.blkIndent -= 4;
|
158 | state.tShift[startLine] = oldTShift;
|
159 | state.sCount[startLine] = oldSCount;
|
160 | state.bMarks[startLine] = oldBMark;
|
161 |
|
162 | token = new state.Token('footnote_reference_close', '', -1);
|
163 | token.level = --state.level;
|
164 | state.tokens.push(token);
|
165 |
|
166 | return true;
|
167 | }
|
168 |
|
169 |
|
170 | function footnote_inline(state, silent) {
|
171 | var labelStart,
|
172 | labelEnd,
|
173 | footnoteId,
|
174 | token,
|
175 | tokens,
|
176 | max = state.posMax,
|
177 | start = state.pos;
|
178 |
|
179 | if (start + 2 >= max) { return false; }
|
180 | if (state.src.charCodeAt(start) !== 0x5E) { return false; }
|
181 | if (state.src.charCodeAt(start + 1) !== 0x5B) { return false; }
|
182 |
|
183 | labelStart = start + 2;
|
184 | labelEnd = parseLinkLabel(state, start + 1);
|
185 |
|
186 |
|
187 | if (labelEnd < 0) { return false; }
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | if (!silent) {
|
193 | if (!state.env.footnotes) { state.env.footnotes = {}; }
|
194 | if (!state.env.footnotes.list) { state.env.footnotes.list = []; }
|
195 | footnoteId = state.env.footnotes.list.length;
|
196 |
|
197 | state.md.inline.parse(
|
198 | state.src.slice(labelStart, labelEnd),
|
199 | state.md,
|
200 | state.env,
|
201 | tokens = []
|
202 | );
|
203 |
|
204 | token = state.push('footnote_ref', '', 0);
|
205 | token.meta = { id: footnoteId };
|
206 |
|
207 | state.env.footnotes.list[footnoteId] = { tokens: tokens };
|
208 | }
|
209 |
|
210 | state.pos = labelEnd + 1;
|
211 | state.posMax = max;
|
212 | return true;
|
213 | }
|
214 |
|
215 |
|
216 | function footnote_ref(state, silent) {
|
217 | var label,
|
218 | pos,
|
219 | footnoteId,
|
220 | footnoteSubId,
|
221 | token,
|
222 | max = state.posMax,
|
223 | start = state.pos;
|
224 |
|
225 |
|
226 | if (start + 3 > max) { return false; }
|
227 |
|
228 | if (!state.env.footnotes || !state.env.footnotes.refs) { return false; }
|
229 | if (state.src.charCodeAt(start) !== 0x5B) { return false; }
|
230 | if (state.src.charCodeAt(start + 1) !== 0x5E) { return false; }
|
231 |
|
232 | for (pos = start + 2; pos < max; pos++) {
|
233 | if (state.src.charCodeAt(pos) === 0x20) { return false; }
|
234 | if (state.src.charCodeAt(pos) === 0x0A) { return false; }
|
235 | if (state.src.charCodeAt(pos) === 0x5D ) {
|
236 | break;
|
237 | }
|
238 | }
|
239 |
|
240 | if (pos === start + 2) { return false; }
|
241 | if (pos >= max) { return false; }
|
242 | pos++;
|
243 |
|
244 | label = state.src.slice(start + 2, pos - 1);
|
245 | if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; }
|
246 |
|
247 | if (!silent) {
|
248 | if (!state.env.footnotes.list) { state.env.footnotes.list = []; }
|
249 |
|
250 | if (state.env.footnotes.refs[':' + label] < 0) {
|
251 | footnoteId = state.env.footnotes.list.length;
|
252 | state.env.footnotes.list[footnoteId] = { label: label, count: 0 };
|
253 | state.env.footnotes.refs[':' + label] = footnoteId;
|
254 | } else {
|
255 | footnoteId = state.env.footnotes.refs[':' + label];
|
256 | }
|
257 |
|
258 | footnoteSubId = state.env.footnotes.list[footnoteId].count;
|
259 | state.env.footnotes.list[footnoteId].count++;
|
260 |
|
261 | token = state.push('footnote_ref', '', 0);
|
262 | token.meta = { id: footnoteId, subId: footnoteSubId, label: label };
|
263 | }
|
264 |
|
265 | state.pos = pos;
|
266 | state.posMax = max;
|
267 | return true;
|
268 | }
|
269 |
|
270 |
|
271 | function footnote_tail(state) {
|
272 | var i, l, j, t, lastParagraph, list, token, tokens, current, currentLabel,
|
273 | insideRef = false,
|
274 | refTokens = {};
|
275 |
|
276 | if (!state.env.footnotes) { return; }
|
277 |
|
278 | state.tokens = state.tokens.filter(function (tok) {
|
279 | if (tok.type === 'footnote_reference_open') {
|
280 | insideRef = true;
|
281 | current = [];
|
282 | currentLabel = tok.meta.label;
|
283 | return false;
|
284 | }
|
285 | if (tok.type === 'footnote_reference_close') {
|
286 | insideRef = false;
|
287 |
|
288 | refTokens[':' + currentLabel] = current;
|
289 | return false;
|
290 | }
|
291 | if (insideRef) { current.push(tok); }
|
292 | return !insideRef;
|
293 | });
|
294 |
|
295 | if (!state.env.footnotes.list) { return; }
|
296 | list = state.env.footnotes.list;
|
297 |
|
298 | token = new state.Token('footnote_block_open', '', 1);
|
299 | state.tokens.push(token);
|
300 |
|
301 | for (i = 0, l = list.length; i < l; i++) {
|
302 | token = new state.Token('footnote_open', '', 1);
|
303 | token.meta = { id: i, label: list[i].label };
|
304 | state.tokens.push(token);
|
305 |
|
306 | if (list[i].tokens) {
|
307 | tokens = [];
|
308 |
|
309 | token = new state.Token('paragraph_open', 'p', 1);
|
310 | token.block = true;
|
311 | tokens.push(token);
|
312 |
|
313 | token = new state.Token('inline', '', 0);
|
314 | token.children = list[i].tokens;
|
315 | token.content = '';
|
316 | tokens.push(token);
|
317 |
|
318 | token = new state.Token('paragraph_close', 'p', -1);
|
319 | token.block = true;
|
320 | tokens.push(token);
|
321 |
|
322 | } else if (list[i].label) {
|
323 | tokens = refTokens[':' + list[i].label];
|
324 | }
|
325 |
|
326 | state.tokens = state.tokens.concat(tokens);
|
327 | if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') {
|
328 | lastParagraph = state.tokens.pop();
|
329 | } else {
|
330 | lastParagraph = null;
|
331 | }
|
332 |
|
333 | t = list[i].count > 0 ? list[i].count : 1;
|
334 | for (j = 0; j < t; j++) {
|
335 | token = new state.Token('footnote_anchor', '', 0);
|
336 | token.meta = { id: i, subId: j, label: list[i].label };
|
337 | state.tokens.push(token);
|
338 | }
|
339 |
|
340 | if (lastParagraph) {
|
341 | state.tokens.push(lastParagraph);
|
342 | }
|
343 |
|
344 | token = new state.Token('footnote_close', '', -1);
|
345 | state.tokens.push(token);
|
346 | }
|
347 |
|
348 | token = new state.Token('footnote_block_close', '', -1);
|
349 | state.tokens.push(token);
|
350 | }
|
351 |
|
352 | md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: [ 'paragraph', 'reference' ] });
|
353 | md.inline.ruler.after('image', 'footnote_inline', footnote_inline);
|
354 | md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref);
|
355 | md.core.ruler.after('inline', 'footnote_tail', footnote_tail);
|
356 | };
|