mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
Moved Markdown out of Discourse.Utilities -> Discourse.Markdown
This commit is contained in:
parent
1416bc7475
commit
cf09e200a5
9 changed files with 330 additions and 255 deletions
188
app/assets/javascripts/discourse/components/markdown.js
Normal file
188
app/assets/javascripts/discourse/components/markdown.js
Normal file
|
@ -0,0 +1,188 @@
|
|||
/*global sanitizeHtml:true Markdown:true */
|
||||
|
||||
/**
|
||||
Contains methods to help us with markdown formatting.
|
||||
|
||||
@class Markdown
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.Markdown = {
|
||||
|
||||
/**
|
||||
Convert a raw string to a cooked markdown string.
|
||||
|
||||
@method cook
|
||||
@param {String} raw the raw string we want to apply markdown to
|
||||
@param {Object} opts the options for the rendering
|
||||
**/
|
||||
cook: function(raw, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
// Make sure we've got a string
|
||||
if (!raw) return "";
|
||||
if (raw.length === 0) return "";
|
||||
|
||||
this.converter = this.markdownConverter(opts);
|
||||
return this.converter.makeHtml(raw);
|
||||
},
|
||||
|
||||
/**
|
||||
Creates a new markdown editor
|
||||
|
||||
@method createNewMarkdownEditor
|
||||
@param {Markdown.Converter} markdownConverter the converter object
|
||||
@param {String} idPostfix
|
||||
@param {Object} options the options for the markdown editor
|
||||
**/
|
||||
createNewMarkdownEditor: function(markdownConverter, idPostfix, options) {
|
||||
options = options || {};
|
||||
options.strings = {
|
||||
bold: I18n.t("js.composer.bold_title") + " <strong> Ctrl+B",
|
||||
boldexample: I18n.t("js.composer.bold_text"),
|
||||
|
||||
italic: I18n.t("js.composer.italic_title") + " <em> Ctrl+I",
|
||||
italicexample: I18n.t("js.composer.italic_text"),
|
||||
|
||||
link: I18n.t("js.composer.link_title") + " <a> Ctrl+L",
|
||||
linkdescription: "enter link description here",
|
||||
linkdialog: "<p><b>" + I18n.t("js.composer.link_dialog_title") + "</b></p><p>http://example.com/ \"" +
|
||||
I18n.t("js.composer.link_optional_text") + "\"</p>",
|
||||
|
||||
quote: I18n.t("js.composer.quote_title") + " <blockquote> Ctrl+Q",
|
||||
quoteexample: I18n.t("js.composer.quote_text"),
|
||||
|
||||
code: I18n.t("js.composer.code_title") + " <pre><code> Ctrl+K",
|
||||
codeexample: I18n.t("js.composer.code_text"),
|
||||
|
||||
image: I18n.t("js.composer.image_title") + " <img> Ctrl+G",
|
||||
imagedescription: I18n.t("js.composer.image_description"),
|
||||
imagedialog: "<p><b>" + I18n.t("js.composer.image_dialog_title") + "</b></p><p>http://example.com/images/diagram.jpg \"" +
|
||||
I18n.t("js.composer.image_optional_text") + "\"<br><br>" + I18n.t("js.composer.image_hosting_hint") + "</p>",
|
||||
|
||||
olist: I18n.t("js.composer.olist_title") + " <ol> Ctrl+O",
|
||||
ulist: I18n.t("js.composer.ulist_title") + " <ul> Ctrl+U",
|
||||
litem: I18n.t("js.compser.list_item"),
|
||||
|
||||
heading: I18n.t("js.composer.heading_title") + " <h1>/<h2> Ctrl+H",
|
||||
headingexample: I18n.t("js.composer.heading_text"),
|
||||
|
||||
hr: I18n.t("js.composer_hr_title") + " <hr> Ctrl+R",
|
||||
|
||||
undo: I18n.t("js.composer.undo_title") + " - Ctrl+Z",
|
||||
redo: I18n.t("js.composer.redo_title") + " - Ctrl+Y",
|
||||
redomac: I18n.t("js.composer.redo_title") + " - Ctrl+Shift+Z",
|
||||
|
||||
help: I18n.t("js.composer.help")
|
||||
};
|
||||
|
||||
return new Markdown.Editor(markdownConverter, idPostfix, options);
|
||||
},
|
||||
|
||||
/**
|
||||
Creates a Markdown.Converter that we we can use for formatting
|
||||
|
||||
@method markdownConverter
|
||||
@param {Object} opts the converting options
|
||||
**/
|
||||
markdownConverter: function(opts) {
|
||||
var converter, mentionLookup,
|
||||
_this = this;
|
||||
converter = new Markdown.Converter();
|
||||
if (opts) {
|
||||
mentionLookup = opts.mentionLookup;
|
||||
}
|
||||
mentionLookup = mentionLookup || Discourse.Mention.lookupCache;
|
||||
|
||||
// Before cooking callbacks
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
_this.trigger('beforeCook', {
|
||||
detail: text,
|
||||
opts: opts
|
||||
});
|
||||
return _this.textResult || text;
|
||||
});
|
||||
|
||||
// Support autolinking of www.something.com
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/(^|[\s\n])(www\.[a-z\.\-\_\(\)\/\?\=\%0-9]+)/gim, function(full, _, rest) {
|
||||
return " <a href=\"http://" + rest + "\">" + rest + "</a>";
|
||||
});
|
||||
});
|
||||
|
||||
// newline prediction in trivial cases
|
||||
if (!Discourse.SiteSettings.traditional_markdown_linebreaks) {
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) {
|
||||
if (t.match(/\n{2}/gim)) {
|
||||
return t;
|
||||
}
|
||||
return t.replace("\n", " \n");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// github style fenced code
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/^`{3}(?:(.*$)\n)?([\s\S]*?)^`{3}/gm, function(wholeMatch, m1, m2) {
|
||||
var escaped;
|
||||
escaped = Handlebars.Utils.escapeExpression(m2);
|
||||
return "<pre><code class='" + (m1 || 'lang-auto') + "'>" + escaped + "</code></pre>";
|
||||
});
|
||||
});
|
||||
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
if (!text) return "";
|
||||
|
||||
// don't to mention voodoo in pres
|
||||
text = text.replace(/<pre>([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
|
||||
return "<pre>" + (inner.replace(/@/g, '@')) + "</pre>";
|
||||
});
|
||||
|
||||
// Add @mentions of names
|
||||
text = text.replace(/([\s\t>,:'|";\]])(@[A-Za-z0-9_-|\.]*[A-Za-z0-9_-|]+)(?=[\s\t<\!:|;',"\?\.])/g, function(x, pre, name) {
|
||||
if (mentionLookup(name.substr(1))) {
|
||||
return "" + pre + "<a href='/users/" + (name.substr(1).toLowerCase()) + "' class='mention'>" + name + "</a>";
|
||||
} else {
|
||||
return "" + pre + "<span class='mention'>" + name + "</span>";
|
||||
}
|
||||
});
|
||||
|
||||
// a primitive attempt at oneboxing, this regex gives me much eye sores
|
||||
text = text.replace(/(<li>)?((<p>|<br>)[\s\n\r]*)(<a href=["]([^"]+)[^>]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|<br>))/gi, function() {
|
||||
// We don't onebox items in a list
|
||||
var onebox, url;
|
||||
if (arguments[1]) {
|
||||
return arguments[0];
|
||||
}
|
||||
url = arguments[5];
|
||||
if (Discourse && Discourse.Onebox) {
|
||||
onebox = Discourse.Onebox.lookupCache(url);
|
||||
}
|
||||
if (onebox && !onebox.isBlank()) {
|
||||
return arguments[2] + onebox;
|
||||
} else {
|
||||
return arguments[2] + arguments[4] + " class=\"onebox\" target=\"_blank\">" + arguments[6];
|
||||
}
|
||||
});
|
||||
|
||||
return(text);
|
||||
});
|
||||
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
return Discourse.BBCode.format(text, opts);
|
||||
});
|
||||
|
||||
if (opts.sanitize) {
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
if (!window.sanitizeHtml) {
|
||||
return "";
|
||||
}
|
||||
return sanitizeHtml(text);
|
||||
});
|
||||
}
|
||||
return converter;
|
||||
}
|
||||
|
||||
};
|
||||
RSVP.EventTarget.mixin(Discourse.Markdown);
|
|
@ -1,5 +1,3 @@
|
|||
/*global sanitizeHtml:true Markdown:true */
|
||||
|
||||
/**
|
||||
General utility functions
|
||||
|
||||
|
@ -148,119 +146,6 @@ Discourse.Utilities = {
|
|||
range.moveStart('character', pos);
|
||||
return range.select();
|
||||
}
|
||||
},
|
||||
|
||||
markdownConverter: function(opts) {
|
||||
var converter, mentionLookup,
|
||||
_this = this;
|
||||
converter = new Markdown.Converter();
|
||||
if (opts) {
|
||||
mentionLookup = opts.mentionLookup;
|
||||
}
|
||||
mentionLookup = mentionLookup || Discourse.Mention.lookupCache;
|
||||
|
||||
// Before cooking callbacks
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
_this.trigger('beforeCook', {
|
||||
detail: text,
|
||||
opts: opts
|
||||
});
|
||||
return _this.textResult || text;
|
||||
});
|
||||
|
||||
// Support autolinking of www.something.com
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/(^|[\s\n])(www\.[a-z\.\-\_\(\)\/\?\=\%0-9]+)/gim, function(full, _, rest) {
|
||||
return " <a href=\"http://" + rest + "\">" + rest + "</a>";
|
||||
});
|
||||
});
|
||||
|
||||
// newline prediction in trivial cases
|
||||
if (!Discourse.SiteSettings.traditional_markdown_linebreaks) {
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/(^[\w<][^\n]*\n+)/gim, function(t) {
|
||||
if (t.match(/\n{2}/gim)) {
|
||||
return t;
|
||||
}
|
||||
return t.replace("\n", " \n");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// github style fenced code
|
||||
converter.hooks.chain("preConversion", function(text) {
|
||||
return text.replace(/^`{3}(?:(.*$)\n)?([\s\S]*?)^`{3}/gm, function(wholeMatch, m1, m2) {
|
||||
var escaped;
|
||||
escaped = Handlebars.Utils.escapeExpression(m2);
|
||||
return "<pre><code class='" + (m1 || 'lang-auto') + "'>" + escaped + "</code></pre>";
|
||||
});
|
||||
});
|
||||
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
if (!text) return "";
|
||||
|
||||
// don't to mention voodoo in pres
|
||||
text = text.replace(/<pre>([\s\S]*@[\s\S]*)<\/pre>/gi, function(wholeMatch, inner) {
|
||||
return "<pre>" + (inner.replace(/@/g, '@')) + "</pre>";
|
||||
});
|
||||
|
||||
// Add @mentions of names
|
||||
text = text.replace(/([\s\t>,:'|";\]])(@[A-Za-z0-9_-|\.]*[A-Za-z0-9_-|]+)(?=[\s\t<\!:|;',"\?\.])/g, function(x, pre, name) {
|
||||
if (mentionLookup(name.substr(1))) {
|
||||
return "" + pre + "<a href='/users/" + (name.substr(1).toLowerCase()) + "' class='mention'>" + name + "</a>";
|
||||
} else {
|
||||
return "" + pre + "<span class='mention'>" + name + "</span>";
|
||||
}
|
||||
});
|
||||
|
||||
// a primitive attempt at oneboxing, this regex gives me much eye sores
|
||||
text = text.replace(/(<li>)?((<p>|<br>)[\s\n\r]*)(<a href=["]([^"]+)[^>]*)>([^<]+<\/a>[\s\n\r]*(?=<\/p>|<br>))/gi, function() {
|
||||
// We don't onebox items in a list
|
||||
var onebox, url;
|
||||
if (arguments[1]) {
|
||||
return arguments[0];
|
||||
}
|
||||
url = arguments[5];
|
||||
if (Discourse && Discourse.Onebox) {
|
||||
onebox = Discourse.Onebox.lookupCache(url);
|
||||
}
|
||||
if (onebox && !onebox.isBlank()) {
|
||||
return arguments[2] + onebox;
|
||||
} else {
|
||||
return arguments[2] + arguments[4] + " class=\"onebox\" target=\"_blank\">" + arguments[6];
|
||||
}
|
||||
});
|
||||
|
||||
return(text);
|
||||
});
|
||||
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
return Discourse.BBCode.format(text, opts);
|
||||
});
|
||||
|
||||
if (opts.sanitize) {
|
||||
converter.hooks.chain("postConversion", function(text) {
|
||||
if (!window.sanitizeHtml) {
|
||||
return "";
|
||||
}
|
||||
return sanitizeHtml(text);
|
||||
});
|
||||
}
|
||||
return converter;
|
||||
},
|
||||
|
||||
// Takes raw input and cooks it to display nicely (mostly markdown)
|
||||
cook: function(raw, opts) {
|
||||
if (!opts) opts = {};
|
||||
|
||||
// Make sure we've got a string
|
||||
if (!raw) return "";
|
||||
|
||||
if (raw.length === 0) return "";
|
||||
|
||||
this.converter = this.markdownConverter(opts);
|
||||
return this.converter.makeHtml(raw);
|
||||
}
|
||||
};
|
||||
|
||||
RSVP.EventTarget.mixin(Discourse.Utilities);
|
||||
};
|
|
@ -251,7 +251,7 @@ Discourse.ComposerView = Discourse.View.extend({
|
|||
});
|
||||
|
||||
topic = this.get('topic');
|
||||
this.editor = editor = new Markdown.Editor(Discourse.Utilities.markdownConverter({
|
||||
this.editor = editor = Discourse.Markdown.createNewMarkdownEditor(Discourse.Markdown.markdownConverter({
|
||||
lookupAvatar: function(username) {
|
||||
return Discourse.Utilities.avatarImg({
|
||||
username: username,
|
||||
|
|
|
@ -34,7 +34,7 @@ Discourse.PagedownEditor = Ember.ContainerView.extend({
|
|||
var $wmdInput;
|
||||
$wmdInput = $('#wmd-input');
|
||||
$wmdInput.data('init', true);
|
||||
this.editor = new Markdown.Editor(Discourse.Utilities.markdownConverter({
|
||||
this.editor = Discourse.Markdown.createNewMarkdownEditor(Discourse.Markdown.markdownConverter({
|
||||
sanitize: true
|
||||
}));
|
||||
return this.editor.run();
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
window.PagedownCustom = {
|
||||
insertButtons: [
|
||||
{
|
||||
id: 'wmd-quote-post',
|
||||
description: 'Quote Post',
|
||||
execute: function() {
|
||||
/* AWFUL but I can't figure out how to call a controller method from outside
|
||||
*/
|
||||
/*global Markdown:true*/
|
||||
|
||||
/* my app?
|
||||
*/
|
||||
return Discourse.__container__.lookup('controller:composer').importQuote();
|
||||
}
|
||||
window.PagedownCustom = {
|
||||
insertButtons: [
|
||||
{
|
||||
id: 'wmd-quote-post',
|
||||
description: I18n.t("js.composer.quote_title"),
|
||||
execute: function() {
|
||||
// AWFUL but I can't figure out how to call a controller method from outside our app
|
||||
return Discourse.__container__.lookup('controller:composer').importQuote();
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
|
@ -92,6 +92,7 @@ module PrettyText
|
|||
|
||||
@ctx.load(app_root + "app/assets/javascripts/discourse/components/bbcode.js")
|
||||
@ctx.load(app_root + "app/assets/javascripts/discourse/components/utilities.js")
|
||||
@ctx.load(app_root + "app/assets/javascripts/discourse/components/markdown.js")
|
||||
|
||||
# Load server side javascripts
|
||||
if DiscoursePluginRegistry.server_side_javascripts.present?
|
||||
|
@ -123,7 +124,7 @@ module PrettyText
|
|||
v8['raw'] = text
|
||||
v8.eval('opts["mentionLookup"] = function(u){return helpers.is_username_valid(u);}')
|
||||
v8.eval('opts["lookupAvatar"] = function(p){return Discourse.Utilities.avatarImg({username: p, size: "tiny", avatarTemplate: helpers.avatar_template(p)});}')
|
||||
baked = v8.eval('Discourse.Utilities.markdownConverter(opts).makeHtml(raw)')
|
||||
baked = v8.eval('Discourse.Markdown.markdownConverter(opts).makeHtml(raw)')
|
||||
end
|
||||
|
||||
# we need some minimal server side stuff, apply CDN and TODO filter disallowed markup
|
||||
|
|
124
spec/javascripts/components/markdown_spec.js
Normal file
124
spec/javascripts/components/markdown_spec.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
/*global waitsFor:true expect:true describe:true beforeEach:true it:true */
|
||||
|
||||
describe("Discourse.Markdown", function() {
|
||||
|
||||
describe("Cooking", function() {
|
||||
|
||||
var cook = function(contents, opts) {
|
||||
opts = opts || {};
|
||||
opts.mentionLookup = opts.mentionLookup || false;
|
||||
return Discourse.Markdown.cook(contents, opts);
|
||||
};
|
||||
|
||||
it("surrounds text with paragraphs", function() {
|
||||
expect(cook("hello")).toBe("<p>hello</p>");
|
||||
});
|
||||
|
||||
it("automatically handles trivial newlines", function() {
|
||||
expect(cook("1\n2\n3")).toBe("<p>1 <br>\n2 <br>\n3</p>");
|
||||
});
|
||||
|
||||
it("handles quotes properly", function() {
|
||||
var cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {
|
||||
topicId: 2,
|
||||
lookupAvatar: function(name) { return "" + name; }
|
||||
});
|
||||
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n" +
|
||||
" bob\n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>");
|
||||
});
|
||||
|
||||
it("includes no avatar if none is found", function() {
|
||||
var cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {
|
||||
topicId: 2,
|
||||
lookupAvatar: function(name) { return null; }
|
||||
});
|
||||
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n" +
|
||||
" \n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>");
|
||||
});
|
||||
|
||||
describe("Links", function() {
|
||||
|
||||
it("allows links to contain query params", function() {
|
||||
expect(cook("Youtube: http://www.youtube.com/watch?v=1MrpeBRkM5A")).
|
||||
toBe('<p>Youtube: <a href="http://www.youtube.com/watch?v=1MrpeBRkM5A">http://www.youtube.com/watch?v=1MrpeBRkM5A</a></p>');
|
||||
});
|
||||
|
||||
it("escapes double underscores in URLs", function() {
|
||||
expect(cook("Derpy: http://derp.com?__test=1")).toBe('<p>Derpy: <a href="http://derp.com?%5F%5Ftest=1">http://derp.com?__test=1</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks something that begins with www", function() {
|
||||
expect(cook("Atwood: www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">www.codinghorror.com</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL with http://www", function() {
|
||||
expect(cook("Atwood: http://www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">http://www.codinghorror.com</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL", function() {
|
||||
expect(cook("EvilTrout: http://eviltrout.com")).toBe('<p>EvilTrout: <a href="http://eviltrout.com">http://eviltrout.com</a></p>');
|
||||
});
|
||||
|
||||
it("supports markdown style links", function() {
|
||||
expect(cook("here is [an example](http://twitter.com)")).toBe('<p>here is <a href="http://twitter.com">an example</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL with parentheses (like Wikipedia)", function() {
|
||||
expect(cook("Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)")).
|
||||
toBe('<p>Batman: <a href="http://en.wikipedia.org/wiki/The_Dark_Knight_(film)">http://en.wikipedia.org/wiki/The_Dark_Knight_(film)</a></p>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Mentioning", function() {
|
||||
|
||||
it("translates mentions to links", function() {
|
||||
expect(cook("Hello @sam", { mentionLookup: (function() { return true; }) })).toBe("<p>Hello <a href='/users/sam' class='mention'>@sam</a></p>");
|
||||
});
|
||||
|
||||
it("adds a mention class", function() {
|
||||
expect(cook("Hello @EvilTrout")).toBe("<p>Hello <span class='mention'>@EvilTrout</span></p>");
|
||||
});
|
||||
|
||||
it("won't add mention class to an email address", function() {
|
||||
expect(cook("robin@email.host")).toBe("<p>robin@email.host</p>");
|
||||
});
|
||||
|
||||
it("won't be affected by email addresses that have a number before the @ symbol", function() {
|
||||
expect(cook("hanzo55@yahoo.com")).toBe("<p>hanzo55@yahoo.com</p>");
|
||||
});
|
||||
|
||||
it("supports a @mention at the beginning of a post", function() {
|
||||
expect(cook("@EvilTrout yo")).toBe("<p><span class='mention'>@EvilTrout</span> yo</p>");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Oneboxing", function() {
|
||||
|
||||
it("doesn't onebox a link within a list", function() {
|
||||
expect(cook("- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org")).not.toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("adds a onebox class to a link on its own line", function() {
|
||||
expect(cook("http://test.com")).toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("supports multiple links", function() {
|
||||
expect(cook("http://test.com\nhttp://test2.com")).toMatch(/onebox[\s\S]+onebox/m);
|
||||
});
|
||||
|
||||
it("doesn't onebox links that have trailing text", function() {
|
||||
expect(cook("http://test.com bob")).not.toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("works with links that have underscores in them", function() {
|
||||
expect(cook("http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street")).
|
||||
toBe("<p><a href=\"http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street\" class=\"onebox\" target=\"_blank\">http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street</a></p>");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -18,125 +18,6 @@ describe("Discourse.Utilities", function() {
|
|||
|
||||
});
|
||||
|
||||
describe("Cooking", function() {
|
||||
|
||||
var cook = function(contents, opts) {
|
||||
opts = opts || {};
|
||||
opts.mentionLookup = opts.mentionLookup || false;
|
||||
return Discourse.Utilities.cook(contents, opts);
|
||||
};
|
||||
|
||||
it("surrounds text with paragraphs", function() {
|
||||
expect(cook("hello")).toBe("<p>hello</p>");
|
||||
});
|
||||
|
||||
it("automatically handles trivial newlines", function() {
|
||||
expect(cook("1\n2\n3")).toBe("<p>1 <br>\n2 <br>\n3</p>");
|
||||
});
|
||||
|
||||
it("handles quotes properly", function() {
|
||||
var cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {
|
||||
topicId: 2,
|
||||
lookupAvatar: function(name) { return "" + name; }
|
||||
});
|
||||
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n" +
|
||||
" bob\n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>");
|
||||
});
|
||||
|
||||
it("includes no avatar if none is found", function() {
|
||||
var cooked = cook("1[quote=\"bob, post:1\"]my quote[/quote]2", {
|
||||
topicId: 2,
|
||||
lookupAvatar: function(name) { return null; }
|
||||
});
|
||||
expect(cooked).toBe("<p>1</p><aside class='quote' data-post=\"1\" >\n <div class='title'>\n <div class='quote-controls'></div>\n" +
|
||||
" \n bob\n said:\n </div>\n <blockquote>my quote</blockquote>\n</aside>\n<p>2</p>");
|
||||
});
|
||||
|
||||
describe("Links", function() {
|
||||
|
||||
it("allows links to contain query params", function() {
|
||||
expect(cook("Youtube: http://www.youtube.com/watch?v=1MrpeBRkM5A")).
|
||||
toBe('<p>Youtube: <a href="http://www.youtube.com/watch?v=1MrpeBRkM5A">http://www.youtube.com/watch?v=1MrpeBRkM5A</a></p>');
|
||||
});
|
||||
|
||||
it("escapes double underscores in URLs", function() {
|
||||
expect(cook("Derpy: http://derp.com?__test=1")).toBe('<p>Derpy: <a href="http://derp.com?%5F%5Ftest=1">http://derp.com?__test=1</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks something that begins with www", function() {
|
||||
expect(cook("Atwood: www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">www.codinghorror.com</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL with http://www", function() {
|
||||
expect(cook("Atwood: http://www.codinghorror.com")).toBe('<p>Atwood: <a href="http://www.codinghorror.com">http://www.codinghorror.com</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL", function() {
|
||||
expect(cook("EvilTrout: http://eviltrout.com")).toBe('<p>EvilTrout: <a href="http://eviltrout.com">http://eviltrout.com</a></p>');
|
||||
});
|
||||
|
||||
it("supports markdown style links", function() {
|
||||
expect(cook("here is [an example](http://twitter.com)")).toBe('<p>here is <a href="http://twitter.com">an example</a></p>');
|
||||
});
|
||||
|
||||
it("autolinks a URL with parentheses (like Wikipedia)", function() {
|
||||
expect(cook("Batman: http://en.wikipedia.org/wiki/The_Dark_Knight_(film)")).
|
||||
toBe('<p>Batman: <a href="http://en.wikipedia.org/wiki/The_Dark_Knight_(film)">http://en.wikipedia.org/wiki/The_Dark_Knight_(film)</a></p>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Mentioning", function() {
|
||||
|
||||
it("translates mentions to links", function() {
|
||||
expect(cook("Hello @sam", { mentionLookup: (function() { return true; }) })).toBe("<p>Hello <a href='/users/sam' class='mention'>@sam</a></p>");
|
||||
});
|
||||
|
||||
it("adds a mention class", function() {
|
||||
expect(cook("Hello @EvilTrout")).toBe("<p>Hello <span class='mention'>@EvilTrout</span></p>");
|
||||
});
|
||||
|
||||
it("won't add mention class to an email address", function() {
|
||||
expect(cook("robin@email.host")).toBe("<p>robin@email.host</p>");
|
||||
});
|
||||
|
||||
it("won't be affected by email addresses that have a number before the @ symbol", function() {
|
||||
expect(cook("hanzo55@yahoo.com")).toBe("<p>hanzo55@yahoo.com</p>");
|
||||
});
|
||||
|
||||
it("supports a @mention at the beginning of a post", function() {
|
||||
expect(cook("@EvilTrout yo")).toBe("<p><span class='mention'>@EvilTrout</span> yo</p>");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Oneboxing", function() {
|
||||
|
||||
it("doesn't onebox a link within a list", function() {
|
||||
expect(cook("- http://www.textfiles.com/bbs/MINDVOX/FORUMS/ethics\n\n- http://drupal.org")).not.toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("adds a onebox class to a link on its own line", function() {
|
||||
expect(cook("http://test.com")).toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("supports multiple links", function() {
|
||||
expect(cook("http://test.com\nhttp://test2.com")).toMatch(/onebox[\s\S]+onebox/m);
|
||||
});
|
||||
|
||||
it("doesn't onebox links that have trailing text", function() {
|
||||
expect(cook("http://test.com bob")).not.toMatch(/onebox/);
|
||||
});
|
||||
|
||||
it("works with links that have underscores in them", function() {
|
||||
expect(cook("http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street")).
|
||||
toBe("<p><a href=\"http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street\" class=\"onebox\" target=\"_blank\">http://en.wikipedia.org/wiki/Homicide:_Life_on_the_Street</a></p>");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("emailValid", function() {
|
||||
|
||||
it("allows upper case in first part of emails", function() {
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue