From aab74cf1ff7416f6cd213909c2d3781182770a0e Mon Sep 17 00:00:00 2001 From: "livefromthemoon@gmail.com" Date: Tue, 2 Nov 2010 03:55:14 +0100 Subject: [PATCH] Added markdown support for status messages --- app/helpers/application_helper.rb | 84 ++++++++++ app/helpers/status_messages_helper.rb | 37 ----- .../status_messages/_status_message.html.haml | 2 +- app/views/status_messages/show.html.haml | 2 +- spec/helpers/application_helper_spec.rb | 151 ++++++++++++++++++ spec/helpers/status_messages_helper_spec.rb | 72 --------- 6 files changed, 237 insertions(+), 111 deletions(-) delete mode 100644 spec/helpers/status_messages_helper_spec.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index d89b0b1ee..420170cb0 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -3,6 +3,8 @@ # the COPYRIGHT file. module ApplicationHelper + @@youtube_title_cache = Hash.new("no-title") + def current_aspect?(aspect) !@aspect.is_a?(Symbol) && @aspect.id == aspect.id end @@ -85,4 +87,86 @@ module ApplicationHelper "#{photos_path}?person_id=#{person_id}" end + + def markdownify(message, options = {}) + message = h(message).html_safe + + [:autolinks, :youtube, :emphasis, :links].each do |k| + if !options.has_key?(k) + options[k] = true + end + end + + if options[:links] + message.gsub!(/\[([^\[]+)\]\(([^ ]+) \"(([^&]|(&[^q])|(&q[^u])|(&qu[^o])|(&quo[^t])|("[^;]))+)\"\)/) do |m| + escape = (options[:emphasis]) ? "\\" : "" + res = "#{$1}" + res + end + message.gsub!(/\[([^\[]+)\]\(([^ ]+)\)/) do |m| + escape = (options[:emphasis]) ? "\\" : "" + res = "#{$1}" + res + end + end + + if options[:youtube] + message.gsub!(/( |^)(http:\/\/)?www\.youtube\.com\/watch[^ ]*v=([A-Za-z0-9_]+)(&[^ ]*|)/) do |m| + res = "#{$1}youtube.com::#{$3}" + res.gsub!(/(\*|_)/) { |m| "\\#{$1}" } if options[:emphasis] + res + end + end + + if options[:autolinks] + message.gsub!(/( |^)(www\.[^ ]+\.[^ ])/, '\1http://\2') + message.gsub!(/(#{$3}} + res.gsub!(/(\*|_)/) { |m| "\\#{$1}" } if options[:emphasis] + res + end + end + end + + if options[:emphasis] + message.gsub!(/([^\\]|^)\*\*(([^*]|([^*]\*[^*]))*[^*\\])\*\*/, '\1\2') + message.gsub!(/([^\\]|^)__(([^_]|([^_]_[^_]))*[^_\\])__/, '\1\2') + message.gsub!(/([^\\]|^)\*([^*]*[^\\])\*/, '\1\2') + message.gsub!(/([^\\]|^)_([^_]*[^\\])_/, '\1\2') + message.gsub!(/([^\\]|^)\*/, '\1') + message.gsub!(/([^\\]|^)_/, '\1') + message.gsub!("\\*", "*") + message.gsub!("\\_", "_") + end + + if options[:youtube] + while youtube = message.match(/youtube\.com::([A-Za-z0-9_\\]+)/) + videoid = youtube[1] + message.gsub!('youtube.com::'+videoid, 'Youtube: ' + youtube_title(videoid) + '') + end + end + + return message + end + + def youtube_title(id) + unless @@youtube_title_cache[id] == 'no-title' + return @@youtube_title_cache[id] + end + + ret = 'Unknown Video Title' #TODO add translation + http = Net::HTTP.new('gdata.youtube.com', 80) + path = '/feeds/api/videos/'+id+'?v=2' + resp, data = http.get(path, nil) + title = data.match(/(.*)<\/title>/) + unless title.nil? + ret = title.to_s[7..-9] + end + + @@youtube_title_cache[id] = ret; + return ret + end end diff --git a/app/helpers/status_messages_helper.rb b/app/helpers/status_messages_helper.rb index 2e010715a..2dfbd31cc 100644 --- a/app/helpers/status_messages_helper.rb +++ b/app/helpers/status_messages_helper.rb @@ -3,8 +3,6 @@ # the COPYRIGHT file. module StatusMessagesHelper - @@youtube_title_cache = Hash.new("no-title") - def my_latest_message unless @latest_status_message.nil? return @latest_status_message.message @@ -12,39 +10,4 @@ module StatusMessagesHelper return I18n.t('status_messages.helper.no_message_to_display') end end - - def make_links(message) - # If there should be some kind of bb-style markup, email/diaspora highlighting, it could go here. - - # next line is important due to XSS! (h is rail's make_html_safe-function) - message = h(message).html_safe - message.gsub!(/( |^)(www\.[^ ]+\.[^ ])/, '\1http://\2') - message.gsub!(/( |^)http:\/\/www\.youtube\.com\/watch[^ ]*v=([A-Za-z0-9_]+)(&[^ ]*|)/, '\1youtube.com::\2') - message.gsub!(/(https|http|ftp):\/\/([^ ]+)/, '<a target="_blank" href="\1://\2">\2</a>') - - while youtube = message.match(/youtube\.com::([A-Za-z0-9_]+)/) - videoid = youtube[1] - message.gsub!('youtube.com::'+videoid, '<a onclick="openVideo(\'youtube.com\', \'' + videoid + '\', this)" href="#video">Youtube: ' + youtube_title(videoid) + '</a>') - end - return message - end - - def youtube_title(id) - unless @@youtube_title_cache[id] == 'no-title' - return @@youtube_title_cache[id] - end - - ret = 'Unknown Video Title' #TODO add translation - http = Net::HTTP.new('gdata.youtube.com', 80) - path = '/feeds/api/videos/'+id+'?v=2' - resp, data = http.get(path, nil) - title = data.match(/<title>(.*)<\/title>/) - unless title.nil? - ret = title.to_s[7..-9] - end - - @@youtube_title_cache[id] = ret; - return ret - end - end diff --git a/app/views/status_messages/_status_message.html.haml b/app/views/status_messages/_status_message.html.haml index 7e065b475..c009520cc 100644 --- a/app/views/status_messages/_status_message.html.haml +++ b/app/views/status_messages/_status_message.html.haml @@ -23,7 +23,7 @@ = render "shared/reshare", :post => post, :current_user => current_user = link_to t('.delete'), status_message_path(post), :confirm => t('.are_you_sure'), :method => :delete, :remote => true, :class => "delete" - = make_links(post.message) + = markdownify(post.message) .info %span.time= link_to(how_long_ago(post), object_path(post)) diff --git a/app/views/status_messages/show.html.haml b/app/views/status_messages/show.html.haml index 0672ca1e8..246ca0e1b 100644 --- a/app/views/status_messages/show.html.haml +++ b/app/views/status_messages/show.html.haml @@ -7,7 +7,7 @@ .span-14.append-1.last %h1.show_text - = make_links(@status_message.message) + = markdownify(@status_message.message) = how_long_ago(@status_message) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 0ba2bb28a..9e7cb7f4c 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -18,4 +18,155 @@ describe ApplicationHelper do person_url(@user).should == "/users/#{@user.id}" end + describe "markdownify" do + describe "autolinks" do + it "should not allow basic XSS/HTML" do + markdownify("<script>alert('XSS is evil')</script>").should == "<script>alert('XSS is evil')</script>" + end + + it "should recognize basic http links (1/3)" do + proto="http" + url="bugs.joindiaspora.com/issues/332" + markdownify(proto+"://"+url).should == "<a target=\"_blank\" href=\""+proto+"://"+url+"\">"+url+"</a>" + end + + it "should recognize basic http links (2/3)" do + proto="http" + url="webmail.example.com?~()!*/" + markdownify(proto+"://"+url).should == "<a target=\"_blank\" href=\""+proto+"://"+url+"\">"+url+"</a>" + end + + it "should recognize basic http links (3/3)" do + proto="http" + url="127.0.0.1:3000/users/sign_in" + markdownify(proto+"://"+url).should == "<a target=\"_blank\" href=\""+proto+"://"+url+"\">"+url+"</a>" + end + + it "should recognize secure https links" do + proto="https" + url="127.0.0.1:3000/users/sign_in" + markdownify(proto+"://"+url).should == "<a target=\"_blank\" href=\""+proto+"://"+url+"\">"+url+"</a>" + end + + it "should recognize youtube links" do + proto="http" + videoid = "0x__dDWdf23" + url="www.youtube.com/watch?v="+videoid+"&a=GxdCwVVULXdvEBKmx_f5ywvZ0zZHHHDU&list=ML&playnext=1" + title = "UP & down & UP & down &" + mock_http = mock("http") + Net::HTTP.stub!(:new).with('gdata.youtube.com', 80).and_return(mock_http) + mock_http.should_receive(:get).with('/feeds/api/videos/'+videoid+'?v=2', nil).and_return([nil, 'Foobar <title>'+title+' hallo welt dsd']) + res = markdownify(proto+'://'+url) + res.should == "Youtube: "+title+"" + end + + it "should recognize a bunch of different links" do + message = "http:// Hello World, this is for www.joindiaspora.com and not for http://www.google.com though their Youtube service is neat, take http://www.youtube.com/watch?v=foobar or www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related It is a good idea we finally have youtube, so enjoy this video http://www.youtube.com/watch?v=rickrolld" + mock_http = mock("http") + Net::HTTP.stub!(:new).with('gdata.youtube.com', 80).and_return(mock_http) + mock_http.should_receive(:get).with('/feeds/api/videos/foobar?v=2', nil).and_return([nil, 'Foobar F 007 - the bar is not enough hallo welt dsd']) + mock_http.should_receive(:get).with('/feeds/api/videos/BARFOO?v=2', nil).and_return([nil, 'Foobar BAR is the new FOO hallo welt dsd']) + mock_http.should_receive(:get).with('/feeds/api/videos/rickrolld?v=2', nil).and_return([nil, 'Foobar Never gonne give you up hallo welt dsd']) + res = markdownify(message) + res.should == "http:// Hello World, this is for www.joindiaspora.com and not for www.google.com though their Youtube service is neat, take Youtube: F 007 - the bar is not enough or Youtube: BAR is the new FOO It is a good idea we finally have youtube, so enjoy this video Youtube: Never gonne give you up" + end + + it "should recognize basic ftp links" do + proto="ftp" + url="ftp.uni-kl.de/CCC/26C3/mp4/26c3-3540-en-a_hackers_utopia.mp4" + # I did not watch that one, but the title sounds nice :P + markdownify(proto+"://"+url).should == ""+url+"" + end + + it "should recognize www links" do + url="www.joindiaspora.com" + markdownify(url).should == ""+url+"" + end + end + + describe "weak emphasis" do + it "should be recognized (1/2)" do + message = "*some text* some text *some text* some text" + markdownify(message).should == "some text some text some text some text" + end + + it "should be recognized (2/2)" do + message = "_some text_ some text _some text_ some text" + markdownify(message).should == "some text some text some text some text" + end + end + + describe "strong emphasis" do + it "should be recognized (1/2)" do + message = "**some text** some text **some text** some text" + markdownify(message).should == "some text some text some text some text" + end + + it "should be recognized (2/2)" do + message = "__some text__ some text __some text__ some text" + markdownify(message).should == "some text some text some text some text" + end + end + + describe "nested weak and strong emphasis" do + it "should be rendered correctly" do + message = "__this is _some_ text__" + markdownify(message).should == "this is some text" + message = "*this is **some** text*" + markdownify(message).should == "this is some text" + message = "___some text___" + markdownify(message).should == "some text" + end + end + + describe "links" do + it "should be recognized without title attribute" do + message = "[link text](http://someurl.com) [link text](http://someurl.com)" + markdownify(message).should == 'link text link text' + end + + it "should be recognized with title attribute" do + message = '[link text](http://someurl.com "some title") [link text](http://someurl.com "some title")' + markdownify(message).should == 'link text link text' + end + end + + describe "nested emphasis and links tags" do + it "should be rendered correctly" do + message = '[**some *link* text**](someurl.com "some title")' + markdownify(message).should == 'some link text' + end + end + + it "should allow escaping" do + message = '*some text* \\*some text* \\**some text* _some text_ \\_some text_ \\__some text_' + markdownify(message).should == "some text *some text *some text some text _some text _some text" + end + + describe "options" do + before do + @message = "http://url.com www.url.com www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related *emphasis* __emphasis__ [link](www.url.com) [link](url.com \"title\")" + end + + it "should allow to render only autolinks" do + res = markdownify(@message, :youtube => false, :emphasis => false, :links => false) + res.should == "url.com www.url.com www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related *emphasis* __emphasis__ [link](www.url.com) [link](url.com "title")" + end + + it "should allow to render only youtube autolinks" do + res = markdownify(@message, :autolinks => false, :emphasis => false, :links => false) + res.should == "http://url.com www.url.com Youtube: BAR is the new FOO *emphasis* __emphasis__ [link](www.url.com) [link](url.com "title")" + end + + it "should allow to render only emphasis tags" do + res = markdownify(@message, :autolinks => false, :youtube => false, :links => false) + res.should == "http://url.com www.url.com www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related emphasis emphasis [link](www.url.com) [link](url.com "title")" + end + + it "should allo to render only links tags" do + res = markdownify(@message, :autolinks => false, :youtube => false, :emphasis => false) + res.should == "http://url.com www.url.com www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related *emphasis* __emphasis__ link link" + end + end + end end diff --git a/spec/helpers/status_messages_helper_spec.rb b/spec/helpers/status_messages_helper_spec.rb deleted file mode 100644 index 0aee3826a..000000000 --- a/spec/helpers/status_messages_helper_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2010, Diaspora Inc. This file is -# licensed under the Affero General Public License version 3 or later. See -# the COPYRIGHT file. - -require 'spec_helper' - -describe StatusMessagesHelper do - it "should not allow basic XSS/HTML" do - make_links("").should == "<script>alert('XSS is evil')</script>" - end - - it "should recognize basic http links (1/3)" do - proto="http" - url="bugs.joindiaspora.com/issues/332" - make_links(proto+"://"+url).should == ""+url+"" - end - - it "should recognize basic http links (2/3)" do - proto="http" - url="webmail.example.com?~()!*/" - make_links(proto+"://"+url).should == ""+url+"" - end - - it "should recognize basic http links (3/3)" do - proto="http" - url="127.0.0.1:3000/users/sign_in" - make_links(proto+"://"+url).should == ""+url+"" - end - - it "should recognize secure https links" do - proto="https" - url="127.0.0.1:3000/users/sign_in" - make_links(proto+"://"+url).should == ""+url+"" - end - - it "should recognize youtube links" do - proto="http" - videoid = "0x__dDWdf23" - url="www.youtube.com/watch?v="+videoid+"&a=GxdCwVVULXdvEBKmx_f5ywvZ0zZHHHDU&list=ML&playnext=1" - title = "UP & down & UP & down &" - mock_http = mock("http") - Net::HTTP.stub!(:new).with('gdata.youtube.com', 80).and_return(mock_http) - mock_http.should_receive(:get).with('/feeds/api/videos/'+videoid+'?v=2', nil).and_return([nil, 'Foobar '+title+' hallo welt dsd']) - res = make_links(proto+'://'+url) - res.should == "Youtube: "+title+"" - end - - it "should recognize a bunch of different links" do - message = "http:// Hello World, this is for www.joindiaspora.com and not for http://www.google.com though their Youtube service is neat, take http://www.youtube.com/watch?v=foobar or www.youtube.com/watch?foo=bar&v=BARFOO&whatever=related It is a good idea we finally have youtube, so enjoy this video http://www.youtube.com/watch?v=rickrolld" - mock_http = mock("http") - Net::HTTP.stub!(:new).with('gdata.youtube.com', 80).and_return(mock_http) - mock_http.should_receive(:get).with('/feeds/api/videos/foobar?v=2', nil).and_return([nil, 'Foobar F 007 - the bar is not enough hallo welt dsd']) - mock_http.should_receive(:get).with('/feeds/api/videos/BARFOO?v=2', nil).and_return([nil, 'Foobar BAR is the new FOO hallo welt dsd']) - mock_http.should_receive(:get).with('/feeds/api/videos/rickrolld?v=2', nil).and_return([nil, 'Foobar Never gonne give you up hallo welt dsd']) - res = make_links(message) - res.should == "http:// Hello World, this is for www.joindiaspora.com and not for www.google.com though their Youtube service is neat, take Youtube: F 007 - the bar is not enough or Youtube: BAR is the new FOO It is a good idea we finally have youtube, so enjoy this video Youtube: Never gonne give you up" - end - - it "should recognize basic ftp links" do - proto="ftp" - url="ftp.uni-kl.de/CCC/26C3/mp4/26c3-3540-en-a_hackers_utopia.mp4" - # I did not watch that one, but the title sounds nice :P - make_links(proto+"://"+url).should == ""+url+"" - end - - it "should recognize www links" do - url="www.joindiaspora.com" - make_links(url).should == ""+url+"" - end - - -end