diff --git a/lib/marked.js b/lib/marked.js index d5522afce2..6f2e39e420 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -21,6 +21,7 @@ var block = { blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, + footnote: /^ *\[\^([^\]]+)\]: /, def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, table: noop, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, @@ -159,6 +160,16 @@ Lexer.prototype.token = function(src, top, bq) { , l; while (src) { + if (top && this.footnote + && this.tokens[this.tokens.length-1].type !== 'footnote_start' + && this.tokens[this.tokens.length-1].type !== 'parapgraph' + && this.tokens[this.tokens.length-1].type !== 'newline') { + this.footenote = false; + this.tokens.push({ + type: 'footnote_end' + }); + } + // newline if (cap = this.rules.newline.exec(src)) { src = src.substring(cap[0].length); @@ -365,6 +376,17 @@ Lexer.prototype.token = function(src, top, bq) { continue; } + // footnote + if ((!bq && top) && (cap = this.rules.footnote.exec(src))) { + src = src.substring(cap[0].length); + this.footnote = true; + this.tokens.push({ + type: 'footnote_start', + text: cap[1] + }); + continue; + } + // def if ((!bq && top) && (cap = this.rules.def.exec(src))) { src = src.substring(cap[0].length); @@ -438,6 +460,13 @@ Lexer.prototype.token = function(src, top, bq) { } } + if (top && this.footnote) { + this.footenote = false; + this.tokens.push({ + type: 'footnote_end' + }); + } + return this.tokens; }; @@ -451,6 +480,7 @@ var inline = { url: noop, tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, link: /^!?\[(inside)\]\(href\)/, + footnoteref: /^ *\[\^([^\]]+)\]/, reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, @@ -623,6 +653,13 @@ InlineLexer.prototype.output = function(src) { continue; } + // footnote + if (cap = this.rules.footnoteref.exec(src)) { + src = src.substring(cap[0].length); + out += this.renderer.footnoteref(escape(cap[1], true)); + continue; + } + // reflink, nolink if ((cap = this.rules.reflink.exec(src)) || (cap = this.rules.nolink.exec(src))) { @@ -859,6 +896,39 @@ Renderer.prototype.del = function(text) { return '' + text + ''; }; +Renderer.prototype.footnoteref = function(ref) { + return '' + + '' + ref + '' + + ''; +}; + +Renderer.prototype.footnote = function(ref, body) { + return '
  • ' + body + + '' + + '
  • \n'; +}; + +Renderer.prototype.footnotes = function(footnotes) { + var text = '' + , items = '' + , i = 0; + + for (; i < footnotes.length; i++) { + items += this.footnote(footnotes[i].ref, footnotes[i].body); + } + + text += this.hr(); + text += '
    \n'; + text += this.list(items, true); + text += '
    \n'; + + footnotes.length = 0; + + return text; +}; + Renderer.prototype.link = function(href, title, text) { if (this.options.sanitize) { try { @@ -900,6 +970,7 @@ function Parser(options) { this.options.renderer = this.options.renderer || new Renderer; this.renderer = this.options.renderer; this.renderer.options = this.options; + this.footnotes = []; } /** @@ -924,6 +995,10 @@ Parser.prototype.parse = function(src) { out += this.tok(); } + if (this.footnotes.length) { + out += this.renderer.footnotes(this.footnotes); + } + return out; }; @@ -1024,6 +1099,21 @@ Parser.prototype.tok = function() { return this.renderer.blockquote(body); } + case 'footnote_start': { + var body = '' + , ref = escape(this.token.text, true); + + while (this.next().type !== 'footnote_end') { + body += this.tok(); + } + + this.footnotes.push({ + ref: ref, + body: body + }); + + return ''; + } case 'list_start': { var body = '' , ordered = this.token.ordered;