Introduce message renderer

This new class replaces all existing server side message
rendering helpers and is the new global entry point for such
needs. All models with relevant fields now expose an instance
of MessageRenderer for those. MessageRenderer acts as
gateway between the existing processing solutions for markdown,
mentions and tags and provides a very flexible interface for
all output needs. This makes the API to obtain a message
in a certain format clear. As a result of centralizing the
processing a lot of duplication is eliminated. Centralizing
the message processing also makes it clear where to change
its behaviour, add new representations and what options
are already available.
This commit is contained in:
Jonne Haß 2014-03-08 23:49:14 +01:00
parent 8f67e1eb17
commit 8280556a47
40 changed files with 545 additions and 389 deletions

View file

@ -65,7 +65,7 @@ gem 'messagebus_ruby_api', '1.0.3'
gem 'nokogiri', '1.6.1' gem 'nokogiri', '1.6.1'
gem 'rails_autolink', '1.1.5' gem 'rails_autolink', '1.1.5'
gem 'redcarpet', '3.0.0' gem 'redcarpet', '3.1.1'
gem 'roxml', '3.1.6' gem 'roxml', '3.1.6'
gem 'ruby-oembed', '0.8.9' gem 'ruby-oembed', '0.8.9'
gem 'opengraph_parser', '0.2.3' gem 'opengraph_parser', '0.2.3'

View file

@ -345,7 +345,7 @@ GEM
rb-inotify (0.9.3) rb-inotify (0.9.3)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
redcarpet (3.0.0) redcarpet (3.1.1)
redis (3.0.6) redis (3.0.6)
redis-namespace (1.4.1) redis-namespace (1.4.1)
redis (~> 3.0.4) redis (~> 3.0.4)
@ -512,7 +512,7 @@ DEPENDENCIES
rails_autolink (= 1.1.5) rails_autolink (= 1.1.5)
rb-fsevent (= 0.9.4) rb-fsevent (= 0.9.4)
rb-inotify (= 0.9.3) rb-inotify (= 0.9.3)
redcarpet (= 3.0.0) redcarpet (= 3.1.1)
remotipart (= 1.2.1) remotipart (= 1.2.1)
rmagick (= 2.13.2) rmagick (= 2.13.2)
roxml (= 3.1.6) roxml (= 3.1.6)

View file

@ -98,7 +98,11 @@ class UsersController < ApplicationController
if @user = User.find_by_username(params[:username]) if @user = User.find_by_username(params[:username])
respond_to do |format| respond_to do |format|
format.atom do format.atom do
@posts = Post.where(:author_id => @user.person_id, :public => true).order('created_at DESC').limit(25) @posts = Post.where(author_id: @user.person_id, public: true)
.order('created_at DESC')
.limit(25)
.map {|post| post.is_a?(Reshare) ? post.absolute_root : post }
.compact
end end
format.any { redirect_to person_path(@user.person) } format.any { redirect_to person_path(@user.person) }

View file

@ -1,62 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
module MarkdownifyHelper
def markdown_options
{
:autolink => true,
:fenced_code_blocks => true,
:space_after_headers => true,
:strikethrough => true,
:tables => true,
:no_intra_emphasis => true,
}
end
def markdownify(target, render_options={})
render_options[:filter_html] = true
render_options[:hard_wrap] ||= true
render_options[:safe_links_only] = true
# This ugly little hack basically means
# "Give me the rawest contents of target available"
if target.respond_to?(:raw_message)
message = target.raw_message
elsif target.respond_to?(:text)
message = target.text
else
message = target.to_s
end
return '' if message.blank?
renderer = Diaspora::Markdownify::HTML.new(render_options)
markdown = Redcarpet::Markdown.new(renderer, markdown_options)
message = markdown.render(message).html_safe
if target.respond_to?(:mentioned_people)
message = Diaspora::Mentionable.format(message, target.mentioned_people)
end
message = Diaspora::Taggable.format_tags(message, :no_escape => true)
return message.html_safe
end
def strip_markdown(text)
renderer = Redcarpet::Markdown.new(Redcarpet::Render::StripDown, markdown_options)
renderer.render(text).strip
end
def process_newlines(message)
# in very clear cases, let newlines become <br /> tags
# Grabbed from Github flavored Markdown
message.gsub(/^[\w\<][^\n]*\n+/) do |x|
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
end
end
end

View file

@ -1,28 +1,20 @@
module NotifierHelper module NotifierHelper
# @param post [Post] The post object. # @param post [Post] The post object.
# @param opts [Hash] Optional hash. Accepts :length and :process_newlines parameters. # @param opts [Hash] Optional hash. Accepts :length parameters.
# @return [String] The truncated and formatted post. # @return [String] The truncated and formatted post.
def post_message(post, opts={}) def post_message(post, opts={})
opts[:length] ||= 200 if post.respond_to? :message
if post.respond_to? :formatted_message post.message.plain_text_without_markdown truncate: opts.fetch(:length, 200)
message = strip_markdown(post.formatted_message(:plain_text => true))
message = truncate(message, :length => opts[:length])
message = process_newlines(message) if opts[:process_newlines]
message
else else
I18n.translate 'notifier.a_post_you_shared' I18n.translate 'notifier.a_post_you_shared'
end end
end end
# @param comment [Comment] The comment to process. # @param comment [Comment] The comment to process.
# @param opts [Hash] Optional hash. Accepts :length and :process_newlines parameters. # @param opts [Hash] Optional hash. Accepts :length parameters.
# @return [String] The truncated and formatted comment. # @return [String] The truncated and formatted comment.
def comment_message(comment, opts={}) def comment_message(comment, opts={})
opts[:length] ||= 600 comment.message.plain_text_without_markdown truncate: opts.fetch(:length, 600)
text = strip_markdown(comment.text)
text = truncate(text, :length => opts[:length])
text = process_newlines(text) if opts[:process_newlines]
text
end end
end end

View file

@ -9,19 +9,8 @@ module PostsHelper
elsif post.is_a?(Reshare) elsif post.is_a?(Reshare)
I18n.t "posts.show.reshare_by", :author => post.author_name I18n.t "posts.show.reshare_by", :author => post.author_name
else else
if post.text.present? if post.message.present?
if opts.has_key?(:length) post.message.title opts
truncate(post.text(:plain_text => true), :length => opts.fetch(:length))
elsif /\A(?: # Regexp to match a Markdown header present on first line :
(?<setext_content>.{1,200}\n(?:={1,200}|-{1,200}))(?:\r?\n|$) # Setext-style header
| # or
(?<atx_content>\#{1,6}\s.{1,200})(?:\r?\n|$) # Atx-style header
)/x =~ post.text(:plain_text => true)
return setext_content unless setext_content.nil?
return atx_content unless atx_content.nil?
else
truncate(post.text(:plain_text => true), :length => 20 )
end
elsif post.respond_to?(:photos) && post.photos.present? elsif post.respond_to?(:photos) && post.photos.present?
I18n.t "posts.show.photos_by", :count => post.photos.size, :author => post.author_name I18n.t "posts.show.photos_by", :count => post.photos.size, :author => post.author_name
end end

View file

@ -1,8 +1,5 @@
module NotificationMailers module NotificationMailers
class AlsoCommented < NotificationMailers::Base class AlsoCommented < NotificationMailers::Base
include ActionView::Helpers::TextHelper
include MarkdownifyHelper
attr_accessor :comment attr_accessor :comment
delegate :post, to: :comment, prefix: true delegate :post, to: :comment, prefix: true
@ -11,8 +8,7 @@ module NotificationMailers
if mail? if mail?
@headers[:from] = "\"#{@comment.author_name} (diaspora*)\" <#{AppConfig.mail.sender_address}>" @headers[:from] = "\"#{@comment.author_name} (diaspora*)\" <#{AppConfig.mail.sender_address}>"
@headers[:subject] = truncate(strip_markdown(@comment.comment_email_subject.squish), :length => TRUNCATION_LEN) @headers[:subject] = "Re: #{@comment.comment_email_subject}"
@headers[:subject] = "Re: #{@headers[:subject]}"
end end
end end

View file

@ -1,6 +1,4 @@
module NotificationMailers module NotificationMailers
TRUNCATION_LEN = 70
class Base class Base
attr_accessor :recipient, :sender attr_accessor :recipient, :sender

View file

@ -1,16 +1,12 @@
module NotificationMailers module NotificationMailers
class CommentOnPost < NotificationMailers::Base class CommentOnPost < NotificationMailers::Base
include ActionView::Helpers::TextHelper
include MarkdownifyHelper
attr_accessor :comment attr_accessor :comment
def set_headers(comment_id) def set_headers(comment_id)
@comment = Comment.find(comment_id) @comment = Comment.find(comment_id)
@headers[:from] = "\"#{@comment.author_name} (diaspora*)\" <#{AppConfig.mail.sender_address}>" @headers[:from] = "\"#{@comment.author_name} (diaspora*)\" <#{AppConfig.mail.sender_address}>"
@headers[:subject] = truncate(strip_markdown(@comment.comment_email_subject.squish), :length => TRUNCATION_LEN) @headers[:subject] = "Re: #{@comment.comment_email_subject}"
@headers[:subject] = "Re: #{@headers[:subject]}"
end end
end end
end end

View file

@ -1,9 +1,8 @@
class Notifier < ActionMailer::Base class Notifier < ActionMailer::Base
helper :application helper :application
helper :markdownify
helper :notifier helper :notifier
helper :people helper :people
def self.admin(string, recipients, opts = {}) def self.admin(string, recipients, opts = {})
mails = [] mails = []
recipients.each do |rec| recipients.each do |rec|
@ -16,7 +15,7 @@ class Notifier < ActionMailer::Base
def single_admin(string, recipient, opts={}) def single_admin(string, recipient, opts={})
@receiver = recipient @receiver = recipient
@string = string.html_safe @string = string.html_safe
if attach = opts.delete(:attachments) if attach = opts.delete(:attachments)
attach.each{ |f| attach.each{ |f|
attachments[f[:name]] = f[:file] attachments[f[:name]] = f[:file]

View file

@ -5,7 +5,7 @@
class Comment < ActiveRecord::Base class Comment < ActiveRecord::Base
include Diaspora::Federated::Base include Diaspora::Federated::Base
include Diaspora::Guid include Diaspora::Guid
include Diaspora::Relayable include Diaspora::Relayable
@ -22,7 +22,7 @@ class Comment < ActiveRecord::Base
belongs_to :commentable, :touch => true, :polymorphic => true belongs_to :commentable, :touch => true, :polymorphic => true
alias_attribute :post, :commentable alias_attribute :post, :commentable
belongs_to :author, :class_name => 'Person' belongs_to :author, :class_name => 'Person'
delegate :name, to: :author, prefix: true delegate :name, to: :author, prefix: true
delegate :comment_email_subject, to: :parent delegate :comment_email_subject, to: :parent
delegate :author_name, to: :parent, prefix: true delegate :author_name, to: :parent, prefix: true
@ -79,6 +79,10 @@ class Comment < ActiveRecord::Base
self.post = parent self.post = parent
end end
def message
@message ||= Diaspora::MessageRenderer.new text
end
def text= text def text= text
self[:text] = text.to_s.strip #to_s if for nil, for whatever reason self[:text] = text.to_s.strip #to_s if for nil, for whatever reason
end end

View file

@ -18,7 +18,7 @@ class Message < ActiveRecord::Base
validate :participant_of_parent_conversation validate :participant_of_parent_conversation
after_create do # don't use 'after_commit' here since there is a call to 'save!' after_create do # don't use 'after_commit' here since there is a call to 'save!'
# inside, which would cause an infinite recursion # inside, which would cause an infinite recursion
#sign comment as commenter #sign comment as commenter
self.author_signature = self.sign_with_key(self.author.owner.encryption_key) if self.author.owner self.author_signature = self.sign_with_key(self.author.owner.encryption_key) if self.author.owner
@ -71,8 +71,8 @@ class Message < ActiveRecord::Base
Notifications::PrivateMessage unless user.person == person Notifications::PrivateMessage unless user.person == person
end end
def formatted_message(opts={}) def message
opts[:plain_text] ? self.text: ERB::Util.h(self.text) @message ||= Diaspora::MessageRenderer.new text
end end
private private

View file

@ -55,7 +55,7 @@ class Profile < ActiveRecord::Base
def receive(user, person) def receive(user, person)
Rails.logger.info("event=receive payload_type=profile sender=#{person} to=#{user}") Rails.logger.info("event=receive payload_type=profile sender=#{person} to=#{user}")
profiles_attr = self.attributes.merge('tag_string' => self.tag_string).slice('diaspora_handle', 'first_name', 'last_name', 'image_url', 'image_url_small', 'image_url_medium', 'birthday', 'gender', 'bio', 'location', 'searchable', 'nsfw', 'tag_string') profiles_attr = self.attributes.merge('tag_string' => self.tag_string).slice('diaspora_handle', 'first_name', 'last_name', 'image_url', 'image_url_small', 'image_url_medium', 'birthday', 'gender', 'bio', 'location', 'searchable', 'nsfw', 'tag_string')
person.profile.update_attributes(profiles_attr) person.profile.update_attributes(profiles_attr)
person.profile person.profile
end end
@ -78,13 +78,13 @@ class Profile < ActiveRecord::Base
def from_omniauth_hash(omniauth_user_hash) def from_omniauth_hash(omniauth_user_hash)
mappings = {"description" => "bio", mappings = {"description" => "bio",
'image' => 'image_url', 'image' => 'image_url',
'name' => 'first_name', 'name' => 'first_name',
'location' => 'location', 'location' => 'location',
} }
update_hash = Hash[ omniauth_user_hash.map {|k, v| [mappings[k], v] } ] update_hash = Hash[ omniauth_user_hash.map {|k, v| [mappings[k], v] } ]
self.attributes.merge(update_hash){|key, old, new| old.blank? ? new : old} self.attributes.merge(update_hash){|key, old, new| old.blank? ? new : old}
end end
@ -132,6 +132,13 @@ class Profile < ActiveRecord::Base
birthday.to_s(:long).gsub(', 1000', '') if birthday.present? birthday.to_s(:long).gsub(', 1000', '') if birthday.present?
end end
def bio_message
@bio_message ||= Diaspora::MessageRenderer.new(bio)
end
def location_message
@location_message ||= Diaspora::MessageRenderer.new(location)
end
def tag_string def tag_string
if @tag_string if @tag_string

View file

@ -41,6 +41,10 @@ class Reshare < Post
self.root ? root.raw_message : super self.root ? root.raw_message : super
end end
def message
absolute_root.message if root.present?
end
def mentioned_people def mentioned_people
self.root ? root.mentioned_people : super self.root ? root.mentioned_people : super
end end

View file

@ -3,11 +3,8 @@
# the COPYRIGHT file. # the COPYRIGHT file.
class Service < ActiveRecord::Base class Service < ActiveRecord::Base
include ActionView::Helpers::TextHelper
include MarkdownifyHelper
attr_accessor :provider, :info, :access_level attr_accessor :provider, :info, :access_level
belongs_to :user belongs_to :user
validates_uniqueness_of :uid, :scope => :type validates_uniqueness_of :uid, :scope => :type
@ -26,12 +23,12 @@ class Service < ActiveRecord::Base
end end
def first_from_omniauth( auth_hash ) def first_from_omniauth( auth_hash )
@@auth = auth_hash @@auth = auth_hash
where( type: service_type, uid: options[:uid] ).first where( type: service_type, uid: options[:uid] ).first
end end
def initialize_from_omniauth( auth_hash ) def initialize_from_omniauth( auth_hash )
@@auth = auth_hash @@auth = auth_hash
service_type.constantize.new( options ) service_type.constantize.new( options )
end end
@ -44,7 +41,7 @@ class Service < ActiveRecord::Base
end end
def options def options
{ {
nickname: auth['info']['nickname'], nickname: auth['info']['nickname'],
access_token: auth['credentials']['token'], access_token: auth['credentials']['token'],
access_secret: auth['credentials']['secret'], access_secret: auth['credentials']['secret'],

View file

@ -1,9 +1,8 @@
class Services::Facebook < Service class Services::Facebook < Service
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include MarkdownifyHelper
OVERRIDE_FIELDS_ON_FB_UPDATE = [:contact_id, :person_id, :request_id, :invitation_id, :photo_url, :name, :username] OVERRIDE_FIELDS_ON_FB_UPDATE = [:contact_id, :person_id, :request_id, :invitation_id, :photo_url, :name, :username]
MAX_CHARACTERS = 63206 MAX_CHARACTERS = 63206
def provider def provider
"facebook" "facebook"
@ -22,11 +21,16 @@ class Services::Facebook < Service
end end
def create_post_params(post) def create_post_params(post)
message = strip_markdown(post.text(:plain_text => true)) message = post.message.plain_text_without_markdown
if post.photos.any? if post.photos.any?
message += " " + Rails.application.routes.url_helpers.short_post_url(post, :protocol => AppConfig.pod_uri.scheme, :host => AppConfig.pod_uri.authority) message += " " + short_post_url(post, protocol: AppConfig.pod_uri.scheme,
host: AppConfig.pod_uri.authority)
end end
{:message => message, :access_token => self.access_token, :link => URI.extract(message, ['https', 'http']).first}
{message: message,
access_token: access_token,
link: URI.extract(message, ['https', 'http']).first
}
end end
def profile_photo_url def profile_photo_url

View file

@ -1,7 +1,4 @@
class Services::Tumblr < Service class Services::Tumblr < Service
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper
MAX_CHARACTERS = 1000 MAX_CHARACTERS = 1000
def provider def provider
@ -38,10 +35,10 @@ class Services::Tumblr < Service
def tumblr_template(post, url) def tumblr_template(post, url)
html = '' html = ''
post.photos.each do |photo| post.photos.each do |photo|
html += "![photo](#{photo.url(:scaled_full)})\n\n" html << "![photo](#{photo.url(:scaled_full)})\n\n"
end end
html += post.text html << post.message.html(mentioned_people: [])
html += "\n\n[original post](#{url})" html << "\n\n[original post](#{url})"
end end
def delete_post(post) def delete_post(post)

View file

@ -1,7 +1,5 @@
class Services::Twitter < Service class Services::Twitter < Service
include ActionView::Helpers::TextHelper
include Rails.application.routes.url_helpers include Rails.application.routes.url_helpers
include MarkdownifyHelper
MAX_CHARACTERS = 140 MAX_CHARACTERS = 140
SHORTENED_URL_LENGTH = 21 SHORTENED_URL_LENGTH = 21
@ -40,7 +38,7 @@ class Services::Twitter < Service
def attempt_post post, retry_count=0 def attempt_post post, retry_count=0
message = build_twitter_post post, retry_count message = build_twitter_post post, retry_count
tweet = client.update message client.update message
rescue Twitter::Error::Forbidden => e rescue Twitter::Error::Forbidden => e
if ! e.message.include? 'is over 140' || retry_count == 20 if ! e.message.include? 'is over 140' || retry_count == 20
raise e raise e
@ -52,7 +50,7 @@ class Services::Twitter < Service
def build_twitter_post post, retry_count=0 def build_twitter_post post, retry_count=0
max_characters = MAX_CHARACTERS - retry_count max_characters = MAX_CHARACTERS - retry_count
post_text = strip_markdown post.text(plain_text: true) post_text = post.message.plain_text_without_markdown
truncate_and_add_post_link post, post_text, max_characters truncate_and_add_post_link post, post_text, max_characters
end end
@ -65,7 +63,7 @@ class Services::Twitter < Service
host: AppConfig.pod_uri.authority host: AppConfig.pod_uri.authority
) )
truncated_text = truncate post_text, length: max_characters - SHORTENED_URL_LENGTH + 1 truncated_text = post_text.truncate max_characters - SHORTENED_URL_LENGTH + 1
truncated_text = restore_truncated_url truncated_text, post_text, max_characters truncated_text = restore_truncated_url truncated_text, post_text, max_characters
"#{truncated_text} #{post_url}" "#{truncated_text} #{post_url}"
@ -95,11 +93,9 @@ class Services::Twitter < Service
return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/ return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0] url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
truncated_text = truncate( truncated_text = post_text.truncate(
post_text, max_characters - SHORTENED_URL_LENGTH + 2,
length: max_characters - SHORTENED_URL_LENGTH + 2, separator: ' ', omission: ''
separator: ' ',
omission: ''
) )
"#{truncated_text} #{url} ..." "#{truncated_text} #{url} ..."

View file

@ -1,32 +1,30 @@
class Services::Wordpress < Service class Services::Wordpress < Service
include ActionView::Helpers::TextHelper
include MarkdownifyHelper
MAX_CHARACTERS = 1000 MAX_CHARACTERS = 1000
attr_accessor :username, :password, :host, :path attr_accessor :username, :password, :host, :path
# uid = blog_id # uid = blog_id
def provider def provider
"wordpress" "wordpress"
end end
def post(post, url='') def post post, url=''
res = Faraday.new(:url => "https://public-api.wordpress.com").post do |req| res = Faraday.new(url: "https://public-api.wordpress.com").post do |req|
req.url "/rest/v1/sites/#{self.uid}/posts/new" req.url "/rest/v1/sites/#{self.uid}/posts/new"
req.body = post_body(post).to_json req.body = post_body(post).to_json
req.headers['Authorization'] = "Bearer #{self.access_token}" req.headers['Authorization'] = "Bearer #{self.access_token}"
req.headers['Content-Type'] = 'application/json' req.headers['Content-Type'] = 'application/json'
end end
JSON.parse res.env[:body] JSON.parse res.env[:body]
end end
def post_body(post, url='') def post_body post
post_text = markdownify(post.text) {
post_title = truncate(strip_markdown(post.text(:plain_text => true)), :length => 40, :separator => ' ') title: post.message.title(length: 40),
content: post.message.markdownified(disable_hovercards: true)
{:title => post_title, :content => post_text.html_safe} }
end end
end end

View file

@ -5,7 +5,6 @@
class StatusMessage < Post class StatusMessage < Post
include Diaspora::Taggable include Diaspora::Taggable
include ActionView::Helpers::TextHelper
include PeopleHelper include PeopleHelper
acts_as_taggable_on :tags acts_as_taggable_on :tags
@ -56,10 +55,6 @@ class StatusMessage < Post
tag_stream(tag_ids) tag_stream(tag_ids)
end end
def text(opts = {})
self.formatted_message(opts)
end
def raw_message def raw_message
read_attribute(:text) read_attribute(:text)
end end
@ -77,12 +72,8 @@ class StatusMessage < Post
self.raw_message.match(/#nsfw/i) || super self.raw_message.match(/#nsfw/i) || super
end end
def formatted_message(opts={}) def message
return self.raw_message unless self.raw_message @message ||= Diaspora::MessageRenderer.new raw_message, mentioned_people: mentioned_people
escaped_message = opts[:plain_text] ? self.raw_message : ERB::Util.h(self.raw_message)
mentioned_message = Diaspora::Mentionable.format(escaped_message, self.mentioned_people, opts)
Diaspora::Taggable.format_tags(mentioned_message, opts.merge(:no_escape => true))
end end
def mentioned_people def mentioned_people
@ -134,7 +125,7 @@ class StatusMessage < Post
end end
def comment_email_subject def comment_email_subject
formatted_message(:plain_text => true) message.title length: 70
end end
def first_photo_url(*args) def first_photo_url(*args)
@ -142,7 +133,7 @@ class StatusMessage < Post
end end
def text_and_photos_blank? def text_and_photos_blank?
self.text.blank? && self.photos.blank? self.raw_message.blank? && self.photos.blank?
end end
def queue_gather_oembed_data def queue_gather_oembed_data

View file

@ -1,6 +1,5 @@
class OEmbedPresenter class OEmbedPresenter
include PostsHelper include PostsHelper
include ActionView::Helpers::TextHelper
def initialize(post, opts = {}) def initialize(post, opts = {})
@post = post @post = post
@ -13,14 +12,14 @@ class OEmbedPresenter
def as_json(opts={}) def as_json(opts={})
{ {
:provider_name => "Diaspora", :provider_name => "Diaspora",
:provider_url => AppConfig.pod_uri.to_s, :provider_url => AppConfig.pod_uri.to_s,
:type => 'rich', :type => 'rich',
:version => '1.0', :version => '1.0',
:title => post_title, :title => post_title,
:author_name => post_author, :author_name => post_author,
:author_url => post_author_url, :author_url => post_author_url,
:width => @opts.fetch(:maxwidth, 516), :width => @opts.fetch(:maxwidth, 516),
:height => @opts.fetch(:maxheight, 320), :height => @opts.fetch(:maxheight, 320),
:html => iframe_html :html => iframe_html
} }

View file

@ -1,6 +1,5 @@
class PostPresenter class PostPresenter
include PostsHelper include PostsHelper
include ActionView::Helpers::TextHelper
attr_accessor :post, :current_user attr_accessor :post, :current_user
@ -47,7 +46,7 @@ class PostPresenter
end end
def title def title
@post.text.present? ? post_page_title(@post) : I18n.translate('posts.presenter.title', :name => @post.author_name) @post.message.present? ? @post.message.title : I18n.t('posts.presenter.title', name: @post.author_name)
end end
def root def root

View file

@ -16,4 +16,4 @@
= timeago(comment.created_at ? comment.created_at : Time.now) = timeago(comment.created_at ? comment.created_at : Time.now)
%div{:class => direction_for(comment.text)} %div{:class => direction_for(comment.text)}
= markdownify(comment) = comment.message.markdownified

View file

@ -11,4 +11,4 @@
= timeago(message.created_at) = timeago(message.created_at)
%div{ :class => direction_for(message.text) } %div{ :class => direction_for(message.text) }
= markdownify(message, :oembed => true) = message.message.markdownified

View file

@ -15,35 +15,35 @@
.profile_button .profile_button
= link_to content_tag(:div, nil, :class => 'icons-mention', :title => t('people.show.mention'), :id => 'mention_button'), new_status_message_path(:person_id => @person.id), :rel => 'facebox' = link_to content_tag(:div, nil, :class => 'icons-mention', :title => t('people.show.mention'), :id => 'mention_button'), new_status_message_path(:person_id => @person.id), :rel => 'facebox'
.white_bar .white_bar
- if @contact.mutual? - if @contact.mutual?
.profile_button .profile_button
= link_to content_tag(:div, nil, :class => 'icons-message', :title => t('people.show.message'), :id => 'message_button'), new_conversation_path(:contact_id => @contact.id, :name => @contact.person.name), :rel => 'facebox' = link_to content_tag(:div, nil, :class => 'icons-message', :title => t('people.show.message'), :id => 'message_button'), new_conversation_path(:contact_id => @contact.id, :name => @contact.person.name), :rel => 'facebox'
.white_bar .white_bar
.profile_button .profile_button
= link_to content_tag(:div, nil, :class => 'icons-ignoreuser block_user', :title => t('ignore'), :id => 'block_user_button', :data => { :person_id => @person.id }), '#', :rel => "nofollow" if @block.blank? = link_to content_tag(:div, nil, :class => 'icons-ignoreuser block_user', :title => t('ignore'), :id => 'block_user_button', :data => { :person_id => @person.id }), '#', :rel => "nofollow" if @block.blank?
%br
%br
-if contact.sharing? || person == current_user.person -if contact.sharing? || person == current_user.person
%ul#profile_information %ul#profile_information
- unless person.bio.blank? - unless person.bio.blank?
%li %li
%h4 %h4
=t('.bio') =t('.bio')
%div{ :class => direction_for(person.bio) } %div{ :class => direction_for(person.bio) }
= markdownify(person.profile.bio, :oembed => true, :newlines => true) = person.profile.bio_message.markdownified
- unless person.profile.location.blank? - unless person.profile.location.blank?
%li %li
%h4 %h4
=t('.location') =t('.location')
%div{ :class => direction_for(person.location) } %div{ :class => direction_for(person.location) }
= markdownify(person.location, :oembed => true, :newlines => true) = person.profile.location_message.markdownified
- unless person.gender.blank? - unless person.gender.blank?
%li %li
@ -66,7 +66,7 @@
= image_tag(photo.url(:thumb_small)) = image_tag(photo.url(:thumb_small))
%br %br
= link_to t('layouts.header.view_all'), person_photos_path(person) = link_to t('layouts.header.view_all'), person_photos_path(person)
- if person == current_user.person - if person == current_user.person
%li.image_list %li.image_list
%h4 %h4

View file

@ -15,7 +15,7 @@
= image_tag post.image_url = image_tag post.image_url
%div{:class => direction_for(post.text)} %div{:class => direction_for(post.text)}
!= markdownify(post) != post.message.markdownified
- if post.o_embed_cache - if post.o_embed_cache
!= o_embed_html post.o_embed_cache != o_embed_html post.o_embed_cache
-if post.open_graph_cache -if post.open_graph_cache

View file

@ -27,12 +27,11 @@ atom_feed({'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
end end
@posts.each do |post| @posts.each do |post|
post = post.absolute_root unless post.absolute_root.nil? if post.is_a? Reshare
feed.entry post, :url => "#{@user.url}p/#{post.id}", feed.entry post, :url => "#{@user.url}p/#{post.id}",
:id => "#{@user.url}p/#{post.id}" do |entry| :id => "#{@user.url}p/#{post.id}" do |entry|
entry.title post_page_title(post) entry.title post.message.title
entry.content markdownify(post), :type => 'html' entry.content post.message.markdownified(disable_hovercards: true), :type => 'html'
entry.tag! 'activity:verb', 'http://activitystrea.ms/schema/1.0/post' entry.tag! 'activity:verb', 'http://activitystrea.ms/schema/1.0/post'
entry.tag! 'activity:object-type', 'http://activitystrea.ms/schema/1.0/note' entry.tag! 'activity:object-type', 'http://activitystrea.ms/schema/1.0/note'
end end

View file

@ -13,9 +13,6 @@ require 'typhoeus'
# Presenters # Presenters
require 'post_presenter' require 'post_presenter'
# Helpers
require 'markdownify_helper'
# Our libs # Our libs
require 'collect_user_photos' require 'collect_user_photos'
require 'diaspora' require 'diaspora'

View file

@ -7,6 +7,7 @@ module Diaspora
require 'diaspora/parser' require 'diaspora/parser'
require 'diaspora/fetcher' require 'diaspora/fetcher'
require 'diaspora/markdownify' require 'diaspora/markdownify'
require 'diaspora/message_renderer'
require 'diaspora/mentionable' require 'diaspora/mentionable'
require 'diaspora/exporter' require 'diaspora/exporter'
require 'diaspora/federated' require 'diaspora/federated'

View file

@ -2,12 +2,10 @@ module Diaspora
module Markdownify module Markdownify
class HTML < Redcarpet::Render::HTML class HTML < Redcarpet::Render::HTML
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper
def autolink(link, type) def autolink link, type
auto_link(link, :link => :urls, :html => { :target => "_blank" }) auto_link(link, link: :urls, html: { target: "_blank" })
end end
end end
end end
end end

View file

@ -0,0 +1,233 @@
module Diaspora
# Takes a raw message text and converts it to
# various desired target formats, respecting
# all possible formatting options supported
# by Diaspora
class MessageRenderer
class Processor
class << self
private :new
def process message, options, &block
return '' if message.blank? # Optimize for empty message
processor = new message, options
processor.instance_exec(&block)
processor.message
end
end
attr_reader :message, :options
def initialize message, options
@message = message
@options = options
end
def squish
@message = message.squish if options[:squish]
end
def append_and_truncate
if options[:truncate]
@message = message.truncate options[:truncate]-options[:append].to_s.size
end
message << options[:append].to_s
message << options[:append_after_truncate].to_s
end
include ActionView::Helpers::TagHelper
def escape
if options[:escape]
# TODO: On Rails 4 port change this to ERB::Util.html_escape_once
# and remove the include
@message = escape_once message
# Special case Hex entities since escape_once
# doesn't catch them.
# TODO: Watch for https://github.com/rails/rails/pull/9102
# on whether this can be removed
@message = message.gsub(/&amp;(#[xX][\dA-Fa-f]{1,4});/, '&\1;')
end
end
def strip_markdown
renderer = Redcarpet::Markdown.new Redcarpet::Render::StripDown, options[:markdown_options]
@message = renderer.render(message).strip
end
def markdownify
renderer = Diaspora::Markdownify::HTML.new options[:markdown_render_options]
markdown = Redcarpet::Markdown.new renderer, options[:markdown_options]
@message = markdown.render message
end
# In very clear cases, let newlines become <br /> tags
# Grabbed from Github flavored Markdown
def process_newlines
message.gsub(/^[\w\<][^\n]*\n+/) do |x|
x =~ /\n{2}/ ? x : (x.strip!; x << " \n")
end
end
def render_mentions
unless options[:disable_hovercards] || options[:mentioned_people].empty?
@message = Diaspora::Mentionable.format message, options[:mentioned_people]
end
if options[:disable_hovercards] || options[:link_all_mentions]
@message = Diaspora::Mentionable.filter_for_aspects message, nil
else
make_mentions_plain_text
end
end
def make_mentions_plain_text
@message = Diaspora::Mentionable.format message, [], plain_text: true
end
def render_tags
@message = Diaspora::Taggable.format_tags message, no_escape: !options[:escape_tags]
end
end
DEFAULTS = {mentioned_people: [],
link_all_mentions: false,
disable_hovercards: false,
truncate: false,
append: nil,
append_after_truncate: nil,
squish: false,
escape: true,
escape_tags: false,
markdown_options: {
autolink: true,
fenced_code_blocks: true,
space_after_headers: true,
strikethrough: true,
tables: true,
no_intra_emphasis: true,
},
markdown_render_options: {
filter_html: true,
hard_wrap: true,
safe_links_only: true
}}.freeze
delegate :empty?, :blank?, :present?, to: :raw
# @param [String] raw_message Raw input text
# @param [Hash] opts Global options affecting output
# @option opts [Array<Person>] :mentioned_people ([]) List of people
# allowed to mention
# @option opts [Boolean] :link_all_mentions (false) Whether to link
# all mentions. This makes plain links to profiles for people not in
# :mentioned_people
# @option opts [Boolean] :disable_hovercards (true) Render all mentions
# as profile links. This implies :link_all_mentions and ignores
# :mentioned_people
# @option opts [#to_i, Boolean] :truncate (false) Truncate message to
# the specified length
# @option opts [String] :append (nil) Append text to the end of
# the (truncated) message, counts into truncation length
# @option opts [String] :append_after_truncate (nil) Append text to the end
# of the (truncated) message, doesn't count into the truncation length
# @option opts [Boolean] :squish (false) Squish the message, that is
# remove all surrounding and consecutive whitespace
# @option opts [Boolean] :escape (true) Escape HTML relevant characters
# in the message. Note that his option is ignored in the plaintext
# renderers.
# @option opts [Boolean] :escape_tags (false) Escape HTML relevant
# characters in tags when rendering them
# @option opts [Hash] :markdown_options Override default options passed
# to Redcarpet
# @option opts [Hash] :markdown_render_options Override default options
# passed to the Redcarpet renderer
def initialize raw_message, opts={}
@raw_message = raw_message
@options = DEFAULTS.deep_merge opts
end
# @param [Hash] opts Override global output options, see {#initialize}
def plain_text opts={}
process(opts) {
make_mentions_plain_text
squish
append_and_truncate
}
end
# @param [Hash] opts Override global output options, see {#initialize}
def plain_text_without_markdown opts={}
process(opts) {
make_mentions_plain_text
strip_markdown
squish
append_and_truncate
}
end
# @param [Hash] opts Override global output options, see {#initialize}
def html opts={}
process(opts) {
escape
render_mentions
render_tags
squish
append_and_truncate
}.html_safe
end
# @param [Hash] opts Override global output options, see {#initialize}
def markdownified opts={}
process(opts) {
process_newlines
markdownify
render_mentions
render_tags
squish
append_and_truncate
}.html_safe
end
# Get a short summary of the message
# @param [Hash] opts Additional options
# @option opts [Integer] :length (20 | first heading) Truncate the title to
# this length. If not given defaults to 20 and to not truncate
# if a heading is found.
def title opts={}
# Setext-style header
heading = if /\A(?<setext_content>.{1,200})\n(?:={1,200}|-{1,200})(?:\r?\n|$)/ =~ @raw_message.lstrip
setext_content
# Atx-style header
elsif /\A\#{1,6}\s+(?<atx_content>.{1,200}?)(?:\s+#+)?(?:\r?\n|$)/ =~ @raw_message.lstrip
atx_content
end
heading &&= heading.strip
if heading && opts[:length]
heading.truncate opts[:length]
elsif heading
heading
else
plain_text_without_markdown squish: true, truncate: opts.fetch(:length, 20)
end
end
def raw
@raw_message
end
def to_s
plain_text
end
private
def process opts, &block
Processor.process(@raw_message, @options.deep_merge(opts), &block)
end
end
end

View file

@ -1,105 +0,0 @@
# Copyright (c) 2010-2011, Diaspora Inc. This file is
# licensed under the Affero General Public License version 3 or later. See
# the COPYRIGHT file.
require 'spec_helper'
describe MarkdownifyHelper do
describe "#markdownify" do
describe "not doing something dumb" do
it "strips out script tags" do
markdownify("<script>alert('XSS is evil')</script>").should ==
"<p>alert(&#39;XSS is evil&#39;)</p>\n"
end
it 'strips onClick handlers from links' do
omghax = '[XSS](http://joindiaspora.com/" onClick="$\(\'a\'\).remove\(\))'
markdownify(omghax).should_not match(/ onClick/i)
end
end
it 'does not barf if message is nil' do
markdownify(nil).should == ''
end
it 'autolinks standard url links' do
markdownified = markdownify("http://joindiaspora.com/")
doc = Nokogiri.parse(markdownified)
link = doc.css("a")
link.attr("href").value.should == "http://joindiaspora.com/"
end
context 'when formatting status messages' do
it "should leave tags intact" do
message = FactoryGirl.create(:status_message,
:author => alice.person,
:text => "I love #markdown")
formatted = markdownify(message)
formatted.should =~ %r{<a href="/tags/markdown" class="tag">#markdown</a>}
end
it 'should leave multi-underscore tags intact' do
message = FactoryGirl.create(
:status_message,
:author => alice.person,
:text => "Here is a #multi_word tag"
)
formatted = markdownify(message)
formatted.should =~ %r{Here is a <a href="/tags/multi_word" class="tag">#multi_word</a> tag}
message = FactoryGirl.create(
:status_message,
:author => alice.person,
:text => "Here is a #multi_word_tag yo"
)
formatted = markdownify(message)
formatted.should =~ %r{Here is a <a href="/tags/multi_word_tag" class="tag">#multi_word_tag</a> yo}
end
it "should leave mentions intact" do
message = FactoryGirl.create(:status_message,
:author => alice.person,
:text => "Hey @{Bob; #{bob.diaspora_handle}}!")
formatted = markdownify(message)
formatted.should =~ /hovercard/
end
it "should leave mentions intact for real diaspora handles" do
new_person = FactoryGirl.create(:person, :diaspora_handle => 'maxwell@joindiaspora.com')
message = FactoryGirl.create(:status_message,
:author => alice.person,
:text => "Hey @{maxwell@joindiaspora.com; #{new_person.diaspora_handle}}!")
formatted = markdownify(message)
formatted.should =~ /hovercard/
end
it 'should process text with both a hashtag and a link' do
message = FactoryGirl.create(:status_message,
:author => alice.person,
:text => "Test #tag?\nhttps://joindiaspora.com\n")
formatted = markdownify(message)
formatted.should == %{<p>Test <a href="/tags/tag" class="tag">#tag</a>?<br>\n<a href="https://joindiaspora.com" target="_blank">https://joindiaspora.com</a></p>\n}
end
it 'should process text with a header' do
message = "# I love markdown"
markdownify(message).should match "I love markdown"
end
end
end
describe "#strip_markdown" do
it 'does not remove markdown in links' do
message = "some text and here comes http://exampe.org/foo_bar_baz a link"
strip_markdown(message).should match message
end
it 'does not destroy hashtag that starts a line' do
message = "#hashtag message"
strip_markdown(message).should match message
end
end
end

View file

@ -5,8 +5,6 @@
require 'spec_helper' require 'spec_helper'
describe NotifierHelper do describe NotifierHelper do
include MarkdownifyHelper
describe '#post_message' do describe '#post_message' do
before do before do
# post for truncate test # post for truncate test

View file

@ -12,30 +12,11 @@ describe PostsHelper do
end end
context 'with posts with text' do context 'with posts with text' do
context 'when :length is passed in parameters' do it "delegates to message.title" do
it 'returns string of size less or equal to :length' do message = double
@sm = double(:text => "## My title\n Post content...") message.should_receive(:title)
string_size = 12 post = double(message: message)
post_page_title(@sm, :length => string_size ).size.should <= string_size post_page_title(post)
end
end
context 'when :length is not passed in parameters' do
context 'with a Markdown header of less than 200 characters on first line'do
it 'returns atx style header' do
@sm = double(:text => "## My title\n Post content...")
post_page_title(@sm).should == "## My title"
end
it 'returns setext style header' do
@sm = double(:text => "My title \n======\n Post content...")
post_page_title(@sm).should == "My title \n======"
end
end
context 'without a Markdown header of less than 200 characters on first line 'do
it 'truncates posts to the 20 first characters' do
@sm = double(:text => "Very, very, very long post")
post_page_title(@sm).should == "Very, very, very ..."
end
end
end end
end end
end end

View file

@ -0,0 +1,175 @@
require 'spec_helper'
describe Diaspora::MessageRenderer do
def message text, opts={}
Diaspora::MessageRenderer.new(text, opts)
end
describe '#title' do
context 'when :length is passed in parameters' do
it 'returns string of size less or equal to :length' do
string_size = 12
title = message("## My title\n Post content...").title(length: string_size)
expect(title.size).to be <= string_size
end
end
context 'when :length is not passed in parameters' do
context 'with a Markdown header of less than 200 characters on first line' do
it 'returns atx style header' do
expect(message("## My title\n Post content...").title).to eq "My title"
expect(message("## My title ##\n Post content...").title).to eq "My title"
end
it 'returns setext style header' do
expect(message("My title \n======\n Post content...").title).to eq "My title"
end
end
context 'without a Markdown header of less than 200 characters on first line ' do
it 'truncates posts to the 20 first characters' do
expect(message("Very, very, very long post").title).to eq "Very, very, very ..."
end
end
end
end
describe '#html' do
it 'escapes the message' do
xss = "</a> <script> alert('hey'); </script>"
expect(message(xss).html).to_not include xss
end
it 'is html_safe' do
expect(message("hey guys").html).to be_html_safe
end
it 'should leave HTML entities intact' do
entities = '&amp; &szlig; &#x27; &#39; &quot;'
expect(message(entities).html).to eq entities
end
context 'with mentions' do
it 'makes hovercard links for mentioned people' do
expect(
message(
"@{Bob; #{bob.person.diaspora_handle}}",
mentioned_people: [bob.person]
).html
).to include 'hovercard'
end
it 'makes plaintext out of mentions of people not in the posts aspects' do
expect(
message("@{Bob; #{bob.person.diaspora_handle}}").html
).to_not include 'href'
end
context 'linking all mentions' do
it 'makes plain links for people not in the post aspects' do
message = message("@{Bob; #{bob.person.diaspora_handle}}", link_all_mentions: true).html
expect(message).to_not include 'hovercard'
expect(message).to include '/u/bob'
end
it "makes no hovercards if they're disabled" do
message = message(
"@{Bob; #{bob.person.diaspora_handle}}",
mentioned_people: [bob.person],
disable_hovercards: true
).html
expect(message).to_not include 'hovercard'
expect(message).to include '/u/bob'
end
end
end
end
describe "#markdownified" do
describe "not doing something dumb" do
it "strips out script tags" do
expect(
message("<script>alert('XSS is evil')</script>").markdownified
).to eq "<p>alert(&#39;XSS is evil&#39;)</p>\n"
end
it 'strips onClick handlers from links' do
expect(
message('[XSS](http://joindiaspora.com/" onClick="$\(\'a\'\).remove\(\))').markdownified
).to_not match(/ onClick/i)
end
end
it 'does not barf if message is nil' do
expect(message(nil).markdownified).to eq ''
end
it 'autolinks standard url links' do
expect(
message("http://joindiaspora.com/"
).markdownified).to include 'href="http://joindiaspora.com/"'
end
context 'when formatting status messages' do
it "should leave tags intact" do
expect(
message("I love #markdown").markdownified
).to match %r{<a href="/tags/markdown" class="tag">#markdown</a>}
end
it 'should leave multi-underscore tags intact' do
expect(
message("Here is a #multi_word tag").markdownified
).to match %r{Here is a <a href="/tags/multi_word" class="tag">#multi_word</a> tag}
expect(
message("Here is a #multi_word_tag yo").markdownified
).to match %r{Here is a <a href="/tags/multi_word_tag" class="tag">#multi_word_tag</a> yo}
end
it "should leave mentions intact" do
expect(
message("Hey @{Bob; #{bob.diaspora_handle}}!", mentioned_people: [bob.person]).markdownified
).to match(/hovercard/)
end
it "should leave mentions intact for real diaspora handles" do
new_person = FactoryGirl.create(:person, diaspora_handle: 'maxwell@joindiaspora.com')
expect(
message(
"Hey @{maxwell@joindiaspora.com; #{new_person.diaspora_handle}}!",
mentioned_people: [new_person]
).markdownified
).to match(/hovercard/)
end
it 'should process text with both a hashtag and a link' do
expect(
message("Test #tag?\nhttps://joindiaspora.com\n").markdownified
).to eq %{<p>Test <a href="/tags/tag" class="tag">#tag</a>?<br>\n<a href="https://joindiaspora.com" target="_blank">https://joindiaspora.com</a></p>\n}
end
it 'should process text with a header' do
expect(message("# I love markdown").markdownified).to match "I love markdown"
end
it 'should leave HTML entities intact' do
entities = '&amp; &szlig; &#x27; &#39; &quot;'
expect(message(entities).markdownified).to eq "<p>#{entities}</p>\n"
end
end
end
describe "#plain_text_without_markdown" do
it 'does not remove markdown in links' do
text = "some text and here comes http://exampe.org/foo_bar_baz a link"
expect(message(text).plain_text_without_markdown).to eq text
end
it 'does not destroy hashtag that starts a line' do
text = "#hashtag message"
expect(message(text).plain_text_without_markdown).to eq text
end
end
end

View file

@ -1,9 +1,6 @@
require 'spec_helper' require 'spec_helper'
describe Notifier do describe Notifier do
include ActionView::Helpers::TextHelper
include MarkdownifyHelper
let(:person) { FactoryGirl.create(:person) } let(:person) { FactoryGirl.create(:person) }
before do before do
@ -120,7 +117,7 @@ describe Notifier do
end end
it 'BODY: contains the truncated original post' do it 'BODY: contains the truncated original post' do
@mail.body.encoded.should include(@sm.formatted_message) @mail.body.encoded.should include(@sm.message.plain_text)
end end
it 'BODY: contains the name of person liking' do it 'BODY: contains the name of person liking' do
@ -150,7 +147,7 @@ describe Notifier do
end end
it 'BODY: contains the truncated original post' do it 'BODY: contains the truncated original post' do
@mail.body.encoded.should include(@sm.formatted_message) @mail.body.encoded.should include(@sm.message.plain_text)
end end
it 'BODY: contains the name of person liking' do it 'BODY: contains the name of person liking' do
@ -224,7 +221,7 @@ describe Notifier do
end end
it 'SUBJECT: has a snippet of the post contents, without markdown and without newlines' do it 'SUBJECT: has a snippet of the post contents, without markdown and without newlines' do
comment_mail.subject.should == "Re: Headline It's really sunny outside today, and this is a super long ..." comment_mail.subject.should == "Re: Headline"
end end
context 'BODY' do context 'BODY' do
@ -265,7 +262,7 @@ describe Notifier do
end end
it 'SUBJECT: has a snippet of the post contents, without markdown and without newlines' do it 'SUBJECT: has a snippet of the post contents, without markdown and without newlines' do
comment_mail.subject.should == "Re: Headline It's really sunny outside today, and this is a super long ..." comment_mail.subject.should == "Re: Headline"
end end
context 'BODY' do context 'BODY' do

View file

@ -25,17 +25,17 @@ describe Services::Facebook do
end end
it 'removes text formatting markdown from post text' do it 'removes text formatting markdown from post text' do
message = "Text with some **bolded** and _italic_ parts." message = double
post = double(:text => message, :photos => []) message.should_receive(:plain_text_without_markdown).and_return("")
post = double(message: message, photos: [])
post_params = @service.create_post_params(post) post_params = @service.create_post_params(post)
post_params[:message].should match "Text with some bolded and italic parts."
end end
it 'does not add post link when no photos' do it 'does not add post link when no photos' do
message = "Text with some **bolded** and _italic_ parts." message = "Some text."
post = double(:text => message, :photos => []) post = double(message: double(plain_text_without_markdown: message), photos: [])
post_params = @service.create_post_params(post) post_params = @service.create_post_params(post)
post_params[:message].should match "Text with some bolded and italic parts." post_params[:message].should_not include "http"
end end
it 'sets facebook id on post' do it 'sets facebook id on post' do

View file

@ -38,9 +38,10 @@ describe Services::Twitter do
end end
it 'removes text formatting markdown from post text' do it 'removes text formatting markdown from post text' do
message = "Text with some **bolded** and _italic_ parts." message = double
post = double(:text => message, :photos => []) message.should_receive(:plain_text_without_markdown).and_return("")
@service.send(:build_twitter_post, post).should match "Text with some bolded and italic parts." post = double(message: message, photos: [])
@service.send(:build_twitter_post, post)
end end
end end
@ -53,19 +54,19 @@ describe Services::Twitter do
it "should not truncate a short message" do it "should not truncate a short message" do
short_message = SecureRandom.hex(20) short_message = SecureRandom.hex(20)
short_post = double(:text => short_message, :photos => []) short_post = double(message: double(plain_text_without_markdown: short_message), photos: [])
@service.send(:build_twitter_post, short_post).should match short_message @service.send(:build_twitter_post, short_post).should match short_message
end end
it "should truncate a long message" do it "should truncate a long message" do
long_message = SecureRandom.hex(220) long_message = SecureRandom.hex(220)
long_post = double(:text => long_message, :id => 1, :photos => []) long_post = double(message: double(plain_text_without_markdown: long_message), id: 1, photos: [])
@service.send(:build_twitter_post, long_post).length.should be < long_message.length @service.send(:build_twitter_post, long_post).length.should be < long_message.length
end end
it "should not truncate a long message with an http url" do it "should not truncate a long message with an http url" do
long_message = " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end long_message = " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
long_post = double(:text => long_message, :id => 1, :photos => []) long_post = double(message: double(plain_text_without_markdown: long_message), id: 1, photos: [])
@post.text = long_message @post.text = long_message
answer = @service.send(:build_twitter_post, @post) answer = @service.send(:build_twitter_post, @post)
@ -74,7 +75,7 @@ describe Services::Twitter do
it "should not cut links when truncating a post" do it "should not cut links when truncating a post" do
long_message = SecureRandom.hex(40) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55) long_message = SecureRandom.hex(40) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55)
long_post = double(:text => long_message, :id => 1, :photos => []) long_post = double(message: double(plain_text_without_markdown: long_message), id: 1, photos: [])
answer = @service.send(:build_twitter_post, long_post) answer = @service.send(:build_twitter_post, long_post)
answer.should match /\.\.\./ answer.should match /\.\.\./
@ -83,7 +84,7 @@ describe Services::Twitter do
it "should append the otherwise-cut link when truncating a post" do it "should append the otherwise-cut link when truncating a post" do
long_message = "http://joindiaspora.com/a-very-long-decoy-url.html " + SecureRandom.hex(20) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55) + " http://joindiaspora.com/a-very-long-decoy-url-part-2.html" long_message = "http://joindiaspora.com/a-very-long-decoy-url.html " + SecureRandom.hex(20) + " http://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + SecureRandom.hex(55) + " http://joindiaspora.com/a-very-long-decoy-url-part-2.html"
long_post = double(:text => long_message, :id => 1, :photos => []) long_post = double(message: double(plain_text_without_markdown: long_message), id: 1, photos: [])
answer = @service.send(:build_twitter_post, long_post) answer = @service.send(:build_twitter_post, long_post)
answer.should match /\.\.\./ answer.should match /\.\.\./
@ -99,7 +100,7 @@ describe Services::Twitter do
it "should truncate a long message with an ftp url" do it "should truncate a long message with an ftp url" do
long_message = @long_message_start + " ftp://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end long_message = @long_message_start + " ftp://joindiaspora.com/a-very-long-url-name-that-will-be-shortened.html " + @long_message_end
long_post = double(:text => long_message, :id => 1, :photos => []) long_post = double(message: double(plain_text_without_markdown: long_message), id: 1, photos: [])
answer = @service.send(:build_twitter_post, long_post) answer = @service.send(:build_twitter_post, long_post)
answer.should match /\.\.\./ answer.should match /\.\.\./
@ -107,7 +108,7 @@ describe Services::Twitter do
it "should not truncate a message of maximum length" do it "should not truncate a message of maximum length" do
exact_size_message = SecureRandom.hex(70) exact_size_message = SecureRandom.hex(70)
exact_size_post = double(:text => exact_size_message, :id => 1, :photos => []) exact_size_post = double(message: double(plain_text_without_markdown: exact_size_message), id: 1, photos: [])
answer = @service.send(:build_twitter_post, exact_size_post) answer = @service.send(:build_twitter_post, exact_size_post)
answer.should match exact_size_message answer.should match exact_size_message

View file

@ -150,18 +150,6 @@ STR
@sm = FactoryGirl.create(:status_message, :text => @test_string ) @sm = FactoryGirl.create(:status_message, :text => @test_string )
end end
describe '#formatted_message' do
it 'escapes the message' do
xss = "</a> <script> alert('hey'); </script>"
@sm.text << xss
@sm.formatted_message.should_not include xss
end
it 'is html_safe' do
@sm.formatted_message.html_safe?.should be_true
end
end
describe '#create_mentions' do describe '#create_mentions' do
it 'creates a mention for everyone mentioned in the message' do it 'creates a mention for everyone mentioned in the message' do
Diaspora::Mentionable.should_receive(:people_from_string).and_return(@people) Diaspora::Mentionable.should_receive(:people_from_string).and_return(@people)

View file

@ -65,32 +65,17 @@ describe PostPresenter do
describe '#title' do describe '#title' do
context 'with posts with text' do context 'with posts with text' do
context 'with a Markdown header of less than 200 characters on first line'do it "delegates to message.title" do
it 'returns atx style header' do message = double(present?: true)
@sm = double(:text => "## My title\n Post content...") message.should_receive(:title)
@presenter.post = @sm @presenter.post = double(message: message)
@presenter.title.should == "## My title" @presenter.title
end
it 'returns setext style header' do
@sm = double(:text => "My title \n======\n Post content...")
@presenter.post = @sm
@presenter.title.should == "My title \n======"
end
end
context 'without a Markdown header of less than 200 characters on first line 'do
it 'truncates post to the 20 first characters' do
@sm = double(:text => "Very, very, very long post")
@presenter.post = @sm
@presenter.title.should == "Very, very, very ..."
end
end end
end end
context 'with posts without text' do context 'with posts without text' do
it ' displays a messaage with the post class' do it ' displays a messaage with the post class' do
@sm = double(:text => "", :author => bob.person, :author_name => bob.person.name) @sm = double(message: double(present?: false), author: bob.person, author_name: bob.person.name)
@presenter.post = @sm @presenter.post = @sm
@presenter.title.should == "A post from #{@sm.author.name}" @presenter.title.should == "A post from #{@sm.author.name}"
end end