##// END OF EJS Templates
Leave wiki links untouched if target project doesn't exist or have no wiki....
Jean-Philippe Lang -
r2375:70efee1bc5b8
parent child
Show More
@@ -1,701 +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.downcase
360 filename = $6.downcase
361 # search for the picture in attachments
361 # search for the picture in attachments
362 if found = attachments.detect { |att| att.filename.downcase == filename }
362 if found = attachments.detect { |att| att.filename.downcase == filename }
363 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
364 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
364 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
365 alt = desc.blank? ? nil : "(#{desc})"
365 alt = desc.blank? ? nil : "(#{desc})"
366 "!#{style}#{image_url}#{alt}!"
366 "!#{style}#{image_url}#{alt}!"
367 else
367 else
368 m
368 m
369 end
369 end
370 end
370 end
371 end
371 end
372
372
373 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) }
374
374
375 # different methods for formatting wiki links
375 # different methods for formatting wiki links
376 case options[:wiki_links]
376 case options[:wiki_links]
377 when :local
377 when :local
378 # used for local links to html files
378 # used for local links to html files
379 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
379 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
380 when :anchor
380 when :anchor
381 # used for single-file wiki export
381 # used for single-file wiki export
382 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
382 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
383 else
383 else
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) }
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) }
385 end
385 end
386
386
387 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)
388
388
389 # Wiki links
389 # Wiki links
390 #
390 #
391 # Examples:
391 # Examples:
392 # [[mypage]]
392 # [[mypage]]
393 # [[mypage|mytext]]
393 # [[mypage|mytext]]
394 # 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:
395 # [[project:]] -> wiki starting page
395 # [[project:]] -> wiki starting page
396 # [[project:|mytext]]
396 # [[project:|mytext]]
397 # [[project:mypage]]
397 # [[project:mypage]]
398 # [[project:mypage|mytext]]
398 # [[project:mypage|mytext]]
399 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
399 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
400 link_project = project
400 link_project = project
401 esc, all, page, title = $1, $2, $3, $5
401 esc, all, page, title = $1, $2, $3, $5
402 if esc.nil?
402 if esc.nil?
403 if page =~ /^([^\:]+)\:(.*)$/
403 if page =~ /^([^\:]+)\:(.*)$/
404 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)
405 page = $2
405 page = $2
406 title ||= $1 if page.blank?
406 title ||= $1 if page.blank?
407 end
407 end
408
408
409 if link_project && link_project.wiki
409 if link_project && link_project.wiki
410 # extract anchor
410 # extract anchor
411 anchor = nil
411 anchor = nil
412 if page =~ /^(.+?)\#(.+)$/
412 if page =~ /^(.+?)\#(.+)$/
413 page, anchor = $1, $2
413 page, anchor = $1, $2
414 end
414 end
415 # check if page exists
415 # check if page exists
416 wiki_page = link_project.wiki.find_page(page)
416 wiki_page = link_project.wiki.find_page(page)
417 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),
418 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
418 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
419 else
419 else
420 # project or wiki doesn't exist
420 # project or wiki doesn't exist
421 title || page
421 all
422 end
422 end
423 else
423 else
424 all
424 all
425 end
425 end
426 end
426 end
427
427
428 # Redmine links
428 # Redmine links
429 #
429 #
430 # Examples:
430 # Examples:
431 # Issues:
431 # Issues:
432 # #52 -> Link to issue #52
432 # #52 -> Link to issue #52
433 # Changesets:
433 # Changesets:
434 # r52 -> Link to revision 52
434 # r52 -> Link to revision 52
435 # commit:a85130f -> Link to scmid starting with a85130f
435 # commit:a85130f -> Link to scmid starting with a85130f
436 # Documents:
436 # Documents:
437 # document#17 -> Link to document with id 17
437 # document#17 -> Link to document with id 17
438 # document:Greetings -> Link to the document with title "Greetings"
438 # document:Greetings -> Link to the document with title "Greetings"
439 # document:"Some document" -> Link to the document with title "Some document"
439 # document:"Some document" -> Link to the document with title "Some document"
440 # Versions:
440 # Versions:
441 # version#3 -> Link to version with id 3
441 # version#3 -> Link to version with id 3
442 # version:1.0.0 -> Link to version named "1.0.0"
442 # version:1.0.0 -> Link to version named "1.0.0"
443 # 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"
444 # Attachments:
444 # Attachments:
445 # 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
446 # Source files:
446 # Source files:
447 # 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
448 # source:some/file@52 -> Link to the file's revision 52
448 # source:some/file@52 -> Link to the file's revision 52
449 # source:some/file#L120 -> Link to line 120 of the file
449 # source:some/file#L120 -> Link to line 120 of the file
450 # 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
451 # export:some/file -> Force the download of the file
451 # export:some/file -> Force the download of the file
452 # Forum messages:
452 # Forum messages:
453 # message#1218 -> Link to message with id 1218
453 # message#1218 -> Link to message with id 1218
454 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|
455 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
456 link = nil
456 link = nil
457 if esc.nil?
457 if esc.nil?
458 if prefix.nil? && sep == 'r'
458 if prefix.nil? && sep == 'r'
459 if project && (changeset = project.changesets.find_by_revision(oid))
459 if project && (changeset = project.changesets.find_by_revision(oid))
460 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},
461 :class => 'changeset',
461 :class => 'changeset',
462 :title => truncate_single_line(changeset.comments, 100))
462 :title => truncate_single_line(changeset.comments, 100))
463 end
463 end
464 elsif sep == '#'
464 elsif sep == '#'
465 oid = oid.to_i
465 oid = oid.to_i
466 case prefix
466 case prefix
467 when nil
467 when nil
468 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))
469 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},
470 :class => (issue.closed? ? 'issue closed' : 'issue'),
470 :class => (issue.closed? ? 'issue closed' : 'issue'),
471 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
471 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
472 link = content_tag('del', link) if issue.closed?
472 link = content_tag('del', link) if issue.closed?
473 end
473 end
474 when 'document'
474 when 'document'
475 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))
476 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},
477 :class => 'document'
477 :class => 'document'
478 end
478 end
479 when 'version'
479 when 'version'
480 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))
481 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},
482 :class => 'version'
482 :class => 'version'
483 end
483 end
484 when 'message'
484 when 'message'
485 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))
486 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,
487 :controller => 'messages',
487 :controller => 'messages',
488 :action => 'show',
488 :action => 'show',
489 :board_id => message.board,
489 :board_id => message.board,
490 :id => message.root,
490 :id => message.root,
491 :anchor => (message.parent ? "message-#{message.id}" : nil)},
491 :anchor => (message.parent ? "message-#{message.id}" : nil)},
492 :class => 'message'
492 :class => 'message'
493 end
493 end
494 end
494 end
495 elsif sep == ':'
495 elsif sep == ':'
496 # removes the double quotes if any
496 # removes the double quotes if any
497 name = oid.gsub(%r{^"(.*)"$}, "\\1")
497 name = oid.gsub(%r{^"(.*)"$}, "\\1")
498 case prefix
498 case prefix
499 when 'document'
499 when 'document'
500 if project && document = project.documents.find_by_title(name)
500 if project && document = project.documents.find_by_title(name)
501 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},
502 :class => 'document'
502 :class => 'document'
503 end
503 end
504 when 'version'
504 when 'version'
505 if project && version = project.versions.find_by_name(name)
505 if project && version = project.versions.find_by_name(name)
506 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},
507 :class => 'version'
507 :class => 'version'
508 end
508 end
509 when 'commit'
509 when 'commit'
510 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
510 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
511 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},
512 :class => 'changeset',
512 :class => 'changeset',
513 :title => truncate_single_line(changeset.comments, 100)
513 :title => truncate_single_line(changeset.comments, 100)
514 end
514 end
515 when 'source', 'export'
515 when 'source', 'export'
516 if project && project.repository
516 if project && project.repository
517 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
517 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
518 path, rev, anchor = $1, $3, $5
518 path, rev, anchor = $1, $3, $5
519 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,
520 :path => to_path_param(path),
520 :path => to_path_param(path),
521 :rev => rev,
521 :rev => rev,
522 :anchor => anchor,
522 :anchor => anchor,
523 :format => (prefix == 'export' ? 'raw' : nil)},
523 :format => (prefix == 'export' ? 'raw' : nil)},
524 :class => (prefix == 'export' ? 'source download' : 'source')
524 :class => (prefix == 'export' ? 'source download' : 'source')
525 end
525 end
526 when 'attachment'
526 when 'attachment'
527 if attachments && attachment = attachments.detect {|a| a.filename == name }
527 if attachments && attachment = attachments.detect {|a| a.filename == name }
528 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},
529 :class => 'attachment'
529 :class => 'attachment'
530 end
530 end
531 end
531 end
532 end
532 end
533 end
533 end
534 leading + (link || "#{prefix}#{sep}#{oid}")
534 leading + (link || "#{prefix}#{sep}#{oid}")
535 end
535 end
536
536
537 text
537 text
538 end
538 end
539
539
540 # Same as Rails' simple_format helper without using paragraphs
540 # Same as Rails' simple_format helper without using paragraphs
541 def simple_format_without_paragraph(text)
541 def simple_format_without_paragraph(text)
542 text.to_s.
542 text.to_s.
543 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
543 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
544 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
544 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
545 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
545 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
546 end
546 end
547
547
548 def error_messages_for(object_name, options = {})
548 def error_messages_for(object_name, options = {})
549 options = options.symbolize_keys
549 options = options.symbolize_keys
550 object = instance_variable_get("@#{object_name}")
550 object = instance_variable_get("@#{object_name}")
551 if object && !object.errors.empty?
551 if object && !object.errors.empty?
552 # build full_messages here with controller current language
552 # build full_messages here with controller current language
553 full_messages = []
553 full_messages = []
554 object.errors.each do |attr, msg|
554 object.errors.each do |attr, msg|
555 next if msg.nil?
555 next if msg.nil?
556 msg = msg.first if msg.is_a? Array
556 msg = msg.first if msg.is_a? Array
557 if attr == "base"
557 if attr == "base"
558 full_messages << l(msg)
558 full_messages << l(msg)
559 else
559 else
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"
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"
561 end
561 end
562 end
562 end
563 # retrieve custom values error messages
563 # retrieve custom values error messages
564 if object.errors[:custom_values]
564 if object.errors[:custom_values]
565 object.custom_values.each do |v|
565 object.custom_values.each do |v|
566 v.errors.each do |attr, msg|
566 v.errors.each do |attr, msg|
567 next if msg.nil?
567 next if msg.nil?
568 msg = msg.first if msg.is_a? Array
568 msg = msg.first if msg.is_a? Array
569 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
569 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
570 end
570 end
571 end
571 end
572 end
572 end
573 content_tag("div",
573 content_tag("div",
574 content_tag(
574 content_tag(
575 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
575 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
576 ) +
576 ) +
577 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
577 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
578 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
578 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
579 )
579 )
580 else
580 else
581 ""
581 ""
582 end
582 end
583 end
583 end
584
584
585 def lang_options_for_select(blank=true)
585 def lang_options_for_select(blank=true)
586 (blank ? [["(auto)", ""]] : []) +
586 (blank ? [["(auto)", ""]] : []) +
587 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 }
588 end
588 end
589
589
590 def label_tag_for(name, option_tags = nil, options = {})
590 def label_tag_for(name, option_tags = nil, options = {})
591 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"): "")
592 content_tag("label", label_text)
592 content_tag("label", label_text)
593 end
593 end
594
594
595 def labelled_tabular_form_for(name, object, options, &proc)
595 def labelled_tabular_form_for(name, object, options, &proc)
596 options[:html] ||= {}
596 options[:html] ||= {}
597 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
597 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
598 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)
599 end
599 end
600
600
601 def back_url_hidden_field_tag
601 def back_url_hidden_field_tag
602 back_url = params[:back_url] || request.env['HTTP_REFERER']
602 back_url = params[:back_url] || request.env['HTTP_REFERER']
603 back_url = CGI.unescape(back_url.to_s)
603 back_url = CGI.unescape(back_url.to_s)
604 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?
605 end
605 end
606
606
607 def check_all_links(form_name)
607 def check_all_links(form_name)
608 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
608 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
609 " | " +
609 " | " +
610 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
610 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
611 end
611 end
612
612
613 def progress_bar(pcts, options={})
613 def progress_bar(pcts, options={})
614 pcts = [pcts, pcts] unless pcts.is_a?(Array)
614 pcts = [pcts, pcts] unless pcts.is_a?(Array)
615 pcts[1] = pcts[1] - pcts[0]
615 pcts[1] = pcts[1] - pcts[0]
616 pcts << (100 - pcts[1] - pcts[0])
616 pcts << (100 - pcts[1] - pcts[0])
617 width = options[:width] || '100px;'
617 width = options[:width] || '100px;'
618 legend = options[:legend] || ''
618 legend = options[:legend] || ''
619 content_tag('table',
619 content_tag('table',
620 content_tag('tr',
620 content_tag('tr',
621 (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') : '') +
622 (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') : '') +
623 (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') : '')
624 ), :class => 'progress', :style => "width: #{width};") +
624 ), :class => 'progress', :style => "width: #{width};") +
625 content_tag('p', legend, :class => 'pourcent')
625 content_tag('p', legend, :class => 'pourcent')
626 end
626 end
627
627
628 def context_menu_link(name, url, options={})
628 def context_menu_link(name, url, options={})
629 options[:class] ||= ''
629 options[:class] ||= ''
630 if options.delete(:selected)
630 if options.delete(:selected)
631 options[:class] << ' icon-checked disabled'
631 options[:class] << ' icon-checked disabled'
632 options[:disabled] = true
632 options[:disabled] = true
633 end
633 end
634 if options.delete(:disabled)
634 if options.delete(:disabled)
635 options.delete(:method)
635 options.delete(:method)
636 options.delete(:confirm)
636 options.delete(:confirm)
637 options.delete(:onclick)
637 options.delete(:onclick)
638 options[:class] << ' disabled'
638 options[:class] << ' disabled'
639 url = '#'
639 url = '#'
640 end
640 end
641 link_to name, url, options
641 link_to name, url, options
642 end
642 end
643
643
644 def calendar_for(field_id)
644 def calendar_for(field_id)
645 include_calendar_headers_tags
645 include_calendar_headers_tags
646 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
646 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
647 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' });")
648 end
648 end
649
649
650 def include_calendar_headers_tags
650 def include_calendar_headers_tags
651 unless @calendar_headers_tags_included
651 unless @calendar_headers_tags_included
652 @calendar_headers_tags_included = true
652 @calendar_headers_tags_included = true
653 content_for :header_tags do
653 content_for :header_tags do
654 javascript_include_tag('calendar/calendar') +
654 javascript_include_tag('calendar/calendar') +
655 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
655 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
656 javascript_include_tag('calendar/calendar-setup') +
656 javascript_include_tag('calendar/calendar-setup') +
657 stylesheet_link_tag('calendar')
657 stylesheet_link_tag('calendar')
658 end
658 end
659 end
659 end
660 end
660 end
661
661
662 def content_for(name, content = nil, &block)
662 def content_for(name, content = nil, &block)
663 @has_content ||= {}
663 @has_content ||= {}
664 @has_content[name] = true
664 @has_content[name] = true
665 super(name, content, &block)
665 super(name, content, &block)
666 end
666 end
667
667
668 def has_content?(name)
668 def has_content?(name)
669 (@has_content && @has_content[name]) || false
669 (@has_content && @has_content[name]) || false
670 end
670 end
671
671
672 # 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
673 # +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>')
674 def avatar(user, options = { })
674 def avatar(user, options = { })
675 if Setting.gravatar_enabled?
675 if Setting.gravatar_enabled?
676 email = nil
676 email = nil
677 if user.respond_to?(:mail)
677 if user.respond_to?(:mail)
678 email = user.mail
678 email = user.mail
679 elsif user.to_s =~ %r{<(.+?)>}
679 elsif user.to_s =~ %r{<(.+?)>}
680 email = $1
680 email = $1
681 end
681 end
682 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
683 end
683 end
684 end
684 end
685
685
686 private
686 private
687
687
688 def wiki_helper
688 def wiki_helper
689 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
689 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
690 extend helper
690 extend helper
691 return self
691 return self
692 end
692 end
693
693
694 def link_to_remote_content_update(text, url_params)
694 def link_to_remote_content_update(text, url_params)
695 link_to_remote(text,
695 link_to_remote(text,
696 {: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)'},
697 {:href => url_for(:params => url_params)}
697 {:href => url_for(:params => url_params)}
698 )
698 )
699 end
699 end
700
700
701 end
701 end
@@ -1,452 +1,455
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="" />',
93 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
94 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />'
94 'No match: !ogo.GIF!' => 'No match: <img src="ogo.GIF" alt="" />'
95 }
95 }
96 attachments = Attachment.find(:all)
96 attachments = Attachment.find(:all)
97 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) }
98 end
98 end
99
99
100 def test_textile_external_links
100 def test_textile_external_links
101 to_test = {
101 to_test = {
102 '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>',
103 '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>',
104 '"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>',
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>',
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>',
106 "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",
107 # no multiline link text
107 # no multiline link text
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"
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"
109 }
109 }
110 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) }
111 end
111 end
112
112
113 def test_redmine_links
113 def test_redmine_links
114 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
114 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
115 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
115 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
116
116
117 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},
118 :class => 'changeset', :title => 'My very first commit')
118 :class => 'changeset', :title => 'My very first commit')
119
119
120 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},
121 :class => 'document')
121 :class => 'document')
122
122
123 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},
124 :class => 'version')
124 :class => 'version')
125
125
126 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
126 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
127
127
128 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
128 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
129 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']}
130
130
131 to_test = {
131 to_test = {
132 # tickets
132 # tickets
133 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
133 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
134 # changesets
134 # changesets
135 'r1' => changeset_link,
135 'r1' => changeset_link,
136 # documents
136 # documents
137 'document#1' => document_link,
137 'document#1' => document_link,
138 'document:"Test document"' => document_link,
138 'document:"Test document"' => document_link,
139 # versions
139 # versions
140 'version#2' => version_link,
140 'version#2' => version_link,
141 'version:1.0' => version_link,
141 'version:1.0' => version_link,
142 'version:"1.0"' => version_link,
142 'version:"1.0"' => version_link,
143 # source
143 # source
144 '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'),
145 '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') + ".",
146 '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') + ".",
147 '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') + ".",
148 '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') + ".",
149 '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') + ",",
150 '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'),
151 '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'),
152 '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'),
153 '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'),
154 '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'),
155 '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'),
156 # message
156 # message
157 'message#4' => link_to('Post 2', message_url, :class => 'message'),
157 'message#4' => link_to('Post 2', message_url, :class => 'message'),
158 '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'),
159 # escaping
159 # escaping
160 '!#3.' => '#3.',
160 '!#3.' => '#3.',
161 '!r1' => 'r1',
161 '!r1' => 'r1',
162 '!document#1' => 'document#1',
162 '!document#1' => 'document#1',
163 '!document:"Test document"' => 'document:"Test document"',
163 '!document:"Test document"' => 'document:"Test document"',
164 '!version#2' => 'version#2',
164 '!version#2' => 'version#2',
165 '!version:1.0' => 'version:1.0',
165 '!version:1.0' => 'version:1.0',
166 '!version:"1.0"' => 'version:"1.0"',
166 '!version:"1.0"' => 'version:"1.0"',
167 '!source:/some/file' => 'source:/some/file',
167 '!source:/some/file' => 'source:/some/file',
168 # invalid expressions
168 # invalid expressions
169 'source:' => 'source:',
169 'source:' => 'source:',
170 # url hash
170 # url hash
171 "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>',
172 }
172 }
173 @project = Project.find(1)
173 @project = Project.find(1)
174 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) }
175 end
175 end
176
176
177 def test_wiki_links
177 def test_wiki_links
178 to_test = {
178 to_test = {
179 '[[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>',
180 '[[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>',
181 # link with anchor
181 # link with anchor
182 '[[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>',
183 '[[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>',
184 # page that doesn't exist
184 # page that doesn't exist
185 '[[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>',
186 '[[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>',
187 # link to another project wiki
187 # link to another project wiki
188 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
188 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
189 '[[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>',
190 '[[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>',
191 '[[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>',
192 '[[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>',
193 # striked through link
193 # striked through link
194 '-[[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>',
195 '-[[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>',
196 # escaping
196 # escaping
197 '![[Another page|Page]]' => '[[Another page|Page]]',
197 '![[Another page|Page]]' => '[[Another page|Page]]',
198 # project does not exist
199 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
200 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
198 }
201 }
199 @project = Project.find(1)
202 @project = Project.find(1)
200 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
203 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
201 end
204 end
202
205
203 def test_html_tags
206 def test_html_tags
204 to_test = {
207 to_test = {
205 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
208 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
206 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
209 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
207 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
210 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
208 # do not escape pre/code tags
211 # do not escape pre/code tags
209 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
212 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
210 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
213 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
211 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
214 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
212 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
215 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
213 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
216 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
214 # remove attributes except class
217 # remove attributes except class
215 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
218 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
216 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
219 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
217 }
220 }
218 to_test.each { |text, result| assert_equal result, textilizable(text) }
221 to_test.each { |text, result| assert_equal result, textilizable(text) }
219 end
222 end
220
223
221 def test_allowed_html_tags
224 def test_allowed_html_tags
222 to_test = {
225 to_test = {
223 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
226 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
224 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
227 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
225 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
228 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
226 }
229 }
227 to_test.each { |text, result| assert_equal result, textilizable(text) }
230 to_test.each { |text, result| assert_equal result, textilizable(text) }
228 end
231 end
229
232
230 def syntax_highlight
233 def syntax_highlight
231 raw = <<-RAW
234 raw = <<-RAW
232 <pre><code class="ruby">
235 <pre><code class="ruby">
233 # Some ruby code here
236 # Some ruby code here
234 </pre></code>
237 </pre></code>
235 RAW
238 RAW
236
239
237 expected = <<-EXPECTED
240 expected = <<-EXPECTED
238 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
241 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
239 </pre></code>
242 </pre></code>
240 EXPECTED
243 EXPECTED
241
244
242 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
245 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
243 end
246 end
244
247
245 def test_wiki_links_in_tables
248 def test_wiki_links_in_tables
246 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
249 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
247 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
250 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
248 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
251 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
249 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
252 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
250 }
253 }
251 @project = Project.find(1)
254 @project = Project.find(1)
252 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
255 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
253 end
256 end
254
257
255 def test_text_formatting
258 def test_text_formatting
256 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
259 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
257 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
260 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
258 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
261 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
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>',
262 '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>',
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',
263 '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',
261 }
264 }
262 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
265 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
263 end
266 end
264
267
265 def test_wiki_horizontal_rule
268 def test_wiki_horizontal_rule
266 assert_equal '<hr />', textilizable('---')
269 assert_equal '<hr />', textilizable('---')
267 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
270 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
268 end
271 end
269
272
270 def test_acronym
273 def test_acronym
271 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
274 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
272 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
275 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
273 end
276 end
274
277
275 def test_footnotes
278 def test_footnotes
276 raw = <<-RAW
279 raw = <<-RAW
277 This is some text[1].
280 This is some text[1].
278
281
279 fn1. This is the foot note
282 fn1. This is the foot note
280 RAW
283 RAW
281
284
282 expected = <<-EXPECTED
285 expected = <<-EXPECTED
283 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
286 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
284 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
287 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
285 EXPECTED
288 EXPECTED
286
289
287 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
290 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
288 end
291 end
289
292
290 def test_table_of_content
293 def test_table_of_content
291 raw = <<-RAW
294 raw = <<-RAW
292 {{toc}}
295 {{toc}}
293
296
294 h1. Title
297 h1. Title
295
298
296 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
299 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
297
300
298 h2. Subtitle with a [[Wiki]] link
301 h2. Subtitle with a [[Wiki]] link
299
302
300 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
303 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
301
304
302 h2. Subtitle with [[Wiki|another Wiki]] link
305 h2. Subtitle with [[Wiki|another Wiki]] link
303
306
304 h2. Subtitle with %{color:red}red text%
307 h2. Subtitle with %{color:red}red text%
305
308
306 h1. Another title
309 h1. Another title
307
310
308 RAW
311 RAW
309
312
310 expected = '<ul class="toc">' +
313 expected = '<ul class="toc">' +
311 '<li class="heading1"><a href="#Title">Title</a></li>' +
314 '<li class="heading1"><a href="#Title">Title</a></li>' +
312 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
315 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
313 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
316 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
314 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
317 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
315 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
318 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
316 '</ul>'
319 '</ul>'
317
320
318 assert textilizable(raw).gsub("\n", "").include?(expected)
321 assert textilizable(raw).gsub("\n", "").include?(expected)
319 end
322 end
320
323
321 def test_blockquote
324 def test_blockquote
322 # orig raw text
325 # orig raw text
323 raw = <<-RAW
326 raw = <<-RAW
324 John said:
327 John said:
325 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
328 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
326 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
329 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
327 > * Donec odio lorem,
330 > * Donec odio lorem,
328 > * sagittis ac,
331 > * sagittis ac,
329 > * malesuada in,
332 > * malesuada in,
330 > * adipiscing eu, dolor.
333 > * adipiscing eu, dolor.
331 >
334 >
332 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
335 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
333 > Proin a tellus. Nam vel neque.
336 > Proin a tellus. Nam vel neque.
334
337
335 He's right.
338 He's right.
336 RAW
339 RAW
337
340
338 # expected html
341 # expected html
339 expected = <<-EXPECTED
342 expected = <<-EXPECTED
340 <p>John said:</p>
343 <p>John said:</p>
341 <blockquote>
344 <blockquote>
342 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
345 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
343 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
346 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
344 <ul>
347 <ul>
345 <li>Donec odio lorem,</li>
348 <li>Donec odio lorem,</li>
346 <li>sagittis ac,</li>
349 <li>sagittis ac,</li>
347 <li>malesuada in,</li>
350 <li>malesuada in,</li>
348 <li>adipiscing eu, dolor.</li>
351 <li>adipiscing eu, dolor.</li>
349 </ul>
352 </ul>
350 <blockquote>
353 <blockquote>
351 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
354 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
352 </blockquote>
355 </blockquote>
353 <p>Proin a tellus. Nam vel neque.</p>
356 <p>Proin a tellus. Nam vel neque.</p>
354 </blockquote>
357 </blockquote>
355 <p>He's right.</p>
358 <p>He's right.</p>
356 EXPECTED
359 EXPECTED
357
360
358 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
361 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
359 end
362 end
360
363
361 def test_table
364 def test_table
362 raw = <<-RAW
365 raw = <<-RAW
363 This is a table with empty cells:
366 This is a table with empty cells:
364
367
365 |cell11|cell12||
368 |cell11|cell12||
366 |cell21||cell23|
369 |cell21||cell23|
367 |cell31|cell32|cell33|
370 |cell31|cell32|cell33|
368 RAW
371 RAW
369
372
370 expected = <<-EXPECTED
373 expected = <<-EXPECTED
371 <p>This is a table with empty cells:</p>
374 <p>This is a table with empty cells:</p>
372
375
373 <table>
376 <table>
374 <tr><td>cell11</td><td>cell12</td><td></td></tr>
377 <tr><td>cell11</td><td>cell12</td><td></td></tr>
375 <tr><td>cell21</td><td></td><td>cell23</td></tr>
378 <tr><td>cell21</td><td></td><td>cell23</td></tr>
376 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
379 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
377 </table>
380 </table>
378 EXPECTED
381 EXPECTED
379
382
380 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
383 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
381 end
384 end
382
385
383 def test_default_formatter
386 def test_default_formatter
384 Setting.text_formatting = 'unknown'
387 Setting.text_formatting = 'unknown'
385 text = 'a *link*: http://www.example.net/'
388 text = 'a *link*: http://www.example.net/'
386 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
389 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
387 Setting.text_formatting = 'textile'
390 Setting.text_formatting = 'textile'
388 end
391 end
389
392
390 def test_date_format_default
393 def test_date_format_default
391 today = Date.today
394 today = Date.today
392 Setting.date_format = ''
395 Setting.date_format = ''
393 assert_equal l_date(today), format_date(today)
396 assert_equal l_date(today), format_date(today)
394 end
397 end
395
398
396 def test_date_format
399 def test_date_format
397 today = Date.today
400 today = Date.today
398 Setting.date_format = '%d %m %Y'
401 Setting.date_format = '%d %m %Y'
399 assert_equal today.strftime('%d %m %Y'), format_date(today)
402 assert_equal today.strftime('%d %m %Y'), format_date(today)
400 end
403 end
401
404
402 def test_time_format_default
405 def test_time_format_default
403 now = Time.now
406 now = Time.now
404 Setting.date_format = ''
407 Setting.date_format = ''
405 Setting.time_format = ''
408 Setting.time_format = ''
406 assert_equal l_datetime(now), format_time(now)
409 assert_equal l_datetime(now), format_time(now)
407 assert_equal l_time(now), format_time(now, false)
410 assert_equal l_time(now), format_time(now, false)
408 end
411 end
409
412
410 def test_time_format
413 def test_time_format
411 now = Time.now
414 now = Time.now
412 Setting.date_format = '%d %m %Y'
415 Setting.date_format = '%d %m %Y'
413 Setting.time_format = '%H %M'
416 Setting.time_format = '%H %M'
414 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
417 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
415 assert_equal now.strftime('%H %M'), format_time(now, false)
418 assert_equal now.strftime('%H %M'), format_time(now, false)
416 end
419 end
417
420
418 def test_utc_time_format
421 def test_utc_time_format
419 now = Time.now.utc
422 now = Time.now.utc
420 Setting.date_format = '%d %m %Y'
423 Setting.date_format = '%d %m %Y'
421 Setting.time_format = '%H %M'
424 Setting.time_format = '%H %M'
422 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
425 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
423 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
426 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
424 end
427 end
425
428
426 def test_due_date_distance_in_words
429 def test_due_date_distance_in_words
427 to_test = { Date.today => 'Due in 0 days',
430 to_test = { Date.today => 'Due in 0 days',
428 Date.today + 1 => 'Due in 1 day',
431 Date.today + 1 => 'Due in 1 day',
429 Date.today + 100 => 'Due in 100 days',
432 Date.today + 100 => 'Due in 100 days',
430 Date.today + 20000 => 'Due in 20000 days',
433 Date.today + 20000 => 'Due in 20000 days',
431 Date.today - 1 => '1 day late',
434 Date.today - 1 => '1 day late',
432 Date.today - 100 => '100 days late',
435 Date.today - 100 => '100 days late',
433 Date.today - 20000 => '20000 days late',
436 Date.today - 20000 => '20000 days late',
434 }
437 }
435 to_test.each do |date, expected|
438 to_test.each do |date, expected|
436 assert_equal expected, due_date_distance_in_words(date)
439 assert_equal expected, due_date_distance_in_words(date)
437 end
440 end
438 end
441 end
439
442
440 def test_avatar
443 def test_avatar
441 # turn on avatars
444 # turn on avatars
442 Setting.gravatar_enabled = '1'
445 Setting.gravatar_enabled = '1'
443 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
446 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
444 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
447 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
445 assert_nil avatar('jsmith')
448 assert_nil avatar('jsmith')
446 assert_nil avatar(nil)
449 assert_nil avatar(nil)
447
450
448 # turn off avatars
451 # turn off avatars
449 Setting.gravatar_enabled = '0'
452 Setting.gravatar_enabled = '0'
450 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
453 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
451 end
454 end
452 end
455 end
General Comments 0
You need to be logged in to leave comments. Login now