2011-09-27 12:50:30 +08:00
# encoding: UTF-8
#
2011-02-01 09:57:29 +08:00
# By Henrik Nyh <http://henrik.nyh.se> 2008-01-30.
# Free to modify and redistribute with credit.
# modified by Dave Nolan <http://textgoeshere.org.uk> 2008-02-06
# Ellipsis appended to text of last HTML node
# Ellipsis inserted after final word break
# modified by Mark Dickson <mark@sitesteaders.com> 2008-12-18
# Option to truncate to last full word
# Option to include a 'more' link
# Check for nil last child
2012-01-21 01:47:43 +08:00
# Copied from http://pastie.textmate.org/342485,
2011-02-01 09:57:29 +08:00
# based on http://henrik.nyh.se/2008/01/rails-truncate-html-helper
module TextHelper
def force_zone ( time )
2011-06-09 08:39:01 +08:00
( time . in_time_zone ( @time_zone || Time . zone ) rescue nil ) || time
2011-02-01 09:57:29 +08:00
end
2012-01-07 07:58:18 +08:00
def self . date_string ( start_date , * args )
2011-02-01 09:57:29 +08:00
return nil unless start_date
start_date = start_date . in_time_zone . to_date rescue start_date . to_date
2012-01-07 07:58:18 +08:00
style = args . last . is_a? ( Symbol ) ? args . pop : :normal
end_date = args . pop
end_date = end_date . in_time_zone . to_date rescue end_date . to_date if end_date
if end_date . nil? || start_date == end_date
date_component ( start_date , style )
else
I18n . t ( 'time.ranges.different_days' , " %{start_date_and_time} to %{end_date_and_time} " ,
:start_date_and_time = > date_component ( start_date , style ) ,
:end_date_and_time = > date_component ( end_date , style )
)
end
end
2012-01-21 01:47:43 +08:00
def self . date_component ( start_date , style = :normal )
2011-12-20 03:09:20 +08:00
today = Time . zone . today
2011-02-01 09:57:29 +08:00
if style != :long
2011-05-07 02:44:34 +08:00
if style != :no_words
string = nil
return string if start_date == today && ( string = I18n . t ( 'date.days.today' , 'Today' ) ) && string . strip . present?
return string if start_date == today + 1 && ( string = I18n . t ( 'date.days.tomorrow' , 'Tomorrow' ) ) && string . strip . present?
return string if start_date == today - 1 && ( string = I18n . t ( 'date.days.yesterday' , 'Yesterday' ) ) && string . strip . present?
return string if start_date < today + 1 . week && start_date > = today && ( string = I18n . l ( start_date , :format = > :weekday ) rescue nil ) && string . strip . present?
end
return I18n . l ( start_date , :format = > :short ) if start_date . year == today . year || style == :short
2011-02-01 09:57:29 +08:00
end
2011-05-07 02:44:34 +08:00
return I18n . l ( start_date , :format = > :medium )
2011-02-01 09:57:29 +08:00
end
2012-01-07 07:58:18 +08:00
def date_string ( * args )
TextHelper . date_string ( * args )
2011-10-06 01:56:38 +08:00
end
2011-02-01 09:57:29 +08:00
2014-05-14 07:48:56 +08:00
def time_string ( start_time , end_time = nil , zone = nil )
zone || = :: Time . zone
start_time = start_time . in_time_zone ( zone ) rescue start_time
end_time = end_time . in_time_zone ( zone ) rescue end_time
2011-02-01 09:57:29 +08:00
return nil unless start_time
2011-05-07 02:44:34 +08:00
result = I18n . l ( start_time , :format = > start_time . min == 0 ? :tiny_on_the_hour : :tiny )
2011-02-01 09:57:29 +08:00
if end_time && end_time != start_time
2014-05-14 07:48:56 +08:00
result = I18n . t ( 'time.ranges.times' , " %{start_time} to %{end_time} " , :start_time = > result , :end_time = > time_string ( end_time , nil , zone ) )
2011-02-01 09:57:29 +08:00
end
result
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
def datetime_span ( * args )
string = datetime_string ( * args )
if string && ! string . empty? && args [ 0 ]
" <span class='zone_cached_datetime' title=' #{ args [ 0 ] . iso8601 rescue " " } '> #{ string } </span> "
else
nil
end
end
2014-05-14 07:48:56 +08:00
def datetime_string ( start_datetime , datetime_type = :event , end_datetime = nil , shorten_midnight = false , zone = nil )
zone || = :: Time . zone
start_datetime = start_datetime . in_time_zone ( zone ) rescue start_datetime
2011-02-01 09:57:29 +08:00
return nil unless start_datetime
2014-05-14 07:48:56 +08:00
end_datetime = end_datetime . in_time_zone ( zone ) rescue end_datetime
2011-02-01 09:57:29 +08:00
if ! datetime_type . is_a? ( Symbol )
datetime_type = :event
end_datetime = nil
end
2011-05-07 02:44:34 +08:00
end_datetime = nil if datetime_type == :due_date
2014-05-14 07:48:56 +08:00
def datetime_component ( date_string , time , type , zone )
2011-05-07 02:44:34 +08:00
if type == :due_date
2014-05-14 07:48:56 +08:00
I18n . t ( 'time.due_date' , " %{date} by %{time} " , :date = > date_string , :time = > time_string ( time , nil , zone ) )
2011-05-07 02:44:34 +08:00
else
2014-05-14 07:48:56 +08:00
I18n . t ( 'time.event' , " %{date} at %{time} " , :date = > date_string , :time = > time_string ( time , nil , zone ) )
2011-05-07 02:44:34 +08:00
end
2011-02-01 09:57:29 +08:00
end
2011-05-07 02:44:34 +08:00
start_date_string = date_string ( start_datetime , datetime_type == :verbose ? :long : :no_words )
2014-05-14 07:48:56 +08:00
start_string = datetime_component ( start_date_string , start_datetime , datetime_type , zone )
2011-05-07 02:44:34 +08:00
if ! end_datetime || end_datetime == start_datetime
if shorten_midnight && ( ( datetime_type == :due_date && start_datetime . hour == 23 && start_datetime . min == 59 ) || ( datetime_type == :event && start_datetime . hour == 0 && start_datetime . min == 0 ) )
start_date_string
else
start_string
end
2011-02-01 09:57:29 +08:00
else
2011-05-07 02:44:34 +08:00
if start_datetime . to_date == end_datetime . to_date
2014-05-14 07:48:56 +08:00
I18n . t ( 'time.ranges.same_day' , " %{date} from %{start_time} to %{end_time} " , :date = > start_date_string , :start_time = > time_string ( start_datetime , nil , zone ) , :end_time = > time_string ( end_datetime , nil , zone ) )
2011-02-01 09:57:29 +08:00
else
2011-05-07 02:44:34 +08:00
end_date_string = date_string ( end_datetime , datetime_type == :verbose ? :long : :no_words )
2014-05-14 07:48:56 +08:00
end_string = datetime_component ( end_date_string , end_datetime , datetime_type , zone )
2011-05-07 02:44:34 +08:00
I18n . t ( 'time.ranges.different_days' , " %{start_date_and_time} to %{end_date_and_time} " , :start_date_and_time = > start_string , :end_date_and_time = > end_string )
2011-02-01 09:57:29 +08:00
end
end
end
2014-04-17 05:19:20 +08:00
def unlocalized_datetime_string ( start_datetime , datetime_type = :event )
start_datetime = start_datetime . in_time_zone rescue start_datetime
return nil unless start_datetime
date_format = ( datetime_type == :verbose || start_datetime . year != Time . zone . today . year ) ? " %b %-d, %Y " : " %b %-d "
time_format = start_datetime . min == 0 ? " %l%P " : " %l:%M%P "
return start_datetime . strftime ( " #{ date_format } at #{ time_format } " )
end
2011-06-10 11:51:23 +08:00
def time_ago_in_words_with_ago ( time )
I18n . t ( '#time.with_ago' , '%{time} ago' , :time = > ( time_ago_in_words time rescue '' ) )
end
2011-06-17 06:12:13 +08:00
# more precise than distance_of_time_in_words, and takes a number of seconds,
# rather than two times. also assumes durations on the scale of hours or
# less, so doesn't bother with days, months, or years
def readable_duration ( seconds )
# keys stolen from ActionView::Helpers::DateHelper#distance_of_time_in_words
case seconds
when 0 ... 60
I18n . t ( 'datetime.distance_in_words.x_seconds' ,
{ :one = > " 1 second " , :other = > " %{count} seconds " } ,
:count = > seconds . round )
when 60 ... 3600
I18n . t ( 'datetime.distance_in_words.x_minutes' ,
{ :one = > " 1 minute " , :other = > " %{count} minutes " } ,
:count = > ( seconds / 60 . 0 ) . round )
else
I18n . t ( 'datetime.distance_in_words.about_x_hours' ,
{ :one = > " about 1 hour " , :other = > " about %{count} hours " } ,
:count = > ( seconds / 3600 . 0 ) . round )
end
end
2011-02-01 09:57:29 +08:00
def truncate_html ( input , options = { } )
doc = Nokogiri :: HTML ( input )
options [ :max_length ] || = 250
num_words = options [ :num_words ] || ( options [ :max_length ] / 5 ) || 30
2011-05-07 02:44:34 +08:00
truncate_string = options [ :ellipsis ] || I18n . t ( 'lib.text_helper.ellipsis' , '...' )
2011-02-01 09:57:29 +08:00
truncate_string += options [ :link ] if options [ :link ]
truncate_elem = Nokogiri :: HTML ( " <span> " + truncate_string + " </span> " ) . at ( " span " )
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
current = doc . children . first
count = 0
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
while true
# we found a text node
if current . is_a? ( Nokogiri :: XML :: Text )
count += current . text . split . length
# we reached our limit, let's get outta here!
break if count > num_words
previous = current
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
if current . children . length > 0
# this node has children, can't be a text node,
# lets descend and look for text nodes
current = current . children . first
elsif ! current . next . nil?
#this has no children, but has a sibling, let's check it out
current = current . next
2012-01-21 01:47:43 +08:00
else
2011-02-01 09:57:29 +08:00
# we are the last child, we need to ascend until we are
# either done or find a sibling to continue on to
n = current
while ! n . is_a? ( Nokogiri :: HTML :: Document ) and n . parent . next . nil?
n = n . parent
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
# we've reached the top and found no more text nodes, break
if n . is_a? ( Nokogiri :: HTML :: Document )
break ;
else
current = n . parent . next
end
end
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
if count > = num_words
unless count == num_words
new_content = current . text . split
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
# If we're here, the last text node we counted eclipsed the number of words
# that we want, so we need to cut down on words. The easiest way to think about
# this is that without this node we'd have fewer words than the limit, so all
# the previous words plus a limited number of words from this node are needed.
# We simply need to figure out how many words are needed and grab that many.
# Then we need to -subtract- an index, because the first word would be index zero.
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
# For example, given:
# <p>Testing this HTML truncater.</p><p>To see if its working.</p>
# Let's say I want 6 words. The correct returned string would be:
# <p>Testing this HTML truncater.</p><p>To see...</p>
# All the words in both paragraphs = 9
# The last paragraph is the one that breaks the limit. How many words would we
# have without it? 4. But we want up to 6, so we might as well get that many.
# 6 - 4 = 2, so we get 2 words from this node, but words #1-2 are indices #0-1, so
# we subtract 1. If this gives us -1, we want nothing from this node. So go back to
# the previous node instead.
index = num_words - ( count - new_content . length ) - 1
if index > = 0
new_content = new_content [ 0 .. index ]
current . add_previous_sibling ( truncate_elem )
new_node = Nokogiri :: XML :: Text . new ( new_content . join ( ' ' ) , doc )
truncate_elem . add_previous_sibling ( new_node )
2012-01-11 11:57:19 +08:00
current = truncate_elem
2011-02-01 09:57:29 +08:00
else
current = previous
# why would we do this next line? it just ends up xml escaping stuff
#current.content = current.content
current . add_next_sibling ( truncate_elem )
2012-01-11 11:57:19 +08:00
current = truncate_elem
2011-02-01 09:57:29 +08:00
end
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
# remove everything else
while ! current . is_a? ( Nokogiri :: HTML :: Document )
while ! current . next . nil?
current . next . remove
end
current = current . parent
end
end
2012-01-21 01:47:43 +08:00
2011-02-01 09:57:29 +08:00
# now we grab the html and not the text.
# we do first because nokogiri adds html and body tags
# which we don't want
res = doc . at_css ( 'body' ) . inner_html rescue nil
res || = doc . root . children . first . inner_html rescue " "
2011-02-01 12:54:52 +08:00
res && res . html_safe
2011-02-01 09:57:29 +08:00
end
2011-05-07 02:44:34 +08:00
2011-06-09 06:34:15 +08:00
def self . make_subject_reply_to ( subject )
blank_re = I18n . t ( '#subject_reply_to' , " Re: %{subject} " , :subject = > '' )
return subject if subject . starts_with? ( blank_re )
I18n . t ( '#subject_reply_to' , " Re: %{subject} " , :subject = > subject )
end
2011-05-07 02:44:34 +08:00
class MarkdownSafeBuffer < String ; end
# use this to flag interpolated parameters as markdown-safe (see
# mt below) so they get eval'ed rather than escaped, e.g.
# mt(:add_description, :example => markdown_safe('`1 + 1 = 2`'))
def markdown_safe ( string )
MarkdownSafeBuffer . new ( string )
end
def markdown_escape ( string )
return string if string . is_a? ( MarkdownSafeBuffer )
markdown_safe ( string . gsub ( / ([ \\ ` \ *_ \ { \ } \ [ \ ] \ ( \ ) \ # \ + \ - \ .!]) / , '\\\\\1' ) )
end
# use this rather than t() if the translation contains trusted markdown
def mt ( * args )
inlinify = :auto
if args . last . is_a? ( Hash )
options = args . last
inlinify = options . delete ( :inlinify ) if options . has_key? ( :inlinify )
options . each_pair do | key , value |
2011-06-17 03:55:59 +08:00
next unless value . is_a? ( String ) && ! value . is_a? ( MarkdownSafeBuffer ) && ! value . is_a? ( ActiveSupport :: SafeBuffer )
2011-06-16 06:46:20 +08:00
next if key == :wrapper
2011-05-07 02:44:34 +08:00
options [ key ] = markdown_escape ( value ) . gsub ( / \ s+ / , ' ' ) . strip
end
end
2011-06-16 06:46:20 +08:00
translated = t ( * args )
2012-12-07 14:28:37 +08:00
markdown ( translated , inlinify )
end
def markdown ( string , inlinify = :auto )
string = ERB :: Util . h ( string ) unless string . html_safe?
result = RDiscount . new ( string ) . to_html . strip
2011-06-22 01:29:04 +08:00
# Strip wrapping <p></p> if inlinify == :auto && they completely wrap the result && there are not multiple <p>'s
result . gsub! ( / < \/ ?p> / , '' ) if inlinify == :auto && result =~ / \ A<p>.*< \/ p> \ z /m && ! ( result =~ / .*<p>.*<p>.* /m )
2013-12-17 04:35:20 +08:00
result . strip . html_safe
2011-05-07 02:44:34 +08:00
end
2011-02-01 09:57:29 +08:00
end