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