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:
parent
8f67e1eb17
commit
8280556a47
40 changed files with 545 additions and 389 deletions
2
Gemfile
2
Gemfile
|
|
@ -65,7 +65,7 @@ gem 'messagebus_ruby_api', '1.0.3'
|
|||
|
||||
gem 'nokogiri', '1.6.1'
|
||||
gem 'rails_autolink', '1.1.5'
|
||||
gem 'redcarpet', '3.0.0'
|
||||
gem 'redcarpet', '3.1.1'
|
||||
gem 'roxml', '3.1.6'
|
||||
gem 'ruby-oembed', '0.8.9'
|
||||
gem 'opengraph_parser', '0.2.3'
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ GEM
|
|||
rb-inotify (0.9.3)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
redcarpet (3.0.0)
|
||||
redcarpet (3.1.1)
|
||||
redis (3.0.6)
|
||||
redis-namespace (1.4.1)
|
||||
redis (~> 3.0.4)
|
||||
|
|
@ -512,7 +512,7 @@ DEPENDENCIES
|
|||
rails_autolink (= 1.1.5)
|
||||
rb-fsevent (= 0.9.4)
|
||||
rb-inotify (= 0.9.3)
|
||||
redcarpet (= 3.0.0)
|
||||
redcarpet (= 3.1.1)
|
||||
remotipart (= 1.2.1)
|
||||
rmagick (= 2.13.2)
|
||||
roxml (= 3.1.6)
|
||||
|
|
|
|||
|
|
@ -98,7 +98,11 @@ class UsersController < ApplicationController
|
|||
if @user = User.find_by_username(params[:username])
|
||||
respond_to do |format|
|
||||
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
|
||||
|
||||
format.any { redirect_to person_path(@user.person) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,28 +1,20 @@
|
|||
module NotifierHelper
|
||||
|
||||
|
||||
# @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.
|
||||
def post_message(post, opts={})
|
||||
opts[:length] ||= 200
|
||||
if post.respond_to? :formatted_message
|
||||
message = strip_markdown(post.formatted_message(:plain_text => true))
|
||||
message = truncate(message, :length => opts[:length])
|
||||
message = process_newlines(message) if opts[:process_newlines]
|
||||
message
|
||||
if post.respond_to? :message
|
||||
post.message.plain_text_without_markdown truncate: opts.fetch(:length, 200)
|
||||
else
|
||||
I18n.translate 'notifier.a_post_you_shared'
|
||||
end
|
||||
end
|
||||
|
||||
# @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.
|
||||
def comment_message(comment, opts={})
|
||||
opts[:length] ||= 600
|
||||
text = strip_markdown(comment.text)
|
||||
text = truncate(text, :length => opts[:length])
|
||||
text = process_newlines(text) if opts[:process_newlines]
|
||||
text
|
||||
comment.message.plain_text_without_markdown truncate: opts.fetch(:length, 600)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,19 +9,8 @@ module PostsHelper
|
|||
elsif post.is_a?(Reshare)
|
||||
I18n.t "posts.show.reshare_by", :author => post.author_name
|
||||
else
|
||||
if post.text.present?
|
||||
if opts.has_key?(:length)
|
||||
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
|
||||
if post.message.present?
|
||||
post.message.title opts
|
||||
elsif post.respond_to?(:photos) && post.photos.present?
|
||||
I18n.t "posts.show.photos_by", :count => post.photos.size, :author => post.author_name
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
module NotificationMailers
|
||||
class AlsoCommented < NotificationMailers::Base
|
||||
include ActionView::Helpers::TextHelper
|
||||
include MarkdownifyHelper
|
||||
|
||||
attr_accessor :comment
|
||||
delegate :post, to: :comment, prefix: true
|
||||
|
||||
|
|
@ -11,8 +8,7 @@ module NotificationMailers
|
|||
|
||||
if mail?
|
||||
@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: #{@headers[:subject]}"
|
||||
@headers[:subject] = "Re: #{@comment.comment_email_subject}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
module NotificationMailers
|
||||
TRUNCATION_LEN = 70
|
||||
|
||||
class Base
|
||||
attr_accessor :recipient, :sender
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
module NotificationMailers
|
||||
class CommentOnPost < NotificationMailers::Base
|
||||
include ActionView::Helpers::TextHelper
|
||||
include MarkdownifyHelper
|
||||
|
||||
attr_accessor :comment
|
||||
|
||||
def set_headers(comment_id)
|
||||
@comment = Comment.find(comment_id)
|
||||
|
||||
@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: #{@headers[:subject]}"
|
||||
@headers[:subject] = "Re: #{@comment.comment_email_subject}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
class Notifier < ActionMailer::Base
|
||||
helper :application
|
||||
helper :markdownify
|
||||
helper :notifier
|
||||
helper :people
|
||||
|
||||
|
||||
def self.admin(string, recipients, opts = {})
|
||||
mails = []
|
||||
recipients.each do |rec|
|
||||
|
|
@ -16,7 +15,7 @@ class Notifier < ActionMailer::Base
|
|||
def single_admin(string, recipient, opts={})
|
||||
@receiver = recipient
|
||||
@string = string.html_safe
|
||||
|
||||
|
||||
if attach = opts.delete(:attachments)
|
||||
attach.each{ |f|
|
||||
attachments[f[:name]] = f[:file]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
class Comment < ActiveRecord::Base
|
||||
|
||||
include Diaspora::Federated::Base
|
||||
|
||||
|
||||
include Diaspora::Guid
|
||||
include Diaspora::Relayable
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ class Comment < ActiveRecord::Base
|
|||
belongs_to :commentable, :touch => true, :polymorphic => true
|
||||
alias_attribute :post, :commentable
|
||||
belongs_to :author, :class_name => 'Person'
|
||||
|
||||
|
||||
delegate :name, to: :author, prefix: true
|
||||
delegate :comment_email_subject, to: :parent
|
||||
delegate :author_name, to: :parent, prefix: true
|
||||
|
|
@ -79,6 +79,10 @@ class Comment < ActiveRecord::Base
|
|||
self.post = parent
|
||||
end
|
||||
|
||||
def message
|
||||
@message ||= Diaspora::MessageRenderer.new text
|
||||
end
|
||||
|
||||
def text= text
|
||||
self[:text] = text.to_s.strip #to_s if for nil, for whatever reason
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ class Message < ActiveRecord::Base
|
|||
validate :participant_of_parent_conversation
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
def formatted_message(opts={})
|
||||
opts[:plain_text] ? self.text: ERB::Util.h(self.text)
|
||||
def message
|
||||
@message ||= Diaspora::MessageRenderer.new text
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class Profile < ActiveRecord::Base
|
|||
def receive(user, person)
|
||||
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')
|
||||
person.profile.update_attributes(profiles_attr)
|
||||
person.profile.update_attributes(profiles_attr)
|
||||
|
||||
person.profile
|
||||
end
|
||||
|
|
@ -78,13 +78,13 @@ class Profile < ActiveRecord::Base
|
|||
|
||||
def from_omniauth_hash(omniauth_user_hash)
|
||||
mappings = {"description" => "bio",
|
||||
'image' => 'image_url',
|
||||
'name' => 'first_name',
|
||||
'image' => 'image_url',
|
||||
'name' => 'first_name',
|
||||
'location' => 'location',
|
||||
}
|
||||
|
||||
update_hash = Hash[ omniauth_user_hash.map {|k, v| [mappings[k], v] } ]
|
||||
|
||||
|
||||
self.attributes.merge(update_hash){|key, old, new| old.blank? ? new : old}
|
||||
end
|
||||
|
||||
|
|
@ -132,6 +132,13 @@ class Profile < ActiveRecord::Base
|
|||
birthday.to_s(:long).gsub(', 1000', '') if birthday.present?
|
||||
end
|
||||
|
||||
def bio_message
|
||||
@bio_message ||= Diaspora::MessageRenderer.new(bio)
|
||||
end
|
||||
|
||||
def location_message
|
||||
@location_message ||= Diaspora::MessageRenderer.new(location)
|
||||
end
|
||||
|
||||
def tag_string
|
||||
if @tag_string
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ class Reshare < Post
|
|||
self.root ? root.raw_message : super
|
||||
end
|
||||
|
||||
def message
|
||||
absolute_root.message if root.present?
|
||||
end
|
||||
|
||||
def mentioned_people
|
||||
self.root ? root.mentioned_people : super
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,11 +3,8 @@
|
|||
# the COPYRIGHT file.
|
||||
|
||||
class Service < ActiveRecord::Base
|
||||
include ActionView::Helpers::TextHelper
|
||||
include MarkdownifyHelper
|
||||
|
||||
attr_accessor :provider, :info, :access_level
|
||||
|
||||
|
||||
belongs_to :user
|
||||
validates_uniqueness_of :uid, :scope => :type
|
||||
|
||||
|
|
@ -26,12 +23,12 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def first_from_omniauth( auth_hash )
|
||||
@@auth = auth_hash
|
||||
@@auth = auth_hash
|
||||
where( type: service_type, uid: options[:uid] ).first
|
||||
end
|
||||
|
||||
def initialize_from_omniauth( auth_hash )
|
||||
@@auth = auth_hash
|
||||
@@auth = auth_hash
|
||||
service_type.constantize.new( options )
|
||||
end
|
||||
|
||||
|
|
@ -44,7 +41,7 @@ class Service < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def options
|
||||
{
|
||||
{
|
||||
nickname: auth['info']['nickname'],
|
||||
access_token: auth['credentials']['token'],
|
||||
access_secret: auth['credentials']['secret'],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
class Services::Facebook < Service
|
||||
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]
|
||||
MAX_CHARACTERS = 63206
|
||||
MAX_CHARACTERS = 63206
|
||||
|
||||
def provider
|
||||
"facebook"
|
||||
|
|
@ -22,11 +21,16 @@ class Services::Facebook < Service
|
|||
end
|
||||
|
||||
def create_post_params(post)
|
||||
message = strip_markdown(post.text(:plain_text => true))
|
||||
message = post.message.plain_text_without_markdown
|
||||
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
|
||||
{: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
|
||||
|
||||
def profile_photo_url
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
class Services::Tumblr < Service
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
MAX_CHARACTERS = 1000
|
||||
|
||||
def provider
|
||||
|
|
@ -38,10 +35,10 @@ class Services::Tumblr < Service
|
|||
def tumblr_template(post, url)
|
||||
html = ''
|
||||
post.photos.each do |photo|
|
||||
html += "})\n\n"
|
||||
html << "})\n\n"
|
||||
end
|
||||
html += post.text
|
||||
html += "\n\n[original post](#{url})"
|
||||
html << post.message.html(mentioned_people: [])
|
||||
html << "\n\n[original post](#{url})"
|
||||
end
|
||||
|
||||
def delete_post(post)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
class Services::Twitter < Service
|
||||
include ActionView::Helpers::TextHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
include MarkdownifyHelper
|
||||
|
||||
MAX_CHARACTERS = 140
|
||||
SHORTENED_URL_LENGTH = 21
|
||||
|
|
@ -40,7 +38,7 @@ class Services::Twitter < Service
|
|||
|
||||
def attempt_post post, retry_count=0
|
||||
message = build_twitter_post post, retry_count
|
||||
tweet = client.update message
|
||||
client.update message
|
||||
rescue Twitter::Error::Forbidden => e
|
||||
if ! e.message.include? 'is over 140' || retry_count == 20
|
||||
raise e
|
||||
|
|
@ -52,7 +50,7 @@ class Services::Twitter < Service
|
|||
def build_twitter_post post, retry_count=0
|
||||
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
|
||||
end
|
||||
|
||||
|
|
@ -65,7 +63,7 @@ class Services::Twitter < Service
|
|||
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} #{post_url}"
|
||||
|
|
@ -95,11 +93,9 @@ class Services::Twitter < Service
|
|||
return truncated_text if truncated_text !~ /#{LINK_PATTERN}\Z/
|
||||
|
||||
url = post_text.match(LINK_PATTERN, truncated_text.rindex('http'))[0]
|
||||
truncated_text = truncate(
|
||||
post_text,
|
||||
length: max_characters - SHORTENED_URL_LENGTH + 2,
|
||||
separator: ' ',
|
||||
omission: ''
|
||||
truncated_text = post_text.truncate(
|
||||
max_characters - SHORTENED_URL_LENGTH + 2,
|
||||
separator: ' ', omission: ''
|
||||
)
|
||||
|
||||
"#{truncated_text} #{url} ..."
|
||||
|
|
|
|||
|
|
@ -1,32 +1,30 @@
|
|||
class Services::Wordpress < Service
|
||||
include ActionView::Helpers::TextHelper
|
||||
include MarkdownifyHelper
|
||||
|
||||
MAX_CHARACTERS = 1000
|
||||
|
||||
|
||||
attr_accessor :username, :password, :host, :path
|
||||
|
||||
|
||||
# uid = blog_id
|
||||
|
||||
|
||||
def provider
|
||||
"wordpress"
|
||||
end
|
||||
|
||||
def post(post, url='')
|
||||
res = Faraday.new(:url => "https://public-api.wordpress.com").post do |req|
|
||||
|
||||
def post post, url=''
|
||||
res = Faraday.new(url: "https://public-api.wordpress.com").post do |req|
|
||||
req.url "/rest/v1/sites/#{self.uid}/posts/new"
|
||||
req.body = post_body(post).to_json
|
||||
req.headers['Authorization'] = "Bearer #{self.access_token}"
|
||||
req.headers['Content-Type'] = 'application/json'
|
||||
end
|
||||
|
||||
JSON.parse res.env[:body]
|
||||
end
|
||||
|
||||
def post_body(post, url='')
|
||||
post_text = markdownify(post.text)
|
||||
post_title = truncate(strip_markdown(post.text(:plain_text => true)), :length => 40, :separator => ' ')
|
||||
|
||||
{:title => post_title, :content => post_text.html_safe}
|
||||
|
||||
def post_body post
|
||||
{
|
||||
title: post.message.title(length: 40),
|
||||
content: post.message.markdownified(disable_hovercards: true)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
class StatusMessage < Post
|
||||
include Diaspora::Taggable
|
||||
|
||||
include ActionView::Helpers::TextHelper
|
||||
include PeopleHelper
|
||||
|
||||
acts_as_taggable_on :tags
|
||||
|
|
@ -56,10 +55,6 @@ class StatusMessage < Post
|
|||
tag_stream(tag_ids)
|
||||
end
|
||||
|
||||
def text(opts = {})
|
||||
self.formatted_message(opts)
|
||||
end
|
||||
|
||||
def raw_message
|
||||
read_attribute(:text)
|
||||
end
|
||||
|
|
@ -77,12 +72,8 @@ class StatusMessage < Post
|
|||
self.raw_message.match(/#nsfw/i) || super
|
||||
end
|
||||
|
||||
def formatted_message(opts={})
|
||||
return self.raw_message unless self.raw_message
|
||||
|
||||
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))
|
||||
def message
|
||||
@message ||= Diaspora::MessageRenderer.new raw_message, mentioned_people: mentioned_people
|
||||
end
|
||||
|
||||
def mentioned_people
|
||||
|
|
@ -134,7 +125,7 @@ class StatusMessage < Post
|
|||
end
|
||||
|
||||
def comment_email_subject
|
||||
formatted_message(:plain_text => true)
|
||||
message.title length: 70
|
||||
end
|
||||
|
||||
def first_photo_url(*args)
|
||||
|
|
@ -142,7 +133,7 @@ class StatusMessage < Post
|
|||
end
|
||||
|
||||
def text_and_photos_blank?
|
||||
self.text.blank? && self.photos.blank?
|
||||
self.raw_message.blank? && self.photos.blank?
|
||||
end
|
||||
|
||||
def queue_gather_oembed_data
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
class OEmbedPresenter
|
||||
include PostsHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
def initialize(post, opts = {})
|
||||
@post = post
|
||||
|
|
@ -13,14 +12,14 @@ class OEmbedPresenter
|
|||
|
||||
def as_json(opts={})
|
||||
{
|
||||
:provider_name => "Diaspora",
|
||||
:provider_name => "Diaspora",
|
||||
:provider_url => AppConfig.pod_uri.to_s,
|
||||
:type => 'rich',
|
||||
:version => '1.0',
|
||||
:title => post_title,
|
||||
:author_name => post_author,
|
||||
:author_url => post_author_url,
|
||||
:width => @opts.fetch(:maxwidth, 516),
|
||||
:width => @opts.fetch(:maxwidth, 516),
|
||||
:height => @opts.fetch(:maxheight, 320),
|
||||
:html => iframe_html
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
class PostPresenter
|
||||
include PostsHelper
|
||||
include ActionView::Helpers::TextHelper
|
||||
|
||||
attr_accessor :post, :current_user
|
||||
|
||||
|
|
@ -47,7 +46,7 @@ class PostPresenter
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
def root
|
||||
|
|
|
|||
|
|
@ -16,4 +16,4 @@
|
|||
= timeago(comment.created_at ? comment.created_at : Time.now)
|
||||
|
||||
%div{:class => direction_for(comment.text)}
|
||||
= markdownify(comment)
|
||||
= comment.message.markdownified
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@
|
|||
= timeago(message.created_at)
|
||||
|
||||
%div{ :class => direction_for(message.text) }
|
||||
= markdownify(message, :oembed => true)
|
||||
= message.message.markdownified
|
||||
|
|
|
|||
|
|
@ -15,35 +15,35 @@
|
|||
.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'
|
||||
.white_bar
|
||||
|
||||
|
||||
- if @contact.mutual?
|
||||
|
||||
|
||||
|
||||
|
||||
.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'
|
||||
.white_bar
|
||||
|
||||
|
||||
.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?
|
||||
|
||||
|
||||
%br
|
||||
|
||||
%br
|
||||
|
||||
-if contact.sharing? || person == current_user.person
|
||||
%ul#profile_information
|
||||
|
||||
|
||||
- unless person.bio.blank?
|
||||
%li
|
||||
%h4
|
||||
=t('.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?
|
||||
%li
|
||||
%h4
|
||||
=t('.location')
|
||||
%div{ :class => direction_for(person.location) }
|
||||
= markdownify(person.location, :oembed => true, :newlines => true)
|
||||
= person.profile.location_message.markdownified
|
||||
|
||||
- unless person.gender.blank?
|
||||
%li
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
= image_tag(photo.url(:thumb_small))
|
||||
%br
|
||||
= link_to t('layouts.header.view_all'), person_photos_path(person)
|
||||
|
||||
|
||||
- if person == current_user.person
|
||||
%li.image_list
|
||||
%h4
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
= image_tag post.image_url
|
||||
|
||||
%div{:class => direction_for(post.text)}
|
||||
!= markdownify(post)
|
||||
!= post.message.markdownified
|
||||
- if post.o_embed_cache
|
||||
!= o_embed_html post.o_embed_cache
|
||||
-if post.open_graph_cache
|
||||
|
|
|
|||
|
|
@ -27,12 +27,11 @@ atom_feed({'xmlns:thr' => 'http://purl.org/syndication/thread/1.0',
|
|||
end
|
||||
|
||||
@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}",
|
||||
:id => "#{@user.url}p/#{post.id}" do |entry|
|
||||
|
||||
entry.title post_page_title(post)
|
||||
entry.content markdownify(post), :type => 'html'
|
||||
entry.title post.message.title
|
||||
entry.content post.message.markdownified(disable_hovercards: true), :type => 'html'
|
||||
entry.tag! 'activity:verb', 'http://activitystrea.ms/schema/1.0/post'
|
||||
entry.tag! 'activity:object-type', 'http://activitystrea.ms/schema/1.0/note'
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,9 +13,6 @@ require 'typhoeus'
|
|||
# Presenters
|
||||
require 'post_presenter'
|
||||
|
||||
# Helpers
|
||||
require 'markdownify_helper'
|
||||
|
||||
# Our libs
|
||||
require 'collect_user_photos'
|
||||
require 'diaspora'
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module Diaspora
|
|||
require 'diaspora/parser'
|
||||
require 'diaspora/fetcher'
|
||||
require 'diaspora/markdownify'
|
||||
require 'diaspora/message_renderer'
|
||||
require 'diaspora/mentionable'
|
||||
require 'diaspora/exporter'
|
||||
require 'diaspora/federated'
|
||||
|
|
|
|||
|
|
@ -2,12 +2,10 @@ module Diaspora
|
|||
module Markdownify
|
||||
class HTML < Redcarpet::Render::HTML
|
||||
include ActionView::Helpers::TextHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def autolink(link, type)
|
||||
auto_link(link, :link => :urls, :html => { :target => "_blank" })
|
||||
def autolink link, type
|
||||
auto_link(link, link: :urls, html: { target: "_blank" })
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
233
lib/diaspora/message_renderer.rb
Normal file
233
lib/diaspora/message_renderer.rb
Normal 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(/&(#[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
|
||||
|
|
@ -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('XSS is evil')</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
|
||||
|
|
@ -5,8 +5,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe NotifierHelper do
|
||||
include MarkdownifyHelper
|
||||
|
||||
describe '#post_message' do
|
||||
before do
|
||||
# post for truncate test
|
||||
|
|
|
|||
|
|
@ -12,30 +12,11 @@ describe PostsHelper do
|
|||
end
|
||||
|
||||
context 'with posts with text' do
|
||||
context 'when :length is passed in parameters' do
|
||||
it 'returns string of size less or equal to :length' do
|
||||
@sm = double(:text => "## My title\n Post content...")
|
||||
string_size = 12
|
||||
post_page_title(@sm, :length => string_size ).size.should <= 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
|
||||
@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
|
||||
it "delegates to message.title" do
|
||||
message = double
|
||||
message.should_receive(:title)
|
||||
post = double(message: message)
|
||||
post_page_title(post)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
175
spec/lib/diaspora/message_renderer_spec.rb
Normal file
175
spec/lib/diaspora/message_renderer_spec.rb
Normal 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 = '& ß ' ' "'
|
||||
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('XSS is evil')</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 = '& ß ' ' "'
|
||||
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
|
||||
|
|
@ -1,9 +1,6 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Notifier do
|
||||
include ActionView::Helpers::TextHelper
|
||||
include MarkdownifyHelper
|
||||
|
||||
let(:person) { FactoryGirl.create(:person) }
|
||||
|
||||
before do
|
||||
|
|
@ -120,7 +117,7 @@ describe Notifier do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
it 'BODY: contains the name of person liking' do
|
||||
|
|
@ -150,7 +147,7 @@ describe Notifier do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
it 'BODY: contains the name of person liking' do
|
||||
|
|
@ -224,7 +221,7 @@ describe Notifier do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
context 'BODY' do
|
||||
|
|
@ -265,7 +262,7 @@ describe Notifier do
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
context 'BODY' do
|
||||
|
|
|
|||
|
|
@ -25,17 +25,17 @@ describe Services::Facebook do
|
|||
end
|
||||
|
||||
it 'removes text formatting markdown from post text' do
|
||||
message = "Text with some **bolded** and _italic_ parts."
|
||||
post = double(:text => message, :photos => [])
|
||||
message = double
|
||||
message.should_receive(:plain_text_without_markdown).and_return("")
|
||||
post = double(message: message, photos: [])
|
||||
post_params = @service.create_post_params(post)
|
||||
post_params[:message].should match "Text with some bolded and italic parts."
|
||||
end
|
||||
|
||||
it 'does not add post link when no photos' do
|
||||
message = "Text with some **bolded** and _italic_ parts."
|
||||
post = double(:text => message, :photos => [])
|
||||
message = "Some text."
|
||||
post = double(message: double(plain_text_without_markdown: message), photos: [])
|
||||
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
|
||||
|
||||
it 'sets facebook id on post' do
|
||||
|
|
|
|||
|
|
@ -38,9 +38,10 @@ describe Services::Twitter do
|
|||
end
|
||||
|
||||
it 'removes text formatting markdown from post text' do
|
||||
message = "Text with some **bolded** and _italic_ parts."
|
||||
post = double(:text => message, :photos => [])
|
||||
@service.send(:build_twitter_post, post).should match "Text with some bolded and italic parts."
|
||||
message = double
|
||||
message.should_receive(:plain_text_without_markdown).and_return("")
|
||||
post = double(message: message, photos: [])
|
||||
@service.send(:build_twitter_post, post)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -53,19 +54,19 @@ describe Services::Twitter do
|
|||
|
||||
it "should not truncate a short message" do
|
||||
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
|
||||
end
|
||||
|
||||
it "should truncate a long message" do
|
||||
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
|
||||
end
|
||||
|
||||
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_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
|
||||
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
|
||||
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.should match /\.\.\./
|
||||
|
|
@ -83,7 +84,7 @@ describe Services::Twitter 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_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.should match /\.\.\./
|
||||
|
|
@ -99,7 +100,7 @@ describe Services::Twitter 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_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.should match /\.\.\./
|
||||
|
|
@ -107,7 +108,7 @@ describe Services::Twitter do
|
|||
|
||||
it "should not truncate a message of maximum length" do
|
||||
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.should match exact_size_message
|
||||
|
|
|
|||
|
|
@ -150,18 +150,6 @@ STR
|
|||
@sm = FactoryGirl.create(:status_message, :text => @test_string )
|
||||
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
|
||||
it 'creates a mention for everyone mentioned in the message' do
|
||||
Diaspora::Mentionable.should_receive(:people_from_string).and_return(@people)
|
||||
|
|
|
|||
|
|
@ -65,32 +65,17 @@ describe PostPresenter do
|
|||
|
||||
describe '#title' do
|
||||
context 'with posts with text' 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...")
|
||||
@presenter.post = @sm
|
||||
@presenter.title.should == "## My 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
|
||||
it "delegates to message.title" do
|
||||
message = double(present?: true)
|
||||
message.should_receive(:title)
|
||||
@presenter.post = double(message: message)
|
||||
@presenter.title
|
||||
end
|
||||
end
|
||||
|
||||
context 'with posts without text' 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.title.should == "A post from #{@sm.author.name}"
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue