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