New API for replacing elements in the final JsonML. Also changes spoiler

tag handling to be more robust with repsect to HTML content.
This commit is contained in:
Robin Ward 2014-01-20 15:09:05 -05:00
parent 2d98720cc8
commit 6b9b2d3d6a
3 changed files with 92 additions and 8 deletions

View File

@ -78,16 +78,23 @@ Discourse.Dialect.inlineBetween({
emitter: function(contents) { return ['a', {href: contents, 'data-bbcode': true}, contents]; }
});
Discourse.Dialect.inlineBetween({
start: '[spoiler]',
stop: '[/spoiler]',
rawContents: true,
emitter: function(contents) {
if (/<img/i.test(contents)) {
return ['div', { 'class': 'spoiler' }, contents];
} else {
return ['span', { 'class': 'spoiler' }, contents];
}
return ["__spoiler", this.processInline(contents)];
}
});
Discourse.Dialect.replaceElement('__spoiler', function(node, processInside) {
// Surround images with a `<div class='spoiler'>`, any other tags
// end up in a `span`
if (node.nodeType === 1 && node.nodeName === "IMG") {
return ['div', {class: 'spoiler'}, processInside(node)];
} else {
return ['span', {class: 'spoiler'}, processInside(node)];
}
});

View File

@ -22,6 +22,50 @@ function initializeDialects() {
initialized = true;
}
/**
Converts an HTML node Element into JsonML.
@method nodeToJsonML
@param {Element} node to convert
@returns {Array} jsonML result
**/
function nodeToJsonML(node) {
if (node.nodeType === 3) {
return node.nodeValue;
}
var result = [node.nodeName.toLowerCase()],
attributes = {};
if (node.attributes && node.attributes.length > 0) {
for (var i=0; i<node.attributes.length; i++) {
var attr = node.attributes[i];
attributes[attr.name] = attr.value;
}
result.push(attributes);
}
var innerResult = eachNodeList(node.childNodes, nodeToJsonML);
return result.concat(innerResult);
}
/**
Iterate through a NodeList and apply a function, returning
a concatenated array of results.
@method eachNodeList
@param {NodeList} nodeList to iterate through
@param {Function} f to call
**/
function eachNodeList(nodeList, f) {
if (!nodeList || nodeList.length === 0) { return []; }
var result = [];
for (var i=0; i<nodeList.length; i++) {
result.push(f(nodeList[i], nodeToJsonML));
}
return result;
}
/**
Process the text nodes in the JsonML tree, calling any emitters that have
been added.
@ -453,6 +497,32 @@ Discourse.Dialect = {
node[node.length-1] = emitter(node[node.length-1]);
}
});
},
/**
Replaces an element by name in the final jsonML tree by parsing the HTML
and using a `replacer` callback.
@method replaceElement
@param {String} nodeName to match
@param {Function} replacer function to call on each node
**/
replaceElement: function(nodeName, replacer) {
Discourse.Dialect.on('parseNode', function(event) {
var node = event.node;
if (node[0] === nodeName) {
var doc = document.implementation.createHTMLDocument("");
if (doc) {
doc.documentElement.innerHTML = node[1];
if (doc.body) {
var jsonML = eachNodeList(doc.body.childNodes, replacer),
parentNode = event.path[event.path.length-1];
parentNode.splice.apply(parentNode, [parentNode.indexOf(node), 1].concat(jsonML));
}
}
}
});
}
};

View File

@ -1,8 +1,8 @@
module("Discourse.BBCode");
var format = function(input, expected, text) {
var cooked = Discourse.Markdown.cook(input, {lookupAvatar: false});
equal(cooked, "<p>" + expected + "</p>", text);
var cooked = Discourse.Markdown.cook(input, {lookupAvatar: false}).replace(/'/g, "\"");
equal(cooked, "<p>" + expected.replace(/'/g, "\"") + "</p>", text);
};
test('basic bbcode', function() {
@ -30,8 +30,15 @@ test('code', function() {
});
test('spoiler', function() {
format("[spoiler][/spoiler]", "", "it can spoil nothing");
format("[spoiler]it's a sled[/spoiler]", "<span class=\"spoiler\">it's a sled</span>", "supports spoiler tags on text");
format("[spoiler]<img src='http://eviltrout.com/eviltrout.png' width='50' height='50'>[/spoiler]", "<div class=\"spoiler\"><img src='http://eviltrout.com/eviltrout.png' width='50' height='50'></div>", "supports spoiler tags on images");
format("[spoiler]<img src='http://eviltrout.com/eviltrout.png' width='50' height='50'>[/spoiler]", "<div class=\"spoiler\"><img src='http://eviltrout.com/eviltrout.png' width='50' height='50'/></div>", "supports spoiler tags on images");
format("foo bar[spoiler]aaa <img src='#'> bbb[/spoiler]",
"foo bar<span class='spoiler'>aaa </span><div class='spoiler'><img src='#'/></div><span class='spoiler'> bbb</span>",
"It wraps images and text differently in the same block");
format("[spoiler]<a href='#'>hello</a>[/spoiler]",
"<span class='spoiler'><a href='#'>hello</a></span>",
"It surrounds other HTML");
});
test('lists', function() {