Better API for parsing out blocks in the parser.
This commit is contained in:
parent
bbd79aafd1
commit
3cec95a2c3
|
@ -102,128 +102,61 @@ replaceBBCodeParams("color", function(param, contents) {
|
|||
}
|
||||
});
|
||||
|
||||
Discourse.Dialect.on("register", function(event) {
|
||||
// Handles `[code] ... [/code]` blocks
|
||||
Discourse.Dialect.replaceBlock({
|
||||
start: /(\[code\])([\s\S]*)/igm,
|
||||
stop: '[/code]',
|
||||
|
||||
var dialect = event.dialect,
|
||||
MD = event.MD;
|
||||
emitter: function(blockContents) {
|
||||
return ['p', ['pre'].concat(blockContents)];
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
Support BBCode [code] blocks
|
||||
// Support BBCode [quote] blocks
|
||||
Discourse.Dialect.replaceBlock({
|
||||
start: new RegExp("\\[quote=?([^\\[\\]]+)?\\]([\\s\\S]*)", "igm"),
|
||||
stop: '[/quote]',
|
||||
emitter: function(blockContents, matches, options) {
|
||||
|
||||
@method bbcodeCode
|
||||
@param {Markdown.Block} block the block to examine
|
||||
@param {Array} next the next blocks in the sequence
|
||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
||||
@namespace Discourse.Dialect
|
||||
**/
|
||||
dialect.inline["[code]"] = function bbcodeCode(text, orig_match) {
|
||||
var bbcodePattern = new RegExp("\\[code\\]([\\s\\S]*?)\\[\\/code\\]", "igm"),
|
||||
m = bbcodePattern.exec(text);
|
||||
var paramsString = matches[1].replace(/\"/g, ''),
|
||||
params = {'class': 'quote'},
|
||||
paramsSplit = paramsString.split(/\, */),
|
||||
username = paramsSplit[0];
|
||||
|
||||
if (m) {
|
||||
var contents = m[1].trim().split("\n");
|
||||
paramsSplit.forEach(function(p,i) {
|
||||
if (i > 0) {
|
||||
var assignment = p.split(':');
|
||||
if (assignment[0] && assignment[1]) {
|
||||
params['data-' + assignment[0]] = assignment[1].trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var html = ['pre', "\n"];
|
||||
contents.forEach(function (n) {
|
||||
html.push(n.trim());
|
||||
html.push(["br"]);
|
||||
html.push("\n");
|
||||
});
|
||||
|
||||
return [m[0].length, html];
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
Support BBCode [quote] blocks
|
||||
var contents = this.processInline(blockContents.join(" \n \n"));
|
||||
contents.unshift('blockquote');
|
||||
|
||||
@method bbcodeQuote
|
||||
@param {Markdown.Block} block the block to examine
|
||||
@param {Array} next the next blocks in the sequence
|
||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
||||
@namespace Discourse.Dialect
|
||||
**/
|
||||
dialect.block['quote'] = function bbcodeQuote(block, next) {
|
||||
var m = new RegExp("\\[quote=?([^\\[\\]]+)?\\]([\\s\\S]*)", "igm").exec(block);
|
||||
if (m) {
|
||||
var paramsString = m[1].replace(/\"/g, ''),
|
||||
params = {'class': 'quote'},
|
||||
paramsSplit = paramsString.split(/\, */),
|
||||
username = paramsSplit[0],
|
||||
opts = dialect.options,
|
||||
startPos = block.indexOf(m[0]),
|
||||
leading,
|
||||
quoteContents = [],
|
||||
result = [];
|
||||
|
||||
if (startPos > 0) {
|
||||
leading = block.slice(0, startPos);
|
||||
|
||||
var para = ['p'];
|
||||
this.processInline(leading).forEach(function (l) {
|
||||
para.push(l);
|
||||
});
|
||||
|
||||
result.push(para);
|
||||
}
|
||||
|
||||
paramsSplit.forEach(function(p,i) {
|
||||
if (i > 0) {
|
||||
var assignment = p.split(':');
|
||||
if (assignment[0] && assignment[1]) {
|
||||
params['data-' + assignment[0]] = assignment[1].trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var avatarImg;
|
||||
if (opts.lookupAvatarByPostNumber) {
|
||||
// client-side, we can retrieve the avatar from the post
|
||||
var postNumber = parseInt(params['data-post'], 10);
|
||||
avatarImg = opts.lookupAvatarByPostNumber(postNumber);
|
||||
} else if (opts.lookupAvatar) {
|
||||
// server-side, we need to lookup the avatar from the username
|
||||
avatarImg = opts.lookupAvatar(username);
|
||||
}
|
||||
|
||||
if (m[2]) { next.unshift(MD.mk_block(m[2])); }
|
||||
|
||||
while (next.length > 0) {
|
||||
var b = next.shift(),
|
||||
n = b.match(/([\s\S]*)\[\/quote\]([\s\S]*)/m);
|
||||
|
||||
if (n) {
|
||||
if (n[2]) {
|
||||
next.unshift(MD.mk_block(n[2]));
|
||||
}
|
||||
quoteContents.push(n[1]);
|
||||
break;
|
||||
} else {
|
||||
quoteContents.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
var contents = this.processInline(quoteContents.join(" \n \n"));
|
||||
contents.unshift('blockquote');
|
||||
|
||||
|
||||
result.push(['p', ['aside', params,
|
||||
return ['p', ['aside', params,
|
||||
['div', {'class': 'title'},
|
||||
['div', {'class': 'quote-controls'}],
|
||||
avatarImg ? avatarImg : "",
|
||||
I18n.t('user.said',{username: username})
|
||||
I18n.t('user.said', {username: username})
|
||||
],
|
||||
contents
|
||||
]]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
]];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Discourse.Dialect.on("parseNode", function(event) {
|
||||
|
||||
var node = event.node,
|
||||
path = event.path;
|
||||
|
||||
|
|
|
@ -204,6 +204,64 @@ Discourse.Dialect = {
|
|||
};
|
||||
},
|
||||
|
||||
replaceBlock: function(args) {
|
||||
dialect.block[args.start.toString()] = function(block, next) {
|
||||
args.start.lastIndex = 0;
|
||||
var m = (args.start).exec(block);
|
||||
if (!m) { return; }
|
||||
|
||||
var startPos = block.indexOf(m[0]),
|
||||
leading,
|
||||
blockContents = [],
|
||||
result = [],
|
||||
lineNumber = block.lineNumber;
|
||||
|
||||
if (startPos > 0) {
|
||||
leading = block.slice(0, startPos);
|
||||
lineNumber += (leading.split("\n").length - 1);
|
||||
|
||||
var para = ['p'];
|
||||
this.processInline(leading).forEach(function (l) {
|
||||
para.push(l);
|
||||
});
|
||||
|
||||
result.push(para);
|
||||
}
|
||||
if (m[2]) { next.unshift(MD.mk_block(m[2], null, lineNumber + 1)); }
|
||||
|
||||
lineNumber++;
|
||||
while (next.length > 0) {
|
||||
var b = next.shift(),
|
||||
blockLine = b.lineNumber,
|
||||
diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber;
|
||||
|
||||
var endFound = b.indexOf(args.stop),
|
||||
leadingContents = b.slice(0, endFound),
|
||||
trailingContents = b.slice(endFound+args.stop.length);
|
||||
|
||||
for (var i=1; i<diff; i++) {
|
||||
blockContents.push("");
|
||||
}
|
||||
lineNumber = blockLine + b.split("\n").length - 1;
|
||||
|
||||
if (endFound !== -1) {
|
||||
if (trailingContents) {
|
||||
next.unshift(MD.mk_block(trailingContents));
|
||||
}
|
||||
|
||||
blockContents.push(leadingContents.replace(/\s+$/, ""));
|
||||
break;
|
||||
} else {
|
||||
blockContents.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
var test = args.emitter.call(this, blockContents, m, dialect.options);
|
||||
result.push(test);
|
||||
return result;
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
After the parser has been executed, post process any text nodes in the HTML document.
|
||||
This is useful if you want to apply a transformation to the text.
|
||||
|
|
|
@ -5,77 +5,12 @@
|
|||
@event register
|
||||
@namespace Discourse.Dialect
|
||||
**/
|
||||
Discourse.Dialect.on("register", function(event) {
|
||||
var dialect = event.dialect,
|
||||
MD = event.MD;
|
||||
|
||||
/**
|
||||
Support for github style code blocks
|
||||
|
||||
@method githubCode
|
||||
@param {Markdown.Block} block the block to examine
|
||||
@param {Array} next the next blocks in the sequence
|
||||
@return {Array} the JsonML containing the markup or undefined if nothing changed.
|
||||
@namespace Discourse.Dialect
|
||||
**/
|
||||
dialect.block.github_code = function githubCode(block, next) {
|
||||
|
||||
var m = /^`{3}([^\n]+)?\n?([\s\S]*)?/gm.exec(block);
|
||||
|
||||
if (m) {
|
||||
var startPos = block.indexOf(m[0]),
|
||||
leading,
|
||||
codeContents = [],
|
||||
result = [],
|
||||
lineNumber = block.lineNumber;
|
||||
|
||||
if (startPos > 0) {
|
||||
leading = block.slice(0, startPos);
|
||||
lineNumber += (leading.split("\n").length - 1);
|
||||
|
||||
var para = ['p'];
|
||||
this.processInline(leading).forEach(function (l) {
|
||||
para.push(l);
|
||||
});
|
||||
|
||||
result.push(para);
|
||||
}
|
||||
|
||||
if (m[2]) { next.unshift(MD.mk_block(m[2], null, lineNumber + 1)); }
|
||||
|
||||
lineNumber++;
|
||||
while (next.length > 0) {
|
||||
var b = next.shift(),
|
||||
blockLine = b.lineNumber,
|
||||
diff = ((typeof blockLine === "undefined") ? lineNumber : blockLine) - lineNumber;
|
||||
|
||||
var endFound = b.indexOf('```'),
|
||||
leadingCode = b.slice(0, endFound),
|
||||
trailingCode = b.slice(endFound+3);
|
||||
|
||||
for (var i=1; i<diff; i++) {
|
||||
codeContents.push("");
|
||||
}
|
||||
lineNumber = blockLine + b.split("\n").length - 1;
|
||||
|
||||
if (endFound !== -1) {
|
||||
if (trailingCode) {
|
||||
next.unshift(MD.mk_block(trailingCode));
|
||||
}
|
||||
|
||||
codeContents.push(leadingCode.replace(/\s+$/, ""));
|
||||
break;
|
||||
} else {
|
||||
codeContents.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
result.push(['p', ['pre', ['code', {'class': m[1] || 'lang-auto'}, codeContents.join("\n") ]]]);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
Discourse.Dialect.replaceBlock({
|
||||
start: /^`{3}([^\n]+)?\n?([\s\S]*)?/gm,
|
||||
stop: '```',
|
||||
emitter: function(blockContents, matches) {
|
||||
return ['p', ['pre', ['code', {'class': matches[1] || 'lang-auto'}, blockContents.join("\n") ]]];
|
||||
}
|
||||
});
|
||||
|
||||
// Ensure that content in a code block is fully escaped. This way it's not white listed
|
||||
|
|
|
@ -14,7 +14,7 @@ describe PrettyText do
|
|||
end
|
||||
|
||||
it "produces a quote even with new lines in it" do
|
||||
PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]").should match_html "<p><aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout said:</div>\n<blockquote>ddd<br>\n</blockquote></aside></p>"
|
||||
PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]").should match_html "<p><aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout said:</div>\n<blockquote>ddd</blockquote></aside></p>"
|
||||
end
|
||||
|
||||
it "should produce a quote" do
|
||||
|
|
|
@ -11,8 +11,8 @@ test('basic bbcode', function() {
|
|||
format("[i]emphasis[/i]", "<span class=\"bbcode-i\">emphasis</span>", "italics text");
|
||||
format("[u]underlined[/u]", "<span class=\"bbcode-u\">underlined</span>", "underlines text");
|
||||
format("[s]strikethrough[/s]", "<span class=\"bbcode-s\">strikethrough</span>", "strikes-through text");
|
||||
format("[code]\nx++\n[/code]", "<pre>\nx++<br/>\n</pre>", "makes code into pre");
|
||||
format("[code]\nx++\ny++\nz++\n[/code]", "<pre>\nx++<br/>\ny++<br/>\nz++<br/>\n</pre>", "makes code into pre");
|
||||
format("[code]\nx++\n[/code]", "<pre>\nx++</pre>", "makes code into pre");
|
||||
format("[code]\nx++\ny++\nz++\n[/code]", "<pre>\nx++\ny++\nz++</pre>", "makes code into pre");
|
||||
format("[spoiler]it's a sled[/spoiler]", "<span class=\"spoiler\">it's a sled</span>", "supports spoiler tags");
|
||||
format("[img]http://eviltrout.com/eviltrout.png[/img]", "<img src=\"http://eviltrout.com/eviltrout.png\"/>", "links images");
|
||||
format("[url]http://bettercallsaul.com[/url]", "<a href=\"http://bettercallsaul.com\">http://bettercallsaul.com</a>", "supports [url] without a title");
|
||||
|
|
|
@ -114,7 +114,7 @@ test("Quotes", function() {
|
|||
cookedOptions("[quote=\"eviltrout, post: 1\"]\na quote\n\nsecond line\n[/quote]",
|
||||
{ topicId: 2 },
|
||||
"<p><aside class=\"quote\" data-post=\"1\"><div class=\"title\"><div class=\"quote-controls\"></div>eviltrout said:</div><blockquote>" +
|
||||
"a quote<br/><br/>second line<br/></blockquote></aside></p>",
|
||||
"a quote<br/><br/>second line</blockquote></aside></p>",
|
||||
"works with multiple lines");
|
||||
|
||||
cookedOptions("1[quote=\"bob, post:1\"]my quote[/quote]2",
|
||||
|
|
Loading…
Reference in New Issue