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