From c5139c9a71b2a5cad23ca67698434ab11e81c450 Mon Sep 17 00:00:00 2001 From: Florian Staudacher Date: Sat, 24 Mar 2012 01:51:41 +0100 Subject: [PATCH] improve behaviour, add more tests --- .../javascripts/app/helpers/text_formatter.js | 27 ++++++++++------ spec/javascripts/app/views/post_view_spec.js | 31 ++++++++++++++++--- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/public/javascripts/app/helpers/text_formatter.js b/public/javascripts/app/helpers/text_formatter.js index f3c6bc675..1e1e03aef 100644 --- a/public/javascripts/app/helpers/text_formatter.js +++ b/public/javascripts/app/helpers/text_formatter.js @@ -15,18 +15,27 @@ // punycode non-ascii chars in urls converter.hooks.chain("preConversion", function(text) { - // remove < > around markdown-style urls - var mdUrlRegex = /<((https?|ftp):[^'">\s]+)>/gi; - text = text.replace(mdUrlRegex, function(wholematch, m1) { - return m1; + + // add < > around plain urls, effectively making them "autolinks" + var urlRegex = /(^|\s)\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi; + text = text.replace(urlRegex, function(wholematch, space, url) { + return space+"<"+url+">"; }); - // regex shamelessly copied from http://daringfireball.net/2010/07/improved_regex_for_matching_urls - var urlRegex = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/g; - return text.replace(urlRegex, function(url){ - var newUrl = "["+url+"]("+punycode.toASCII(url)+")"; // console.log( punycode.toASCII(url) ); - return newUrl; + // process links + var linkRegex = /(\[.*\]:\s)?(<|\()((https?|ftp):[^'">\s]+)(>|\))/gi; + text = text.replace(linkRegex, function() { + var unicodeUrl = arguments[3]; + var asciiUrl = punycode.toASCII(unicodeUrl); + if(arguments[1] == "") { // inline link + if(arguments[2] == "<") return "["+unicodeUrl+"]("+asciiUrl+")"; // without link text + else return arguments[2]+asciiUrl+arguments[5]; // with link text + } else { // reference style link + return arguments[1]+asciiUrl; + } }); + + return text; }); converter.hooks.chain("postConversion", function (text) { diff --git a/spec/javascripts/app/views/post_view_spec.js b/spec/javascripts/app/views/post_view_spec.js index b1ec3b956..df6f31b73 100644 --- a/spec/javascripts/app/views/post_view_spec.js +++ b/spec/javascripts/app/views/post_view_spec.js @@ -142,14 +142,35 @@ describe("app.views.Post", function(){ }) context("markdown rendering", function() { - it("correctly handles non-ascii characters in urls", function() { + beforeEach(function() { // example from issue #2665 - var evilUrl = "http://www.bürgerentscheid-krankenhäuser.de"; - this.statusMessage.set({text: "<"+evilUrl+">"}); + this.evilUrl = "http://www.bürgerentscheid-krankenhäuser.de"; + this.asciiUrl = "http://www.xn--brgerentscheid-krankenhuser-xkc78d.de"; + }); + + it("correctly handles non-ascii characters in urls", function() { + this.statusMessage.set({text: "<"+this.evilUrl+">"}); var view = new app.views.Post({model : this.statusMessage}).render(); - expect($(view.el).html()).toContain("http://www.xn--brgerentscheid-krankenhuser-xkc78d.de"); - expect($(view.el).html()).toContain(evilUrl); + expect($(view.el).html()).toContain(this.asciiUrl); + expect($(view.el).html()).toContain(this.evilUrl); + }); + + it("doesn't break link texts for non-ascii urls", function() { + var linkText = "check out this awesome link!"; + this.statusMessage.set({text: "["+linkText+"]("+this.evilUrl+")"}); + var view = new app.views.Post({model: this.statusMessage}).render(); + + expect($(view.el).html()).toContain(this.asciiUrl); + expect($(view.el).html()).toContain(linkText); + }); + + it("doesn't break reference style links for non-ascii urls", function() { + var postContent = "blabla blab [my special link][1] bla blabla\n\n[1]: "+this.evilUrl+" and an optional title)"; + this.statusMessage.set({text: postContent}); + var view = new app.views.Post({model: this.statusMessage}).render(); + + expect($(view.el).html()).not.toContain(this.evilUrl); }); });