Replace pagedown by markdown-it

This commit is contained in:
Steffen van Bergerem 2015-01-04 20:26:40 +01:00
parent c246e80b1d
commit 026773194a
18 changed files with 936 additions and 1763 deletions

18
Gemfile
View file

@ -82,13 +82,17 @@ gem 'entypo-rails', '2.2.2'
# JavaScript # JavaScript
gem 'backbone-on-rails', '1.1.2' gem 'backbone-on-rails', '1.1.2'
gem 'handlebars_assets', '0.18.0' gem 'handlebars_assets', '0.18.0'
gem 'jquery-rails', '3.1.2' gem 'jquery-rails', '3.1.2'
gem 'rails-assets-jquery', '1.11.1' # Should be kept in sync with jquery-rails gem 'rails-assets-jquery', '1.11.1' # Should be kept in sync with jquery-rails
gem 'js_image_paths', '0.0.1' gem 'js_image_paths', '0.0.1'
gem 'js-routes', '0.9.9' gem 'js-routes', '0.9.9'
gem 'rails-assets-punycode', '1.3.2' gem 'rails-assets-punycode', '1.3.2'
gem 'rails-assets-markdown-it', '3.0.1'
gem 'rails-assets-markdown-it-hashtag', '0.2.1'
gem 'rails-assets-markdown-it-diaspora-mention', '0.1.0'
gem 'rails-assets-markdown-it--markdown-it-for-inline', '0.1.0'
# jQuery plugins # jQuery plugins

View file

@ -438,6 +438,10 @@ GEM
rails-assets-jquery (>= 1.6) rails-assets-jquery (>= 1.6)
rails-assets-jquery.slimscroll (1.3.3) rails-assets-jquery.slimscroll (1.3.3)
rails-assets-jquery (>= 1.7) rails-assets-jquery (>= 1.7)
rails-assets-markdown-it--markdown-it-for-inline (0.1.0)
rails-assets-markdown-it (3.0.1)
rails-assets-markdown-it-diaspora-mention (0.1.0)
rails-assets-markdown-it-hashtag (0.2.1)
rails-assets-perfect-scrollbar (0.5.7) rails-assets-perfect-scrollbar (0.5.7)
rails-assets-jquery (>= 1.10) rails-assets-jquery (>= 1.10)
rails-assets-punycode (1.3.2) rails-assets-punycode (1.3.2)
@ -681,6 +685,10 @@ DEPENDENCIES
rails-assets-jquery-idletimer (= 1.0.1) rails-assets-jquery-idletimer (= 1.0.1)
rails-assets-jquery-placeholder (= 2.0.8) rails-assets-jquery-placeholder (= 2.0.8)
rails-assets-jquery-textchange (= 0.2.3) rails-assets-jquery-textchange (= 0.2.3)
rails-assets-markdown-it (= 3.0.1)
rails-assets-markdown-it--markdown-it-for-inline (= 0.1.0)
rails-assets-markdown-it-diaspora-mention (= 0.1.0)
rails-assets-markdown-it-hashtag (= 0.2.1)
rails-assets-perfect-scrollbar (= 0.5.7) rails-assets-perfect-scrollbar (= 0.5.7)
rails-assets-punycode (= 1.3.2) rails-assets-punycode (= 1.3.2)
rails-i18n (= 4.0.3) rails-i18n (= 4.0.3)

View file

@ -99,7 +99,7 @@ Handlebars.registerHelper('fmtTags', function(tags) {
}); });
Handlebars.registerHelper('fmtText', function(text) { Handlebars.registerHelper('fmtText', function(text) {
return new Handlebars.SafeString(app.helpers.textFormatter(text, null)); return new Handlebars.SafeString(app.helpers.textFormatter(text));
}); });
Handlebars.registerHelper('isCurrentPage', function(path_helper, id, options){ Handlebars.registerHelper('isCurrentPage', function(path_helper, id, options){

View file

@ -1,146 +1,76 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
// cache url regex globally, for direct acces when testing
$(function() {
Diaspora.url_regex = /(^|\s)\b((?:(?:https?|ftp):(?:\/{1,3})|www\.)(?:[^"<>\)\s]|\(([^\s()<>]+|(\([^\s()<>]+\)))\))+)(?=\s|$)/gi;
});
(function(){ (function(){
//make it so I take text and mentions rather than the modelapp.helpers.textFormatter( app.helpers.textFormatter = function(text, mentions) {
var textFormatter = function textFormatter(text, model) { mentions = mentions ? mentions : [];
var mentions = model ? model.get("mentioned_people") : [];
return textFormatter.mentionify( var punycodeURL = function(url){
textFormatter.hashtagify( try {
textFormatter.markdownify(text) while(url.indexOf("%") !== -1 && url != decodeURI(url)) url = decodeURI(url);
), mentions
)
};
textFormatter.markdownify = function markdownify(text){
var converter = Markdown.getSanitizingConverter();
// punycode non-ascii chars in urls
converter.hooks.chain("preConversion", function(text) {
// add < > around plain urls, effectively making them "autolinks"
text = text.replace(Diaspora.url_regex, function() {
var url = arguments[2];
if( url.match(/^[^\w]/) ) return url; // evil witchcraft, noop
return arguments[1]+"<"+url+">";
});
// process links
// regex copied from: https://code.google.com/p/pagedown/source/browse/Markdown.Converter.js#1198 (and slightly expanded)
var linkRegex = /(\[.*\]:\s)?(<|\()((?:(https?|ftp):\/\/[^\/'">\s]|www)[^'">\s]+?)([>\)]{1,2})/gi;
text = text.replace(linkRegex, function() {
var unicodeUrl = arguments[3];
var urlSuffix = arguments[5];
unicodeUrl = ( unicodeUrl.match(/^www/) ) ? ('http://' + unicodeUrl) : unicodeUrl;
// handle parentheses, especially in case the link ends with ')'
if( urlSuffix.indexOf(')') != -1 && urlSuffix.indexOf('>') != -1 ) {
unicodeUrl += ')';
urlSuffix = '>';
}
// url*DE*code as much as possible
try {
while( unicodeUrl.indexOf("%") !== -1 && unicodeUrl != decodeURI(unicodeUrl) ) {
unicodeUrl = decodeURI(unicodeUrl);
}
}
catch(e){}
// markdown doesn't like '(' or ')' anywhere, except where it wants
var workingUrl = unicodeUrl.replace(/\(/, "%28").replace(/\)/, "%29");
var addr = parse_url(unicodeUrl);
if( !addr.host ) addr.host = ""; // must not be 'undefined'
var asciiUrl = // rebuild the url
(!addr.scheme ? '' : addr.scheme +
( (addr.scheme.toLowerCase()=="mailto") ? ':' : '://')) +
(!addr.user ? '' : addr.user +
(!addr.pass ? '' : ':'+addr.pass) + '@') +
punycode.toASCII(addr.host) +
(!addr.port ? '' : ':' + addr.port) +
(!addr.path ? '' : encodeURI(addr.path) ) +
(!addr.query ? '' : '?' + encodeURI(addr.query) ) +
(!addr.fragment ? '' : '#' + encodeURI(addr.fragment) );
if( !arguments[1] || arguments[1] == "") { // inline link
if(arguments[2] == "<") return "["+workingUrl+"]("+asciiUrl+")"; // without link text
else return arguments[2]+asciiUrl+urlSuffix; // with link text
} else { // reference style link
return arguments[1]+asciiUrl;
}
});
return text;
});
// make nice little utf-8 symbols
converter.hooks.chain("preConversion", function(text) {
var input_strings = [
"<->", "->", "<-",
"(c)", "(r)", "(tm)",
"<3"
];
var output_symbols = [
"↔", "→", "←",
"©", "®", "™",
"♥"
];
// quote function from: http://stackoverflow.com/a/494122
var quote = function(str) {
return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
};
_.each(input_strings, function(str, idx) {
var r = new RegExp(quote(str), "gi");
text = text.replace(r, output_symbols[idx]);
});
return text;
});
converter.hooks.chain("postConversion", function (text) {
return text.replace(/(\"(?:(?:http|https):\/\/)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(?:\/\S*)?\")(\>)/g, '$1 target="_blank">')
});
return converter.makeHtml(text)
};
textFormatter.hashtagify = function hashtagify(text){
var utf8WordCharcters =/(<a[^>]*>.*?<\/a>)|(\s|^|>)#([\u0080-\uFFFF|\w|-]+|&lt;3)/g;
return text.replace(utf8WordCharcters, function(result, linkTag, preceeder, tagText) {
if(linkTag)
return linkTag;
else
return preceeder + "<a href='/tags/" + tagText.toLowerCase() +
"' class='tag'>#" + tagText + "</a>";
});
};
textFormatter.mentionify = function mentionify(text, mentions) {
var mentionRegex = /@\{([^;]+); ([^\}]+)\}/g
return text.replace(mentionRegex, function(mentionText, fullName, diasporaId) {
var person = _.find(mentions, function(person){
return (diasporaId == person.diaspora_id || person.handle) //jquery.mentionsInput gives us person.handle
})
if(person) {
var url = person.url || "/people/" + person.guid //jquery.mentionsInput gives us person.url
, personText = "<a href='" + url + "' class='mention'>" + fullName + "</a>"
} else {
personText = fullName;
} }
catch(e){}
return personText var addr = parse_url(url);
}) if( !addr.host ) addr.host = ""; // must not be 'undefined'
}
app.helpers.textFormatter = textFormatter; url = // rebuild the url
(!addr.scheme ? '' : addr.scheme +
( (addr.scheme.toLowerCase()=="mailto") ? ':' : '://')) +
(!addr.user ? '' : addr.user +
(!addr.pass ? '' : ':'+addr.pass) + '@') +
punycode.toASCII(addr.host) +
(!addr.port ? '' : ':' + addr.port) +
(!addr.path ? '' : encodeURI(addr.path) ) +
(!addr.query ? '' : '?' + encodeURI(addr.query) ) +
(!addr.fragment ? '' : '#' + encodeURI(addr.fragment) );
return url;
};
var md = window.markdownit({
breaks: true,
html: false,
linkify: true,
typographer: true
});
var inlinePlugin = window.markdownitForInline;
md.use(inlinePlugin, 'utf8_symbols', 'text', function (tokens, idx) {
tokens[idx].content = tokens[idx].content.replace(/<->/g, "↔")
.replace(/<-/g, "←")
.replace(/->/g, "→")
.replace(/<3/g, "♥");
});
md.use(inlinePlugin, 'link_new_window_and_punycode', 'link_open', function (tokens, idx) {
tokens[idx].href = punycodeURL(tokens[idx].href);
tokens[idx].target = "_blank";
});
md.use(inlinePlugin, 'image_punycode', 'image', function (tokens, idx) {
tokens[idx].src = punycodeURL(tokens[idx].src);
});
var hashtagPlugin = window.markdownitHashtag;
md.use(hashtagPlugin, {
// compare tag_text_regexp in app/models/acts_as_taggable_on-tag.rb
hashtagRegExp: '[' + PosixBracketExpressions.alnum + '_\\-]+|<3',
// compare tag_strings in lib/diaspora/taggabe.rb
preceding: '^|\\s'
});
var mentionPlugin = window.markdownitDiasporaMention;
md.use(mentionPlugin, mentions);
// TODO this is a temporary fix
// remove it as soon as markdown-it fixes its autolinking feature
var linkifyPlugin = window.markdownitDiasporaLinkify;
md.use(linkifyPlugin);
// Bootstrap table markup
md.renderer.rules.table_open = function () { return '<table class="table table-striped">\n'; };
return md.render(text);
};
})(); })();
// @license-end // @license-end

View file

@ -33,7 +33,7 @@ app.pages.SinglePostViewer = app.views.Base.extend({
postRenderTemplate : function() { postRenderTemplate : function() {
if(this.model.get("title")){ if(this.model.get("title")){
// formats title to html... // formats title to html...
var html_title = app.helpers.textFormatter(this.model.get("title"), this.model); var html_title = app.helpers.textFormatter(this.model.get("title"), this.model.get("mentioned_people"));
//... and converts html to plain text //... and converts html to plain text
document.title = $('<div>').html(html_title).text(); document.title = $('<div>').html(html_title).text();
} }

View file

@ -20,7 +20,7 @@ app.views.Comment = app.views.Content.extend({
presenter : function() { presenter : function() {
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
canRemove: this.canRemove(), canRemove: this.canRemove(),
text : app.helpers.textFormatter(this.model.get("text"), this.model) text : app.helpers.textFormatter(this.model.get("text"))
}) })
}, },

View file

@ -7,7 +7,7 @@ app.views.Content = app.views.Base.extend({
presenter : function(){ presenter : function(){
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
text : app.helpers.textFormatter(this.model.get("text"), this.model), text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people")),
largePhoto : this.largePhoto(), largePhoto : this.largePhoto(),
smallPhotos : this.smallPhotos(), smallPhotos : this.smallPhotos(),
location: this.location() location: this.location()

View file

@ -5,7 +5,7 @@ app.views.Post = app.views.Base.extend({
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
authorIsCurrentUser : app.currentUser.isAuthorOf(this.model), authorIsCurrentUser : app.currentUser.isAuthorOf(this.model),
showPost : this.showPost(), showPost : this.showPost(),
text : app.helpers.textFormatter(this.model.get("text"), this.model) text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people"))
}) })
}, },

View file

@ -29,7 +29,7 @@ app.views.SinglePostContent = app.views.Base.extend({
return _.extend(this.defaultPresenter(), { return _.extend(this.defaultPresenter(), {
authorIsCurrentUser :app.currentUser.isAuthorOf(this.model), authorIsCurrentUser :app.currentUser.isAuthorOf(this.model),
showPost : this.showPost(), showPost : this.showPost(),
text : app.helpers.textFormatter(this.model.get("text"), this.model) text : app.helpers.textFormatter(this.model.get("text"), this.model.get("mentioned_people"))
}) })
}, },

View file

@ -23,7 +23,12 @@
//= require keycodes //= require keycodes
//= require fileuploader-custom //= require fileuploader-custom
//= require handlebars.runtime //= require handlebars.runtime
//= require markdown //= require posix-bracket-expressions
//= require markdown-it
//= require markdown-it-hashtag
//= require markdown-it-diaspora-linkify
//= require markdown-it-diaspora-mention
//= require markdown-it-for-inline
//= require punycode //= require punycode
//= require parse_url //= require parse_url
//= require clear-form //= require clear-form

View file

@ -1102,8 +1102,8 @@ en:
discard_post: "Discard post" discard_post: "Discard post"
new_user_prefill: new_user_prefill:
newhere: "NewHere" newhere: "NewHere"
hello: "Hey everyone, I'm #%{new_user_tag}. " hello: "Hey everyone, Im #%{new_user_tag}. "
i_like: "I'm interested in %{tags}. " i_like: "Im interested in %{tags}. "
invited_by: "Thanks for the invite, " invited_by: "Thanks for the invite, "
poll: poll:
remove_poll_answer: "Remove option" remove_poll_answer: "Remove option"

View file

@ -44,7 +44,7 @@ Feature: new user registration
And I confirm the alert And I confirm the alert
Then I should be on the stream page Then I should be on the stream page
When I submit the publisher When I submit the publisher
Then "Hey everyone, I'm #NewHere." should be post 1 Then "Hey everyone, Im #NewHere." should be post 1
Scenario: new user with some tags posts first status message Scenario: new user with some tags posts first status message
When I fill in the following: When I fill in the following:
@ -54,7 +54,7 @@ Feature: new user registration
And I follow "awesome_button" And I follow "awesome_button"
Then I should be on the stream page Then I should be on the stream page
When I submit the publisher When I submit the publisher
Then "Hey everyone, I'm #NewHere. I'm interested in #rockstar." should be post 1 Then "Hey everyone, Im #NewHere. Im interested in #rockstar." should be post 1
Scenario: closing a popover clears getting started Scenario: closing a popover clears getting started
When I follow "awesome_button" When I follow "awesome_button"

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,571 @@
// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
var PosixBracketExpressions = {
alnum : '\\u0030-\\u0039'
+ '\\u0041-\\u005a'
+ '\\u0061-\\u007a'
+ '\\u00aa'
+ '\\u00b5'
+ '\\u00ba'
+ '\\u00c0-\\u00d6'
+ '\\u00d8-\\u00f6'
+ '\\u00f8-\\u02c1'
+ '\\u02c6-\\u02d1'
+ '\\u02e0-\\u02e4'
+ '\\u02ec'
+ '\\u02ee'
+ '\\u0345'
+ '\\u0370-\\u0374'
+ '\\u0376-\\u0377'
+ '\\u037a-\\u037d'
+ '\\u0386'
+ '\\u0388-\\u038a'
+ '\\u038c'
+ '\\u038e-\\u03a1'
+ '\\u03a3-\\u03f5'
+ '\\u03f7-\\u0481'
+ '\\u048a-\\u0527'
+ '\\u0531-\\u0556'
+ '\\u0559'
+ '\\u0561-\\u0587'
+ '\\u05b0-\\u05bd'
+ '\\u05bf'
+ '\\u05c1-\\u05c2'
+ '\\u05c4-\\u05c5'
+ '\\u05c7'
+ '\\u05d0-\\u05ea'
+ '\\u05f0-\\u05f2'
+ '\\u0610-\\u061a'
+ '\\u0620-\\u0657'
+ '\\u0659-\\u0669'
+ '\\u066e-\\u06d3'
+ '\\u06d5-\\u06dc'
+ '\\u06e1-\\u06e8'
+ '\\u06ed-\\u06fc'
+ '\\u06ff'
+ '\\u0710-\\u073f'
+ '\\u074d-\\u07b1'
+ '\\u07c0-\\u07ea'
+ '\\u07f4-\\u07f5'
+ '\\u07fa'
+ '\\u0800-\\u0817'
+ '\\u081a-\\u082c'
+ '\\u0840-\\u0858'
+ '\\u08a0'
+ '\\u08a2-\\u08ac'
+ '\\u08e4-\\u08e9'
+ '\\u08f0-\\u08fe'
+ '\\u0900-\\u093b'
+ '\\u093d-\\u094c'
+ '\\u094e-\\u0950'
+ '\\u0955-\\u0963'
+ '\\u0966-\\u096f'
+ '\\u0971-\\u0977'
+ '\\u0979-\\u097f'
+ '\\u0981-\\u0983'
+ '\\u0985-\\u098c'
+ '\\u098f-\\u0990'
+ '\\u0993-\\u09a8'
+ '\\u09aa-\\u09b0'
+ '\\u09b2'
+ '\\u09b6-\\u09b9'
+ '\\u09bd-\\u09c4'
+ '\\u09c7-\\u09c8'
+ '\\u09cb-\\u09cc'
+ '\\u09ce'
+ '\\u09d7'
+ '\\u09dc-\\u09dd'
+ '\\u09df-\\u09e3'
+ '\\u09e6-\\u09f1'
+ '\\u0a01-\\u0a03'
+ '\\u0a05-\\u0a0a'
+ '\\u0a0f-\\u0a10'
+ '\\u0a13-\\u0a28'
+ '\\u0a2a-\\u0a30'
+ '\\u0a32-\\u0a33'
+ '\\u0a35-\\u0a36'
+ '\\u0a38-\\u0a39'
+ '\\u0a3e-\\u0a42'
+ '\\u0a47-\\u0a48'
+ '\\u0a4b-\\u0a4c'
+ '\\u0a51'
+ '\\u0a59-\\u0a5c'
+ '\\u0a5e'
+ '\\u0a66-\\u0a75'
+ '\\u0a81-\\u0a83'
+ '\\u0a85-\\u0a8d'
+ '\\u0a8f-\\u0a91'
+ '\\u0a93-\\u0aa8'
+ '\\u0aaa-\\u0ab0'
+ '\\u0ab2-\\u0ab3'
+ '\\u0ab5-\\u0ab9'
+ '\\u0abd-\\u0ac5'
+ '\\u0ac7-\\u0ac9'
+ '\\u0acb-\\u0acc'
+ '\\u0ad0'
+ '\\u0ae0-\\u0ae3'
+ '\\u0ae6-\\u0aef'
+ '\\u0b01-\\u0b03'
+ '\\u0b05-\\u0b0c'
+ '\\u0b0f-\\u0b10'
+ '\\u0b13-\\u0b28'
+ '\\u0b2a-\\u0b30'
+ '\\u0b32-\\u0b33'
+ '\\u0b35-\\u0b39'
+ '\\u0b3d-\\u0b44'
+ '\\u0b47-\\u0b48'
+ '\\u0b4b-\\u0b4c'
+ '\\u0b56-\\u0b57'
+ '\\u0b5c-\\u0b5d'
+ '\\u0b5f-\\u0b63'
+ '\\u0b66-\\u0b6f'
+ '\\u0b71'
+ '\\u0b82-\\u0b83'
+ '\\u0b85-\\u0b8a'
+ '\\u0b8e-\\u0b90'
+ '\\u0b92-\\u0b95'
+ '\\u0b99-\\u0b9a'
+ '\\u0b9c'
+ '\\u0b9e-\\u0b9f'
+ '\\u0ba3-\\u0ba4'
+ '\\u0ba8-\\u0baa'
+ '\\u0bae-\\u0bb9'
+ '\\u0bbe-\\u0bc2'
+ '\\u0bc6-\\u0bc8'
+ '\\u0bca-\\u0bcc'
+ '\\u0bd0'
+ '\\u0bd7'
+ '\\u0be6-\\u0bef'
+ '\\u0c01-\\u0c03'
+ '\\u0c05-\\u0c0c'
+ '\\u0c0e-\\u0c10'
+ '\\u0c12-\\u0c28'
+ '\\u0c2a-\\u0c33'
+ '\\u0c35-\\u0c39'
+ '\\u0c3d-\\u0c44'
+ '\\u0c46-\\u0c48'
+ '\\u0c4a-\\u0c4c'
+ '\\u0c55-\\u0c56'
+ '\\u0c58-\\u0c59'
+ '\\u0c60-\\u0c63'
+ '\\u0c66-\\u0c6f'
+ '\\u0c82-\\u0c83'
+ '\\u0c85-\\u0c8c'
+ '\\u0c8e-\\u0c90'
+ '\\u0c92-\\u0ca8'
+ '\\u0caa-\\u0cb3'
+ '\\u0cb5-\\u0cb9'
+ '\\u0cbd-\\u0cc4'
+ '\\u0cc6-\\u0cc8'
+ '\\u0cca-\\u0ccc'
+ '\\u0cd5-\\u0cd6'
+ '\\u0cde'
+ '\\u0ce0-\\u0ce3'
+ '\\u0ce6-\\u0cef'
+ '\\u0cf1-\\u0cf2'
+ '\\u0d02-\\u0d03'
+ '\\u0d05-\\u0d0c'
+ '\\u0d0e-\\u0d10'
+ '\\u0d12-\\u0d3a'
+ '\\u0d3d-\\u0d44'
+ '\\u0d46-\\u0d48'
+ '\\u0d4a-\\u0d4c'
+ '\\u0d4e'
+ '\\u0d57'
+ '\\u0d60-\\u0d63'
+ '\\u0d66-\\u0d6f'
+ '\\u0d7a-\\u0d7f'
+ '\\u0d82-\\u0d83'
+ '\\u0d85-\\u0d96'
+ '\\u0d9a-\\u0db1'
+ '\\u0db3-\\u0dbb'
+ '\\u0dbd'
+ '\\u0dc0-\\u0dc6'
+ '\\u0dcf-\\u0dd4'
+ '\\u0dd6'
+ '\\u0dd8-\\u0ddf'
+ '\\u0df2-\\u0df3'
+ '\\u0e01-\\u0e3a'
+ '\\u0e40-\\u0e46'
+ '\\u0e4d'
+ '\\u0e50-\\u0e59'
+ '\\u0e81-\\u0e82'
+ '\\u0e84'
+ '\\u0e87-\\u0e88'
+ '\\u0e8a'
+ '\\u0e8d'
+ '\\u0e94-\\u0e97'
+ '\\u0e99-\\u0e9f'
+ '\\u0ea1-\\u0ea3'
+ '\\u0ea5'
+ '\\u0ea7'
+ '\\u0eaa-\\u0eab'
+ '\\u0ead-\\u0eb9'
+ '\\u0ebb-\\u0ebd'
+ '\\u0ec0-\\u0ec4'
+ '\\u0ec6'
+ '\\u0ecd'
+ '\\u0ed0-\\u0ed9'
+ '\\u0edc-\\u0edf'
+ '\\u0f00'
+ '\\u0f20-\\u0f29'
+ '\\u0f40-\\u0f47'
+ '\\u0f49-\\u0f6c'
+ '\\u0f71-\\u0f81'
+ '\\u0f88-\\u0f97'
+ '\\u0f99-\\u0fbc'
+ '\\u1000-\\u1036'
+ '\\u1038'
+ '\\u103b-\\u1049'
+ '\\u1050-\\u1062'
+ '\\u1065-\\u1068'
+ '\\u106e-\\u1086'
+ '\\u108e'
+ '\\u1090-\\u1099'
+ '\\u109c-\\u109d'
+ '\\u10a0-\\u10c5'
+ '\\u10c7'
+ '\\u10cd'
+ '\\u10d0-\\u10fa'
+ '\\u10fc-\\u1248'
+ '\\u124a-\\u124d'
+ '\\u1250-\\u1256'
+ '\\u1258'
+ '\\u125a-\\u125d'
+ '\\u1260-\\u1288'
+ '\\u128a-\\u128d'
+ '\\u1290-\\u12b0'
+ '\\u12b2-\\u12b5'
+ '\\u12b8-\\u12be'
+ '\\u12c0'
+ '\\u12c2-\\u12c5'
+ '\\u12c8-\\u12d6'
+ '\\u12d8-\\u1310'
+ '\\u1312-\\u1315'
+ '\\u1318-\\u135a'
+ '\\u135f'
+ '\\u1380-\\u138f'
+ '\\u13a0-\\u13f4'
+ '\\u1401-\\u166c'
+ '\\u166f-\\u167f'
+ '\\u1681-\\u169a'
+ '\\u16a0-\\u16ea'
+ '\\u16ee-\\u16f0'
+ '\\u1700-\\u170c'
+ '\\u170e-\\u1713'
+ '\\u1720-\\u1733'
+ '\\u1740-\\u1753'
+ '\\u1760-\\u176c'
+ '\\u176e-\\u1770'
+ '\\u1772-\\u1773'
+ '\\u1780-\\u17b3'
+ '\\u17b6-\\u17c8'
+ '\\u17d7'
+ '\\u17dc'
+ '\\u17e0-\\u17e9'
+ '\\u1810-\\u1819'
+ '\\u1820-\\u1877'
+ '\\u1880-\\u18aa'
+ '\\u18b0-\\u18f5'
+ '\\u1900-\\u191c'
+ '\\u1920-\\u192b'
+ '\\u1930-\\u1938'
+ '\\u1946-\\u196d'
+ '\\u1970-\\u1974'
+ '\\u1980-\\u19ab'
+ '\\u19b0-\\u19c9'
+ '\\u19d0-\\u19d9'
+ '\\u1a00-\\u1a1b'
+ '\\u1a20-\\u1a5e'
+ '\\u1a61-\\u1a74'
+ '\\u1a80-\\u1a89'
+ '\\u1a90-\\u1a99'
+ '\\u1aa7'
+ '\\u1b00-\\u1b33'
+ '\\u1b35-\\u1b43'
+ '\\u1b45-\\u1b4b'
+ '\\u1b50-\\u1b59'
+ '\\u1b80-\\u1ba9'
+ '\\u1bac-\\u1be5'
+ '\\u1be7-\\u1bf1'
+ '\\u1c00-\\u1c35'
+ '\\u1c40-\\u1c49'
+ '\\u1c4d-\\u1c7d'
+ '\\u1ce9-\\u1cec'
+ '\\u1cee-\\u1cf3'
+ '\\u1cf5-\\u1cf6'
+ '\\u1d00-\\u1dbf'
+ '\\u1e00-\\u1f15'
+ '\\u1f18-\\u1f1d'
+ '\\u1f20-\\u1f45'
+ '\\u1f48-\\u1f4d'
+ '\\u1f50-\\u1f57'
+ '\\u1f59'
+ '\\u1f5b'
+ '\\u1f5d'
+ '\\u1f5f-\\u1f7d'
+ '\\u1f80-\\u1fb4'
+ '\\u1fb6-\\u1fbc'
+ '\\u1fbe'
+ '\\u1fc2-\\u1fc4'
+ '\\u1fc6-\\u1fcc'
+ '\\u1fd0-\\u1fd3'
+ '\\u1fd6-\\u1fdb'
+ '\\u1fe0-\\u1fec'
+ '\\u1ff2-\\u1ff4'
+ '\\u1ff6-\\u1ffc'
+ '\\u2071'
+ '\\u207f'
+ '\\u2090-\\u209c'
+ '\\u2102'
+ '\\u2107'
+ '\\u210a-\\u2113'
+ '\\u2115'
+ '\\u2119-\\u211d'
+ '\\u2124'
+ '\\u2126'
+ '\\u2128'
+ '\\u212a-\\u212d'
+ '\\u212f-\\u2139'
+ '\\u213c-\\u213f'
+ '\\u2145-\\u2149'
+ '\\u214e'
+ '\\u2160-\\u2188'
+ '\\u24b6-\\u24e9'
+ '\\u2c00-\\u2c2e'
+ '\\u2c30-\\u2c5e'
+ '\\u2c60-\\u2ce4'
+ '\\u2ceb-\\u2cee'
+ '\\u2cf2-\\u2cf3'
+ '\\u2d00-\\u2d25'
+ '\\u2d27'
+ '\\u2d2d'
+ '\\u2d30-\\u2d67'
+ '\\u2d6f'
+ '\\u2d80-\\u2d96'
+ '\\u2da0-\\u2da6'
+ '\\u2da8-\\u2dae'
+ '\\u2db0-\\u2db6'
+ '\\u2db8-\\u2dbe'
+ '\\u2dc0-\\u2dc6'
+ '\\u2dc8-\\u2dce'
+ '\\u2dd0-\\u2dd6'
+ '\\u2dd8-\\u2dde'
+ '\\u2de0-\\u2dff'
+ '\\u2e2f'
+ '\\u3005-\\u3007'
+ '\\u3021-\\u3029'
+ '\\u3031-\\u3035'
+ '\\u3038-\\u303c'
+ '\\u3041-\\u3096'
+ '\\u309d-\\u309f'
+ '\\u30a1-\\u30fa'
+ '\\u30fc-\\u30ff'
+ '\\u3105-\\u312d'
+ '\\u3131-\\u318e'
+ '\\u31a0-\\u31ba'
+ '\\u31f0-\\u31ff'
+ '\\u3400-\\u4db5'
+ '\\u4e00-\\u9fcc'
+ '\\ua000-\\ua48c'
+ '\\ua4d0-\\ua4fd'
+ '\\ua500-\\ua60c'
+ '\\ua610-\\ua62b'
+ '\\ua640-\\ua66e'
+ '\\ua674-\\ua67b'
+ '\\ua67f-\\ua697'
+ '\\ua69f-\\ua6ef'
+ '\\ua717-\\ua71f'
+ '\\ua722-\\ua788'
+ '\\ua78b-\\ua78e'
+ '\\ua790-\\ua793'
+ '\\ua7a0-\\ua7aa'
+ '\\ua7f8-\\ua801'
+ '\\ua803-\\ua805'
+ '\\ua807-\\ua80a'
+ '\\ua80c-\\ua827'
+ '\\ua840-\\ua873'
+ '\\ua880-\\ua8c3'
+ '\\ua8d0-\\ua8d9'
+ '\\ua8f2-\\ua8f7'
+ '\\ua8fb'
+ '\\ua900-\\ua92a'
+ '\\ua930-\\ua952'
+ '\\ua960-\\ua97c'
+ '\\ua980-\\ua9b2'
+ '\\ua9b4-\\ua9bf'
+ '\\ua9cf-\\ua9d9'
+ '\\uaa00-\\uaa36'
+ '\\uaa40-\\uaa4d'
+ '\\uaa50-\\uaa59'
+ '\\uaa60-\\uaa76'
+ '\\uaa7a'
+ '\\uaa80-\\uaabe'
+ '\\uaac0'
+ '\\uaac2'
+ '\\uaadb-\\uaadd'
+ '\\uaae0-\\uaaef'
+ '\\uaaf2-\\uaaf5'
+ '\\uab01-\\uab06'
+ '\\uab09-\\uab0e'
+ '\\uab11-\\uab16'
+ '\\uab20-\\uab26'
+ '\\uab28-\\uab2e'
+ '\\uabc0-\\uabea'
+ '\\uabf0-\\uabf9'
+ '\\uac00-\\ud7a3'
+ '\\ud7b0-\\ud7c6'
+ '\\ud7cb-\\ud7fb'
+ '\\uf900-\\ufa6d'
+ '\\ufa70-\\ufad9'
+ '\\ufb00-\\ufb06'
+ '\\ufb13-\\ufb17'
+ '\\ufb1d-\\ufb28'
+ '\\ufb2a-\\ufb36'
+ '\\ufb38-\\ufb3c'
+ '\\ufb3e'
+ '\\ufb40-\\ufb41'
+ '\\ufb43-\\ufb44'
+ '\\ufb46-\\ufbb1'
+ '\\ufbd3-\\ufd3d'
+ '\\ufd50-\\ufd8f'
+ '\\ufd92-\\ufdc7'
+ '\\ufdf0-\\ufdfb'
+ '\\ufe70-\\ufe74'
+ '\\ufe76-\\ufefc'
+ '\\uff10-\\uff19'
+ '\\uff21-\\uff3a'
+ '\\uff41-\\uff5a'
+ '\\uff66-\\uffbe'
+ '\\uffc2-\\uffc7'
+ '\\uffca-\\uffcf'
+ '\\uffd2-\\uffd7'
+ '\\uffda-\\uffdc'
+ '\\u10000-\\u1000b'
+ '\\u1000d-\\u10026'
+ '\\u10028-\\u1003a'
+ '\\u1003c-\\u1003d'
+ '\\u1003f-\\u1004d'
+ '\\u10050-\\u1005d'
+ '\\u10080-\\u100fa'
+ '\\u10140-\\u10174'
+ '\\u10280-\\u1029c'
+ '\\u102a0-\\u102d0'
+ '\\u10300-\\u1031e'
+ '\\u10330-\\u1034a'
+ '\\u10380-\\u1039d'
+ '\\u103a0-\\u103c3'
+ '\\u103c8-\\u103cf'
+ '\\u103d1-\\u103d5'
+ '\\u10400-\\u1049d'
+ '\\u104a0-\\u104a9'
+ '\\u10800-\\u10805'
+ '\\u10808'
+ '\\u1080a-\\u10835'
+ '\\u10837-\\u10838'
+ '\\u1083c'
+ '\\u1083f-\\u10855'
+ '\\u10900-\\u10915'
+ '\\u10920-\\u10939'
+ '\\u10980-\\u109b7'
+ '\\u109be-\\u109bf'
+ '\\u10a00-\\u10a03'
+ '\\u10a05-\\u10a06'
+ '\\u10a0c-\\u10a13'
+ '\\u10a15-\\u10a17'
+ '\\u10a19-\\u10a33'
+ '\\u10a60-\\u10a7c'
+ '\\u10b00-\\u10b35'
+ '\\u10b40-\\u10b55'
+ '\\u10b60-\\u10b72'
+ '\\u10c00-\\u10c48'
+ '\\u11000-\\u11045'
+ '\\u11066-\\u1106f'
+ '\\u11082-\\u110b8'
+ '\\u110d0-\\u110e8'
+ '\\u110f0-\\u110f9'
+ '\\u11100-\\u11132'
+ '\\u11136-\\u1113f'
+ '\\u11180-\\u111bf'
+ '\\u111c1-\\u111c4'
+ '\\u111d0-\\u111d9'
+ '\\u11680-\\u116b5'
+ '\\u116c0-\\u116c9'
+ '\\u12000-\\u1236e'
+ '\\u12400-\\u12462'
+ '\\u13000-\\u1342e'
+ '\\u16800-\\u16a38'
+ '\\u16f00-\\u16f44'
+ '\\u16f50-\\u16f7e'
+ '\\u16f93-\\u16f9f'
+ '\\u1b000-\\u1b001'
+ '\\u1d400-\\u1d454'
+ '\\u1d456-\\u1d49c'
+ '\\u1d49e-\\u1d49f'
+ '\\u1d4a2'
+ '\\u1d4a5-\\u1d4a6'
+ '\\u1d4a9-\\u1d4ac'
+ '\\u1d4ae-\\u1d4b9'
+ '\\u1d4bb'
+ '\\u1d4bd-\\u1d4c3'
+ '\\u1d4c5-\\u1d505'
+ '\\u1d507-\\u1d50a'
+ '\\u1d50d-\\u1d514'
+ '\\u1d516-\\u1d51c'
+ '\\u1d51e-\\u1d539'
+ '\\u1d53b-\\u1d53e'
+ '\\u1d540-\\u1d544'
+ '\\u1d546'
+ '\\u1d54a-\\u1d550'
+ '\\u1d552-\\u1d6a5'
+ '\\u1d6a8-\\u1d6c0'
+ '\\u1d6c2-\\u1d6da'
+ '\\u1d6dc-\\u1d6fa'
+ '\\u1d6fc-\\u1d714'
+ '\\u1d716-\\u1d734'
+ '\\u1d736-\\u1d74e'
+ '\\u1d750-\\u1d76e'
+ '\\u1d770-\\u1d788'
+ '\\u1d78a-\\u1d7a8'
+ '\\u1d7aa-\\u1d7c2'
+ '\\u1d7c4-\\u1d7cb'
+ '\\u1d7ce-\\u1d7ff'
+ '\\u1ee00-\\u1ee03'
+ '\\u1ee05-\\u1ee1f'
+ '\\u1ee21-\\u1ee22'
+ '\\u1ee24'
+ '\\u1ee27'
+ '\\u1ee29-\\u1ee32'
+ '\\u1ee34-\\u1ee37'
+ '\\u1ee39'
+ '\\u1ee3b'
+ '\\u1ee42'
+ '\\u1ee47'
+ '\\u1ee49'
+ '\\u1ee4b'
+ '\\u1ee4d-\\u1ee4f'
+ '\\u1ee51-\\u1ee52'
+ '\\u1ee54'
+ '\\u1ee57'
+ '\\u1ee59'
+ '\\u1ee5b'
+ '\\u1ee5d'
+ '\\u1ee5f'
+ '\\u1ee61-\\u1ee62'
+ '\\u1ee64'
+ '\\u1ee67-\\u1ee6a'
+ '\\u1ee6c-\\u1ee72'
+ '\\u1ee74-\\u1ee77'
+ '\\u1ee79-\\u1ee7c'
+ '\\u1ee7e'
+ '\\u1ee80-\\u1ee89'
+ '\\u1ee8b-\\u1ee9b'
+ '\\u1eea1-\\u1eea3'
+ '\\u1eea5-\\u1eea9'
+ '\\u1eeab-\\u1eebb'
+ '\\u20000-\\u2a6d6'
+ '\\u2a700-\\u2b734'
+ '\\u2b740-\\u2b81d'
+ '\\u2f800-\\u2fa1d'
};
// @license-end

View file

@ -5,26 +5,80 @@ describe("app.helpers.textFormatter", function(){
this.formatter = app.helpers.textFormatter; this.formatter = app.helpers.textFormatter;
}) })
describe("main", function(){ // Some basic specs. For more detailed specs see
it("calls mentionify, hashtagify, and markdownify", function(){ // https://github.com/svbergerem/markdown-it-hashtag/tree/master/test
spyOn(app.helpers.textFormatter, "mentionify") context("hashtags", function() {
spyOn(app.helpers.textFormatter, "hashtagify") beforeEach(function() {
spyOn(app.helpers.textFormatter, "markdownify") this.tags = [
"tag",
"diaspora",
"PARTIES",
"<3"
];
});
app.helpers.textFormatter(this.statusMessage.get("text"), this.statusMessage) it("renders tags as links", function() {
expect(app.helpers.textFormatter.mentionify).toHaveBeenCalled() var formattedText = this.formatter('#'+this.tags.join(" #"));
expect(app.helpers.textFormatter.hashtagify).toHaveBeenCalled() _.each(this.tags, function(tag) {
expect(app.helpers.textFormatter.markdownify).toHaveBeenCalled() var link ='<a class="tag" href="/tags/'+tag.toLowerCase()+'">#'+tag+'</a>';
expect(formattedText).toContain(link);
});
});
});
// Some basic specs. For more detailed specs see
// https://github.com/diaspora/markdown-it-diaspora-mention/tree/master/test
context("mentions", 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]})
}) })
// A couple of complex (intergration) test cases here would be rad. it("matches mentions", function(){
}) var formattedText = this.formatter(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people"))
var wrapper = $("<div>").html(formattedText);
describe(".markdownify", function(){ _.each([this.alice, this.bob], function(person) {
// NOTE: for some strange reason, links separated by just a whitespace character expect(wrapper.find("a[href='/people/" + person.guid + "']").text()).toContain(person.name)
// 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("returns mentions for on posts that haven't been saved yet (framer posts)", function(){
var freshBob = factory.author({
name : "Bob Grimm",
handle : "bob@example.com",
url : 'googlebot.com',
id : "666"
})
this.statusMessage.set({'mentioned_people' : [freshBob] })
var formattedText = this.formatter(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people"))
var wrapper = $("<div>").html(formattedText);
expect(wrapper.find("a[href='googlebot.com']").text()).toContain(freshBob.name)
})
it('returns the name of the mention if the mention does not exist in the array', function(){
var text = "hey there @{Chris Smith; chris@example.com}"
var formattedText = this.formatter(text, [])
expect(formattedText.match(/\<a/)).toBeNull();
expect(formattedText).toContain('Chris Smith');
});
});
context("markdown", function(){
it("autolinks", function(){ it("autolinks", function(){
var links = [ var links = [
"http://google.com", "http://google.com",
@ -37,11 +91,7 @@ describe("app.helpers.textFormatter", function(){
"www.google.com" "www.google.com"
]; ];
// The join that would make this particular test fail: var formattedText = this.formatter(links.join(" "));
//
// var formattedText = this.formatter.markdownify(links.join(" "))
var formattedText = this.formatter.markdownify(links.join(" and "));
var wrapper = $("<div>").html(formattedText); var wrapper = $("<div>").html(formattedText);
_.each(links, function(link) { _.each(links, function(link) {
@ -67,13 +117,13 @@ describe("app.helpers.textFormatter", function(){
it("correctly converts the input strings to their corresponding output symbol", function() { it("correctly converts the input strings to their corresponding output symbol", function() {
_.each(this.input_strings, function(str, idx) { _.each(this.input_strings, function(str, idx) {
var text = this.formatter.markdownify(str); var text = this.formatter(str);
expect(text).toContain(this.output_symbols[idx]); expect(text).toContain(this.output_symbols[idx]);
}, this); }, this);
}); });
it("converts all symbols at once", function() { it("converts all symbols at once", function() {
var text = this.formatter.markdownify(this.input_strings.join(" ")); var text = this.formatter(this.input_strings.join(" "));
_.each(this.output_symbols, function(sym) { _.each(this.output_symbols, function(sym) {
expect(text).toContain(sym); expect(text).toContain(sym);
}); });
@ -97,21 +147,21 @@ describe("app.helpers.textFormatter", function(){
"http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c/", "http://xn--4gbrim.xn----ymcbaaajlc6dj7bxne2c.xn--wgbh1c/",
"http:///scholar.google.com/citations?view_op=top_venues", "http:///scholar.google.com/citations?view_op=top_venues",
"http://lyricstranslate.com/en/someone-you-%E0%B4%A8%E0%B4%BF%E0%B4%A8%E0%B5%8D%E0%B4%A8%E0%B5%86-%E0%B4%AA%E0%B5%8B%E0%B4%B2%E0%B5%8A%E0%B4%B0%E0%B4%BE%E0%B4%B3%E0%B5%8D%E2%80%8D.html", "http://lyricstranslate.com/en/someone-you-%E0%B4%A8%E0%B4%BF%E0%B4%A8%E0%B5%8D%E0%B4%A8%E0%B5%86-%E0%B4%AA%E0%B5%8B%E0%B4%B2%E0%B5%8A%E0%B4%B0%E0%B4%BE%E0%B4%B3%E0%B5%8D%E2%80%8D.html",
"http://de.wikipedia.org/wiki/Liste_der_Abk%C3%BCrzungen_%28Netzjargon%29", "http://de.wikipedia.org/wiki/Liste_der_Abk%C3%BCrzungen_(Netzjargon)",
"http://wiki.com/?query=Kr%E4fte", "http://wiki.com/?query=Kr%E4fte",
]; ];
}); });
it("correctly encodes to punycode", function() { it("correctly encodes to punycode", function() {
_.each(this.evilUrls, function(url, num) { _.each(this.evilUrls, function(url, num) {
var text = this.formatter.markdownify( "<" + url + ">" ); var text = this.formatter( "<" + url + ">" );
expect(text).toContain(this.asciiUrls[num]); expect(text).toContain(this.asciiUrls[num]);
}, this); }, this);
}); });
it("doesn't break link texts", function() { it("doesn't break link texts", function() {
var linkText = "check out this awesome link!"; var linkText = "check out this awesome link!";
var text = this.formatter.markdownify( "["+linkText+"]("+this.evilUrls[0]+")" ); var text = this.formatter( "["+linkText+"]("+this.evilUrls[0]+")" );
expect(text).toContain(this.asciiUrls[0]); expect(text).toContain(this.asciiUrls[0]);
expect(text).toContain(linkText); expect(text).toContain(linkText);
@ -119,30 +169,29 @@ describe("app.helpers.textFormatter", function(){
it("doesn't break reference style links", function() { it("doesn't break reference style links", function() {
var postContent = "blabla blab [my special link][1] bla blabla\n\n[1]: "+this.evilUrls[0]+" and an optional title)"; var postContent = "blabla blab [my special link][1] bla blabla\n\n[1]: "+this.evilUrls[0]+" and an optional title)";
var text = this.formatter.markdownify(postContent); var text = this.formatter(postContent);
expect(text).not.toContain(this.evilUrls[0]); expect(text).not.toContain('"'+this.evilUrls[0]+'"');
expect(text).toContain(this.asciiUrls[0]); expect(text).toContain(this.asciiUrls[0]);
}); });
it("can be used as img src", function() { it("can be used as img src", function() {
var postContent = "![logo]("+ this.evilUrls[1] +")"; var postContent = "![logo]("+ this.evilUrls[1] +")";
var niceImg = 'src="'+ this.asciiUrls[1] +'"'; // the "" are from src="" var niceImg = 'src="'+ this.asciiUrls[1] +'"'; // the "" are from src=""
var text = this.formatter.markdownify(postContent); var text = this.formatter(postContent);
expect(text).toContain(niceImg); expect(text).toContain(niceImg);
}); });
it("doesn't break linked images", function() { it("doesn't break linked images", function() {
var postContent = "I am linking an image here [![some-alt-text]("+this.evilUrls[1]+")]("+this.evilUrls[3]+")"; var postContent = "I am linking an image here [![some-alt-text]("+this.evilUrls[1]+")]("+this.evilUrls[3]+")";
var text = this.formatter.markdownify(postContent); var text = this.formatter(postContent);
var linked_image = 'src="'+this.asciiUrls[1]+'"'; var linked_image = 'src="'+this.asciiUrls[1]+'"';
var image_link = 'href="'+this.asciiUrls[3]+'"'; var image_link = 'href="'+this.asciiUrls[3]+'"';
expect(text).toContain(linked_image); expect(text).toContain(linked_image);
expect(text).toContain(image_link); expect(text).toContain(image_link);
}); });
}); });
context("misc breakage and/or other issues with weird urls", function(){ context("misc breakage and/or other issues with weird urls", function(){
@ -151,10 +200,10 @@ describe("app.helpers.textFormatter", function(){
var text_part = 'Revert "rails admin is conflicting with client side validations: see https://github.com/sferik/rails_admin/issues/985"'; var text_part = 'Revert "rails admin is conflicting with client side validations: see https://github.com/sferik/rails_admin/issues/985"';
var link_part = 'https://github.com/diaspora/diaspora/commit/61f40fc6bfe6bb859c995023b5a17d22c9b5e6e5'; var link_part = 'https://github.com/diaspora/diaspora/commit/61f40fc6bfe6bb859c995023b5a17d22c9b5e6e5';
var content = '['+text_part+']('+link_part+')'; var content = '['+text_part+']('+link_part+')';
var parsed = this.formatter.markdownify(content); var parsed = this.formatter(content);
var link = 'href="' + link_part + '"'; var link = 'href="' + link_part + '"';
var text = '>'+ text_part +'<'; var text = '>Revert “rails admin is conflicting with client side validations: see https://github.com/sferik/rails_admin/issues/985”<';
expect(parsed).toContain(link); expect(parsed).toContain(link);
expect(parsed).toContain(text); expect(parsed).toContain(text);
@ -167,149 +216,16 @@ describe("app.helpers.textFormatter", function(){
}); });
it("doesn't get double-encoded", function(){ it("doesn't get double-encoded", function(){
var parsed = this.formatter.markdownify(this.input); var parsed = this.formatter(this.input);
expect(parsed).toContain(this.correctHref); expect(parsed).toContain(this.correctHref);
}); });
it("gets correctly decoded, even when multiply encoded", function() { it("gets correctly decoded, even when multiply encoded", function() {
var uglyUrl = encodeURI(encodeURI(encodeURI(this.input))); var uglyUrl = encodeURI(encodeURI(encodeURI(this.input)));
var parsed = this.formatter.markdownify(uglyUrl); var parsed = this.formatter(uglyUrl);
expect(parsed).toContain(this.correctHref); expect(parsed).toContain(this.correctHref);
}); });
}); });
it("tests a bunch of benchmark urls", function(){
var self = this;
$.ajax({
async: false,
cache: false,
url: '/spec/fixtures/good_urls.txt',
success: function(data) { self.url_list = data.split("\n"); }
});
_.each(this.url_list, function(url) {
// 'comments'
if( url.match(/^#/) ) return;
// regex.test is stupid, use match and boolean-ify it
var result = !!url.match(Diaspora.url_regex);
expect(result).toBeTruthy();
if( !result && console && console.log ) {
console.log(url);
}
});
});
// TODO: try to match the 'bad_urls.txt' and have as few matches as possible
}); });
});
})
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")
})
it("and the resultant link has the tags name downcased", function(){
var formattedText = this.formatter.hashtagify("#PARTIES, I love")
expect(formattedText).toContain("/tags/parties")
})
it("doesn't create tag if the text is a link", function(){
var tags = ['diaspora', 'twitter', 'hrabrahabr'];
var text = $('<a/>', { href: 'http://me.co' }).html('#me')[0].outerHTML;
_.each(tags, function(tagName){
text += ' #'+tagName+',';
});
text += 'I love';
var formattedText = this.formatter.hashtagify(text);
var wrapper = $('<div>').html(formattedText);
expect(wrapper.find("a[href='http://me.co']").text()).toContain('#me');
_.each(tags, function(tagName){
expect(wrapper.find("a[href='/tags/"+tagName+"']").text()).toContain('#'+tagName);
});
})
})
})
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.guid + "']").text()).toContain(person.name)
})
});
it("returns mentions for on posts that haven't been saved yet (framer posts)", function(){
var freshBob = factory.author({
name : "Bob Grimm",
handle : "bob@example.com",
url : 'googlebot.com',
id : "666"
})
this.statusMessage.set({'mentioned_people' : [freshBob] })
var formattedText = this.formatter.mentionify(this.statusMessage.get("text"), this.statusMessage.get("mentioned_people"))
var wrapper = $("<div>").html(formattedText);
expect(wrapper.find("a[href='googlebot.com']").text()).toContain(freshBob.name)
})
it('returns the name of the mention if the mention does not exist in the array', function(){
var text = "hey there @{Chris Smith; chris@example.com}"
var formattedText = this.formatter.mentionify(text, [])
expect(formattedText.match(/\<a/)).toBeNull();
});
})
})
}) })

View file

@ -1 +0,0 @@
//= require_tree ./markdown

File diff suppressed because it is too large Load diff

View file

@ -1,108 +0,0 @@
(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;
}
})();