FIX: Don't double sanitize values, allow blockquotes with leading text

This commit is contained in:
Robin Ward 2013-10-18 15:20:27 -04:00
parent d7182d0b14
commit 1113b8d7a8
4 changed files with 92 additions and 37 deletions

View File

@ -7,7 +7,8 @@
var parser = window.BetterMarkdown,
MD = parser.Markdown,
dialect = MD.dialects.Discourse = MD.subclassDialect( MD.dialects.Gruber ),
initialized = false;
initialized = false,
emitters = [];
/**
Initialize our dialects for processing.
@ -21,6 +22,51 @@ function initializeDialects() {
initialized = true;
}
/**
Process the text nodes in the JsonML tree, calling any emitters that have
been added.
@method processTextNodes
@param {Array} node the JsonML tree
@param {Object} event the parse node event data
**/
function processTextNodes(node, event) {
if (node.length < 2) { return; }
if (node[0] === '__RAW') {
return;
}
var skipSanitize = [];
for (var j=1; j<node.length; j++) {
var textContent = node[j];
if (typeof textContent === "string") {
if (dialect.options.sanitize && !skipSanitize[textContent]) {
textContent = Discourse.Markdown.sanitize(textContent);
}
var result = textContent;
emitters.forEach(function (e) {
result = e(result, event)
});
if (result) {
if (result instanceof Array) {
result.forEach(function (r) { skipSanitize[r] = true; });
node.splice.apply(node, [j, 1].concat(result));
} else {
node[j] = result;
}
} else {
node[j] = textContent;
}
}
}
}
/**
Parse a JSON ML tree, using registered handlers to adjust it if necessary.
@ -33,8 +79,9 @@ function initializeDialects() {
function parseTree(tree, path, insideCounts) {
if (tree instanceof Array) {
Discourse.Dialect.trigger('parseNode', {node: tree, path: path, dialect: dialect, insideCounts: insideCounts || {}});
var event = {node: tree, path: path, dialect: dialect, insideCounts: insideCounts || {}};
Discourse.Dialect.trigger('parseNode', event);
processTextNodes(tree, event);
path = path || [];
insideCounts = insideCounts || {};
@ -100,7 +147,9 @@ Discourse.Dialect = {
if (!initialized) { initializeDialects(); }
dialect.options = opts;
var tree = parser.toHTMLTree(text, 'Discourse');
return parser.renderJsonML(parseTree(tree));
html = parser.renderJsonML(parseTree(tree));
return html;
},
/**
@ -363,36 +412,7 @@ Discourse.Dialect = {
@param {Function} emitter The function to call with the text. It returns JsonML to modify the tree.
**/
postProcessText: function(emitter) {
Discourse.Dialect.on("parseNode", function(event) {
var node = event.node;
if (node.length < 2) { return; }
if (node[0] === '__RAW') {
return;
}
for (var j=1; j<node.length; j++) {
var textContent = node[j];
if (typeof textContent === "string") {
if (dialect.options.sanitize) {
textContent = Discourse.Markdown.sanitize(textContent);
}
var result = emitter(textContent, event);
if (result) {
if (result instanceof Array) {
node.splice.apply(node, [j, 1].concat(result));
} else {
node[j] = result;
}
} else {
node[j] = textContent;
}
}
}
});
emitters.push(emitter);
},
/**

View File

@ -4,14 +4,38 @@
var blockTags = ['address', 'article', 'aside', 'audio', 'blockquote', 'canvas', 'dd', 'div',
'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'noscript', 'ol', 'output',
'p', 'pre', 'section', 'table', 'tfoot', 'ul', 'video'];
'p', 'pre', 'section', 'table', 'tfoot', 'ul', 'video'],
splitAtLast = function(tag, block, next, first) {
var endTag = "</" + tag + ">",
endTagIndex = first ? block.indexOf(endTag) : block.lastIndexOf(endTag);
if (endTagIndex !== -1) {
endTagIndex += endTag.length;
var leading = block.substr(0, endTagIndex),
trailing = block.substr(endTagIndex).replace(/^\s+/, '');
if (trailing.length) {
next.unshift(trailing);
}
return [ leading ];
}
};
Discourse.Dialect.registerBlock('html', function(block, next) {
var m = /^<([^>]+)\>/.exec(block);
// Fix manual blockquote paragraphing even though it's not strictly correct
var split = splitAtLast('blockquote', block, next, true);
if (split) { return split; }
var m = /^<([^>]+)\>/m.exec(block);
if (m && m[1]) {
var tag = m[1].split(/\s/);
if (tag && tag[0] && blockTags.indexOf(tag[0]) !== -1) {
split = splitAtLast(tag[0], block, next);
if (split) { return split; }
return [ block.toString() ];
}
}

View File

@ -10,10 +10,11 @@ Discourse.Dialect.postProcessText(function (text, event) {
if (linebreaks || (insideCounts.pre > 0)) { return; }
if (text === "\n") {
// If the tage is just a new line, replace it with a `<br>`
// If the tag is just a new line, replace it with a `<br>`
return [['br']];
} else {
// If the text node contains new lines, perhaps with text between them, insert the
// `<br>` tags.
var split = text.split(/\n+/);
@ -25,6 +26,7 @@ Discourse.Dialect.postProcessText(function (text, event) {
if (i !== split.length-1) { replacement.push(['br']); }
}
}
return replacement;
}
}

View File

@ -54,6 +54,14 @@ test("Line Breaks", function() {
cooked("[] first choice\n[] second choice",
"<p>[] first choice<br/>[] second choice</p>",
"it handles new lines correctly with [] options");
cooked("<blockquote>evil</blockquote>\ntrout",
"<blockquote>evil</blockquote>\n\n<p>trout</p>",
"it doesn't insert <br> after blockquotes");
cooked("leading<blockquote>evil</blockquote>\ntrout",
"leading<blockquote>evil</blockquote>\n\n<p>trout</p>",
"it doesn't insert <br> after blockquotes with leading text");
});
test("Paragraphs for HTML", function() {
@ -311,6 +319,7 @@ test("sanitize", function() {
"<p><a href=\"http://disneyland.disney.go.com/\">disney</a> <a href=\"http://reddit.com\">reddit</a></p>",
"we can embed proper links");
cooked("<blockquote>a\n</blockquote>\n", "<blockquote>a\n\n<br/>\n\n</blockquote>", "it does not double sanitize");
});
test("URLs in BBCode tags", function() {