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