##// END OF EJS Templates
Adds projects links (#4812)....
Jean-Philippe Lang -
r3308:3168ee3d47f5
parent child
Show More
@@ -1,734 +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)?((#|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, oid = $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(oid))
519 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
519 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
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 = oid.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'
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},
555 :class => 'project'
556 end
552 end
557 end
553 elsif sep == ':'
558 elsif sep == ':'
554 # removes the double quotes if any
559 # removes the double quotes if any
555 name = oid.gsub(%r{^"(.*)"$}, "\\1")
560 name = oid.gsub(%r{^"(.*)"$}, "\\1")
556 case prefix
561 case prefix
557 when 'document'
562 when 'document'
558 if project && document = project.documents.find_by_title(name)
563 if project && document = project.documents.find_by_title(name)
559 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},
560 :class => 'document'
565 :class => 'document'
561 end
566 end
562 when 'version'
567 when 'version'
563 if project && version = project.versions.find_by_name(name)
568 if project && version = project.versions.find_by_name(name)
564 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},
565 :class => 'version'
570 :class => 'version'
566 end
571 end
567 when 'commit'
572 when 'commit'
568 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
573 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
569 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},
570 :class => 'changeset',
575 :class => 'changeset',
571 :title => truncate_single_line(changeset.comments, :length => 100)
576 :title => truncate_single_line(changeset.comments, :length => 100)
572 end
577 end
573 when 'source', 'export'
578 when 'source', 'export'
574 if project && project.repository
579 if project && project.repository
575 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
580 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
576 path, rev, anchor = $1, $3, $5
581 path, rev, anchor = $1, $3, $5
577 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,
578 :path => to_path_param(path),
583 :path => to_path_param(path),
579 :rev => rev,
584 :rev => rev,
580 :anchor => anchor,
585 :anchor => anchor,
581 :format => (prefix == 'export' ? 'raw' : nil)},
586 :format => (prefix == 'export' ? 'raw' : nil)},
582 :class => (prefix == 'export' ? 'source download' : 'source')
587 :class => (prefix == 'export' ? 'source download' : 'source')
583 end
588 end
584 when 'attachment'
589 when 'attachment'
585 if attachments && attachment = attachments.detect {|a| a.filename == name }
590 if attachments && attachment = attachments.detect {|a| a.filename == name }
586 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},
587 :class => 'attachment'
592 :class => 'attachment'
588 end
593 end
594 when 'project'
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},
597 :class => 'project'
598 end
589 end
599 end
590 end
600 end
591 end
601 end
592 leading + (link || "#{prefix}#{sep}#{oid}")
602 leading + (link || "#{prefix}#{sep}#{oid}")
593 end
603 end
594
604
595 text
605 text
596 end
606 end
597
607
598 # Same as Rails' simple_format helper without using paragraphs
608 # Same as Rails' simple_format helper without using paragraphs
599 def simple_format_without_paragraph(text)
609 def simple_format_without_paragraph(text)
600 text.to_s.
610 text.to_s.
601 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
611 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
602 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
612 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
603 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
613 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
604 end
614 end
605
615
606 def lang_options_for_select(blank=true)
616 def lang_options_for_select(blank=true)
607 (blank ? [["(auto)", ""]] : []) +
617 (blank ? [["(auto)", ""]] : []) +
608 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 }
609 end
619 end
610
620
611 def label_tag_for(name, option_tags = nil, options = {})
621 def label_tag_for(name, option_tags = nil, options = {})
612 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"): "")
613 content_tag("label", label_text)
623 content_tag("label", label_text)
614 end
624 end
615
625
616 def labelled_tabular_form_for(name, object, options, &proc)
626 def labelled_tabular_form_for(name, object, options, &proc)
617 options[:html] ||= {}
627 options[:html] ||= {}
618 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
628 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
619 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)
620 end
630 end
621
631
622 def back_url_hidden_field_tag
632 def back_url_hidden_field_tag
623 back_url = params[:back_url] || request.env['HTTP_REFERER']
633 back_url = params[:back_url] || request.env['HTTP_REFERER']
624 back_url = CGI.unescape(back_url.to_s)
634 back_url = CGI.unescape(back_url.to_s)
625 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?
626 end
636 end
627
637
628 def check_all_links(form_name)
638 def check_all_links(form_name)
629 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
639 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
630 " | " +
640 " | " +
631 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
641 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
632 end
642 end
633
643
634 def progress_bar(pcts, options={})
644 def progress_bar(pcts, options={})
635 pcts = [pcts, pcts] unless pcts.is_a?(Array)
645 pcts = [pcts, pcts] unless pcts.is_a?(Array)
636 pcts = pcts.collect(&:round)
646 pcts = pcts.collect(&:round)
637 pcts[1] = pcts[1] - pcts[0]
647 pcts[1] = pcts[1] - pcts[0]
638 pcts << (100 - pcts[1] - pcts[0])
648 pcts << (100 - pcts[1] - pcts[0])
639 width = options[:width] || '100px;'
649 width = options[:width] || '100px;'
640 legend = options[:legend] || ''
650 legend = options[:legend] || ''
641 content_tag('table',
651 content_tag('table',
642 content_tag('tr',
652 content_tag('tr',
643 (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') : '') +
644 (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') : '') +
645 (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') : '')
646 ), :class => 'progress', :style => "width: #{width};") +
656 ), :class => 'progress', :style => "width: #{width};") +
647 content_tag('p', legend, :class => 'pourcent')
657 content_tag('p', legend, :class => 'pourcent')
648 end
658 end
649
659
650 def context_menu_link(name, url, options={})
660 def context_menu_link(name, url, options={})
651 options[:class] ||= ''
661 options[:class] ||= ''
652 if options.delete(:selected)
662 if options.delete(:selected)
653 options[:class] << ' icon-checked disabled'
663 options[:class] << ' icon-checked disabled'
654 options[:disabled] = true
664 options[:disabled] = true
655 end
665 end
656 if options.delete(:disabled)
666 if options.delete(:disabled)
657 options.delete(:method)
667 options.delete(:method)
658 options.delete(:confirm)
668 options.delete(:confirm)
659 options.delete(:onclick)
669 options.delete(:onclick)
660 options[:class] << ' disabled'
670 options[:class] << ' disabled'
661 url = '#'
671 url = '#'
662 end
672 end
663 link_to name, url, options
673 link_to name, url, options
664 end
674 end
665
675
666 def calendar_for(field_id)
676 def calendar_for(field_id)
667 include_calendar_headers_tags
677 include_calendar_headers_tags
668 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
678 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
669 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' });")
670 end
680 end
671
681
672 def include_calendar_headers_tags
682 def include_calendar_headers_tags
673 unless @calendar_headers_tags_included
683 unless @calendar_headers_tags_included
674 @calendar_headers_tags_included = true
684 @calendar_headers_tags_included = true
675 content_for :header_tags do
685 content_for :header_tags do
676 start_of_week = case Setting.start_of_week.to_i
686 start_of_week = case Setting.start_of_week.to_i
677 when 1
687 when 1
678 'Calendar._FD = 1;' # Monday
688 'Calendar._FD = 1;' # Monday
679 when 7
689 when 7
680 'Calendar._FD = 0;' # Sunday
690 'Calendar._FD = 0;' # Sunday
681 else
691 else
682 '' # use language
692 '' # use language
683 end
693 end
684
694
685 javascript_include_tag('calendar/calendar') +
695 javascript_include_tag('calendar/calendar') +
686 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") +
687 javascript_tag(start_of_week) +
697 javascript_tag(start_of_week) +
688 javascript_include_tag('calendar/calendar-setup') +
698 javascript_include_tag('calendar/calendar-setup') +
689 stylesheet_link_tag('calendar')
699 stylesheet_link_tag('calendar')
690 end
700 end
691 end
701 end
692 end
702 end
693
703
694 def content_for(name, content = nil, &block)
704 def content_for(name, content = nil, &block)
695 @has_content ||= {}
705 @has_content ||= {}
696 @has_content[name] = true
706 @has_content[name] = true
697 super(name, content, &block)
707 super(name, content, &block)
698 end
708 end
699
709
700 def has_content?(name)
710 def has_content?(name)
701 (@has_content && @has_content[name]) || false
711 (@has_content && @has_content[name]) || false
702 end
712 end
703
713
704 # 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
705 # +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>')
706 def avatar(user, options = { })
716 def avatar(user, options = { })
707 if Setting.gravatar_enabled?
717 if Setting.gravatar_enabled?
708 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
718 options.merge!({:ssl => Setting.protocol == 'https', :default => Setting.gravatar_default})
709 email = nil
719 email = nil
710 if user.respond_to?(:mail)
720 if user.respond_to?(:mail)
711 email = user.mail
721 email = user.mail
712 elsif user.to_s =~ %r{<(.+?)>}
722 elsif user.to_s =~ %r{<(.+?)>}
713 email = $1
723 email = $1
714 end
724 end
715 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
716 end
726 end
717 end
727 end
718
728
719 private
729 private
720
730
721 def wiki_helper
731 def wiki_helper
722 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
732 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
723 extend helper
733 extend helper
724 return self
734 return self
725 end
735 end
726
736
727 def link_to_remote_content_update(text, url_params)
737 def link_to_remote_content_update(text, url_params)
728 link_to_remote(text,
738 link_to_remote(text,
729 {: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)'},
730 {:href => url_for(:params => url_params)}
740 {:href => url_for(:params => url_params)}
731 )
741 )
732 end
742 end
733
743
734 end
744 end
@@ -1,532 +1,539
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
32
32 def setup
33 def setup
33 super
34 super
34 end
35 end
35
36
36 def test_auto_links
37 def test_auto_links
37 to_test = {
38 to_test = {
38 '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>',
39 '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>',
40 '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>.',
41 '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>.',
42 '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>.',
43 '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>).',
44 '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>.',
45 '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>',
46 '(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>)',
47 '(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>)',
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_(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>)',
50 '(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>)',
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 '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>',
53 '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>',
54 '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>',
55 '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>',
56 '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>',
57 '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>',
58 '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>',
59 '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>',
60 # two exclamation marks
61 # two exclamation marks
61 '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>',
62 }
63 }
63 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) }
64 end
65 end
65
66
66 def test_auto_mailto
67 def test_auto_mailto
67 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>',
68 textilizable('test@foo.bar')
69 textilizable('test@foo.bar')
69 end
70 end
70
71
71 def test_inline_images
72 def test_inline_images
72 to_test = {
73 to_test = {
73 '!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="" />',
74 '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>',
75 '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="" />',
76 # inline styles should be stripped
77 # inline styles should be stripped
77 '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="" />',
78 '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" />',
79 '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;" />',
80 }
81 }
81 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) }
82 end
83 end
83
84
84 def test_inline_images_inside_tags
85 def test_inline_images_inside_tags
85 raw = <<-RAW
86 raw = <<-RAW
86 h1. !foo.png! Heading
87 h1. !foo.png! Heading
87
88
88 Centered image:
89 Centered image:
89
90
90 p=. !bar.gif!
91 p=. !bar.gif!
91 RAW
92 RAW
92
93
93 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
94 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
94 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
95 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
95 end
96 end
96
97
97 def test_acronyms
98 def test_acronyms
98 to_test = {
99 to_test = {
99 '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>',
100 '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>',
101 }
102 }
102 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) }
103
104
104 end
105 end
105
106
106 def test_attached_images
107 def test_attached_images
107 to_test = {
108 to_test = {
108 '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" />',
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 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
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 # link image
113 # link image
113 '!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>',
114 }
115 }
115 attachments = Attachment.find(:all)
116 attachments = Attachment.find(:all)
116 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) }
117 end
118 end
118
119
119 def test_textile_external_links
120 def test_textile_external_links
120 to_test = {
121 to_test = {
121 '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>',
122 '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>',
123 '"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>',
124 '"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>',
125 "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",
126 # no multiline link text
127 # no multiline link text
127 "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",
128 # mailto link
129 # mailto link
129 "\"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>",
130 # two exclamation marks
131 # two exclamation marks
131 '"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>',
132 }
133 }
133 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) }
134 end
135 end
135
136
136 def test_redmine_links
137 def test_redmine_links
137 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
138 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
138 :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)')
139
140
140 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},
141 :class => 'changeset', :title => 'My very first commit')
142 :class => 'changeset', :title => 'My very first commit')
142 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},
143 :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')
144
145
145 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},
146 :class => 'document')
147 :class => 'document')
147
148
148 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},
149 :class => 'version')
150 :class => 'version')
150
151
151 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
152 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
152
153
154 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
155
153 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
156 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
154 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']}
155
158
156 to_test = {
159 to_test = {
157 # tickets
160 # tickets
158 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
161 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
159 # changesets
162 # changesets
160 'r1' => changeset_link,
163 'r1' => changeset_link,
161 'r1.' => "#{changeset_link}.",
164 'r1.' => "#{changeset_link}.",
162 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
165 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
163 'r1,r2' => "#{changeset_link},#{changeset_link2}",
166 'r1,r2' => "#{changeset_link},#{changeset_link2}",
164 # documents
167 # documents
165 'document#1' => document_link,
168 'document#1' => document_link,
166 'document:"Test document"' => document_link,
169 'document:"Test document"' => document_link,
167 # versions
170 # versions
168 'version#2' => version_link,
171 'version#2' => version_link,
169 'version:1.0' => version_link,
172 'version:1.0' => version_link,
170 'version:"1.0"' => version_link,
173 'version:"1.0"' => version_link,
171 # source
174 # source
172 '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'),
173 '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') + ".",
174 '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') + ".",
175 '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') + ".",
176 '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') + ".",
177 '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') + ",",
178 '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'),
179 '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'),
180 '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'),
181 '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'),
182 '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'),
183 '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'),
184 # message
187 # message
185 'message#4' => link_to('Post 2', message_url, :class => 'message'),
188 'message#4' => link_to('Post 2', message_url, :class => 'message'),
186 '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
191 'project#3' => 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'),
187 # escaping
194 # escaping
188 '!#3.' => '#3.',
195 '!#3.' => '#3.',
189 '!r1' => 'r1',
196 '!r1' => 'r1',
190 '!document#1' => 'document#1',
197 '!document#1' => 'document#1',
191 '!document:"Test document"' => 'document:"Test document"',
198 '!document:"Test document"' => 'document:"Test document"',
192 '!version#2' => 'version#2',
199 '!version#2' => 'version#2',
193 '!version:1.0' => 'version:1.0',
200 '!version:1.0' => 'version:1.0',
194 '!version:"1.0"' => 'version:"1.0"',
201 '!version:"1.0"' => 'version:"1.0"',
195 '!source:/some/file' => 'source:/some/file',
202 '!source:/some/file' => 'source:/some/file',
196 # invalid expressions
203 # invalid expressions
197 'source:' => 'source:',
204 'source:' => 'source:',
198 # url hash
205 # url hash
199 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
206 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
200 }
207 }
201 @project = Project.find(1)
208 @project = Project.find(1)
202 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
209 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
203 end
210 end
204
211
205 def test_wiki_links
212 def test_wiki_links
206 to_test = {
213 to_test = {
207 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
214 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
208 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
215 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
209 # link with anchor
216 # link with anchor
210 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
217 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
211 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
218 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
212 # page that doesn't exist
219 # page that doesn't exist
213 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
220 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
214 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
221 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
215 # link to another project wiki
222 # link to another project wiki
216 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
223 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">onlinestore</a>',
217 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
224 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki/" class="wiki-page">Wiki</a>',
218 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
225 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
219 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
226 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
220 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
227 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
221 # striked through link
228 # striked through link
222 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
229 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
223 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
230 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
224 # escaping
231 # escaping
225 '![[Another page|Page]]' => '[[Another page|Page]]',
232 '![[Another page|Page]]' => '[[Another page|Page]]',
226 # project does not exist
233 # project does not exist
227 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
234 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
228 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
235 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
229 }
236 }
230 @project = Project.find(1)
237 @project = Project.find(1)
231 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
238 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
232 end
239 end
233
240
234 def test_html_tags
241 def test_html_tags
235 to_test = {
242 to_test = {
236 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
243 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
237 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
244 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
238 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
245 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
239 # do not escape pre/code tags
246 # do not escape pre/code tags
240 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
247 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
241 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
248 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
242 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
249 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
243 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
250 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
244 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
251 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
245 # remove attributes except class
252 # remove attributes except class
246 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
253 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
247 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
254 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
248 }
255 }
249 to_test.each { |text, result| assert_equal result, textilizable(text) }
256 to_test.each { |text, result| assert_equal result, textilizable(text) }
250 end
257 end
251
258
252 def test_allowed_html_tags
259 def test_allowed_html_tags
253 to_test = {
260 to_test = {
254 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
261 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
255 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
262 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
256 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
263 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
257 }
264 }
258 to_test.each { |text, result| assert_equal result, textilizable(text) }
265 to_test.each { |text, result| assert_equal result, textilizable(text) }
259 end
266 end
260
267
261 def test_pre_tags
268 def test_pre_tags
262 raw = <<-RAW
269 raw = <<-RAW
263 Before
270 Before
264
271
265 <pre>
272 <pre>
266 <prepared-statement-cache-size>32</prepared-statement-cache-size>
273 <prepared-statement-cache-size>32</prepared-statement-cache-size>
267 </pre>
274 </pre>
268
275
269 After
276 After
270 RAW
277 RAW
271
278
272 expected = <<-EXPECTED
279 expected = <<-EXPECTED
273 <p>Before</p>
280 <p>Before</p>
274 <pre>
281 <pre>
275 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
282 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
276 </pre>
283 </pre>
277 <p>After</p>
284 <p>After</p>
278 EXPECTED
285 EXPECTED
279
286
280 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
287 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
281 end
288 end
282
289
283 def test_syntax_highlight
290 def test_syntax_highlight
284 raw = <<-RAW
291 raw = <<-RAW
285 <pre><code class="ruby">
292 <pre><code class="ruby">
286 # Some ruby code here
293 # Some ruby code here
287 </pre></code>
294 </pre></code>
288 RAW
295 RAW
289
296
290 expected = <<-EXPECTED
297 expected = <<-EXPECTED
291 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
298 <pre><code class="ruby CodeRay"><span class="no">1</span> <span class="c"># Some ruby code here</span>
292 </pre></code>
299 </pre></code>
293 EXPECTED
300 EXPECTED
294
301
295 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
302 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
296 end
303 end
297
304
298 def test_wiki_links_in_tables
305 def test_wiki_links_in_tables
299 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
306 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
300 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
307 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
301 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
308 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
302 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
309 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
303 }
310 }
304 @project = Project.find(1)
311 @project = Project.find(1)
305 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
312 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
306 end
313 end
307
314
308 def test_text_formatting
315 def test_text_formatting
309 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
316 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
310 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
317 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
311 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
318 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
312 '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>',
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>',
313 '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',
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',
314 }
321 }
315 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
322 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
316 end
323 end
317
324
318 def test_wiki_horizontal_rule
325 def test_wiki_horizontal_rule
319 assert_equal '<hr />', textilizable('---')
326 assert_equal '<hr />', textilizable('---')
320 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
327 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
321 end
328 end
322
329
323 def test_acronym
330 def test_acronym
324 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
331 assert_equal '<p>This is an acronym: <acronym title="American Civil Liberties Union">ACLU</acronym>.</p>',
325 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
332 textilizable('This is an acronym: ACLU(American Civil Liberties Union).')
326 end
333 end
327
334
328 def test_footnotes
335 def test_footnotes
329 raw = <<-RAW
336 raw = <<-RAW
330 This is some text[1].
337 This is some text[1].
331
338
332 fn1. This is the foot note
339 fn1. This is the foot note
333 RAW
340 RAW
334
341
335 expected = <<-EXPECTED
342 expected = <<-EXPECTED
336 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
343 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
337 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
344 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
338 EXPECTED
345 EXPECTED
339
346
340 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
347 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
341 end
348 end
342
349
343 def test_table_of_content
350 def test_table_of_content
344 raw = <<-RAW
351 raw = <<-RAW
345 {{toc}}
352 {{toc}}
346
353
347 h1. Title
354 h1. Title
348
355
349 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
356 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
350
357
351 h2. Subtitle with a [[Wiki]] link
358 h2. Subtitle with a [[Wiki]] link
352
359
353 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
360 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
354
361
355 h2. Subtitle with [[Wiki|another Wiki]] link
362 h2. Subtitle with [[Wiki|another Wiki]] link
356
363
357 h2. Subtitle with %{color:red}red text%
364 h2. Subtitle with %{color:red}red text%
358
365
359 h1. Another title
366 h1. Another title
360
367
361 RAW
368 RAW
362
369
363 expected = '<ul class="toc">' +
370 expected = '<ul class="toc">' +
364 '<li class="heading1"><a href="#Title">Title</a></li>' +
371 '<li class="heading1"><a href="#Title">Title</a></li>' +
365 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
372 '<li class="heading2"><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
366 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
373 '<li class="heading2"><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
367 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
374 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
368 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
375 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
369 '</ul>'
376 '</ul>'
370
377
371 assert textilizable(raw).gsub("\n", "").include?(expected)
378 assert textilizable(raw).gsub("\n", "").include?(expected)
372 end
379 end
373
380
374 def test_blockquote
381 def test_blockquote
375 # orig raw text
382 # orig raw text
376 raw = <<-RAW
383 raw = <<-RAW
377 John said:
384 John said:
378 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
385 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
379 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
386 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
380 > * Donec odio lorem,
387 > * Donec odio lorem,
381 > * sagittis ac,
388 > * sagittis ac,
382 > * malesuada in,
389 > * malesuada in,
383 > * adipiscing eu, dolor.
390 > * adipiscing eu, dolor.
384 >
391 >
385 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
392 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
386 > Proin a tellus. Nam vel neque.
393 > Proin a tellus. Nam vel neque.
387
394
388 He's right.
395 He's right.
389 RAW
396 RAW
390
397
391 # expected html
398 # expected html
392 expected = <<-EXPECTED
399 expected = <<-EXPECTED
393 <p>John said:</p>
400 <p>John said:</p>
394 <blockquote>
401 <blockquote>
395 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
402 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
396 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
403 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
397 <ul>
404 <ul>
398 <li>Donec odio lorem,</li>
405 <li>Donec odio lorem,</li>
399 <li>sagittis ac,</li>
406 <li>sagittis ac,</li>
400 <li>malesuada in,</li>
407 <li>malesuada in,</li>
401 <li>adipiscing eu, dolor.</li>
408 <li>adipiscing eu, dolor.</li>
402 </ul>
409 </ul>
403 <blockquote>
410 <blockquote>
404 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
411 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
405 </blockquote>
412 </blockquote>
406 <p>Proin a tellus. Nam vel neque.</p>
413 <p>Proin a tellus. Nam vel neque.</p>
407 </blockquote>
414 </blockquote>
408 <p>He's right.</p>
415 <p>He's right.</p>
409 EXPECTED
416 EXPECTED
410
417
411 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
418 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
412 end
419 end
413
420
414 def test_table
421 def test_table
415 raw = <<-RAW
422 raw = <<-RAW
416 This is a table with empty cells:
423 This is a table with empty cells:
417
424
418 |cell11|cell12||
425 |cell11|cell12||
419 |cell21||cell23|
426 |cell21||cell23|
420 |cell31|cell32|cell33|
427 |cell31|cell32|cell33|
421 RAW
428 RAW
422
429
423 expected = <<-EXPECTED
430 expected = <<-EXPECTED
424 <p>This is a table with empty cells:</p>
431 <p>This is a table with empty cells:</p>
425
432
426 <table>
433 <table>
427 <tr><td>cell11</td><td>cell12</td><td></td></tr>
434 <tr><td>cell11</td><td>cell12</td><td></td></tr>
428 <tr><td>cell21</td><td></td><td>cell23</td></tr>
435 <tr><td>cell21</td><td></td><td>cell23</td></tr>
429 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
436 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
430 </table>
437 </table>
431 EXPECTED
438 EXPECTED
432
439
433 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
440 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
434 end
441 end
435
442
436 def test_table_with_line_breaks
443 def test_table_with_line_breaks
437 raw = <<-RAW
444 raw = <<-RAW
438 This is a table with line breaks:
445 This is a table with line breaks:
439
446
440 |cell11
447 |cell11
441 continued|cell12||
448 continued|cell12||
442 |-cell21-||cell23
449 |-cell21-||cell23
443 cell23 line2
450 cell23 line2
444 cell23 *line3*|
451 cell23 *line3*|
445 |cell31|cell32
452 |cell31|cell32
446 cell32 line2|cell33|
453 cell32 line2|cell33|
447
454
448 RAW
455 RAW
449
456
450 expected = <<-EXPECTED
457 expected = <<-EXPECTED
451 <p>This is a table with line breaks:</p>
458 <p>This is a table with line breaks:</p>
452
459
453 <table>
460 <table>
454 <tr>
461 <tr>
455 <td>cell11<br />continued</td>
462 <td>cell11<br />continued</td>
456 <td>cell12</td>
463 <td>cell12</td>
457 <td></td>
464 <td></td>
458 </tr>
465 </tr>
459 <tr>
466 <tr>
460 <td><del>cell21</del></td>
467 <td><del>cell21</del></td>
461 <td></td>
468 <td></td>
462 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
469 <td>cell23<br/>cell23 line2<br/>cell23 <strong>line3</strong></td>
463 </tr>
470 </tr>
464 <tr>
471 <tr>
465 <td>cell31</td>
472 <td>cell31</td>
466 <td>cell32<br/>cell32 line2</td>
473 <td>cell32<br/>cell32 line2</td>
467 <td>cell33</td>
474 <td>cell33</td>
468 </tr>
475 </tr>
469 </table>
476 </table>
470 EXPECTED
477 EXPECTED
471
478
472 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
479 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
473 end
480 end
474
481
475 def test_textile_should_not_mangle_brackets
482 def test_textile_should_not_mangle_brackets
476 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
483 assert_equal '<p>[msg1][msg2]</p>', textilizable('[msg1][msg2]')
477 end
484 end
478
485
479 def test_default_formatter
486 def test_default_formatter
480 Setting.text_formatting = 'unknown'
487 Setting.text_formatting = 'unknown'
481 text = 'a *link*: http://www.example.net/'
488 text = 'a *link*: http://www.example.net/'
482 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
489 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
483 Setting.text_formatting = 'textile'
490 Setting.text_formatting = 'textile'
484 end
491 end
485
492
486 def test_due_date_distance_in_words
493 def test_due_date_distance_in_words
487 to_test = { Date.today => 'Due in 0 days',
494 to_test = { Date.today => 'Due in 0 days',
488 Date.today + 1 => 'Due in 1 day',
495 Date.today + 1 => 'Due in 1 day',
489 Date.today + 100 => 'Due in about 3 months',
496 Date.today + 100 => 'Due in about 3 months',
490 Date.today + 20000 => 'Due in over 54 years',
497 Date.today + 20000 => 'Due in over 54 years',
491 Date.today - 1 => '1 day late',
498 Date.today - 1 => '1 day late',
492 Date.today - 100 => 'about 3 months late',
499 Date.today - 100 => 'about 3 months late',
493 Date.today - 20000 => 'over 54 years late',
500 Date.today - 20000 => 'over 54 years late',
494 }
501 }
495 to_test.each do |date, expected|
502 to_test.each do |date, expected|
496 assert_equal expected, due_date_distance_in_words(date)
503 assert_equal expected, due_date_distance_in_words(date)
497 end
504 end
498 end
505 end
499
506
500 def test_avatar
507 def test_avatar
501 # turn on avatars
508 # turn on avatars
502 Setting.gravatar_enabled = '1'
509 Setting.gravatar_enabled = '1'
503 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
510 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
504 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
511 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
505 assert_nil avatar('jsmith')
512 assert_nil avatar('jsmith')
506 assert_nil avatar(nil)
513 assert_nil avatar(nil)
507
514
508 # turn off avatars
515 # turn off avatars
509 Setting.gravatar_enabled = '0'
516 Setting.gravatar_enabled = '0'
510 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
517 assert_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
511 end
518 end
512
519
513 def test_link_to_user
520 def test_link_to_user
514 user = User.find(2)
521 user = User.find(2)
515 t = link_to_user(user)
522 t = link_to_user(user)
516 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
523 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
517 end
524 end
518
525
519 def test_link_to_user_should_not_link_to_locked_user
526 def test_link_to_user_should_not_link_to_locked_user
520 user = User.find(5)
527 user = User.find(5)
521 assert user.locked?
528 assert user.locked?
522 t = link_to_user(user)
529 t = link_to_user(user)
523 assert_equal user.name, t
530 assert_equal user.name, t
524 end
531 end
525
532
526 def test_link_to_user_should_not_link_to_anonymous
533 def test_link_to_user_should_not_link_to_anonymous
527 user = User.anonymous
534 user = User.anonymous
528 assert user.anonymous?
535 assert user.anonymous?
529 t = link_to_user(user)
536 t = link_to_user(user)
530 assert_equal ::I18n.t(:label_user_anonymous), t
537 assert_equal ::I18n.t(:label_user_anonymous), t
531 end
538 end
532 end
539 end
General Comments 0
You need to be logged in to leave comments. Login now