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