diff --git a/app/assets/javascripts/discourse/components/bbcode.js b/app/assets/javascripts/discourse/components/bbcode.js index dc475815737..fe5bae14a0a 100644 --- a/app/assets/javascripts/discourse/components/bbcode.js +++ b/app/assets/javascripts/discourse/components/bbcode.js @@ -3,14 +3,14 @@ /** Support for BBCode rendering - @class BBCode + @class BBCode @namespace Discourse @module Discourse -**/ +**/ Discourse.BBCode = { QUOTE_REGEXP: /\[quote=([^\]]*)\]([\s\S]*?)\[\/quote\]/im, - + // Define our replacers replacers: { base: { @@ -45,9 +45,7 @@ Discourse.BBCode = { "spoiler": function(_, content) { return "" + content + ""; } }, withArgs: { - "size": function(_, size, content) { - return "" + content + ""; - } + "size": function(_, size, content) { return "" + content + ""; } } }, @@ -62,49 +60,62 @@ Discourse.BBCode = { } }, withArgs: { - "size": function(_, size, content) { - return "" + content + ""; - } + "size": function(_, size, content) { return "" + content + ""; } } } }, - // Apply a particular set of replacers - apply: function(text, environment) { - var replacer; - replacer = Discourse.BBCode.parsedReplacers()[environment]; + /** + Apply a particular set of replacers + @method apply + @param {String} text The text we want to format + @param {String} environment The environment in which this + **/ + apply: function(text, environment) { + var replacer = Discourse.BBCode.parsedReplacers()[environment]; + // apply all available replacers replacer.forEach(function(r) { text = text.replace(r.regexp, r.fn); }); return text; }, + /** + Lazy parse replacers + + @property parsedReplacers + **/ parsedReplacers: function() { - var result; if (this.parsed) return this.parsed; - - result = {}; + + var result = {}; + Object.keys(Discourse.BBCode.replacers, function(name, rules) { - var parsed; - parsed = result[name] = []; + + var parsed = result[name] = []; + Object.keys(Object.merge(Discourse.BBCode.replacers.base.withoutArgs, rules.withoutArgs), function(tag, val) { - return parsed.push({ - regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), - fn: val - }); + parsed.push({ regexp: new RegExp("\\[" + tag + "\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), fn: val }); }); - return Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) { - return parsed.push({ - regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), - fn: val - }); + + Object.keys(Object.merge(Discourse.BBCode.replacers.base.withArgs, rules.withArgs), function(tag, val) { + parsed.push({ regexp: new RegExp("\\[" + tag + "=?(.+?)\\]([\\s\\S]*?)\\[\\/" + tag + "\\]", "igm"), fn: val }); }); + }); + this.parsed = result; return this.parsed; }, + /** + Build the BBCode quote around the selected text + + @method buildQuoteBBCode + @param {Discourse.Post} post The post we are quoting + @param {String} contents The text selected + **/ buildQuoteBBCode: function(post, contents) { var contents_hashed, result, sansQuotes, stripped, stripped_hashed, tmp; if (!contents) contents = ""; @@ -112,41 +123,44 @@ Discourse.BBCode = { sansQuotes = contents.replace(this.QUOTE_REGEXP, '').trim(); if (sansQuotes.length === 0) return ""; + result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id')); + /* Strip the HTML from cooked */ tmp = document.createElement('div'); tmp.innerHTML = post.get('cooked'); stripped = tmp.textContent || tmp.innerText; - /* + /* Let's remove any non alphanumeric characters as a kind of hash. Yes it's not accurate but it should work almost every time we need it to. It would be unlikely that the user would quote another post that matches in exactly this way. */ stripped_hashed = stripped.replace(/[^a-zA-Z0-9]/g, ''); contents_hashed = contents.replace(/[^a-zA-Z0-9]/g, ''); - result = "[quote=\"" + (post.get('username')) + ", post:" + (post.get('post_number')) + ", topic:" + (post.get('topic_id')); /* If the quote is the full message, attribute it as such */ - if (stripped_hashed === contents_hashed) { - result += ", full:true"; - } + if (stripped_hashed === contents_hashed) result += ", full:true"; result += "\"]\n" + sansQuotes + "\n[/quote]\n\n"; + return result; }, - formatQuote: function(text, opts) { + /** + Replace quotes with appropriate markup - /* Replace quotes with appropriate markup */ + @method formatQuote + @param {String} text The text inside which we want to replace quotes + @param {Object} opts Rendering options + **/ + formatQuote: function(text, opts) { var args, matches, params, paramsSplit, paramsString, templateName, username; while (matches = this.QUOTE_REGEXP.exec(text)) { - paramsString = matches[1]; - paramsString = paramsString.replace(/\"/g, ''); + paramsString = matches[1].replace(/\"/g, ''); paramsSplit = paramsString.split(/\, */); params = []; paramsSplit.each(function(p, i) { - var assignment; if (i > 0) { - assignment = p.split(':'); + var assignment = p.split(':'); if (assignment[0] && assignment[1]) { return params.push({ key: assignment[0], @@ -157,17 +171,17 @@ Discourse.BBCode = { }); username = paramsSplit[0]; - /* Arguments for formatting */ + // Arguments for formatting args = { username: username, params: params, quote: matches[2].trim(), avatarImg: opts.lookupAvatar ? opts.lookupAvatar(username) : void 0 }; + // Name of the template templateName = 'quote'; - if (opts && opts.environment) { - templateName = "quote_" + opts.environment; - } + if (opts && opts.environment) templateName = "quote_" + opts.environment; + // Apply the template text = text.replace(matches[0], "
" + HANDLEBARS_TEMPLATES[templateName](args) + ""); } return text; @@ -181,12 +195,10 @@ Discourse.BBCode = { @param {Object} opts Rendering options **/ format: function(text, opts) { - var environment; - if (opts && opts.environment) environment = opts.environment; - if (!environment) environment = 'default'; - + var environment = opts && opts.environment ? opts.environment : 'default'; + // Apply replacers for basic tags text = Discourse.BBCode.apply(text, environment); - // Add quotes + // Format text = Discourse.BBCode.formatQuote(text, opts); return text; } diff --git a/spec/javascripts/bbcode_spec.js b/spec/javascripts/bbcode_spec.js index 0a39bd710fb..3d7e1d72cb0 100644 --- a/spec/javascripts/bbcode_spec.js +++ b/spec/javascripts/bbcode_spec.js @@ -1,6 +1,7 @@ /*global waitsFor:true expect:true describe:true beforeEach:true it:true */ describe("Discourse.BBCode", function() { + var format = Discourse.BBCode.format; describe('default replacer', function() { @@ -105,35 +106,6 @@ describe("Discourse.BBCode", function() { }); - describe("quoting", function() { - - it("can quote", function() { - expect(format("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]", - { lookupAvatar: false }) - ).toBe("
\n"); - }); - - it("can nest quotes", function() { - expect(format("[quote=\"eviltrout, post:1, topic:1\"]abc[quote=\"eviltrout, post:2, topic:2\"]nested[/quote][/quote]", - { lookupAvatar: false }) - ).toBe("
\n"); - }); - - it("can handle more than one quote", function() { - expect(format("before[quote=\"eviltrout, post:1, topic:1\"]first[/quote]middle[quote=\"eviltrout, post:2, topic:2\"]second[/quote]after", - { lookupAvatar: false }) - ).toBe("before
first\n
middle
second\n
after"); - }); - - }); - }); describe('email environment', function() { @@ -228,4 +200,65 @@ describe("Discourse.BBCode", function() { }); + describe("quoting", function() { + + it("can quote", function() { + expect(format("[quote=\"eviltrout, post:1, topic:1\"]abc[/quote]", { lookupAvatar: false })). + toBe("
abc\n
"); + }); + + it("can nest quotes", function() { + expect(format("[quote=\"eviltrout, post:1, topic:1\"]abc[quote=\"eviltrout, post:2, topic:2\"]nested[/quote][/quote]", { lookupAvatar: false })). + toBe("
abc\n\n \n\n \n \n eviltrout\n said:\n\nnested\n
"); + }); + + it("can handle more than one quote", function() { + expect(format("before[quote=\"eviltrout, post:1, topic:1\"]first[/quote]middle[quote=\"eviltrout, post:2, topic:2\"]second[/quote]after", { lookupAvatar: false })). + toBe("before
first\n
middle
second\n
after"); + }); + + describe("buildQuoteBBCode", function() { + + var build = Discourse.BBCode.buildQuoteBBCode; + + var post = Discourse.Post.create({ + cooked: "
lorem ipsum
", + username: "eviltrout", + post_number: 1, + topic_id: 2, + }); + + it("returns an empty string when contents is undefined", function() { + expect(build(post, undefined)).toBe(""); + expect(build(post, null)).toBe(""); + expect(build(post, "")).toBe(""); + }); + + it("returns the quoted contents", function() { + expect(build(post, "lorem")).toBe("[quote=\"eviltrout, post:1, topic:2\"]\nlorem\n[/quote]\n\n"); + }); + + it("trims white spaces before & after the quoted contents", function() { + expect(build(post, " lorem ")).toBe("[quote=\"eviltrout, post:1, topic:2\"]\nlorem\n[/quote]\n\n"); + }); + + it("marks quotes as full when the quote is the full message", function() { + expect(build(post, "lorem ipsum")).toBe("[quote=\"eviltrout, post:1, topic:2, full:true\"]\nlorem ipsum\n[/quote]\n\n"); + }); + + it("keeps BBCode formatting", function() { + expect(build(post, "**lorem** ipsum")).toBe("[quote=\"eviltrout, post:1, topic:2, full:true\"]\n**lorem** ipsum\n[/quote]\n\n"); + }); + + }); + + }); + });