From 0aeacd84338ccd214f253e446e1be9dce0221177 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Mon, 17 Feb 2014 15:35:48 +0800 Subject: [PATCH 1/4] Footnotes feature. It looks like MultiMarkdown Footnotes Syntax: Here is some text containing a footnote.[^somesamplefootnote] [^somesamplefootnote]: Here is the text of the footnote itself. - http://daringfireball.net/2005/07/footnotes - https://github.com/fletcher/MultiMarkdown/wiki/MultiMarkdown-Syntax-Guide#wiki-footnotes - http://six.pairlist.net/pipermail/markdown-discuss/2005-August/001442.html - http://six.pairlist.net/pipermail/markdown-discuss/2005-August/001480.html --- lib/marked.js | 121 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 103 insertions(+), 18 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index 26568328e1..ac6b1e43e6 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -21,7 +21,8 @@ var block = { blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, list: /^( *)(bull) [\s\S]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, - def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + def: /^ *\[([^^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + footnote: noop, table: noop, paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, text: /^[^\n]+/ @@ -73,7 +74,6 @@ block.gfm = merge({}, block.normal, { fences: /^ *(`{3,}|~{3,}) *(\S+)? *\n([\s\S]+?)\s*\1 *(?:\n+|$)/, paragraph: /^/ }); - block.gfm.paragraph = replace(block.paragraph) ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|' @@ -81,13 +81,32 @@ block.gfm.paragraph = replace(block.paragraph) (); /** - * GFM + Tables Block Grammar + * Tables Block Grammar */ -block.tables = merge({}, block.gfm, { +block.tables = { nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ -}); +}; + +/** + * Footnotes Block Grammar + */ +block.footnotes = { + footnote: /^\[(\^[^\]]+)\]: *([^\n]*(?:\n [^\n]*)*)/, +}; +block.footnotes.normal = { + footnote: block.footnotes.footnote +}; +block.footnotes.normal.paragraph = replace(block.paragraph)( + '))+)', '|' + block.footnotes.footnote.source + '))+)' +)(); +block.footnotes.gfm = { + footnote: block.footnotes.footnote +}; +block.footnotes.gfm.paragraph = replace(block.gfm.paragraph)( + '))+)', '|' + block.footnotes.footnote.source + '))+)' +)(); /** * Block Lexer @@ -96,15 +115,21 @@ block.tables = merge({}, block.gfm, { function Lexer(options) { this.tokens = []; this.tokens.links = {}; + this.tokens.footnotes = []; this.options = options || marked.defaults; this.rules = block.normal; if (this.options.gfm) { - if (this.options.tables) { - this.rules = block.tables; - } else { - this.rules = block.gfm; + this.rules = block.gfm; + if (this.options.footnotes) { + this.rules = merge({}, this.rules, block.footnotes.gfm); } + } else if (this.options.footnotes) { + this.rules = merge({}, this.rules, block.footnotes.normal); + } + + if (this.options.tables) { + this.rules = merge({}, this.rules, block.tables); } } @@ -144,6 +169,7 @@ Lexer.prototype.lex = function(src) { Lexer.prototype.token = function(src, top) { var src = src.replace(/^ +$/gm, '') , next + , key , loose , cap , bull @@ -370,6 +396,15 @@ Lexer.prototype.token = function(src, top) { continue; } + // footnote + if (top && (cap = this.rules.footnote.exec(src))) { + src = src.substring(cap[0].length); + key = cap[1].toLowerCase(); + this.tokens.footnotes.push({key: key, text: cap[2]}); + this.tokens.footnotes[key] = {text: cap[2], count: this.tokens.footnotes.length}; + continue; + } + // table (gfm) if (top && (cap = this.rules.table.exec(src))) { src = src.substring(cap[0].length); @@ -453,10 +488,11 @@ var inline = { code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, br: /^ {2,}\n(?!\s*$)/, del: noop, + footnote: noop, text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; inline.link = replace(inline.link) @@ -478,10 +514,10 @@ inline.normal = merge({}, inline); * Pedantic Inline Grammar */ -inline.pedantic = merge({}, inline.normal, { +inline.pedantic = { strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ -}); +}; /** * GFM Inline Grammar @@ -506,13 +542,22 @@ inline.breaks = merge({}, inline.gfm, { text: replace(inline.gfm.text)('{2,}', '*')() }); +/** + * Footnote Inline Grammar + */ + +inline.footnote = { + footnote: /^\[\^([^\]]+)\]/ +}; + /** * Inline Lexer & Compiler */ -function InlineLexer(links, options) { +function InlineLexer(links, footnotes, options) { this.options = options || marked.defaults; this.links = links; + this.footnotes = footnotes || {}; this.rules = inline.normal; this.renderer = this.options.renderer || new Renderer; this.renderer.options = this.options; @@ -528,8 +573,14 @@ function InlineLexer(links, options) { } else { this.rules = inline.gfm; } - } else if (this.options.pedantic) { - this.rules = inline.pedantic; + } + + if (this.options.footnote) { + this.rules = merge({}, this.rules, inline.footnote); + } + + if (this.options.pedantic) { + this.rules = merge({}, this.rules, inline.pedantic); } } @@ -543,8 +594,8 @@ InlineLexer.rules = inline; * Static Lexing/Compiling Method */ -InlineLexer.output = function(src, links, options) { - var inline = new InlineLexer(links, options); +InlineLexer.output = function(src, links, footnotes, options) { + var inline = new InlineLexer(links, footnotes, options); return inline.output(src); }; @@ -555,6 +606,8 @@ InlineLexer.output = function(src, links, options) { InlineLexer.prototype.output = function(src) { var out = '' , link + , key + , ret , text , href , cap; @@ -626,6 +679,16 @@ InlineLexer.prototype.output = function(src) { continue; } + // footnote + if (cap = this.rules.footnote.exec(src)) { + key = cap[1].toLowerCase(); + if (ret = this.footnotes[key]) { + src = src.substring(cap[0].length); + out += this.renderer.footnoteref(key, ret.count); + } + continue; + } + // strong if (cap = this.rules.strong.exec(src)) { src = src.substring(cap[0].length); @@ -875,6 +938,23 @@ Renderer.prototype.image = function(href, title, text) { return out; }; +Renderer.prototype.footnoteref = function(key, count) { + var out = ''; + out += count + ''; + return out; +}; + +Renderer.prototype.footnotes = function(notes) { + var out = '
    '; + for (var i = 0; i < notes.length; i++) { + out += '
  1. '; + out += notes[i].text; + out += '
  2. '; + } + out += '
'; + return out; +}; + /** * Parsing & Compiling */ @@ -902,7 +982,7 @@ Parser.parse = function(src, options, renderer) { */ Parser.prototype.parse = function(src) { - this.inline = new InlineLexer(src.links, this.options, this.renderer); + this.inline = new InlineLexer(src.links, src.footnotes, this.options); this.tokens = src.reverse(); var out = ''; @@ -910,6 +990,10 @@ Parser.prototype.parse = function(src) { out += this.tok(); } + if (src.footnotes.length) { + out += this.renderer.footnotes(src.footnotes); + } + return out; }; @@ -1209,6 +1293,7 @@ marked.setOptions = function(opt) { marked.defaults = { gfm: true, tables: true, + footnotes: false, breaks: false, pedantic: false, sanitize: false, From 2af24aefd45ae4a2c90fd73564c1d18893401ab8 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Mon, 17 Feb 2014 15:46:39 +0800 Subject: [PATCH 2/4] Fix: Add ref links for footnotes --- lib/marked.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/marked.js b/lib/marked.js index ac6b1e43e6..e7178026ec 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -940,7 +940,7 @@ Renderer.prototype.image = function(href, title, text) { Renderer.prototype.footnoteref = function(key, count) { var out = ''; - out += count + ''; + out += '' + count + ''; return out; }; @@ -949,6 +949,7 @@ Renderer.prototype.footnotes = function(notes) { for (var i = 0; i < notes.length; i++) { out += '
  • '; out += notes[i].text; + out += '' out += '
  • '; } out += ''; From 159b50e06123879df79d8d5e28c4f270c07d37f2 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Mon, 17 Feb 2014 16:18:03 +0800 Subject: [PATCH 3/4] Fix footnotes regex. Fix footnotes workflow. --- lib/marked.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/marked.js b/lib/marked.js index e7178026ec..5d9ad697dd 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -399,7 +399,7 @@ Lexer.prototype.token = function(src, top) { // footnote if (top && (cap = this.rules.footnote.exec(src))) { src = src.substring(cap[0].length); - key = cap[1].toLowerCase(); + key = cap[1].slice(1).toLowerCase(); this.tokens.footnotes.push({key: key, text: cap[2]}); this.tokens.footnotes[key] = {text: cap[2], count: this.tokens.footnotes.length}; continue; @@ -575,7 +575,7 @@ function InlineLexer(links, footnotes, options) { } } - if (this.options.footnote) { + if (this.options.footnotes) { this.rules = merge({}, this.rules, inline.footnote); } @@ -654,6 +654,16 @@ InlineLexer.prototype.output = function(src) { continue; } + // footnote + if (cap = this.rules.footnote.exec(src)) { + key = cap[1].toLowerCase(); + if (ret = this.footnotes[key]) { + src = src.substring(cap[0].length); + out += this.renderer.footnoteref(key, ret.count); + continue; + } + } + // link if (cap = this.rules.link.exec(src)) { src = src.substring(cap[0].length); @@ -679,16 +689,6 @@ InlineLexer.prototype.output = function(src) { continue; } - // footnote - if (cap = this.rules.footnote.exec(src)) { - key = cap[1].toLowerCase(); - if (ret = this.footnotes[key]) { - src = src.substring(cap[0].length); - out += this.renderer.footnoteref(key, ret.count); - } - continue; - } - // strong if (cap = this.rules.strong.exec(src)) { src = src.substring(cap[0].length); From 18b4858838546bba5f6a1293cbb399898340b618 Mon Sep 17 00:00:00 2001 From: Hsiaoming Yang Date: Fri, 21 Feb 2014 10:24:26 +0800 Subject: [PATCH 4/4] Update footnote regex pattern --- lib/marked.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/marked.js b/lib/marked.js index 5d9ad697dd..dff91e1f7e 100644 --- a/lib/marked.js +++ b/lib/marked.js @@ -93,7 +93,7 @@ block.tables = { * Footnotes Block Grammar */ block.footnotes = { - footnote: /^\[(\^[^\]]+)\]: *([^\n]*(?:\n [^\n]*)*)/, + footnote: /^\[\^([^\]]+)\]: *([^\n]*(?:\n+|$)(?: {1,}[^\n]*(?:\n+|$))*)/ }; block.footnotes.normal = { footnote: block.footnotes.footnote