##// END OF EJS Templates
Fixed: Textile image with style attribute cause internal server error....
Jean-Philippe Lang -
r1004:5f871e965746
parent child
Show More
@@ -1,407 +1,407
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 module ApplicationHelper
19 19 include Redmine::WikiFormatting::Macros::Definitions
20 20
21 21 def current_role
22 22 @current_role ||= User.current.role_for_project(@project)
23 23 end
24 24
25 25 # Return true if user is authorized for controller/action, otherwise false
26 26 def authorize_for(controller, action)
27 27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 28 end
29 29
30 30 # Display a link if user is authorized
31 31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 33 end
34 34
35 35 # Display a link to user's account page
36 36 def link_to_user(user)
37 37 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
38 38 end
39 39
40 40 def link_to_issue(issue)
41 41 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
42 42 end
43 43
44 44 def toggle_link(name, id, options={})
45 45 onclick = "Element.toggle('#{id}'); "
46 46 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
47 47 onclick << "return false;"
48 48 link_to(name, "#", :onclick => onclick)
49 49 end
50 50
51 51 def show_and_goto_link(name, id, options={})
52 52 onclick = "Element.show('#{id}'); "
53 53 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 54 onclick << "location.href='##{id}-anchor'; "
55 55 onclick << "return false;"
56 56 link_to(name, "#", options.merge(:onclick => onclick))
57 57 end
58 58
59 59 def image_to_function(name, function, html_options = {})
60 60 html_options.symbolize_keys!
61 61 tag(:input, html_options.merge({
62 62 :type => "image", :src => image_path(name),
63 63 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
64 64 }))
65 65 end
66 66
67 67 def prompt_to_remote(name, text, param, url, html_options = {})
68 68 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
69 69 link_to name, {}, html_options
70 70 end
71 71
72 72 def format_date(date)
73 73 return nil unless date
74 74 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
75 75 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
76 76 date.strftime(@date_format)
77 77 end
78 78
79 79 def format_time(time, include_date = true)
80 80 return nil unless time
81 81 time = time.to_time if time.is_a?(String)
82 82 zone = User.current.time_zone
83 83 if time.utc?
84 84 local = zone ? zone.adjust(time) : time.getlocal
85 85 else
86 86 local = zone ? zone.adjust(time.getutc) : time
87 87 end
88 88 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
89 89 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
90 90 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
91 91 end
92 92
93 93 def authoring(created, author)
94 94 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
95 95 l(:label_added_time_by, author || 'Anonymous', time_tag)
96 96 end
97 97
98 98 def day_name(day)
99 99 l(:general_day_names).split(',')[day-1]
100 100 end
101 101
102 102 def month_name(month)
103 103 l(:actionview_datehelper_select_month_names).split(',')[month-1]
104 104 end
105 105
106 106 def pagination_links_full(paginator, options={}, html_options={})
107 107 page_param = options.delete(:page_param) || :page
108 108
109 109 html = ''
110 110 html << link_to_remote(('&#171; ' + l(:label_previous)),
111 111 {:update => "content", :url => options.merge(page_param => paginator.current.previous)},
112 112 {:href => url_for(:params => options.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
113 113
114 114 html << (pagination_links_each(paginator, options) do |n|
115 115 link_to_remote(n.to_s,
116 116 {:url => {:params => options.merge(page_param => n)}, :update => 'content'},
117 117 {:href => url_for(:params => options.merge(page_param => n))})
118 118 end || '')
119 119
120 120 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
121 121 {:update => "content", :url => options.merge(page_param => paginator.current.next)},
122 122 {:href => url_for(:params => options.merge(page_param => paginator.current.next))}) if paginator.current.next
123 123 html
124 124 end
125 125
126 126 def set_html_title(text)
127 127 @html_header_title = text
128 128 end
129 129
130 130 def html_title
131 131 title = []
132 132 title << @project.name if @project
133 133 title << @html_header_title
134 134 title << Setting.app_title
135 135 title.compact.join(' - ')
136 136 end
137 137
138 138 ACCESSKEYS = {:edit => 'e',
139 139 :preview => 'r',
140 140 :quick_search => 'f',
141 141 :search => '4',
142 142 }.freeze unless const_defined?(:ACCESSKEYS)
143 143
144 144 def accesskey(s)
145 145 ACCESSKEYS[s]
146 146 end
147 147
148 148 # Formats text according to system settings.
149 149 # 2 ways to call this method:
150 150 # * with a String: textilizable(text, options)
151 151 # * with an object and one of its attribute: textilizable(issue, :description, options)
152 152 def textilizable(*args)
153 153 options = args.last.is_a?(Hash) ? args.pop : {}
154 154 case args.size
155 155 when 1
156 156 obj = nil
157 157 text = args.shift || ''
158 158 when 2
159 159 obj = args.shift
160 160 text = obj.send(args.shift)
161 161 else
162 162 raise ArgumentError, 'invalid arguments to textilizable'
163 163 end
164 164
165 165 # when using an image link, try to use an attachment, if possible
166 166 attachments = options[:attachments]
167 167 if attachments
168 text = text.gsub(/!([<>=]*)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
169 align = $1
170 filename = $2
168 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
169 style = $1
170 filename = $6
171 171 rf = Regexp.new(filename, Regexp::IGNORECASE)
172 172 # search for the picture in attachments
173 173 if found = attachments.detect { |att| att.filename =~ rf }
174 174 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
175 "!#{align}#{image_url}!"
175 "!#{style}#{image_url}!"
176 176 else
177 "!#{align}#{filename}!"
177 "!#{style}#{filename}!"
178 178 end
179 179 end
180 180 end
181 181
182 182 text = (Setting.text_formatting == 'textile') ?
183 183 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
184 184 simple_format(auto_link(h(text)))
185 185
186 186 # different methods for formatting wiki links
187 187 case options[:wiki_links]
188 188 when :local
189 189 # used for local links to html files
190 190 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
191 191 when :anchor
192 192 # used for single-file wiki export
193 193 format_wiki_link = Proc.new {|project, title| "##{title}" }
194 194 else
195 195 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
196 196 end
197 197
198 198 project = options[:project] || @project
199 199
200 200 # turn wiki links into html links
201 201 # example:
202 202 # [[mypage]]
203 203 # [[mypage|mytext]]
204 204 # wiki links can refer other project wikis, using project name or identifier:
205 205 # [[project:]] -> wiki starting page
206 206 # [[project:|mytext]]
207 207 # [[project:mypage]]
208 208 # [[project:mypage|mytext]]
209 209 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
210 210 link_project = project
211 211 page = $1
212 212 title = $3
213 213 if page =~ /^([^\:]+)\:(.*)$/
214 214 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
215 215 page = title || $2
216 216 title = $1 if page.blank?
217 217 end
218 218
219 219 if link_project && link_project.wiki
220 220 # check if page exists
221 221 wiki_page = link_project.wiki.find_page(page)
222 222 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
223 223 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
224 224 else
225 225 # project or wiki doesn't exist
226 226 title || page
227 227 end
228 228 end
229 229
230 230 # turn issue and revision ids into links
231 231 # example:
232 232 # #52 -> <a href="/issues/show/52">#52</a>
233 233 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
234 234 text = text.gsub(%r{([\s\(,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
235 235 leading, otype, oid = $1, $2, $3
236 236 link = nil
237 237 if otype == 'r'
238 238 if project && (changeset = project.changesets.find_by_revision(oid))
239 239 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
240 240 :title => truncate(changeset.comments, 100))
241 241 end
242 242 else
243 243 if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
244 244 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
245 245 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
246 246 link = content_tag('del', link) if issue.closed?
247 247 end
248 248 end
249 249 leading + (link || "#{otype}#{oid}")
250 250 end
251 251
252 252 text
253 253 end
254 254
255 255 # Same as Rails' simple_format helper without using paragraphs
256 256 def simple_format_without_paragraph(text)
257 257 text.to_s.
258 258 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
259 259 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
260 260 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
261 261 end
262 262
263 263 def error_messages_for(object_name, options = {})
264 264 options = options.symbolize_keys
265 265 object = instance_variable_get("@#{object_name}")
266 266 if object && !object.errors.empty?
267 267 # build full_messages here with controller current language
268 268 full_messages = []
269 269 object.errors.each do |attr, msg|
270 270 next if msg.nil?
271 271 msg = msg.first if msg.is_a? Array
272 272 if attr == "base"
273 273 full_messages << l(msg)
274 274 else
275 275 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
276 276 end
277 277 end
278 278 # retrieve custom values error messages
279 279 if object.errors[:custom_values]
280 280 object.custom_values.each do |v|
281 281 v.errors.each do |attr, msg|
282 282 next if msg.nil?
283 283 msg = msg.first if msg.is_a? Array
284 284 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
285 285 end
286 286 end
287 287 end
288 288 content_tag("div",
289 289 content_tag(
290 290 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
291 291 ) +
292 292 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
293 293 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
294 294 )
295 295 else
296 296 ""
297 297 end
298 298 end
299 299
300 300 def lang_options_for_select(blank=true)
301 301 (blank ? [["(auto)", ""]] : []) +
302 302 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
303 303 end
304 304
305 305 def label_tag_for(name, option_tags = nil, options = {})
306 306 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
307 307 content_tag("label", label_text)
308 308 end
309 309
310 310 def labelled_tabular_form_for(name, object, options, &proc)
311 311 options[:html] ||= {}
312 312 options[:html].store :class, "tabular"
313 313 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
314 314 end
315 315
316 316 def check_all_links(form_name)
317 317 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
318 318 " | " +
319 319 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
320 320 end
321 321
322 322 def progress_bar(pcts, options={})
323 323 pcts = [pcts, pcts] unless pcts.is_a?(Array)
324 324 pcts[1] = pcts[1] - pcts[0]
325 325 pcts << (100 - pcts[1] - pcts[0])
326 326 width = options[:width] || '100px;'
327 327 legend = options[:legend] || ''
328 328 content_tag('table',
329 329 content_tag('tr',
330 330 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
331 331 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
332 332 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
333 333 ), :class => 'progress', :style => "width: #{width};") +
334 334 content_tag('p', legend, :class => 'pourcent')
335 335 end
336 336
337 337 def context_menu_link(name, url, options={})
338 338 options[:class] ||= ''
339 339 if options.delete(:selected)
340 340 options[:class] << ' icon-checked disabled'
341 341 options[:disabled] = true
342 342 end
343 343 if options.delete(:disabled)
344 344 options.delete(:method)
345 345 options.delete(:confirm)
346 346 options.delete(:onclick)
347 347 options[:class] << ' disabled'
348 348 url = '#'
349 349 end
350 350 link_to name, url, options
351 351 end
352 352
353 353 def calendar_for(field_id)
354 354 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
355 355 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
356 356 end
357 357
358 358 def wikitoolbar_for(field_id)
359 359 return '' unless Setting.text_formatting == 'textile'
360 360 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
361 361 end
362 362
363 363 def content_for(name, content = nil, &block)
364 364 @has_content ||= {}
365 365 @has_content[name] = true
366 366 super(name, content, &block)
367 367 end
368 368
369 369 def has_content?(name)
370 370 (@has_content && @has_content[name]) || false
371 371 end
372 372 end
373 373
374 374 class TabularFormBuilder < ActionView::Helpers::FormBuilder
375 375 include GLoc
376 376
377 377 def initialize(object_name, object, template, options, proc)
378 378 set_language_if_valid options.delete(:lang)
379 379 @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
380 380 end
381 381
382 382 (field_helpers - %w(radio_button hidden_field) + %w(date_select)).each do |selector|
383 383 src = <<-END_SRC
384 384 def #{selector}(field, options = {})
385 385 return super if options.delete :no_label
386 386 label_text = l(options[:label]) if options[:label]
387 387 label_text ||= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym)
388 388 label_text << @template.content_tag("span", " *", :class => "required") if options.delete(:required)
389 389 label = @template.content_tag("label", label_text,
390 390 :class => (@object && @object.errors[field] ? "error" : nil),
391 391 :for => (@object_name.to_s + "_" + field.to_s))
392 392 label + super
393 393 end
394 394 END_SRC
395 395 class_eval src, __FILE__, __LINE__
396 396 end
397 397
398 398 def select(field, choices, options = {}, html_options = {})
399 399 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
400 400 label = @template.content_tag("label", label_text,
401 401 :class => (@object && @object.errors[field] ? "error" : nil),
402 402 :for => (@object_name.to_s + "_" + field.to_s))
403 403 label + super
404 404 end
405 405
406 406 end
407 407
@@ -1,160 +1,161
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 'redcloth'
19 19 require 'coderay'
20 20
21 21 module Redmine
22 22 module WikiFormatting
23 23
24 24 private
25 25
26 26 class TextileFormatter < RedCloth
27 27
28 RULES = [:inline_auto_link, :inline_auto_mailto, :textile, :inline_toc, :inline_macros]
28 # auto_link rule after textile rules so that it doesn't break !image_url! tags
29 RULES = [:textile, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
29 30
30 31 def initialize(*args)
31 32 super
32 33 self.hard_breaks=true
33 34 self.no_span_caps=true
34 35 end
35 36
36 37 def to_html(*rules, &block)
37 38 @toc = []
38 39 @macros_runner = block
39 40 super(*RULES).to_s
40 41 end
41 42
42 43 private
43 44
44 45 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
45 46 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
46 47 def hard_break( text )
47 48 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
48 49 end
49 50
50 51 # Patch to add code highlighting support to RedCloth
51 52 def smooth_offtags( text )
52 53 unless @pre_list.empty?
53 54 ## replace <pre> content
54 55 text.gsub!(/<redpre#(\d+)>/) do
55 56 content = @pre_list[$1.to_i]
56 57 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
57 58 content = "<code class=\"#{$1} CodeRay\">" +
58 59 CodeRay.scan($2, $1).html(:escape => false, :line_numbers => :inline)
59 60 end
60 61 content
61 62 end
62 63 end
63 64 end
64 65
65 66 # Patch to add 'table of content' support to RedCloth
66 67 def textile_p_withtoc(tag, atts, cite, content)
67 68 if tag =~ /^h(\d)$/
68 69 @toc << [$1.to_i, content]
69 70 end
70 71 content = "<a name=\"#{@toc.length}\" class=\"wiki-page\"></a>" + content
71 72 textile_p(tag, atts, cite, content)
72 73 end
73 74
74 75 alias :textile_h1 :textile_p_withtoc
75 76 alias :textile_h2 :textile_p_withtoc
76 77 alias :textile_h3 :textile_p_withtoc
77 78
78 79 def inline_toc(text)
79 80 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
80 81 div_class = 'toc'
81 82 div_class << ' right' if $1 == '>'
82 83 div_class << ' left' if $1 == '<'
83 84 out = "<div class=\"#{div_class}\">"
84 85 @toc.each_with_index do |heading, index|
85 86 # remove wiki links from the item
86 87 toc_item = heading.last.gsub(/(\[\[|\]\])/, '')
87 88 out << "<a href=\"##{index+1}\" class=\"heading#{heading.first}\">#{toc_item}</a>"
88 89 end
89 90 out << '</div>'
90 91 out
91 92 end
92 93 end
93 94
94 95 MACROS_RE = /
95 96 \{\{ # opening tag
96 97 ([\w]+) # macro name
97 98 (\(([^\}]*)\))? # optional arguments
98 99 \}\} # closing tag
99 100 /x unless const_defined?(:MACROS_RE)
100 101
101 102 def inline_macros(text)
102 103 text.gsub!(MACROS_RE) do
103 104 all, macro = $&, $1.downcase
104 105 args = ($3 || '').split(',').each(&:strip)
105 106 begin
106 107 @macros_runner.call(macro, args)
107 108 rescue => e
108 109 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
109 110 end || all
110 111 end
111 112 end
112 113
113 114 AUTO_LINK_RE = %r{
114 115 ( # leading text
115 116 <\w+.*?>| # leading HTML tag, or
116 117 [^=<>!:'"/]| # leading punctuation, or
117 118 ^ # beginning of line
118 119 )
119 120 (
120 121 (?:https?://)| # protocol spec, or
121 122 (?:www\.) # www.*
122 123 )
123 124 (
124 125 (\S+?) # url
125 126 (\/)? # slash
126 127 )
127 128 ([^\w\=\/;]*?) # post
128 129 (?=<|\s|$)
129 130 }x unless const_defined?(:AUTO_LINK_RE)
130 131
131 132 # Turns all urls into clickable links (code from Rails).
132 133 def inline_auto_link(text)
133 134 text.gsub!(AUTO_LINK_RE) do
134 135 all, leading, proto, url, post = $&, $1, $2, $3, $6
135 136 if leading =~ /<a\s/i || leading =~ /![<>=]?/
136 137 # don't replace URL's that are already linked
137 138 # and URL's prefixed with ! !> !< != (textile images)
138 139 all
139 140 else
140 141 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
141 142 end
142 143 end
143 144 end
144 145
145 146 # Turns all email addresses into clickable links (code from Rails).
146 147 def inline_auto_mailto(text)
147 148 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
148 149 text = $1
149 150 %{<a href="mailto:#{$1}" class="email">#{text}</a>}
150 151 end
151 152 end
152 153 end
153 154
154 155 public
155 156
156 157 def self.to_html(text, options = {}, &block)
157 158 TextileFormatter.new(text).to_html(&block)
158 159 end
159 160 end
160 161 end
@@ -1,106 +1,112
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
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 '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 35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 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 37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 38 }
39 39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 40 end
41 41
42 42 def test_auto_mailto
43 43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 44 textilizable('test@foo.bar')
45 45 end
46 46
47 def test_textile_tags
47 def test_inline_images
48 48 to_test = {
49 # inline images
50 49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
51 50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
52 # textile links
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="" />',
53 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
56
57 def test_textile_external_links
58 to_test = {
53 59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
54 60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
55 61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
56 62 }
57 63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
58 64 end
59 65
60 66 def test_redmine_links
61 67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
62 68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
63 69 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
64 70 :class => 'changeset', :title => 'My very first commit')
65 71
66 72 to_test = {
67 73 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
68 74 'r1' => changeset_link
69 75 }
70 76 @project = Project.find(1)
71 77 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
72 78 end
73 79
74 80 def test_macro_hello_world
75 81 text = "{{hello_world}}"
76 82 assert textilizable(text).match(/Hello world!/)
77 83 end
78 84
79 85 def test_date_format_default
80 86 today = Date.today
81 87 Setting.date_format = ''
82 88 assert_equal l_date(today), format_date(today)
83 89 end
84 90
85 91 def test_date_format
86 92 today = Date.today
87 93 Setting.date_format = '%d %m %Y'
88 94 assert_equal today.strftime('%d %m %Y'), format_date(today)
89 95 end
90 96
91 97 def test_time_format_default
92 98 now = Time.now
93 99 Setting.date_format = ''
94 100 Setting.time_format = ''
95 101 assert_equal l_datetime(now), format_time(now)
96 102 assert_equal l_time(now), format_time(now, false)
97 103 end
98 104
99 105 def test_time_format
100 106 now = Time.now
101 107 Setting.date_format = '%d %m %Y'
102 108 Setting.time_format = '%H %M'
103 109 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
104 110 assert_equal now.strftime('%H %M'), format_time(now, false)
105 111 end
106 112 end
General Comments 0
You need to be logged in to leave comments. Login now