mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-02-17 04:01:29 -05:00
FIX: Don't double sanitize values, allow blockquotes with leading text
This commit is contained in:
parent
d7182d0b14
commit
1113b8d7a8
4 changed files with 92 additions and 37 deletions
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -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() ];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in a new issue