##// END OF EJS Templates
Merged r4257 from trunk....
Eric Davis -
r4207:79a0d9242180
parent child
Show More
@@ -1,857 +1,843
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 # Generates a link to a project if active
111 # Generates a link to a project if active
126 # Examples:
112 # Examples:
127 #
113 #
128 # link_to_project(project) # => link to the specified project overview
114 # link_to_project(project) # => link to the specified project overview
129 # link_to_project(project, :action=>'settings') # => link to project settings
115 # link_to_project(project, :action=>'settings') # => link to project settings
130 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
116 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
131 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
117 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
132 #
118 #
133 def link_to_project(project, options={}, html_options = nil)
119 def link_to_project(project, options={}, html_options = nil)
134 if project.active?
120 if project.active?
135 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
121 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
136 link_to(h(project), url, html_options)
122 link_to(h(project), url, html_options)
137 else
123 else
138 h(project)
124 h(project)
139 end
125 end
140 end
126 end
141
127
142 def toggle_link(name, id, options={})
128 def toggle_link(name, id, options={})
143 onclick = "Element.toggle('#{id}'); "
129 onclick = "Element.toggle('#{id}'); "
144 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
130 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
145 onclick << "return false;"
131 onclick << "return false;"
146 link_to(name, "#", :onclick => onclick)
132 link_to(name, "#", :onclick => onclick)
147 end
133 end
148
134
149 def image_to_function(name, function, html_options = {})
135 def image_to_function(name, function, html_options = {})
150 html_options.symbolize_keys!
136 html_options.symbolize_keys!
151 tag(:input, html_options.merge({
137 tag(:input, html_options.merge({
152 :type => "image", :src => image_path(name),
138 :type => "image", :src => image_path(name),
153 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
139 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
154 }))
140 }))
155 end
141 end
156
142
157 def prompt_to_remote(name, text, param, url, html_options = {})
143 def prompt_to_remote(name, text, param, url, html_options = {})
158 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
144 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
159 link_to name, {}, html_options
145 link_to name, {}, html_options
160 end
146 end
161
147
162 def format_activity_title(text)
148 def format_activity_title(text)
163 h(truncate_single_line(text, :length => 100))
149 h(truncate_single_line(text, :length => 100))
164 end
150 end
165
151
166 def format_activity_day(date)
152 def format_activity_day(date)
167 date == Date.today ? l(:label_today).titleize : format_date(date)
153 date == Date.today ? l(:label_today).titleize : format_date(date)
168 end
154 end
169
155
170 def format_activity_description(text)
156 def format_activity_description(text)
171 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
157 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')).gsub(/[\r\n]+/, "<br />")
172 end
158 end
173
159
174 def format_version_name(version)
160 def format_version_name(version)
175 if version.project == @project
161 if version.project == @project
176 h(version)
162 h(version)
177 else
163 else
178 h("#{version.project} - #{version}")
164 h("#{version.project} - #{version}")
179 end
165 end
180 end
166 end
181
167
182 def due_date_distance_in_words(date)
168 def due_date_distance_in_words(date)
183 if date
169 if date
184 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
170 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
185 end
171 end
186 end
172 end
187
173
188 def render_page_hierarchy(pages, node=nil)
174 def render_page_hierarchy(pages, node=nil)
189 content = ''
175 content = ''
190 if pages[node]
176 if pages[node]
191 content << "<ul class=\"pages-hierarchy\">\n"
177 content << "<ul class=\"pages-hierarchy\">\n"
192 pages[node].each do |page|
178 pages[node].each do |page|
193 content << "<li>"
179 content << "<li>"
194 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
180 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'index', :id => page.project, :page => page.title},
195 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
181 :title => (page.respond_to?(:updated_on) ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
196 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
182 content << "\n" + render_page_hierarchy(pages, page.id) if pages[page.id]
197 content << "</li>\n"
183 content << "</li>\n"
198 end
184 end
199 content << "</ul>\n"
185 content << "</ul>\n"
200 end
186 end
201 content
187 content
202 end
188 end
203
189
204 # Renders flash messages
190 # Renders flash messages
205 def render_flash_messages
191 def render_flash_messages
206 s = ''
192 s = ''
207 flash.each do |k,v|
193 flash.each do |k,v|
208 s << content_tag('div', v, :class => "flash #{k}")
194 s << content_tag('div', v, :class => "flash #{k}")
209 end
195 end
210 s
196 s
211 end
197 end
212
198
213 # Renders tabs and their content
199 # Renders tabs and their content
214 def render_tabs(tabs)
200 def render_tabs(tabs)
215 if tabs.any?
201 if tabs.any?
216 render :partial => 'common/tabs', :locals => {:tabs => tabs}
202 render :partial => 'common/tabs', :locals => {:tabs => tabs}
217 else
203 else
218 content_tag 'p', l(:label_no_data), :class => "nodata"
204 content_tag 'p', l(:label_no_data), :class => "nodata"
219 end
205 end
220 end
206 end
221
207
222 # Renders the project quick-jump box
208 # Renders the project quick-jump box
223 def render_project_jump_box
209 def render_project_jump_box
224 # Retrieve them now to avoid a COUNT query
210 # Retrieve them now to avoid a COUNT query
225 projects = User.current.projects.all
211 projects = User.current.projects.all
226 if projects.any?
212 if projects.any?
227 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
213 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
228 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
214 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
229 '<option value="" disabled="disabled">---</option>'
215 '<option value="" disabled="disabled">---</option>'
230 s << project_tree_options_for_select(projects, :selected => @project) do |p|
216 s << project_tree_options_for_select(projects, :selected => @project) do |p|
231 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
217 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
232 end
218 end
233 s << '</select>'
219 s << '</select>'
234 s
220 s
235 end
221 end
236 end
222 end
237
223
238 def project_tree_options_for_select(projects, options = {})
224 def project_tree_options_for_select(projects, options = {})
239 s = ''
225 s = ''
240 project_tree(projects) do |project, level|
226 project_tree(projects) do |project, level|
241 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
227 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
242 tag_options = {:value => project.id}
228 tag_options = {:value => project.id}
243 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
229 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
244 tag_options[:selected] = 'selected'
230 tag_options[:selected] = 'selected'
245 else
231 else
246 tag_options[:selected] = nil
232 tag_options[:selected] = nil
247 end
233 end
248 tag_options.merge!(yield(project)) if block_given?
234 tag_options.merge!(yield(project)) if block_given?
249 s << content_tag('option', name_prefix + h(project), tag_options)
235 s << content_tag('option', name_prefix + h(project), tag_options)
250 end
236 end
251 s
237 s
252 end
238 end
253
239
254 # Yields the given block for each project with its level in the tree
240 # Yields the given block for each project with its level in the tree
255 def project_tree(projects, &block)
241 def project_tree(projects, &block)
256 ancestors = []
242 ancestors = []
257 projects.sort_by(&:lft).each do |project|
243 projects.sort_by(&:lft).each do |project|
258 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
244 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
259 ancestors.pop
245 ancestors.pop
260 end
246 end
261 yield project, ancestors.size
247 yield project, ancestors.size
262 ancestors << project
248 ancestors << project
263 end
249 end
264 end
250 end
265
251
266 def project_nested_ul(projects, &block)
252 def project_nested_ul(projects, &block)
267 s = ''
253 s = ''
268 if projects.any?
254 if projects.any?
269 ancestors = []
255 ancestors = []
270 projects.sort_by(&:lft).each do |project|
256 projects.sort_by(&:lft).each do |project|
271 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
257 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
272 s << "<ul>\n"
258 s << "<ul>\n"
273 else
259 else
274 ancestors.pop
260 ancestors.pop
275 s << "</li>"
261 s << "</li>"
276 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
262 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
277 ancestors.pop
263 ancestors.pop
278 s << "</ul></li>\n"
264 s << "</ul></li>\n"
279 end
265 end
280 end
266 end
281 s << "<li>"
267 s << "<li>"
282 s << yield(project).to_s
268 s << yield(project).to_s
283 ancestors << project
269 ancestors << project
284 end
270 end
285 s << ("</li></ul>\n" * ancestors.size)
271 s << ("</li></ul>\n" * ancestors.size)
286 end
272 end
287 s
273 s
288 end
274 end
289
275
290 def principals_check_box_tags(name, principals)
276 def principals_check_box_tags(name, principals)
291 s = ''
277 s = ''
292 principals.sort.each do |principal|
278 principals.sort.each do |principal|
293 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
279 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
294 end
280 end
295 s
281 s
296 end
282 end
297
283
298 # Truncates and returns the string as a single line
284 # Truncates and returns the string as a single line
299 def truncate_single_line(string, *args)
285 def truncate_single_line(string, *args)
300 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
286 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
301 end
287 end
302
288
303 # Truncates at line break after 250 characters or options[:length]
289 # Truncates at line break after 250 characters or options[:length]
304 def truncate_lines(string, options={})
290 def truncate_lines(string, options={})
305 length = options[:length] || 250
291 length = options[:length] || 250
306 if string.to_s =~ /\A(.{#{length}}.*?)$/m
292 if string.to_s =~ /\A(.{#{length}}.*?)$/m
307 "#{$1}..."
293 "#{$1}..."
308 else
294 else
309 string
295 string
310 end
296 end
311 end
297 end
312
298
313 def html_hours(text)
299 def html_hours(text)
314 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
300 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
315 end
301 end
316
302
317 def authoring(created, author, options={})
303 def authoring(created, author, options={})
318 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
304 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created))
319 end
305 end
320
306
321 def time_tag(time)
307 def time_tag(time)
322 text = distance_of_time_in_words(Time.now, time)
308 text = distance_of_time_in_words(Time.now, time)
323 if @project
309 if @project
324 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
310 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
325 else
311 else
326 content_tag('acronym', text, :title => format_time(time))
312 content_tag('acronym', text, :title => format_time(time))
327 end
313 end
328 end
314 end
329
315
330 def syntax_highlight(name, content)
316 def syntax_highlight(name, content)
331 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
317 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
332 end
318 end
333
319
334 def to_path_param(path)
320 def to_path_param(path)
335 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
321 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
336 end
322 end
337
323
338 def pagination_links_full(paginator, count=nil, options={})
324 def pagination_links_full(paginator, count=nil, options={})
339 page_param = options.delete(:page_param) || :page
325 page_param = options.delete(:page_param) || :page
340 per_page_links = options.delete(:per_page_links)
326 per_page_links = options.delete(:per_page_links)
341 url_param = params.dup
327 url_param = params.dup
342 # don't reuse query params if filters are present
328 # don't reuse query params if filters are present
343 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
329 url_param.merge!(:fields => nil, :values => nil, :operators => nil) if url_param.delete(:set_filter)
344
330
345 html = ''
331 html = ''
346 if paginator.current.previous
332 if paginator.current.previous
347 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
333 html << link_to_remote_content_update('&#171; ' + l(:label_previous), url_param.merge(page_param => paginator.current.previous)) + ' '
348 end
334 end
349
335
350 html << (pagination_links_each(paginator, options) do |n|
336 html << (pagination_links_each(paginator, options) do |n|
351 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
337 link_to_remote_content_update(n.to_s, url_param.merge(page_param => n))
352 end || '')
338 end || '')
353
339
354 if paginator.current.next
340 if paginator.current.next
355 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
341 html << ' ' + link_to_remote_content_update((l(:label_next) + ' &#187;'), url_param.merge(page_param => paginator.current.next))
356 end
342 end
357
343
358 unless count.nil?
344 unless count.nil?
359 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
345 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
360 if per_page_links != false && links = per_page_links(paginator.items_per_page)
346 if per_page_links != false && links = per_page_links(paginator.items_per_page)
361 html << " | #{links}"
347 html << " | #{links}"
362 end
348 end
363 end
349 end
364
350
365 html
351 html
366 end
352 end
367
353
368 def per_page_links(selected=nil)
354 def per_page_links(selected=nil)
369 url_param = params.dup
355 url_param = params.dup
370 url_param.clear if url_param.has_key?(:set_filter)
356 url_param.clear if url_param.has_key?(:set_filter)
371
357
372 links = Setting.per_page_options_array.collect do |n|
358 links = Setting.per_page_options_array.collect do |n|
373 n == selected ? n : link_to_remote(n, {:update => "content",
359 n == selected ? n : link_to_remote(n, {:update => "content",
374 :url => params.dup.merge(:per_page => n),
360 :url => params.dup.merge(:per_page => n),
375 :method => :get},
361 :method => :get},
376 {:href => url_for(url_param.merge(:per_page => n))})
362 {:href => url_for(url_param.merge(:per_page => n))})
377 end
363 end
378 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
364 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
379 end
365 end
380
366
381 def reorder_links(name, url)
367 def reorder_links(name, url)
382 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
368 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)), url.merge({"#{name}[move_to]" => 'highest'}), :method => :post, :title => l(:label_sort_highest)) +
383 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
369 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)), url.merge({"#{name}[move_to]" => 'higher'}), :method => :post, :title => l(:label_sort_higher)) +
384 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
370 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)), url.merge({"#{name}[move_to]" => 'lower'}), :method => :post, :title => l(:label_sort_lower)) +
385 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
371 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)), url.merge({"#{name}[move_to]" => 'lowest'}), :method => :post, :title => l(:label_sort_lowest))
386 end
372 end
387
373
388 def breadcrumb(*args)
374 def breadcrumb(*args)
389 elements = args.flatten
375 elements = args.flatten
390 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
376 elements.any? ? content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb') : nil
391 end
377 end
392
378
393 def other_formats_links(&block)
379 def other_formats_links(&block)
394 concat('<p class="other-formats">' + l(:label_export_to))
380 concat('<p class="other-formats">' + l(:label_export_to))
395 yield Redmine::Views::OtherFormatsBuilder.new(self)
381 yield Redmine::Views::OtherFormatsBuilder.new(self)
396 concat('</p>')
382 concat('</p>')
397 end
383 end
398
384
399 def page_header_title
385 def page_header_title
400 if @project.nil? || @project.new_record?
386 if @project.nil? || @project.new_record?
401 h(Setting.app_title)
387 h(Setting.app_title)
402 else
388 else
403 b = []
389 b = []
404 ancestors = (@project.root? ? [] : @project.ancestors.visible)
390 ancestors = (@project.root? ? [] : @project.ancestors.visible)
405 if ancestors.any?
391 if ancestors.any?
406 root = ancestors.shift
392 root = ancestors.shift
407 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
393 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
408 if ancestors.size > 2
394 if ancestors.size > 2
409 b << '&#8230;'
395 b << '&#8230;'
410 ancestors = ancestors[-2, 2]
396 ancestors = ancestors[-2, 2]
411 end
397 end
412 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
398 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
413 end
399 end
414 b << h(@project)
400 b << h(@project)
415 b.join(' &#187; ')
401 b.join(' &#187; ')
416 end
402 end
417 end
403 end
418
404
419 def html_title(*args)
405 def html_title(*args)
420 if args.empty?
406 if args.empty?
421 title = []
407 title = []
422 title << @project.name if @project
408 title << @project.name if @project
423 title += @html_title if @html_title
409 title += @html_title if @html_title
424 title << Setting.app_title
410 title << Setting.app_title
425 title.select {|t| !t.blank? }.join(' - ')
411 title.select {|t| !t.blank? }.join(' - ')
426 else
412 else
427 @html_title ||= []
413 @html_title ||= []
428 @html_title += args
414 @html_title += args
429 end
415 end
430 end
416 end
431
417
432 # Returns the theme, controller name, and action as css classes for the
418 # Returns the theme, controller name, and action as css classes for the
433 # HTML body.
419 # HTML body.
434 def body_css_classes
420 def body_css_classes
435 css = []
421 css = []
436 if theme = Redmine::Themes.theme(Setting.ui_theme)
422 if theme = Redmine::Themes.theme(Setting.ui_theme)
437 css << 'theme-' + theme.name
423 css << 'theme-' + theme.name
438 end
424 end
439
425
440 css << 'controller-' + params[:controller]
426 css << 'controller-' + params[:controller]
441 css << 'action-' + params[:action]
427 css << 'action-' + params[:action]
442 css.join(' ')
428 css.join(' ')
443 end
429 end
444
430
445 def accesskey(s)
431 def accesskey(s)
446 Redmine::AccessKeys.key_for s
432 Redmine::AccessKeys.key_for s
447 end
433 end
448
434
449 # Formats text according to system settings.
435 # Formats text according to system settings.
450 # 2 ways to call this method:
436 # 2 ways to call this method:
451 # * with a String: textilizable(text, options)
437 # * with a String: textilizable(text, options)
452 # * with an object and one of its attribute: textilizable(issue, :description, options)
438 # * with an object and one of its attribute: textilizable(issue, :description, options)
453 def textilizable(*args)
439 def textilizable(*args)
454 options = args.last.is_a?(Hash) ? args.pop : {}
440 options = args.last.is_a?(Hash) ? args.pop : {}
455 case args.size
441 case args.size
456 when 1
442 when 1
457 obj = options[:object]
443 obj = options[:object]
458 text = args.shift
444 text = args.shift
459 when 2
445 when 2
460 obj = args.shift
446 obj = args.shift
461 attr = args.shift
447 attr = args.shift
462 text = obj.send(attr).to_s
448 text = obj.send(attr).to_s
463 else
449 else
464 raise ArgumentError, 'invalid arguments to textilizable'
450 raise ArgumentError, 'invalid arguments to textilizable'
465 end
451 end
466 return '' if text.blank?
452 return '' if text.blank?
467 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
453 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
468 only_path = options.delete(:only_path) == false ? false : true
454 only_path = options.delete(:only_path) == false ? false : true
469
455
470 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
456 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
471
457
472 parse_non_pre_blocks(text) do |text|
458 parse_non_pre_blocks(text) do |text|
473 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
459 [:parse_inline_attachments, :parse_wiki_links, :parse_redmine_links].each do |method_name|
474 send method_name, text, project, obj, attr, only_path, options
460 send method_name, text, project, obj, attr, only_path, options
475 end
461 end
476 end
462 end
477 end
463 end
478
464
479 def parse_non_pre_blocks(text)
465 def parse_non_pre_blocks(text)
480 s = StringScanner.new(text)
466 s = StringScanner.new(text)
481 tags = []
467 tags = []
482 parsed = ''
468 parsed = ''
483 while !s.eos?
469 while !s.eos?
484 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
470 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
485 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
471 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
486 if tags.empty?
472 if tags.empty?
487 yield text
473 yield text
488 end
474 end
489 parsed << text
475 parsed << text
490 if tag
476 if tag
491 if closing
477 if closing
492 if tags.last == tag.downcase
478 if tags.last == tag.downcase
493 tags.pop
479 tags.pop
494 end
480 end
495 else
481 else
496 tags << tag.downcase
482 tags << tag.downcase
497 end
483 end
498 parsed << full_tag
484 parsed << full_tag
499 end
485 end
500 end
486 end
501 # Close any non closing tags
487 # Close any non closing tags
502 while tag = tags.pop
488 while tag = tags.pop
503 parsed << "</#{tag}>"
489 parsed << "</#{tag}>"
504 end
490 end
505 parsed
491 parsed
506 end
492 end
507
493
508 def parse_inline_attachments(text, project, obj, attr, only_path, options)
494 def parse_inline_attachments(text, project, obj, attr, only_path, options)
509 # when using an image link, try to use an attachment, if possible
495 # when using an image link, try to use an attachment, if possible
510 if options[:attachments] || (obj && obj.respond_to?(:attachments))
496 if options[:attachments] || (obj && obj.respond_to?(:attachments))
511 attachments = nil
497 attachments = nil
512 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
498 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
513 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
499 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
514 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
500 attachments ||= (options[:attachments] || obj.attachments).sort_by(&:created_on).reverse
515 # search for the picture in attachments
501 # search for the picture in attachments
516 if found = attachments.detect { |att| att.filename.downcase == filename }
502 if found = attachments.detect { |att| att.filename.downcase == filename }
517 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
503 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
518 desc = found.description.to_s.gsub('"', '')
504 desc = found.description.to_s.gsub('"', '')
519 if !desc.blank? && alttext.blank?
505 if !desc.blank? && alttext.blank?
520 alt = " title=\"#{desc}\" alt=\"#{desc}\""
506 alt = " title=\"#{desc}\" alt=\"#{desc}\""
521 end
507 end
522 "src=\"#{image_url}\"#{alt}"
508 "src=\"#{image_url}\"#{alt}"
523 else
509 else
524 m
510 m
525 end
511 end
526 end
512 end
527 end
513 end
528 end
514 end
529
515
530 # Wiki links
516 # Wiki links
531 #
517 #
532 # Examples:
518 # Examples:
533 # [[mypage]]
519 # [[mypage]]
534 # [[mypage|mytext]]
520 # [[mypage|mytext]]
535 # wiki links can refer other project wikis, using project name or identifier:
521 # wiki links can refer other project wikis, using project name or identifier:
536 # [[project:]] -> wiki starting page
522 # [[project:]] -> wiki starting page
537 # [[project:|mytext]]
523 # [[project:|mytext]]
538 # [[project:mypage]]
524 # [[project:mypage]]
539 # [[project:mypage|mytext]]
525 # [[project:mypage|mytext]]
540 def parse_wiki_links(text, project, obj, attr, only_path, options)
526 def parse_wiki_links(text, project, obj, attr, only_path, options)
541 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
527 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
542 link_project = project
528 link_project = project
543 esc, all, page, title = $1, $2, $3, $5
529 esc, all, page, title = $1, $2, $3, $5
544 if esc.nil?
530 if esc.nil?
545 if page =~ /^([^\:]+)\:(.*)$/
531 if page =~ /^([^\:]+)\:(.*)$/
546 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
532 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
547 page = $2
533 page = $2
548 title ||= $1 if page.blank?
534 title ||= $1 if page.blank?
549 end
535 end
550
536
551 if link_project && link_project.wiki
537 if link_project && link_project.wiki
552 # extract anchor
538 # extract anchor
553 anchor = nil
539 anchor = nil
554 if page =~ /^(.+?)\#(.+)$/
540 if page =~ /^(.+?)\#(.+)$/
555 page, anchor = $1, $2
541 page, anchor = $1, $2
556 end
542 end
557 # check if page exists
543 # check if page exists
558 wiki_page = link_project.wiki.find_page(page)
544 wiki_page = link_project.wiki.find_page(page)
559 url = case options[:wiki_links]
545 url = case options[:wiki_links]
560 when :local; "#{title}.html"
546 when :local; "#{title}.html"
561 when :anchor; "##{title}" # used for single-file wiki export
547 when :anchor; "##{title}" # used for single-file wiki export
562 else
548 else
563 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
549 url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => link_project, :page => Wiki.titleize(page), :anchor => anchor)
564 end
550 end
565 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
551 link_to((title || page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
566 else
552 else
567 # project or wiki doesn't exist
553 # project or wiki doesn't exist
568 all
554 all
569 end
555 end
570 else
556 else
571 all
557 all
572 end
558 end
573 end
559 end
574 end
560 end
575
561
576 # Redmine links
562 # Redmine links
577 #
563 #
578 # Examples:
564 # Examples:
579 # Issues:
565 # Issues:
580 # #52 -> Link to issue #52
566 # #52 -> Link to issue #52
581 # Changesets:
567 # Changesets:
582 # r52 -> Link to revision 52
568 # r52 -> Link to revision 52
583 # commit:a85130f -> Link to scmid starting with a85130f
569 # commit:a85130f -> Link to scmid starting with a85130f
584 # Documents:
570 # Documents:
585 # document#17 -> Link to document with id 17
571 # document#17 -> Link to document with id 17
586 # document:Greetings -> Link to the document with title "Greetings"
572 # document:Greetings -> Link to the document with title "Greetings"
587 # document:"Some document" -> Link to the document with title "Some document"
573 # document:"Some document" -> Link to the document with title "Some document"
588 # Versions:
574 # Versions:
589 # version#3 -> Link to version with id 3
575 # version#3 -> Link to version with id 3
590 # version:1.0.0 -> Link to version named "1.0.0"
576 # version:1.0.0 -> Link to version named "1.0.0"
591 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
577 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
592 # Attachments:
578 # Attachments:
593 # attachment:file.zip -> Link to the attachment of the current object named file.zip
579 # attachment:file.zip -> Link to the attachment of the current object named file.zip
594 # Source files:
580 # Source files:
595 # source:some/file -> Link to the file located at /some/file in the project's repository
581 # source:some/file -> Link to the file located at /some/file in the project's repository
596 # source:some/file@52 -> Link to the file's revision 52
582 # source:some/file@52 -> Link to the file's revision 52
597 # source:some/file#L120 -> Link to line 120 of the file
583 # source:some/file#L120 -> Link to line 120 of the file
598 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
584 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
599 # export:some/file -> Force the download of the file
585 # export:some/file -> Force the download of the file
600 # Forum messages:
586 # Forum messages:
601 # message#1218 -> Link to message with id 1218
587 # message#1218 -> Link to message with id 1218
602 def parse_redmine_links(text, project, obj, attr, only_path, options)
588 def parse_redmine_links(text, project, obj, attr, only_path, options)
603 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
589 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(attachment|document|version|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
604 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
590 leading, esc, prefix, sep, identifier = $1, $2, $3, $5 || $7, $6 || $8
605 link = nil
591 link = nil
606 if esc.nil?
592 if esc.nil?
607 if prefix.nil? && sep == 'r'
593 if prefix.nil? && sep == 'r'
608 if project && (changeset = project.changesets.find_by_revision(identifier))
594 if project && (changeset = project.changesets.find_by_revision(identifier))
609 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
595 link = link_to("r#{identifier}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
610 :class => 'changeset',
596 :class => 'changeset',
611 :title => truncate_single_line(changeset.comments, :length => 100))
597 :title => truncate_single_line(changeset.comments, :length => 100))
612 end
598 end
613 elsif sep == '#'
599 elsif sep == '#'
614 oid = identifier.to_i
600 oid = identifier.to_i
615 case prefix
601 case prefix
616 when nil
602 when nil
617 if issue = Issue.visible.find_by_id(oid, :include => :status)
603 if issue = Issue.visible.find_by_id(oid, :include => :status)
618 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
604 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
619 :class => issue.css_classes,
605 :class => issue.css_classes,
620 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
606 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
621 end
607 end
622 when 'document'
608 when 'document'
623 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
609 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
624 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
610 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
625 :class => 'document'
611 :class => 'document'
626 end
612 end
627 when 'version'
613 when 'version'
628 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
614 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
629 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
615 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
630 :class => 'version'
616 :class => 'version'
631 end
617 end
632 when 'message'
618 when 'message'
633 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
619 if message = Message.find_by_id(oid, :include => [:parent, {:board => :project}], :conditions => Project.visible_by(User.current))
634 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
620 link = link_to h(truncate(message.subject, :length => 60)), {:only_path => only_path,
635 :controller => 'messages',
621 :controller => 'messages',
636 :action => 'show',
622 :action => 'show',
637 :board_id => message.board,
623 :board_id => message.board,
638 :id => message.root,
624 :id => message.root,
639 :anchor => (message.parent ? "message-#{message.id}" : nil)},
625 :anchor => (message.parent ? "message-#{message.id}" : nil)},
640 :class => 'message'
626 :class => 'message'
641 end
627 end
642 when 'project'
628 when 'project'
643 if p = Project.visible.find_by_id(oid)
629 if p = Project.visible.find_by_id(oid)
644 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
630 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
645 end
631 end
646 end
632 end
647 elsif sep == ':'
633 elsif sep == ':'
648 # removes the double quotes if any
634 # removes the double quotes if any
649 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
635 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
650 case prefix
636 case prefix
651 when 'document'
637 when 'document'
652 if project && document = project.documents.find_by_title(name)
638 if project && document = project.documents.find_by_title(name)
653 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
639 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
654 :class => 'document'
640 :class => 'document'
655 end
641 end
656 when 'version'
642 when 'version'
657 if project && version = project.versions.find_by_name(name)
643 if project && version = project.versions.find_by_name(name)
658 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
644 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
659 :class => 'version'
645 :class => 'version'
660 end
646 end
661 when 'commit'
647 when 'commit'
662 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
648 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
663 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
649 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
664 :class => 'changeset',
650 :class => 'changeset',
665 :title => truncate_single_line(changeset.comments, :length => 100)
651 :title => truncate_single_line(changeset.comments, :length => 100)
666 end
652 end
667 when 'source', 'export'
653 when 'source', 'export'
668 if project && project.repository
654 if project && project.repository
669 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
655 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
670 path, rev, anchor = $1, $3, $5
656 path, rev, anchor = $1, $3, $5
671 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
657 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
672 :path => to_path_param(path),
658 :path => to_path_param(path),
673 :rev => rev,
659 :rev => rev,
674 :anchor => anchor,
660 :anchor => anchor,
675 :format => (prefix == 'export' ? 'raw' : nil)},
661 :format => (prefix == 'export' ? 'raw' : nil)},
676 :class => (prefix == 'export' ? 'source download' : 'source')
662 :class => (prefix == 'export' ? 'source download' : 'source')
677 end
663 end
678 when 'attachment'
664 when 'attachment'
679 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
665 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
680 if attachments && attachment = attachments.detect {|a| a.filename == name }
666 if attachments && attachment = attachments.detect {|a| a.filename == name }
681 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
667 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
682 :class => 'attachment'
668 :class => 'attachment'
683 end
669 end
684 when 'project'
670 when 'project'
685 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
671 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
686 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
672 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
687 end
673 end
688 end
674 end
689 end
675 end
690 end
676 end
691 leading + (link || "#{prefix}#{sep}#{identifier}")
677 leading + (link || "#{prefix}#{sep}#{identifier}")
692 end
678 end
693 end
679 end
694
680
695 # Same as Rails' simple_format helper without using paragraphs
681 # Same as Rails' simple_format helper without using paragraphs
696 def simple_format_without_paragraph(text)
682 def simple_format_without_paragraph(text)
697 text.to_s.
683 text.to_s.
698 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
684 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
699 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
685 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
700 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
686 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
701 end
687 end
702
688
703 def lang_options_for_select(blank=true)
689 def lang_options_for_select(blank=true)
704 (blank ? [["(auto)", ""]] : []) +
690 (blank ? [["(auto)", ""]] : []) +
705 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
691 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
706 end
692 end
707
693
708 def label_tag_for(name, option_tags = nil, options = {})
694 def label_tag_for(name, option_tags = nil, options = {})
709 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
695 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
710 content_tag("label", label_text)
696 content_tag("label", label_text)
711 end
697 end
712
698
713 def labelled_tabular_form_for(name, object, options, &proc)
699 def labelled_tabular_form_for(name, object, options, &proc)
714 options[:html] ||= {}
700 options[:html] ||= {}
715 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
701 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
716 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
702 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
717 end
703 end
718
704
719 def back_url_hidden_field_tag
705 def back_url_hidden_field_tag
720 back_url = params[:back_url] || request.env['HTTP_REFERER']
706 back_url = params[:back_url] || request.env['HTTP_REFERER']
721 back_url = CGI.unescape(back_url.to_s)
707 back_url = CGI.unescape(back_url.to_s)
722 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
708 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
723 end
709 end
724
710
725 def check_all_links(form_name)
711 def check_all_links(form_name)
726 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
712 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
727 " | " +
713 " | " +
728 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
714 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
729 end
715 end
730
716
731 def progress_bar(pcts, options={})
717 def progress_bar(pcts, options={})
732 pcts = [pcts, pcts] unless pcts.is_a?(Array)
718 pcts = [pcts, pcts] unless pcts.is_a?(Array)
733 pcts = pcts.collect(&:round)
719 pcts = pcts.collect(&:round)
734 pcts[1] = pcts[1] - pcts[0]
720 pcts[1] = pcts[1] - pcts[0]
735 pcts << (100 - pcts[1] - pcts[0])
721 pcts << (100 - pcts[1] - pcts[0])
736 width = options[:width] || '100px;'
722 width = options[:width] || '100px;'
737 legend = options[:legend] || ''
723 legend = options[:legend] || ''
738 content_tag('table',
724 content_tag('table',
739 content_tag('tr',
725 content_tag('tr',
740 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
726 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : '') +
741 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
727 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : '') +
742 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
728 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : '')
743 ), :class => 'progress', :style => "width: #{width};") +
729 ), :class => 'progress', :style => "width: #{width};") +
744 content_tag('p', legend, :class => 'pourcent')
730 content_tag('p', legend, :class => 'pourcent')
745 end
731 end
746
732
747 def checked_image(checked=true)
733 def checked_image(checked=true)
748 if checked
734 if checked
749 image_tag 'toggle_check.png'
735 image_tag 'toggle_check.png'
750 end
736 end
751 end
737 end
752
738
753 def context_menu(url)
739 def context_menu(url)
754 unless @context_menu_included
740 unless @context_menu_included
755 content_for :header_tags do
741 content_for :header_tags do
756 javascript_include_tag('context_menu') +
742 javascript_include_tag('context_menu') +
757 stylesheet_link_tag('context_menu')
743 stylesheet_link_tag('context_menu')
758 end
744 end
759 if l(:direction) == 'rtl'
745 if l(:direction) == 'rtl'
760 content_for :header_tags do
746 content_for :header_tags do
761 stylesheet_link_tag('context_menu_rtl')
747 stylesheet_link_tag('context_menu_rtl')
762 end
748 end
763 end
749 end
764 @context_menu_included = true
750 @context_menu_included = true
765 end
751 end
766 javascript_tag "new ContextMenu('#{ url_for(url) }')"
752 javascript_tag "new ContextMenu('#{ url_for(url) }')"
767 end
753 end
768
754
769 def context_menu_link(name, url, options={})
755 def context_menu_link(name, url, options={})
770 options[:class] ||= ''
756 options[:class] ||= ''
771 if options.delete(:selected)
757 if options.delete(:selected)
772 options[:class] << ' icon-checked disabled'
758 options[:class] << ' icon-checked disabled'
773 options[:disabled] = true
759 options[:disabled] = true
774 end
760 end
775 if options.delete(:disabled)
761 if options.delete(:disabled)
776 options.delete(:method)
762 options.delete(:method)
777 options.delete(:confirm)
763 options.delete(:confirm)
778 options.delete(:onclick)
764 options.delete(:onclick)
779 options[:class] << ' disabled'
765 options[:class] << ' disabled'
780 url = '#'
766 url = '#'
781 end
767 end
782 link_to name, url, options
768 link_to name, url, options
783 end
769 end
784
770
785 def calendar_for(field_id)
771 def calendar_for(field_id)
786 include_calendar_headers_tags
772 include_calendar_headers_tags
787 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
773 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
788 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
774 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
789 end
775 end
790
776
791 def include_calendar_headers_tags
777 def include_calendar_headers_tags
792 unless @calendar_headers_tags_included
778 unless @calendar_headers_tags_included
793 @calendar_headers_tags_included = true
779 @calendar_headers_tags_included = true
794 content_for :header_tags do
780 content_for :header_tags do
795 start_of_week = case Setting.start_of_week.to_i
781 start_of_week = case Setting.start_of_week.to_i
796 when 1
782 when 1
797 'Calendar._FD = 1;' # Monday
783 'Calendar._FD = 1;' # Monday
798 when 7
784 when 7
799 'Calendar._FD = 0;' # Sunday
785 'Calendar._FD = 0;' # Sunday
800 else
786 else
801 '' # use language
787 '' # use language
802 end
788 end
803
789
804 javascript_include_tag('calendar/calendar') +
790 javascript_include_tag('calendar/calendar') +
805 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
791 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
806 javascript_tag(start_of_week) +
792 javascript_tag(start_of_week) +
807 javascript_include_tag('calendar/calendar-setup') +
793 javascript_include_tag('calendar/calendar-setup') +
808 stylesheet_link_tag('calendar')
794 stylesheet_link_tag('calendar')
809 end
795 end
810 end
796 end
811 end
797 end
812
798
813 def content_for(name, content = nil, &block)
799 def content_for(name, content = nil, &block)
814 @has_content ||= {}
800 @has_content ||= {}
815 @has_content[name] = true
801 @has_content[name] = true
816 super(name, content, &block)
802 super(name, content, &block)
817 end
803 end
818
804
819 def has_content?(name)
805 def has_content?(name)
820 (@has_content && @has_content[name]) || false
806 (@has_content && @has_content[name]) || false
821 end
807 end
822
808
823 # Returns the avatar image tag for the given +user+ if avatars are enabled
809 # Returns the avatar image tag for the given +user+ if avatars are enabled
824 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
810 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
825 def avatar(user, options = { })
811 def avatar(user, options = { })
826 if Setting.gravatar_enabled?
812 if Setting.gravatar_enabled?
827 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
813 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
828 email = nil
814 email = nil
829 if user.respond_to?(:mail)
815 if user.respond_to?(:mail)
830 email = user.mail
816 email = user.mail
831 elsif user.to_s =~ %r{<(.+?)>}
817 elsif user.to_s =~ %r{<(.+?)>}
832 email = $1
818 email = $1
833 end
819 end
834 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
820 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
835 end
821 end
836 end
822 end
837
823
838 def favicon
824 def favicon
839 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
825 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />"
840 end
826 end
841
827
842 private
828 private
843
829
844 def wiki_helper
830 def wiki_helper
845 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
831 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
846 extend helper
832 extend helper
847 return self
833 return self
848 end
834 end
849
835
850 def link_to_remote_content_update(text, url_params)
836 def link_to_remote_content_update(text, url_params)
851 link_to_remote(text,
837 link_to_remote(text,
852 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
838 {:url => url_params, :method => :get, :update => 'content', :complete => 'window.scrollTo(0,0)'},
853 {:href => url_for(:params => url_params)}
839 {:href => url_for(:params => url_params)}
854 )
840 )
855 end
841 end
856
842
857 end
843 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_nil avatar(User.find_by_mail('jsmith@somenet.foo'))
595 assert_nil 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