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