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