##// END OF EJS Templates
Redmine links regexp fix (#1369, url hash turned into a ticket link)....
Jean-Philippe Lang -
r1478:cd824c6ecf38
parent child
Show More
@@ -1,517 +1,517
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 module ApplicationHelper
18 module ApplicationHelper
19 include Redmine::WikiFormatting::Macros::Definitions
19 include Redmine::WikiFormatting::Macros::Definitions
20
20
21 def current_role
21 def current_role
22 @current_role ||= User.current.role_for_project(@project)
22 @current_role ||= User.current.role_for_project(@project)
23 end
23 end
24
24
25 # Return true if user is authorized for controller/action, otherwise false
25 # Return true if user is authorized for controller/action, otherwise false
26 def authorize_for(controller, action)
26 def authorize_for(controller, action)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 end
28 end
29
29
30 # Display a link if user is authorized
30 # Display a link if user is authorized
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 end
33 end
34
34
35 # Display a link to user's account page
35 # Display a link to user's account page
36 def link_to_user(user)
36 def link_to_user(user)
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
38 end
38 end
39
39
40 def link_to_issue(issue, options={})
40 def link_to_issue(issue, options={})
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
41 link_to "#{issue.tracker.name} ##{issue.id}", {:controller => "issues", :action => "show", :id => issue}, options
42 end
42 end
43
43
44 def toggle_link(name, id, options={})
44 def toggle_link(name, id, options={})
45 onclick = "Element.toggle('#{id}'); "
45 onclick = "Element.toggle('#{id}'); "
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
47 onclick << "return false;"
47 onclick << "return false;"
48 link_to(name, "#", :onclick => onclick)
48 link_to(name, "#", :onclick => onclick)
49 end
49 end
50
50
51 def show_and_goto_link(name, id, options={})
51 def show_and_goto_link(name, id, options={})
52 onclick = "Element.show('#{id}'); "
52 onclick = "Element.show('#{id}'); "
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << "Element.scrollTo('#{id}'); "
54 onclick << "Element.scrollTo('#{id}'); "
55 onclick << "return false;"
55 onclick << "return false;"
56 link_to(name, "#", options.merge(:onclick => onclick))
56 link_to(name, "#", options.merge(:onclick => onclick))
57 end
57 end
58
58
59 def image_to_function(name, function, html_options = {})
59 def image_to_function(name, function, html_options = {})
60 html_options.symbolize_keys!
60 html_options.symbolize_keys!
61 tag(:input, html_options.merge({
61 tag(:input, html_options.merge({
62 :type => "image", :src => image_path(name),
62 :type => "image", :src => image_path(name),
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
64 }))
64 }))
65 end
65 end
66
66
67 def prompt_to_remote(name, text, param, url, html_options = {})
67 def prompt_to_remote(name, text, param, url, html_options = {})
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
69 link_to name, {}, html_options
69 link_to name, {}, html_options
70 end
70 end
71
71
72 def format_date(date)
72 def format_date(date)
73 return nil unless date
73 return nil unless date
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
76 date.strftime(@date_format)
76 date.strftime(@date_format)
77 end
77 end
78
78
79 def format_time(time, include_date = true)
79 def format_time(time, include_date = true)
80 return nil unless time
80 return nil unless time
81 time = time.to_time if time.is_a?(String)
81 time = time.to_time if time.is_a?(String)
82 zone = User.current.time_zone
82 zone = User.current.time_zone
83 if time.utc?
83 if time.utc?
84 local = zone ? zone.adjust(time) : time.getlocal
84 local = zone ? zone.adjust(time) : time.getlocal
85 else
85 else
86 local = zone ? zone.adjust(time.getutc) : time
86 local = zone ? zone.adjust(time.getutc) : time
87 end
87 end
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
91 end
91 end
92
92
93 # Truncates and returns the string as a single line
93 # Truncates and returns the string as a single line
94 def truncate_single_line(string, *args)
94 def truncate_single_line(string, *args)
95 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
95 truncate(string, *args).gsub(%r{[\r\n]+}m, ' ')
96 end
96 end
97
97
98 def html_hours(text)
98 def html_hours(text)
99 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
99 text.gsub(%r{(\d+)\.(\d+)}, '<span class="hours hours-int">\1</span><span class="hours hours-dec">.\2</span>')
100 end
100 end
101
101
102 def authoring(created, author)
102 def authoring(created, author)
103 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
103 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
104 l(:label_added_time_by, author || 'Anonymous', time_tag)
104 l(:label_added_time_by, author || 'Anonymous', time_tag)
105 end
105 end
106
106
107 def l_or_humanize(s)
107 def l_or_humanize(s)
108 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
108 l_has_string?("label_#{s}".to_sym) ? l("label_#{s}".to_sym) : s.to_s.humanize
109 end
109 end
110
110
111 def day_name(day)
111 def day_name(day)
112 l(:general_day_names).split(',')[day-1]
112 l(:general_day_names).split(',')[day-1]
113 end
113 end
114
114
115 def month_name(month)
115 def month_name(month)
116 l(:actionview_datehelper_select_month_names).split(',')[month-1]
116 l(:actionview_datehelper_select_month_names).split(',')[month-1]
117 end
117 end
118
118
119 def pagination_links_full(paginator, count=nil, options={})
119 def pagination_links_full(paginator, count=nil, options={})
120 page_param = options.delete(:page_param) || :page
120 page_param = options.delete(:page_param) || :page
121 url_param = params.dup
121 url_param = params.dup
122 # don't reuse params if filters are present
122 # don't reuse params if filters are present
123 url_param.clear if url_param.has_key?(:set_filter)
123 url_param.clear if url_param.has_key?(:set_filter)
124
124
125 html = ''
125 html = ''
126 html << link_to_remote(('&#171; ' + l(:label_previous)),
126 html << link_to_remote(('&#171; ' + l(:label_previous)),
127 {:update => 'content',
127 {:update => 'content',
128 :url => url_param.merge(page_param => paginator.current.previous),
128 :url => url_param.merge(page_param => paginator.current.previous),
129 :complete => 'window.scrollTo(0,0)'},
129 :complete => 'window.scrollTo(0,0)'},
130 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
130 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
131
131
132 html << (pagination_links_each(paginator, options) do |n|
132 html << (pagination_links_each(paginator, options) do |n|
133 link_to_remote(n.to_s,
133 link_to_remote(n.to_s,
134 {:url => {:params => url_param.merge(page_param => n)},
134 {:url => {:params => url_param.merge(page_param => n)},
135 :update => 'content',
135 :update => 'content',
136 :complete => 'window.scrollTo(0,0)'},
136 :complete => 'window.scrollTo(0,0)'},
137 {:href => url_for(:params => url_param.merge(page_param => n))})
137 {:href => url_for(:params => url_param.merge(page_param => n))})
138 end || '')
138 end || '')
139
139
140 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
140 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
141 {:update => 'content',
141 {:update => 'content',
142 :url => url_param.merge(page_param => paginator.current.next),
142 :url => url_param.merge(page_param => paginator.current.next),
143 :complete => 'window.scrollTo(0,0)'},
143 :complete => 'window.scrollTo(0,0)'},
144 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
144 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
145
145
146 unless count.nil?
146 unless count.nil?
147 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
147 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
148 end
148 end
149
149
150 html
150 html
151 end
151 end
152
152
153 def per_page_links(selected=nil)
153 def per_page_links(selected=nil)
154 url_param = params.dup
154 url_param = params.dup
155 url_param.clear if url_param.has_key?(:set_filter)
155 url_param.clear if url_param.has_key?(:set_filter)
156
156
157 links = Setting.per_page_options_array.collect do |n|
157 links = Setting.per_page_options_array.collect do |n|
158 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
158 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
159 {:href => url_for(url_param.merge(:per_page => n))})
159 {:href => url_for(url_param.merge(:per_page => n))})
160 end
160 end
161 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
161 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
162 end
162 end
163
163
164 def breadcrumb(*args)
164 def breadcrumb(*args)
165 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
165 content_tag('p', args.join(' &#187; ') + ' &#187; ', :class => 'breadcrumb')
166 end
166 end
167
167
168 def html_title(*args)
168 def html_title(*args)
169 if args.empty?
169 if args.empty?
170 title = []
170 title = []
171 title << @project.name if @project
171 title << @project.name if @project
172 title += @html_title if @html_title
172 title += @html_title if @html_title
173 title << Setting.app_title
173 title << Setting.app_title
174 title.compact.join(' - ')
174 title.compact.join(' - ')
175 else
175 else
176 @html_title ||= []
176 @html_title ||= []
177 @html_title += args
177 @html_title += args
178 end
178 end
179 end
179 end
180
180
181 def accesskey(s)
181 def accesskey(s)
182 Redmine::AccessKeys.key_for s
182 Redmine::AccessKeys.key_for s
183 end
183 end
184
184
185 # Formats text according to system settings.
185 # Formats text according to system settings.
186 # 2 ways to call this method:
186 # 2 ways to call this method:
187 # * with a String: textilizable(text, options)
187 # * with a String: textilizable(text, options)
188 # * with an object and one of its attribute: textilizable(issue, :description, options)
188 # * with an object and one of its attribute: textilizable(issue, :description, options)
189 def textilizable(*args)
189 def textilizable(*args)
190 options = args.last.is_a?(Hash) ? args.pop : {}
190 options = args.last.is_a?(Hash) ? args.pop : {}
191 case args.size
191 case args.size
192 when 1
192 when 1
193 obj = nil
193 obj = nil
194 text = args.shift
194 text = args.shift
195 when 2
195 when 2
196 obj = args.shift
196 obj = args.shift
197 text = obj.send(args.shift).to_s
197 text = obj.send(args.shift).to_s
198 else
198 else
199 raise ArgumentError, 'invalid arguments to textilizable'
199 raise ArgumentError, 'invalid arguments to textilizable'
200 end
200 end
201 return '' if text.blank?
201 return '' if text.blank?
202
202
203 only_path = options.delete(:only_path) == false ? false : true
203 only_path = options.delete(:only_path) == false ? false : true
204
204
205 # when using an image link, try to use an attachment, if possible
205 # when using an image link, try to use an attachment, if possible
206 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
206 attachments = options[:attachments] || (obj && obj.respond_to?(:attachments) ? obj.attachments : nil)
207
207
208 if attachments
208 if attachments
209 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
209 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
210 style = $1
210 style = $1
211 filename = $6
211 filename = $6
212 rf = Regexp.new(filename, Regexp::IGNORECASE)
212 rf = Regexp.new(filename, Regexp::IGNORECASE)
213 # search for the picture in attachments
213 # search for the picture in attachments
214 if found = attachments.detect { |att| att.filename =~ rf }
214 if found = attachments.detect { |att| att.filename =~ rf }
215 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
215 image_url = url_for :only_path => only_path, :controller => 'attachments', :action => 'download', :id => found
216 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
216 desc = found.description.to_s.gsub(/^([^\(\)]*).*$/, "\\1")
217 alt = desc.blank? ? nil : "(#{desc})"
217 alt = desc.blank? ? nil : "(#{desc})"
218 "!#{style}#{image_url}#{alt}!"
218 "!#{style}#{image_url}#{alt}!"
219 else
219 else
220 "!#{style}#{filename}!"
220 "!#{style}#{filename}!"
221 end
221 end
222 end
222 end
223 end
223 end
224
224
225 text = (Setting.text_formatting == 'textile') ?
225 text = (Setting.text_formatting == 'textile') ?
226 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
226 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
227 simple_format(auto_link(h(text)))
227 simple_format(auto_link(h(text)))
228
228
229 # different methods for formatting wiki links
229 # different methods for formatting wiki links
230 case options[:wiki_links]
230 case options[:wiki_links]
231 when :local
231 when :local
232 # used for local links to html files
232 # used for local links to html files
233 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
233 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
234 when :anchor
234 when :anchor
235 # used for single-file wiki export
235 # used for single-file wiki export
236 format_wiki_link = Proc.new {|project, title| "##{title}" }
236 format_wiki_link = Proc.new {|project, title| "##{title}" }
237 else
237 else
238 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
238 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
239 end
239 end
240
240
241 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
241 project = options[:project] || @project || (obj && obj.respond_to?(:project) ? obj.project : nil)
242
242
243 # Wiki links
243 # Wiki links
244 #
244 #
245 # Examples:
245 # Examples:
246 # [[mypage]]
246 # [[mypage]]
247 # [[mypage|mytext]]
247 # [[mypage|mytext]]
248 # wiki links can refer other project wikis, using project name or identifier:
248 # wiki links can refer other project wikis, using project name or identifier:
249 # [[project:]] -> wiki starting page
249 # [[project:]] -> wiki starting page
250 # [[project:|mytext]]
250 # [[project:|mytext]]
251 # [[project:mypage]]
251 # [[project:mypage]]
252 # [[project:mypage|mytext]]
252 # [[project:mypage|mytext]]
253 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
253 text = text.gsub(/(!)?(\[\[([^\]\n\|]+)(\|([^\]\n\|]+))?\]\])/) do |m|
254 link_project = project
254 link_project = project
255 esc, all, page, title = $1, $2, $3, $5
255 esc, all, page, title = $1, $2, $3, $5
256 if esc.nil?
256 if esc.nil?
257 if page =~ /^([^\:]+)\:(.*)$/
257 if page =~ /^([^\:]+)\:(.*)$/
258 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
258 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
259 page = $2
259 page = $2
260 title ||= $1 if page.blank?
260 title ||= $1 if page.blank?
261 end
261 end
262
262
263 if link_project && link_project.wiki
263 if link_project && link_project.wiki
264 # check if page exists
264 # check if page exists
265 wiki_page = link_project.wiki.find_page(page)
265 wiki_page = link_project.wiki.find_page(page)
266 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
266 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
267 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
267 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
268 else
268 else
269 # project or wiki doesn't exist
269 # project or wiki doesn't exist
270 title || page
270 title || page
271 end
271 end
272 else
272 else
273 all
273 all
274 end
274 end
275 end
275 end
276
276
277 # Redmine links
277 # Redmine links
278 #
278 #
279 # Examples:
279 # Examples:
280 # Issues:
280 # Issues:
281 # #52 -> Link to issue #52
281 # #52 -> Link to issue #52
282 # Changesets:
282 # Changesets:
283 # r52 -> Link to revision 52
283 # r52 -> Link to revision 52
284 # commit:a85130f -> Link to scmid starting with a85130f
284 # commit:a85130f -> Link to scmid starting with a85130f
285 # Documents:
285 # Documents:
286 # document#17 -> Link to document with id 17
286 # document#17 -> Link to document with id 17
287 # document:Greetings -> Link to the document with title "Greetings"
287 # document:Greetings -> Link to the document with title "Greetings"
288 # document:"Some document" -> Link to the document with title "Some document"
288 # document:"Some document" -> Link to the document with title "Some document"
289 # Versions:
289 # Versions:
290 # version#3 -> Link to version with id 3
290 # version#3 -> Link to version with id 3
291 # version:1.0.0 -> Link to version named "1.0.0"
291 # version:1.0.0 -> Link to version named "1.0.0"
292 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
292 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
293 # Attachments:
293 # Attachments:
294 # attachment:file.zip -> Link to the attachment of the current object named file.zip
294 # attachment:file.zip -> Link to the attachment of the current object named file.zip
295 # Source files:
295 # Source files:
296 # source:some/file -> Link to the file located at /some/file in the project's repository
296 # source:some/file -> Link to the file located at /some/file in the project's repository
297 # source:some/file@52 -> Link to the file's revision 52
297 # source:some/file@52 -> Link to the file's revision 52
298 # source:some/file#L120 -> Link to line 120 of the file
298 # source:some/file#L120 -> Link to line 120 of the file
299 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
299 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
300 # export:some/file -> Force the download of the file
300 # export:some/file -> Force the download of the file
301 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
301 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
302 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
302 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
303 link = nil
303 link = nil
304 if esc.nil?
304 if esc.nil?
305 if prefix.nil? && sep == 'r'
305 if prefix.nil? && sep == 'r'
306 if project && (changeset = project.changesets.find_by_revision(oid))
306 if project && (changeset = project.changesets.find_by_revision(oid))
307 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
307 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
308 :class => 'changeset',
308 :class => 'changeset',
309 :title => truncate_single_line(changeset.comments, 100))
309 :title => truncate_single_line(changeset.comments, 100))
310 end
310 end
311 elsif sep == '#'
311 elsif sep == '#'
312 oid = oid.to_i
312 oid = oid.to_i
313 case prefix
313 case prefix
314 when nil
314 when nil
315 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
315 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
316 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
316 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
317 :class => (issue.closed? ? 'issue closed' : 'issue'),
317 :class => (issue.closed? ? 'issue closed' : 'issue'),
318 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
318 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
319 link = content_tag('del', link) if issue.closed?
319 link = content_tag('del', link) if issue.closed?
320 end
320 end
321 when 'document'
321 when 'document'
322 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
322 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
323 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
323 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
324 :class => 'document'
324 :class => 'document'
325 end
325 end
326 when 'version'
326 when 'version'
327 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
327 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
328 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
328 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
329 :class => 'version'
329 :class => 'version'
330 end
330 end
331 end
331 end
332 elsif sep == ':'
332 elsif sep == ':'
333 # removes the double quotes if any
333 # removes the double quotes if any
334 name = oid.gsub(%r{^"(.*)"$}, "\\1")
334 name = oid.gsub(%r{^"(.*)"$}, "\\1")
335 case prefix
335 case prefix
336 when 'document'
336 when 'document'
337 if project && document = project.documents.find_by_title(name)
337 if project && document = project.documents.find_by_title(name)
338 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
338 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
339 :class => 'document'
339 :class => 'document'
340 end
340 end
341 when 'version'
341 when 'version'
342 if project && version = project.versions.find_by_name(name)
342 if project && version = project.versions.find_by_name(name)
343 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
343 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
344 :class => 'version'
344 :class => 'version'
345 end
345 end
346 when 'commit'
346 when 'commit'
347 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
347 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
348 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
348 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
349 :class => 'changeset',
349 :class => 'changeset',
350 :title => truncate_single_line(changeset.comments, 100)
350 :title => truncate_single_line(changeset.comments, 100)
351 end
351 end
352 when 'source', 'export'
352 when 'source', 'export'
353 if project && project.repository
353 if project && project.repository
354 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
354 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
355 path, rev, anchor = $1, $3, $5
355 path, rev, anchor = $1, $3, $5
356 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
356 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project, :path => path,
357 :rev => rev,
357 :rev => rev,
358 :anchor => anchor,
358 :anchor => anchor,
359 :format => (prefix == 'export' ? 'raw' : nil)},
359 :format => (prefix == 'export' ? 'raw' : nil)},
360 :class => (prefix == 'export' ? 'source download' : 'source')
360 :class => (prefix == 'export' ? 'source download' : 'source')
361 end
361 end
362 when 'attachment'
362 when 'attachment'
363 if attachments && attachment = attachments.detect {|a| a.filename == name }
363 if attachments && attachment = attachments.detect {|a| a.filename == name }
364 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
364 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
365 :class => 'attachment'
365 :class => 'attachment'
366 end
366 end
367 end
367 end
368 end
368 end
369 end
369 end
370 leading + (link || "#{prefix}#{sep}#{oid}")
370 leading + (link || "#{prefix}#{sep}#{oid}")
371 end
371 end
372
372
373 text
373 text
374 end
374 end
375
375
376 # Same as Rails' simple_format helper without using paragraphs
376 # Same as Rails' simple_format helper without using paragraphs
377 def simple_format_without_paragraph(text)
377 def simple_format_without_paragraph(text)
378 text.to_s.
378 text.to_s.
379 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
379 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
380 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
380 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
381 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
381 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
382 end
382 end
383
383
384 def error_messages_for(object_name, options = {})
384 def error_messages_for(object_name, options = {})
385 options = options.symbolize_keys
385 options = options.symbolize_keys
386 object = instance_variable_get("@#{object_name}")
386 object = instance_variable_get("@#{object_name}")
387 if object && !object.errors.empty?
387 if object && !object.errors.empty?
388 # build full_messages here with controller current language
388 # build full_messages here with controller current language
389 full_messages = []
389 full_messages = []
390 object.errors.each do |attr, msg|
390 object.errors.each do |attr, msg|
391 next if msg.nil?
391 next if msg.nil?
392 msg = msg.first if msg.is_a? Array
392 msg = msg.first if msg.is_a? Array
393 if attr == "base"
393 if attr == "base"
394 full_messages << l(msg)
394 full_messages << l(msg)
395 else
395 else
396 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
396 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
397 end
397 end
398 end
398 end
399 # retrieve custom values error messages
399 # retrieve custom values error messages
400 if object.errors[:custom_values]
400 if object.errors[:custom_values]
401 object.custom_values.each do |v|
401 object.custom_values.each do |v|
402 v.errors.each do |attr, msg|
402 v.errors.each do |attr, msg|
403 next if msg.nil?
403 next if msg.nil?
404 msg = msg.first if msg.is_a? Array
404 msg = msg.first if msg.is_a? Array
405 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
405 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
406 end
406 end
407 end
407 end
408 end
408 end
409 content_tag("div",
409 content_tag("div",
410 content_tag(
410 content_tag(
411 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
411 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
412 ) +
412 ) +
413 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
413 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
414 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
414 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
415 )
415 )
416 else
416 else
417 ""
417 ""
418 end
418 end
419 end
419 end
420
420
421 def lang_options_for_select(blank=true)
421 def lang_options_for_select(blank=true)
422 (blank ? [["(auto)", ""]] : []) +
422 (blank ? [["(auto)", ""]] : []) +
423 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
423 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
424 end
424 end
425
425
426 def label_tag_for(name, option_tags = nil, options = {})
426 def label_tag_for(name, option_tags = nil, options = {})
427 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
427 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
428 content_tag("label", label_text)
428 content_tag("label", label_text)
429 end
429 end
430
430
431 def labelled_tabular_form_for(name, object, options, &proc)
431 def labelled_tabular_form_for(name, object, options, &proc)
432 options[:html] ||= {}
432 options[:html] ||= {}
433 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
433 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
434 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
434 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
435 end
435 end
436
436
437 def back_url_hidden_field_tag
437 def back_url_hidden_field_tag
438 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
438 hidden_field_tag 'back_url', (params[:back_url] || request.env['HTTP_REFERER'])
439 end
439 end
440
440
441 def check_all_links(form_name)
441 def check_all_links(form_name)
442 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
442 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
443 " | " +
443 " | " +
444 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
444 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
445 end
445 end
446
446
447 def progress_bar(pcts, options={})
447 def progress_bar(pcts, options={})
448 pcts = [pcts, pcts] unless pcts.is_a?(Array)
448 pcts = [pcts, pcts] unless pcts.is_a?(Array)
449 pcts[1] = pcts[1] - pcts[0]
449 pcts[1] = pcts[1] - pcts[0]
450 pcts << (100 - pcts[1] - pcts[0])
450 pcts << (100 - pcts[1] - pcts[0])
451 width = options[:width] || '100px;'
451 width = options[:width] || '100px;'
452 legend = options[:legend] || ''
452 legend = options[:legend] || ''
453 content_tag('table',
453 content_tag('table',
454 content_tag('tr',
454 content_tag('tr',
455 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
455 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
456 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
456 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
457 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
457 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
458 ), :class => 'progress', :style => "width: #{width};") +
458 ), :class => 'progress', :style => "width: #{width};") +
459 content_tag('p', legend, :class => 'pourcent')
459 content_tag('p', legend, :class => 'pourcent')
460 end
460 end
461
461
462 def context_menu_link(name, url, options={})
462 def context_menu_link(name, url, options={})
463 options[:class] ||= ''
463 options[:class] ||= ''
464 if options.delete(:selected)
464 if options.delete(:selected)
465 options[:class] << ' icon-checked disabled'
465 options[:class] << ' icon-checked disabled'
466 options[:disabled] = true
466 options[:disabled] = true
467 end
467 end
468 if options.delete(:disabled)
468 if options.delete(:disabled)
469 options.delete(:method)
469 options.delete(:method)
470 options.delete(:confirm)
470 options.delete(:confirm)
471 options.delete(:onclick)
471 options.delete(:onclick)
472 options[:class] << ' disabled'
472 options[:class] << ' disabled'
473 url = '#'
473 url = '#'
474 end
474 end
475 link_to name, url, options
475 link_to name, url, options
476 end
476 end
477
477
478 def calendar_for(field_id)
478 def calendar_for(field_id)
479 include_calendar_headers_tags
479 include_calendar_headers_tags
480 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
480 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
481 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
481 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
482 end
482 end
483
483
484 def include_calendar_headers_tags
484 def include_calendar_headers_tags
485 unless @calendar_headers_tags_included
485 unless @calendar_headers_tags_included
486 @calendar_headers_tags_included = true
486 @calendar_headers_tags_included = true
487 content_for :header_tags do
487 content_for :header_tags do
488 javascript_include_tag('calendar/calendar') +
488 javascript_include_tag('calendar/calendar') +
489 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
489 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
490 javascript_include_tag('calendar/calendar-setup') +
490 javascript_include_tag('calendar/calendar-setup') +
491 stylesheet_link_tag('calendar')
491 stylesheet_link_tag('calendar')
492 end
492 end
493 end
493 end
494 end
494 end
495
495
496 def wikitoolbar_for(field_id)
496 def wikitoolbar_for(field_id)
497 return '' unless Setting.text_formatting == 'textile'
497 return '' unless Setting.text_formatting == 'textile'
498
498
499 help_link = l(:setting_text_formatting) + ': ' +
499 help_link = l(:setting_text_formatting) + ': ' +
500 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
500 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
501 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
501 :onclick => "window.open(\"#{ compute_public_path('wiki_syntax', 'help', 'html') }\", \"\", \"resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\"); return false;")
502
502
503 javascript_include_tag('jstoolbar/jstoolbar') +
503 javascript_include_tag('jstoolbar/jstoolbar') +
504 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
504 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
505 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
505 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
506 end
506 end
507
507
508 def content_for(name, content = nil, &block)
508 def content_for(name, content = nil, &block)
509 @has_content ||= {}
509 @has_content ||= {}
510 @has_content[name] = true
510 @has_content[name] = true
511 super(name, content, &block)
511 super(name, content, &block)
512 end
512 end
513
513
514 def has_content?(name)
514 def has_content?(name)
515 (@has_content && @has_content[name]) || false
515 (@has_content && @has_content[name]) || false
516 end
516 end
517 end
517 end
@@ -1,291 +1,293
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
24
24
25 def setup
25 def setup
26 super
26 super
27 end
27 end
28
28
29 def test_auto_links
29 def test_auto_links
30 to_test = {
30 to_test = {
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 '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>.',
34 '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>.',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 '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>',
36 '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>',
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 }
38 }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 end
40 end
41
41
42 def test_auto_mailto
42 def test_auto_mailto
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 textilizable('test@foo.bar')
44 textilizable('test@foo.bar')
45 end
45 end
46
46
47 def test_inline_images
47 def test_inline_images
48 to_test = {
48 to_test = {
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 }
53 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
55 end
56
56
57 def test_textile_external_links
57 def test_textile_external_links
58 to_test = {
58 to_test = {
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
62 # no multiline link text
62 # no multiline link text
63 "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 />\nand another on a second line\":test"
63 "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 />\nand another on a second line\":test"
64 }
64 }
65 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
65 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
66 end
66 end
67
67
68 def test_redmine_links
68 def test_redmine_links
69 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
69 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
70 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
70 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
71
71
72 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
72 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
73 :class => 'changeset', :title => 'My very first commit')
73 :class => 'changeset', :title => 'My very first commit')
74
74
75 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
75 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
76 :class => 'document')
76 :class => 'document')
77
77
78 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
78 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
79 :class => 'version')
79 :class => 'version')
80
80
81 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
81 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
82
82
83 to_test = {
83 to_test = {
84 # tickets
84 # tickets
85 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
85 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
86 # changesets
86 # changesets
87 'r1' => changeset_link,
87 'r1' => changeset_link,
88 # documents
88 # documents
89 'document#1' => document_link,
89 'document#1' => document_link,
90 'document:"Test document"' => document_link,
90 'document:"Test document"' => document_link,
91 # versions
91 # versions
92 'version#2' => version_link,
92 'version#2' => version_link,
93 'version:1.0' => version_link,
93 'version:1.0' => version_link,
94 'version:"1.0"' => version_link,
94 'version:"1.0"' => version_link,
95 # source
95 # source
96 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
96 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
97 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
97 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
98 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
98 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
99 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
99 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
100 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
100 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
101 # escaping
101 # escaping
102 '!#3.' => '#3.',
102 '!#3.' => '#3.',
103 '!r1' => 'r1',
103 '!r1' => 'r1',
104 '!document#1' => 'document#1',
104 '!document#1' => 'document#1',
105 '!document:"Test document"' => 'document:"Test document"',
105 '!document:"Test document"' => 'document:"Test document"',
106 '!version#2' => 'version#2',
106 '!version#2' => 'version#2',
107 '!version:1.0' => 'version:1.0',
107 '!version:1.0' => 'version:1.0',
108 '!version:"1.0"' => 'version:"1.0"',
108 '!version:"1.0"' => 'version:"1.0"',
109 '!source:/some/file' => 'source:/some/file',
109 '!source:/some/file' => 'source:/some/file',
110 # invalid expressions
110 # invalid expressions
111 'source:' => 'source:'
111 'source:' => 'source:',
112 # url hash
113 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
112 }
114 }
113 @project = Project.find(1)
115 @project = Project.find(1)
114 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
116 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
115 end
117 end
116
118
117 def test_wiki_links
119 def test_wiki_links
118 to_test = {
120 to_test = {
119 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
121 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
120 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
122 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
121 # page that doesn't exist
123 # page that doesn't exist
122 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
124 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
123 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
125 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
124 # link to another project wiki
126 # link to another project wiki
125 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
127 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
126 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
128 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
127 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
129 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
128 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
130 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
129 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
131 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
130 # striked through link
132 # striked through link
131 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
133 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
132 # escaping
134 # escaping
133 '![[Another page|Page]]' => '[[Another page|Page]]',
135 '![[Another page|Page]]' => '[[Another page|Page]]',
134 }
136 }
135 @project = Project.find(1)
137 @project = Project.find(1)
136 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
138 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
137 end
139 end
138
140
139 def test_html_tags
141 def test_html_tags
140 to_test = {
142 to_test = {
141 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
143 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
142 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
144 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
143 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
145 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
144 # do not escape pre/code tags
146 # do not escape pre/code tags
145 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
147 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
146 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
148 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
147 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
149 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
148 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
150 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
149 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
151 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
150 }
152 }
151 to_test.each { |text, result| assert_equal result, textilizable(text) }
153 to_test.each { |text, result| assert_equal result, textilizable(text) }
152 end
154 end
153
155
154 def test_wiki_links_in_tables
156 def test_wiki_links_in_tables
155 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
157 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
156 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
158 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
157 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
159 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
158 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
160 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
159 }
161 }
160 @project = Project.find(1)
162 @project = Project.find(1)
161 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
163 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
162 end
164 end
163
165
164 def test_text_formatting
166 def test_text_formatting
165 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
167 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
166 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
168 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
167 }
169 }
168 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
170 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
169 end
171 end
170
172
171 def test_wiki_horizontal_rule
173 def test_wiki_horizontal_rule
172 assert_equal '<hr />', textilizable('---')
174 assert_equal '<hr />', textilizable('---')
173 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
175 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
174 end
176 end
175
177
176 def test_blockquote
178 def test_blockquote
177 # orig raw text
179 # orig raw text
178 raw = <<-RAW
180 raw = <<-RAW
179 John said:
181 John said:
180 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
182 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
181 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
183 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
182 > * Donec odio lorem,
184 > * Donec odio lorem,
183 > * sagittis ac,
185 > * sagittis ac,
184 > * malesuada in,
186 > * malesuada in,
185 > * adipiscing eu, dolor.
187 > * adipiscing eu, dolor.
186 >
188 >
187 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
189 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
188 > Proin a tellus. Nam vel neque.
190 > Proin a tellus. Nam vel neque.
189
191
190 He's right.
192 He's right.
191 RAW
193 RAW
192
194
193 # expected html
195 # expected html
194 expected = <<-EXPECTED
196 expected = <<-EXPECTED
195 <p>John said:</p>
197 <p>John said:</p>
196 <blockquote>
198 <blockquote>
197 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
199 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
198 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
200 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
199 <ul>
201 <ul>
200 <li>Donec odio lorem,</li>
202 <li>Donec odio lorem,</li>
201 <li>sagittis ac,</li>
203 <li>sagittis ac,</li>
202 <li>malesuada in,</li>
204 <li>malesuada in,</li>
203 <li>adipiscing eu, dolor.</li>
205 <li>adipiscing eu, dolor.</li>
204 </ul>
206 </ul>
205 <blockquote>
207 <blockquote>
206 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
208 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
207 </blockquote>
209 </blockquote>
208 <p>Proin a tellus. Nam vel neque.</p>
210 <p>Proin a tellus. Nam vel neque.</p>
209 </blockquote>
211 </blockquote>
210 <p>He's right.</p>
212 <p>He's right.</p>
211 EXPECTED
213 EXPECTED
212
214
213 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
215 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
214 end
216 end
215
217
216 def test_table
218 def test_table
217 raw = <<-RAW
219 raw = <<-RAW
218 This is a table with empty cells:
220 This is a table with empty cells:
219
221
220 |cell11|cell12||
222 |cell11|cell12||
221 |cell21||cell23|
223 |cell21||cell23|
222 |cell31|cell32|cell33|
224 |cell31|cell32|cell33|
223 RAW
225 RAW
224
226
225 expected = <<-EXPECTED
227 expected = <<-EXPECTED
226 <p>This is a table with empty cells:</p>
228 <p>This is a table with empty cells:</p>
227
229
228 <table>
230 <table>
229 <tr><td>cell11</td><td>cell12</td><td></td></tr>
231 <tr><td>cell11</td><td>cell12</td><td></td></tr>
230 <tr><td>cell21</td><td></td><td>cell23</td></tr>
232 <tr><td>cell21</td><td></td><td>cell23</td></tr>
231 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
233 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
232 </table>
234 </table>
233 EXPECTED
235 EXPECTED
234
236
235 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
237 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
236 end
238 end
237
239
238 def test_macro_hello_world
240 def test_macro_hello_world
239 text = "{{hello_world}}"
241 text = "{{hello_world}}"
240 assert textilizable(text).match(/Hello world!/)
242 assert textilizable(text).match(/Hello world!/)
241 # escaping
243 # escaping
242 text = "!{{hello_world}}"
244 text = "!{{hello_world}}"
243 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
245 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
244 end
246 end
245
247
246 def test_macro_include
248 def test_macro_include
247 @project = Project.find(1)
249 @project = Project.find(1)
248 # include a page of the current project wiki
250 # include a page of the current project wiki
249 text = "{{include(Another page)}}"
251 text = "{{include(Another page)}}"
250 assert textilizable(text).match(/This is a link to a ticket/)
252 assert textilizable(text).match(/This is a link to a ticket/)
251
253
252 @project = nil
254 @project = nil
253 # include a page of a specific project wiki
255 # include a page of a specific project wiki
254 text = "{{include(ecookbook:Another page)}}"
256 text = "{{include(ecookbook:Another page)}}"
255 assert textilizable(text).match(/This is a link to a ticket/)
257 assert textilizable(text).match(/This is a link to a ticket/)
256
258
257 text = "{{include(ecookbook:)}}"
259 text = "{{include(ecookbook:)}}"
258 assert textilizable(text).match(/CookBook documentation/)
260 assert textilizable(text).match(/CookBook documentation/)
259
261
260 text = "{{include(unknowidentifier:somepage)}}"
262 text = "{{include(unknowidentifier:somepage)}}"
261 assert textilizable(text).match(/Unknow project/)
263 assert textilizable(text).match(/Unknow project/)
262 end
264 end
263
265
264 def test_date_format_default
266 def test_date_format_default
265 today = Date.today
267 today = Date.today
266 Setting.date_format = ''
268 Setting.date_format = ''
267 assert_equal l_date(today), format_date(today)
269 assert_equal l_date(today), format_date(today)
268 end
270 end
269
271
270 def test_date_format
272 def test_date_format
271 today = Date.today
273 today = Date.today
272 Setting.date_format = '%d %m %Y'
274 Setting.date_format = '%d %m %Y'
273 assert_equal today.strftime('%d %m %Y'), format_date(today)
275 assert_equal today.strftime('%d %m %Y'), format_date(today)
274 end
276 end
275
277
276 def test_time_format_default
278 def test_time_format_default
277 now = Time.now
279 now = Time.now
278 Setting.date_format = ''
280 Setting.date_format = ''
279 Setting.time_format = ''
281 Setting.time_format = ''
280 assert_equal l_datetime(now), format_time(now)
282 assert_equal l_datetime(now), format_time(now)
281 assert_equal l_time(now), format_time(now, false)
283 assert_equal l_time(now), format_time(now, false)
282 end
284 end
283
285
284 def test_time_format
286 def test_time_format
285 now = Time.now
287 now = Time.now
286 Setting.date_format = '%d %m %Y'
288 Setting.date_format = '%d %m %Y'
287 Setting.time_format = '%H %M'
289 Setting.time_format = '%H %M'
288 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
290 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
289 assert_equal now.strftime('%H %M'), format_time(now, false)
291 assert_equal now.strftime('%H %M'), format_time(now, false)
290 end
292 end
291 end
293 end
General Comments 0
You need to be logged in to leave comments. Login now