quoting fixes

- allow bbcode quotes to be nested
- don't allow the '=' to be omitted from quotes
- fix some css that made assumptions about nested quotes
This commit is contained in:
Ben Lubar
2014-05-27 21:46:31 -05:00
parent f6753d3d46
commit 73946e5402
5 changed files with 64 additions and 29 deletions

View File

@ -105,6 +105,7 @@ replaceBBCodeParams("size", function(param, contents) {
Discourse.Dialect.replaceBlock({ Discourse.Dialect.replaceBlock({
start: /(\[code\])([\s\S]*)/igm, start: /(\[code\])([\s\S]*)/igm,
stop: '[/code]', stop: '[/code]',
rawContents: true,
emitter: function(blockContents) { emitter: function(blockContents) {
return ['p', ['pre'].concat(blockContents.join("\n"))]; return ['p', ['pre'].concat(blockContents.join("\n"))];

View File

@ -305,6 +305,7 @@ Discourse.Dialect = {
Discourse.Dialect.replaceBlock({ Discourse.Dialect.replaceBlock({
start: /(\[code\])([\s\S]*)/igm, start: /(\[code\])([\s\S]*)/igm,
stop: '[/code]', stop: '[/code]',
rawContents: true,
emitter: function(blockContents) { emitter: function(blockContents) {
return ['p', ['pre'].concat(blockContents)]; return ['p', ['pre'].concat(blockContents)];
@ -314,9 +315,10 @@ Discourse.Dialect = {
@method replaceBlock @method replaceBlock
@param {Object} args Our replacement options @param {Object} args Our replacement options
@param {String} [opts.start] The starting regexp we want to find @param {RegExp} [args.start] The starting regexp we want to find
@param {String} [opts.stop] The ending token we want to find @param {String} [args.stop] The ending token we want to find
@param {Function} [opts.emitter] The emitting function to transform the contents of the block into jsonML @param {Boolean} [args.rawContents] True to skip recursive processing
@param {Function} [args.emitter] The emitting function to transform the contents of the block into jsonML
**/ **/
replaceBlock: function(args) { replaceBlock: function(args) {
@ -327,7 +329,7 @@ Discourse.Dialect = {
if (!m) { return; } if (!m) { return; }
var startPos = block.indexOf(m[0]), var startPos = args.start.lastIndex - m[0].length,
leading, leading,
blockContents = [], blockContents = [],
result = [], result = [],
@ -351,14 +353,11 @@ Discourse.Dialect = {
lineNumber++; lineNumber++;
var blockClosed = false; var blockClosed = false;
if (next.length > 0) { for (var i=0; i<next.length; i++) {
for (var i=0; i<next.length; i++) { if (next[i].indexOf(args.stop) >= 0) {
if (next[i].indexOf(args.stop) >= 0) { blockClosed = true;
blockClosed = true; break;
break;
}
} }
} }
@ -367,33 +366,52 @@ Discourse.Dialect = {
return; return;
} }
var numOpen = 1;
while (next.length > 0) { while (next.length > 0) {
var b = next.shift(), var b = next.shift(),
blockLine = b.lineNumber, blockLine = b.lineNumber,
diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber, diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber,
endFound = b.indexOf(args.stop), endFound = b.indexOf(args.stop),
leadingContents = b.slice(0, endFound), leadingContents = b.slice(0, endFound),
trailingContents = b.slice(endFound+args.stop.length); trailingContents = b.slice(endFound+args.stop.length),
m2;
if (endFound >= 0) { blockClosed = true; } if (endFound === -1) {
leadingContents = b;
}
args.start.lastIndex = 0;
if (m2 = (args.start).exec(leadingContents)) {
numOpen++;
args.start.lastIndex -= m2[0].length - 1;
while (m2 = (args.start).exec(leadingContents)) {
numOpen++;
args.start.lastIndex -= m2[0].length - 1;
}
}
if (endFound >= 0) { numOpen--; }
for (var j=1; j<diff; j++) { for (var j=1; j<diff; j++) {
blockContents.push(""); blockContents.push("");
} }
lineNumber = blockLine + b.split("\n").length - 1; lineNumber = blockLine + b.split("\n").length - 1;
if (endFound !== -1) { if (endFound >= 0) {
if (trailingContents) { if (trailingContents) {
next.unshift(MD.mk_block(trailingContents.replace(/^\s+/, ""))); next.unshift(MD.mk_block(trailingContents.replace(/^\s+/, "")));
} }
blockContents.push(leadingContents.replace(/\s+$/, "")); blockContents.push(leadingContents.replace(/\s+$/, ""));
break;
if (numOpen === 0) {
break;
}
blockContents.push(args.stop);
} else { } else {
blockContents.push(b); blockContents.push(b);
} }
} }
var emitterResult = args.emitter.call(this, blockContents, m, dialect.options); var emitterResult = args.emitter.call(this, blockContents, m, dialect.options);
if (emitterResult) { if (emitterResult) {
result.push(emitterResult); result.push(emitterResult);

View File

@ -5,16 +5,16 @@
var esc = Handlebars.Utils.escapeExpression; var esc = Handlebars.Utils.escapeExpression;
Discourse.Dialect.replaceBlock({ Discourse.Dialect.replaceBlock({
start: new RegExp("\\[quote=?([^\\[\\]]+)?\\]([\\s\\S]*)", "igm"), start: new RegExp("\\[quote(=[^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
stop: '[/quote]', stop: '[/quote]',
emitter: function(blockContents, matches, options) { emitter: function(blockContents, matches, options) {
var params = {'class': 'quote'}, var params = {'class': 'quote'},
username; username = null;
if (matches[1]) { if (matches[1]) {
var paramsString = matches[1].replace(/\"/g, ''), var paramsString = matches[1].replace(/^=|\"/g, ''),
paramsSplit = paramsString.split(/\, */); paramsSplit = paramsString.split(/\,\s*/);
username = paramsSplit[0]; username = paramsSplit[0];
@ -38,25 +38,37 @@ Discourse.Dialect.replaceBlock({
avatarImg = options.lookupAvatar(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']; var contents = ['blockquote'];
if (blockContents.length) { if (blockContents.length) {
var self = this; var self = this;
if (blockContents && (typeof blockContents[0] === "string")) { var nextContents = blockContents.slice(1);
blockContents[0] = blockContents[0].replace(/^[\s]*/, ''); blockContents = this.processBlock(blockContents[0], nextContents).concat(nextContents);
}
blockContents.forEach(function (bc) { blockContents.forEach(function (bc) {
var processed = self.processInline(bc); if (typeof bc === "string" || bc instanceof String) {
if (processed.length) { var processed = self.processInline(String(bc));
contents.push(['p'].concat(processed)); if (processed.length) {
contents.push(['p'].concat(processed));
}
} else {
contents.push(bc);
} }
}); });
} }
// If there's no username just return a simple quote // If there's no username just return a simple quote
if (!username) { if (!username) {
return ['p', ['aside', params, contents ]]; return ['p', ['aside', params, contents]];
} }
return ['p', ['aside', params, return ['p', ['aside', params,

View File

@ -686,7 +686,7 @@ blockquote { /* solo quotes */
} }
.quote { /* quotes with attribution */ .quote { /* quotes with attribution */
blockquote { &>blockquote {
margin-top: 0; margin-top: 0;
padding-top: 0; padding-top: 0;
p:first-of-type {margin:0;} p:first-of-type {margin:0;}
@ -708,6 +708,10 @@ blockquote { /* solo quotes */
background: darken(scale-color-diff(), 5%); background: darken(scale-color-diff(), 5%);
border-left: 5px solid darken(scale-color-diff(), 12%); border-left: 5px solid darken(scale-color-diff(), 12%);
} }
aside.quote>blockquote, aside.quote>.title {
border-left: 0;
}
} }
} }

View File

@ -99,7 +99,7 @@ test("quotes", function() {
"<aside class=\"quote\"><blockquote><p><em>test</em></p></blockquote></aside>", "<aside class=\"quote\"><blockquote><p><em>test</em></p></blockquote></aside>",
"it doesn't insert a new line for italics"); "it doesn't insert a new line for italics");
format("[quote,script='a'><script>alert('test');//':a][/quote]", format("[quote=,script='a'><script>alert('test');//':a][/quote]",
"<aside class=\"quote\" data-script=&#x27;a&#x27;&gt;&lt;script&gt;alert(&#x27;test&#x27;);//&#x27;=\"a\"><blockquote></blockquote></aside>", "<aside class=\"quote\" data-script=&#x27;a&#x27;&gt;&lt;script&gt;alert(&#x27;test&#x27;);//&#x27;=\"a\"><blockquote></blockquote></aside>",
"It will not create a script tag within an attribute"); "It will not create a script tag within an attribute");
}); });