##// END OF EJS Templates
Adds support for wiki links with anchor (#1647)....
Jean-Philippe Lang -
r1697:198a8c602dc1
parent child
Show More
@@ -1,535 +1,540
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 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
249 format_wiki_link = Proc.new {|project, title, anchor| "#{title}.html" }
250 250 when :anchor
251 251 # used for single-file wiki export
252 format_wiki_link = Proc.new {|project, title| "##{title}" }
252 format_wiki_link = Proc.new {|project, title, anchor| "##{title}" }
253 253 else
254 format_wiki_link = Proc.new {|project, title| url_for(:only_path => only_path, :controller => 'wiki', :action => 'index', :id => project, :page => title) }
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 # extract anchor
281 anchor = nil
282 if page =~ /^(.+?)\#(.+)$/
283 page, anchor = $1, $2
284 end
280 285 # check if page exists
281 286 wiki_page = link_project.wiki.find_page(page)
282 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
287 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page), anchor),
283 288 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
284 289 else
285 290 # project or wiki doesn't exist
286 291 title || page
287 292 end
288 293 else
289 294 all
290 295 end
291 296 end
292 297
293 298 # Redmine links
294 299 #
295 300 # Examples:
296 301 # Issues:
297 302 # #52 -> Link to issue #52
298 303 # Changesets:
299 304 # r52 -> Link to revision 52
300 305 # commit:a85130f -> Link to scmid starting with a85130f
301 306 # Documents:
302 307 # document#17 -> Link to document with id 17
303 308 # document:Greetings -> Link to the document with title "Greetings"
304 309 # document:"Some document" -> Link to the document with title "Some document"
305 310 # Versions:
306 311 # version#3 -> Link to version with id 3
307 312 # version:1.0.0 -> Link to version named "1.0.0"
308 313 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
309 314 # Attachments:
310 315 # attachment:file.zip -> Link to the attachment of the current object named file.zip
311 316 # Source files:
312 317 # source:some/file -> Link to the file located at /some/file in the project's repository
313 318 # source:some/file@52 -> Link to the file's revision 52
314 319 # source:some/file#L120 -> Link to line 120 of the file
315 320 # source:some/file@52#L120 -> Link to line 120 of the file's revision 52
316 321 # export:some/file -> Force the download of the file
317 322 text = text.gsub(%r{([\s\(,\-\>]|^)(!)?(attachment|document|version|commit|source|export)?((#|r)(\d+)|(:)([^"\s<>][^\s<>]*?|"[^"]+?"))(?=(?=[[:punct:]]\W)|\s|<|$)}) do |m|
318 323 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
319 324 link = nil
320 325 if esc.nil?
321 326 if prefix.nil? && sep == 'r'
322 327 if project && (changeset = project.changesets.find_by_revision(oid))
323 328 link = link_to("r#{oid}", {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => oid},
324 329 :class => 'changeset',
325 330 :title => truncate_single_line(changeset.comments, 100))
326 331 end
327 332 elsif sep == '#'
328 333 oid = oid.to_i
329 334 case prefix
330 335 when nil
331 336 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
332 337 link = link_to("##{oid}", {:only_path => only_path, :controller => 'issues', :action => 'show', :id => oid},
333 338 :class => (issue.closed? ? 'issue closed' : 'issue'),
334 339 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
335 340 link = content_tag('del', link) if issue.closed?
336 341 end
337 342 when 'document'
338 343 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
339 344 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
340 345 :class => 'document'
341 346 end
342 347 when 'version'
343 348 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
344 349 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
345 350 :class => 'version'
346 351 end
347 352 end
348 353 elsif sep == ':'
349 354 # removes the double quotes if any
350 355 name = oid.gsub(%r{^"(.*)"$}, "\\1")
351 356 case prefix
352 357 when 'document'
353 358 if project && document = project.documents.find_by_title(name)
354 359 link = link_to h(document.title), {:only_path => only_path, :controller => 'documents', :action => 'show', :id => document},
355 360 :class => 'document'
356 361 end
357 362 when 'version'
358 363 if project && version = project.versions.find_by_name(name)
359 364 link = link_to h(version.name), {:only_path => only_path, :controller => 'versions', :action => 'show', :id => version},
360 365 :class => 'version'
361 366 end
362 367 when 'commit'
363 368 if project && (changeset = project.changesets.find(:first, :conditions => ["scmid LIKE ?", "#{name}%"]))
364 369 link = link_to h("#{name}"), {:only_path => only_path, :controller => 'repositories', :action => 'revision', :id => project, :rev => changeset.revision},
365 370 :class => 'changeset',
366 371 :title => truncate_single_line(changeset.comments, 100)
367 372 end
368 373 when 'source', 'export'
369 374 if project && project.repository
370 375 name =~ %r{^[/\\]*(.*?)(@([0-9a-f]+))?(#(L\d+))?$}
371 376 path, rev, anchor = $1, $3, $5
372 377 link = link_to h("#{prefix}:#{name}"), {:controller => 'repositories', :action => 'entry', :id => project,
373 378 :path => to_path_param(path),
374 379 :rev => rev,
375 380 :anchor => anchor,
376 381 :format => (prefix == 'export' ? 'raw' : nil)},
377 382 :class => (prefix == 'export' ? 'source download' : 'source')
378 383 end
379 384 when 'attachment'
380 385 if attachments && attachment = attachments.detect {|a| a.filename == name }
381 386 link = link_to h(attachment.filename), {:only_path => only_path, :controller => 'attachments', :action => 'download', :id => attachment},
382 387 :class => 'attachment'
383 388 end
384 389 end
385 390 end
386 391 end
387 392 leading + (link || "#{prefix}#{sep}#{oid}")
388 393 end
389 394
390 395 text
391 396 end
392 397
393 398 # Same as Rails' simple_format helper without using paragraphs
394 399 def simple_format_without_paragraph(text)
395 400 text.to_s.
396 401 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
397 402 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
398 403 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
399 404 end
400 405
401 406 def error_messages_for(object_name, options = {})
402 407 options = options.symbolize_keys
403 408 object = instance_variable_get("@#{object_name}")
404 409 if object && !object.errors.empty?
405 410 # build full_messages here with controller current language
406 411 full_messages = []
407 412 object.errors.each do |attr, msg|
408 413 next if msg.nil?
409 414 msg = msg.first if msg.is_a? Array
410 415 if attr == "base"
411 416 full_messages << l(msg)
412 417 else
413 418 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
414 419 end
415 420 end
416 421 # retrieve custom values error messages
417 422 if object.errors[:custom_values]
418 423 object.custom_values.each do |v|
419 424 v.errors.each do |attr, msg|
420 425 next if msg.nil?
421 426 msg = msg.first if msg.is_a? Array
422 427 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
423 428 end
424 429 end
425 430 end
426 431 content_tag("div",
427 432 content_tag(
428 433 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
429 434 ) +
430 435 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
431 436 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
432 437 )
433 438 else
434 439 ""
435 440 end
436 441 end
437 442
438 443 def lang_options_for_select(blank=true)
439 444 (blank ? [["(auto)", ""]] : []) +
440 445 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
441 446 end
442 447
443 448 def label_tag_for(name, option_tags = nil, options = {})
444 449 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
445 450 content_tag("label", label_text)
446 451 end
447 452
448 453 def labelled_tabular_form_for(name, object, options, &proc)
449 454 options[:html] ||= {}
450 455 options[:html][:class] = 'tabular' unless options[:html].has_key?(:class)
451 456 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
452 457 end
453 458
454 459 def back_url_hidden_field_tag
455 460 back_url = params[:back_url] || request.env['HTTP_REFERER']
456 461 hidden_field_tag('back_url', back_url) unless back_url.blank?
457 462 end
458 463
459 464 def check_all_links(form_name)
460 465 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
461 466 " | " +
462 467 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
463 468 end
464 469
465 470 def progress_bar(pcts, options={})
466 471 pcts = [pcts, pcts] unless pcts.is_a?(Array)
467 472 pcts[1] = pcts[1] - pcts[0]
468 473 pcts << (100 - pcts[1] - pcts[0])
469 474 width = options[:width] || '100px;'
470 475 legend = options[:legend] || ''
471 476 content_tag('table',
472 477 content_tag('tr',
473 478 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
474 479 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
475 480 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
476 481 ), :class => 'progress', :style => "width: #{width};") +
477 482 content_tag('p', legend, :class => 'pourcent')
478 483 end
479 484
480 485 def context_menu_link(name, url, options={})
481 486 options[:class] ||= ''
482 487 if options.delete(:selected)
483 488 options[:class] << ' icon-checked disabled'
484 489 options[:disabled] = true
485 490 end
486 491 if options.delete(:disabled)
487 492 options.delete(:method)
488 493 options.delete(:confirm)
489 494 options.delete(:onclick)
490 495 options[:class] << ' disabled'
491 496 url = '#'
492 497 end
493 498 link_to name, url, options
494 499 end
495 500
496 501 def calendar_for(field_id)
497 502 include_calendar_headers_tags
498 503 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
499 504 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
500 505 end
501 506
502 507 def include_calendar_headers_tags
503 508 unless @calendar_headers_tags_included
504 509 @calendar_headers_tags_included = true
505 510 content_for :header_tags do
506 511 javascript_include_tag('calendar/calendar') +
507 512 javascript_include_tag("calendar/lang/calendar-#{current_language}.js") +
508 513 javascript_include_tag('calendar/calendar-setup') +
509 514 stylesheet_link_tag('calendar')
510 515 end
511 516 end
512 517 end
513 518
514 519 def wikitoolbar_for(field_id)
515 520 return '' unless Setting.text_formatting == 'textile'
516 521
517 522 help_link = l(:setting_text_formatting) + ': ' +
518 523 link_to(l(:label_help), compute_public_path('wiki_syntax', 'help', 'html'),
519 524 :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;")
520 525
521 526 javascript_include_tag('jstoolbar/jstoolbar') +
522 527 javascript_include_tag("jstoolbar/lang/jstoolbar-#{current_language}") +
523 528 javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.setHelpLink('#{help_link}'); toolbar.draw();")
524 529 end
525 530
526 531 def content_for(name, content = nil, &block)
527 532 @has_content ||= {}
528 533 @has_content[name] = true
529 534 super(name, content, &block)
530 535 end
531 536
532 537 def has_content?(name)
533 538 (@has_content && @has_content[name]) || false
534 539 end
535 540 end
@@ -1,342 +1,345
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 23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
24 24
25 25 def setup
26 26 super
27 27 end
28 28
29 29 def test_auto_links
30 30 to_test = {
31 31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 34 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
35 35 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
36 36 '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 37 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
38 38 '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 39 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
40 40 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
41 41 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
42 42 }
43 43 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
44 44 end
45 45
46 46 def test_auto_mailto
47 47 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
48 48 textilizable('test@foo.bar')
49 49 end
50 50
51 51 def test_inline_images
52 52 to_test = {
53 53 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
54 54 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
55 55 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
56 56 '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 57 }
58 58 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
59 59 end
60 60
61 61 def test_textile_external_links
62 62 to_test = {
63 63 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
64 64 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
65 65 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
66 66 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
67 67 # no multiline link text
68 68 "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 69 }
70 70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 71 end
72 72
73 73 def test_redmine_links
74 74 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
75 75 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
76 76
77 77 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
78 78 :class => 'changeset', :title => 'My very first commit')
79 79
80 80 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
81 81 :class => 'document')
82 82
83 83 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
84 84 :class => 'version')
85 85
86 86 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
87 87 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
88 88
89 89 to_test = {
90 90 # tickets
91 91 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
92 92 # changesets
93 93 'r1' => changeset_link,
94 94 # documents
95 95 'document#1' => document_link,
96 96 'document:"Test document"' => document_link,
97 97 # versions
98 98 'version#2' => version_link,
99 99 'version:1.0' => version_link,
100 100 'version:"1.0"' => version_link,
101 101 # source
102 102 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
103 103 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
104 104 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
105 105 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
106 106 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
107 107 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
108 108 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
109 109 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
110 110 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
111 111 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
112 112 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
113 113 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
114 114 # escaping
115 115 '!#3.' => '#3.',
116 116 '!r1' => 'r1',
117 117 '!document#1' => 'document#1',
118 118 '!document:"Test document"' => 'document:"Test document"',
119 119 '!version#2' => 'version#2',
120 120 '!version:1.0' => 'version:1.0',
121 121 '!version:"1.0"' => 'version:"1.0"',
122 122 '!source:/some/file' => 'source:/some/file',
123 123 # invalid expressions
124 124 'source:' => 'source:',
125 125 # url hash
126 126 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
127 127 }
128 128 @project = Project.find(1)
129 129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
130 130 end
131 131
132 132 def test_wiki_links
133 133 to_test = {
134 134 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
135 135 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
136 # link with anchor
137 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
138 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
136 139 # page that doesn't exist
137 140 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
138 141 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
139 142 # link to another project wiki
140 143 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
141 144 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
142 145 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
143 146 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
144 147 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
145 148 # striked through link
146 149 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
147 150 # escaping
148 151 '![[Another page|Page]]' => '[[Another page|Page]]',
149 152 }
150 153 @project = Project.find(1)
151 154 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
152 155 end
153 156
154 157 def test_html_tags
155 158 to_test = {
156 159 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
157 160 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
158 161 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
159 162 # do not escape pre/code tags
160 163 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
161 164 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
162 165 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
163 166 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
164 167 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
165 168 }
166 169 to_test.each { |text, result| assert_equal result, textilizable(text) }
167 170 end
168 171
169 172 def test_allowed_html_tags
170 173 to_test = {
171 174 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
172 175 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
173 176 }
174 177 to_test.each { |text, result| assert_equal result, textilizable(text) }
175 178 end
176 179
177 180 def test_wiki_links_in_tables
178 181 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
179 182 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
180 183 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
181 184 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
182 185 }
183 186 @project = Project.find(1)
184 187 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
185 188 end
186 189
187 190 def test_text_formatting
188 191 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
189 192 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
190 193 }
191 194 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
192 195 end
193 196
194 197 def test_wiki_horizontal_rule
195 198 assert_equal '<hr />', textilizable('---')
196 199 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
197 200 end
198 201
199 202 def test_table_of_content
200 203 raw = <<-RAW
201 204 {{toc}}
202 205
203 206 h1. Title
204 207
205 208 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
206 209
207 210 h2. Subtitle
208 211
209 212 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
210 213
211 214 h2. Subtitle with %{color:red}red text%
212 215
213 216 h1. Another title
214 217
215 218 RAW
216 219
217 220 expected = '<ul class="toc">' +
218 221 '<li class="heading1"><a href="#Title">Title</a></li>' +
219 222 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
220 223 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
221 224 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
222 225 '</ul>'
223 226
224 227 assert textilizable(raw).gsub("\n", "").include?(expected)
225 228 end
226 229
227 230 def test_blockquote
228 231 # orig raw text
229 232 raw = <<-RAW
230 233 John said:
231 234 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
232 235 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
233 236 > * Donec odio lorem,
234 237 > * sagittis ac,
235 238 > * malesuada in,
236 239 > * adipiscing eu, dolor.
237 240 >
238 241 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
239 242 > Proin a tellus. Nam vel neque.
240 243
241 244 He's right.
242 245 RAW
243 246
244 247 # expected html
245 248 expected = <<-EXPECTED
246 249 <p>John said:</p>
247 250 <blockquote>
248 251 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
249 252 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
250 253 <ul>
251 254 <li>Donec odio lorem,</li>
252 255 <li>sagittis ac,</li>
253 256 <li>malesuada in,</li>
254 257 <li>adipiscing eu, dolor.</li>
255 258 </ul>
256 259 <blockquote>
257 260 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
258 261 </blockquote>
259 262 <p>Proin a tellus. Nam vel neque.</p>
260 263 </blockquote>
261 264 <p>He's right.</p>
262 265 EXPECTED
263 266
264 267 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
265 268 end
266 269
267 270 def test_table
268 271 raw = <<-RAW
269 272 This is a table with empty cells:
270 273
271 274 |cell11|cell12||
272 275 |cell21||cell23|
273 276 |cell31|cell32|cell33|
274 277 RAW
275 278
276 279 expected = <<-EXPECTED
277 280 <p>This is a table with empty cells:</p>
278 281
279 282 <table>
280 283 <tr><td>cell11</td><td>cell12</td><td></td></tr>
281 284 <tr><td>cell21</td><td></td><td>cell23</td></tr>
282 285 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
283 286 </table>
284 287 EXPECTED
285 288
286 289 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
287 290 end
288 291
289 292 def test_macro_hello_world
290 293 text = "{{hello_world}}"
291 294 assert textilizable(text).match(/Hello world!/)
292 295 # escaping
293 296 text = "!{{hello_world}}"
294 297 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
295 298 end
296 299
297 300 def test_macro_include
298 301 @project = Project.find(1)
299 302 # include a page of the current project wiki
300 303 text = "{{include(Another page)}}"
301 304 assert textilizable(text).match(/This is a link to a ticket/)
302 305
303 306 @project = nil
304 307 # include a page of a specific project wiki
305 308 text = "{{include(ecookbook:Another page)}}"
306 309 assert textilizable(text).match(/This is a link to a ticket/)
307 310
308 311 text = "{{include(ecookbook:)}}"
309 312 assert textilizable(text).match(/CookBook documentation/)
310 313
311 314 text = "{{include(unknowidentifier:somepage)}}"
312 315 assert textilizable(text).match(/Unknow project/)
313 316 end
314 317
315 318 def test_date_format_default
316 319 today = Date.today
317 320 Setting.date_format = ''
318 321 assert_equal l_date(today), format_date(today)
319 322 end
320 323
321 324 def test_date_format
322 325 today = Date.today
323 326 Setting.date_format = '%d %m %Y'
324 327 assert_equal today.strftime('%d %m %Y'), format_date(today)
325 328 end
326 329
327 330 def test_time_format_default
328 331 now = Time.now
329 332 Setting.date_format = ''
330 333 Setting.time_format = ''
331 334 assert_equal l_datetime(now), format_time(now)
332 335 assert_equal l_time(now), format_time(now, false)
333 336 end
334 337
335 338 def test_time_format
336 339 now = Time.now
337 340 Setting.date_format = '%d %m %Y'
338 341 Setting.time_format = '%H %M'
339 342 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
340 343 assert_equal now.strftime('%H %M'), format_time(now, false)
341 344 end
342 345 end
General Comments 0
You need to be logged in to leave comments. Login now