FIX: BBCode contents can span multiple lines

This commit is contained in:
Robin Ward 2014-08-22 14:32:06 -04:00
parent 5454c1ed24
commit 300dbdc88b
3 changed files with 94 additions and 97 deletions

View File

@ -13,6 +13,57 @@
@param {Boolean} [opts.wordBoundary] If true, the match must be on a word boundary
@param {Boolean} [opts.spaceBoundary] If true, the match must be on a sppace boundary
**/
Discourse.BBCode = {};
Discourse.BBCode.register = function(codeName, args, emitter) {
// Optional second param for args
if (typeof args === "function") {
emitter = args;
args = {};
}
Discourse.Dialect.replaceBlock({
start: new RegExp("\\[" + codeName + "(=[^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
stop: new RegExp("\\[\\/" + codeName + "\\]", "igm"),
emitter: function(blockContents, matches, options) {
while (blockContents.length && (typeof blockContents[0] === "string" || blockContents[0] instanceof String)) {
blockContents[0] = String(blockContents[0]).replace(/^\s+/, '');
if (!blockContents[0].length) {
blockContents.shift();
} else {
break;
}
}
var contents = [];
if (blockContents.length) {
var self = this;
var nextContents = blockContents.slice(1);
blockContents = this.processBlock(blockContents[0], nextContents).concat(nextContents);
blockContents.forEach(function (bc) {
if (typeof bc === "string" || bc instanceof String) {
var processed = self.processInline(String(bc));
if (processed.length) {
contents.push(['p'].concat(processed));
}
} else {
contents.push(bc);
}
});
}
if (!args.singlePara && contents.length === 1) {
contents[0].shift();
contents = contents[0];
}
var result = emitter(contents, matches[1] ? matches[1].replace(/^=|\"/g, '') : null, options);
return args.noWrap ? result : ['p', result];
}
});
};
function replaceBBCode(tag, emitter, opts) {
opts = opts || {};
opts = _.merge(opts, { start: "[" + tag + "]", stop: "[/" + tag + "]", emitter: emitter });
@ -69,20 +120,6 @@ function removeEmptyLines(contents) {
return result;
}
/**
Creates a BBCode handler that accepts parameters. Passes them to the emitter.
Processes the inside recursively so it can be nested.
@method replaceBBCodeParams
@param {tag} tag the tag we want to match
@param {function} emitter the function that creates JsonML for the tag
**/
function replaceBBCodeParams(tag, emitter) {
replaceBBCodeParamsRaw(tag, function (param, contents) {
return emitter(param, this.processInline(contents));
});
}
replaceBBCode('b', function(contents) { return ['span', {'class': 'bbcode-b'}].concat(contents); });
replaceBBCode('i', function(contents) { return ['span', {'class': 'bbcode-i'}].concat(contents); });
replaceBBCode('u', function(contents) { return ['span', {'class': 'bbcode-u'}].concat(contents); });
@ -112,8 +149,8 @@ replaceBBCodeParamsRaw("email", function(param, contents) {
return ['a', {href: "mailto:" + param, 'data-bbcode': true}, contents];
});
replaceBBCodeParams("size", function(param, contents) {
return ['span', {'class': "bbcode-size-" + (parseInt(param, 10) || 1)}].concat(contents);
Discourse.BBCode.register('size', function(contents, params) {
return ['span', {'class': "bbcode-size-" + (parseInt(params, 10) || 1)}].concat(contents);
});
Discourse.Markdown.whiteListTag('span', 'class', /^bbcode-size-\d+$/);

View File

@ -1,87 +1,47 @@
/**
Support for quoting other users.
**/
var esc = Handlebars.Utils.escapeExpression;
Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(contents, bbParams, options) {
var params = {'class': 'quote'},
username = null;
Discourse.Dialect.replaceBlock({
start: new RegExp("\\[quote(=[^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
stop: /\[\/quote\]/igm,
emitter: function(blockContents, matches, options) {
if (bbParams) {
var paramsSplit = bbParams.split(/\,\s*/);
username = paramsSplit[0];
var params = {'class': 'quote'},
username = null;
if (matches[1]) {
var paramsString = matches[1].replace(/^=|\"/g, ''),
paramsSplit = paramsString.split(/\,\s*/);
username = paramsSplit[0];
paramsSplit.forEach(function(p,i) {
if (i > 0) {
var assignment = p.split(':');
if (assignment[0] && assignment[1]) {
var escaped = esc(assignment[0]);
// don't escape attributes, makes no sense
if(escaped === assignment[0]) {
params['data-' + assignment[0]] = esc(assignment[1].trim());
}
paramsSplit.forEach(function(p,i) {
if (i > 0) {
var assignment = p.split(':');
if (assignment[0] && assignment[1]) {
var escaped = esc(assignment[0]);
// don't escape attributes, makes no sense
if(escaped === assignment[0]) {
params['data-' + assignment[0]] = esc(assignment[1].trim());
}
}
});
}
var avatarImg;
if (options.lookupAvatarByPostNumber) {
// client-side, we can retrieve the avatar from the post
var postNumber = parseInt(params['data-post'], 10);
avatarImg = options.lookupAvatarByPostNumber(postNumber);
} else if (options.lookupAvatar) {
// server-side, we need to lookup the avatar from the username
avatarImg = options.lookupAvatar(username);
}
while (blockContents.length && (typeof blockContents[0] === "string" || blockContents[0] instanceof String)) {
blockContents[0] = String(blockContents[0]).replace(/^\s+/, '');
if (!blockContents[0].length) {
blockContents.shift();
} else {
break;
}
}
var contents = ['blockquote'];
if (blockContents.length) {
var self = this;
var nextContents = blockContents.slice(1);
blockContents = this.processBlock(blockContents[0], nextContents).concat(nextContents);
blockContents.forEach(function (bc) {
if (typeof bc === "string" || bc instanceof String) {
var processed = self.processInline(String(bc));
if (processed.length) {
contents.push(['p'].concat(processed));
}
} else {
contents.push(bc);
}
});
}
// If there's no username just return a simple quote
if (!username) {
return ['p', ['aside', params, contents]];
}
return ['aside', params,
['div', {'class': 'title'},
['div', {'class': 'quote-controls'}],
avatarImg ? ['__RAW', avatarImg] : "",
username ? I18n.t('user.said', {username: username}) : ""
],
contents
];
});
}
var avatarImg;
if (options.lookupAvatarByPostNumber) {
// client-side, we can retrieve the avatar from the post
var postNumber = parseInt(params['data-post'], 10);
avatarImg = options.lookupAvatarByPostNumber(postNumber);
} else if (options.lookupAvatar) {
// server-side, we need to lookup the avatar from the username
avatarImg = options.lookupAvatar(username);
}
// If there's no username just return a simple quote
if (!username) {
return ['p', ['aside', params, ['blockquote'].concat(contents)]];
}
return ['aside', params,
['div', {'class': 'title'},
['div', {'class': 'quote-controls'}],
avatarImg ? ['__RAW', avatarImg] : "",
username ? I18n.t('user.said', {username: username}) : ""
],
['blockquote'].concat(contents)
];
});

View File

@ -62,8 +62,8 @@ test("size tags", function() {
format("[size=asdf]regular[/size]",
"<span class=\"bbcode-size-1\">regular</span>",
"it only supports numbers in bbcode");
format("[size=35]\nNEWLINE\n[/size]",
"<span class=\"bbcode-size-35\"><br>NEWLINE<br></span>",
format("[size=35]NEWLINE\n\ntest[/size]",
"<span class=\"bbcode-size-35\"><p>NEWLINE</p><p>test</p></span>",
"works with newlines");
});