using pageDown Markdown library, fixing autolinking. created app.helpers.textFormatter, which takes care of text formatting; functions can be called individually throughout the app
This commit is contained in:
parent
f2cc8b4e41
commit
8150d32b86
9 changed files with 1593 additions and 176 deletions
|
|
@ -9,10 +9,9 @@ javascripts:
|
||||||
main:
|
main:
|
||||||
- public/javascripts/vendor/underscore.js
|
- public/javascripts/vendor/underscore.js
|
||||||
- public/javascripts/vendor/backbone.js
|
- public/javascripts/vendor/backbone.js
|
||||||
|
- public/javascripts/vendor/markdown/*
|
||||||
- public/javascripts/vendor/markdown.js
|
|
||||||
|
|
||||||
- public/javascripts/app/app.js
|
- public/javascripts/app/app.js
|
||||||
|
- public/javascripts/app/helpers/*
|
||||||
- public/javascripts/app/router.js
|
- public/javascripts/app/router.js
|
||||||
- public/javascripts/app/views.js
|
- public/javascripts/app/views.js
|
||||||
- public/javascripts/app/models/post.js
|
- public/javascripts/app/models/post.js
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
var app = {
|
var app = {
|
||||||
collections: {},
|
collections: {},
|
||||||
models: {},
|
models: {},
|
||||||
|
helpers: {},
|
||||||
views: {},
|
views: {},
|
||||||
|
|
||||||
user: function(user) {
|
user: function(user) {
|
||||||
|
|
|
||||||
38
public/javascripts/app/helpers/text_formatter.js
Normal file
38
public/javascripts/app/helpers/text_formatter.js
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
(function(){
|
||||||
|
var textFormatter = function textFormatter(model) {
|
||||||
|
var text = model.get("text");
|
||||||
|
var mentions = model.get("mentioned_people");
|
||||||
|
|
||||||
|
return textFormatter.mentionify(
|
||||||
|
textFormatter.hashtagify(
|
||||||
|
textFormatter.markdownify(text)
|
||||||
|
), mentions
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
textFormatter.markdownify = function markdownify(text){
|
||||||
|
var converter = Markdown.getSanitizingConverter();
|
||||||
|
return converter.makeHtml(text)
|
||||||
|
};
|
||||||
|
|
||||||
|
textFormatter.hashtagify = function hashtagify(text){
|
||||||
|
var utf8WordCharcters =/(\s|^|>)#([\u0080-\uFFFF|\w|-]+|<3)/g
|
||||||
|
return text.replace(utf8WordCharcters, function(hashtag, preceeder, tagText) {
|
||||||
|
return preceeder + "<a href='/tags/" + tagText + "' class='tag'>#" + tagText + "</a>"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
textFormatter.mentionify = function mentionify(text, mentions) {
|
||||||
|
var mentionRegex = /@\{([^;]+); ([^\}]+)\}/g
|
||||||
|
return text.replace(mentionRegex, function(mentionText, fullName, diasporaId) {
|
||||||
|
var personId = _.find(mentions, function(person){
|
||||||
|
return person.diaspora_id == diasporaId
|
||||||
|
}).id
|
||||||
|
|
||||||
|
return "<a href='/people/" + personId + "' class='mention'>" + fullName + "</a>"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
app.helpers.textFormatter = textFormatter;
|
||||||
|
})();
|
||||||
|
|
||||||
|
|
@ -2,54 +2,10 @@ app.views.Content = app.views.StreamObject.extend({
|
||||||
presenter : function(){
|
presenter : function(){
|
||||||
var model = this.model
|
var model = this.model
|
||||||
return _.extend(this.defaultPresenter(), {
|
return _.extend(this.defaultPresenter(), {
|
||||||
text : metafyText(model.get("text")),
|
text : app.helpers.textFormatter(model),
|
||||||
o_embed_html : embedHTML(model)
|
o_embed_html : embedHTML(model)
|
||||||
})
|
})
|
||||||
|
|
||||||
function metafyText(text) {
|
|
||||||
//we want it to return at least a <p> from markdown
|
|
||||||
text = text || ""
|
|
||||||
return urlify(
|
|
||||||
mentionify(
|
|
||||||
hashtagify(
|
|
||||||
markdownify(text)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function markdownify(text){
|
|
||||||
//markdown returns falsy when it performs no substitutions, apparently...
|
|
||||||
return markdown.toHTML(text) || text
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashtagify(text){
|
|
||||||
var utf8WordCharcters =/(\s|^|>)#([\u0080-\uFFFF|\w|-]+|<3)/g
|
|
||||||
return text.replace(utf8WordCharcters, function(hashtag, preceeder, tagText) {
|
|
||||||
return preceeder + "<a href='/tags/" + tagText + "' class='tag'>#" + tagText + "</a>"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function mentionify(text) {
|
|
||||||
var mentionRegex = /@\{([^;]+); ([^\}]+)\}/g
|
|
||||||
return text.replace(mentionRegex, function(mentionText, fullName, diasporaId) {
|
|
||||||
var personId = _.find(model.get("mentioned_people"), function(person){
|
|
||||||
return person.diaspora_id == diasporaId
|
|
||||||
}).id
|
|
||||||
|
|
||||||
return "<a href='/people/" + personId + "' class='mention'>" + fullName + "</a>"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function urlify(text) {
|
|
||||||
var urlRegex = /(=\s?'|=\s?")?[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?(#!)&//=;]*)?/gi
|
|
||||||
return text.replace(urlRegex, function(url, preceeder, bang) {
|
|
||||||
if(preceeder) return url
|
|
||||||
var protocol = (url.search(/:\/\//) == -1 ? "http://" : "")
|
|
||||||
return "<a href='" + protocol + url + "' target=_blank>" + url + "</a>"
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function embedHTML(model){
|
function embedHTML(model){
|
||||||
if(!model.get("o_embed_cache")) { return ""; }
|
if(!model.get("o_embed_cache")) { return ""; }
|
||||||
return model.get("o_embed_cache").data.html
|
return model.get("o_embed_cache").data.html
|
||||||
|
|
@ -57,7 +13,6 @@ app.views.Content = app.views.StreamObject.extend({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
app.views.StatusMessage = app.views.Content.extend({
|
app.views.StatusMessage = app.views.Content.extend({
|
||||||
template_name : "#status-message-template"
|
template_name : "#status-message-template"
|
||||||
});
|
});
|
||||||
|
|
|
||||||
1332
public/javascripts/vendor/markdown/Markdown.Converter.js
vendored
Normal file
1332
public/javascripts/vendor/markdown/Markdown.Converter.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
108
public/javascripts/vendor/markdown/Markdown.Sanitizer.js
vendored
Normal file
108
public/javascripts/vendor/markdown/Markdown.Sanitizer.js
vendored
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
(function () {
|
||||||
|
var output, Converter;
|
||||||
|
if (typeof exports === "object" && typeof require === "function") { // we're in a CommonJS (e.g. Node.js) module
|
||||||
|
output = exports;
|
||||||
|
Converter = require("./Markdown.Converter").Converter;
|
||||||
|
} else {
|
||||||
|
output = window.Markdown;
|
||||||
|
Converter = output.Converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
output.getSanitizingConverter = function () {
|
||||||
|
var converter = new Converter();
|
||||||
|
converter.hooks.chain("postConversion", sanitizeHtml);
|
||||||
|
converter.hooks.chain("postConversion", balanceTags);
|
||||||
|
return converter;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeHtml(html) {
|
||||||
|
return html.replace(/<[^>]*>?/gi, sanitizeTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (tags that can be opened/closed) | (tags that stand alone)
|
||||||
|
var basic_tag_whitelist = /^(<\/?(b|blockquote|code|del|dd|dl|dt|em|h1|h2|h3|i|kbd|li|ol|p|pre|s|sup|sub|strong|strike|ul)>|<(br|hr)\s?\/?>)$/i;
|
||||||
|
// <a href="url..." optional title>|</a>
|
||||||
|
var a_white = /^(<a\shref="((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\stitle="[^"<>]+")?\s?>|<\/a>)$/i;
|
||||||
|
|
||||||
|
// <img src="url..." optional width optional height optional alt optional title
|
||||||
|
var img_white = /^(<img\ssrc="(https?:\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+"(\swidth="\d{1,3}")?(\sheight="\d{1,3}")?(\salt="[^"<>]*")?(\stitle="[^"<>]*")?\s?\/?>)$/i;
|
||||||
|
|
||||||
|
function sanitizeTag(tag) {
|
||||||
|
if (tag.match(basic_tag_whitelist) || tag.match(a_white) || tag.match(img_white))
|
||||||
|
return tag;
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// attempt to balance HTML tags in the html string
|
||||||
|
/// by removing any unmatched opening or closing tags
|
||||||
|
/// IMPORTANT: we *assume* HTML has *already* been
|
||||||
|
/// sanitized and is safe/sane before balancing!
|
||||||
|
///
|
||||||
|
/// adapted from CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
|
||||||
|
/// </summary>
|
||||||
|
function balanceTags(html) {
|
||||||
|
|
||||||
|
if (html == "")
|
||||||
|
return "";
|
||||||
|
|
||||||
|
var re = /<\/?\w+[^>]*(\s|$|>)/g;
|
||||||
|
// convert everything to lower case; this makes
|
||||||
|
// our case insensitive comparisons easier
|
||||||
|
var tags = html.toLowerCase().match(re);
|
||||||
|
|
||||||
|
// no HTML tags present? nothing to do; exit now
|
||||||
|
var tagcount = (tags || []).length;
|
||||||
|
if (tagcount == 0)
|
||||||
|
return html;
|
||||||
|
|
||||||
|
var tagname, tag;
|
||||||
|
var ignoredtags = "<p><img><br><li><hr>";
|
||||||
|
var match;
|
||||||
|
var tagpaired = [];
|
||||||
|
var tagremove = [];
|
||||||
|
var needsRemoval = false;
|
||||||
|
|
||||||
|
// loop through matched tags in forward order
|
||||||
|
for (var ctag = 0; ctag < tagcount; ctag++) {
|
||||||
|
tagname = tags[ctag].replace(/<\/?(\w+).*/, "$1");
|
||||||
|
// skip any already paired tags
|
||||||
|
// and skip tags in our ignore list; assume they're self-closed
|
||||||
|
if (tagpaired[ctag] || ignoredtags.search("<" + tagname + ">") > -1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
tag = tags[ctag];
|
||||||
|
match = -1;
|
||||||
|
|
||||||
|
if (!/^<\//.test(tag)) {
|
||||||
|
// this is an opening tag
|
||||||
|
// search forwards (next tags), look for closing tags
|
||||||
|
for (var ntag = ctag + 1; ntag < tagcount; ntag++) {
|
||||||
|
if (!tagpaired[ntag] && tags[ntag] == "</" + tagname + ">") {
|
||||||
|
match = ntag;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match == -1)
|
||||||
|
needsRemoval = tagremove[ctag] = true; // mark for removal
|
||||||
|
else
|
||||||
|
tagpaired[match] = true; // mark paired
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsRemoval)
|
||||||
|
return html;
|
||||||
|
|
||||||
|
// delete all orphaned tags from the string
|
||||||
|
|
||||||
|
var ctag = 0;
|
||||||
|
html = html.replace(re, function (match) {
|
||||||
|
var res = tagremove[ctag] ? "" : match;
|
||||||
|
ctag++;
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
})();
|
||||||
109
spec/javascripts/app/helpers/text_formatter_spec.js
Normal file
109
spec/javascripts/app/helpers/text_formatter_spec.js
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
describe("app.helpers.textFormatter", function(){
|
||||||
|
|
||||||
|
beforeEach(function(){
|
||||||
|
this.statusMessage = factory.post();
|
||||||
|
this.formatter = app.helpers.textFormatter;
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("main", function(){
|
||||||
|
it("calls mentionify, hashtagify, and markdownify", function(){
|
||||||
|
spyOn(app.helpers.textFormatter, "mentionify")
|
||||||
|
spyOn(app.helpers.textFormatter, "hashtagify")
|
||||||
|
spyOn(app.helpers.textFormatter, "markdownify")
|
||||||
|
|
||||||
|
app.helpers.textFormatter(this.statusMessage)
|
||||||
|
expect(app.helpers.textFormatter.mentionify).toHaveBeenCalled()
|
||||||
|
expect(app.helpers.textFormatter.hashtagify).toHaveBeenCalled()
|
||||||
|
expect(app.helpers.textFormatter.markdownify).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
|
||||||
|
// A couple of complex (intergration) test cases here would be rad.
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(".markdownify", function(){
|
||||||
|
// NOTE: for some strange reason, links separated by just a whitespace character
|
||||||
|
// will not be autolinked; thus we join our URLS here with (" and ").
|
||||||
|
// This test will fail if our join is just (" ") -- an edge case that should be addressed.
|
||||||
|
|
||||||
|
it("autolinks", function(){
|
||||||
|
var links = ["http://google.com",
|
||||||
|
"https://joindiaspora.com",
|
||||||
|
"http://www.yahooligans.com",
|
||||||
|
"http://obama.com",
|
||||||
|
"http://japan.co.jp"]
|
||||||
|
|
||||||
|
// The join that would make this particular test fail:
|
||||||
|
//
|
||||||
|
// var formattedText = this.formatter.markdownify(links.join(" "))
|
||||||
|
|
||||||
|
var formattedText = this.formatter.markdownify(links.join(" and "))
|
||||||
|
var wrapper = $("<div>").html(formattedText);
|
||||||
|
|
||||||
|
_.each(links, function(link) {
|
||||||
|
expect(wrapper.find("a[href='" + link + "']").text()).toContain(link)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(".hashtagify", function(){
|
||||||
|
context("changes hashtags to links", function(){
|
||||||
|
it("creates links to hashtags", function(){
|
||||||
|
var formattedText = this.formatter.hashtagify("I love #parties and #rockstars and #unicorns")
|
||||||
|
var wrapper = $("<div>").html(formattedText);
|
||||||
|
|
||||||
|
_.each(["parties", "rockstars", "unicorns"], function(tagName){
|
||||||
|
expect(wrapper.find("a[href='/tags/" + tagName + "']").text()).toContain(tagName)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("requires hashtags to be preceeded with a space", function(){
|
||||||
|
var formattedText = this.formatter.hashtagify("I love the#parties")
|
||||||
|
expect(formattedText).not.toContain('/tags/parties')
|
||||||
|
})
|
||||||
|
|
||||||
|
// NOTE THIS DIVERGES FROM GRUBER'S ORIGINAL DIALECT OF MARKDOWN.
|
||||||
|
// We had to edit Markdown.Converter.js line 747
|
||||||
|
//
|
||||||
|
// text = text.replace(/^(\#{1,6})[ \t]+(.+?)[ \t]*\#*\n+/gm,
|
||||||
|
// [ \t]* changed to [ \t]+
|
||||||
|
//
|
||||||
|
it("doesn't create a header tag if the first word is a hashtag", function(){
|
||||||
|
var formattedText = this.formatter.hashtagify("#parties, I love")
|
||||||
|
var wrapper = $("<div>").html(formattedText);
|
||||||
|
|
||||||
|
expect(wrapper.find("h1").length).toBe(0)
|
||||||
|
expect(wrapper.find("a[href='/tags/parties']").text()).toContain("#parties")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe(".mentionify", function(){
|
||||||
|
context("changes mention markup to links", function(){
|
||||||
|
beforeEach(function(){
|
||||||
|
this.alice = factory.author({
|
||||||
|
name : "Alice Smith",
|
||||||
|
diaspora_id : "alice@example.com",
|
||||||
|
id : "555"
|
||||||
|
})
|
||||||
|
|
||||||
|
this.bob = factory.author({
|
||||||
|
name : "Bob Grimm",
|
||||||
|
diaspora_id : "bob@example.com",
|
||||||
|
id : "666"
|
||||||
|
})
|
||||||
|
|
||||||
|
this.statusMessage.set({text: "hey there @{Alice Smith; alice@example.com} and @{Bob Grimm; bob@example.com}"})
|
||||||
|
this.statusMessage.set({mentioned_people : [this.alice, this.bob]})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("matches mentions", function(){
|
||||||
|
var formattedText = this.formatter.mentionify(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people"))
|
||||||
|
var wrapper = $("<div>").html(formattedText);
|
||||||
|
|
||||||
|
_.each([this.alice, this.bob], function(person) {
|
||||||
|
expect(wrapper.find("a[href='/people/" + person.id + "']").text()).toContain(person.name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -40,132 +40,6 @@ describe("app.views.Post", function(){
|
||||||
expect(view.$(".post_initial_info").html()).not.toContain("0 Reshares")
|
expect(view.$(".post_initial_info").html()).not.toContain("0 Reshares")
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should markdownify the post's text", function(){
|
|
||||||
this.statusMessage.set({text: "I have three Belly Buttons"})
|
|
||||||
spyOn(window.markdown, "toHTML")
|
|
||||||
new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(window.markdown.toHTML).toHaveBeenCalledWith("I have three Belly Buttons")
|
|
||||||
})
|
|
||||||
|
|
||||||
context("changes hashtags to links", function(){
|
|
||||||
it("links to a hashtag to the tag page", function(){
|
|
||||||
this.statusMessage.set({text: "I love #parties"})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$("a:contains('#parties')").attr('href')).toBe('/tags/parties')
|
|
||||||
})
|
|
||||||
|
|
||||||
it("changes all hashtags", function(){
|
|
||||||
this.statusMessage.set({text: "I love #parties and #rockstars and #unicorns"})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$("a.tag").length).toBe(3)
|
|
||||||
expect(view.$("a:contains('#parties')")).toExist();
|
|
||||||
expect(view.$("a:contains('#rockstars')")).toExist();
|
|
||||||
expect(view.$("a:contains('#unicorns')")).toExist();
|
|
||||||
})
|
|
||||||
|
|
||||||
it("requires hashtags to be preceeded with a space", function(){
|
|
||||||
this.statusMessage.set({text: "I love the#parties"})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$(".tag").length).toBe(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
// NOTE THIS DIVERGES FROM GRUBER'S ORIGINAL DIALECT OF MARKDOWN.
|
|
||||||
// We had to edit markdown.js line 291 - good people would have made a new dialect.
|
|
||||||
//
|
|
||||||
// original : var m = block.match( /^(#{1,6})\s*(.*?)\s*#*\s*(?:\n|$)/ );
|
|
||||||
// \s* changed to \s+
|
|
||||||
//
|
|
||||||
it("doesn't create a header tag if the first word is a hashtag", function(){
|
|
||||||
this.statusMessage.set({text: "#parties, I love"})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$("h1:contains(parties)")).not.toExist();
|
|
||||||
expect(view.$("a:contains('#parties')")).toExist();
|
|
||||||
})
|
|
||||||
|
|
||||||
it("works on reshares", function(){
|
|
||||||
this.statusMessage.set({text: "I love #parties"})
|
|
||||||
var reshare = new app.models.Reshare(factory.post({
|
|
||||||
text : this.statusMessage.get("text"),
|
|
||||||
root : this.statusMessage
|
|
||||||
}))
|
|
||||||
|
|
||||||
var view = new app.views.Post({model : reshare}).render();
|
|
||||||
expect(view.$("a:contains('#parties')").attr('href')).toBe('/tags/parties')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context("changes mention markup to links", function(){
|
|
||||||
beforeEach(function(){
|
|
||||||
this.alice = factory.author({
|
|
||||||
name : "Alice Smith",
|
|
||||||
diaspora_id : "alice@example.com",
|
|
||||||
id : "555"
|
|
||||||
})
|
|
||||||
|
|
||||||
this.bob = factory.author({
|
|
||||||
name : "Bob Grimm",
|
|
||||||
diaspora_id : "bob@example.com",
|
|
||||||
id : "666"
|
|
||||||
})
|
|
||||||
|
|
||||||
this.statusMessage.set({mentioned_people : [this.alice, this.bob]})
|
|
||||||
this.statusMessage.set({text: "hey there @{Alice Smith; alice@example.com} and @{Bob Grimm; bob@example.com}"})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("links to the mentioned person's page", function(){
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$("a:contains('Alice Smith')").attr('href')).toBe('/people/555')
|
|
||||||
})
|
|
||||||
|
|
||||||
it("matches all mentions", function(){
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
expect(view.$("a.mention").length).toBe(2)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("works on reshares", function(){
|
|
||||||
var reshare = new app.models.Reshare(factory.post({
|
|
||||||
text : this.statusMessage.get("text"),
|
|
||||||
mentioned_people : this.statusMessage.get("mentioned_people"),
|
|
||||||
root : this.statusMessage
|
|
||||||
}))
|
|
||||||
|
|
||||||
var view = new app.views.Post({model : reshare}).render();
|
|
||||||
expect(view.$("a.mention").length).toBe(2)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context("generates urls from plaintext", function(){
|
|
||||||
it("works", function(){
|
|
||||||
links = ["http://google.com",
|
|
||||||
"https://joindiaspora.com",
|
|
||||||
"http://www.yahooligans.com",
|
|
||||||
"http://obama.com",
|
|
||||||
"http://japan.co.jp"]
|
|
||||||
|
|
||||||
this.statusMessage.set({text : links.join(" ")})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
|
|
||||||
_.each(links, function(link) {
|
|
||||||
expect(view.$("a[href='" + link + "']").text()).toContain(link)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it("works with urls that use #! syntax (i'm looking at you, twitter)')", function(){
|
|
||||||
link = "http://twitter.com/#!/hashbangs?gross=true"
|
|
||||||
this.statusMessage.set({text : link})
|
|
||||||
var view = new app.views.Post({model : this.statusMessage}).render();
|
|
||||||
|
|
||||||
expect(view.$("a[href='" + link + "']").text()).toContain(link)
|
|
||||||
})
|
|
||||||
|
|
||||||
it("doesn't create link tags for links that are already in <a/> or <img/> tags", function(){
|
|
||||||
link = "http://google.com"
|
|
||||||
|
|
||||||
this.statusMessage.set({text : ""})
|
|
||||||
var view = new app.views.Content({model : this.statusMessage})
|
|
||||||
expect(view.presenter().text).toNotContain('</a>')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
context("embed_html", function(){
|
context("embed_html", function(){
|
||||||
it("provides oembed html from the model response", function(){
|
it("provides oembed html from the model response", function(){
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ src_files:
|
||||||
- public/javascripts/vendor/jquery.charcount.js
|
- public/javascripts/vendor/jquery.charcount.js
|
||||||
- public/javascripts/vendor/timeago.js
|
- public/javascripts/vendor/timeago.js
|
||||||
- public/javascripts/vendor/facebox.js
|
- public/javascripts/vendor/facebox.js
|
||||||
- public/javascripts/vendor/markdown.js
|
- public/javascripts/vendor/markdown/*
|
||||||
- public/javascripts/jquery.infieldlabel-custom.js
|
- public/javascripts/jquery.infieldlabel-custom.js
|
||||||
- public/javascripts/vendor/underscore.js
|
- public/javascripts/vendor/underscore.js
|
||||||
- public/javascripts/vendor/backbone.js
|
- public/javascripts/vendor/backbone.js
|
||||||
|
|
@ -36,6 +36,7 @@ src_files:
|
||||||
- public/javascripts/widgets/*
|
- public/javascripts/widgets/*
|
||||||
|
|
||||||
- public/javascripts/app/app.js
|
- public/javascripts/app/app.js
|
||||||
|
- public/javascripts/app/helpers/*
|
||||||
- public/javascripts/app/router.js
|
- public/javascripts/app/router.js
|
||||||
- public/javascripts/app/views.js
|
- public/javascripts/app/views.js
|
||||||
- public/javascripts/app/models/post.js
|
- public/javascripts/app/models/post.js
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue