mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 23:07:28 +08:00
FIX: BBCode contents can span multiple lines
This commit is contained in:
@ -13,6 +13,57 @@
|
|||||||
@param {Boolean} [opts.wordBoundary] If true, the match must be on a word boundary
|
@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
|
@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) {
|
function replaceBBCode(tag, emitter, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
opts = _.merge(opts, { start: "[" + tag + "]", stop: "[/" + tag + "]", emitter: emitter });
|
opts = _.merge(opts, { start: "[" + tag + "]", stop: "[/" + tag + "]", emitter: emitter });
|
||||||
@ -69,20 +120,6 @@ function removeEmptyLines(contents) {
|
|||||||
return result;
|
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('b', function(contents) { return ['span', {'class': 'bbcode-b'}].concat(contents); });
|
||||||
replaceBBCode('i', function(contents) { return ['span', {'class': 'bbcode-i'}].concat(contents); });
|
replaceBBCode('i', function(contents) { return ['span', {'class': 'bbcode-i'}].concat(contents); });
|
||||||
replaceBBCode('u', function(contents) { return ['span', {'class': 'bbcode-u'}].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];
|
return ['a', {href: "mailto:" + param, 'data-bbcode': true}, contents];
|
||||||
});
|
});
|
||||||
|
|
||||||
replaceBBCodeParams("size", function(param, contents) {
|
Discourse.BBCode.register('size', function(contents, params) {
|
||||||
return ['span', {'class': "bbcode-size-" + (parseInt(param, 10) || 1)}].concat(contents);
|
return ['span', {'class': "bbcode-size-" + (parseInt(params, 10) || 1)}].concat(contents);
|
||||||
});
|
});
|
||||||
Discourse.Markdown.whiteListTag('span', 'class', /^bbcode-size-\d+$/);
|
Discourse.Markdown.whiteListTag('span', 'class', /^bbcode-size-\d+$/);
|
||||||
|
|
||||||
|
@ -1,87 +1,47 @@
|
|||||||
/**
|
|
||||||
Support for quoting other users.
|
|
||||||
**/
|
|
||||||
|
|
||||||
var esc = Handlebars.Utils.escapeExpression;
|
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({
|
if (bbParams) {
|
||||||
start: new RegExp("\\[quote(=[^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
|
var paramsSplit = bbParams.split(/\,\s*/);
|
||||||
stop: /\[\/quote\]/igm,
|
username = paramsSplit[0];
|
||||||
emitter: function(blockContents, matches, options) {
|
|
||||||
|
|
||||||
var params = {'class': 'quote'},
|
paramsSplit.forEach(function(p,i) {
|
||||||
username = null;
|
if (i > 0) {
|
||||||
|
var assignment = p.split(':');
|
||||||
if (matches[1]) {
|
if (assignment[0] && assignment[1]) {
|
||||||
var paramsString = matches[1].replace(/^=|\"/g, ''),
|
var escaped = esc(assignment[0]);
|
||||||
paramsSplit = paramsString.split(/\,\s*/);
|
// don't escape attributes, makes no sense
|
||||||
|
if(escaped === assignment[0]) {
|
||||||
username = paramsSplit[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)
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
@ -62,8 +62,8 @@ test("size tags", function() {
|
|||||||
format("[size=asdf]regular[/size]",
|
format("[size=asdf]regular[/size]",
|
||||||
"<span class=\"bbcode-size-1\">regular</span>",
|
"<span class=\"bbcode-size-1\">regular</span>",
|
||||||
"it only supports numbers in bbcode");
|
"it only supports numbers in bbcode");
|
||||||
format("[size=35]\nNEWLINE\n[/size]",
|
format("[size=35]NEWLINE\n\ntest[/size]",
|
||||||
"<span class=\"bbcode-size-35\"><br>NEWLINE<br></span>",
|
"<span class=\"bbcode-size-35\"><p>NEWLINE</p><p>test</p></span>",
|
||||||
"works with newlines");
|
"works with newlines");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user