From db1b7cdbff80df4b568e5bb11395aea2eb999e3d Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 22 Sep 2012 22:49:14 +0200 Subject: [PATCH 1/8] Add Markdown support. Markdown support is optional. It is enabled by setting the `format` property of the configuration object to "markdown." ReSpec uses marked for parsing Markkdown. Note that the content of SECTION elements, and P elements with a class name of "note" or "issue" is also parsed. The HTML created by the Markdown parser is turned into a nested structure of SECTION elements, following the strucutre given by the headings. For example, the following markup: Title ----- ### Subtitle ### Here's some text. ### Another subtitle ### More text. will be transformed into:

Title

Subtitle

Here's some text.

Another subtitle

More text.

--- js/core/markdown.js | 159 ++++++++ js/core/marked.js | 781 +++++++++++++++++++++++++++++++++++++++ js/profile-w3c-common.js | 1 + 3 files changed, 941 insertions(+) create mode 100644 js/core/markdown.js create mode 100644 js/core/marked.js diff --git a/js/core/markdown.js b/js/core/markdown.js new file mode 100644 index 0000000000..d0fcc52e2f --- /dev/null +++ b/js/core/markdown.js @@ -0,0 +1,159 @@ +// Module core/markdown +// Handles the optional markdown processing. +// +// Markdown support is optional. It is enabled by setting the `format` +// property of the configuration object to "markdown." +// +// We use marked for parsing Markkdown. +// +// Note that the content of SECTION elements, and P elements with a +// class name of "note" or "issue" is also parsed. +// +// The HTML created by the Markdown parser is turned into a nested +// structure of SECTION elements, following the strucutre given by +// the headings. For example, the following markup: +// +// Title +// ----- +// +// ### Subtitle ### +// +// Here's some text. +// +// ### Another subtitle ### +// +// More text. +// +// will be transformed into: +// +//
+//

Title

+//
+//

Subtitle

+//

Here's some text.

+//
+//
+//

Another subtitle

+//

More text.

+//
+//
+ +define( + ['core/marked'], + function (markdown) { + marked.setOptions({ + gfm: false, + pedantic: false, + sanitize: false + }); + + return { + toHTML: function(text) { + return marked(text); + }, + + processBody: function(doc) { + var fragment = doc.createDocumentFragment() + , div = doc.createElement('div') + , node + ; + + div.innerHTML = this.toHTML(doc.body.innerHTML); + while (node = div.firstChild) { + fragment.appendChild(node); + } + return fragment; + }, + + processSections: function(doc) { + var self = this; + $('section', doc).each(function() { + this.innerHTML = self.toHTML(this.innerHTML); + }); + }, + + processIssuesAndNotes: function(doc) { + var div = doc.createElement('div'); + var self = this; + $('p.issue, p.note', doc).each(function() { + div.innerHTML = self.toHTML(this.innerHTML); + this.innerHTML = ''; + var node = div.firstChild; + while (node.firstChild) { + this.appendChild(node.firstChild); + } + }); + }, + + structure: function(fragment, doc) { + var output = doc.createDocumentFragment() + , current = output + , stack = [output] + , node + , tagName + ; + + function newSection(node, position) { + var section = doc.createElement('section'); + section.appendChild(node); + findParent(position).appendChild(section); + stack[position] = section; + current = section; + } + + function findParent(position) { + while (1) { + position-- + parent = stack[position]; + if (parent) return parent; + } + } + + while (node = fragment.firstChild) { + if (node.nodeType !== 1) { + fragment.removeChild(node); + continue; + } + tagName = node.tagName.toLowerCase(); + switch (tagName) { + case 'h1': + newSection(node, 1); + break; + case 'h2': + newSection(node, 2); + break; + case 'h3': + newSection(node, 3); + break; + case 'h4': + newSection(node, 4); + break; + case 'h5': + newSection(node, 5); + break; + case 'h6': + newSection(node, 6); + break; + default: + current.appendChild(node); + } + } + + return output; + }, + + run: function (conf, doc, cb, msg) { + msg.pub("start", "core/markdown"); + if (conf.format === 'markdown') { + this.processIssuesAndNotes(doc); + this.processSections(doc); + fragment = this.structure(this.processBody(doc), doc); + doc.body.innerHTML = ''; + doc.body.appendChild(fragment) + } + msg.pub("end", "core/markdown"); + cb(); + } + }; + } +); diff --git a/js/core/marked.js b/js/core/marked.js new file mode 100644 index 0000000000..62a5eb9993 --- /dev/null +++ b/js/core/marked.js @@ -0,0 +1,781 @@ +/** + * marked - A markdown parser (https://github.com/chjj/marked) + * Copyright (c) 2011-2012, Christopher Jeffrey. (MIT Licensed) + */ + +;(function() { + +/** + * Block-Level Grammar + */ + +var block = { + newline: /^\n+/, + code: /^( {4}[^\n]+\n*)+/, + fences: noop, + hr: /^( *[-*_]){3,} *(?:\n+|$)/, + heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, + lheading: /^([^\n]+)\n *(=|-){3,} *\n*/, + blockquote: /^( *>[^\n]+(\n[^\n]+)*\n*)+/, + list: /^( *)(bull) [^\0]+?(?:hr|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, + html: /^ *(?:comment|closed|closing) *(?:\n{2,}|\s*$)/, + def: /^ *\[([^\]]+)\]: *([^\s]+)(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, + paragraph: /^([^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+\n*/, + text: /^[^\n]+/ +}; + +block.bullet = /(?:[*+-]|\d+\.)/; +block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; +block.item = replace(block.item, 'gm') + (/bull/g, block.bullet) + (); + +block.list = replace(block.list) + (/bull/g, block.bullet) + ('hr', /\n+(?=(?: *[-*_]){3,} *(?:\n+|$))/) + (); + +block.html = replace(block.html) + ('comment', //) + ('closed', /<(tag)[^\0]+?<\/\1>/) + ('closing', /])*?>/) + (/tag/g, tag()) + (); + +block.paragraph = replace(block.paragraph) + ('hr', block.hr) + ('heading', block.heading) + ('lheading', block.lheading) + ('blockquote', block.blockquote) + ('tag', '<' + tag()) + ('def', block.def) + (); + +block.normal = { + fences: block.fences, + paragraph: block.paragraph +}; + +block.gfm = { + fences: /^ *(```|~~~) *(\w+)? *\n([^\0]+?)\s*\1 *(?:\n+|$)/, + paragraph: /^/ +}; + +block.gfm.paragraph = replace(block.paragraph) + ('(?!', '(?!' + block.gfm.fences.source.replace('\\1', '\\2') + '|') + (); + +/** + * Block Lexer + */ + +block.lexer = function(src) { + var tokens = []; + + tokens.links = {}; + + src = src + .replace(/\r\n|\r/g, '\n') + .replace(/\t/g, ' '); + + return block.token(src, tokens, true); +}; + +block.token = function(src, tokens, top) { + var src = src.replace(/^ +$/gm, '') + , next + , loose + , cap + , item + , space + , i + , l; + + while (src) { + // newline + if (cap = block.newline.exec(src)) { + src = src.substring(cap[0].length); + if (cap[0].length > 1) { + tokens.push({ + type: 'space' + }); + } + } + + // code + if (cap = block.code.exec(src)) { + src = src.substring(cap[0].length); + cap = cap[0].replace(/^ {4}/gm, ''); + tokens.push({ + type: 'code', + text: !options.pedantic + ? cap.replace(/\n+$/, '') + : cap + }); + continue; + } + + // fences (gfm) + if (cap = block.fences.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'code', + lang: cap[2], + text: cap[3] + }); + continue; + } + + // heading + if (cap = block.heading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[1].length, + text: cap[2] + }); + continue; + } + + // lheading + if (cap = block.lheading.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'heading', + depth: cap[2] === '=' ? 1 : 2, + text: cap[1] + }); + continue; + } + + // hr + if (cap = block.hr.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'hr' + }); + continue; + } + + // blockquote + if (cap = block.blockquote.exec(src)) { + src = src.substring(cap[0].length); + + tokens.push({ + type: 'blockquote_start' + }); + + cap = cap[0].replace(/^ *> ?/gm, ''); + + // Pass `top` to keep the current + // "toplevel" state. This is exactly + // how markdown.pl works. + block.token(cap, tokens, top); + + tokens.push({ + type: 'blockquote_end' + }); + + continue; + } + + // list + if (cap = block.list.exec(src)) { + src = src.substring(cap[0].length); + + tokens.push({ + type: 'list_start', + ordered: isFinite(cap[2]) + }); + + // Get each top-level item. + cap = cap[0].match(block.item); + + next = false; + l = cap.length; + i = 0; + + for (; i < l; i++) { + item = cap[i]; + + // Remove the list item's bullet + // so it is seen as the next token. + space = item.length; + item = item.replace(/^ *([*+-]|\d+\.) +/, ''); + + // Outdent whatever the + // list item contains. Hacky. + if (~item.indexOf('\n ')) { + space -= item.length; + item = !options.pedantic + ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') + : item.replace(/^ {1,4}/gm, ''); + } + + // Determine whether item is loose or not. + // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ + // for discount behavior. + loose = next || /\n\n(?!\s*$)/.test(item); + if (i !== l - 1) { + next = item[item.length-1] === '\n'; + if (!loose) loose = next; + } + + tokens.push({ + type: loose + ? 'loose_item_start' + : 'list_item_start' + }); + + // Recurse. + block.token(item, tokens); + + tokens.push({ + type: 'list_item_end' + }); + } + + tokens.push({ + type: 'list_end' + }); + + continue; + } + + // html + if (cap = block.html.exec(src)) { + src = src.substring(cap[0].length); + tokens.push({ + type: options.sanitize + ? 'paragraph' + : 'html', + pre: cap[1] === 'pre', + text: cap[0] + }); + continue; + } + + // def + if (top && (cap = block.def.exec(src))) { + src = src.substring(cap[0].length); + tokens.links[cap[1].toLowerCase()] = { + href: cap[2], + title: cap[3] + }; + continue; + } + + // top-level paragraph + if (top && (cap = block.paragraph.exec(src))) { + src = src.substring(cap[0].length); + tokens.push({ + type: 'paragraph', + text: cap[0] + }); + continue; + } + + // text + if (cap = block.text.exec(src)) { + // Top-level should never reach here. + src = src.substring(cap[0].length); + tokens.push({ + type: 'text', + text: cap[0] + }); + continue; + } + } + + return tokens; +}; + +/** + * Inline Processing + */ + +var inline = { + escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, + autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, + url: noop, + tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, + link: /^!?\[(inside)\]\(href\)/, + reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, + nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, + strong: /^__([^\0]+?)__(?!_)|^\*\*([^\0]+?)\*\*(?!\*)/, + em: /^\b_((?:__|[^\0])+?)_\b|^\*((?:\*\*|[^\0])+?)\*(?!\*)/, + code: /^(`+)([^\0]*?[^`])\1(?!`)/, + br: /^ {2,}\n(?!\s*$)/, + text: /^[^\0]+?(?=[\\?(?:\s+['"]([^\0]*?)['"])?\s*/; + +inline.link = replace(inline.link) + ('inside', inline._linkInside) + ('href', inline._linkHref) + (); + +inline.reflink = replace(inline.reflink) + ('inside', inline._linkInside) + (); + +inline.normal = { + url: inline.url, + strong: inline.strong, + em: inline.em, + text: inline.text +}; + +inline.pedantic = { + strong: /^__(?=\S)([^\0]*?\S)__(?!_)|^\*\*(?=\S)([^\0]*?\S)\*\*(?!\*)/, + em: /^_(?=\S)([^\0]*?\S)_(?!_)|^\*(?=\S)([^\0]*?\S)\*(?!\*)/ +}; + +inline.gfm = { + url: /^(https?:\/\/[^\s]+[^.,:;"')\]\s])/, + text: /^[^\0]+?(?=[\\' + + text + + ''; + continue; + } + + // url (gfm) + if (cap = inline.url.exec(src)) { + src = src.substring(cap[0].length); + text = escape(cap[1]); + href = text; + out += '' + + text + + ''; + continue; + } + + // tag + if (cap = inline.tag.exec(src)) { + src = src.substring(cap[0].length); + out += options.sanitize + ? escape(cap[0]) + : cap[0]; + continue; + } + + // link + if (cap = inline.link.exec(src)) { + src = src.substring(cap[0].length); + out += outputLink(cap, { + href: cap[2], + title: cap[3] + }); + continue; + } + + // reflink, nolink + if ((cap = inline.reflink.exec(src)) + || (cap = inline.nolink.exec(src))) { + src = src.substring(cap[0].length); + link = (cap[2] || cap[1]).replace(/\s+/g, ' '); + link = links[link.toLowerCase()]; + if (!link || !link.href) { + out += cap[0][0]; + src = cap[0].substring(1) + src; + continue; + } + out += outputLink(cap, link); + continue; + } + + // strong + if (cap = inline.strong.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + inline.lexer(cap[2] || cap[1]) + + ''; + continue; + } + + // em + if (cap = inline.em.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + inline.lexer(cap[2] || cap[1]) + + ''; + continue; + } + + // code + if (cap = inline.code.exec(src)) { + src = src.substring(cap[0].length); + out += '' + + escape(cap[2], true) + + ''; + continue; + } + + // br + if (cap = inline.br.exec(src)) { + src = src.substring(cap[0].length); + out += '
'; + continue; + } + + // text + if (cap = inline.text.exec(src)) { + src = src.substring(cap[0].length); + out += escape(cap[0]); + continue; + } + } + + return out; +}; + +function outputLink(cap, link) { + if (cap[0][0] !== '!') { + return '' + + inline.lexer(cap[1]) + + ''; + } else { + return ''
+      + escape(cap[1])
+      + ''; + } +} + +/** + * Parsing + */ + +var tokens + , token; + +function next() { + return token = tokens.pop(); +} + +function tok() { + switch (token.type) { + case 'space': { + return ''; + } + case 'hr': { + return '
\n'; + } + case 'heading': { + return '' + + inline.lexer(token.text) + + '\n'; + } + case 'code': { + if (options.highlight) { + token.code = options.highlight(token.text, token.lang); + if (token.code != null && token.code !== token.text) { + token.escaped = true; + token.text = token.code; + } + } + + if (!token.escaped) { + token.text = escape(token.text, true); + } + + return '
'
+        + token.text
+        + '
\n'; + } + case 'blockquote_start': { + var body = ''; + + while (next().type !== 'blockquote_end') { + body += tok(); + } + + return '
\n' + + body + + '
\n'; + } + case 'list_start': { + var type = token.ordered ? 'ol' : 'ul' + , body = ''; + + while (next().type !== 'list_end') { + body += tok(); + } + + return '<' + + type + + '>\n' + + body + + '\n'; + } + case 'list_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += token.type === 'text' + ? parseText() + : tok(); + } + + return '
  • ' + + body + + '
  • \n'; + } + case 'loose_item_start': { + var body = ''; + + while (next().type !== 'list_item_end') { + body += tok(); + } + + return '
  • ' + + body + + '
  • \n'; + } + case 'html': { + return !token.pre && !options.pedantic + ? inline.lexer(token.text) + : token.text; + } + case 'paragraph': { + return '

    ' + + inline.lexer(token.text) + + '

    \n'; + } + case 'text': { + return '

    ' + + parseText() + + '

    \n'; + } + } +} + +function parseText() { + var body = token.text + , top; + + while ((top = tokens[tokens.length-1]) + && top.type === 'text') { + body += '\n' + next().text; + } + + return inline.lexer(body); +} + +function parse(src) { + tokens = src.reverse(); + + var out = ''; + while (next()) { + out += tok(); + } + + tokens = null; + token = null; + + return out; +} + +/** + * Helpers + */ + +function escape(html, encode) { + return html + .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); +} + +function mangle(text) { + var out = '' + , l = text.length + , i = 0 + , ch; + + for (; i < l; i++) { + ch = text.charCodeAt(i); + if (Math.random() > 0.5) { + ch = 'x' + ch.toString(16); + } + out += '&#' + ch + ';'; + } + + return out; +} + +function tag() { + var tag = '(?!(?:' + + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|@)\\b'; + + return tag; +} + +function replace(regex, opt) { + regex = regex.source; + opt = opt || ''; + return function self(name, val) { + if (!name) return new RegExp(regex, opt); + val = val.source || val; + val = val.replace(/(^|[^\[])\^/g, '$1'); + regex = regex.replace(name, val); + return self; + }; +} + +function noop() {} +noop.exec = noop; + +/** + * Marked + */ + +function marked(src, opt) { + setOptions(opt); + return parse(block.lexer(src)); +} + +/** + * Options + */ + +var options + , defaults; + +function setOptions(opt) { + if (!opt) opt = defaults; + if (options === opt) return; + options = opt; + + if (options.gfm) { + block.fences = block.gfm.fences; + block.paragraph = block.gfm.paragraph; + inline.text = inline.gfm.text; + inline.url = inline.gfm.url; + } else { + block.fences = block.normal.fences; + block.paragraph = block.normal.paragraph; + inline.text = inline.normal.text; + inline.url = inline.normal.url; + } + + if (options.pedantic) { + inline.em = inline.pedantic.em; + inline.strong = inline.pedantic.strong; + } else { + inline.em = inline.normal.em; + inline.strong = inline.normal.strong; + } +} + +marked.options = +marked.setOptions = function(opt) { + defaults = opt; + setOptions(opt); + return marked; +}; + +marked.setOptions({ + gfm: true, + pedantic: false, + sanitize: false, + highlight: null +}); + +/** + * Expose + */ + +marked.parser = function(src, opt) { + setOptions(opt); + return parse(src); +}; + +marked.lexer = function(src, opt) { + setOptions(opt); + return block.lexer(src); +}; + +marked.parse = marked; + +if (typeof module !== 'undefined') { + module.exports = marked; +} else { + this.marked = marked; +} + +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); \ No newline at end of file diff --git a/js/profile-w3c-common.js b/js/profile-w3c-common.js index fbf08fd004..8ad949d229 100644 --- a/js/profile-w3c-common.js +++ b/js/profile-w3c-common.js @@ -4,6 +4,7 @@ define([ , "core/base-runner" , "core/override-configuration" , "core/default-root-attr" + , "core/markdown" , "core/style" , "w3c/style" , "w3c/headers" From 787a54dffeda87f996d9107c92a677f845b642e5 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 24 Sep 2012 01:12:58 +0200 Subject: [PATCH 2/8] Unescape > to fix broken support for block quotes. --- js/core/markdown.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/core/markdown.js b/js/core/markdown.js index d0fcc52e2f..5b340670b7 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -35,7 +35,7 @@ //
    //

    Another subtitle

    //

    More text.

    -//
    +// // define( @@ -49,6 +49,7 @@ define( return { toHTML: function(text) { + text = text.replace(/>/g, '>'); return marked(text); }, From 8871272114843e0b23696d2e17657806d733c081 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 24 Sep 2012 18:01:55 +0200 Subject: [PATCH 3/8] Handles markdown content being nested inside elements with soft tabs. E.g.: '''
    This is a title --------------- And this more text.

    This is a title

    And this more text.

    This is a title
    ---------------
    
    And this more text.
    is already escaped, and + // thus blockquotes aren't picked up by the parser. This fixes + // it. text = text.replace(/>/g, '>'); + text = this.removeLeftPadding(text); return marked(text); }, + removeLeftPadding: function(text) { + // Handles markdown content being nested + // inside elements with soft tabs. E.g.: + //
    + // This is a title + // --------------- + // + // And this more text. + //
    + //

    This is a title

    + //

    And this more text.

    + // + //
    This is a title
    +                // ---------------
    +                // 
    +                // And this more text.
    + // current) { + min = current + } + } + + var re = new RegExp("\n[ ]{0," + min + "}", "g"); + text = text.replace(re, '\n'); + } + return text; + }, + processBody: function(doc) { var fragment = doc.createDocumentFragment() , div = doc.createElement('div') From e42b29e24130d3c45e1c13a68b21f0c2866077e0 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 17 Oct 2012 00:05:27 +0200 Subject: [PATCH 4/8] Notes and issues need not be paragraphs. --- js/core/markdown.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/core/markdown.js b/js/core/markdown.js index ad13101ca0..448abe500b 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -124,7 +124,7 @@ define( processIssuesAndNotes: function(doc) { var div = doc.createElement('div'); var self = this; - $('p.issue, p.note', doc).each(function() { + $('.issue, .note', doc).each(function() { div.innerHTML = self.toHTML(this.innerHTML); this.innerHTML = ''; var node = div.firstChild; From ea8149c4e7648cee17748f6d88875316ad46767f Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Wed, 17 Oct 2012 11:57:50 +0200 Subject: [PATCH 5/8] Also process markdown within requirements. --- js/core/markdown.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/core/markdown.js b/js/core/markdown.js index 448abe500b..3b7087cda4 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -6,8 +6,8 @@ // // We use marked for parsing Markkdown. // -// Note that the content of SECTION elements, and P elements with a -// class name of "note" or "issue" is also parsed. +// Note that the content of SECTION elements, and elements with a +// class name of "note", "issue" or "req" are also parsed. // // The HTML created by the Markdown parser is turned into a nested // structure of SECTION elements, following the strucutre given by @@ -121,10 +121,10 @@ define( }); }, - processIssuesAndNotes: function(doc) { + processIssuesNotesAndReqs: function(doc) { var div = doc.createElement('div'); var self = this; - $('.issue, .note', doc).each(function() { + $('.issue, .note, .req', doc).each(function() { div.innerHTML = self.toHTML(this.innerHTML); this.innerHTML = ''; var node = div.firstChild; @@ -194,7 +194,7 @@ define( run: function (conf, doc, cb, msg) { msg.pub("start", "core/markdown"); if (conf.format === 'markdown') { - this.processIssuesAndNotes(doc); + this.processIssuesNotesAndReqs(doc); this.processSections(doc); fragment = this.structure(this.processBody(doc), doc); doc.body.innerHTML = ''; From 5da31681eaf3f060e6d6aebf19ff92b0cbb9d6a9 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 18 Oct 2012 02:22:13 +0200 Subject: [PATCH 6/8] Fix leaked global variables. --- js/core/markdown.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/js/core/markdown.js b/js/core/markdown.js index 3b7087cda4..7f9085aa45 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -151,8 +151,9 @@ define( } function findParent(position) { - while (1) { - position-- + var parent; + while (position > 0) { + position--; parent = stack[position]; if (parent) return parent; } @@ -196,7 +197,7 @@ define( if (conf.format === 'markdown') { this.processIssuesNotesAndReqs(doc); this.processSections(doc); - fragment = this.structure(this.processBody(doc), doc); + var fragment = this.structure(this.processBody(doc), doc); doc.body.innerHTML = ''; doc.body.appendChild(fragment) } From 66610621f8c6567549bb15f3d1909196efeb3c3d Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 18 Oct 2012 02:23:25 +0200 Subject: [PATCH 7/8] Add specs for markdown implementation. --- tests/SpecRunner.html | 1 + tests/spec/core/markdown-spec.js | 107 +++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tests/spec/core/markdown-spec.js diff --git a/tests/SpecRunner.html b/tests/SpecRunner.html index d34c9ef5bd..06e761afca 100644 --- a/tests/SpecRunner.html +++ b/tests/SpecRunner.html @@ -37,6 +37,7 @@ + diff --git a/tests/spec/core/markdown-spec.js b/tests/spec/core/markdown-spec.js new file mode 100644 index 0000000000..df7fd329f5 --- /dev/null +++ b/tests/spec/core/markdown-spec.js @@ -0,0 +1,107 @@ +describe("Core - Markdown", function () { + var MAXOUT = 5000 + , basicConfig = { + editors: [{ name: "Robin Berjon" }] + , specStatus: "WD" + , format: "markdown" + }; + it("should process standard markdown content", function () { + var doc; + runs(function () { + makeRSDoc({ config: basicConfig, + body: '\nFoo\n===\n' + }, function (rsdoc) { doc = rsdoc; }); + }); + waitsFor(function () { return doc; }, MAXOUT); + + runs(function () { + var $foo = $('#foo', doc); + expect($foo.length).toEqual(1); + expect($foo.text()).toEqual("1. Foo"); + }); + }); + + it("should process markdown inside of sections", function () { + var doc; + runs(function () { + makeRSDoc({ config: basicConfig, + body: '
    \nFoo\n===\n
    ' + }, function (rsdoc) { doc = rsdoc; }); + }); + waitsFor(function () { return doc; }, MAXOUT); + + runs(function () { + var $foo = $('#foo', doc); + expect($foo.length).toEqual(1); + expect($foo.text()).toMatch(/1\. Foo/); + }); + }); + + it("should process markdown inside of notes, issues and reqs.", function () { + var doc; + runs(function () { + makeRSDoc({ config: basicConfig, + body: '

    _foo_

    _foo_
    • \n### _foo_###\n
    ' + }, function (rsdoc) { doc = rsdoc; }); + }); + waitsFor(function () { return doc; }, MAXOUT); + + runs(function () { + expect($('.note em', doc).length).toEqual(1); + expect($('.issue em', doc).length).toEqual(1); + expect($('.req em', doc).length).toEqual(1); + expect($('.req h3', doc).length).toEqual(1); + }); + }); + + it("should remove left padding before processing markdown content", function () { + var doc; + runs(function () { + makeRSDoc({ config: basicConfig, + body: '\n Foo\n ===\n' + }, function (rsdoc) { doc = rsdoc; }); + }); + waitsFor(function () { return doc; }, MAXOUT); + + runs(function () { + expect($('code', doc).length).toEqual(0); + }); + }); + + it("should structure content in nested sections with appropriate titles", function () { + var doc; + runs(function () { + makeRSDoc({ config: basicConfig, + body: '\nFoo\n===\n\nBar\n---\n\nBaz\n---\n\n### Foobar ###\n\n#### Foobaz ####\n\nZing\n---\n\n' + }, function (rsdoc) { doc = rsdoc; }); + }); + waitsFor(function () { return doc; }, MAXOUT); + + runs(function () { + var $foo = $('#foo', doc); + expect($foo.prop("tagName")).toEqual('SECTION'); + expect($foo.find('> h2').length).toEqual(1); + expect($foo.find('> h2').text()).toEqual("1. Foo"); + + expect($foo.find('#bar').prop("tagName")).toEqual('SECTION'); + expect($foo.find('#bar > h3').length).toEqual(1); + expect($foo.find('#bar > h3').text()).toEqual("1.1 Bar"); + + expect($foo.find('#baz').prop("tagName")).toEqual('SECTION'); + expect($foo.find('#baz > h3').length).toEqual(1); + expect($foo.find('#baz > h3').text()).toEqual("1.2 Baz"); + + expect($foo.find('#baz > #foobar').prop("tagName")).toEqual('SECTION'); + expect($foo.find('#baz > #foobar > h4').length).toEqual(1); + expect($foo.find('#baz > #foobar > h4').text()).toEqual("1.2.1 Foobar"); + + expect($foo.find('#baz > #foobar > #foobaz').prop("tagName")).toEqual('SECTION'); + expect($foo.find('#baz > #foobar > #foobaz > h5').length).toEqual(1); + expect($foo.find('#baz > #foobar > #foobaz > h5').text()).toEqual("1.2.1.1 Foobaz"); + + expect($foo.find('#zing').prop("tagName")).toEqual('SECTION'); + expect($foo.find('#zing > h3').length).toEqual(1); + expect($foo.find('#zing > h3').text()).toEqual("1.3 Zing"); + }); + }); +}); \ No newline at end of file From 49eaaffbda27e25a03505e5efa7aad9d73dc4b23 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Thu, 18 Oct 2012 02:34:36 +0200 Subject: [PATCH 8/8] Comment-out the code dealing with markdown parsing of content nested in markup and explain why. --- js/core/markdown.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/js/core/markdown.js b/js/core/markdown.js index 7f9085aa45..0ca9b28840 100644 --- a/js/core/markdown.js +++ b/js/core/markdown.js @@ -195,7 +195,14 @@ define( run: function (conf, doc, cb, msg) { msg.pub("start", "core/markdown"); if (conf.format === 'markdown') { - this.processIssuesNotesAndReqs(doc); + // Marked, the Markdown implementation we're currently using + // parses markdown nested in markup (unless it's in a section element). + // Turns out this is both what we need and generally not what other + // parsers do. + // In case we switch to another parser later on, we'll need to + // uncomment the below line of code. + // + // this.processIssuesNotesAndReqs(doc); this.processSections(doc); var fragment = this.structure(this.processBody(doc), doc); doc.body.innerHTML = '';