##// END OF EJS Templates
Adds support for multiple repositories to redmine links (#779)....
Jean-Philippe Lang -
r8574:f2e7aa596b4e
parent child
Show More
@@ -1,1092 +1,1112
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
4 # Copyright (C) 2006-2011 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require 'forwardable'
20 require 'forwardable'
21 require 'cgi'
21 require 'cgi'
22
22
23 module ApplicationHelper
23 module ApplicationHelper
24 include Redmine::WikiFormatting::Macros::Definitions
24 include Redmine::WikiFormatting::Macros::Definitions
25 include Redmine::I18n
25 include Redmine::I18n
26 include GravatarHelper::PublicMethods
26 include GravatarHelper::PublicMethods
27
27
28 extend Forwardable
28 extend Forwardable
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
29 def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter
30
30
31 # Return true if user is authorized for controller/action, otherwise false
31 # Return true if user is authorized for controller/action, otherwise false
32 def authorize_for(controller, action)
32 def authorize_for(controller, action)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
33 User.current.allowed_to?({:controller => controller, :action => action}, @project)
34 end
34 end
35
35
36 # Display a link if user is authorized
36 # Display a link if user is authorized
37 #
37 #
38 # @param [String] name Anchor text (passed to link_to)
38 # @param [String] name Anchor text (passed to link_to)
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
39 # @param [Hash] options Hash params. This will checked by authorize_for to see if the user is authorized
40 # @param [optional, Hash] html_options Options passed to link_to
40 # @param [optional, Hash] html_options Options passed to link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
41 # @param [optional, Hash] parameters_for_method_reference Extra parameters for link_to
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
42 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
43 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
44 end
44 end
45
45
46 # Display a link to remote if user is authorized
46 # Display a link to remote if user is authorized
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
47 def link_to_remote_if_authorized(name, options = {}, html_options = nil)
48 url = options[:url] || {}
48 url = options[:url] || {}
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
49 link_to_remote(name, options, html_options) if authorize_for(url[:controller] || params[:controller], url[:action])
50 end
50 end
51
51
52 # Displays a link to user's account page if active
52 # Displays a link to user's account page if active
53 def link_to_user(user, options={})
53 def link_to_user(user, options={})
54 if user.is_a?(User)
54 if user.is_a?(User)
55 name = h(user.name(options[:format]))
55 name = h(user.name(options[:format]))
56 if user.active?
56 if user.active?
57 link_to name, :controller => 'users', :action => 'show', :id => user
57 link_to name, :controller => 'users', :action => 'show', :id => user
58 else
58 else
59 name
59 name
60 end
60 end
61 else
61 else
62 h(user.to_s)
62 h(user.to_s)
63 end
63 end
64 end
64 end
65
65
66 # Displays a link to +issue+ with its subject.
66 # Displays a link to +issue+ with its subject.
67 # Examples:
67 # Examples:
68 #
68 #
69 # link_to_issue(issue) # => Defect #6: This is the subject
69 # link_to_issue(issue) # => Defect #6: This is the subject
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
70 # link_to_issue(issue, :truncate => 6) # => Defect #6: This i...
71 # link_to_issue(issue, :subject => false) # => Defect #6
71 # link_to_issue(issue, :subject => false) # => Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
72 # link_to_issue(issue, :project => true) # => Foo - Defect #6
73 #
73 #
74 def link_to_issue(issue, options={})
74 def link_to_issue(issue, options={})
75 title = nil
75 title = nil
76 subject = nil
76 subject = nil
77 if options[:subject] == false
77 if options[:subject] == false
78 title = truncate(issue.subject, :length => 60)
78 title = truncate(issue.subject, :length => 60)
79 else
79 else
80 subject = issue.subject
80 subject = issue.subject
81 if options[:truncate]
81 if options[:truncate]
82 subject = truncate(subject, :length => options[:truncate])
82 subject = truncate(subject, :length => options[:truncate])
83 end
83 end
84 end
84 end
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
85 s = link_to "#{h(issue.tracker)} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue},
86 :class => issue.css_classes,
86 :class => issue.css_classes,
87 :title => title
87 :title => title
88 s << h(": #{subject}") if subject
88 s << h(": #{subject}") if subject
89 s = h("#{issue.project} - ") + s if options[:project]
89 s = h("#{issue.project} - ") + s if options[:project]
90 s
90 s
91 end
91 end
92
92
93 # Generates a link to an attachment.
93 # Generates a link to an attachment.
94 # Options:
94 # Options:
95 # * :text - Link text (default to attachment filename)
95 # * :text - Link text (default to attachment filename)
96 # * :download - Force download (default: false)
96 # * :download - Force download (default: false)
97 def link_to_attachment(attachment, options={})
97 def link_to_attachment(attachment, options={})
98 text = options.delete(:text) || attachment.filename
98 text = options.delete(:text) || attachment.filename
99 action = options.delete(:download) ? 'download' : 'show'
99 action = options.delete(:download) ? 'download' : 'show'
100 link_to(h(text),
100 link_to(h(text),
101 {:controller => 'attachments', :action => action,
101 {:controller => 'attachments', :action => action,
102 :id => attachment, :filename => attachment.filename },
102 :id => attachment, :filename => attachment.filename },
103 options)
103 options)
104 end
104 end
105
105
106 # Generates a link to a SCM revision
106 # Generates a link to a SCM revision
107 # Options:
107 # Options:
108 # * :text - Link text (default to the formatted revision)
108 # * :text - Link text (default to the formatted revision)
109 def link_to_revision(revision, repository, options={})
109 def link_to_revision(revision, repository, options={})
110 if repository.is_a?(Project)
110 if repository.is_a?(Project)
111 repository = repository.repository
111 repository = repository.repository
112 end
112 end
113 text = options.delete(:text) || format_revision(revision)
113 text = options.delete(:text) || format_revision(revision)
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
114 rev = revision.respond_to?(:identifier) ? revision.identifier : revision
115 link_to(
115 link_to(
116 h(text),
116 h(text),
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
117 {:controller => 'repositories', :action => 'revision', :id => repository.project, :repository_id => repository.identifier_param, :rev => rev},
118 :title => l(:label_revision_id, format_revision(revision))
118 :title => l(:label_revision_id, format_revision(revision))
119 )
119 )
120 end
120 end
121
121
122 # Generates a link to a message
122 # Generates a link to a message
123 def link_to_message(message, options={}, html_options = nil)
123 def link_to_message(message, options={}, html_options = nil)
124 link_to(
124 link_to(
125 h(truncate(message.subject, :length => 60)),
125 h(truncate(message.subject, :length => 60)),
126 { :controller => 'messages', :action => 'show',
126 { :controller => 'messages', :action => 'show',
127 :board_id => message.board_id,
127 :board_id => message.board_id,
128 :id => message.root,
128 :id => message.root,
129 :r => (message.parent_id && message.id),
129 :r => (message.parent_id && message.id),
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
130 :anchor => (message.parent_id ? "message-#{message.id}" : nil)
131 }.merge(options),
131 }.merge(options),
132 html_options
132 html_options
133 )
133 )
134 end
134 end
135
135
136 # Generates a link to a project if active
136 # Generates a link to a project if active
137 # Examples:
137 # Examples:
138 #
138 #
139 # link_to_project(project) # => link to the specified project overview
139 # link_to_project(project) # => link to the specified project overview
140 # link_to_project(project, :action=>'settings') # => link to project settings
140 # link_to_project(project, :action=>'settings') # => link to project settings
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
141 # link_to_project(project, {:only_path => false}, :class => "project") # => 3rd arg adds html options
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
142 # link_to_project(project, {}, :class => "project") # => html options with default url (project overview)
143 #
143 #
144 def link_to_project(project, options={}, html_options = nil)
144 def link_to_project(project, options={}, html_options = nil)
145 if project.active?
145 if project.active?
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
146 url = {:controller => 'projects', :action => 'show', :id => project}.merge(options)
147 link_to(h(project), url, html_options)
147 link_to(h(project), url, html_options)
148 else
148 else
149 h(project)
149 h(project)
150 end
150 end
151 end
151 end
152
152
153 def toggle_link(name, id, options={})
153 def toggle_link(name, id, options={})
154 onclick = "Element.toggle('#{id}'); "
154 onclick = "Element.toggle('#{id}'); "
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
155 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
156 onclick << "return false;"
156 onclick << "return false;"
157 link_to(name, "#", :onclick => onclick)
157 link_to(name, "#", :onclick => onclick)
158 end
158 end
159
159
160 def image_to_function(name, function, html_options = {})
160 def image_to_function(name, function, html_options = {})
161 html_options.symbolize_keys!
161 html_options.symbolize_keys!
162 tag(:input, html_options.merge({
162 tag(:input, html_options.merge({
163 :type => "image", :src => image_path(name),
163 :type => "image", :src => image_path(name),
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
164 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
165 }))
165 }))
166 end
166 end
167
167
168 def prompt_to_remote(name, text, param, url, html_options = {})
168 def prompt_to_remote(name, text, param, url, html_options = {})
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
169 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
170 link_to name, {}, html_options
170 link_to name, {}, html_options
171 end
171 end
172
172
173 def format_activity_title(text)
173 def format_activity_title(text)
174 h(truncate_single_line(text, :length => 100))
174 h(truncate_single_line(text, :length => 100))
175 end
175 end
176
176
177 def format_activity_day(date)
177 def format_activity_day(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
178 date == Date.today ? l(:label_today).titleize : format_date(date)
179 end
179 end
180
180
181 def format_activity_description(text)
181 def format_activity_description(text)
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
182 h(truncate(text.to_s, :length => 120).gsub(%r{[\r\n]*<(pre|code)>.*$}m, '...')
183 ).gsub(/[\r\n]+/, "<br />").html_safe
183 ).gsub(/[\r\n]+/, "<br />").html_safe
184 end
184 end
185
185
186 def format_version_name(version)
186 def format_version_name(version)
187 if version.project == @project
187 if version.project == @project
188 h(version)
188 h(version)
189 else
189 else
190 h("#{version.project} - #{version}")
190 h("#{version.project} - #{version}")
191 end
191 end
192 end
192 end
193
193
194 def due_date_distance_in_words(date)
194 def due_date_distance_in_words(date)
195 if date
195 if date
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
196 l((date < Date.today ? :label_roadmap_overdue : :label_roadmap_due_in), distance_of_date_in_words(Date.today, date))
197 end
197 end
198 end
198 end
199
199
200 def render_page_hierarchy(pages, node=nil, options={})
200 def render_page_hierarchy(pages, node=nil, options={})
201 content = ''
201 content = ''
202 if pages[node]
202 if pages[node]
203 content << "<ul class=\"pages-hierarchy\">\n"
203 content << "<ul class=\"pages-hierarchy\">\n"
204 pages[node].each do |page|
204 pages[node].each do |page|
205 content << "<li>"
205 content << "<li>"
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
206 content << link_to(h(page.pretty_title), {:controller => 'wiki', :action => 'show', :project_id => page.project, :id => page.title},
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
207 :title => (options[:timestamp] && page.updated_on ? l(:label_updated_time, distance_of_time_in_words(Time.now, page.updated_on)) : nil))
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
208 content << "\n" + render_page_hierarchy(pages, page.id, options) if pages[page.id]
209 content << "</li>\n"
209 content << "</li>\n"
210 end
210 end
211 content << "</ul>\n"
211 content << "</ul>\n"
212 end
212 end
213 content.html_safe
213 content.html_safe
214 end
214 end
215
215
216 # Renders flash messages
216 # Renders flash messages
217 def render_flash_messages
217 def render_flash_messages
218 s = ''
218 s = ''
219 flash.each do |k,v|
219 flash.each do |k,v|
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
220 s << (content_tag('div', v.html_safe, :class => "flash #{k}"))
221 end
221 end
222 s.html_safe
222 s.html_safe
223 end
223 end
224
224
225 # Renders tabs and their content
225 # Renders tabs and their content
226 def render_tabs(tabs)
226 def render_tabs(tabs)
227 if tabs.any?
227 if tabs.any?
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
228 render :partial => 'common/tabs', :locals => {:tabs => tabs}
229 else
229 else
230 content_tag 'p', l(:label_no_data), :class => "nodata"
230 content_tag 'p', l(:label_no_data), :class => "nodata"
231 end
231 end
232 end
232 end
233
233
234 # Renders the project quick-jump box
234 # Renders the project quick-jump box
235 def render_project_jump_box
235 def render_project_jump_box
236 return unless User.current.logged?
236 return unless User.current.logged?
237 projects = User.current.memberships.collect(&:project).compact.uniq
237 projects = User.current.memberships.collect(&:project).compact.uniq
238 if projects.any?
238 if projects.any?
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
239 s = '<select onchange="if (this.value != \'\') { window.location = this.value; }">' +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
240 "<option value=''>#{ l(:label_jump_to_a_project) }</option>" +
241 '<option value="" disabled="disabled">---</option>'
241 '<option value="" disabled="disabled">---</option>'
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
242 s << project_tree_options_for_select(projects, :selected => @project) do |p|
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
243 { :value => url_for(:controller => 'projects', :action => 'show', :id => p, :jump => current_menu_item) }
244 end
244 end
245 s << '</select>'
245 s << '</select>'
246 s.html_safe
246 s.html_safe
247 end
247 end
248 end
248 end
249
249
250 def project_tree_options_for_select(projects, options = {})
250 def project_tree_options_for_select(projects, options = {})
251 s = ''
251 s = ''
252 project_tree(projects) do |project, level|
252 project_tree(projects) do |project, level|
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
253 name_prefix = (level > 0 ? ('&nbsp;' * 2 * level + '&#187; ') : '')
254 tag_options = {:value => project.id}
254 tag_options = {:value => project.id}
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
255 if project == options[:selected] || (options[:selected].respond_to?(:include?) && options[:selected].include?(project))
256 tag_options[:selected] = 'selected'
256 tag_options[:selected] = 'selected'
257 else
257 else
258 tag_options[:selected] = nil
258 tag_options[:selected] = nil
259 end
259 end
260 tag_options.merge!(yield(project)) if block_given?
260 tag_options.merge!(yield(project)) if block_given?
261 s << content_tag('option', name_prefix + h(project), tag_options)
261 s << content_tag('option', name_prefix + h(project), tag_options)
262 end
262 end
263 s.html_safe
263 s.html_safe
264 end
264 end
265
265
266 # Yields the given block for each project with its level in the tree
266 # Yields the given block for each project with its level in the tree
267 #
267 #
268 # Wrapper for Project#project_tree
268 # Wrapper for Project#project_tree
269 def project_tree(projects, &block)
269 def project_tree(projects, &block)
270 Project.project_tree(projects, &block)
270 Project.project_tree(projects, &block)
271 end
271 end
272
272
273 def project_nested_ul(projects, &block)
273 def project_nested_ul(projects, &block)
274 s = ''
274 s = ''
275 if projects.any?
275 if projects.any?
276 ancestors = []
276 ancestors = []
277 projects.sort_by(&:lft).each do |project|
277 projects.sort_by(&:lft).each do |project|
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
278 if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
279 s << "<ul>\n"
279 s << "<ul>\n"
280 else
280 else
281 ancestors.pop
281 ancestors.pop
282 s << "</li>"
282 s << "</li>"
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
283 while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
284 ancestors.pop
284 ancestors.pop
285 s << "</ul></li>\n"
285 s << "</ul></li>\n"
286 end
286 end
287 end
287 end
288 s << "<li>"
288 s << "<li>"
289 s << yield(project).to_s
289 s << yield(project).to_s
290 ancestors << project
290 ancestors << project
291 end
291 end
292 s << ("</li></ul>\n" * ancestors.size)
292 s << ("</li></ul>\n" * ancestors.size)
293 end
293 end
294 s.html_safe
294 s.html_safe
295 end
295 end
296
296
297 def principals_check_box_tags(name, principals)
297 def principals_check_box_tags(name, principals)
298 s = ''
298 s = ''
299 principals.sort.each do |principal|
299 principals.sort.each do |principal|
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
300 s << "<label>#{ check_box_tag name, principal.id, false } #{h principal}</label>\n"
301 end
301 end
302 s.html_safe
302 s.html_safe
303 end
303 end
304
304
305 # Returns a string for users/groups option tags
305 # Returns a string for users/groups option tags
306 def principals_options_for_select(collection, selected=nil)
306 def principals_options_for_select(collection, selected=nil)
307 s = ''
307 s = ''
308 if collection.include?(User.current)
308 if collection.include?(User.current)
309 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
309 s << content_tag('option', "<< #{l(:label_me)} >>", :value => User.current.id)
310 end
310 end
311 groups = ''
311 groups = ''
312 collection.sort.each do |element|
312 collection.sort.each do |element|
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
313 selected_attribute = ' selected="selected"' if option_value_selected?(element, selected)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
314 (element.is_a?(Group) ? groups : s) << %(<option value="#{element.id}"#{selected_attribute}>#{h element.name}</option>)
315 end
315 end
316 unless groups.empty?
316 unless groups.empty?
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
317 s << %(<optgroup label="#{h(l(:label_group_plural))}">#{groups}</optgroup>)
318 end
318 end
319 s
319 s
320 end
320 end
321
321
322 # Truncates and returns the string as a single line
322 # Truncates and returns the string as a single line
323 def truncate_single_line(string, *args)
323 def truncate_single_line(string, *args)
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
324 truncate(string.to_s, *args).gsub(%r{[\r\n]+}m, ' ')
325 end
325 end
326
326
327 # Truncates at line break after 250 characters or options[:length]
327 # Truncates at line break after 250 characters or options[:length]
328 def truncate_lines(string, options={})
328 def truncate_lines(string, options={})
329 length = options[:length] || 250
329 length = options[:length] || 250
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
330 if string.to_s =~ /\A(.{#{length}}.*?)$/m
331 "#{$1}..."
331 "#{$1}..."
332 else
332 else
333 string
333 string
334 end
334 end
335 end
335 end
336
336
337 def anchor(text)
337 def anchor(text)
338 text.to_s.gsub(' ', '_')
338 text.to_s.gsub(' ', '_')
339 end
339 end
340
340
341 def html_hours(text)
341 def html_hours(text)
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
342 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>').html_safe
343 end
343 end
344
344
345 def authoring(created, author, options={})
345 def authoring(created, author, options={})
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
346 l(options[:label] || :label_added_time_by, :author => link_to_user(author), :age => time_tag(created)).html_safe
347 end
347 end
348
348
349 def time_tag(time)
349 def time_tag(time)
350 text = distance_of_time_in_words(Time.now, time)
350 text = distance_of_time_in_words(Time.now, time)
351 if @project
351 if @project
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
352 link_to(text, {:controller => 'activities', :action => 'index', :id => @project, :from => time.to_date}, :title => format_time(time))
353 else
353 else
354 content_tag('acronym', text, :title => format_time(time))
354 content_tag('acronym', text, :title => format_time(time))
355 end
355 end
356 end
356 end
357
357
358 def syntax_highlight(name, content)
358 def syntax_highlight(name, content)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
359 Redmine::SyntaxHighlighting.highlight_by_filename(content, name)
360 end
360 end
361
361
362 def to_path_param(path)
362 def to_path_param(path)
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
363 path.to_s.split(%r{[/\\]}).select {|p| !p.blank?}
364 end
364 end
365
365
366 def pagination_links_full(paginator, count=nil, options={})
366 def pagination_links_full(paginator, count=nil, options={})
367 page_param = options.delete(:page_param) || :page
367 page_param = options.delete(:page_param) || :page
368 per_page_links = options.delete(:per_page_links)
368 per_page_links = options.delete(:per_page_links)
369 url_param = params.dup
369 url_param = params.dup
370
370
371 html = ''
371 html = ''
372 if paginator.current.previous
372 if paginator.current.previous
373 # \xc2\xab(utf-8) = &#171;
373 # \xc2\xab(utf-8) = &#171;
374 html << link_to_content_update(
374 html << link_to_content_update(
375 "\xc2\xab " + l(:label_previous),
375 "\xc2\xab " + l(:label_previous),
376 url_param.merge(page_param => paginator.current.previous)) + ' '
376 url_param.merge(page_param => paginator.current.previous)) + ' '
377 end
377 end
378
378
379 html << (pagination_links_each(paginator, options) do |n|
379 html << (pagination_links_each(paginator, options) do |n|
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
380 link_to_content_update(n.to_s, url_param.merge(page_param => n))
381 end || '')
381 end || '')
382
382
383 if paginator.current.next
383 if paginator.current.next
384 # \xc2\xbb(utf-8) = &#187;
384 # \xc2\xbb(utf-8) = &#187;
385 html << ' ' + link_to_content_update(
385 html << ' ' + link_to_content_update(
386 (l(:label_next) + " \xc2\xbb"),
386 (l(:label_next) + " \xc2\xbb"),
387 url_param.merge(page_param => paginator.current.next))
387 url_param.merge(page_param => paginator.current.next))
388 end
388 end
389
389
390 unless count.nil?
390 unless count.nil?
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
391 html << " (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})"
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
392 if per_page_links != false && links = per_page_links(paginator.items_per_page)
393 html << " | #{links}"
393 html << " | #{links}"
394 end
394 end
395 end
395 end
396
396
397 html.html_safe
397 html.html_safe
398 end
398 end
399
399
400 def per_page_links(selected=nil)
400 def per_page_links(selected=nil)
401 links = Setting.per_page_options_array.collect do |n|
401 links = Setting.per_page_options_array.collect do |n|
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
402 n == selected ? n : link_to_content_update(n, params.merge(:per_page => n))
403 end
403 end
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
404 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
405 end
405 end
406
406
407 def reorder_links(name, url, method = :post)
407 def reorder_links(name, url, method = :post)
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
408 link_to(image_tag('2uparrow.png', :alt => l(:label_sort_highest)),
409 url.merge({"#{name}[move_to]" => 'highest'}),
409 url.merge({"#{name}[move_to]" => 'highest'}),
410 :method => method, :title => l(:label_sort_highest)) +
410 :method => method, :title => l(:label_sort_highest)) +
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
411 link_to(image_tag('1uparrow.png', :alt => l(:label_sort_higher)),
412 url.merge({"#{name}[move_to]" => 'higher'}),
412 url.merge({"#{name}[move_to]" => 'higher'}),
413 :method => method, :title => l(:label_sort_higher)) +
413 :method => method, :title => l(:label_sort_higher)) +
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
414 link_to(image_tag('1downarrow.png', :alt => l(:label_sort_lower)),
415 url.merge({"#{name}[move_to]" => 'lower'}),
415 url.merge({"#{name}[move_to]" => 'lower'}),
416 :method => method, :title => l(:label_sort_lower)) +
416 :method => method, :title => l(:label_sort_lower)) +
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
417 link_to(image_tag('2downarrow.png', :alt => l(:label_sort_lowest)),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
418 url.merge({"#{name}[move_to]" => 'lowest'}),
419 :method => method, :title => l(:label_sort_lowest))
419 :method => method, :title => l(:label_sort_lowest))
420 end
420 end
421
421
422 def breadcrumb(*args)
422 def breadcrumb(*args)
423 elements = args.flatten
423 elements = args.flatten
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
424 elements.any? ? content_tag('p', (args.join(" \xc2\xbb ") + " \xc2\xbb ").html_safe, :class => 'breadcrumb') : nil
425 end
425 end
426
426
427 def other_formats_links(&block)
427 def other_formats_links(&block)
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
428 concat('<p class="other-formats">'.html_safe + l(:label_export_to))
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
429 yield Redmine::Views::OtherFormatsBuilder.new(self)
430 concat('</p>'.html_safe)
430 concat('</p>'.html_safe)
431 end
431 end
432
432
433 def page_header_title
433 def page_header_title
434 if @project.nil? || @project.new_record?
434 if @project.nil? || @project.new_record?
435 h(Setting.app_title)
435 h(Setting.app_title)
436 else
436 else
437 b = []
437 b = []
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
438 ancestors = (@project.root? ? [] : @project.ancestors.visible.all)
439 if ancestors.any?
439 if ancestors.any?
440 root = ancestors.shift
440 root = ancestors.shift
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
441 b << link_to_project(root, {:jump => current_menu_item}, :class => 'root')
442 if ancestors.size > 2
442 if ancestors.size > 2
443 b << "\xe2\x80\xa6"
443 b << "\xe2\x80\xa6"
444 ancestors = ancestors[-2, 2]
444 ancestors = ancestors[-2, 2]
445 end
445 end
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
446 b += ancestors.collect {|p| link_to_project(p, {:jump => current_menu_item}, :class => 'ancestor') }
447 end
447 end
448 b << h(@project)
448 b << h(@project)
449 b.join(" \xc2\xbb ").html_safe
449 b.join(" \xc2\xbb ").html_safe
450 end
450 end
451 end
451 end
452
452
453 def html_title(*args)
453 def html_title(*args)
454 if args.empty?
454 if args.empty?
455 title = @html_title || []
455 title = @html_title || []
456 title << @project.name if @project
456 title << @project.name if @project
457 title << Setting.app_title unless Setting.app_title == title.last
457 title << Setting.app_title unless Setting.app_title == title.last
458 title.select {|t| !t.blank? }.join(' - ')
458 title.select {|t| !t.blank? }.join(' - ')
459 else
459 else
460 @html_title ||= []
460 @html_title ||= []
461 @html_title += args
461 @html_title += args
462 end
462 end
463 end
463 end
464
464
465 # Returns the theme, controller name, and action as css classes for the
465 # Returns the theme, controller name, and action as css classes for the
466 # HTML body.
466 # HTML body.
467 def body_css_classes
467 def body_css_classes
468 css = []
468 css = []
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
469 if theme = Redmine::Themes.theme(Setting.ui_theme)
470 css << 'theme-' + theme.name
470 css << 'theme-' + theme.name
471 end
471 end
472
472
473 css << 'controller-' + params[:controller]
473 css << 'controller-' + params[:controller]
474 css << 'action-' + params[:action]
474 css << 'action-' + params[:action]
475 css.join(' ')
475 css.join(' ')
476 end
476 end
477
477
478 def accesskey(s)
478 def accesskey(s)
479 Redmine::AccessKeys.key_for s
479 Redmine::AccessKeys.key_for s
480 end
480 end
481
481
482 # Formats text according to system settings.
482 # Formats text according to system settings.
483 # 2 ways to call this method:
483 # 2 ways to call this method:
484 # * with a String: textilizable(text, options)
484 # * with a String: textilizable(text, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
485 # * with an object and one of its attribute: textilizable(issue, :description, options)
486 def textilizable(*args)
486 def textilizable(*args)
487 options = args.last.is_a?(Hash) ? args.pop : {}
487 options = args.last.is_a?(Hash) ? args.pop : {}
488 case args.size
488 case args.size
489 when 1
489 when 1
490 obj = options[:object]
490 obj = options[:object]
491 text = args.shift
491 text = args.shift
492 when 2
492 when 2
493 obj = args.shift
493 obj = args.shift
494 attr = args.shift
494 attr = args.shift
495 text = obj.send(attr).to_s
495 text = obj.send(attr).to_s
496 else
496 else
497 raise ArgumentError, 'invalid arguments to textilizable'
497 raise ArgumentError, 'invalid arguments to textilizable'
498 end
498 end
499 return '' if text.blank?
499 return '' if text.blank?
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
500 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
501 only_path = options.delete(:only_path) == false ? false : true
501 only_path = options.delete(:only_path) == false ? false : true
502
502
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
503 text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr)
504
504
505 @parsed_headings = []
505 @parsed_headings = []
506 @current_section = 0 if options[:edit_section_links]
506 @current_section = 0 if options[:edit_section_links]
507 text = parse_non_pre_blocks(text) do |text|
507 text = parse_non_pre_blocks(text) do |text|
508 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
508 [:parse_sections, :parse_inline_attachments, :parse_wiki_links, :parse_redmine_links, :parse_macros, :parse_headings].each do |method_name|
509 send method_name, text, project, obj, attr, only_path, options
509 send method_name, text, project, obj, attr, only_path, options
510 end
510 end
511 end
511 end
512
512
513 if @parsed_headings.any?
513 if @parsed_headings.any?
514 replace_toc(text, @parsed_headings)
514 replace_toc(text, @parsed_headings)
515 end
515 end
516
516
517 text.html_safe
517 text.html_safe
518 end
518 end
519
519
520 def parse_non_pre_blocks(text)
520 def parse_non_pre_blocks(text)
521 s = StringScanner.new(text)
521 s = StringScanner.new(text)
522 tags = []
522 tags = []
523 parsed = ''
523 parsed = ''
524 while !s.eos?
524 while !s.eos?
525 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
525 s.scan(/(.*?)(<(\/)?(pre|code)(.*?)>|\z)/im)
526 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
526 text, full_tag, closing, tag = s[1], s[2], s[3], s[4]
527 if tags.empty?
527 if tags.empty?
528 yield text
528 yield text
529 end
529 end
530 parsed << text
530 parsed << text
531 if tag
531 if tag
532 if closing
532 if closing
533 if tags.last == tag.downcase
533 if tags.last == tag.downcase
534 tags.pop
534 tags.pop
535 end
535 end
536 else
536 else
537 tags << tag.downcase
537 tags << tag.downcase
538 end
538 end
539 parsed << full_tag
539 parsed << full_tag
540 end
540 end
541 end
541 end
542 # Close any non closing tags
542 # Close any non closing tags
543 while tag = tags.pop
543 while tag = tags.pop
544 parsed << "</#{tag}>"
544 parsed << "</#{tag}>"
545 end
545 end
546 parsed.html_safe
546 parsed.html_safe
547 end
547 end
548
548
549 def parse_inline_attachments(text, project, obj, attr, only_path, options)
549 def parse_inline_attachments(text, project, obj, attr, only_path, options)
550 # when using an image link, try to use an attachment, if possible
550 # when using an image link, try to use an attachment, if possible
551 if options[:attachments] || (obj && obj.respond_to?(:attachments))
551 if options[:attachments] || (obj && obj.respond_to?(:attachments))
552 attachments = options[:attachments] || obj.attachments
552 attachments = options[:attachments] || obj.attachments
553 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
553 text.gsub!(/src="([^\/"]+\.(bmp|gif|jpg|jpe|jpeg|png))"(\s+alt="([^"]*)")?/i) do |m|
554 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
554 filename, ext, alt, alttext = $1.downcase, $2, $3, $4
555 # search for the picture in attachments
555 # search for the picture in attachments
556 if found = Attachment.latest_attach(attachments, filename)
556 if found = Attachment.latest_attach(attachments, filename)
557 image_url = url_for :only_path => only_path, :controller => 'attachments',
557 image_url = url_for :only_path => only_path, :controller => 'attachments',
558 :action => 'download', :id => found
558 :action => 'download', :id => found
559 desc = found.description.to_s.gsub('"', '')
559 desc = found.description.to_s.gsub('"', '')
560 if !desc.blank? && alttext.blank?
560 if !desc.blank? && alttext.blank?
561 alt = " title=\"#{desc}\" alt=\"#{desc}\""
561 alt = " title=\"#{desc}\" alt=\"#{desc}\""
562 end
562 end
563 "src=\"#{image_url}\"#{alt}".html_safe
563 "src=\"#{image_url}\"#{alt}".html_safe
564 else
564 else
565 m.html_safe
565 m.html_safe
566 end
566 end
567 end
567 end
568 end
568 end
569 end
569 end
570
570
571 # Wiki links
571 # Wiki links
572 #
572 #
573 # Examples:
573 # Examples:
574 # [[mypage]]
574 # [[mypage]]
575 # [[mypage|mytext]]
575 # [[mypage|mytext]]
576 # wiki links can refer other project wikis, using project name or identifier:
576 # wiki links can refer other project wikis, using project name or identifier:
577 # [[project:]] -> wiki starting page
577 # [[project:]] -> wiki starting page
578 # [[project:|mytext]]
578 # [[project:|mytext]]
579 # [[project:mypage]]
579 # [[project:mypage]]
580 # [[project:mypage|mytext]]
580 # [[project:mypage|mytext]]
581 def parse_wiki_links(text, project, obj, attr, only_path, options)
581 def parse_wiki_links(text, project, obj, attr, only_path, options)
582 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
582 text.gsub!(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
583 link_project = project
583 link_project = project
584 esc, all, page, title = $1, $2, $3, $5
584 esc, all, page, title = $1, $2, $3, $5
585 if esc.nil?
585 if esc.nil?
586 if page =~ /^([^\:]+)\:(.*)$/
586 if page =~ /^([^\:]+)\:(.*)$/
587 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
587 link_project = Project.find_by_identifier($1) || Project.find_by_name($1)
588 page = $2
588 page = $2
589 title ||= $1 if page.blank?
589 title ||= $1 if page.blank?
590 end
590 end
591
591
592 if link_project && link_project.wiki
592 if link_project && link_project.wiki
593 # extract anchor
593 # extract anchor
594 anchor = nil
594 anchor = nil
595 if page =~ /^(.+?)\#(.+)$/
595 if page =~ /^(.+?)\#(.+)$/
596 page, anchor = $1, $2
596 page, anchor = $1, $2
597 end
597 end
598 anchor = sanitize_anchor_name(anchor) if anchor.present?
598 anchor = sanitize_anchor_name(anchor) if anchor.present?
599 # check if page exists
599 # check if page exists
600 wiki_page = link_project.wiki.find_page(page)
600 wiki_page = link_project.wiki.find_page(page)
601 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
601 url = if anchor.present? && wiki_page.present? && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version)) && obj.page == wiki_page
602 "##{anchor}"
602 "##{anchor}"
603 else
603 else
604 case options[:wiki_links]
604 case options[:wiki_links]
605 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
605 when :local; "#{page.present? ? Wiki.titleize(page) : ''}.html" + (anchor.present? ? "##{anchor}" : '')
606 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
606 when :anchor; "##{page.present? ? Wiki.titleize(page) : title}" + (anchor.present? ? "_#{anchor}" : '') # used for single-file wiki export
607 else
607 else
608 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
608 wiki_page_id = page.present? ? Wiki.titleize(page) : nil
609 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
609 parent = wiki_page.nil? && obj.is_a?(WikiContent) && obj.page && project == link_project ? obj.page.title : nil
610 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
610 url_for(:only_path => only_path, :controller => 'wiki', :action => 'show', :project_id => link_project,
611 :id => wiki_page_id, :anchor => anchor, :parent => parent)
611 :id => wiki_page_id, :anchor => anchor, :parent => parent)
612 end
612 end
613 end
613 end
614 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
614 link_to(title.present? ? title.html_safe : h(page), url, :class => ('wiki-page' + (wiki_page ? '' : ' new')))
615 else
615 else
616 # project or wiki doesn't exist
616 # project or wiki doesn't exist
617 all.html_safe
617 all.html_safe
618 end
618 end
619 else
619 else
620 all.html_safe
620 all.html_safe
621 end
621 end
622 end
622 end
623 end
623 end
624
624
625 # Redmine links
625 # Redmine links
626 #
626 #
627 # Examples:
627 # Examples:
628 # Issues:
628 # Issues:
629 # #52 -> Link to issue #52
629 # #52 -> Link to issue #52
630 # Changesets:
630 # Changesets:
631 # r52 -> Link to revision 52
631 # r52 -> Link to revision 52
632 # commit:a85130f -> Link to scmid starting with a85130f
632 # commit:a85130f -> Link to scmid starting with a85130f
633 # Documents:
633 # Documents:
634 # document#17 -> Link to document with id 17
634 # document#17 -> Link to document with id 17
635 # document:Greetings -> Link to the document with title "Greetings"
635 # document:Greetings -> Link to the document with title "Greetings"
636 # document:"Some document" -> Link to the document with title "Some document"
636 # document:"Some document" -> Link to the document with title "Some document"
637 # Versions:
637 # Versions:
638 # version#3 -> Link to version with id 3
638 # version#3 -> Link to version with id 3
639 # version:1.0.0 -> Link to version named "1.0.0"
639 # version:1.0.0 -> Link to version named "1.0.0"
640 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
640 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
641 # Attachments:
641 # Attachments:
642 # attachment:file.zip -> Link to the attachment of the current object named file.zip
642 # attachment:file.zip -> Link to the attachment of the current object named file.zip
643 # Source files:
643 # Source files:
644 # source:some/file -> Link to the file located at /some/file in the project's repository
644 # source:some/file -> Link to the file located at /some/file in the project's repository
645 # source:some/file@52 -> Link to the file's revision 52
645 # source:some/file@52 -> Link to the file's revision 52
646 # source:some/file#L120 -> Link to line 120 of the file
646 # source:some/file#L120 -> Link to line 120 of the file
647 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
647 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
648 # export:some/file -> Force the download of the file
648 # export:some/file -> Force the download of the file
649 # Forum messages:
649 # Forum messages:
650 # message#1218 -> Link to message with id 1218
650 # message#1218 -> Link to message with id 1218
651 #
651 #
652 # Links can refer other objects from other projects, using project identifier:
652 # Links can refer other objects from other projects, using project identifier:
653 # identifier:r52
653 # identifier:r52
654 # identifier:document:"Some document"
654 # identifier:document:"Some document"
655 # identifier:version:1.0.0
655 # identifier:version:1.0.0
656 # identifier:source:some/file
656 # identifier:source:some/file
657 def parse_redmine_links(text, project, obj, attr, only_path, options)
657 def parse_redmine_links(text, project, obj, attr, only_path, options)
658 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|commit|source|export|message|project)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|,|\s|\]|<|$)}) do |m|
658 text.gsub!(%r{([\s\(,\-\[\>]|^)(!)?(([a-z0-9\-]+):)?(attachment|document|version|forum|news|message|project|commit|source|export)?(((#)|((([a-z0-9\-]+)\|)?(r)))(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]][^A-Za-z0-9_/])|,|\s|\]|<|$)}) do |m|
659 leading, esc, project_prefix, project_identifier, prefix, sep, identifier = $1, $2, $3, $4, $5, $7 || $9, $8 || $10
659 leading, esc, project_prefix, project_identifier, prefix, repo_prefix, repo_identifier, sep, identifier = $1, $2, $3, $4, $5, $10, $11, $8 || $12 || $14, $13 || $15
660 link = nil
660 link = nil
661 if project_identifier
661 if project_identifier
662 project = Project.visible.find_by_identifier(project_identifier)
662 project = Project.visible.find_by_identifier(project_identifier)
663 end
663 end
664 if esc.nil?
664 if esc.nil?
665 if prefix.nil? && sep == 'r'
665 if prefix.nil? && sep == 'r'
666 # project.changesets.visible raises an SQL error because of a double join on repositories
666 if project
667 if project && project.repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(project.repository.id, identifier))
667 repository = nil
668 link = link_to(h("#{project_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
668 if repo_identifier
669 :class => 'changeset',
669 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
670 :title => truncate_single_line(changeset.comments, :length => 100))
670 else
671 repository = project.repository
672 end
673 # project.changesets.visible raises an SQL error because of a double join on repositories
674 if repository && (changeset = Changeset.visible.find_by_repository_id_and_revision(repository.id, identifier))
675 link = link_to(h("#{project_prefix}#{repo_prefix}r#{identifier}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.revision},
676 :class => 'changeset',
677 :title => truncate_single_line(changeset.comments, :length => 100))
678 end
671 end
679 end
672 elsif sep == '#'
680 elsif sep == '#'
673 oid = identifier.to_i
681 oid = identifier.to_i
674 case prefix
682 case prefix
675 when nil
683 when nil
676 if issue = Issue.visible.find_by_id(oid, :include => :status)
684 if issue = Issue.visible.find_by_id(oid, :include => :status)
677 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
685 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
678 :class => issue.css_classes,
686 :class => issue.css_classes,
679 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
687 :title => "#{truncate(issue.subject, :length => 100)} (#{issue.status.name})")
680 end
688 end
681 when 'document'
689 when 'document'
682 if document = Document.visible.find_by_id(oid)
690 if document = Document.visible.find_by_id(oid)
683 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
691 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
684 :class => 'document'
692 :class => 'document'
685 end
693 end
686 when 'version'
694 when 'version'
687 if version = Version.visible.find_by_id(oid)
695 if version = Version.visible.find_by_id(oid)
688 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
696 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
689 :class => 'version'
697 :class => 'version'
690 end
698 end
691 when 'message'
699 when 'message'
692 if message = Message.visible.find_by_id(oid, :include => :parent)
700 if message = Message.visible.find_by_id(oid, :include => :parent)
693 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
701 link = link_to_message(message, {:only_path => only_path}, :class => 'message')
694 end
702 end
695 when 'forum'
703 when 'forum'
696 if board = Board.visible.find_by_id(oid)
704 if board = Board.visible.find_by_id(oid)
697 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
705 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
698 :class => 'board'
706 :class => 'board'
699 end
707 end
700 when 'news'
708 when 'news'
701 if news = News.visible.find_by_id(oid)
709 if news = News.visible.find_by_id(oid)
702 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
710 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
703 :class => 'news'
711 :class => 'news'
704 end
712 end
705 when 'project'
713 when 'project'
706 if p = Project.visible.find_by_id(oid)
714 if p = Project.visible.find_by_id(oid)
707 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
715 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
708 end
716 end
709 end
717 end
710 elsif sep == ':'
718 elsif sep == ':'
711 # removes the double quotes if any
719 # removes the double quotes if any
712 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
720 name = identifier.gsub(%r{^"(.*)"$}, "\\1")
713 case prefix
721 case prefix
714 when 'document'
722 when 'document'
715 if project && document = project.documents.visible.find_by_title(name)
723 if project && document = project.documents.visible.find_by_title(name)
716 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
724 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
717 :class => 'document'
725 :class => 'document'
718 end
726 end
719 when 'version'
727 when 'version'
720 if project && version = project.versions.visible.find_by_name(name)
728 if project && version = project.versions.visible.find_by_name(name)
721 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
729 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
722 :class => 'version'
730 :class => 'version'
723 end
731 end
724 when 'forum'
732 when 'forum'
725 if project && board = project.boards.visible.find_by_name(name)
733 if project && board = project.boards.visible.find_by_name(name)
726 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
734 link = link_to h(board.name), {:only_path => only_path, :controller => 'boards', :action => 'show', :id => board, :project_id => board.project},
727 :class => 'board'
735 :class => 'board'
728 end
736 end
729 when 'news'
737 when 'news'
730 if project && news = project.news.visible.find_by_title(name)
738 if project && news = project.news.visible.find_by_title(name)
731 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
739 link = link_to h(news.title), {:only_path => only_path, :controller => 'news', :action => 'show', :id => news},
732 :class => 'news'
740 :class => 'news'
733 end
741 end
734 when 'commit'
742 when 'commit', 'source', 'export'
735 if project && project.repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", project.repository.id, "#{name}%"]))
743 if project
736 link = link_to h("#{project_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.identifier},
744 repository = nil
737 :class => 'changeset',
745 if name =~ %r{^(([a-z0-9\-]+)\|)(.+)$}
738 :title => truncate_single_line(h(changeset.comments), :length => 100)
746 repo_prefix, repo_identifier, name = $1, $2, $3
739 end
747 repository = project.repositories.detect {|repo| repo.identifier == repo_identifier}
740 when 'source', 'export'
748 else
741 if project && project.repository && User.current.allowed_to?(:browse_repository, project)
749 repository = project.repository
742 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
750 end
743 path, rev, anchor = $1, $3, $5
751 if prefix == 'commit'
744 link = link_to h("#{project_prefix}#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
752 if repository && (changeset = Changeset.visible.find(:first, :conditions => ["repository_id = ? AND scmid LIKE ?", repository.id, "#{name}%"]))
745 :path => to_path_param(path),
753 link = link_to h("#{project_prefix}#{repo_prefix}#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :repository_id => repository.identifier_param, :rev => changeset.identifier},
746 :rev => rev,
754 :class => 'changeset',
747 :anchor => anchor,
755 :title => truncate_single_line(h(changeset.comments), :length => 100)
748 :format => (prefix == 'export' ? 'raw' : nil)},
756 end
749 :class => (prefix == 'export' ? 'source download' : 'source')
757 else
758 if repository && User.current.allowed_to?(:browse_repository, project)
759 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
760 path, rev, anchor = $1, $3, $5
761 link = link_to h("#{project_prefix}#{prefix}:#{repo_prefix}#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :repository_id => repository.identifier_param,
762 :path => to_path_param(path),
763 :rev => rev,
764 :anchor => anchor,
765 :format => (prefix == 'export' ? 'raw' : nil)},
766 :class => (prefix == 'export' ? 'source download' : 'source')
767 end
768 end
769 repo_prefix = nil
750 end
770 end
751 when 'attachment'
771 when 'attachment'
752 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
772 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
753 if attachments && attachment = attachments.detect {|a| a.filename == name }
773 if attachments && attachment = attachments.detect {|a| a.filename == name }
754 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
774 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
755 :class => 'attachment'
775 :class => 'attachment'
756 end
776 end
757 when 'project'
777 when 'project'
758 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
778 if p = Project.visible.find(:first, :conditions => ["identifier = :s OR LOWER(name) = :s", {:s => name.downcase}])
759 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
779 link = link_to_project(p, {:only_path => only_path}, :class => 'project')
760 end
780 end
761 end
781 end
762 end
782 end
763 end
783 end
764 (leading + (link || "#{project_prefix}#{prefix}#{sep}#{identifier}")).html_safe
784 (leading + (link || "#{project_prefix}#{prefix}#{repo_prefix}#{sep}#{identifier}")).html_safe
765 end
785 end
766 end
786 end
767
787
768 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
788 HEADING_RE = /(<h(1|2|3|4)( [^>]+)?>(.+?)<\/h(1|2|3|4)>)/i unless const_defined?(:HEADING_RE)
769
789
770 def parse_sections(text, project, obj, attr, only_path, options)
790 def parse_sections(text, project, obj, attr, only_path, options)
771 return unless options[:edit_section_links]
791 return unless options[:edit_section_links]
772 text.gsub!(HEADING_RE) do
792 text.gsub!(HEADING_RE) do
773 @current_section += 1
793 @current_section += 1
774 if @current_section > 1
794 if @current_section > 1
775 content_tag('div',
795 content_tag('div',
776 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
796 link_to(image_tag('edit.png'), options[:edit_section_links].merge(:section => @current_section)),
777 :class => 'contextual',
797 :class => 'contextual',
778 :title => l(:button_edit_section)) + $1
798 :title => l(:button_edit_section)) + $1
779 else
799 else
780 $1
800 $1
781 end
801 end
782 end
802 end
783 end
803 end
784
804
785 # Headings and TOC
805 # Headings and TOC
786 # Adds ids and links to headings unless options[:headings] is set to false
806 # Adds ids and links to headings unless options[:headings] is set to false
787 def parse_headings(text, project, obj, attr, only_path, options)
807 def parse_headings(text, project, obj, attr, only_path, options)
788 return if options[:headings] == false
808 return if options[:headings] == false
789
809
790 text.gsub!(HEADING_RE) do
810 text.gsub!(HEADING_RE) do
791 level, attrs, content = $2.to_i, $3, $4
811 level, attrs, content = $2.to_i, $3, $4
792 item = strip_tags(content).strip
812 item = strip_tags(content).strip
793 anchor = sanitize_anchor_name(item)
813 anchor = sanitize_anchor_name(item)
794 # used for single-file wiki export
814 # used for single-file wiki export
795 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
815 anchor = "#{obj.page.title}_#{anchor}" if options[:wiki_links] == :anchor && (obj.is_a?(WikiContent) || obj.is_a?(WikiContent::Version))
796 @parsed_headings << [level, anchor, item]
816 @parsed_headings << [level, anchor, item]
797 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
817 "<a name=\"#{anchor}\"></a>\n<h#{level} #{attrs}>#{content}<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a></h#{level}>"
798 end
818 end
799 end
819 end
800
820
801 MACROS_RE = /
821 MACROS_RE = /
802 (!)? # escaping
822 (!)? # escaping
803 (
823 (
804 \{\{ # opening tag
824 \{\{ # opening tag
805 ([\w]+) # macro name
825 ([\w]+) # macro name
806 (\(([^\}]*)\))? # optional arguments
826 (\(([^\}]*)\))? # optional arguments
807 \}\} # closing tag
827 \}\} # closing tag
808 )
828 )
809 /x unless const_defined?(:MACROS_RE)
829 /x unless const_defined?(:MACROS_RE)
810
830
811 # Macros substitution
831 # Macros substitution
812 def parse_macros(text, project, obj, attr, only_path, options)
832 def parse_macros(text, project, obj, attr, only_path, options)
813 text.gsub!(MACROS_RE) do
833 text.gsub!(MACROS_RE) do
814 esc, all, macro = $1, $2, $3.downcase
834 esc, all, macro = $1, $2, $3.downcase
815 args = ($5 || '').split(',').each(&:strip)
835 args = ($5 || '').split(',').each(&:strip)
816 if esc.nil?
836 if esc.nil?
817 begin
837 begin
818 exec_macro(macro, obj, args)
838 exec_macro(macro, obj, args)
819 rescue => e
839 rescue => e
820 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
840 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
821 end || all
841 end || all
822 else
842 else
823 all
843 all
824 end
844 end
825 end
845 end
826 end
846 end
827
847
828 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
848 TOC_RE = /<p>\{\{([<>]?)toc\}\}<\/p>/i unless const_defined?(:TOC_RE)
829
849
830 # Renders the TOC with given headings
850 # Renders the TOC with given headings
831 def replace_toc(text, headings)
851 def replace_toc(text, headings)
832 text.gsub!(TOC_RE) do
852 text.gsub!(TOC_RE) do
833 if headings.empty?
853 if headings.empty?
834 ''
854 ''
835 else
855 else
836 div_class = 'toc'
856 div_class = 'toc'
837 div_class << ' right' if $1 == '>'
857 div_class << ' right' if $1 == '>'
838 div_class << ' left' if $1 == '<'
858 div_class << ' left' if $1 == '<'
839 out = "<ul class=\"#{div_class}\"><li>"
859 out = "<ul class=\"#{div_class}\"><li>"
840 root = headings.map(&:first).min
860 root = headings.map(&:first).min
841 current = root
861 current = root
842 started = false
862 started = false
843 headings.each do |level, anchor, item|
863 headings.each do |level, anchor, item|
844 if level > current
864 if level > current
845 out << '<ul><li>' * (level - current)
865 out << '<ul><li>' * (level - current)
846 elsif level < current
866 elsif level < current
847 out << "</li></ul>\n" * (current - level) + "</li><li>"
867 out << "</li></ul>\n" * (current - level) + "</li><li>"
848 elsif started
868 elsif started
849 out << '</li><li>'
869 out << '</li><li>'
850 end
870 end
851 out << "<a href=\"##{anchor}\">#{item}</a>"
871 out << "<a href=\"##{anchor}\">#{item}</a>"
852 current = level
872 current = level
853 started = true
873 started = true
854 end
874 end
855 out << '</li></ul>' * (current - root)
875 out << '</li></ul>' * (current - root)
856 out << '</li></ul>'
876 out << '</li></ul>'
857 end
877 end
858 end
878 end
859 end
879 end
860
880
861 # Same as Rails' simple_format helper without using paragraphs
881 # Same as Rails' simple_format helper without using paragraphs
862 def simple_format_without_paragraph(text)
882 def simple_format_without_paragraph(text)
863 text.to_s.
883 text.to_s.
864 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
884 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
865 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
885 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
866 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
886 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />'). # 1 newline -> br
867 html_safe
887 html_safe
868 end
888 end
869
889
870 def lang_options_for_select(blank=true)
890 def lang_options_for_select(blank=true)
871 (blank ? [["(auto)", ""]] : []) +
891 (blank ? [["(auto)", ""]] : []) +
872 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
892 valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
873 end
893 end
874
894
875 def label_tag_for(name, option_tags = nil, options = {})
895 def label_tag_for(name, option_tags = nil, options = {})
876 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
896 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
877 content_tag("label", label_text)
897 content_tag("label", label_text)
878 end
898 end
879
899
880 def labelled_tabular_form_for(*args, &proc)
900 def labelled_tabular_form_for(*args, &proc)
881 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
901 ActiveSupport::Deprecation.warn "ApplicationHelper#labelled_tabular_form_for is deprecated and will be removed in Redmine 1.5. Use #labelled_form_for instead."
882 args << {} unless args.last.is_a?(Hash)
902 args << {} unless args.last.is_a?(Hash)
883 options = args.last
903 options = args.last
884 options[:html] ||= {}
904 options[:html] ||= {}
885 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
905 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
886 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
906 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
887 form_for(*args, &proc)
907 form_for(*args, &proc)
888 end
908 end
889
909
890 def labelled_form_for(*args, &proc)
910 def labelled_form_for(*args, &proc)
891 args << {} unless args.last.is_a?(Hash)
911 args << {} unless args.last.is_a?(Hash)
892 options = args.last
912 options = args.last
893 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
913 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
894 form_for(*args, &proc)
914 form_for(*args, &proc)
895 end
915 end
896
916
897 def labelled_fields_for(*args, &proc)
917 def labelled_fields_for(*args, &proc)
898 args << {} unless args.last.is_a?(Hash)
918 args << {} unless args.last.is_a?(Hash)
899 options = args.last
919 options = args.last
900 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
920 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
901 fields_for(*args, &proc)
921 fields_for(*args, &proc)
902 end
922 end
903
923
904 def labelled_remote_form_for(*args, &proc)
924 def labelled_remote_form_for(*args, &proc)
905 args << {} unless args.last.is_a?(Hash)
925 args << {} unless args.last.is_a?(Hash)
906 options = args.last
926 options = args.last
907 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
927 options.merge!({:builder => Redmine::Views::LabelledFormBuilder})
908 remote_form_for(*args, &proc)
928 remote_form_for(*args, &proc)
909 end
929 end
910
930
911 def back_url_hidden_field_tag
931 def back_url_hidden_field_tag
912 back_url = params[:back_url] || request.env['HTTP_REFERER']
932 back_url = params[:back_url] || request.env['HTTP_REFERER']
913 back_url = CGI.unescape(back_url.to_s)
933 back_url = CGI.unescape(back_url.to_s)
914 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
934 hidden_field_tag('back_url', CGI.escape(back_url)) unless back_url.blank?
915 end
935 end
916
936
917 def check_all_links(form_name)
937 def check_all_links(form_name)
918 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
938 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
919 " | ".html_safe +
939 " | ".html_safe +
920 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
940 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
921 end
941 end
922
942
923 def progress_bar(pcts, options={})
943 def progress_bar(pcts, options={})
924 pcts = [pcts, pcts] unless pcts.is_a?(Array)
944 pcts = [pcts, pcts] unless pcts.is_a?(Array)
925 pcts = pcts.collect(&:round)
945 pcts = pcts.collect(&:round)
926 pcts[1] = pcts[1] - pcts[0]
946 pcts[1] = pcts[1] - pcts[0]
927 pcts << (100 - pcts[1] - pcts[0])
947 pcts << (100 - pcts[1] - pcts[0])
928 width = options[:width] || '100px;'
948 width = options[:width] || '100px;'
929 legend = options[:legend] || ''
949 legend = options[:legend] || ''
930 content_tag('table',
950 content_tag('table',
931 content_tag('tr',
951 content_tag('tr',
932 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
952 (pcts[0] > 0 ? content_tag('td', '', :style => "width: #{pcts[0]}%;", :class => 'closed') : ''.html_safe) +
933 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
953 (pcts[1] > 0 ? content_tag('td', '', :style => "width: #{pcts[1]}%;", :class => 'done') : ''.html_safe) +
934 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
954 (pcts[2] > 0 ? content_tag('td', '', :style => "width: #{pcts[2]}%;", :class => 'todo') : ''.html_safe)
935 ), :class => 'progress', :style => "width: #{width};").html_safe +
955 ), :class => 'progress', :style => "width: #{width};").html_safe +
936 content_tag('p', legend, :class => 'pourcent').html_safe
956 content_tag('p', legend, :class => 'pourcent').html_safe
937 end
957 end
938
958
939 def checked_image(checked=true)
959 def checked_image(checked=true)
940 if checked
960 if checked
941 image_tag 'toggle_check.png'
961 image_tag 'toggle_check.png'
942 end
962 end
943 end
963 end
944
964
945 def context_menu(url)
965 def context_menu(url)
946 unless @context_menu_included
966 unless @context_menu_included
947 content_for :header_tags do
967 content_for :header_tags do
948 javascript_include_tag('context_menu') +
968 javascript_include_tag('context_menu') +
949 stylesheet_link_tag('context_menu')
969 stylesheet_link_tag('context_menu')
950 end
970 end
951 if l(:direction) == 'rtl'
971 if l(:direction) == 'rtl'
952 content_for :header_tags do
972 content_for :header_tags do
953 stylesheet_link_tag('context_menu_rtl')
973 stylesheet_link_tag('context_menu_rtl')
954 end
974 end
955 end
975 end
956 @context_menu_included = true
976 @context_menu_included = true
957 end
977 end
958 javascript_tag "new ContextMenu('#{ url_for(url) }')"
978 javascript_tag "new ContextMenu('#{ url_for(url) }')"
959 end
979 end
960
980
961 def context_menu_link(name, url, options={})
981 def context_menu_link(name, url, options={})
962 options[:class] ||= ''
982 options[:class] ||= ''
963 if options.delete(:selected)
983 if options.delete(:selected)
964 options[:class] << ' icon-checked disabled'
984 options[:class] << ' icon-checked disabled'
965 options[:disabled] = true
985 options[:disabled] = true
966 end
986 end
967 if options.delete(:disabled)
987 if options.delete(:disabled)
968 options.delete(:method)
988 options.delete(:method)
969 options.delete(:confirm)
989 options.delete(:confirm)
970 options.delete(:onclick)
990 options.delete(:onclick)
971 options[:class] << ' disabled'
991 options[:class] << ' disabled'
972 url = '#'
992 url = '#'
973 end
993 end
974 link_to h(name), url, options
994 link_to h(name), url, options
975 end
995 end
976
996
977 def calendar_for(field_id)
997 def calendar_for(field_id)
978 include_calendar_headers_tags
998 include_calendar_headers_tags
979 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
999 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
980 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
1000 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
981 end
1001 end
982
1002
983 def include_calendar_headers_tags
1003 def include_calendar_headers_tags
984 unless @calendar_headers_tags_included
1004 unless @calendar_headers_tags_included
985 @calendar_headers_tags_included = true
1005 @calendar_headers_tags_included = true
986 content_for :header_tags do
1006 content_for :header_tags do
987 start_of_week = case Setting.start_of_week.to_i
1007 start_of_week = case Setting.start_of_week.to_i
988 when 1
1008 when 1
989 'Calendar._FD = 1;' # Monday
1009 'Calendar._FD = 1;' # Monday
990 when 7
1010 when 7
991 'Calendar._FD = 0;' # Sunday
1011 'Calendar._FD = 0;' # Sunday
992 when 6
1012 when 6
993 'Calendar._FD = 6;' # Saturday
1013 'Calendar._FD = 6;' # Saturday
994 else
1014 else
995 '' # use language
1015 '' # use language
996 end
1016 end
997
1017
998 javascript_include_tag('calendar/calendar') +
1018 javascript_include_tag('calendar/calendar') +
999 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1019 javascript_include_tag("calendar/lang/calendar-#{current_language.to_s.downcase}.js") +
1000 javascript_tag(start_of_week) +
1020 javascript_tag(start_of_week) +
1001 javascript_include_tag('calendar/calendar-setup') +
1021 javascript_include_tag('calendar/calendar-setup') +
1002 stylesheet_link_tag('calendar')
1022 stylesheet_link_tag('calendar')
1003 end
1023 end
1004 end
1024 end
1005 end
1025 end
1006
1026
1007 def content_for(name, content = nil, &block)
1027 def content_for(name, content = nil, &block)
1008 @has_content ||= {}
1028 @has_content ||= {}
1009 @has_content[name] = true
1029 @has_content[name] = true
1010 super(name, content, &block)
1030 super(name, content, &block)
1011 end
1031 end
1012
1032
1013 def has_content?(name)
1033 def has_content?(name)
1014 (@has_content && @has_content[name]) || false
1034 (@has_content && @has_content[name]) || false
1015 end
1035 end
1016
1036
1017 def email_delivery_enabled?
1037 def email_delivery_enabled?
1018 !!ActionMailer::Base.perform_deliveries
1038 !!ActionMailer::Base.perform_deliveries
1019 end
1039 end
1020
1040
1021 # Returns the avatar image tag for the given +user+ if avatars are enabled
1041 # Returns the avatar image tag for the given +user+ if avatars are enabled
1022 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1042 # +user+ can be a User or a string that will be scanned for an email address (eg. 'joe <joe@foo.bar>')
1023 def avatar(user, options = { })
1043 def avatar(user, options = { })
1024 if Setting.gravatar_enabled?
1044 if Setting.gravatar_enabled?
1025 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1045 options.merge!({:ssl => (defined?(request) && request.ssl?), :default => Setting.gravatar_default})
1026 email = nil
1046 email = nil
1027 if user.respond_to?(:mail)
1047 if user.respond_to?(:mail)
1028 email = user.mail
1048 email = user.mail
1029 elsif user.to_s =~ %r{<(.+?)>}
1049 elsif user.to_s =~ %r{<(.+?)>}
1030 email = $1
1050 email = $1
1031 end
1051 end
1032 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1052 return gravatar(email.to_s.downcase, options) unless email.blank? rescue nil
1033 else
1053 else
1034 ''
1054 ''
1035 end
1055 end
1036 end
1056 end
1037
1057
1038 def sanitize_anchor_name(anchor)
1058 def sanitize_anchor_name(anchor)
1039 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1059 anchor.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
1040 end
1060 end
1041
1061
1042 # Returns the javascript tags that are included in the html layout head
1062 # Returns the javascript tags that are included in the html layout head
1043 def javascript_heads
1063 def javascript_heads
1044 tags = javascript_include_tag(:defaults)
1064 tags = javascript_include_tag(:defaults)
1045 unless User.current.pref.warn_on_leaving_unsaved == '0'
1065 unless User.current.pref.warn_on_leaving_unsaved == '0'
1046 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1066 tags << "\n".html_safe + javascript_tag("Event.observe(window, 'load', function(){ new WarnLeavingUnsaved('#{escape_javascript( l(:text_warn_on_leaving_unsaved) )}'); });")
1047 end
1067 end
1048 tags
1068 tags
1049 end
1069 end
1050
1070
1051 def favicon
1071 def favicon
1052 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1072 "<link rel='shortcut icon' href='#{image_path('/favicon.ico')}' />".html_safe
1053 end
1073 end
1054
1074
1055 def robot_exclusion_tag
1075 def robot_exclusion_tag
1056 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1076 '<meta name="robots" content="noindex,follow,noarchive" />'.html_safe
1057 end
1077 end
1058
1078
1059 # Returns true if arg is expected in the API response
1079 # Returns true if arg is expected in the API response
1060 def include_in_api_response?(arg)
1080 def include_in_api_response?(arg)
1061 unless @included_in_api_response
1081 unless @included_in_api_response
1062 param = params[:include]
1082 param = params[:include]
1063 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1083 @included_in_api_response = param.is_a?(Array) ? param.collect(&:to_s) : param.to_s.split(',')
1064 @included_in_api_response.collect!(&:strip)
1084 @included_in_api_response.collect!(&:strip)
1065 end
1085 end
1066 @included_in_api_response.include?(arg.to_s)
1086 @included_in_api_response.include?(arg.to_s)
1067 end
1087 end
1068
1088
1069 # Returns options or nil if nometa param or X-Redmine-Nometa header
1089 # Returns options or nil if nometa param or X-Redmine-Nometa header
1070 # was set in the request
1090 # was set in the request
1071 def api_meta(options)
1091 def api_meta(options)
1072 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1092 if params[:nometa].present? || request.headers['X-Redmine-Nometa']
1073 # compatibility mode for activeresource clients that raise
1093 # compatibility mode for activeresource clients that raise
1074 # an error when unserializing an array with attributes
1094 # an error when unserializing an array with attributes
1075 nil
1095 nil
1076 else
1096 else
1077 options
1097 options
1078 end
1098 end
1079 end
1099 end
1080
1100
1081 private
1101 private
1082
1102
1083 def wiki_helper
1103 def wiki_helper
1084 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1104 helper = Redmine::WikiFormatting.helper_for(Setting.text_formatting)
1085 extend helper
1105 extend helper
1086 return self
1106 return self
1087 end
1107 end
1088
1108
1089 def link_to_content_update(text, url_params = {}, html_options = {})
1109 def link_to_content_update(text, url_params = {}, html_options = {})
1090 link_to(text, url_params, html_options)
1110 link_to(text, url_params, html_options)
1091 end
1111 end
1092 end
1112 end
@@ -1,271 +1,280
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3 <head>
3 <head>
4 <title>RedmineWikiFormatting</title>
4 <title>RedmineWikiFormatting</title>
5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5 <meta http-equiv="content-type" content="text/html; charset=utf-8" />
6 <style type="text/css">
6 <style type="text/css">
7 body { font:80% Verdana,Tahoma,Arial,sans-serif; }
7 body { font:80% Verdana,Tahoma,Arial,sans-serif; }
8 h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
8 h1, h2, h3, h4 { font-family: Trebuchet MS,Georgia,"Times New Roman",serif; }
9 pre, code { font-size:120%; }
9 pre, code { font-size:120%; }
10 pre code { font-size:100%; }
10 pre code { font-size:100%; }
11 pre {
11 pre {
12 margin: 1em 1em 1em 1.6em;
12 margin: 1em 1em 1em 1.6em;
13 padding: 2px;
13 padding: 2px;
14 background-color: #fafafa;
14 background-color: #fafafa;
15 border: 1px solid #dadada;
15 border: 1px solid #dadada;
16 width:95%;
16 width:95%;
17 overflow-x: auto;
17 overflow-x: auto;
18 }
18 }
19 a.new { color: #b73535; }
19 a.new { color: #b73535; }
20
20
21 .syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 }
21 .syntaxhl .line-numbers { padding: 2px 4px 2px 4px; background-color: #eee; margin:0 }
22 .syntaxhl .comment { color:#666; }
22 .syntaxhl .comment { color:#666; }
23
23
24 .syntaxhl .class { color:#B06; font-weight:bold }
24 .syntaxhl .class { color:#B06; font-weight:bold }
25 .syntaxhl .delimiter { color:black }
25 .syntaxhl .delimiter { color:black }
26 .syntaxhl .function { color:#06B; font-weight:bold }
26 .syntaxhl .function { color:#06B; font-weight:bold }
27
27
28 .syntaxhl .inline { background: #eee }
28 .syntaxhl .inline { background: #eee }
29 .syntaxhl .inline .inline-delimiter { font-weight: bold; color: #888 }
29 .syntaxhl .inline .inline-delimiter { font-weight: bold; color: #888 }
30
30
31 .syntaxhl .instance-variable { color:#33B }
31 .syntaxhl .instance-variable { color:#33B }
32 .syntaxhl .reserved { color:#080; font-weight:bold }
32 .syntaxhl .reserved { color:#080; font-weight:bold }
33
33
34 .syntaxhl .string { background-color:#fff0f0; color: #D20; }
34 .syntaxhl .string { background-color:#fff0f0; color: #D20; }
35 .syntaxhl .string .delimiter { color:#710 }
35 .syntaxhl .string .delimiter { color:#710 }
36
36
37 </style>
37 </style>
38 </head>
38 </head>
39
39
40 <body>
40 <body>
41 <h1><a name="1" class="wiki-page"></a>Wiki formatting</h1>
41 <h1><a name="1" class="wiki-page"></a>Wiki formatting</h1>
42
42
43 <h2><a name="2" class="wiki-page"></a>Links</h2>
43 <h2><a name="2" class="wiki-page"></a>Links</h2>
44
44
45 <h3><a name="3" class="wiki-page"></a>Redmine links</h3>
45 <h3><a name="3" class="wiki-page"></a>Redmine links</h3>
46
46
47 <p>Redmine allows hyperlinking between issues, changesets and wiki pages from anywhere wiki formatting is used.</p>
47 <p>Redmine allows hyperlinking between resources (issues, changesets, wiki pages...) from anywhere wiki formatting is used.</p>
48 <ul>
48 <ul>
49 <li>Link to an issue: <strong>#124</strong> (displays <del><a href="#" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li>
49 <li>Link to an issue: <strong>#124</strong> (displays <del><a href="#" class="issue" title="bulk edit doesn't change the category or fixed version properties (Closed)">#124</a></del>, link is striked-through if the issue is closed)</li>
50 <li>Link to a changeset: <strong>r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">r758</a>)</li>
51 <li>Link to a changeset with a non-numeric hash: <strong>commit:c6f4d0fd</strong> (displays <a href="#" class="changeset">c6f4d0fd</a>).</li>
52 <li>Link to a changeset of another project: <strong>sandbox:r758</strong> (displays <a href="#" class="changeset" title="Search engine now only searches objects the user is allowed to view.">sandbox:r758</a>)</li>
53 <li>Link to a changeset with a non-numeric hash: <strong>sandbox:c6f4d0fd</strong> (displays <a href="#" class="changeset">sandbox:c6f4d0fd</a>).</li>
54 </ul>
50 </ul>
55
51
56 <p>Wiki links:</p>
52 <p>Wiki links:</p>
57
53
58 <ul>
54 <ul>
59 <li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li>
55 <li><strong>[[Guide]]</strong> displays a link to the page named 'Guide': <a href="#" class="wiki-page">Guide</a></li>
60 <li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li>
56 <li><strong>[[Guide#further-reading]]</strong> takes you to the anchor "further-reading". Headings get automatically assigned anchors so that you can refer to them: <a href="#" class="wiki-page">Guide</a></li>
61 <li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li>
57 <li><strong>[[Guide|User manual]]</strong> displays a link to the same page but with a different text: <a href="#" class="wiki-page">User manual</a></li>
62 </ul>
58 </ul>
63
59
64 <p>You can also link to pages of an other project wiki:</p>
60 <p>You can also link to pages of an other project wiki:</p>
65
61
66 <ul>
62 <ul>
67 <li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
63 <li><strong>[[sandbox:some page]]</strong> displays a link to the page named 'Some page' of the Sandbox wiki</li>
68 <li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li>
64 <li><strong>[[sandbox:]]</strong> displays a link to the Sandbox wiki main page</li>
69 </ul>
65 </ul>
70
66
71 <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p>
67 <p>Wiki links are displayed in red if the page doesn't exist yet, eg: <a href="#" class="wiki-page new">Nonexistent page</a>.</p>
72
68
73 <p>Links to other resources:</p>
69 <p>Links to other resources:</p>
74
70
75 <ul>
71 <ul>
76 <li>Documents:
72 <li>Documents:
77 <ul>
73 <ul>
78 <li><strong>document#17</strong> (link to document with id 17)</li>
74 <li><strong>document#17</strong> (link to document with id 17)</li>
79 <li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li>
75 <li><strong>document:Greetings</strong> (link to the document with title "Greetings")</li>
80 <li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li>
76 <li><strong>document:"Some document"</strong> (double quotes can be used when document title contains spaces)</li>
81 <li><strong>sandbox:document:"Some document"</strong> (link to a document with title "Some document" in other project "sandbox")</li>
77 <li><strong>sandbox:document:"Some document"</strong> (link to a document with title "Some document" in other project "sandbox")</li>
82 </ul></li>
78 </ul></li>
83 </ul>
79 </ul>
84
80
85 <ul>
81 <ul>
86 <li>Versions:
82 <li>Versions:
87 <ul>
83 <ul>
88 <li><strong>version#3</strong> (link to version with id 3)</li>
84 <li><strong>version#3</strong> (link to version with id 3)</li>
89 <li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li>
85 <li><strong>version:1.0.0</strong> (link to version named "1.0.0")</li>
90 <li><strong>version:"1.0 beta 2"</strong></li>
86 <li><strong>version:"1.0 beta 2"</strong></li>
91 <li><strong>sandbox:version:1.0.0</strong> (link to version "1.0.0" in the project "sandbox")</li>
87 <li><strong>sandbox:version:1.0.0</strong> (link to version "1.0.0" in the project "sandbox")</li>
92 </ul></li>
88 </ul></li>
93 </ul>
89 </ul>
94
90
95 <ul>
91 <ul>
96 <li>Attachments:
92 <li>Attachments:
97 <ul>
93 <ul>
98 <li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li>
94 <li><strong>attachment:file.zip</strong> (link to the attachment of the current object named file.zip)</li>
99 <li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li>
95 <li>For now, attachments of the current object can be referenced only (if you're on an issue, it's possible to reference attachments of this issue only)</li>
100 </ul></li>
96 </ul></li>
101 </ul>
97 </ul>
102
98
103 <ul>
99 <ul>
100 <li>Changesets:
101 <ul>
102 <li><strong>r758</strong> (link to a changeset)</li>
103 <li><strong>commit:c6f4d0fd</strong> (link to a changeset with a non-numeric hash)</li>
104 <li><strong>svn1|r758</strong> (link to a changeset of a specific repository, for projects with multiple repositories)</li>
105 <li><strong>commit:hg|c6f4d0fd</strong> (link to a changeset with a non-numeric hash of a specific repository)</li>
106 <li><strong>sandbox:r758</strong> (link to a changeset of another project)</li>
107 <li><strong>sandbox:commit:c6f4d0fd</strong> (link to a changeset with a non-numeric hash of another project)</li>
108 </ul></li>
109 </ul>
110
111 <ul>
104 <li>Repository files:
112 <li>Repository files:
105 <ul>
113 <ul>
106 <li><strong>source:some/file</strong> (link to the file located at /some/file in the project's repository)</li>
114 <li><strong>source:some/file</strong> (link to the file located at /some/file in the project's repository)</li>
107 <li><strong>source:some/file@52</strong> (link to the file's revision 52)</li>
115 <li><strong>source:some/file@52</strong> (link to the file's revision 52)</li>
108 <li><strong>source:some/file#L120</strong> (link to line 120 of the file)</li>
116 <li><strong>source:some/file#L120</strong> (link to line 120 of the file)</li>
109 <li><strong>source:some/file@52#L120</strong> (link to line 120 of the file's revision 52)</li>
117 <li><strong>source:some/file@52#L120</strong> (link to line 120 of the file's revision 52)</li>
110 <li><strong>source:"some file@52#L120"</strong> (use double quotes when the URL contains spaces</li>
118 <li><strong>source:"some file@52#L120"</strong> (use double quotes when the URL contains spaces</li>
111 <li><strong>export:some/file</strong> (force the download of the file)</li>
119 <li><strong>export:some/file</strong> (force the download of the file)</li>
120 <li><strong>source:svn1|some/file</strong> (link to a file of a specific repository, for projects with multiple repositories)</li>
112 <li><strong>sandbox:source:some/file</strong> (link to the file located at /some/file in the repository of the project "sandbox")</li>
121 <li><strong>sandbox:source:some/file</strong> (link to the file located at /some/file in the repository of the project "sandbox")</li>
113 <li><strong>sandbox:export:some/file</strong> (force the download of the file)</li>
122 <li><strong>sandbox:export:some/file</strong> (force the download of the file)</li>
114 </ul></li>
123 </ul></li>
115 </ul>
124 </ul>
116
125
117 <ul>
126 <ul>
118 <li>Forum messages:
127 <li>Forum messages:
119 <ul>
128 <ul>
120 <li><strong>message#1218</strong> (link to message with id 1218)</li>
129 <li><strong>message#1218</strong> (link to message with id 1218)</li>
121 </ul></li>
130 </ul></li>
122 </ul>
131 </ul>
123
132
124 <ul>
133 <ul>
125 <li>Projects:
134 <li>Projects:
126 <ul>
135 <ul>
127 <li><strong>project#3</strong> (link to project with id 3)</li>
136 <li><strong>project#3</strong> (link to project with id 3)</li>
128 <li><strong>project:someproject</strong> (link to project named "someproject")</li>
137 <li><strong>project:someproject</strong> (link to project named "someproject")</li>
129 </ul></li>
138 </ul></li>
130 </ul>
139 </ul>
131
140
132
141
133 <p>Escaping:</p>
142 <p>Escaping:</p>
134
143
135 <ul>
144 <ul>
136 <li>You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !</li>
145 <li>You can prevent Redmine links from being parsed by preceding them with an exclamation mark: !</li>
137 </ul>
146 </ul>
138
147
139
148
140 <h3><a name="4" class="wiki-page"></a>External links</h3>
149 <h3><a name="4" class="wiki-page"></a>External links</h3>
141
150
142 <p>HTTP URLs and email addresses are automatically turned into clickable links:</p>
151 <p>HTTP URLs and email addresses are automatically turned into clickable links:</p>
143
152
144 <pre>
153 <pre>
145 http://www.redmine.org, someone@foo.bar
154 http://www.redmine.org, someone@foo.bar
146 </pre>
155 </pre>
147
156
148 <p>displays: <a class="external" href="http://www.redmine.org">http://www.redmine.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p>
157 <p>displays: <a class="external" href="http://www.redmine.org">http://www.redmine.org</a>, <a href="mailto:someone@foo.bar" class="email">someone@foo.bar</a></p>
149
158
150 <p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p>
159 <p>If you want to display a specific text instead of the URL, you can use the standard textile syntax:</p>
151
160
152 <pre>
161 <pre>
153 "Redmine web site":http://www.redmine.org
162 "Redmine web site":http://www.redmine.org
154 </pre>
163 </pre>
155
164
156 <p>displays: <a href="http://www.redmine.org" class="external">Redmine web site</a></p>
165 <p>displays: <a href="http://www.redmine.org" class="external">Redmine web site</a></p>
157
166
158
167
159 <h2><a name="5" class="wiki-page"></a>Text formatting</h2>
168 <h2><a name="5" class="wiki-page"></a>Text formatting</h2>
160
169
161
170
162 <p>For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See <a class="external" href="http://www.textism.com/tools/textile/">http://www.textism.com/tools/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p>
171 <p>For things such as headlines, bold, tables, lists, Redmine supports Textile syntax. See <a class="external" href="http://www.textism.com/tools/textile/">http://www.textism.com/tools/textile/</a> for information on using any of these features. A few samples are included below, but the engine is capable of much more of that.</p>
163
172
164 <h3><a name="6" class="wiki-page"></a>Font style</h3>
173 <h3><a name="6" class="wiki-page"></a>Font style</h3>
165
174
166 <pre>
175 <pre>
167 * *bold*
176 * *bold*
168 * _italic_
177 * _italic_
169 * _*bold italic*_
178 * _*bold italic*_
170 * +underline+
179 * +underline+
171 * -strike-through-
180 * -strike-through-
172 </pre>
181 </pre>
173
182
174 <p>Display:</p>
183 <p>Display:</p>
175
184
176 <ul>
185 <ul>
177 <li><strong>bold</strong></li>
186 <li><strong>bold</strong></li>
178 <li><em>italic</em></li>
187 <li><em>italic</em></li>
179 <li><em>*bold italic*</em></li>
188 <li><em>*bold italic*</em></li>
180 <li><ins>underline</ins></li>
189 <li><ins>underline</ins></li>
181 <li><del>strike-through</del></li>
190 <li><del>strike-through</del></li>
182 </ul>
191 </ul>
183
192
184 <h3><a name="7" class="wiki-page"></a>Inline images</h3>
193 <h3><a name="7" class="wiki-page"></a>Inline images</h3>
185
194
186 <ul>
195 <ul>
187 <li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li>
196 <li><strong>!image_url!</strong> displays an image located at image_url (textile syntax)</li>
188 <li><strong>!>image_url!</strong> right floating image</li>
197 <li><strong>!>image_url!</strong> right floating image</li>
189 <li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li>
198 <li>If you have an image attached to your wiki page, it can be displayed inline using its filename: <strong>!attached_image.png!</strong></li>
190 </ul>
199 </ul>
191
200
192 <h3><a name="8" class="wiki-page"></a>Headings</h3>
201 <h3><a name="8" class="wiki-page"></a>Headings</h3>
193
202
194 <pre>
203 <pre>
195 h1. Heading
204 h1. Heading
196 h2. Subheading
205 h2. Subheading
197 h3. Subsubheading
206 h3. Subsubheading
198 </pre>
207 </pre>
199
208
200 <p>Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p>
209 <p>Redmine assigns an anchor to each of those headings thus you can link to them with "#Heading", "#Subheading" and so forth.</p>
201
210
202
211
203 <h3><a name="9" class="wiki-page"></a>Paragraphs</h3>
212 <h3><a name="9" class="wiki-page"></a>Paragraphs</h3>
204
213
205 <pre>
214 <pre>
206 p>. right aligned
215 p>. right aligned
207 p=. centered
216 p=. centered
208 </pre>
217 </pre>
209
218
210 <p style="text-align:center;">This is a centered paragraph.</p>
219 <p style="text-align:center;">This is a centered paragraph.</p>
211
220
212
221
213 <h3><a name="10" class="wiki-page"></a>Blockquotes</h3>
222 <h3><a name="10" class="wiki-page"></a>Blockquotes</h3>
214
223
215 <p>Start the paragraph with <strong>bq.</strong></p>
224 <p>Start the paragraph with <strong>bq.</strong></p>
216
225
217 <pre>
226 <pre>
218 bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
227 bq. Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.
219 To go live, all you need to add is a database and a web server.
228 To go live, all you need to add is a database and a web server.
220 </pre>
229 </pre>
221
230
222 <p>Display:</p>
231 <p>Display:</p>
223
232
224 <blockquote>
233 <blockquote>
225 <p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p>
234 <p>Rails is a full-stack framework for developing database-backed web applications according to the Model-View-Control pattern.<br />To go live, all you need to add is a database and a web server.</p>
226 </blockquote>
235 </blockquote>
227
236
228
237
229 <h3><a name="11" class="wiki-page"></a>Table of content</h3>
238 <h3><a name="11" class="wiki-page"></a>Table of content</h3>
230
239
231 <pre>
240 <pre>
232 {{toc}} => left aligned toc
241 {{toc}} => left aligned toc
233 {{>toc}} => right aligned toc
242 {{>toc}} => right aligned toc
234 </pre>
243 </pre>
235
244
236 <h2><a name="12" class="wiki-page"></a>Macros</h2>
245 <h2><a name="12" class="wiki-page"></a>Macros</h2>
237
246
238 <p>Redmine has the following builtin macros:</p>
247 <p>Redmine has the following builtin macros:</p>
239
248
240 <p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p>
249 <p><dl><dt><code>hello_world</code></dt><dd><p>Sample macro.</p></dd><dt><code>include</code></dt><dd><p>Include a wiki page. Example:</p>
241
250
242 <pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p>
251 <pre><code>{{include(Foo)}}</code></pre></dd><dt><code>macro_list</code></dt><dd><p>Displays a list of all available macros, including description if available.</p></dd></dl></p>
243
252
244
253
245 <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
254 <h2><a name="13" class="wiki-page"></a>Code highlighting</h2>
246
255
247 <p>Default code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p>
256 <p>Default code highlightment relies on <a href="http://coderay.rubychan.de/" class="external">CodeRay</a>, a fast syntax highlighting library written completely in Ruby. It currently supports c, cpp, css, delphi, groovy, html, java, javascript, json, php, python, rhtml, ruby, scheme, sql, xml and yaml languages.</p>
248
257
249 <p>You can highlight code in your wiki page using this syntax:</p>
258 <p>You can highlight code in your wiki page using this syntax:</p>
250
259
251 <pre>
260 <pre>
252 &lt;pre&gt;&lt;code class="ruby"&gt;
261 &lt;pre&gt;&lt;code class="ruby"&gt;
253 Place you code here.
262 Place you code here.
254 &lt;/code&gt;&lt;/pre&gt;
263 &lt;/code&gt;&lt;/pre&gt;
255 </pre>
264 </pre>
256
265
257 <p>Example:</p>
266 <p>Example:</p>
258
267
259 <pre><code class="ruby syntaxhl"><span class="line-numbers"> 1</span> <span class="comment"># The Greeter class</span>
268 <pre><code class="ruby syntaxhl"><span class="line-numbers"> 1</span> <span class="comment"># The Greeter class</span>
260 <span class="line-numbers"> 2</span> <span class="reserved">class</span> <span class="class">Greeter</span>
269 <span class="line-numbers"> 2</span> <span class="reserved">class</span> <span class="class">Greeter</span>
261 <span class="line-numbers"> 3</span> <span class="reserved">def</span> <span class="function">initialize</span>(name)
270 <span class="line-numbers"> 3</span> <span class="reserved">def</span> <span class="function">initialize</span>(name)
262 <span class="line-numbers"> 4</span> <span class="instance-variable">@name</span> = name.capitalize
271 <span class="line-numbers"> 4</span> <span class="instance-variable">@name</span> = name.capitalize
263 <span class="line-numbers"> 5</span> <span class="reserved">end</span>
272 <span class="line-numbers"> 5</span> <span class="reserved">end</span>
264 <span class="line-numbers"> 6</span>
273 <span class="line-numbers"> 6</span>
265 <span class="line-numbers"> 7</span> <span class="reserved">def</span> <span class="function">salute</span>
274 <span class="line-numbers"> 7</span> <span class="reserved">def</span> <span class="function">salute</span>
266 <span class="line-numbers"> 8</span> puts <span class="string"><span class="delimiter">"</span><span class="content">Hello </span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">@name</span><span class="inline-delimiter">}</span></span><span class="content">!</span><span class="delimiter">"</span></span>
275 <span class="line-numbers"> 8</span> puts <span class="string"><span class="delimiter">"</span><span class="content">Hello </span><span class="inline"><span class="inline-delimiter">#{</span><span class="instance-variable">@name</span><span class="inline-delimiter">}</span></span><span class="content">!</span><span class="delimiter">"</span></span>
267 <span class="line-numbers"> 9</span> <span class="reserved">end</span>
276 <span class="line-numbers"> 9</span> <span class="reserved">end</span>
268 <span class="line-numbers"><strong>10</strong></span> <span class="reserved">end</span></code>
277 <span class="line-numbers"><strong>10</strong></span> <span class="reserved">end</span></code>
269 </pre>
278 </pre>
270 </body>
279 </body>
271 </html>
280 </html>
@@ -1,887 +1,954
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 fixtures :projects, :roles, :enabled_modules, :users,
21 fixtures :projects, :roles, :enabled_modules, :users,
22 :repositories, :changesets,
22 :repositories, :changesets,
23 :trackers, :issue_statuses, :issues, :versions, :documents,
23 :trackers, :issue_statuses, :issues, :versions, :documents,
24 :wikis, :wiki_pages, :wiki_contents,
24 :wikis, :wiki_pages, :wiki_contents,
25 :boards, :messages, :news,
25 :boards, :messages, :news,
26 :attachments, :enumerations
26 :attachments, :enumerations
27
27
28 def setup
28 def setup
29 super
29 super
30 set_tmp_attachments_directory
30 set_tmp_attachments_directory
31 end
31 end
32
32
33 context "#link_to_if_authorized" do
33 context "#link_to_if_authorized" do
34 context "authorized user" do
34 context "authorized user" do
35 should "be tested"
35 should "be tested"
36 end
36 end
37
37
38 context "unauthorized user" do
38 context "unauthorized user" do
39 should "be tested"
39 should "be tested"
40 end
40 end
41
41
42 should "allow using the :controller and :action for the target link" do
42 should "allow using the :controller and :action for the target link" do
43 User.current = User.find_by_login('admin')
43 User.current = User.find_by_login('admin')
44
44
45 @project = Issue.first.project # Used by helper
45 @project = Issue.first.project # Used by helper
46 response = link_to_if_authorized("By controller/action",
46 response = link_to_if_authorized("By controller/action",
47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
47 {:controller => 'issues', :action => 'edit', :id => Issue.first.id})
48 assert_match /href/, response
48 assert_match /href/, response
49 end
49 end
50
50
51 end
51 end
52
52
53 def test_auto_links
53 def test_auto_links
54 to_test = {
54 to_test = {
55 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
55 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
56 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
56 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
57 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
57 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
58 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
58 'https://foo.bar.' => '<a class="external" href="https://foo.bar">https://foo.bar</a>.',
59 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
59 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
60 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
60 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
61 '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>.',
61 '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://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
62 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
63 '(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>)',
63 '(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)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
64 '(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>).',
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_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
66 '(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)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
67 '(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>).',
68 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
69 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
69 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
70 '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>',
70 '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#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
71 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
72 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
72 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
73 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
73 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
74 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
74 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
75 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
75 'ftps://foo.bar' => '<a class="external" href="ftps://foo.bar">ftps://foo.bar</a>',
76 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
76 'sftp://foo.bar' => '<a class="external" href="sftp://foo.bar">sftp://foo.bar</a>',
77 # two exclamation marks
77 # two exclamation marks
78 '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>',
78 '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 # escaping
79 # escaping
80 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
80 'http://foo"bar' => '<a class="external" href="http://foo&quot;bar">http://foo"bar</a>',
81 # wrap in angle brackets
81 # wrap in angle brackets
82 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
82 '<http://foo.bar>' => '&lt;<a class="external" href="http://foo.bar">http://foo.bar</a>&gt;'
83 }
83 }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
84 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
85 end
85 end
86
86
87 def test_auto_mailto
87 def test_auto_mailto
88 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
88 assert_equal '<p><a class="email" href="mailto:test@foo.bar">test@foo.bar</a></p>',
89 textilizable('test@foo.bar')
89 textilizable('test@foo.bar')
90 end
90 end
91
91
92 def test_inline_images
92 def test_inline_images
93 to_test = {
93 to_test = {
94 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
94 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
95 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
95 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
96 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
96 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
97 # inline styles should be stripped
97 # inline styles should be stripped
98 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
98 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" alt="" />',
99 '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" />',
99 '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 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;" />',
100 '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 }
101 }
102 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
102 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
103 end
103 end
104
104
105 def test_inline_images_inside_tags
105 def test_inline_images_inside_tags
106 raw = <<-RAW
106 raw = <<-RAW
107 h1. !foo.png! Heading
107 h1. !foo.png! Heading
108
108
109 Centered image:
109 Centered image:
110
110
111 p=. !bar.gif!
111 p=. !bar.gif!
112 RAW
112 RAW
113
113
114 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
114 assert textilizable(raw).include?('<img src="foo.png" alt="" />')
115 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
115 assert textilizable(raw).include?('<img src="bar.gif" alt="" />')
116 end
116 end
117
117
118 def test_attached_images
118 def test_attached_images
119 to_test = {
119 to_test = {
120 'Inline image: !logo.gif!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
120 '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" />',
121 'Inline image: !logo.GIF!' => 'Inline image: <img src="/attachments/download/3" title="This is a logo" alt="This is a logo" />',
122 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
122 'No match: !ogo.gif!' => 'No match: <img src="ogo.gif" alt="" />',
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 # link image
124 # link image
125 '!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>',
125 '!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 }
126 }
127 attachments = Attachment.find(:all)
127 attachments = Attachment.find(:all)
128 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
128 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
129 end
129 end
130
130
131 def test_attached_images_filename_extension
131 def test_attached_images_filename_extension
132 set_tmp_attachments_directory
132 set_tmp_attachments_directory
133 a1 = Attachment.new(
133 a1 = Attachment.new(
134 :container => Issue.find(1),
134 :container => Issue.find(1),
135 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
135 :file => mock_file_with_options({:original_filename => "testtest.JPG"}),
136 :author => User.find(1))
136 :author => User.find(1))
137 assert a1.save
137 assert a1.save
138 assert_equal "testtest.JPG", a1.filename
138 assert_equal "testtest.JPG", a1.filename
139 assert_equal "image/jpeg", a1.content_type
139 assert_equal "image/jpeg", a1.content_type
140 assert a1.image?
140 assert a1.image?
141
141
142 a2 = Attachment.new(
142 a2 = Attachment.new(
143 :container => Issue.find(1),
143 :container => Issue.find(1),
144 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
144 :file => mock_file_with_options({:original_filename => "testtest.jpeg"}),
145 :author => User.find(1))
145 :author => User.find(1))
146 assert a2.save
146 assert a2.save
147 assert_equal "testtest.jpeg", a2.filename
147 assert_equal "testtest.jpeg", a2.filename
148 assert_equal "image/jpeg", a2.content_type
148 assert_equal "image/jpeg", a2.content_type
149 assert a2.image?
149 assert a2.image?
150
150
151 a3 = Attachment.new(
151 a3 = Attachment.new(
152 :container => Issue.find(1),
152 :container => Issue.find(1),
153 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
153 :file => mock_file_with_options({:original_filename => "testtest.JPE"}),
154 :author => User.find(1))
154 :author => User.find(1))
155 assert a3.save
155 assert a3.save
156 assert_equal "testtest.JPE", a3.filename
156 assert_equal "testtest.JPE", a3.filename
157 assert_equal "image/jpeg", a3.content_type
157 assert_equal "image/jpeg", a3.content_type
158 assert a3.image?
158 assert a3.image?
159
159
160 a4 = Attachment.new(
160 a4 = Attachment.new(
161 :container => Issue.find(1),
161 :container => Issue.find(1),
162 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
162 :file => mock_file_with_options({:original_filename => "Testtest.BMP"}),
163 :author => User.find(1))
163 :author => User.find(1))
164 assert a4.save
164 assert a4.save
165 assert_equal "Testtest.BMP", a4.filename
165 assert_equal "Testtest.BMP", a4.filename
166 assert_equal "image/x-ms-bmp", a4.content_type
166 assert_equal "image/x-ms-bmp", a4.content_type
167 assert a4.image?
167 assert a4.image?
168
168
169 to_test = {
169 to_test = {
170 'Inline image: !testtest.jpg!' =>
170 'Inline image: !testtest.jpg!' =>
171 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
171 'Inline image: <img src="/attachments/download/' + a1.id.to_s + '" alt="" />',
172 'Inline image: !testtest.jpeg!' =>
172 'Inline image: !testtest.jpeg!' =>
173 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
173 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
174 'Inline image: !testtest.jpe!' =>
174 'Inline image: !testtest.jpe!' =>
175 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
175 'Inline image: <img src="/attachments/download/' + a3.id.to_s + '" alt="" />',
176 'Inline image: !testtest.bmp!' =>
176 'Inline image: !testtest.bmp!' =>
177 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
177 'Inline image: <img src="/attachments/download/' + a4.id.to_s + '" alt="" />',
178 }
178 }
179
179
180 attachments = [a1, a2, a3, a4]
180 attachments = [a1, a2, a3, a4]
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
181 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
182 end
182 end
183
183
184 def test_attached_images_should_read_later
184 def test_attached_images_should_read_later
185 set_fixtures_attachments_directory
185 set_fixtures_attachments_directory
186 a1 = Attachment.find(16)
186 a1 = Attachment.find(16)
187 assert_equal "testfile.png", a1.filename
187 assert_equal "testfile.png", a1.filename
188 assert a1.readable?
188 assert a1.readable?
189 assert (! a1.visible?(User.anonymous))
189 assert (! a1.visible?(User.anonymous))
190 assert a1.visible?(User.find(2))
190 assert a1.visible?(User.find(2))
191 a2 = Attachment.find(17)
191 a2 = Attachment.find(17)
192 assert_equal "testfile.PNG", a2.filename
192 assert_equal "testfile.PNG", a2.filename
193 assert a2.readable?
193 assert a2.readable?
194 assert (! a2.visible?(User.anonymous))
194 assert (! a2.visible?(User.anonymous))
195 assert a2.visible?(User.find(2))
195 assert a2.visible?(User.find(2))
196 assert a1.created_on < a2.created_on
196 assert a1.created_on < a2.created_on
197
197
198 to_test = {
198 to_test = {
199 'Inline image: !testfile.png!' =>
199 'Inline image: !testfile.png!' =>
200 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
200 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
201 'Inline image: !Testfile.PNG!' =>
201 'Inline image: !Testfile.PNG!' =>
202 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
202 'Inline image: <img src="/attachments/download/' + a2.id.to_s + '" alt="" />',
203 }
203 }
204 attachments = [a1, a2]
204 attachments = [a1, a2]
205 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
205 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => attachments) }
206 set_tmp_attachments_directory
206 set_tmp_attachments_directory
207 end
207 end
208
208
209 def test_textile_external_links
209 def test_textile_external_links
210 to_test = {
210 to_test = {
211 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
211 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
212 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
212 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
213 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
213 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
214 '"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>',
214 '"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>',
215 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
215 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
216 # no multiline link text
216 # no multiline link text
217 "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",
217 "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",
218 # mailto link
218 # mailto link
219 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
219 "\"system administrator\":mailto:sysadmin@example.com?subject=redmine%20permissions" => "<a href=\"mailto:sysadmin@example.com?subject=redmine%20permissions\">system administrator</a>",
220 # two exclamation marks
220 # two exclamation marks
221 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
221 '"a link":http://example.net/path!602815048C7B5C20!302.html' => '<a href="http://example.net/path!602815048C7B5C20!302.html" class="external">a link</a>',
222 # escaping
222 # escaping
223 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
223 '"test":http://foo"bar' => '<a href="http://foo&quot;bar" class="external">test</a>',
224 }
224 }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
225 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
226 end
226 end
227
227
228 def test_redmine_links
228 def test_redmine_links
229 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
229 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
230 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
230 :class => 'issue status-1 priority-1 overdue', :title => 'Error 281 when updating a recipe (New)')
231
231
232 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
232 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
233 :class => 'changeset', :title => 'My very first commit')
233 :class => 'changeset', :title => 'My very first commit')
234 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
234 changeset_link2 = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
235 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
235 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
236
236
237 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
237 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
238 :class => 'document')
238 :class => 'document')
239
239
240 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
240 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
241 :class => 'version')
241 :class => 'version')
242
242
243 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
243 board_url = {:controller => 'boards', :action => 'show', :id => 2, :project_id => 'ecookbook'}
244
244
245 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
245 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
246
246
247 news_url = {:controller => 'news', :action => 'show', :id => 1}
247 news_url = {:controller => 'news', :action => 'show', :id => 1}
248
248
249 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
249 project_url = {:controller => 'projects', :action => 'show', :id => 'subproject1'}
250
250
251 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
251 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
252 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
252 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
253
253
254 to_test = {
254 to_test = {
255 # tickets
255 # tickets
256 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
256 '#3, [#3], (#3) and #3.' => "#{issue_link}, [#{issue_link}], (#{issue_link}) and #{issue_link}.",
257 # changesets
257 # changesets
258 'r1' => changeset_link,
258 'r1' => changeset_link,
259 'r1.' => "#{changeset_link}.",
259 'r1.' => "#{changeset_link}.",
260 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
260 'r1, r2' => "#{changeset_link}, #{changeset_link2}",
261 'r1,r2' => "#{changeset_link},#{changeset_link2}",
261 'r1,r2' => "#{changeset_link},#{changeset_link2}",
262 # documents
262 # documents
263 'document#1' => document_link,
263 'document#1' => document_link,
264 'document:"Test document"' => document_link,
264 'document:"Test document"' => document_link,
265 # versions
265 # versions
266 'version#2' => version_link,
266 'version#2' => version_link,
267 'version:1.0' => version_link,
267 'version:1.0' => version_link,
268 'version:"1.0"' => version_link,
268 'version:"1.0"' => version_link,
269 # source
269 # source
270 'source:some/file' => link_to('source:some/file', source_url, :class => 'source'),
270 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
271 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
271 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
272 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
272 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
273 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
273 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
274 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
274 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
275 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
275 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
276 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
276 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
277 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
277 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
278 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
278 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
279 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
279 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
280 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
280 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
281 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
281 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
282 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
282 # forum
283 # forum
283 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
284 'forum#2' => link_to('Discussion', board_url, :class => 'board'),
284 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
285 'forum:Discussion' => link_to('Discussion', board_url, :class => 'board'),
285 # message
286 # message
286 'message#4' => link_to('Post 2', message_url, :class => 'message'),
287 'message#4' => link_to('Post 2', message_url, :class => 'message'),
287 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
288 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5', :r => 5), :class => 'message'),
288 # news
289 # news
289 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
290 'news#1' => link_to('eCookbook first release !', news_url, :class => 'news'),
290 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
291 'news:"eCookbook first release !"' => link_to('eCookbook first release !', news_url, :class => 'news'),
291 # project
292 # project
292 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
293 'project#3' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
293 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
294 'project:subproject1' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
294 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
295 'project:"eCookbook subProject 1"' => link_to('eCookbook Subproject 1', project_url, :class => 'project'),
295 # escaping
296 # escaping
296 '!#3.' => '#3.',
297 '!#3.' => '#3.',
297 '!r1' => 'r1',
298 '!r1' => 'r1',
298 '!document#1' => 'document#1',
299 '!document#1' => 'document#1',
299 '!document:"Test document"' => 'document:"Test document"',
300 '!document:"Test document"' => 'document:"Test document"',
300 '!version#2' => 'version#2',
301 '!version#2' => 'version#2',
301 '!version:1.0' => 'version:1.0',
302 '!version:1.0' => 'version:1.0',
302 '!version:"1.0"' => 'version:"1.0"',
303 '!version:"1.0"' => 'version:"1.0"',
303 '!source:/some/file' => 'source:/some/file',
304 '!source:/some/file' => 'source:/some/file',
304 # not found
305 # not found
305 '#0123456789' => '#0123456789',
306 '#0123456789' => '#0123456789',
306 # invalid expressions
307 # invalid expressions
307 'source:' => 'source:',
308 'source:' => 'source:',
308 # url hash
309 # url hash
309 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
310 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
310 }
311 }
311 @project = Project.find(1)
312 @project = Project.find(1)
312 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
313 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
313 end
314 end
314
315
315 def test_cross_project_redmine_links
316 def test_cross_project_redmine_links
316 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
317 source_link = link_to('ecookbook:source:/some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']},
317 :class => 'source')
318 :class => 'source')
318
319
319 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
320 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
320 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
321 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
321
322
322 to_test = {
323 to_test = {
323 # documents
324 # documents
324 'document:"Test document"' => 'document:"Test document"',
325 'document:"Test document"' => 'document:"Test document"',
325 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
326 'ecookbook:document:"Test document"' => '<a href="/documents/1" class="document">Test document</a>',
326 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
327 'invalid:document:"Test document"' => 'invalid:document:"Test document"',
327 # versions
328 # versions
328 'version:"1.0"' => 'version:"1.0"',
329 'version:"1.0"' => 'version:"1.0"',
329 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
330 'ecookbook:version:"1.0"' => '<a href="/versions/2" class="version">1.0</a>',
330 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
331 'invalid:version:"1.0"' => 'invalid:version:"1.0"',
331 # changeset
332 # changeset
332 'r2' => 'r2',
333 'r2' => 'r2',
333 'ecookbook:r2' => changeset_link,
334 'ecookbook:r2' => changeset_link,
334 'invalid:r2' => 'invalid:r2',
335 'invalid:r2' => 'invalid:r2',
335 # source
336 # source
336 'source:/some/file' => 'source:/some/file',
337 'source:/some/file' => 'source:/some/file',
337 'ecookbook:source:/some/file' => source_link,
338 'ecookbook:source:/some/file' => source_link,
338 'invalid:source:/some/file' => 'invalid:source:/some/file',
339 'invalid:source:/some/file' => 'invalid:source:/some/file',
339 }
340 }
340 @project = Project.find(3)
341 @project = Project.find(3)
341 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
342 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
342 end
343 end
343
344
345 def test_multiple_repositories_redmine_links
346 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
347 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
348 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
349 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
350
351 changeset_link = link_to('r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
352 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
353 svn_changeset_link = link_to('svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
354 :class => 'changeset', :title => '')
355 hg_changeset_link = link_to('hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
356 :class => 'changeset', :title => '')
357
358 source_link = link_to('source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
359 hg_source_link = link_to('source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
360
361 to_test = {
362 'r2' => changeset_link,
363 'svn1|r123' => svn_changeset_link,
364 'invalid|r123' => 'invalid|r123',
365 'commit:hg1|abcd' => hg_changeset_link,
366 'commit:invalid|abcd' => 'commit:invalid|abcd',
367 # source
368 'source:some/file' => source_link,
369 'source:hg1|some/file' => hg_source_link,
370 'source:invalid|some/file' => 'source:invalid|some/file',
371 }
372
373 @project = Project.find(1)
374 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
375 end
376
377 def test_cross_project_multiple_repositories_redmine_links
378 svn = Repository::Subversion.create!(:project_id => 1, :identifier => 'svn1', :url => 'file:///foo/hg')
379 Changeset.create!(:repository => svn, :committed_on => Time.now, :revision => '123')
380 hg = Repository::Mercurial.create!(:project_id => 1, :identifier => 'hg1', :url => '/foo/hg')
381 Changeset.create!(:repository => hg, :committed_on => Time.now, :revision => '123', :scmid => 'abcd')
382
383 changeset_link = link_to('ecookbook:r2', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 2},
384 :class => 'changeset', :title => 'This commit fixes #1, #2 and references #1 & #3')
385 svn_changeset_link = link_to('ecookbook:svn1|r123', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'svn1', :rev => 123},
386 :class => 'changeset', :title => '')
387 hg_changeset_link = link_to('ecookbook:hg1|abcd', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :repository_id => 'hg1', :rev => 'abcd'},
388 :class => 'changeset', :title => '')
389
390 source_link = link_to('ecookbook:source:some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}, :class => 'source')
391 hg_source_link = link_to('ecookbook:source:hg1|some/file', {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :repository_id => 'hg1', :path => ['some', 'file']}, :class => 'source')
392
393 to_test = {
394 'ecookbook:r2' => changeset_link,
395 'ecookbook:svn1|r123' => svn_changeset_link,
396 'ecookbook:invalid|r123' => 'ecookbook:invalid|r123',
397 'ecookbook:commit:hg1|abcd' => hg_changeset_link,
398 'ecookbook:commit:invalid|abcd' => 'ecookbook:commit:invalid|abcd',
399 'invalid:commit:invalid|abcd' => 'invalid:commit:invalid|abcd',
400 # source
401 'ecookbook:source:some/file' => source_link,
402 'ecookbook:source:hg1|some/file' => hg_source_link,
403 'ecookbook:source:invalid|some/file' => 'ecookbook:source:invalid|some/file',
404 'invalid:source:invalid|some/file' => 'invalid:source:invalid|some/file',
405 }
406
407 @project = Project.find(3)
408 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text), "#{text} failed" }
409 end
410
344 def test_redmine_links_git_commit
411 def test_redmine_links_git_commit
345 changeset_link = link_to('abcd',
412 changeset_link = link_to('abcd',
346 {
413 {
347 :controller => 'repositories',
414 :controller => 'repositories',
348 :action => 'revision',
415 :action => 'revision',
349 :id => 'subproject1',
416 :id => 'subproject1',
350 :rev => 'abcd',
417 :rev => 'abcd',
351 },
418 },
352 :class => 'changeset', :title => 'test commit')
419 :class => 'changeset', :title => 'test commit')
353 to_test = {
420 to_test = {
354 'commit:abcd' => changeset_link,
421 'commit:abcd' => changeset_link,
355 }
422 }
356 @project = Project.find(3)
423 @project = Project.find(3)
357 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
424 r = Repository::Git.create!(:project => @project, :url => '/tmp/test/git')
358 assert r
425 assert r
359 c = Changeset.new(:repository => r,
426 c = Changeset.new(:repository => r,
360 :committed_on => Time.now,
427 :committed_on => Time.now,
361 :revision => 'abcd',
428 :revision => 'abcd',
362 :scmid => 'abcd',
429 :scmid => 'abcd',
363 :comments => 'test commit')
430 :comments => 'test commit')
364 assert( c.save )
431 assert( c.save )
365 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
432 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
366 end
433 end
367
434
368 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
435 # TODO: Bazaar commit id contains mail address, so it contains '@' and '_'.
369 def test_redmine_links_darcs_commit
436 def test_redmine_links_darcs_commit
370 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
437 changeset_link = link_to('20080308225258-98289-abcd456efg.gz',
371 {
438 {
372 :controller => 'repositories',
439 :controller => 'repositories',
373 :action => 'revision',
440 :action => 'revision',
374 :id => 'subproject1',
441 :id => 'subproject1',
375 :rev => '123',
442 :rev => '123',
376 },
443 },
377 :class => 'changeset', :title => 'test commit')
444 :class => 'changeset', :title => 'test commit')
378 to_test = {
445 to_test = {
379 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
446 'commit:20080308225258-98289-abcd456efg.gz' => changeset_link,
380 }
447 }
381 @project = Project.find(3)
448 @project = Project.find(3)
382 r = Repository::Darcs.create!(
449 r = Repository::Darcs.create!(
383 :project => @project, :url => '/tmp/test/darcs',
450 :project => @project, :url => '/tmp/test/darcs',
384 :log_encoding => 'UTF-8')
451 :log_encoding => 'UTF-8')
385 assert r
452 assert r
386 c = Changeset.new(:repository => r,
453 c = Changeset.new(:repository => r,
387 :committed_on => Time.now,
454 :committed_on => Time.now,
388 :revision => '123',
455 :revision => '123',
389 :scmid => '20080308225258-98289-abcd456efg.gz',
456 :scmid => '20080308225258-98289-abcd456efg.gz',
390 :comments => 'test commit')
457 :comments => 'test commit')
391 assert( c.save )
458 assert( c.save )
392 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
459 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
393 end
460 end
394
461
395 def test_redmine_links_mercurial_commit
462 def test_redmine_links_mercurial_commit
396 changeset_link_rev = link_to('r123',
463 changeset_link_rev = link_to('r123',
397 {
464 {
398 :controller => 'repositories',
465 :controller => 'repositories',
399 :action => 'revision',
466 :action => 'revision',
400 :id => 'subproject1',
467 :id => 'subproject1',
401 :rev => '123' ,
468 :rev => '123' ,
402 },
469 },
403 :class => 'changeset', :title => 'test commit')
470 :class => 'changeset', :title => 'test commit')
404 changeset_link_commit = link_to('abcd',
471 changeset_link_commit = link_to('abcd',
405 {
472 {
406 :controller => 'repositories',
473 :controller => 'repositories',
407 :action => 'revision',
474 :action => 'revision',
408 :id => 'subproject1',
475 :id => 'subproject1',
409 :rev => 'abcd' ,
476 :rev => 'abcd' ,
410 },
477 },
411 :class => 'changeset', :title => 'test commit')
478 :class => 'changeset', :title => 'test commit')
412 to_test = {
479 to_test = {
413 'r123' => changeset_link_rev,
480 'r123' => changeset_link_rev,
414 'commit:abcd' => changeset_link_commit,
481 'commit:abcd' => changeset_link_commit,
415 }
482 }
416 @project = Project.find(3)
483 @project = Project.find(3)
417 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
484 r = Repository::Mercurial.create!(:project => @project, :url => '/tmp/test')
418 assert r
485 assert r
419 c = Changeset.new(:repository => r,
486 c = Changeset.new(:repository => r,
420 :committed_on => Time.now,
487 :committed_on => Time.now,
421 :revision => '123',
488 :revision => '123',
422 :scmid => 'abcd',
489 :scmid => 'abcd',
423 :comments => 'test commit')
490 :comments => 'test commit')
424 assert( c.save )
491 assert( c.save )
425 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
492 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
426 end
493 end
427
494
428 def test_attachment_links
495 def test_attachment_links
429 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
496 attachment_link = link_to('error281.txt', {:controller => 'attachments', :action => 'download', :id => '1'}, :class => 'attachment')
430 to_test = {
497 to_test = {
431 'attachment:error281.txt' => attachment_link
498 'attachment:error281.txt' => attachment_link
432 }
499 }
433 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
500 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :attachments => Issue.find(3).attachments), "#{text} failed" }
434 end
501 end
435
502
436 def test_wiki_links
503 def test_wiki_links
437 to_test = {
504 to_test = {
438 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
505 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
439 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
506 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
440 # title content should be formatted
507 # title content should be formatted
441 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
508 '[[Another page|With _styled_ *title*]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With <em>styled</em> <strong>title</strong></a>',
442 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
509 '[[Another page|With title containing <strong>HTML entities &amp; markups</strong>]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">With title containing &lt;strong&gt;HTML entities &amp; markups&lt;/strong&gt;</a>',
443 # link with anchor
510 # link with anchor
444 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
511 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
445 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
512 '[[Another page#anchor|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page#anchor" class="wiki-page">Page</a>',
446 # page that doesn't exist
513 # page that doesn't exist
447 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
514 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
448 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
515 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page" class="wiki-page new">404</a>',
449 # link to another project wiki
516 # link to another project wiki
450 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
517 '[[onlinestore:]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">onlinestore</a>',
451 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
518 '[[onlinestore:|Wiki]]' => '<a href="/projects/onlinestore/wiki" class="wiki-page">Wiki</a>',
452 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
519 '[[onlinestore:Start page]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Start page</a>',
453 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
520 '[[onlinestore:Start page|Text]]' => '<a href="/projects/onlinestore/wiki/Start_page" class="wiki-page">Text</a>',
454 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
521 '[[onlinestore:Unknown page]]' => '<a href="/projects/onlinestore/wiki/Unknown_page" class="wiki-page new">Unknown page</a>',
455 # striked through link
522 # striked through link
456 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
523 '-[[Another page|Page]]-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a></del>',
457 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
524 '-[[Another page|Page]] link-' => '<del><a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a> link</del>',
458 # escaping
525 # escaping
459 '![[Another page|Page]]' => '[[Another page|Page]]',
526 '![[Another page|Page]]' => '[[Another page|Page]]',
460 # project does not exist
527 # project does not exist
461 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
528 '[[unknowproject:Start]]' => '[[unknowproject:Start]]',
462 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
529 '[[unknowproject:Start|Page title]]' => '[[unknowproject:Start|Page title]]',
463 }
530 }
464
531
465 @project = Project.find(1)
532 @project = Project.find(1)
466 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
533 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
467 end
534 end
468
535
469 def test_wiki_links_within_local_file_generation_context
536 def test_wiki_links_within_local_file_generation_context
470
537
471 to_test = {
538 to_test = {
472 # link to a page
539 # link to a page
473 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
540 '[[CookBook documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">CookBook documentation</a>',
474 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
541 '[[CookBook documentation|documentation]]' => '<a href="CookBook_documentation.html" class="wiki-page">documentation</a>',
475 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
542 '[[CookBook documentation#One-section]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">CookBook documentation</a>',
476 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
543 '[[CookBook documentation#One-section|documentation]]' => '<a href="CookBook_documentation.html#One-section" class="wiki-page">documentation</a>',
477 # page that doesn't exist
544 # page that doesn't exist
478 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
545 '[[Unknown page]]' => '<a href="Unknown_page.html" class="wiki-page new">Unknown page</a>',
479 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
546 '[[Unknown page|404]]' => '<a href="Unknown_page.html" class="wiki-page new">404</a>',
480 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
547 '[[Unknown page#anchor]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">Unknown page</a>',
481 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
548 '[[Unknown page#anchor|404]]' => '<a href="Unknown_page.html#anchor" class="wiki-page new">404</a>',
482 }
549 }
483
550
484 @project = Project.find(1)
551 @project = Project.find(1)
485
552
486 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
553 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :local) }
487 end
554 end
488
555
489 def test_wiki_links_within_wiki_page_context
556 def test_wiki_links_within_wiki_page_context
490
557
491 page = WikiPage.find_by_title('Another_page' )
558 page = WikiPage.find_by_title('Another_page' )
492
559
493 to_test = {
560 to_test = {
494 # link to another page
561 # link to another page
495 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
562 '[[CookBook documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
496 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
563 '[[CookBook documentation|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">documentation</a>',
497 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
564 '[[CookBook documentation#One-section]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
498 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
565 '[[CookBook documentation#One-section|documentation]]' => '<a href="/projects/ecookbook/wiki/CookBook_documentation#One-section" class="wiki-page">documentation</a>',
499 # link to the current page
566 # link to the current page
500 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
567 '[[Another page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Another page</a>',
501 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
568 '[[Another page|Page]]' => '<a href="/projects/ecookbook/wiki/Another_page" class="wiki-page">Page</a>',
502 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
569 '[[Another page#anchor]]' => '<a href="#anchor" class="wiki-page">Another page</a>',
503 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
570 '[[Another page#anchor|Page]]' => '<a href="#anchor" class="wiki-page">Page</a>',
504 # page that doesn't exist
571 # page that doesn't exist
505 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">Unknown page</a>',
572 '[[Unknown page]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">Unknown page</a>',
506 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">404</a>',
573 '[[Unknown page|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page" class="wiki-page new">404</a>',
507 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">Unknown page</a>',
574 '[[Unknown page#anchor]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">Unknown page</a>',
508 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">404</a>',
575 '[[Unknown page#anchor|404]]' => '<a href="/projects/ecookbook/wiki/Unknown_page?parent=Another_page#anchor" class="wiki-page new">404</a>',
509 }
576 }
510
577
511 @project = Project.find(1)
578 @project = Project.find(1)
512
579
513 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.new( :text => text, :page => page ), :text) }
580 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(WikiContent.new( :text => text, :page => page ), :text) }
514 end
581 end
515
582
516 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
583 def test_wiki_links_anchor_option_should_prepend_page_title_to_href
517
584
518 to_test = {
585 to_test = {
519 # link to a page
586 # link to a page
520 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
587 '[[CookBook documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">CookBook documentation</a>',
521 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
588 '[[CookBook documentation|documentation]]' => '<a href="#CookBook_documentation" class="wiki-page">documentation</a>',
522 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
589 '[[CookBook documentation#One-section]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">CookBook documentation</a>',
523 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
590 '[[CookBook documentation#One-section|documentation]]' => '<a href="#CookBook_documentation_One-section" class="wiki-page">documentation</a>',
524 # page that doesn't exist
591 # page that doesn't exist
525 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
592 '[[Unknown page]]' => '<a href="#Unknown_page" class="wiki-page new">Unknown page</a>',
526 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
593 '[[Unknown page|404]]' => '<a href="#Unknown_page" class="wiki-page new">404</a>',
527 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
594 '[[Unknown page#anchor]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">Unknown page</a>',
528 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
595 '[[Unknown page#anchor|404]]' => '<a href="#Unknown_page_anchor" class="wiki-page new">404</a>',
529 }
596 }
530
597
531 @project = Project.find(1)
598 @project = Project.find(1)
532
599
533 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
600 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text, :wiki_links => :anchor) }
534 end
601 end
535
602
536 def test_html_tags
603 def test_html_tags
537 to_test = {
604 to_test = {
538 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
605 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
539 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
606 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
540 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
607 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
541 # do not escape pre/code tags
608 # do not escape pre/code tags
542 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
609 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
543 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
610 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
544 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
611 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
545 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
612 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
546 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
613 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
547 # remove attributes except class
614 # remove attributes except class
548 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
615 "<pre class='foo'>some text</pre>" => "<pre class='foo'>some text</pre>",
549 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
616 '<pre class="foo">some text</pre>' => '<pre class="foo">some text</pre>',
550 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
617 "<pre class='foo bar'>some text</pre>" => "<pre class='foo bar'>some text</pre>",
551 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
618 '<pre class="foo bar">some text</pre>' => '<pre class="foo bar">some text</pre>',
552 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
619 "<pre onmouseover='alert(1)'>some text</pre>" => "<pre>some text</pre>",
553 # xss
620 # xss
554 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
621 '<pre><code class=""onmouseover="alert(1)">text</code></pre>' => '<pre><code>text</code></pre>',
555 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
622 '<pre class=""onmouseover="alert(1)">text</pre>' => '<pre>text</pre>',
556 }
623 }
557 to_test.each { |text, result| assert_equal result, textilizable(text) }
624 to_test.each { |text, result| assert_equal result, textilizable(text) }
558 end
625 end
559
626
560 def test_allowed_html_tags
627 def test_allowed_html_tags
561 to_test = {
628 to_test = {
562 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
629 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
563 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
630 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
564 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
631 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
565 }
632 }
566 to_test.each { |text, result| assert_equal result, textilizable(text) }
633 to_test.each { |text, result| assert_equal result, textilizable(text) }
567 end
634 end
568
635
569 def test_pre_tags
636 def test_pre_tags
570 raw = <<-RAW
637 raw = <<-RAW
571 Before
638 Before
572
639
573 <pre>
640 <pre>
574 <prepared-statement-cache-size>32</prepared-statement-cache-size>
641 <prepared-statement-cache-size>32</prepared-statement-cache-size>
575 </pre>
642 </pre>
576
643
577 After
644 After
578 RAW
645 RAW
579
646
580 expected = <<-EXPECTED
647 expected = <<-EXPECTED
581 <p>Before</p>
648 <p>Before</p>
582 <pre>
649 <pre>
583 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
650 &lt;prepared-statement-cache-size&gt;32&lt;/prepared-statement-cache-size&gt;
584 </pre>
651 </pre>
585 <p>After</p>
652 <p>After</p>
586 EXPECTED
653 EXPECTED
587
654
588 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
655 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
589 end
656 end
590
657
591 def test_pre_content_should_not_parse_wiki_and_redmine_links
658 def test_pre_content_should_not_parse_wiki_and_redmine_links
592 raw = <<-RAW
659 raw = <<-RAW
593 [[CookBook documentation]]
660 [[CookBook documentation]]
594
661
595 #1
662 #1
596
663
597 <pre>
664 <pre>
598 [[CookBook documentation]]
665 [[CookBook documentation]]
599
666
600 #1
667 #1
601 </pre>
668 </pre>
602 RAW
669 RAW
603
670
604 expected = <<-EXPECTED
671 expected = <<-EXPECTED
605 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
672 <p><a href="/projects/ecookbook/wiki/CookBook_documentation" class="wiki-page">CookBook documentation</a></p>
606 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
673 <p><a href="/issues/1" class="issue status-1 priority-1" title="Can't print recipes (New)">#1</a></p>
607 <pre>
674 <pre>
608 [[CookBook documentation]]
675 [[CookBook documentation]]
609
676
610 #1
677 #1
611 </pre>
678 </pre>
612 EXPECTED
679 EXPECTED
613
680
614 @project = Project.find(1)
681 @project = Project.find(1)
615 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
682 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
616 end
683 end
617
684
618 def test_non_closing_pre_blocks_should_be_closed
685 def test_non_closing_pre_blocks_should_be_closed
619 raw = <<-RAW
686 raw = <<-RAW
620 <pre><code>
687 <pre><code>
621 RAW
688 RAW
622
689
623 expected = <<-EXPECTED
690 expected = <<-EXPECTED
624 <pre><code>
691 <pre><code>
625 </code></pre>
692 </code></pre>
626 EXPECTED
693 EXPECTED
627
694
628 @project = Project.find(1)
695 @project = Project.find(1)
629 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
696 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
630 end
697 end
631
698
632 def test_syntax_highlight
699 def test_syntax_highlight
633 raw = <<-RAW
700 raw = <<-RAW
634 <pre><code class="ruby">
701 <pre><code class="ruby">
635 # Some ruby code here
702 # Some ruby code here
636 </code></pre>
703 </code></pre>
637 RAW
704 RAW
638
705
639 expected = <<-EXPECTED
706 expected = <<-EXPECTED
640 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
707 <pre><code class="ruby syntaxhl"><span class=\"CodeRay\"><span class="line-numbers">1</span><span class="comment"># Some ruby code here</span></span>
641 </code></pre>
708 </code></pre>
642 EXPECTED
709 EXPECTED
643
710
644 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
711 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
645 end
712 end
646
713
647 def test_wiki_links_in_tables
714 def test_wiki_links_in_tables
648 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
715 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
649 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
716 '<tr><td><a href="/projects/ecookbook/wiki/Page" class="wiki-page new">Link title</a></td>' +
650 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
717 '<td><a href="/projects/ecookbook/wiki/Other_Page" class="wiki-page new">Other title</a></td>' +
651 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
718 '</tr><tr><td>Cell 21</td><td><a href="/projects/ecookbook/wiki/Last_page" class="wiki-page new">Last page</a></td></tr>'
652 }
719 }
653 @project = Project.find(1)
720 @project = Project.find(1)
654 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
721 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
655 end
722 end
656
723
657 def test_text_formatting
724 def test_text_formatting
658 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
725 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
659 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
726 '(_text within parentheses_)' => '(<em>text within parentheses</em>)',
660 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
727 'a *Humane Web* Text Generator' => 'a <strong>Humane Web</strong> Text Generator',
661 '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>',
728 '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>',
662 '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',
729 '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',
663 }
730 }
664 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
731 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
665 end
732 end
666
733
667 def test_wiki_horizontal_rule
734 def test_wiki_horizontal_rule
668 assert_equal '<hr />', textilizable('---')
735 assert_equal '<hr />', textilizable('---')
669 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
736 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
670 end
737 end
671
738
672 def test_footnotes
739 def test_footnotes
673 raw = <<-RAW
740 raw = <<-RAW
674 This is some text[1].
741 This is some text[1].
675
742
676 fn1. This is the foot note
743 fn1. This is the foot note
677 RAW
744 RAW
678
745
679 expected = <<-EXPECTED
746 expected = <<-EXPECTED
680 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
747 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
681 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
748 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
682 EXPECTED
749 EXPECTED
683
750
684 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
751 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
685 end
752 end
686
753
687 def test_headings
754 def test_headings
688 raw = 'h1. Some heading'
755 raw = 'h1. Some heading'
689 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
756 expected = %|<a name="Some-heading"></a>\n<h1 >Some heading<a href="#Some-heading" class="wiki-anchor">&para;</a></h1>|
690
757
691 assert_equal expected, textilizable(raw)
758 assert_equal expected, textilizable(raw)
692 end
759 end
693
760
694 def test_headings_with_special_chars
761 def test_headings_with_special_chars
695 # This test makes sure that the generated anchor names match the expected
762 # This test makes sure that the generated anchor names match the expected
696 # ones even if the heading text contains unconventional characters
763 # ones even if the heading text contains unconventional characters
697 raw = 'h1. Some heading related to version 0.5'
764 raw = 'h1. Some heading related to version 0.5'
698 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
765 anchor = sanitize_anchor_name("Some-heading-related-to-version-0.5")
699 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
766 expected = %|<a name="#{anchor}"></a>\n<h1 >Some heading related to version 0.5<a href="##{anchor}" class="wiki-anchor">&para;</a></h1>|
700
767
701 assert_equal expected, textilizable(raw)
768 assert_equal expected, textilizable(raw)
702 end
769 end
703
770
704 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
771 def test_headings_in_wiki_single_page_export_should_be_prepended_with_page_title
705 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
772 page = WikiPage.new( :title => 'Page Title', :wiki_id => 1 )
706 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
773 content = WikiContent.new( :text => 'h1. Some heading', :page => page )
707
774
708 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
775 expected = %|<a name="Page_Title_Some-heading"></a>\n<h1 >Some heading<a href="#Page_Title_Some-heading" class="wiki-anchor">&para;</a></h1>|
709
776
710 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
777 assert_equal expected, textilizable(content, :text, :wiki_links => :anchor )
711 end
778 end
712
779
713 def test_table_of_content
780 def test_table_of_content
714 raw = <<-RAW
781 raw = <<-RAW
715 {{toc}}
782 {{toc}}
716
783
717 h1. Title
784 h1. Title
718
785
719 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
786 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
720
787
721 h2. Subtitle with a [[Wiki]] link
788 h2. Subtitle with a [[Wiki]] link
722
789
723 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
790 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
724
791
725 h2. Subtitle with [[Wiki|another Wiki]] link
792 h2. Subtitle with [[Wiki|another Wiki]] link
726
793
727 h2. Subtitle with %{color:red}red text%
794 h2. Subtitle with %{color:red}red text%
728
795
729 <pre>
796 <pre>
730 some code
797 some code
731 </pre>
798 </pre>
732
799
733 h3. Subtitle with *some* _modifiers_
800 h3. Subtitle with *some* _modifiers_
734
801
735 h1. Another title
802 h1. Another title
736
803
737 h3. An "Internet link":http://www.redmine.org/ inside subtitle
804 h3. An "Internet link":http://www.redmine.org/ inside subtitle
738
805
739 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
806 h2. "Project Name !/attachments/1234/logo_small.gif! !/attachments/5678/logo_2.png!":/projects/projectname/issues
740
807
741 RAW
808 RAW
742
809
743 expected = '<ul class="toc">' +
810 expected = '<ul class="toc">' +
744 '<li><a href="#Title">Title</a>' +
811 '<li><a href="#Title">Title</a>' +
745 '<ul>' +
812 '<ul>' +
746 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
813 '<li><a href="#Subtitle-with-a-Wiki-link">Subtitle with a Wiki link</a></li>' +
747 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
814 '<li><a href="#Subtitle-with-another-Wiki-link">Subtitle with another Wiki link</a></li>' +
748 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
815 '<li><a href="#Subtitle-with-red-text">Subtitle with red text</a>' +
749 '<ul>' +
816 '<ul>' +
750 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
817 '<li><a href="#Subtitle-with-some-modifiers">Subtitle with some modifiers</a></li>' +
751 '</ul>' +
818 '</ul>' +
752 '</li>' +
819 '</li>' +
753 '</ul>' +
820 '</ul>' +
754 '</li>' +
821 '</li>' +
755 '<li><a href="#Another-title">Another title</a>' +
822 '<li><a href="#Another-title">Another title</a>' +
756 '<ul>' +
823 '<ul>' +
757 '<li>' +
824 '<li>' +
758 '<ul>' +
825 '<ul>' +
759 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
826 '<li><a href="#An-Internet-link-inside-subtitle">An Internet link inside subtitle</a></li>' +
760 '</ul>' +
827 '</ul>' +
761 '</li>' +
828 '</li>' +
762 '<li><a href="#Project-Name">Project Name</a></li>' +
829 '<li><a href="#Project-Name">Project Name</a></li>' +
763 '</ul>' +
830 '</ul>' +
764 '</li>' +
831 '</li>' +
765 '</ul>'
832 '</ul>'
766
833
767 @project = Project.find(1)
834 @project = Project.find(1)
768 assert textilizable(raw).gsub("\n", "").include?(expected)
835 assert textilizable(raw).gsub("\n", "").include?(expected)
769 end
836 end
770
837
771 def test_table_of_content_should_contain_included_page_headings
838 def test_table_of_content_should_contain_included_page_headings
772 raw = <<-RAW
839 raw = <<-RAW
773 {{toc}}
840 {{toc}}
774
841
775 h1. Included
842 h1. Included
776
843
777 {{include(Child_1)}}
844 {{include(Child_1)}}
778 RAW
845 RAW
779
846
780 expected = '<ul class="toc">' +
847 expected = '<ul class="toc">' +
781 '<li><a href="#Included">Included</a></li>' +
848 '<li><a href="#Included">Included</a></li>' +
782 '<li><a href="#Child-page-1">Child page 1</a></li>' +
849 '<li><a href="#Child-page-1">Child page 1</a></li>' +
783 '</ul>'
850 '</ul>'
784
851
785 @project = Project.find(1)
852 @project = Project.find(1)
786 assert textilizable(raw).gsub("\n", "").include?(expected)
853 assert textilizable(raw).gsub("\n", "").include?(expected)
787 end
854 end
788
855
789 def test_default_formatter
856 def test_default_formatter
790 with_settings :text_formatting => 'unknown' do
857 with_settings :text_formatting => 'unknown' do
791 text = 'a *link*: http://www.example.net/'
858 text = 'a *link*: http://www.example.net/'
792 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
859 assert_equal '<p>a *link*: <a href="http://www.example.net/">http://www.example.net/</a></p>', textilizable(text)
793 end
860 end
794 end
861 end
795
862
796 def test_due_date_distance_in_words
863 def test_due_date_distance_in_words
797 to_test = { Date.today => 'Due in 0 days',
864 to_test = { Date.today => 'Due in 0 days',
798 Date.today + 1 => 'Due in 1 day',
865 Date.today + 1 => 'Due in 1 day',
799 Date.today + 100 => 'Due in about 3 months',
866 Date.today + 100 => 'Due in about 3 months',
800 Date.today + 20000 => 'Due in over 54 years',
867 Date.today + 20000 => 'Due in over 54 years',
801 Date.today - 1 => '1 day late',
868 Date.today - 1 => '1 day late',
802 Date.today - 100 => 'about 3 months late',
869 Date.today - 100 => 'about 3 months late',
803 Date.today - 20000 => 'over 54 years late',
870 Date.today - 20000 => 'over 54 years late',
804 }
871 }
805 ::I18n.locale = :en
872 ::I18n.locale = :en
806 to_test.each do |date, expected|
873 to_test.each do |date, expected|
807 assert_equal expected, due_date_distance_in_words(date)
874 assert_equal expected, due_date_distance_in_words(date)
808 end
875 end
809 end
876 end
810
877
811 def test_avatar
878 def test_avatar
812 # turn on avatars
879 # turn on avatars
813 Setting.gravatar_enabled = '1'
880 Setting.gravatar_enabled = '1'
814 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
881 assert avatar(User.find_by_mail('jsmith@somenet.foo')).include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
815 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
882 assert avatar('jsmith <jsmith@somenet.foo>').include?(Digest::MD5.hexdigest('jsmith@somenet.foo'))
816 assert_nil avatar('jsmith')
883 assert_nil avatar('jsmith')
817 assert_nil avatar(nil)
884 assert_nil avatar(nil)
818
885
819 # turn off avatars
886 # turn off avatars
820 Setting.gravatar_enabled = '0'
887 Setting.gravatar_enabled = '0'
821 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
888 assert_equal '', avatar(User.find_by_mail('jsmith@somenet.foo'))
822 end
889 end
823
890
824 def test_link_to_user
891 def test_link_to_user
825 user = User.find(2)
892 user = User.find(2)
826 t = link_to_user(user)
893 t = link_to_user(user)
827 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
894 assert_equal "<a href=\"/users/2\">#{ user.name }</a>", t
828 end
895 end
829
896
830 def test_link_to_user_should_not_link_to_locked_user
897 def test_link_to_user_should_not_link_to_locked_user
831 user = User.find(5)
898 user = User.find(5)
832 assert user.locked?
899 assert user.locked?
833 t = link_to_user(user)
900 t = link_to_user(user)
834 assert_equal user.name, t
901 assert_equal user.name, t
835 end
902 end
836
903
837 def test_link_to_user_should_not_link_to_anonymous
904 def test_link_to_user_should_not_link_to_anonymous
838 user = User.anonymous
905 user = User.anonymous
839 assert user.anonymous?
906 assert user.anonymous?
840 t = link_to_user(user)
907 t = link_to_user(user)
841 assert_equal ::I18n.t(:label_user_anonymous), t
908 assert_equal ::I18n.t(:label_user_anonymous), t
842 end
909 end
843
910
844 def test_link_to_project
911 def test_link_to_project
845 project = Project.find(1)
912 project = Project.find(1)
846 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
913 assert_equal %(<a href="/projects/ecookbook">eCookbook</a>),
847 link_to_project(project)
914 link_to_project(project)
848 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
915 assert_equal %(<a href="/projects/ecookbook/settings">eCookbook</a>),
849 link_to_project(project, :action => 'settings')
916 link_to_project(project, :action => 'settings')
850 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
917 assert_equal %(<a href="http://test.host/projects/ecookbook?jump=blah">eCookbook</a>),
851 link_to_project(project, {:only_path => false, :jump => 'blah'})
918 link_to_project(project, {:only_path => false, :jump => 'blah'})
852 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
919 assert_equal %(<a href="/projects/ecookbook/settings" class="project">eCookbook</a>),
853 link_to_project(project, {:action => 'settings'}, :class => "project")
920 link_to_project(project, {:action => 'settings'}, :class => "project")
854 end
921 end
855
922
856 def test_principals_options_for_select_with_users
923 def test_principals_options_for_select_with_users
857 User.current = nil
924 User.current = nil
858 users = [User.find(2), User.find(4)]
925 users = [User.find(2), User.find(4)]
859 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
926 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>),
860 principals_options_for_select(users)
927 principals_options_for_select(users)
861 end
928 end
862
929
863 def test_principals_options_for_select_with_selected
930 def test_principals_options_for_select_with_selected
864 User.current = nil
931 User.current = nil
865 users = [User.find(2), User.find(4)]
932 users = [User.find(2), User.find(4)]
866 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
933 assert_equal %(<option value="2">John Smith</option><option value="4" selected="selected">Robert Hill</option>),
867 principals_options_for_select(users, User.find(4))
934 principals_options_for_select(users, User.find(4))
868 end
935 end
869
936
870 def test_principals_options_for_select_with_users_and_groups
937 def test_principals_options_for_select_with_users_and_groups
871 User.current = nil
938 User.current = nil
872 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
939 users = [User.find(2), Group.find(11), User.find(4), Group.find(10)]
873 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
940 assert_equal %(<option value="2">John Smith</option><option value="4">Robert Hill</option>) +
874 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
941 %(<optgroup label="Groups"><option value="10">A Team</option><option value="11">B Team</option></optgroup>),
875 principals_options_for_select(users)
942 principals_options_for_select(users)
876 end
943 end
877
944
878 def test_principals_options_for_select_with_empty_collection
945 def test_principals_options_for_select_with_empty_collection
879 assert_equal '', principals_options_for_select([])
946 assert_equal '', principals_options_for_select([])
880 end
947 end
881
948
882 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
949 def test_principals_options_for_select_should_include_me_option_when_current_user_is_in_collection
883 users = [User.find(2), User.find(4)]
950 users = [User.find(2), User.find(4)]
884 User.current = User.find(4)
951 User.current = User.find(4)
885 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
952 assert_include '<option value="4"><< me >></option>', principals_options_for_select(users)
886 end
953 end
887 end
954 end
General Comments 0
You need to be logged in to leave comments. Login now