// Process footnotes // 'use strict'; //////////////////////////////////////////////////////////////////////////////// // Renderer partials function _footnote_ref(tokens, idx) { var n = Number(tokens[idx].meta.id + 1).toString(); var id = 'fnref' + n; if (tokens[idx].meta.subId > 0) { id += ':' + tokens[idx].meta.subId; } return '[' + n + ']'; } function _footnote_block_open(tokens, idx, options) { return (options.xhtmlOut ? '
\n' : '
\n') + '
\n' + '
    \n'; } function _footnote_block_close() { return '
\n
\n'; } function _footnote_open(tokens, idx) { var id = Number(tokens[idx].meta.id + 1).toString(); return '
  • '; } function _footnote_close() { return '
  • \n'; } function _footnote_anchor(tokens, idx) { var n = Number(tokens[idx].meta.id + 1).toString(); var id = 'fnref' + n; if (tokens[idx].meta.subId > 0) { id += ':' + tokens[idx].meta.subId; } return ' \u21a9'; /* ↩ */ } //////////////////////////////////////////////////////////////////////////////// module.exports = function sub_plugin(md) { var parseLinkLabel = md.helpers.parseLinkLabel, isSpace = md.utils.isSpace; md.renderer.rules.footnote_ref = _footnote_ref; md.renderer.rules.footnote_block_open = _footnote_block_open; md.renderer.rules.footnote_block_close = _footnote_block_close; md.renderer.rules.footnote_open = _footnote_open; md.renderer.rules.footnote_close = _footnote_close; md.renderer.rules.footnote_anchor = _footnote_anchor; // Process footnote block definition function footnote_def(state, startLine, endLine, silent) { var oldBMark, oldTShift, oldSCount, oldParentType, pos, label, token, initial, offset, ch, posAfterColon, start = state.bMarks[startLine] + state.tShift[startLine], max = state.eMarks[startLine]; // line should be at least 5 chars - "[^x]:" if (start + 4 > max) { return false; } if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } for (pos = start + 2; pos < max; pos++) { if (state.src.charCodeAt(pos) === 0x20) { return false; } if (state.src.charCodeAt(pos) === 0x5D /* ] */) { break; } } if (pos === start + 2) { return false; } // no empty footnote labels if (pos + 1 >= max || state.src.charCodeAt(++pos) !== 0x3A /* : */) { return false; } if (silent) { return true; } pos++; if (!state.env.footnotes) { state.env.footnotes = {}; } if (!state.env.footnotes.refs) { state.env.footnotes.refs = {}; } label = state.src.slice(start + 2, pos - 2); state.env.footnotes.refs[':' + label] = -1; token = new state.Token('footnote_reference_open', '', 1); token.meta = { label: label }; token.level = state.level++; state.tokens.push(token); oldBMark = state.bMarks[startLine]; oldTShift = state.tShift[startLine]; oldSCount = state.sCount[startLine]; oldParentType = state.parentType; posAfterColon = pos; initial = offset = state.sCount[startLine] + pos - (state.bMarks[startLine] + state.tShift[startLine]); while (pos < max) { ch = state.src.charCodeAt(pos); if (isSpace(ch)) { if (ch === 0x09) { offset += 4 - offset % 4; } else { offset++; } } else { break; } pos++; } state.tShift[startLine] = pos - posAfterColon; state.sCount[startLine] = offset - initial; state.bMarks[startLine] = posAfterColon; state.blkIndent += 4; state.parentType = 'footnote'; if (state.sCount[startLine] < state.blkIndent) { state.sCount[startLine] += state.blkIndent; } state.md.block.tokenize(state, startLine, endLine, true); state.parentType = oldParentType; state.blkIndent -= 4; state.tShift[startLine] = oldTShift; state.sCount[startLine] = oldSCount; state.bMarks[startLine] = oldBMark; token = new state.Token('footnote_reference_close', '', -1); token.level = --state.level; state.tokens.push(token); return true; } // Process inline footnotes (^[...]) function footnote_inline(state, silent) { var labelStart, labelEnd, footnoteId, token, tokens, max = state.posMax, start = state.pos; if (start + 2 >= max) { return false; } if (state.src.charCodeAt(start) !== 0x5E/* ^ */) { return false; } if (state.src.charCodeAt(start + 1) !== 0x5B/* [ */) { return false; } labelStart = start + 2; labelEnd = parseLinkLabel(state, start + 1); // parser failed to find ']', so it's not a valid note if (labelEnd < 0) { return false; } // We found the end of the link, and know for a fact it's a valid link; // so all that's left to do is to call tokenizer. // if (!silent) { if (!state.env.footnotes) { state.env.footnotes = {}; } if (!state.env.footnotes.list) { state.env.footnotes.list = []; } footnoteId = state.env.footnotes.list.length; state.md.inline.parse( state.src.slice(labelStart, labelEnd), state.md, state.env, tokens = [] ); token = state.push('footnote_ref', '', 0); token.meta = { id: footnoteId }; state.env.footnotes.list[footnoteId] = { tokens: tokens }; } state.pos = labelEnd + 1; state.posMax = max; return true; } // Process footnote references ([^...]) function footnote_ref(state, silent) { var label, pos, footnoteId, footnoteSubId, token, max = state.posMax, start = state.pos; // should be at least 4 chars - "[^x]" if (start + 3 > max) { return false; } if (!state.env.footnotes || !state.env.footnotes.refs) { return false; } if (state.src.charCodeAt(start) !== 0x5B/* [ */) { return false; } if (state.src.charCodeAt(start + 1) !== 0x5E/* ^ */) { return false; } for (pos = start + 2; pos < max; pos++) { if (state.src.charCodeAt(pos) === 0x20) { return false; } if (state.src.charCodeAt(pos) === 0x0A) { return false; } if (state.src.charCodeAt(pos) === 0x5D /* ] */) { break; } } if (pos === start + 2) { return false; } // no empty footnote labels if (pos >= max) { return false; } pos++; label = state.src.slice(start + 2, pos - 1); if (typeof state.env.footnotes.refs[':' + label] === 'undefined') { return false; } if (!silent) { if (!state.env.footnotes.list) { state.env.footnotes.list = []; } if (state.env.footnotes.refs[':' + label] < 0) { footnoteId = state.env.footnotes.list.length; state.env.footnotes.list[footnoteId] = { label: label, count: 0 }; state.env.footnotes.refs[':' + label] = footnoteId; } else { footnoteId = state.env.footnotes.refs[':' + label]; } footnoteSubId = state.env.footnotes.list[footnoteId].count; state.env.footnotes.list[footnoteId].count++; token = state.push('footnote_ref', '', 0); token.meta = { id: footnoteId, subId: footnoteSubId }; } state.pos = pos; state.posMax = max; return true; } // Glue footnote tokens to end of token stream function footnote_tail(state) { var i, l, j, t, lastParagraph, list, token, tokens, current, currentLabel, insideRef = false, refTokens = {}; if (!state.env.footnotes) { return; } state.tokens = state.tokens.filter(function(tok) { if (tok.type === 'footnote_reference_open') { insideRef = true; current = []; currentLabel = tok.meta.label; return false; } if (tok.type === 'footnote_reference_close') { insideRef = false; // prepend ':' to avoid conflict with Object.prototype members refTokens[':' + currentLabel] = current; return false; } if (insideRef) { current.push(tok); } return !insideRef; }); if (!state.env.footnotes.list) { return; } list = state.env.footnotes.list; token = new state.Token('footnote_block_open', '', 1); state.tokens.push(token); for (i = 0, l = list.length; i < l; i++) { token = new state.Token('footnote_open', '', 1); token.meta = { id: i }; state.tokens.push(token); if (list[i].tokens) { tokens = []; token = new state.Token('paragraph_open', 'p', 1); token.block = true; tokens.push(token); token = new state.Token('inline', '', 0); token.children = list[i].tokens; token.content = ''; tokens.push(token); token = new state.Token('paragraph_close', 'p', -1); token.block = true; tokens.push(token); } else if (list[i].label) { tokens = refTokens[':' + list[i].label]; } state.tokens = state.tokens.concat(tokens); if (state.tokens[state.tokens.length - 1].type === 'paragraph_close') { lastParagraph = state.tokens.pop(); } else { lastParagraph = null; } t = list[i].count > 0 ? list[i].count : 1; for (j = 0; j < t; j++) { token = new state.Token('footnote_anchor', '', 0); token.meta = { id: i, subId: j }; state.tokens.push(token); } if (lastParagraph) { state.tokens.push(lastParagraph); } token = new state.Token('footnote_close', '', -1); state.tokens.push(token); } token = new state.Token('footnote_block_close', '', -1); state.tokens.push(token); } md.block.ruler.before('reference', 'footnote_def', footnote_def, { alt: [ 'paragraph', 'reference' ] }); md.inline.ruler.after('image', 'footnote_inline', footnote_inline); md.inline.ruler.after('footnote_inline', 'footnote_ref', footnote_ref); md.core.ruler.after('inline', 'footnote_tail', footnote_tail); };