##// END OF EJS Templates
Fixed: inline attached image should not match partial filename (#2683)....
Jean-Philippe Lang -
r2359:ff0c96011fc8
parent child
Show More
@@ -1,702 +1,701
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require 'coderay'
18 require 'coderay'
19 require 'coderay/helpers/file_type'
19 require 'coderay/helpers/file_type'
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include GravatarHelper::PublicMethods
25 include GravatarHelper::PublicMethods
26
26
27 extend Forwardable
27 extend Forwardable
28 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
28 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29
29
30 def current_role
30 def current_role
31 @current_role ||= User.current.role_for_project(@project)
31 @current_role ||= User.current.role_for_project(@project)
32 end
32 end
33
33
34 # Return true if user is authorized for controller/action, otherwise false
34 # Return true if user is authorized for controller/action, otherwise false
35 def authorize_for(controller, action)
35 def authorize_for(controller, action)
36 User.current.allowed_to?({:controller => controller, :action => action}, @project)
36 User.current.allowed_to?({:controller => controller, :action => action}, @project)
37 end
37 end
38
38
39 # Display a link if user is authorized
39 # Display a link if user is authorized
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
40 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
41 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
42 end
42 end
43
43
44 # Display a link to remote if user is authorized
44 # Display a link to remote if user is authorized
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
45 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
46 url = options[:url] || {}
46 url = options[:url] || {}
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
47 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
48 end
48 end
49
49
50 # Display a link to user's account page
50 # Display a link to user's account page
51 def link_to_user(user, options={})
51 def link_to_user(user, options={})
52 (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
52 (user && !user.anonymous?) ? link_to(user.name(options[:format]), :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
53 end
53 end
54
54
55 def link_to_issue(issue, options={})
55 def link_to_issue(issue, options={})
56 options[:class] ||= ''
56 options[:class] ||= ''
57 options[:class] << ' issue'
57 options[:class] << ' issue'
58 options[:class] << ' closed' if issue.closed?
58 options[:class] << ' closed' if issue.closed?
59 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
59 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
60 end
60 end
61
61
62 # Generates a link to an attachment.
62 # Generates a link to an attachment.
63 # Options:
63 # Options:
64 # * :text - Link text (default to attachment filename)
64 # * :text - Link text (default to attachment filename)
65 # * :download - Force download (default: false)
65 # * :download - Force download (default: false)
66 def link_to_attachment(attachment, options={})
66 def link_to_attachment(attachment, options={})
67 text = options.delete(:text) || attachment.filename
67 text = options.delete(:text) || attachment.filename
68 action = options.delete(:download) ? 'download' : 'show'
68 action = options.delete(:download) ? 'download' : 'show'
69
69
70 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
70 link_to(h(text), {:controller => 'attachments', :action => action, :id => attachment, :filename => attachment.filename }, options)
71 end
71 end
72
72
73 def toggle_link(name, id, options={})
73 def toggle_link(name, id, options={})
74 onclick = "Element.toggle('#{id}'); "
74 onclick = "Element.toggle('#{id}'); "
75 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
75 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
76 onclick << "return false;"
76 onclick << "return false;"
77 link_to(name, "#", :onclick => onclick)
77 link_to(name, "#", :onclick => onclick)
78 end
78 end
79
79
80 def image_to_function(name, function, html_options = {})
80 def image_to_function(name, function, html_options = {})
81 html_options.symbolize_keys!
81 html_options.symbolize_keys!
82 tag(:input, html_options.merge({
82 tag(:input, html_options.merge({
83 :type => "image", :src => image_path(name),
83 :type => "image", :src => image_path(name),
84 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
84 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
85 }))
85 }))
86 end
86 end
87
87
88 def prompt_to_remote(name, text, param, url, html_options = {})
88 def prompt_to_remote(name, text, param, url, html_options = {})
89 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
89 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
90 link_to name, {}, html_options
90 link_to name, {}, html_options
91 end
91 end
92
92
93 def format_date(date)
93 def format_date(date)
94 return nil unless date
94 return nil unless date
95 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
95 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 date.strftime(@date_format)
97 date.strftime(@date_format)
98 end
98 end
99
99
100 def format_time(time, include_date = true)
100 def format_time(time, include_date = true)
101 return nil unless time
101 return nil unless time
102 time = time.to_time if time.is_a?(String)
102 time = time.to_time if time.is_a?(String)
103 zone = User.current.time_zone
103 zone = User.current.time_zone
104 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
104 local = zone ? time.in_time_zone(zone) : (time.utc? ? time.localtime : time)
105 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
105 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
106 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
106 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
107 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
107 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
108 end
108 end
109
109
110 def format_activity_title(text)
110 def format_activity_title(text)
111 h(truncate_single_line(text, 100))
111 h(truncate_single_line(text, 100))
112 end
112 end
113
113
114 def format_activity_day(date)
114 def format_activity_day(date)
115 date == Date.today ? l(:label_today).titleize : format_date(date)
115 date == Date.today ? l(:label_today).titleize : format_date(date)
116 end
116 end
117
117
118 def format_activity_description(text)
118 def format_activity_description(text)
119 h(truncate(text.to_s, 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
119 h(truncate(text.to_s, 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
120 end
120 end
121
121
122 def distance_of_date_in_words(from_date, to_date = 0)
122 def distance_of_date_in_words(from_date, to_date = 0)
123 from_date = from_date.to_date if from_date.respond_to?(:to_date)
123 from_date = from_date.to_date if from_date.respond_to?(:to_date)
124 to_date = to_date.to_date if to_date.respond_to?(:to_date)
124 to_date = to_date.to_date if to_date.respond_to?(:to_date)
125 distance_in_days = (to_date - from_date).abs
125 distance_in_days = (to_date - from_date).abs
126 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
126 lwr(:actionview_datehelper_time_in_words_day, distance_in_days)
127 end
127 end
128
128
129 def due_date_distance_in_words(date)
129 def due_date_distance_in_words(date)
130 if date
130 if date
131 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
131 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
132 end
132 end
133 end
133 end
134
134
135 def render_page_hierarchy(pages, node=nil)
135 def render_page_hierarchy(pages, node=nil)
136 content = ''
136 content = ''
137 if pages[node]
137 if pages[node]
138 content << "<ul class=\"pages-hierarchy\">\n"
138 content << "<ul class=\"pages-hierarchy\">\n"
139 pages[node].each do |page|
139 pages[node].each do |page|
140 content << "<li>"
140 content << "<li>"
141 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
141 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
142 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
142 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
143 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
143 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
144 content << "</li>\n"
144 content << "</li>\n"
145 end
145 end
146 content << "</ul>\n"
146 content << "</ul>\n"
147 end
147 end
148 content
148 content
149 end
149 end
150
150
151 # Renders flash messages
151 # Renders flash messages
152 def render_flash_messages
152 def render_flash_messages
153 s = ''
153 s = ''
154 flash.each do |k,v|
154 flash.each do |k,v|
155 s << content_tag('div', v, :class => "flash #{k}")
155 s << content_tag('div', v, :class => "flash #{k}")
156 end
156 end
157 s
157 s
158 end
158 end
159
159
160 # Renders the project quick-jump box
160 # Renders the project quick-jump box
161 def render_project_jump_box
161 def render_project_jump_box
162 # Retrieve them now to avoid a COUNT query
162 # Retrieve them now to avoid a COUNT query
163 projects = User.current.projects.all
163 projects = User.current.projects.all
164 if projects.any?
164 if projects.any?
165 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
165 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
166 "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
166 "<option selected='selected'>#{ l(:label_jump_to_a_project) }</option>" +
167 '<option disabled="disabled">---</option>'
167 '<option disabled="disabled">---</option>'
168 s << project_tree_options_for_select(projects) do |p|
168 s << project_tree_options_for_select(projects) do |p|
169 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
169 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
170 end
170 end
171 s << '</select>'
171 s << '</select>'
172 s
172 s
173 end
173 end
174 end
174 end
175
175
176 def project_tree_options_for_select(projects, options = {})
176 def project_tree_options_for_select(projects, options = {})
177 s = ''
177 s = ''
178 project_tree(projects) do |project, level|
178 project_tree(projects) do |project, level|
179 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
179 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
180 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
180 tag_options = {:value => project.id, :selected => ((project == options[:selected]) ? 'selected' : nil)}
181 tag_options.merge!(yield(project)) if block_given?
181 tag_options.merge!(yield(project)) if block_given?
182 s << content_tag('option', name_prefix + h(project), tag_options)
182 s << content_tag('option', name_prefix + h(project), tag_options)
183 end
183 end
184 s
184 s
185 end
185 end
186
186
187 # Yields the given block for each project with its level in the tree
187 # Yields the given block for each project with its level in the tree
188 def project_tree(projects, &block)
188 def project_tree(projects, &block)
189 ancestors = []
189 ancestors = []
190 projects.sort_by(&:lft).each do |project|
190 projects.sort_by(&:lft).each do |project|
191 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
191 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
192 ancestors.pop
192 ancestors.pop
193 end
193 end
194 yield project, ancestors.size
194 yield project, ancestors.size
195 ancestors << project
195 ancestors << project
196 end
196 end
197 end
197 end
198
198
199 def project_nested_ul(projects, &block)
199 def project_nested_ul(projects, &block)
200 s = ''
200 s = ''
201 if projects.any?
201 if projects.any?
202 ancestors = []
202 ancestors = []
203 projects.sort_by(&:lft).each do |project|
203 projects.sort_by(&:lft).each do |project|
204 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
204 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
205 s << "<ul>\n"
205 s << "<ul>\n"
206 else
206 else
207 ancestors.pop
207 ancestors.pop
208 s << "</li>"
208 s << "</li>"
209 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
209 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
210 ancestors.pop
210 ancestors.pop
211 s << "</ul></li>\n"
211 s << "</ul></li>\n"
212 end
212 end
213 end
213 end
214 s << "<li>"
214 s << "<li>"
215 s << yield(project).to_s
215 s << yield(project).to_s
216 ancestors << project
216 ancestors << project
217 end
217 end
218 s << ("</li></ul>\n" * ancestors.size)
218 s << ("</li></ul>\n" * ancestors.size)
219 end
219 end
220 s
220 s
221 end
221 end
222
222
223 # Truncates and returns the string as a single line
223 # Truncates and returns the string as a single line
224 def truncate_single_line(string, *args)
224 def truncate_single_line(string, *args)
225 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
225 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
226 end
226 end
227
227
228 def html_hours(text)
228 def html_hours(text)
229 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
229 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
230 end
230 end
231
231
232 def authoring(created, author, options={})
232 def authoring(created, author, options={})
233 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
233 time_tag = @project.nil? ? content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created)) :
234 link_to(distance_of_time_in_words(Time.now, created),
234 link_to(distance_of_time_in_words(Time.now, created),
235 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
235 {:controller => 'projects', :action => 'activity', :id => @project, :from => created.to_date},
236 :title => format_time(created))
236 :title => format_time(created))
237 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
237 author_tag = (author.is_a?(User) && !author.anonymous?) ? link_to(h(author), :controller => 'account', :action => 'show', :id => author) : h(author || 'Anonymous')
238 l(options[:label] || :label_added_time_by, author_tag, time_tag)
238 l(options[:label] || :label_added_time_by, author_tag, time_tag)
239 end
239 end
240
240
241 def l_or_humanize(s, options={})
241 def l_or_humanize(s, options={})
242 k = "#{options[:prefix]}#{s}".to_sym
242 k = "#{options[:prefix]}#{s}".to_sym
243 l_has_string?(k) ? l(k) : s.to_s.humanize
243 l_has_string?(k) ? l(k) : s.to_s.humanize
244 end
244 end
245
245
246 def day_name(day)
246 def day_name(day)
247 l(:general_day_names).split(',')[day-1]
247 l(:general_day_names).split(',')[day-1]
248 end
248 end
249
249
250 def month_name(month)
250 def month_name(month)
251 l(:actionview_datehelper_select_month_names).split(',')[month-1]
251 l(:actionview_datehelper_select_month_names).split(',')[month-1]
252 end
252 end
253
253
254 def syntax_highlight(name, content)
254 def syntax_highlight(name, content)
255 type = CodeRay::FileType[name]
255 type = CodeRay::FileType[name]
256 type ? CodeRay.scan(content, type).html : h(content)
256 type ? CodeRay.scan(content, type).html : h(content)
257 end
257 end
258
258
259 def to_path_param(path)
259 def to_path_param(path)
260 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
260 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
261 end
261 end
262
262
263 def pagination_links_full(paginator, count=nil, options={})
263 def pagination_links_full(paginator, count=nil, options={})
264 page_param = options.delete(:page_param) || :page
264 page_param = options.delete(:page_param) || :page
265 url_param = params.dup
265 url_param = params.dup
266 # don't reuse params if filters are present
266 # don't reuse params if filters are present
267 url_param.clear if url_param.has_key?(:set_filter)
267 url_param.clear if url_param.has_key?(:set_filter)
268
268
269 html = ''
269 html = ''
270 if paginator.current.previous
270 if paginator.current.previous
271 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
271 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
272 end
272 end
273
273
274 html << (pagination_links_each(paginator, options) do |n|
274 html << (pagination_links_each(paginator, options) do |n|
275 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
275 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
276 end || '')
276 end || '')
277
277
278 if paginator.current.next
278 if paginator.current.next
279 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
279 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
280 end
280 end
281
281
282 unless count.nil?
282 unless count.nil?
283 html << [
283 html << [
284 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
284 " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})",
285 per_page_links(paginator.items_per_page)
285 per_page_links(paginator.items_per_page)
286 ].compact.join(' | ')
286 ].compact.join(' | ')
287 end
287 end
288
288
289 html
289 html
290 end
290 end
291
291
292 def per_page_links(selected=nil)
292 def per_page_links(selected=nil)
293 url_param = params.dup
293 url_param = params.dup
294 url_param.clear if url_param.has_key?(:set_filter)
294 url_param.clear if url_param.has_key?(:set_filter)
295
295
296 links = Setting.per_page_options_array.collect do |n|
296 links = Setting.per_page_options_array.collect do |n|
297 n == selected ? n : link_to_remote(n, {:update => "content",
297 n == selected ? n : link_to_remote(n, {:update => "content",
298 :url => params.dup.merge(:per_page => n),
298 :url => params.dup.merge(:per_page => n),
299 :method => :get},
299 :method => :get},
300 {:href => url_for(url_param.merge(:per_page => n))})
300 {:href => url_for(url_param.merge(:per_page => n))})
301 end
301 end
302 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
302 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
303 end
303 end
304
304
305 def breadcrumb(*args)
305 def breadcrumb(*args)
306 elements = args.flatten
306 elements = args.flatten
307 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
307 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
308 end
308 end
309
309
310 def other_formats_links(&block)
310 def other_formats_links(&block)
311 concat('<p class="other-formats">' + l(:label_export_to), block.binding)
311 concat('<p class="other-formats">' + l(:label_export_to), block.binding)
312 yield Redmine::Views::OtherFormatsBuilder.new(self)
312 yield Redmine::Views::OtherFormatsBuilder.new(self)
313 concat('</p>', block.binding)
313 concat('</p>', block.binding)
314 end
314 end
315
315
316 def html_title(*args)
316 def html_title(*args)
317 if args.empty?
317 if args.empty?
318 title = []
318 title = []
319 title << @project.name if @project
319 title << @project.name if @project
320 title += @html_title if @html_title
320 title += @html_title if @html_title
321 title << Setting.app_title
321 title << Setting.app_title
322 title.compact.join(' - ')
322 title.compact.join(' - ')
323 else
323 else
324 @html_title ||= []
324 @html_title ||= []
325 @html_title += args
325 @html_title += args
326 end
326 end
327 end
327 end
328
328
329 def accesskey(s)
329 def accesskey(s)
330 Redmine::AccessKeys.key_for s
330 Redmine::AccessKeys.key_for s
331 end
331 end
332
332
333 # Formats text according to system settings.
333 # Formats text according to system settings.
334 # 2 ways to call this method:
334 # 2 ways to call this method:
335 # * with a String: textilizable(text, options)
335 # * with a String: textilizable(text, options)
336 # * with an object and one of its attribute: textilizable(issue, :description, options)
336 # * with an object and one of its attribute: textilizable(issue, :description, options)
337 def textilizable(*args)
337 def textilizable(*args)
338 options = args.last.is_a?(Hash) ? args.pop : {}
338 options = args.last.is_a?(Hash) ? args.pop : {}
339 case args.size
339 case args.size
340 when 1
340 when 1
341 obj = options[:object]
341 obj = options[:object]
342 text = args.shift
342 text = args.shift
343 when 2
343 when 2
344 obj = args.shift
344 obj = args.shift
345 text = obj.send(args.shift).to_s
345 text = obj.send(args.shift).to_s
346 else
346 else
347 raise ArgumentError, 'invalid arguments to textilizable'
347 raise ArgumentError, 'invalid arguments to textilizable'
348 end
348 end
349 return '' if text.blank?
349 return '' if text.blank?
350
350
351 only_path = options.delete(:only_path) == false ? false : true
351 only_path = options.delete(:only_path) == false ? false : true
352
352
353 # when using an image link, try to use an attachment, if possible
353 # when using an image link, try to use an attachment, if possible
354 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
354 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
355
355
356 if attachments
356 if attachments
357 attachments = attachments.sort_by(&:created_on).reverse
357 attachments = attachments.sort_by(&:created_on).reverse
358 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
358 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(bmp|gif|jpg|jpeg|png))!/i) do |m|
359 style = $1
359 style = $1
360 filename = $6
360 filename = $6.downcase
361 rf = Regexp.new(Regexp.escape(filename), Regexp::IGNORECASE)
362 # search for the picture in attachments
361 # search for the picture in attachments
363 if found = attachments.detect { |att| att.filename =~ rf }
362 if found = attachments.detect { |att| att.filename.downcase == filename }
364 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
363 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
365 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
364 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
366 alt = desc.blank? ? nil : "(#{desc})"
365 alt = desc.blank? ? nil : "(#{desc})"
367 "!#{style}#{image_url}#{alt}!"
366 "!#{style}#{image_url}#{alt}!"
368 else
367 else
369 "!#{style}#{filename}!"
368 m
370 end
369 end
371 end
370 end
372 end
371 end
373
372
374 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
373 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text) { |macro, args| exec_macro(macro, obj, args) }
375
374
376 # different methods for formatting wiki links
375 # different methods for formatting wiki links
377 case options[:wiki_links]
376 case options[:wiki_links]
378 when :local
377 when :local
379 # used for local links to html files
378 # used for local links to html files
380 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
379 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
381 when :anchor
380 when :anchor
382 # used for single-file wiki export
381 # used for single-file wiki export
383 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
382 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
384 else
383 else
385 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
384 format_wiki_link = Proc.new {|project, title, anchor| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title, :anchor => anchor) }
386 end
385 end
387
386
388 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
387 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
389
388
390 # Wiki links
389 # Wiki links
391 #
390 #
392 # Examples:
391 # Examples:
393 # [[mypage]]
392 # [[mypage]]
394 # [[mypage|mytext]]
393 # [[mypage|mytext]]
395 # wiki links can refer other project wikis, using project name or identifier:
394 # wiki links can refer other project wikis, using project name or identifier:
396 # [[project:]] -> wiki starting page
395 # [[project:]] -> wiki starting page
397 # [[project:|mytext]]
396 # [[project:|mytext]]
398 # [[project:mypage]]
397 # [[project:mypage]]
399 # [[project:mypage|mytext]]
398 # [[project:mypage|mytext]]
400 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
399 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
401 link_project = project
400 link_project = project
402 esc, all, page, title = $1, $2, $3, $5
401 esc, all, page, title = $1, $2, $3, $5
403 if esc.nil?
402 if esc.nil?
404 if page =~ /^([^\:]+)\:(.*)$/
403 if page =~ /^([^\:]+)\:(.*)$/
405 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
404 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
406 page = $2
405 page = $2
407 title ||= $1 if page.blank?
406 title ||= $1 if page.blank?
408 end
407 end
409
408
410 if link_project && link_project.wiki
409 if link_project && link_project.wiki
411 # extract anchor
410 # extract anchor
412 anchor = nil
411 anchor = nil
413 if page =~ /^(.+?)\#(.+)$/
412 if page =~ /^(.+?)\#(.+)$/
414 page, anchor = $1, $2
413 page, anchor = $1, $2
415 end
414 end
416 # check if page exists
415 # check if page exists
417 wiki_page = link_project.wiki.find_page(page)
416 wiki_page = link_project.wiki.find_page(page)
418 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
417 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
419 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
418 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
420 else
419 else
421 # project or wiki doesn't exist
420 # project or wiki doesn't exist
422 title || page
421 title || page
423 end
422 end
424 else
423 else
425 all
424 all
426 end
425 end
427 end
426 end
428
427
429 # Redmine links
428 # Redmine links
430 #
429 #
431 # Examples:
430 # Examples:
432 # Issues:
431 # Issues:
433 # #52 -> Link to issue #52
432 # #52 -> Link to issue #52
434 # Changesets:
433 # Changesets:
435 # r52 -> Link to revision 52
434 # r52 -> Link to revision 52
436 # commit:a85130f -> Link to scmid starting with a85130f
435 # commit:a85130f -> Link to scmid starting with a85130f
437 # Documents:
436 # Documents:
438 # document#17 -> Link to document with id 17
437 # document#17 -> Link to document with id 17
439 # document:Greetings -> Link to the document with title "Greetings"
438 # document:Greetings -> Link to the document with title "Greetings"
440 # document:"Some document" -> Link to the document with title "Some document"
439 # document:"Some document" -> Link to the document with title "Some document"
441 # Versions:
440 # Versions:
442 # version#3 -> Link to version with id 3
441 # version#3 -> Link to version with id 3
443 # version:1.0.0 -> Link to version named "1.0.0"
442 # version:1.0.0 -> Link to version named "1.0.0"
444 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
443 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
445 # Attachments:
444 # Attachments:
446 # attachment:file.zip -> Link to the attachment of the current object named file.zip
445 # attachment:file.zip -> Link to the attachment of the current object named file.zip
447 # Source files:
446 # Source files:
448 # source:some/file -> Link to the file located at /some/file in the project's repository
447 # source:some/file -> Link to the file located at /some/file in the project's repository
449 # source:some/file@52 -> Link to the file's revision 52
448 # source:some/file@52 -> Link to the file's revision 52
450 # source:some/file#L120 -> Link to line 120 of the file
449 # source:some/file#L120 -> Link to line 120 of the file
451 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
450 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
452 # export:some/file -> Force the download of the file
451 # export:some/file -> Force the download of the file
453 # Forum messages:
452 # Forum messages:
454 # message#1218 -> Link to message with id 1218
453 # message#1218 -> Link to message with id 1218
455 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
454 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export|message)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
456 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
455 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
457 link = nil
456 link = nil
458 if esc.nil?
457 if esc.nil?
459 if prefix.nil? && sep == 'r'
458 if prefix.nil? && sep == 'r'
460 if project && (changeset = project.changesets.find_by_revision(oid))
459 if project && (changeset = project.changesets.find_by_revision(oid))
461 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
460 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
462 :class => 'changeset',
461 :class => 'changeset',
463 :title => truncate_single_line(changeset.comments, 100))
462 :title => truncate_single_line(changeset.comments, 100))
464 end
463 end
465 elsif sep == '#'
464 elsif sep == '#'
466 oid = oid.to_i
465 oid = oid.to_i
467 case prefix
466 case prefix
468 when nil
467 when nil
469 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
468 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
470 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
469 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
471 :class => (issue.closed? ? 'issue closed' : 'issue'),
470 :class => (issue.closed? ? 'issue closed' : 'issue'),
472 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
471 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
473 link = content_tag('del', link) if issue.closed?
472 link = content_tag('del', link) if issue.closed?
474 end
473 end
475 when 'document'
474 when 'document'
476 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
475 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
477 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
476 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
478 :class => 'document'
477 :class => 'document'
479 end
478 end
480 when 'version'
479 when 'version'
481 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
480 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
482 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
481 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
483 :class => 'version'
482 :class => 'version'
484 end
483 end
485 when 'message'
484 when 'message'
486 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
485 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
487 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
486 link = link_to h(truncate(message.subject, 60)), {:only_path => only_path,
488 :controller => 'messages',
487 :controller => 'messages',
489 :action => 'show',
488 :action => 'show',
490 :board_id => message.board,
489 :board_id => message.board,
491 :id => message.root,
490 :id => message.root,
492 :anchor => (message.parent ? "message-#{message.id}" : nil)},
491 :anchor => (message.parent ? "message-#{message.id}" : nil)},
493 :class => 'message'
492 :class => 'message'
494 end
493 end
495 end
494 end
496 elsif sep == ':'
495 elsif sep == ':'
497 # removes the double quotes if any
496 # removes the double quotes if any
498 name = oid.gsub(%r{^"(.*)"$}, "\\1")
497 name = oid.gsub(%r{^"(.*)"$}, "\\1")
499 case prefix
498 case prefix
500 when 'document'
499 when 'document'
501 if project && document = project.documents.find_by_title(name)
500 if project && document = project.documents.find_by_title(name)
502 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
501 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
503 :class => 'document'
502 :class => 'document'
504 end
503 end
505 when 'version'
504 when 'version'
506 if project && version = project.versions.find_by_name(name)
505 if project && version = project.versions.find_by_name(name)
507 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
506 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
508 :class => 'version'
507 :class => 'version'
509 end
508 end
510 when 'commit'
509 when 'commit'
511 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
510 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
512 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
511 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
513 :class => 'changeset',
512 :class => 'changeset',
514 :title => truncate_single_line(changeset.comments, 100)
513 :title => truncate_single_line(changeset.comments, 100)
515 end
514 end
516 when 'source', 'export'
515 when 'source', 'export'
517 if project && project.repository
516 if project && project.repository
518 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
517 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
519 path, rev, anchor = $1, $3, $5
518 path, rev, anchor = $1, $3, $5
520 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
519 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
521 :path => to_path_param(path),
520 :path => to_path_param(path),
522 :rev => rev,
521 :rev => rev,
523 :anchor => anchor,
522 :anchor => anchor,
524 :format => (prefix == 'export' ? 'raw' : nil)},
523 :format => (prefix == 'export' ? 'raw' : nil)},
525 :class => (prefix == 'export' ? 'source download' : 'source')
524 :class => (prefix == 'export' ? 'source download' : 'source')
526 end
525 end
527 when 'attachment'
526 when 'attachment'
528 if attachments && attachment = attachments.detect {|a| a.filename == name }
527 if attachments && attachment = attachments.detect {|a| a.filename == name }
529 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
528 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
530 :class => 'attachment'
529 :class => 'attachment'
531 end
530 end
532 end
531 end
533 end
532 end
534 end
533 end
535 leading + (link || "#{prefix}#{sep}#{oid}")
534 leading + (link || "#{prefix}#{sep}#{oid}")
536 end
535 end
537
536
538 text
537 text
539 end
538 end
540
539
541 # Same as Rails' simple_format helper without using paragraphs
540 # Same as Rails' simple_format helper without using paragraphs
542 def simple_format_without_paragraph(text)
541 def simple_format_without_paragraph(text)
543 text.to_s.
542 text.to_s.
544 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
543 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
545 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
544 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
546 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
545 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
547 end
546 end
548
547
549 def error_messages_for(object_name, options = {})
548 def error_messages_for(object_name, options = {})
550 options = options.symbolize_keys
549 options = options.symbolize_keys
551 object = instance_variable_get("@#{object_name}")
550 object = instance_variable_get("@#{object_name}")
552 if object && !object.errors.empty?
551 if object && !object.errors.empty?
553 # build full_messages here with controller current language
552 # build full_messages here with controller current language
554 full_messages = []
553 full_messages = []
555 object.errors.each do |attr, msg|
554 object.errors.each do |attr, msg|
556 next if msg.nil?
555 next if msg.nil?
557 msg = msg.first if msg.is_a? Array
556 msg = msg.first if msg.is_a? Array
558 if attr == "base"
557 if attr == "base"
559 full_messages << l(msg)
558 full_messages << l(msg)
560 else
559 else
561 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
560 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
562 end
561 end
563 end
562 end
564 # retrieve custom values error messages
563 # retrieve custom values error messages
565 if object.errors[:custom_values]
564 if object.errors[:custom_values]
566 object.custom_values.each do |v|
565 object.custom_values.each do |v|
567 v.errors.each do |attr, msg|
566 v.errors.each do |attr, msg|
568 next if msg.nil?
567 next if msg.nil?
569 msg = msg.first if msg.is_a? Array
568 msg = msg.first if msg.is_a? Array
570 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
569 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
571 end
570 end
572 end
571 end
573 end
572 end
574 content_tag("div",
573 content_tag("div",
575 content_tag(
574 content_tag(
576 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
575 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
577 ) +
576 ) +
578 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
577 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
579 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
578 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
580 )
579 )
581 else
580 else
582 ""
581 ""
583 end
582 end
584 end
583 end
585
584
586 def lang_options_for_select(blank=true)
585 def lang_options_for_select(blank=true)
587 (blank ? [["(auto)", ""]] : []) +
586 (blank ? [["(auto)", ""]] : []) +
588 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
587 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
589 end
588 end
590
589
591 def label_tag_for(name, option_tags = nil, options = {})
590 def label_tag_for(name, option_tags = nil, options = {})
592 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
591 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
593 content_tag("label", label_text)
592 content_tag("label", label_text)
594 end
593 end
595
594
596 def labelled_tabular_form_for(name, object, options, &proc)
595 def labelled_tabular_form_for(name, object, options, &proc)
597 options[:html] ||= {}
596 options[:html] ||= {}
598 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
597 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
599 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
598 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
600 end
599 end
601
600
602 def back_url_hidden_field_tag
601 def back_url_hidden_field_tag
603 back_url = params[:back_url] || request.env['HTTP_REFERER']
602 back_url = params[:back_url] || request.env['HTTP_REFERER']
604 back_url = CGI.unescape(back_url.to_s)
603 back_url = CGI.unescape(back_url.to_s)
605 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
604 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
606 end
605 end
607
606
608 def check_all_links(form_name)
607 def check_all_links(form_name)
609 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
608 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
610 " | " +
609 " | " +
611 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
610 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
612 end
611 end
613
612
614 def progress_bar(pcts, options={})
613 def progress_bar(pcts, options={})
615 pcts = [pcts, pcts] unless pcts.is_a?(Array)
614 pcts = [pcts, pcts] unless pcts.is_a?(Array)
616 pcts[1] = pcts[1] - pcts[0]
615 pcts[1] = pcts[1] - pcts[0]
617 pcts << (100 - pcts[1] - pcts[0])
616 pcts << (100 - pcts[1] - pcts[0])
618 width = options[:width] || '100px;'
617 width = options[:width] || '100px;'
619 legend = options[:legend] || ''
618 legend = options[:legend] || ''
620 content_tag('table',
619 content_tag('table',
621 content_tag('tr',
620 content_tag('tr',
622 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
621 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0].floor}%;", :class => 'closed') : '') +
623 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
622 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1].floor}%;", :class => 'done') : '') +
624 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
623 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2].floor}%;", :class => 'todo') : '')
625 ), :class => 'progress', :style => "width: #{width};") +
624 ), :class => 'progress', :style => "width: #{width};") +
626 content_tag('p', legend, :class => 'pourcent')
625 content_tag('p', legend, :class => 'pourcent')
627 end
626 end
628
627
629 def context_menu_link(name, url, options={})
628 def context_menu_link(name, url, options={})
630 options[:class] ||= ''
629 options[:class] ||= ''
631 if options.delete(:selected)
630 if options.delete(:selected)
632 options[:class] << ' icon-checked disabled'
631 options[:class] << ' icon-checked disabled'
633 options[:disabled] = true
632 options[:disabled] = true
634 end
633 end
635 if options.delete(:disabled)
634 if options.delete(:disabled)
636 options.delete(:method)
635 options.delete(:method)
637 options.delete(:confirm)
636 options.delete(:confirm)
638 options.delete(:onclick)
637 options.delete(:onclick)
639 options[:class] << ' disabled'
638 options[:class] << ' disabled'
640 url = '#'
639 url = '#'
641 end
640 end
642 link_to name, url, options
641 link_to name, url, options
643 end
642 end
644
643
645 def calendar_for(field_id)
644 def calendar_for(field_id)
646 include_calendar_headers_tags
645 include_calendar_headers_tags
647 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
646 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
648 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
647 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
649 end
648 end
650
649
651 def include_calendar_headers_tags
650 def include_calendar_headers_tags
652 unless @calendar_headers_tags_included
651 unless @calendar_headers_tags_included
653 @calendar_headers_tags_included = true
652 @calendar_headers_tags_included = true
654 content_for :header_tags do
653 content_for :header_tags do
655 javascript_include_tag('calendar/calendar') +
654 javascript_include_tag('calendar/calendar') +
656 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
655 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
657 javascript_include_tag('calendar/calendar-setup') +
656 javascript_include_tag('calendar/calendar-setup') +
658 stylesheet_link_tag('calendar')
657 stylesheet_link_tag('calendar')
659 end
658 end
660 end
659 end
661 end
660 end
662
661
663 def content_for(name, content = nil, &block)
662 def content_for(name, content = nil, &block)
664 @has_content ||= {}
663 @has_content ||= {}
665 @has_content[name] = true
664 @has_content[name] = true
666 super(name, content, &block)
665 super(name, content, &block)
667 end
666 end
668
667
669 def has_content?(name)
668 def has_content?(name)
670 (@has_content && @has_content[name]) || false
669 (@has_content && @has_content[name]) || false
671 end
670 end
672
671
673 # Returns the avatar image tag for the given +user+ if avatars are enabled
672 # Returns the avatar image tag for the given +user+ if avatars are enabled
674 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
673 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
675 def avatar(user, options = { })
674 def avatar(user, options = { })
676 if Setting.gravatar_enabled?
675 if Setting.gravatar_enabled?
677 email = nil
676 email = nil
678 if user.respond_to?(:mail)
677 if user.respond_to?(:mail)
679 email = user.mail
678 email = user.mail
680 elsif user.to_s =~ %r{<(.+?)>}
679 elsif user.to_s =~ %r{<(.+?)>}
681 email = $1
680 email = $1
682 end
681 end
683 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
682 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
684 end
683 end
685 end
684 end
686
685
687 private
686 private
688
687
689 def wiki_helper
688 def wiki_helper
690 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
689 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
691 extend helper
690 extend helper
692 return self
691 return self
693 end
692 end
694
693
695 def link_to_remote_content_update(text, url_params)
694 def link_to_remote_content_update(text, url_params)
696 link_to_remote(text,
695 link_to_remote(text,
697 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
696 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
698 {:href => url_for(:params => url_params)}
697 {:href => url_for(:params => url_params)}
699 )
698 )
700 end
699 end
701
700
702 end
701 end
@@ -1,450 +1,452
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules, :users,
23 fixtures :projects, :roles, :enabled_modules, :users,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages,
27 :boards, :messages,
28 :attachments
28 :attachments
29
29
30 def setup
30 def setup
31 super
31 super
32 end
32 end
33
33
34 def test_auto_links
34 def test_auto_links
35 to_test = {
35 to_test = {
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
39 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
40 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
41 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
42 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
43 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
44 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
45 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
46 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
48 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
49 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
50 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
51 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
52 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
53 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
54 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
55 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
56 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
57 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
58 }
58 }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
59 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
60 end
60 end
61
61
62 def test_auto_mailto
62 def test_auto_mailto
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
63 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
64 textilizable('test@foo.bar')
64 textilizable('test@foo.bar')
65 end
65 end
66
66
67 def test_inline_images
67 def test_inline_images
68 to_test = {
68 to_test = {
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
69 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
70 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
71 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
72 # inline styles should be stripped
72 # inline styles should be stripped
73 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
73 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
74 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
74 'with title !http://foo.bar/image.jpg(This is a title)!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a title" alt="This is a title" />',
75 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
75 'with title !http://foo.bar/image.jpg(This is a double-quoted "title")!' => 'with title <img src="http://foo.bar/image.jpg" title="This is a double-quoted &quot;title&quot;" alt="This is a double-quoted &quot;title&quot;" />',
76 }
76 }
77 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
77 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
78 end
78 end
79
79
80 def test_acronyms
80 def test_acronyms
81 to_test = {
81 to_test = {
82 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
82 'this is an acronym: GPL(General Public License)' => 'this is an acronym: <acronym title="General Public License">GPL</acronym>',
83 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
83 'GPL(This is a double-quoted "title")' => '<acronym title="This is a double-quoted &quot;title&quot;">GPL</acronym>',
84 }
84 }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
86
86
87 end
87 end
88
88
89 def test_attached_images
89 def test_attached_images
90 to_test = {
90 to_test = {
91 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
91 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
92 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />'
92 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
93 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
94 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />'
93 }
95 }
94 attachments = Attachment.find(:all)
96 attachments = Attachment.find(:all)
95 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
96 end
98 end
97
99
98 def test_textile_external_links
100 def test_textile_external_links
99 to_test = {
101 to_test = {
100 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
102 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
101 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
103 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
102 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
104 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
103 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
105 '"link (Link title with "double-quotes")":http://foo.bar' => '<a href="http://foo.bar" title="Link title with &quot;double-quotes&quot;" class="external">link</a>',
104 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
106 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
105 # no multiline link text
107 # no multiline link text
106 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
108 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
107 }
109 }
108 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
110 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
109 end
111 end
110
112
111 def test_redmine_links
113 def test_redmine_links
112 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
114 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
113 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
115 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
114
116
115 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
117 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
116 :class => 'changeset', :title => 'My very first commit')
118 :class => 'changeset', :title => 'My very first commit')
117
119
118 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
120 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
119 :class => 'document')
121 :class => 'document')
120
122
121 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
123 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
122 :class => 'version')
124 :class => 'version')
123
125
124 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
126 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
125
127
126 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
128 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
127 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
129 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
128
130
129 to_test = {
131 to_test = {
130 # tickets
132 # tickets
131 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
133 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
132 # changesets
134 # changesets
133 'r1' => changeset_link,
135 'r1' => changeset_link,
134 # documents
136 # documents
135 'document#1' => document_link,
137 'document#1' => document_link,
136 'document:"Test document"' => document_link,
138 'document:"Test document"' => document_link,
137 # versions
139 # versions
138 'version#2' => version_link,
140 'version#2' => version_link,
139 'version:1.0' => version_link,
141 'version:1.0' => version_link,
140 'version:"1.0"' => version_link,
142 'version:"1.0"' => version_link,
141 # source
143 # source
142 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
144 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
143 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
145 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
144 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
146 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
145 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
147 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
146 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
148 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
147 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
149 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
148 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
150 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
149 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
151 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
150 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
152 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
151 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
153 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
152 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
154 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
153 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
155 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
154 # message
156 # message
155 'message#4' => link_to('Post 2', message_url, :class => 'message'),
157 'message#4' => link_to('Post 2', message_url, :class => 'message'),
156 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
158 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
157 # escaping
159 # escaping
158 '!#3.' => '#3.',
160 '!#3.' => '#3.',
159 '!r1' => 'r1',
161 '!r1' => 'r1',
160 '!document#1' => 'document#1',
162 '!document#1' => 'document#1',
161 '!document:"Test document"' => 'document:"Test document"',
163 '!document:"Test document"' => 'document:"Test document"',
162 '!version#2' => 'version#2',
164 '!version#2' => 'version#2',
163 '!version:1.0' => 'version:1.0',
165 '!version:1.0' => 'version:1.0',
164 '!version:"1.0"' => 'version:"1.0"',
166 '!version:"1.0"' => 'version:"1.0"',
165 '!source:/some/file' => 'source:/some/file',
167 '!source:/some/file' => 'source:/some/file',
166 # invalid expressions
168 # invalid expressions
167 'source:' => 'source:',
169 'source:' => 'source:',
168 # url hash
170 # url hash
169 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
171 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
170 }
172 }
171 @project = Project.find(1)
173 @project = Project.find(1)
172 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
174 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
173 end
175 end
174
176
175 def test_wiki_links
177 def test_wiki_links
176 to_test = {
178 to_test = {
177 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
179 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
178 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
180 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
179 # link with anchor
181 # link with anchor
180 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
182 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
181 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
183 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
182 # page that doesn't exist
184 # page that doesn't exist
183 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
185 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
184 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
186 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
185 # link to another project wiki
187 # link to another project wiki
186 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
188 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
187 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
189 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
188 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
190 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
189 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
191 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
190 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
192 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
191 # striked through link
193 # striked through link
192 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
194 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
193 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
195 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
194 # escaping
196 # escaping
195 '![[Another page|Page]]' => '[[Another page|Page]]',
197 '![[Another page|Page]]' => '[[Another page|Page]]',
196 }
198 }
197 @project = Project.find(1)
199 @project = Project.find(1)
198 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
200 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
199 end
201 end
200
202
201 def test_html_tags
203 def test_html_tags
202 to_test = {
204 to_test = {
203 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
205 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
204 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
206 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
205 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
207 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
206 # do not escape pre/code tags
208 # do not escape pre/code tags
207 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
209 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
208 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
210 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
209 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
211 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
210 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
212 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
211 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
213 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
212 # remove attributes except class
214 # remove attributes except class
213 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
215 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
214 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
216 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
215 }
217 }
216 to_test.each { |text, result| assert_equal result, textilizable(text) }
218 to_test.each { |text, result| assert_equal result, textilizable(text) }
217 end
219 end
218
220
219 def test_allowed_html_tags
221 def test_allowed_html_tags
220 to_test = {
222 to_test = {
221 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
223 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
222 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
224 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
223 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
225 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
224 }
226 }
225 to_test.each { |text, result| assert_equal result, textilizable(text) }
227 to_test.each { |text, result| assert_equal result, textilizable(text) }
226 end
228 end
227
229
228 def syntax_highlight
230 def syntax_highlight
229 raw = <<-RAW
231 raw = <<-RAW
230 <pre><code class="ruby">
232 <pre><code class="ruby">
231 # Some ruby code here
233 # Some ruby code here
232 </pre></code>
234 </pre></code>
233 RAW
235 RAW
234
236
235 expected = <<-EXPECTED
237 expected = <<-EXPECTED
236 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
238 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
237 </pre></code>
239 </pre></code>
238 EXPECTED
240 EXPECTED
239
241
240 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
242 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
241 end
243 end
242
244
243 def test_wiki_links_in_tables
245 def test_wiki_links_in_tables
244 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
246 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
245 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
247 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
246 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
248 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
247 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
249 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
248 }
250 }
249 @project = Project.find(1)
251 @project = Project.find(1)
250 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
252 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
251 end
253 end
252
254
253 def test_text_formatting
255 def test_text_formatting
254 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
256 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
255 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
257 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
256 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
258 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
257 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
259 'a H *umane* W *eb* T *ext* G *enerator*' => 'a H <strong>umane</strong> W <strong>eb</strong> T <strong>ext</strong> G <strong>enerator</strong>',
258 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
260 'a *H* umane *W* eb *T* ext *G* enerator' => 'a <strong>H</strong> umane <strong>W</strong> eb <strong>T</strong> ext <strong>G</strong> enerator',
259 }
261 }
260 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
261 end
263 end
262
264
263 def test_wiki_horizontal_rule
265 def test_wiki_horizontal_rule
264 assert_equal '<hr />', textilizable('---')
266 assert_equal '<hr />', textilizable('---')
265 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
267 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
266 end
268 end
267
269
268 def test_acronym
270 def test_acronym
269 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
271 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
270 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
272 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
271 end
273 end
272
274
273 def test_footnotes
275 def test_footnotes
274 raw = <<-RAW
276 raw = <<-RAW
275 This is some text[1].
277 This is some text[1].
276
278
277 fn1. This is the foot note
279 fn1. This is the foot note
278 RAW
280 RAW
279
281
280 expected = <<-EXPECTED
282 expected = <<-EXPECTED
281 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
283 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
282 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
284 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
283 EXPECTED
285 EXPECTED
284
286
285 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
287 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
286 end
288 end
287
289
288 def test_table_of_content
290 def test_table_of_content
289 raw = <<-RAW
291 raw = <<-RAW
290 {{toc}}
292 {{toc}}
291
293
292 h1. Title
294 h1. Title
293
295
294 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
296 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
295
297
296 h2. Subtitle with a [[Wiki]] link
298 h2. Subtitle with a [[Wiki]] link
297
299
298 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
300 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
299
301
300 h2. Subtitle with [[Wiki|another Wiki]] link
302 h2. Subtitle with [[Wiki|another Wiki]] link
301
303
302 h2. Subtitle with %{color:red}red text%
304 h2. Subtitle with %{color:red}red text%
303
305
304 h1. Another title
306 h1. Another title
305
307
306 RAW
308 RAW
307
309
308 expected = '<ul class="toc">' +
310 expected = '<ul class="toc">' +
309 '<li class="heading1"><a href="#Title">Title</a></li>' +
311 '<li class="heading1"><a href="#Title">Title</a></li>' +
310 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
312 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
311 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
313 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
312 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
314 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
313 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
315 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
314 '</ul>'
316 '</ul>'
315
317
316 assert textilizable(raw).gsub("\n", "").include?(expected)
318 assert textilizable(raw).gsub("\n", "").include?(expected)
317 end
319 end
318
320
319 def test_blockquote
321 def test_blockquote
320 # orig raw text
322 # orig raw text
321 raw = <<-RAW
323 raw = <<-RAW
322 John said:
324 John said:
323 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
325 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
324 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
326 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
325 > * Donec odio lorem,
327 > * Donec odio lorem,
326 > * sagittis ac,
328 > * sagittis ac,
327 > * malesuada in,
329 > * malesuada in,
328 > * adipiscing eu, dolor.
330 > * adipiscing eu, dolor.
329 >
331 >
330 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
332 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
331 > Proin a tellus. Nam vel neque.
333 > Proin a tellus. Nam vel neque.
332
334
333 He's right.
335 He's right.
334 RAW
336 RAW
335
337
336 # expected html
338 # expected html
337 expected = <<-EXPECTED
339 expected = <<-EXPECTED
338 <p>John said:</p>
340 <p>John said:</p>
339 <blockquote>
341 <blockquote>
340 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
342 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
341 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
343 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
342 <ul>
344 <ul>
343 <li>Donec odio lorem,</li>
345 <li>Donec odio lorem,</li>
344 <li>sagittis ac,</li>
346 <li>sagittis ac,</li>
345 <li>malesuada in,</li>
347 <li>malesuada in,</li>
346 <li>adipiscing eu, dolor.</li>
348 <li>adipiscing eu, dolor.</li>
347 </ul>
349 </ul>
348 <blockquote>
350 <blockquote>
349 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
351 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
350 </blockquote>
352 </blockquote>
351 <p>Proin a tellus. Nam vel neque.</p>
353 <p>Proin a tellus. Nam vel neque.</p>
352 </blockquote>
354 </blockquote>
353 <p>He's right.</p>
355 <p>He's right.</p>
354 EXPECTED
356 EXPECTED
355
357
356 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
358 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
357 end
359 end
358
360
359 def test_table
361 def test_table
360 raw = <<-RAW
362 raw = <<-RAW
361 This is a table with empty cells:
363 This is a table with empty cells:
362
364
363 |cell11|cell12||
365 |cell11|cell12||
364 |cell21||cell23|
366 |cell21||cell23|
365 |cell31|cell32|cell33|
367 |cell31|cell32|cell33|
366 RAW
368 RAW
367
369
368 expected = <<-EXPECTED
370 expected = <<-EXPECTED
369 <p>This is a table with empty cells:</p>
371 <p>This is a table with empty cells:</p>
370
372
371 <table>
373 <table>
372 <tr><td>cell11</td><td>cell12</td><td></td></tr>
374 <tr><td>cell11</td><td>cell12</td><td></td></tr>
373 <tr><td>cell21</td><td></td><td>cell23</td></tr>
375 <tr><td>cell21</td><td></td><td>cell23</td></tr>
374 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
376 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
375 </table>
377 </table>
376 EXPECTED
378 EXPECTED
377
379
378 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
380 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
379 end
381 end
380
382
381 def test_default_formatter
383 def test_default_formatter
382 Setting.text_formatting = 'unknown'
384 Setting.text_formatting = 'unknown'
383 text = 'a *link*: http://www.example.net/'
385 text = 'a *link*: http://www.example.net/'
384 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
386 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
385 Setting.text_formatting = 'textile'
387 Setting.text_formatting = 'textile'
386 end
388 end
387
389
388 def test_date_format_default
390 def test_date_format_default
389 today = Date.today
391 today = Date.today
390 Setting.date_format = ''
392 Setting.date_format = ''
391 assert_equal l_date(today), format_date(today)
393 assert_equal l_date(today), format_date(today)
392 end
394 end
393
395
394 def test_date_format
396 def test_date_format
395 today = Date.today
397 today = Date.today
396 Setting.date_format = '%d %m %Y'
398 Setting.date_format = '%d %m %Y'
397 assert_equal today.strftime('%d %m %Y'), format_date(today)
399 assert_equal today.strftime('%d %m %Y'), format_date(today)
398 end
400 end
399
401
400 def test_time_format_default
402 def test_time_format_default
401 now = Time.now
403 now = Time.now
402 Setting.date_format = ''
404 Setting.date_format = ''
403 Setting.time_format = ''
405 Setting.time_format = ''
404 assert_equal l_datetime(now), format_time(now)
406 assert_equal l_datetime(now), format_time(now)
405 assert_equal l_time(now), format_time(now, false)
407 assert_equal l_time(now), format_time(now, false)
406 end
408 end
407
409
408 def test_time_format
410 def test_time_format
409 now = Time.now
411 now = Time.now
410 Setting.date_format = '%d %m %Y'
412 Setting.date_format = '%d %m %Y'
411 Setting.time_format = '%H %M'
413 Setting.time_format = '%H %M'
412 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
414 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
413 assert_equal now.strftime('%H %M'), format_time(now, false)
415 assert_equal now.strftime('%H %M'), format_time(now, false)
414 end
416 end
415
417
416 def test_utc_time_format
418 def test_utc_time_format
417 now = Time.now.utc
419 now = Time.now.utc
418 Setting.date_format = '%d %m %Y'
420 Setting.date_format = '%d %m %Y'
419 Setting.time_format = '%H %M'
421 Setting.time_format = '%H %M'
420 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
422 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
421 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
423 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
422 end
424 end
423
425
424 def test_due_date_distance_in_words
426 def test_due_date_distance_in_words
425 to_test = { Date.today => 'Due in 0 days',
427 to_test = { Date.today => 'Due in 0 days',
426 Date.today + 1 => 'Due in 1 day',
428 Date.today + 1 => 'Due in 1 day',
427 Date.today + 100 => 'Due in 100 days',
429 Date.today + 100 => 'Due in 100 days',
428 Date.today + 20000 => 'Due in 20000 days',
430 Date.today + 20000 => 'Due in 20000 days',
429 Date.today - 1 => '1 day late',
431 Date.today - 1 => '1 day late',
430 Date.today - 100 => '100 days late',
432 Date.today - 100 => '100 days late',
431 Date.today - 20000 => '20000 days late',
433 Date.today - 20000 => '20000 days late',
432 }
434 }
433 to_test.each do |date, expected|
435 to_test.each do |date, expected|
434 assert_equal expected, due_date_distance_in_words(date)
436 assert_equal expected, due_date_distance_in_words(date)
435 end
437 end
436 end
438 end
437
439
438 def test_avatar
440 def test_avatar
439 # turn on avatars
441 # turn on avatars
440 Setting.gravatar_enabled = '1'
442 Setting.gravatar_enabled = '1'
441 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
443 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
442 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
444 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
443 assert_nil avatar('jsmith')
445 assert_nil avatar('jsmith')
444 assert_nil avatar(nil)
446 assert_nil avatar(nil)
445
447
446 # turn off avatars
448 # turn off avatars
447 Setting.gravatar_enabled = '0'
449 Setting.gravatar_enabled = '0'
448 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
450 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
449 end
451 end
450 end
452 end
General Comments 0
You need to be logged in to leave comments. Login now