##// END OF EJS Templates
Redmine links can be used to link to documents, versions and attachments....
Jean-Philippe Lang -
r1050:702b521b453a
parent child
Show More
@@ -1,395 +1,443
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 module ApplicationHelper
18 module ApplicationHelper
19 include Redmine::WikiFormatting::Macros::Definitions
19 include Redmine::WikiFormatting::Macros::Definitions
20
20
21 def current_role
21 def current_role
22 @current_role ||= User.current.role_for_project(@project)
22 @current_role ||= User.current.role_for_project(@project)
23 end
23 end
24
24
25 # Return true if user is authorized for controller/action, otherwise false
25 # Return true if user is authorized for controller/action, otherwise false
26 def authorize_for(controller, action)
26 def authorize_for(controller, action)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
27 User.current.allowed_to?({:controller => controller, :action => action}, @project)
28 end
28 end
29
29
30 # Display a link if user is authorized
30 # Display a link if user is authorized
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
31 def link_to_if_authorized(name, options = {}, html_options = nil, *parameters_for_method_reference)
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
32 link_to(name, options, html_options, *parameters_for_method_reference) if authorize_for(options[:controller] || params[:controller], options[:action])
33 end
33 end
34
34
35 def link_to_signin
35 def link_to_signin
36 link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => 'signin'
36 link_to l(:label_login), { :controller => 'account', :action => 'login' }, :class => 'signin'
37 end
37 end
38
38
39 def link_to_signout
39 def link_to_signout
40 link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => 'logout'
40 link_to l(:label_logout), { :controller => 'account', :action => 'logout' }, :class => 'logout'
41 end
41 end
42
42
43 # Display a link to user's account page
43 # Display a link to user's account page
44 def link_to_user(user)
44 def link_to_user(user)
45 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
45 user ? link_to(user, :controller => 'account', :action => 'show', :id => user) : 'Anonymous'
46 end
46 end
47
47
48 def link_to_issue(issue)
48 def link_to_issue(issue)
49 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
49 link_to "#{issue.tracker.name} ##{issue.id}", :controller => "issues", :action => "show", :id => issue
50 end
50 end
51
51
52 def toggle_link(name, id, options={})
52 def toggle_link(name, id, options={})
53 onclick = "Element.toggle('#{id}'); "
53 onclick = "Element.toggle('#{id}'); "
54 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
54 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
55 onclick << "return false;"
55 onclick << "return false;"
56 link_to(name, "#", :onclick => onclick)
56 link_to(name, "#", :onclick => onclick)
57 end
57 end
58
58
59 def show_and_goto_link(name, id, options={})
59 def show_and_goto_link(name, id, options={})
60 onclick = "Element.show('#{id}'); "
60 onclick = "Element.show('#{id}'); "
61 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
61 onclick << (options[:focus] ? "Form.Element.focus('#{options[:focus]}'); " : "this.blur(); ")
62 onclick << "location.href='##{id}-anchor'; "
62 onclick << "location.href='##{id}-anchor'; "
63 onclick << "return false;"
63 onclick << "return false;"
64 link_to(name, "#", options.merge(:onclick => onclick))
64 link_to(name, "#", options.merge(:onclick => onclick))
65 end
65 end
66
66
67 def image_to_function(name, function, html_options = {})
67 def image_to_function(name, function, html_options = {})
68 html_options.symbolize_keys!
68 html_options.symbolize_keys!
69 tag(:input, html_options.merge({
69 tag(:input, html_options.merge({
70 :type => "image", :src => image_path(name),
70 :type => "image", :src => image_path(name),
71 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
71 :onclick => (html_options[:onclick] ? "#{html_options[:onclick]}; " : "") + "#{function};"
72 }))
72 }))
73 end
73 end
74
74
75 def prompt_to_remote(name, text, param, url, html_options = {})
75 def prompt_to_remote(name, text, param, url, html_options = {})
76 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
76 html_options[:onclick] = "promptToRemote('#{text}', '#{param}', '#{url_for(url)}'); return false;"
77 link_to name, {}, html_options
77 link_to name, {}, html_options
78 end
78 end
79
79
80 def format_date(date)
80 def format_date(date)
81 return nil unless date
81 return nil unless date
82 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
82 # "Setting.date_format.size < 2" is a temporary fix (content of date_format setting changed)
83 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
83 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
84 date.strftime(@date_format)
84 date.strftime(@date_format)
85 end
85 end
86
86
87 def format_time(time, include_date = true)
87 def format_time(time, include_date = true)
88 return nil unless time
88 return nil unless time
89 time = time.to_time if time.is_a?(String)
89 time = time.to_time if time.is_a?(String)
90 zone = User.current.time_zone
90 zone = User.current.time_zone
91 if time.utc?
91 if time.utc?
92 local = zone ? zone.adjust(time) : time.getlocal
92 local = zone ? zone.adjust(time) : time.getlocal
93 else
93 else
94 local = zone ? zone.adjust(time.getutc) : time
94 local = zone ? zone.adjust(time.getutc) : time
95 end
95 end
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
96 @date_format ||= (Setting.date_format.blank? || Setting.date_format.size < 2 ? l(:general_fmt_date) : Setting.date_format)
97 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
97 @time_format ||= (Setting.time_format.blank? ? l(:general_fmt_time) : Setting.time_format)
98 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
98 include_date ? local.strftime("#{@date_format} #{@time_format}") : local.strftime(@time_format)
99 end
99 end
100
100
101 def authoring(created, author)
101 def authoring(created, author)
102 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
102 time_tag = content_tag('acronym', distance_of_time_in_words(Time.now, created), :title => format_time(created))
103 l(:label_added_time_by, author || 'Anonymous', time_tag)
103 l(:label_added_time_by, author || 'Anonymous', time_tag)
104 end
104 end
105
105
106 def day_name(day)
106 def day_name(day)
107 l(:general_day_names).split(',')[day-1]
107 l(:general_day_names).split(',')[day-1]
108 end
108 end
109
109
110 def month_name(month)
110 def month_name(month)
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
111 l(:actionview_datehelper_select_month_names).split(',')[month-1]
112 end
112 end
113
113
114 def pagination_links_full(paginator, count=nil, options={})
114 def pagination_links_full(paginator, count=nil, options={})
115 page_param = options.delete(:page_param) || :page
115 page_param = options.delete(:page_param) || :page
116 url_param = params.dup
116 url_param = params.dup
117
117
118 html = ''
118 html = ''
119 html << link_to_remote(('&#171; ' + l(:label_previous)),
119 html << link_to_remote(('&#171; ' + l(:label_previous)),
120 {:update => "content", :url => url_param.merge(page_param => paginator.current.previous)},
120 {:update => "content", :url => url_param.merge(page_param => paginator.current.previous)},
121 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
121 {:href => url_for(:params => url_param.merge(page_param => paginator.current.previous))}) + ' ' if paginator.current.previous
122
122
123 html << (pagination_links_each(paginator, options) do |n|
123 html << (pagination_links_each(paginator, options) do |n|
124 link_to_remote(n.to_s,
124 link_to_remote(n.to_s,
125 {:url => {:params => url_param.merge(page_param => n)}, :update => 'content'},
125 {:url => {:params => url_param.merge(page_param => n)}, :update => 'content'},
126 {:href => url_for(:params => url_param.merge(page_param => n))})
126 {:href => url_for(:params => url_param.merge(page_param => n))})
127 end || '')
127 end || '')
128
128
129 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
129 html << ' ' + link_to_remote((l(:label_next) + ' &#187;'),
130 {:update => "content", :url => url_param.merge(page_param => paginator.current.next)},
130 {:update => "content", :url => url_param.merge(page_param => paginator.current.next)},
131 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
131 {:href => url_for(:params => url_param.merge(page_param => paginator.current.next))}) if paginator.current.next
132
132
133 unless count.nil?
133 unless count.nil?
134 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
134 html << [" (#{paginator.current.first_item}-#{paginator.current.last_item}/#{count})", per_page_links(paginator.items_per_page)].compact.join(' | ')
135 end
135 end
136
136
137 html
137 html
138 end
138 end
139
139
140 def per_page_links(selected=nil)
140 def per_page_links(selected=nil)
141 links = Setting.per_page_options_array.collect do |n|
141 links = Setting.per_page_options_array.collect do |n|
142 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
142 n == selected ? n : link_to_remote(n, {:update => "content", :url => params.dup.merge(:per_page => n)},
143 {:href => url_for(params.dup.merge(:per_page => n))})
143 {:href => url_for(params.dup.merge(:per_page => n))})
144 end
144 end
145 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
145 links.size > 1 ? l(:label_display_per_page, links.join(', ')) : nil
146 end
146 end
147
147
148 def html_title(*args)
148 def html_title(*args)
149 if args.empty?
149 if args.empty?
150 title = []
150 title = []
151 title << @project.name if @project
151 title << @project.name if @project
152 title += @html_title if @html_title
152 title += @html_title if @html_title
153 title << Setting.app_title
153 title << Setting.app_title
154 title.compact.join(' - ')
154 title.compact.join(' - ')
155 else
155 else
156 @html_title ||= []
156 @html_title ||= []
157 @html_title += args
157 @html_title += args
158 end
158 end
159 end
159 end
160
160
161 ACCESSKEYS = {:edit => 'e',
161 ACCESSKEYS = {:edit => 'e',
162 :preview => 'r',
162 :preview => 'r',
163 :quick_search => 'f',
163 :quick_search => 'f',
164 :search => '4',
164 :search => '4',
165 }.freeze unless const_defined?(:ACCESSKEYS)
165 }.freeze unless const_defined?(:ACCESSKEYS)
166
166
167 def accesskey(s)
167 def accesskey(s)
168 ACCESSKEYS[s]
168 ACCESSKEYS[s]
169 end
169 end
170
170
171 # Formats text according to system settings.
171 # Formats text according to system settings.
172 # 2 ways to call this method:
172 # 2 ways to call this method:
173 # * with a String: textilizable(text, options)
173 # * with a String: textilizable(text, options)
174 # * with an object and one of its attribute: textilizable(issue, :description, options)
174 # * with an object and one of its attribute: textilizable(issue, :description, options)
175 def textilizable(*args)
175 def textilizable(*args)
176 options = args.last.is_a?(Hash) ? args.pop : {}
176 options = args.last.is_a?(Hash) ? args.pop : {}
177 case args.size
177 case args.size
178 when 1
178 when 1
179 obj = nil
179 obj = nil
180 text = args.shift || ''
180 text = args.shift || ''
181 when 2
181 when 2
182 obj = args.shift
182 obj = args.shift
183 text = obj.send(args.shift)
183 text = obj.send(args.shift)
184 else
184 else
185 raise ArgumentError, 'invalid arguments to textilizable'
185 raise ArgumentError, 'invalid arguments to textilizable'
186 end
186 end
187
187
188 # when using an image link, try to use an attachment, if possible
188 # when using an image link, try to use an attachment, if possible
189 attachments = options[:attachments]
189 attachments = options[:attachments]
190 if attachments
190 if attachments
191 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
191 text = text.gsub(/!((\<|\=|\>)?(\([^\)]+\))?(\[[^\]]+\])?(\{[^\}]+\})?)(\S+\.(gif|jpg|jpeg|png))!/) do |m|
192 style = $1
192 style = $1
193 filename = $6
193 filename = $6
194 rf = Regexp.new(filename, Regexp::IGNORECASE)
194 rf = Regexp.new(filename, Regexp::IGNORECASE)
195 # search for the picture in attachments
195 # search for the picture in attachments
196 if found = attachments.detect { |att| att.filename =~ rf }
196 if found = attachments.detect { |att| att.filename =~ rf }
197 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
197 image_url = url_for :controller => 'attachments', :action => 'download', :id => found.id
198 "!#{style}#{image_url}!"
198 "!#{style}#{image_url}!"
199 else
199 else
200 "!#{style}#{filename}!"
200 "!#{style}#{filename}!"
201 end
201 end
202 end
202 end
203 end
203 end
204
204
205 text = (Setting.text_formatting == 'textile') ?
205 text = (Setting.text_formatting == 'textile') ?
206 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
206 Redmine::WikiFormatting.to_html(text) { |macro, args| exec_macro(macro, obj, args) } :
207 simple_format(auto_link(h(text)))
207 simple_format(auto_link(h(text)))
208
208
209 # different methods for formatting wiki links
209 # different methods for formatting wiki links
210 case options[:wiki_links]
210 case options[:wiki_links]
211 when :local
211 when :local
212 # used for local links to html files
212 # used for local links to html files
213 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
213 format_wiki_link = Proc.new {|project, title| "#{title}.html" }
214 when :anchor
214 when :anchor
215 # used for single-file wiki export
215 # used for single-file wiki export
216 format_wiki_link = Proc.new {|project, title| "##{title}" }
216 format_wiki_link = Proc.new {|project, title| "##{title}" }
217 else
217 else
218 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
218 format_wiki_link = Proc.new {|project, title| url_for :controller => 'wiki', :action => 'index', :id => project, :page => title }
219 end
219 end
220
220
221 project = options[:project] || @project
221 project = options[:project] || @project
222
222
223 # turn wiki links into html links
223 # Wiki links
224 # example:
224 #
225 # Examples:
225 # [[mypage]]
226 # [[mypage]]
226 # [[mypage|mytext]]
227 # [[mypage|mytext]]
227 # wiki links can refer other project wikis, using project name or identifier:
228 # wiki links can refer other project wikis, using project name or identifier:
228 # [[project:]] -> wiki starting page
229 # [[project:]] -> wiki starting page
229 # [[project:|mytext]]
230 # [[project:|mytext]]
230 # [[project:mypage]]
231 # [[project:mypage]]
231 # [[project:mypage|mytext]]
232 # [[project:mypage|mytext]]
232 text = text.gsub(/\[\[([^\]\|]+)(\|([^\]\|]+))?\]\]/) do |m|
233 text = text.gsub(/(!)?(\[\[([^\]\|]+)(\|([^\]\|]+))?\]\])/) do |m|
233 link_project = project
234 link_project = project
234 page = $1
235 esc, all, page, title = $1, $2, $3, $5
235 title = $3
236 if esc.nil?
236 if page =~ /^([^\:]+)\:(.*)$/
237 if page =~ /^([^\:]+)\:(.*)$/
237 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
238 link_project = Project.find_by_name($1) || Project.find_by_identifier($1)
238 page = title || $2
239 page = $2
239 title = $1 if page.blank?
240 title ||= $1 if page.blank?
240 end
241 end
241
242
242 if link_project && link_project.wiki
243 if link_project && link_project.wiki
243 # check if page exists
244 # check if page exists
244 wiki_page = link_project.wiki.find_page(page)
245 wiki_page = link_project.wiki.find_page(page)
245 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
246 link_to((title || page), format_wiki_link.call(link_project, Wiki.titleize(page)),
246 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
247 :class => ('wiki-page' + (wiki_page ? '' : ' new')))
248 else
249 # project or wiki doesn't exist
250 title || page
251 end
247 else
252 else
248 # project or wiki doesn't exist
253 all
249 title || page
250 end
254 end
251 end
255 end
252
256
253 # turn issue and revision ids into links
257 # Redmine links
254 # example:
258 #
255 # #52 -> <a href="/issues/show/52">#52</a>
259 # Examples:
256 # r52 -> <a href="/repositories/revision/6?rev=52">r52</a> (project.id is 6)
260 # Issues:
257 text = text.gsub(%r{([\s\(,-^])(#|r)(\d+)(?=[[:punct:]]|\s|<|$)}) do |m|
261 # #52 -> Link to issue #52
258 leading, otype, oid = $1, $2, $3
262 # Changesets:
263 # r52 -> Link to revision 52
264 # Documents:
265 # document#17 -> Link to document with id 17
266 # document:Greetings -> Link to the document with title "Greetings"
267 # document:"Some document" -> Link to the document with title "Some document"
268 # Versions:
269 # version#3 -> Link to version with id 3
270 # version:1.0.0 -> Link to version named "1.0.0"
271 # version:"1.0 beta 2" -> Link to version named "1.0 beta 2"
272 # Attachments:
273 # attachment:file.zip -> Link to the attachment of the current object named file.zip
274 text = text.gsub(%r{([\s\(,-^])(!)?(attachment|document|version)?((#|r)(\d+)|(:)([^"][^\s<>]+|"[^"]+"))(?=[[:punct:]]|\s|<|$)}) do |m|
275 leading, esc, prefix, sep, oid = $1, $2, $3, $5 || $7, $6 || $8
259 link = nil
276 link = nil
260 if otype == 'r'
277 if esc.nil?
261 if project && (changeset = project.changesets.find_by_revision(oid))
278 if prefix.nil? && sep == 'r'
262 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
279 if project && (changeset = project.changesets.find_by_revision(oid))
263 :title => truncate(changeset.comments, 100))
280 link = link_to("r#{oid}", {:controller => 'repositories', :action => 'revision', :id => project.id, :rev => oid}, :class => 'changeset',
264 end
281 :title => truncate(changeset.comments, 100))
265 else
282 end
266 if issue = Issue.find_by_id(oid.to_i, :include => [:project, :status], :conditions => Project.visible_by(User.current))
283 elsif sep == '#'
267 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
284 oid = oid.to_i
268 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
285 case prefix
269 link = content_tag('del', link) if issue.closed?
286 when nil
287 if issue = Issue.find_by_id(oid, :include => [:project, :status], :conditions => Project.visible_by(User.current))
288 link = link_to("##{oid}", {:controller => 'issues', :action => 'show', :id => oid}, :class => 'issue',
289 :title => "#{truncate(issue.subject, 100)} (#{issue.status.name})")
290 link = content_tag('del', link) if issue.closed?
291 end
292 when 'document'
293 if document = Document.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
294 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
295 end
296 when 'version'
297 if version = Version.find_by_id(oid, :include => [:project], :conditions => Project.visible_by(User.current))
298 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
299 end
300 end
301 elsif sep == ':'
302 # removes the double quotes if any
303 name = oid.gsub(%r{^"(.*)"$}, "\\1")
304 case prefix
305 when 'document'
306 if project && document = project.documents.find_by_title(name)
307 link = link_to h(document.title), {:controller => 'documents', :action => 'show', :id => document}, :class => 'document'
308 end
309 when 'version'
310 if project && version = project.versions.find_by_name(name)
311 link = link_to h(version.name), {:controller => 'versions', :action => 'show', :id => version}, :class => 'version'
312 end
313 when 'attachment'
314 if attachments && attachment = attachments.detect {|a| a.filename == name }
315 link = link_to h(attachment.filename), {:controller => 'attachments', :action => 'download', :id => attachment}, :class => 'attachment'
316 end
317 end
270 end
318 end
271 end
319 end
272 leading + (link || "#{otype}#{oid}")
320 leading + (link || "#{prefix}#{sep}#{oid}")
273 end
321 end
274
322
275 text
323 text
276 end
324 end
277
325
278 # Same as Rails' simple_format helper without using paragraphs
326 # Same as Rails' simple_format helper without using paragraphs
279 def simple_format_without_paragraph(text)
327 def simple_format_without_paragraph(text)
280 text.to_s.
328 text.to_s.
281 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
329 gsub(/\r\n?/, "\n"). # \r\n and \r -> \n
282 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
330 gsub(/\n\n+/, "<br /><br />"). # 2+ newline -> 2 br
283 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
331 gsub(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
284 end
332 end
285
333
286 def error_messages_for(object_name, options = {})
334 def error_messages_for(object_name, options = {})
287 options = options.symbolize_keys
335 options = options.symbolize_keys
288 object = instance_variable_get("@#{object_name}")
336 object = instance_variable_get("@#{object_name}")
289 if object && !object.errors.empty?
337 if object && !object.errors.empty?
290 # build full_messages here with controller current language
338 # build full_messages here with controller current language
291 full_messages = []
339 full_messages = []
292 object.errors.each do |attr, msg|
340 object.errors.each do |attr, msg|
293 next if msg.nil?
341 next if msg.nil?
294 msg = msg.first if msg.is_a? Array
342 msg = msg.first if msg.is_a? Array
295 if attr == "base"
343 if attr == "base"
296 full_messages << l(msg)
344 full_messages << l(msg)
297 else
345 else
298 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
346 full_messages << "&#171; " + (l_has_string?("field_" + attr) ? l("field_" + attr) : object.class.human_attribute_name(attr)) + " &#187; " + l(msg) unless attr == "custom_values"
299 end
347 end
300 end
348 end
301 # retrieve custom values error messages
349 # retrieve custom values error messages
302 if object.errors[:custom_values]
350 if object.errors[:custom_values]
303 object.custom_values.each do |v|
351 object.custom_values.each do |v|
304 v.errors.each do |attr, msg|
352 v.errors.each do |attr, msg|
305 next if msg.nil?
353 next if msg.nil?
306 msg = msg.first if msg.is_a? Array
354 msg = msg.first if msg.is_a? Array
307 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
355 full_messages << "&#171; " + v.custom_field.name + " &#187; " + l(msg)
308 end
356 end
309 end
357 end
310 end
358 end
311 content_tag("div",
359 content_tag("div",
312 content_tag(
360 content_tag(
313 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
361 options[:header_tag] || "span", lwr(:gui_validation_error, full_messages.length) + ":"
314 ) +
362 ) +
315 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
363 content_tag("ul", full_messages.collect { |msg| content_tag("li", msg) }),
316 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
364 "id" => options[:id] || "errorExplanation", "class" => options[:class] || "errorExplanation"
317 )
365 )
318 else
366 else
319 ""
367 ""
320 end
368 end
321 end
369 end
322
370
323 def lang_options_for_select(blank=true)
371 def lang_options_for_select(blank=true)
324 (blank ? [["(auto)", ""]] : []) +
372 (blank ? [["(auto)", ""]] : []) +
325 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
373 GLoc.valid_languages.collect{|lang| [ ll(lang.to_s, :general_lang_name), lang.to_s]}.sort{|x,y| x.last <=> y.last }
326 end
374 end
327
375
328 def label_tag_for(name, option_tags = nil, options = {})
376 def label_tag_for(name, option_tags = nil, options = {})
329 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
377 label_text = l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) + (options.delete(:required) ? @template.content_tag("span", " *", :class => "required"): "")
330 content_tag("label", label_text)
378 content_tag("label", label_text)
331 end
379 end
332
380
333 def labelled_tabular_form_for(name, object, options, &proc)
381 def labelled_tabular_form_for(name, object, options, &proc)
334 options[:html] ||= {}
382 options[:html] ||= {}
335 options[:html].store :class, "tabular"
383 options[:html].store :class, "tabular"
336 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
384 form_for(name, object, options.merge({ :builder => TabularFormBuilder, :lang => current_language}), &proc)
337 end
385 end
338
386
339 def check_all_links(form_name)
387 def check_all_links(form_name)
340 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
388 link_to_function(l(:button_check_all), "checkAll('#{form_name}', true)") +
341 " | " +
389 " | " +
342 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
390 link_to_function(l(:button_uncheck_all), "checkAll('#{form_name}', false)")
343 end
391 end
344
392
345 def progress_bar(pcts, options={})
393 def progress_bar(pcts, options={})
346 pcts = [pcts, pcts] unless pcts.is_a?(Array)
394 pcts = [pcts, pcts] unless pcts.is_a?(Array)
347 pcts[1] = pcts[1] - pcts[0]
395 pcts[1] = pcts[1] - pcts[0]
348 pcts << (100 - pcts[1] - pcts[0])
396 pcts << (100 - pcts[1] - pcts[0])
349 width = options[:width] || '100px;'
397 width = options[:width] || '100px;'
350 legend = options[:legend] || ''
398 legend = options[:legend] || ''
351 content_tag('table',
399 content_tag('table',
352 content_tag('tr',
400 content_tag('tr',
353 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
401 (pcts[0] > 0 ? content_tag('td', '', :width => "#{pcts[0].floor}%;", :class => 'closed') : '') +
354 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
402 (pcts[1] > 0 ? content_tag('td', '', :width => "#{pcts[1].floor}%;", :class => 'done') : '') +
355 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
403 (pcts[2] > 0 ? content_tag('td', '', :width => "#{pcts[2].floor}%;", :class => 'todo') : '')
356 ), :class => 'progress', :style => "width: #{width};") +
404 ), :class => 'progress', :style => "width: #{width};") +
357 content_tag('p', legend, :class => 'pourcent')
405 content_tag('p', legend, :class => 'pourcent')
358 end
406 end
359
407
360 def context_menu_link(name, url, options={})
408 def context_menu_link(name, url, options={})
361 options[:class] ||= ''
409 options[:class] ||= ''
362 if options.delete(:selected)
410 if options.delete(:selected)
363 options[:class] << ' icon-checked disabled'
411 options[:class] << ' icon-checked disabled'
364 options[:disabled] = true
412 options[:disabled] = true
365 end
413 end
366 if options.delete(:disabled)
414 if options.delete(:disabled)
367 options.delete(:method)
415 options.delete(:method)
368 options.delete(:confirm)
416 options.delete(:confirm)
369 options.delete(:onclick)
417 options.delete(:onclick)
370 options[:class] << ' disabled'
418 options[:class] << ' disabled'
371 url = '#'
419 url = '#'
372 end
420 end
373 link_to name, url, options
421 link_to name, url, options
374 end
422 end
375
423
376 def calendar_for(field_id)
424 def calendar_for(field_id)
377 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
425 image_tag("calendar.png", {:id => "#{field_id}_trigger",:class => "calendar-trigger"}) +
378 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
426 javascript_tag("Calendar.setup({inputField : '#{field_id}', ifFormat : '%Y-%m-%d', button : '#{field_id}_trigger' });")
379 end
427 end
380
428
381 def wikitoolbar_for(field_id)
429 def wikitoolbar_for(field_id)
382 return '' unless Setting.text_formatting == 'textile'
430 return '' unless Setting.text_formatting == 'textile'
383 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
431 javascript_include_tag('jstoolbar') + javascript_tag("var toolbar = new jsToolBar($('#{field_id}')); toolbar.draw();")
384 end
432 end
385
433
386 def content_for(name, content = nil, &block)
434 def content_for(name, content = nil, &block)
387 @has_content ||= {}
435 @has_content ||= {}
388 @has_content[name] = true
436 @has_content[name] = true
389 super(name, content, &block)
437 super(name, content, &block)
390 end
438 end
391
439
392 def has_content?(name)
440 def has_content?(name)
393 (@has_content && @has_content[name]) || false
441 (@has_content && @has_content[name]) || false
394 end
442 end
395 end
443 end
@@ -1,1132 +1,1132
1 # vim:ts=4:sw=4:
1 # vim:ts=4:sw=4:
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
2 # = RedCloth - Textile and Markdown Hybrid for Ruby
3 #
3 #
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
7 # License:: BSD
7 # License:: BSD
8 #
8 #
9 # (see http://hobix.com/textile/ for a Textile Reference.)
9 # (see http://hobix.com/textile/ for a Textile Reference.)
10 #
10 #
11 # Based on (and also inspired by) both:
11 # Based on (and also inspired by) both:
12 #
12 #
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14 # Textism for PHP: http://www.textism.com/tools/textile/
14 # Textism for PHP: http://www.textism.com/tools/textile/
15 #
15 #
16 #
16 #
17
17
18 # = RedCloth
18 # = RedCloth
19 #
19 #
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
20 # RedCloth is a Ruby library for converting Textile and/or Markdown
21 # into HTML. You can use either format, intermingled or separately.
21 # into HTML. You can use either format, intermingled or separately.
22 # You can also extend RedCloth to honor your own custom text stylings.
22 # You can also extend RedCloth to honor your own custom text stylings.
23 #
23 #
24 # RedCloth users are encouraged to use Textile if they are generating
24 # RedCloth users are encouraged to use Textile if they are generating
25 # HTML and to use Markdown if others will be viewing the plain text.
25 # HTML and to use Markdown if others will be viewing the plain text.
26 #
26 #
27 # == What is Textile?
27 # == What is Textile?
28 #
28 #
29 # Textile is a simple formatting style for text
29 # Textile is a simple formatting style for text
30 # documents, loosely based on some HTML conventions.
30 # documents, loosely based on some HTML conventions.
31 #
31 #
32 # == Sample Textile Text
32 # == Sample Textile Text
33 #
33 #
34 # h2. This is a title
34 # h2. This is a title
35 #
35 #
36 # h3. This is a subhead
36 # h3. This is a subhead
37 #
37 #
38 # This is a bit of paragraph.
38 # This is a bit of paragraph.
39 #
39 #
40 # bq. This is a blockquote.
40 # bq. This is a blockquote.
41 #
41 #
42 # = Writing Textile
42 # = Writing Textile
43 #
43 #
44 # A Textile document consists of paragraphs. Paragraphs
44 # A Textile document consists of paragraphs. Paragraphs
45 # can be specially formatted by adding a small instruction
45 # can be specially formatted by adding a small instruction
46 # to the beginning of the paragraph.
46 # to the beginning of the paragraph.
47 #
47 #
48 # h[n]. Header of size [n].
48 # h[n]. Header of size [n].
49 # bq. Blockquote.
49 # bq. Blockquote.
50 # # Numeric list.
50 # # Numeric list.
51 # * Bulleted list.
51 # * Bulleted list.
52 #
52 #
53 # == Quick Phrase Modifiers
53 # == Quick Phrase Modifiers
54 #
54 #
55 # Quick phrase modifiers are also included, to allow formatting
55 # Quick phrase modifiers are also included, to allow formatting
56 # of small portions of text within a paragraph.
56 # of small portions of text within a paragraph.
57 #
57 #
58 # \_emphasis\_
58 # \_emphasis\_
59 # \_\_italicized\_\_
59 # \_\_italicized\_\_
60 # \*strong\*
60 # \*strong\*
61 # \*\*bold\*\*
61 # \*\*bold\*\*
62 # ??citation??
62 # ??citation??
63 # -deleted text-
63 # -deleted text-
64 # +inserted text+
64 # +inserted text+
65 # ^superscript^
65 # ^superscript^
66 # ~subscript~
66 # ~subscript~
67 # @code@
67 # @code@
68 # %(classname)span%
68 # %(classname)span%
69 #
69 #
70 # ==notextile== (leave text alone)
70 # ==notextile== (leave text alone)
71 #
71 #
72 # == Links
72 # == Links
73 #
73 #
74 # To make a hypertext link, put the link text in "quotation
74 # To make a hypertext link, put the link text in "quotation
75 # marks" followed immediately by a colon and the URL of the link.
75 # marks" followed immediately by a colon and the URL of the link.
76 #
76 #
77 # Optional: text in (parentheses) following the link text,
77 # Optional: text in (parentheses) following the link text,
78 # but before the closing quotation mark, will become a Title
78 # but before the closing quotation mark, will become a Title
79 # attribute for the link, visible as a tool tip when a cursor is above it.
79 # attribute for the link, visible as a tool tip when a cursor is above it.
80 #
80 #
81 # Example:
81 # Example:
82 #
82 #
83 # "This is a link (This is a title) ":http://www.textism.com
83 # "This is a link (This is a title) ":http://www.textism.com
84 #
84 #
85 # Will become:
85 # Will become:
86 #
86 #
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
88 #
88 #
89 # == Images
89 # == Images
90 #
90 #
91 # To insert an image, put the URL for the image inside exclamation marks.
91 # To insert an image, put the URL for the image inside exclamation marks.
92 #
92 #
93 # Optional: text that immediately follows the URL in (parentheses) will
93 # Optional: text that immediately follows the URL in (parentheses) will
94 # be used as the Alt text for the image. Images on the web should always
94 # be used as the Alt text for the image. Images on the web should always
95 # have descriptive Alt text for the benefit of readers using non-graphical
95 # have descriptive Alt text for the benefit of readers using non-graphical
96 # browsers.
96 # browsers.
97 #
97 #
98 # Optional: place a colon followed by a URL immediately after the
98 # Optional: place a colon followed by a URL immediately after the
99 # closing ! to make the image into a link.
99 # closing ! to make the image into a link.
100 #
100 #
101 # Example:
101 # Example:
102 #
102 #
103 # !http://www.textism.com/common/textist.gif(Textist)!
103 # !http://www.textism.com/common/textist.gif(Textist)!
104 #
104 #
105 # Will become:
105 # Will become:
106 #
106 #
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
108 #
108 #
109 # With a link:
109 # With a link:
110 #
110 #
111 # !/common/textist.gif(Textist)!:http://textism.com
111 # !/common/textist.gif(Textist)!:http://textism.com
112 #
112 #
113 # Will become:
113 # Will become:
114 #
114 #
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
116 #
116 #
117 # == Defining Acronyms
117 # == Defining Acronyms
118 #
118 #
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
119 # HTML allows authors to define acronyms via the tag. The definition appears as a
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
121 # this should be used at least once for each acronym in documents where they appear.
121 # this should be used at least once for each acronym in documents where they appear.
122 #
122 #
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
123 # To quickly define an acronym in Textile, place the full text in (parentheses)
124 # immediately following the acronym.
124 # immediately following the acronym.
125 #
125 #
126 # Example:
126 # Example:
127 #
127 #
128 # ACLU(American Civil Liberties Union)
128 # ACLU(American Civil Liberties Union)
129 #
129 #
130 # Will become:
130 # Will become:
131 #
131 #
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
133 #
133 #
134 # == Adding Tables
134 # == Adding Tables
135 #
135 #
136 # In Textile, simple tables can be added by seperating each column by
136 # In Textile, simple tables can be added by seperating each column by
137 # a pipe.
137 # a pipe.
138 #
138 #
139 # |a|simple|table|row|
139 # |a|simple|table|row|
140 # |And|Another|table|row|
140 # |And|Another|table|row|
141 #
141 #
142 # Attributes are defined by style definitions in parentheses.
142 # Attributes are defined by style definitions in parentheses.
143 #
143 #
144 # table(border:1px solid black).
144 # table(border:1px solid black).
145 # (background:#ddd;color:red). |{}| | | |
145 # (background:#ddd;color:red). |{}| | | |
146 #
146 #
147 # == Using RedCloth
147 # == Using RedCloth
148 #
148 #
149 # RedCloth is simply an extension of the String class, which can handle
149 # RedCloth is simply an extension of the String class, which can handle
150 # Textile formatting. Use it like a String and output HTML with its
150 # Textile formatting. Use it like a String and output HTML with its
151 # RedCloth#to_html method.
151 # RedCloth#to_html method.
152 #
152 #
153 # doc = RedCloth.new "
153 # doc = RedCloth.new "
154 #
154 #
155 # h2. Test document
155 # h2. Test document
156 #
156 #
157 # Just a simple test."
157 # Just a simple test."
158 #
158 #
159 # puts doc.to_html
159 # puts doc.to_html
160 #
160 #
161 # By default, RedCloth uses both Textile and Markdown formatting, with
161 # By default, RedCloth uses both Textile and Markdown formatting, with
162 # Textile formatting taking precedence. If you want to turn off Markdown
162 # Textile formatting taking precedence. If you want to turn off Markdown
163 # formatting, to boost speed and limit the processor:
163 # formatting, to boost speed and limit the processor:
164 #
164 #
165 # class RedCloth::Textile.new( str )
165 # class RedCloth::Textile.new( str )
166
166
167 class RedCloth < String
167 class RedCloth < String
168
168
169 VERSION = '3.0.4'
169 VERSION = '3.0.4'
170 DEFAULT_RULES = [:textile, :markdown]
170 DEFAULT_RULES = [:textile, :markdown]
171
171
172 #
172 #
173 # Two accessor for setting security restrictions.
173 # Two accessor for setting security restrictions.
174 #
174 #
175 # This is a nice thing if you're using RedCloth for
175 # This is a nice thing if you're using RedCloth for
176 # formatting in public places (e.g. Wikis) where you
176 # formatting in public places (e.g. Wikis) where you
177 # don't want users to abuse HTML for bad things.
177 # don't want users to abuse HTML for bad things.
178 #
178 #
179 # If +:filter_html+ is set, HTML which wasn't
179 # If +:filter_html+ is set, HTML which wasn't
180 # created by the Textile processor will be escaped.
180 # created by the Textile processor will be escaped.
181 #
181 #
182 # If +:filter_styles+ is set, it will also disable
182 # If +:filter_styles+ is set, it will also disable
183 # the style markup specifier. ('{color: red}')
183 # the style markup specifier. ('{color: red}')
184 #
184 #
185 attr_accessor :filter_html, :filter_styles
185 attr_accessor :filter_html, :filter_styles
186
186
187 #
187 #
188 # Accessor for toggling hard breaks.
188 # Accessor for toggling hard breaks.
189 #
189 #
190 # If +:hard_breaks+ is set, single newlines will
190 # If +:hard_breaks+ is set, single newlines will
191 # be converted to HTML break tags. This is the
191 # be converted to HTML break tags. This is the
192 # default behavior for traditional RedCloth.
192 # default behavior for traditional RedCloth.
193 #
193 #
194 attr_accessor :hard_breaks
194 attr_accessor :hard_breaks
195
195
196 # Accessor for toggling lite mode.
196 # Accessor for toggling lite mode.
197 #
197 #
198 # In lite mode, block-level rules are ignored. This means
198 # In lite mode, block-level rules are ignored. This means
199 # that tables, paragraphs, lists, and such aren't available.
199 # that tables, paragraphs, lists, and such aren't available.
200 # Only the inline markup for bold, italics, entities and so on.
200 # Only the inline markup for bold, italics, entities and so on.
201 #
201 #
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
203 # r.to_html
203 # r.to_html
204 # #=> "And then? She <strong>fell</strong>!"
204 # #=> "And then? She <strong>fell</strong>!"
205 #
205 #
206 attr_accessor :lite_mode
206 attr_accessor :lite_mode
207
207
208 #
208 #
209 # Accessor for toggling span caps.
209 # Accessor for toggling span caps.
210 #
210 #
211 # Textile places `span' tags around capitalized
211 # Textile places `span' tags around capitalized
212 # words by default, but this wreaks havoc on Wikis.
212 # words by default, but this wreaks havoc on Wikis.
213 # If +:no_span_caps+ is set, this will be
213 # If +:no_span_caps+ is set, this will be
214 # suppressed.
214 # suppressed.
215 #
215 #
216 attr_accessor :no_span_caps
216 attr_accessor :no_span_caps
217
217
218 #
218 #
219 # Establishes the markup predence. Available rules include:
219 # Establishes the markup predence. Available rules include:
220 #
220 #
221 # == Textile Rules
221 # == Textile Rules
222 #
222 #
223 # The following textile rules can be set individually. Or add the complete
223 # The following textile rules can be set individually. Or add the complete
224 # set of rules with the single :textile rule, which supplies the rule set in
224 # set of rules with the single :textile rule, which supplies the rule set in
225 # the following precedence:
225 # the following precedence:
226 #
226 #
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
228 # block_textile_table:: Textile table block structures
228 # block_textile_table:: Textile table block structures
229 # block_textile_lists:: Textile list structures
229 # block_textile_lists:: Textile list structures
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
231 # inline_textile_image:: Textile inline images
231 # inline_textile_image:: Textile inline images
232 # inline_textile_link:: Textile inline links
232 # inline_textile_link:: Textile inline links
233 # inline_textile_span:: Textile inline spans
233 # inline_textile_span:: Textile inline spans
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
235 #
235 #
236 # == Markdown
236 # == Markdown
237 #
237 #
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
239 # block_markdown_setext:: Markdown setext headers
239 # block_markdown_setext:: Markdown setext headers
240 # block_markdown_atx:: Markdown atx headers
240 # block_markdown_atx:: Markdown atx headers
241 # block_markdown_rule:: Markdown horizontal rules
241 # block_markdown_rule:: Markdown horizontal rules
242 # block_markdown_bq:: Markdown blockquotes
242 # block_markdown_bq:: Markdown blockquotes
243 # block_markdown_lists:: Markdown lists
243 # block_markdown_lists:: Markdown lists
244 # inline_markdown_link:: Markdown links
244 # inline_markdown_link:: Markdown links
245 attr_accessor :rules
245 attr_accessor :rules
246
246
247 # Returns a new RedCloth object, based on _string_ and
247 # Returns a new RedCloth object, based on _string_ and
248 # enforcing all the included _restrictions_.
248 # enforcing all the included _restrictions_.
249 #
249 #
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
251 # r.to_html
251 # r.to_html
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
253 #
253 #
254 def initialize( string, restrictions = [] )
254 def initialize( string, restrictions = [] )
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
256 super( string )
256 super( string )
257 end
257 end
258
258
259 #
259 #
260 # Generates HTML from the Textile contents.
260 # Generates HTML from the Textile contents.
261 #
261 #
262 # r = RedCloth.new( "And then? She *fell*!" )
262 # r = RedCloth.new( "And then? She *fell*!" )
263 # r.to_html( true )
263 # r.to_html( true )
264 # #=>"And then? She <strong>fell</strong>!"
264 # #=>"And then? She <strong>fell</strong>!"
265 #
265 #
266 def to_html( *rules )
266 def to_html( *rules )
267 rules = DEFAULT_RULES if rules.empty?
267 rules = DEFAULT_RULES if rules.empty?
268 # make our working copy
268 # make our working copy
269 text = self.dup
269 text = self.dup
270
270
271 @urlrefs = {}
271 @urlrefs = {}
272 @shelf = []
272 @shelf = []
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
275 :inline_textile_code, :inline_textile_span, :glyphs_textile]
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 :block_markdown_bq, :block_markdown_lists,
277 :block_markdown_bq, :block_markdown_lists,
278 :inline_markdown_reflink, :inline_markdown_link]
278 :inline_markdown_reflink, :inline_markdown_link]
279 @rules = rules.collect do |rule|
279 @rules = rules.collect do |rule|
280 case rule
280 case rule
281 when :markdown
281 when :markdown
282 markdown_rules
282 markdown_rules
283 when :textile
283 when :textile
284 textile_rules
284 textile_rules
285 else
285 else
286 rule
286 rule
287 end
287 end
288 end.flatten
288 end.flatten
289
289
290 # standard clean up
290 # standard clean up
291 incoming_entities text
291 incoming_entities text
292 clean_white_space text
292 clean_white_space text
293
293
294 # start processor
294 # start processor
295 @pre_list = []
295 @pre_list = []
296 rip_offtags text
296 rip_offtags text
297 no_textile text
297 no_textile text
298 hard_break text
298 hard_break text
299 unless @lite_mode
299 unless @lite_mode
300 refs text
300 refs text
301 blocks text
301 blocks text
302 end
302 end
303 inline text
303 inline text
304 smooth_offtags text
304 smooth_offtags text
305
305
306 retrieve text
306 retrieve text
307
307
308 text.gsub!( /<\/?notextile>/, '' )
308 text.gsub!( /<\/?notextile>/, '' )
309 text.gsub!( /x%x%/, '&#38;' )
309 text.gsub!( /x%x%/, '&#38;' )
310 clean_html text if filter_html
310 clean_html text if filter_html
311 text.strip!
311 text.strip!
312 text
312 text
313
313
314 end
314 end
315
315
316 #######
316 #######
317 private
317 private
318 #######
318 #######
319 #
319 #
320 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
320 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
321 # (from PyTextile)
321 # (from PyTextile)
322 #
322 #
323 TEXTILE_TAGS =
323 TEXTILE_TAGS =
324
324
325 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
325 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
326 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
326 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
327 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
327 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
328 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
328 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
329 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
329 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
330
330
331 collect! do |a, b|
331 collect! do |a, b|
332 [a.chr, ( b.zero? and "" or "&#{ b };" )]
332 [a.chr, ( b.zero? and "" or "&#{ b };" )]
333 end
333 end
334
334
335 #
335 #
336 # Regular expressions to convert to HTML.
336 # Regular expressions to convert to HTML.
337 #
337 #
338 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
338 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
339 A_VLGN = /[\-^~]/
339 A_VLGN = /[\-^~]/
340 C_CLAS = '(?:\([^)]+\))'
340 C_CLAS = '(?:\([^)]+\))'
341 C_LNGE = '(?:\[[^\]]+\])'
341 C_LNGE = '(?:\[[^\]]+\])'
342 C_STYL = '(?:\{[^}]+\})'
342 C_STYL = '(?:\{[^}]+\})'
343 S_CSPN = '(?:\\\\\d+)'
343 S_CSPN = '(?:\\\\\d+)'
344 S_RSPN = '(?:/\d+)'
344 S_RSPN = '(?:/\d+)'
345 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
345 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
346 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
346 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
347 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
347 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
348 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
348 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
349 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
349 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
350 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
350 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
351 PUNCT_Q = Regexp::quote( '*-_+^~%' )
351 PUNCT_Q = Regexp::quote( '*-_+^~%' )
352 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
352 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
353
353
354 # Text markup tags, don't conflict with block tags
354 # Text markup tags, don't conflict with block tags
355 SIMPLE_HTML_TAGS = [
355 SIMPLE_HTML_TAGS = [
356 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
356 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
357 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
357 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
358 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
358 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
359 ]
359 ]
360
360
361 QTAGS = [
361 QTAGS = [
362 ['**', 'b', :limit],
362 ['**', 'b', :limit],
363 ['*', 'strong', :limit],
363 ['*', 'strong', :limit],
364 ['??', 'cite', :limit],
364 ['??', 'cite', :limit],
365 ['-', 'del', :limit],
365 ['-', 'del', :limit],
366 ['__', 'i', :limit],
366 ['__', 'i', :limit],
367 ['_', 'em', :limit],
367 ['_', 'em', :limit],
368 ['%', 'span', :limit],
368 ['%', 'span', :limit],
369 ['+', 'ins', :limit],
369 ['+', 'ins', :limit],
370 ['^', 'sup', :limit],
370 ['^', 'sup', :limit],
371 ['~', 'sub', :limit]
371 ['~', 'sub', :limit]
372 ]
372 ]
373 QTAGS.collect! do |rc, ht, rtype|
373 QTAGS.collect! do |rc, ht, rtype|
374 rcq = Regexp::quote rc
374 rcq = Regexp::quote rc
375 re =
375 re =
376 case rtype
376 case rtype
377 when :limit
377 when :limit
378 /(\W)
378 /(\W)
379 (#{rcq})
379 (#{rcq})
380 (#{C})
380 (#{C})
381 (?::(\S+?))?
381 (?::(\S+?))?
382 (\S.*?\S|\S)
382 (\S.*?\S|\S)
383 #{rcq}
383 #{rcq}
384 (?=\W)/x
384 (?=\W)/x
385 else
385 else
386 /(#{rcq})
386 /(#{rcq})
387 (#{C})
387 (#{C})
388 (?::(\S+))?
388 (?::(\S+))?
389 (\S.*?\S|\S)
389 (\S.*?\S|\S)
390 #{rcq}/xm
390 #{rcq}/xm
391 end
391 end
392 [rc, ht, re, rtype]
392 [rc, ht, re, rtype]
393 end
393 end
394
394
395 # Elements to handle
395 # Elements to handle
396 GLYPHS = [
396 GLYPHS = [
397 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
397 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
398 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
398 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
399 [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
399 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
400 [ /\'/, '&#8216;' ], # single opening
400 # [ /\'/, '&#8216;' ], # single opening
401 [ /</, '&lt;' ], # less-than
401 [ /</, '&lt;' ], # less-than
402 [ />/, '&gt;' ], # greater-than
402 [ />/, '&gt;' ], # greater-than
403 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
403 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
404 [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
404 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
405 [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
405 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
406 [ /"/, '&#8220;' ], # double opening
406 # [ /"/, '&#8220;' ], # double opening
407 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
407 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
408 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
408 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
409 [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
409 [ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
410 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
410 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
411 [ /\s->\s/, ' &rarr; ' ], # right arrow
411 [ /\s->\s/, ' &rarr; ' ], # right arrow
412 [ /\s-\s/, ' &#8211; ' ], # en dash
412 [ /\s-\s/, ' &#8211; ' ], # en dash
413 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
413 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
414 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
414 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
415 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
415 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
416 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
416 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
417 ]
417 ]
418
418
419 H_ALGN_VALS = {
419 H_ALGN_VALS = {
420 '<' => 'left',
420 '<' => 'left',
421 '=' => 'center',
421 '=' => 'center',
422 '>' => 'right',
422 '>' => 'right',
423 '<>' => 'justify'
423 '<>' => 'justify'
424 }
424 }
425
425
426 V_ALGN_VALS = {
426 V_ALGN_VALS = {
427 '^' => 'top',
427 '^' => 'top',
428 '-' => 'middle',
428 '-' => 'middle',
429 '~' => 'bottom'
429 '~' => 'bottom'
430 }
430 }
431
431
432 #
432 #
433 # Flexible HTML escaping
433 # Flexible HTML escaping
434 #
434 #
435 def htmlesc( str, mode )
435 def htmlesc( str, mode )
436 str.gsub!( '&', '&amp;' )
436 str.gsub!( '&', '&amp;' )
437 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
437 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
438 str.gsub!( "'", '&#039;' ) if mode == :Quotes
438 str.gsub!( "'", '&#039;' ) if mode == :Quotes
439 str.gsub!( '<', '&lt;')
439 str.gsub!( '<', '&lt;')
440 str.gsub!( '>', '&gt;')
440 str.gsub!( '>', '&gt;')
441 end
441 end
442
442
443 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
443 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
444 def pgl( text )
444 def pgl( text )
445 GLYPHS.each do |re, resub, tog|
445 GLYPHS.each do |re, resub, tog|
446 next if tog and method( tog ).call
446 next if tog and method( tog ).call
447 text.gsub! re, resub
447 text.gsub! re, resub
448 end
448 end
449 end
449 end
450
450
451 # Parses Textile attribute lists and builds an HTML attribute string
451 # Parses Textile attribute lists and builds an HTML attribute string
452 def pba( text_in, element = "" )
452 def pba( text_in, element = "" )
453
453
454 return '' unless text_in
454 return '' unless text_in
455
455
456 style = []
456 style = []
457 text = text_in.dup
457 text = text_in.dup
458 if element == 'td'
458 if element == 'td'
459 colspan = $1 if text =~ /\\(\d+)/
459 colspan = $1 if text =~ /\\(\d+)/
460 rowspan = $1 if text =~ /\/(\d+)/
460 rowspan = $1 if text =~ /\/(\d+)/
461 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
461 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
462 end
462 end
463
463
464 style << "#{ $1 };" if not filter_styles and
464 style << "#{ $1 };" if not filter_styles and
465 text.sub!( /\{([^}]*)\}/, '' )
465 text.sub!( /\{([^}]*)\}/, '' )
466
466
467 lang = $1 if
467 lang = $1 if
468 text.sub!( /\[([^)]+?)\]/, '' )
468 text.sub!( /\[([^)]+?)\]/, '' )
469
469
470 cls = $1 if
470 cls = $1 if
471 text.sub!( /\(([^()]+?)\)/, '' )
471 text.sub!( /\(([^()]+?)\)/, '' )
472
472
473 style << "padding-left:#{ $1.length }em;" if
473 style << "padding-left:#{ $1.length }em;" if
474 text.sub!( /([(]+)/, '' )
474 text.sub!( /([(]+)/, '' )
475
475
476 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
476 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
477
477
478 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
478 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
479
479
480 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
480 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
481
481
482 atts = ''
482 atts = ''
483 atts << " style=\"#{ style.join }\"" unless style.empty?
483 atts << " style=\"#{ style.join }\"" unless style.empty?
484 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
484 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
485 atts << " lang=\"#{ lang }\"" if lang
485 atts << " lang=\"#{ lang }\"" if lang
486 atts << " id=\"#{ id }\"" if id
486 atts << " id=\"#{ id }\"" if id
487 atts << " colspan=\"#{ colspan }\"" if colspan
487 atts << " colspan=\"#{ colspan }\"" if colspan
488 atts << " rowspan=\"#{ rowspan }\"" if rowspan
488 atts << " rowspan=\"#{ rowspan }\"" if rowspan
489
489
490 atts
490 atts
491 end
491 end
492
492
493 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
493 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
494
494
495 # Parses a Textile table block, building HTML from the result.
495 # Parses a Textile table block, building HTML from the result.
496 def block_textile_table( text )
496 def block_textile_table( text )
497 text.gsub!( TABLE_RE ) do |matches|
497 text.gsub!( TABLE_RE ) do |matches|
498
498
499 tatts, fullrow = $~[1..2]
499 tatts, fullrow = $~[1..2]
500 tatts = pba( tatts, 'table' )
500 tatts = pba( tatts, 'table' )
501 tatts = shelve( tatts ) if tatts
501 tatts = shelve( tatts ) if tatts
502 rows = []
502 rows = []
503
503
504 fullrow.
504 fullrow.
505 split( /\|$/m ).
505 split( /\|$/m ).
506 delete_if { |x| x.empty? }.
506 delete_if { |x| x.empty? }.
507 each do |row|
507 each do |row|
508
508
509 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
509 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
510
510
511 cells = []
511 cells = []
512 row.split( '|' ).each do |cell|
512 row.split( '|' ).each do |cell|
513 ctyp = 'd'
513 ctyp = 'd'
514 ctyp = 'h' if cell =~ /^_/
514 ctyp = 'h' if cell =~ /^_/
515
515
516 catts = ''
516 catts = ''
517 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
517 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
518
518
519 unless cell.strip.empty?
519 unless cell.strip.empty?
520 catts = shelve( catts ) if catts
520 catts = shelve( catts ) if catts
521 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
521 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
522 end
522 end
523 end
523 end
524 ratts = shelve( ratts ) if ratts
524 ratts = shelve( ratts ) if ratts
525 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
525 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
526 end
526 end
527 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
527 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
528 end
528 end
529 end
529 end
530
530
531 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
531 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
532 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
532 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
533
533
534 # Parses Textile lists and generates HTML
534 # Parses Textile lists and generates HTML
535 def block_textile_lists( text )
535 def block_textile_lists( text )
536 text.gsub!( LISTS_RE ) do |match|
536 text.gsub!( LISTS_RE ) do |match|
537 lines = match.split( /\n/ )
537 lines = match.split( /\n/ )
538 last_line = -1
538 last_line = -1
539 depth = []
539 depth = []
540 lines.each_with_index do |line, line_id|
540 lines.each_with_index do |line, line_id|
541 if line =~ LISTS_CONTENT_RE
541 if line =~ LISTS_CONTENT_RE
542 tl,atts,content = $~[1..3]
542 tl,atts,content = $~[1..3]
543 if depth.last
543 if depth.last
544 if depth.last.length > tl.length
544 if depth.last.length > tl.length
545 (depth.length - 1).downto(0) do |i|
545 (depth.length - 1).downto(0) do |i|
546 break if depth[i].length == tl.length
546 break if depth[i].length == tl.length
547 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
547 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
548 depth.pop
548 depth.pop
549 end
549 end
550 end
550 end
551 if depth.last and depth.last.length == tl.length
551 if depth.last and depth.last.length == tl.length
552 lines[line_id - 1] << '</li>'
552 lines[line_id - 1] << '</li>'
553 end
553 end
554 end
554 end
555 unless depth.last == tl
555 unless depth.last == tl
556 depth << tl
556 depth << tl
557 atts = pba( atts )
557 atts = pba( atts )
558 atts = shelve( atts ) if atts
558 atts = shelve( atts ) if atts
559 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
559 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
560 else
560 else
561 lines[line_id] = "\t\t<li>#{ content }"
561 lines[line_id] = "\t\t<li>#{ content }"
562 end
562 end
563 last_line = line_id
563 last_line = line_id
564
564
565 else
565 else
566 last_line = line_id
566 last_line = line_id
567 end
567 end
568 if line_id - last_line > 1 or line_id == lines.length - 1
568 if line_id - last_line > 1 or line_id == lines.length - 1
569 depth.delete_if do |v|
569 depth.delete_if do |v|
570 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
570 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
571 end
571 end
572 end
572 end
573 end
573 end
574 lines.join( "\n" )
574 lines.join( "\n" )
575 end
575 end
576 end
576 end
577
577
578 CODE_RE = /(\W)
578 CODE_RE = /(\W)
579 @
579 @
580 (?:\|(\w+?)\|)?
580 (?:\|(\w+?)\|)?
581 (.+?)
581 (.+?)
582 @
582 @
583 (?=\W)/x
583 (?=\W)/x
584
584
585 def inline_textile_code( text )
585 def inline_textile_code( text )
586 text.gsub!( CODE_RE ) do |m|
586 text.gsub!( CODE_RE ) do |m|
587 before,lang,code,after = $~[1..4]
587 before,lang,code,after = $~[1..4]
588 lang = " lang=\"#{ lang }\"" if lang
588 lang = " lang=\"#{ lang }\"" if lang
589 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
589 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
590 end
590 end
591 end
591 end
592
592
593 def lT( text )
593 def lT( text )
594 text =~ /\#$/ ? 'o' : 'u'
594 text =~ /\#$/ ? 'o' : 'u'
595 end
595 end
596
596
597 def hard_break( text )
597 def hard_break( text )
598 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
598 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
599 end
599 end
600
600
601 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
601 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
602
602
603 def blocks( text, deep_code = false )
603 def blocks( text, deep_code = false )
604 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
604 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
605 plain = blk !~ /\A[#*> ]/
605 plain = blk !~ /\A[#*> ]/
606
606
607 # skip blocks that are complex HTML
607 # skip blocks that are complex HTML
608 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
608 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
609 blk
609 blk
610 else
610 else
611 # search for indentation levels
611 # search for indentation levels
612 blk.strip!
612 blk.strip!
613 if blk.empty?
613 if blk.empty?
614 blk
614 blk
615 else
615 else
616 code_blk = nil
616 code_blk = nil
617 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
617 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
618 flush_left iblk
618 flush_left iblk
619 blocks iblk, plain
619 blocks iblk, plain
620 iblk.gsub( /^(\S)/, "\t\\1" )
620 iblk.gsub( /^(\S)/, "\t\\1" )
621 if plain
621 if plain
622 code_blk = iblk; ""
622 code_blk = iblk; ""
623 else
623 else
624 iblk
624 iblk
625 end
625 end
626 end
626 end
627
627
628 block_applied = 0
628 block_applied = 0
629 @rules.each do |rule_name|
629 @rules.each do |rule_name|
630 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
630 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
631 end
631 end
632 if block_applied.zero?
632 if block_applied.zero?
633 if deep_code
633 if deep_code
634 blk = "\t<pre><code>#{ blk }</code></pre>"
634 blk = "\t<pre><code>#{ blk }</code></pre>"
635 else
635 else
636 blk = "\t<p>#{ blk }</p>"
636 blk = "\t<p>#{ blk }</p>"
637 end
637 end
638 end
638 end
639 # hard_break blk
639 # hard_break blk
640 blk + "\n#{ code_blk }"
640 blk + "\n#{ code_blk }"
641 end
641 end
642 end
642 end
643
643
644 end.join( "\n\n" ) )
644 end.join( "\n\n" ) )
645 end
645 end
646
646
647 def textile_bq( tag, atts, cite, content )
647 def textile_bq( tag, atts, cite, content )
648 cite, cite_title = check_refs( cite )
648 cite, cite_title = check_refs( cite )
649 cite = " cite=\"#{ cite }\"" if cite
649 cite = " cite=\"#{ cite }\"" if cite
650 atts = shelve( atts ) if atts
650 atts = shelve( atts ) if atts
651 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
651 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
652 end
652 end
653
653
654 def textile_p( tag, atts, cite, content )
654 def textile_p( tag, atts, cite, content )
655 atts = shelve( atts ) if atts
655 atts = shelve( atts ) if atts
656 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
656 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
657 end
657 end
658
658
659 alias textile_h1 textile_p
659 alias textile_h1 textile_p
660 alias textile_h2 textile_p
660 alias textile_h2 textile_p
661 alias textile_h3 textile_p
661 alias textile_h3 textile_p
662 alias textile_h4 textile_p
662 alias textile_h4 textile_p
663 alias textile_h5 textile_p
663 alias textile_h5 textile_p
664 alias textile_h6 textile_p
664 alias textile_h6 textile_p
665
665
666 def textile_fn_( tag, num, atts, cite, content )
666 def textile_fn_( tag, num, atts, cite, content )
667 atts << " id=\"fn#{ num }\""
667 atts << " id=\"fn#{ num }\""
668 content = "<sup>#{ num }</sup> #{ content }"
668 content = "<sup>#{ num }</sup> #{ content }"
669 atts = shelve( atts ) if atts
669 atts = shelve( atts ) if atts
670 "\t<p#{ atts }>#{ content }</p>"
670 "\t<p#{ atts }>#{ content }</p>"
671 end
671 end
672
672
673 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
673 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
674
674
675 def block_textile_prefix( text )
675 def block_textile_prefix( text )
676 if text =~ BLOCK_RE
676 if text =~ BLOCK_RE
677 tag,tagpre,num,atts,cite,content = $~[1..6]
677 tag,tagpre,num,atts,cite,content = $~[1..6]
678 atts = pba( atts )
678 atts = pba( atts )
679
679
680 # pass to prefix handler
680 # pass to prefix handler
681 if respond_to? "textile_#{ tag }", true
681 if respond_to? "textile_#{ tag }", true
682 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
682 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
683 elsif respond_to? "textile_#{ tagpre }_", true
683 elsif respond_to? "textile_#{ tagpre }_", true
684 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
684 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
685 end
685 end
686 end
686 end
687 end
687 end
688
688
689 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
689 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
690 def block_markdown_setext( text )
690 def block_markdown_setext( text )
691 if text =~ SETEXT_RE
691 if text =~ SETEXT_RE
692 tag = if $2 == "="; "h1"; else; "h2"; end
692 tag = if $2 == "="; "h1"; else; "h2"; end
693 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
693 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
694 blocks cont
694 blocks cont
695 text.replace( blk + cont )
695 text.replace( blk + cont )
696 end
696 end
697 end
697 end
698
698
699 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
699 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
700 [ ]*
700 [ ]*
701 (.+?) # $2 = Header text
701 (.+?) # $2 = Header text
702 [ ]*
702 [ ]*
703 \#* # optional closing #'s (not counted)
703 \#* # optional closing #'s (not counted)
704 $/x
704 $/x
705 def block_markdown_atx( text )
705 def block_markdown_atx( text )
706 if text =~ ATX_RE
706 if text =~ ATX_RE
707 tag = "h#{ $1.length }"
707 tag = "h#{ $1.length }"
708 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
708 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
709 blocks cont
709 blocks cont
710 text.replace( blk + cont )
710 text.replace( blk + cont )
711 end
711 end
712 end
712 end
713
713
714 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
714 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
715
715
716 def block_markdown_bq( text )
716 def block_markdown_bq( text )
717 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
717 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
718 blk.gsub!( /^ *> ?/, '' )
718 blk.gsub!( /^ *> ?/, '' )
719 flush_left blk
719 flush_left blk
720 blocks blk
720 blocks blk
721 blk.gsub!( /^(\S)/, "\t\\1" )
721 blk.gsub!( /^(\S)/, "\t\\1" )
722 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
722 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
723 end
723 end
724 end
724 end
725
725
726 MARKDOWN_RULE_RE = /^(#{
726 MARKDOWN_RULE_RE = /^(#{
727 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
727 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
728 })$/
728 })$/
729
729
730 def block_markdown_rule( text )
730 def block_markdown_rule( text )
731 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
731 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
732 "<hr />"
732 "<hr />"
733 end
733 end
734 end
734 end
735
735
736 # XXX TODO XXX
736 # XXX TODO XXX
737 def block_markdown_lists( text )
737 def block_markdown_lists( text )
738 end
738 end
739
739
740 def inline_textile_span( text )
740 def inline_textile_span( text )
741 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
741 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
742 text.gsub!( qtag_re ) do |m|
742 text.gsub!( qtag_re ) do |m|
743
743
744 case rtype
744 case rtype
745 when :limit
745 when :limit
746 sta,qtag,atts,cite,content = $~[1..5]
746 sta,qtag,atts,cite,content = $~[1..5]
747 else
747 else
748 qtag,atts,cite,content = $~[1..4]
748 qtag,atts,cite,content = $~[1..4]
749 sta = ''
749 sta = ''
750 end
750 end
751 atts = pba( atts )
751 atts = pba( atts )
752 atts << " cite=\"#{ cite }\"" if cite
752 atts << " cite=\"#{ cite }\"" if cite
753 atts = shelve( atts ) if atts
753 atts = shelve( atts ) if atts
754
754
755 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
755 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
756
756
757 end
757 end
758 end
758 end
759 end
759 end
760
760
761 LINK_RE = /
761 LINK_RE = /
762 ([\s\[{(]|[#{PUNCT}])? # $pre
762 ([\s\[{(]|[#{PUNCT}])? # $pre
763 " # start
763 " # start
764 (#{C}) # $atts
764 (#{C}) # $atts
765 ([^"]+?) # $text
765 ([^"]+?) # $text
766 \s?
766 \s?
767 (?:\(([^)]+?)\)(?="))? # $title
767 (?:\(([^)]+?)\)(?="))? # $title
768 ":
768 ":
769 (\S+?) # $url
769 (\S+?) # $url
770 (\/)? # $slash
770 (\/)? # $slash
771 ([^\w\/;]*?) # $post
771 ([^\w\/;]*?) # $post
772 (?=<|\s|$)
772 (?=<|\s|$)
773 /x
773 /x
774
774
775 def inline_textile_link( text )
775 def inline_textile_link( text )
776 text.gsub!( LINK_RE ) do |m|
776 text.gsub!( LINK_RE ) do |m|
777 pre,atts,text,title,url,slash,post = $~[1..7]
777 pre,atts,text,title,url,slash,post = $~[1..7]
778
778
779 url, url_title = check_refs( url )
779 url, url_title = check_refs( url )
780 title ||= url_title
780 title ||= url_title
781
781
782 atts = pba( atts )
782 atts = pba( atts )
783 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
783 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
784 atts << " title=\"#{ title }\"" if title
784 atts << " title=\"#{ title }\"" if title
785 atts = shelve( atts ) if atts
785 atts = shelve( atts ) if atts
786
786
787 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
787 external = (url =~ /^http:\/\//) ? ' class="external"' : ''
788
788
789 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
789 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
790 end
790 end
791 end
791 end
792
792
793 MARKDOWN_REFLINK_RE = /
793 MARKDOWN_REFLINK_RE = /
794 \[([^\[\]]+)\] # $text
794 \[([^\[\]]+)\] # $text
795 [ ]? # opt. space
795 [ ]? # opt. space
796 (?:\n[ ]*)? # one optional newline followed by spaces
796 (?:\n[ ]*)? # one optional newline followed by spaces
797 \[(.*?)\] # $id
797 \[(.*?)\] # $id
798 /x
798 /x
799
799
800 def inline_markdown_reflink( text )
800 def inline_markdown_reflink( text )
801 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
801 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
802 text, id = $~[1..2]
802 text, id = $~[1..2]
803
803
804 if id.empty?
804 if id.empty?
805 url, title = check_refs( text )
805 url, title = check_refs( text )
806 else
806 else
807 url, title = check_refs( id )
807 url, title = check_refs( id )
808 end
808 end
809
809
810 atts = " href=\"#{ url }\""
810 atts = " href=\"#{ url }\""
811 atts << " title=\"#{ title }\"" if title
811 atts << " title=\"#{ title }\"" if title
812 atts = shelve( atts )
812 atts = shelve( atts )
813
813
814 "<a#{ atts }>#{ text }</a>"
814 "<a#{ atts }>#{ text }</a>"
815 end
815 end
816 end
816 end
817
817
818 MARKDOWN_LINK_RE = /
818 MARKDOWN_LINK_RE = /
819 \[([^\[\]]+)\] # $text
819 \[([^\[\]]+)\] # $text
820 \( # open paren
820 \( # open paren
821 [ \t]* # opt space
821 [ \t]* # opt space
822 <?(.+?)>? # $href
822 <?(.+?)>? # $href
823 [ \t]* # opt space
823 [ \t]* # opt space
824 (?: # whole title
824 (?: # whole title
825 (['"]) # $quote
825 (['"]) # $quote
826 (.*?) # $title
826 (.*?) # $title
827 \3 # matching quote
827 \3 # matching quote
828 )? # title is optional
828 )? # title is optional
829 \)
829 \)
830 /x
830 /x
831
831
832 def inline_markdown_link( text )
832 def inline_markdown_link( text )
833 text.gsub!( MARKDOWN_LINK_RE ) do |m|
833 text.gsub!( MARKDOWN_LINK_RE ) do |m|
834 text, url, quote, title = $~[1..4]
834 text, url, quote, title = $~[1..4]
835
835
836 atts = " href=\"#{ url }\""
836 atts = " href=\"#{ url }\""
837 atts << " title=\"#{ title }\"" if title
837 atts << " title=\"#{ title }\"" if title
838 atts = shelve( atts )
838 atts = shelve( atts )
839
839
840 "<a#{ atts }>#{ text }</a>"
840 "<a#{ atts }>#{ text }</a>"
841 end
841 end
842 end
842 end
843
843
844 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
844 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
845 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
845 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
846
846
847 def refs( text )
847 def refs( text )
848 @rules.each do |rule_name|
848 @rules.each do |rule_name|
849 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
849 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
850 end
850 end
851 end
851 end
852
852
853 def refs_textile( text )
853 def refs_textile( text )
854 text.gsub!( TEXTILE_REFS_RE ) do |m|
854 text.gsub!( TEXTILE_REFS_RE ) do |m|
855 flag, url = $~[2..3]
855 flag, url = $~[2..3]
856 @urlrefs[flag.downcase] = [url, nil]
856 @urlrefs[flag.downcase] = [url, nil]
857 nil
857 nil
858 end
858 end
859 end
859 end
860
860
861 def refs_markdown( text )
861 def refs_markdown( text )
862 text.gsub!( MARKDOWN_REFS_RE ) do |m|
862 text.gsub!( MARKDOWN_REFS_RE ) do |m|
863 flag, url = $~[2..3]
863 flag, url = $~[2..3]
864 title = $~[6]
864 title = $~[6]
865 @urlrefs[flag.downcase] = [url, title]
865 @urlrefs[flag.downcase] = [url, title]
866 nil
866 nil
867 end
867 end
868 end
868 end
869
869
870 def check_refs( text )
870 def check_refs( text )
871 ret = @urlrefs[text.downcase] if text
871 ret = @urlrefs[text.downcase] if text
872 ret || [text, nil]
872 ret || [text, nil]
873 end
873 end
874
874
875 IMAGE_RE = /
875 IMAGE_RE = /
876 (<p>|.|^) # start of line?
876 (<p>|.|^) # start of line?
877 \! # opening
877 \! # opening
878 (\<|\=|\>)? # optional alignment atts
878 (\<|\=|\>)? # optional alignment atts
879 (#{C}) # optional style,class atts
879 (#{C}) # optional style,class atts
880 (?:\. )? # optional dot-space
880 (?:\. )? # optional dot-space
881 ([^\s(!]+?) # presume this is the src
881 ([^\s(!]+?) # presume this is the src
882 \s? # optional space
882 \s? # optional space
883 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
883 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
884 \! # closing
884 \! # closing
885 (?::#{ HYPERLINK })? # optional href
885 (?::#{ HYPERLINK })? # optional href
886 /x
886 /x
887
887
888 def inline_textile_image( text )
888 def inline_textile_image( text )
889 text.gsub!( IMAGE_RE ) do |m|
889 text.gsub!( IMAGE_RE ) do |m|
890 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
890 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
891 atts = pba( atts )
891 atts = pba( atts )
892 atts = " src=\"#{ url }\"#{ atts }"
892 atts = " src=\"#{ url }\"#{ atts }"
893 atts << " title=\"#{ title }\"" if title
893 atts << " title=\"#{ title }\"" if title
894 atts << " alt=\"#{ title }\""
894 atts << " alt=\"#{ title }\""
895 # size = @getimagesize($url);
895 # size = @getimagesize($url);
896 # if($size) $atts.= " $size[3]";
896 # if($size) $atts.= " $size[3]";
897
897
898 href, alt_title = check_refs( href ) if href
898 href, alt_title = check_refs( href ) if href
899 url, url_title = check_refs( url )
899 url, url_title = check_refs( url )
900
900
901 out = ''
901 out = ''
902 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
902 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
903 out << "<img#{ shelve( atts ) } />"
903 out << "<img#{ shelve( atts ) } />"
904 out << "</a>#{ href_a1 }#{ href_a2 }" if href
904 out << "</a>#{ href_a1 }#{ href_a2 }" if href
905
905
906 if algn
906 if algn
907 algn = h_align( algn )
907 algn = h_align( algn )
908 if stln == "<p>"
908 if stln == "<p>"
909 out = "<p style=\"float:#{ algn }\">#{ out }"
909 out = "<p style=\"float:#{ algn }\">#{ out }"
910 else
910 else
911 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
911 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
912 end
912 end
913 else
913 else
914 out = stln + out
914 out = stln + out
915 end
915 end
916
916
917 out
917 out
918 end
918 end
919 end
919 end
920
920
921 def shelve( val )
921 def shelve( val )
922 @shelf << val
922 @shelf << val
923 " :redsh##{ @shelf.length }:"
923 " :redsh##{ @shelf.length }:"
924 end
924 end
925
925
926 def retrieve( text )
926 def retrieve( text )
927 @shelf.each_with_index do |r, i|
927 @shelf.each_with_index do |r, i|
928 text.gsub!( " :redsh##{ i + 1 }:", r )
928 text.gsub!( " :redsh##{ i + 1 }:", r )
929 end
929 end
930 end
930 end
931
931
932 def incoming_entities( text )
932 def incoming_entities( text )
933 ## turn any incoming ampersands into a dummy character for now.
933 ## turn any incoming ampersands into a dummy character for now.
934 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
934 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
935 ## implying an incoming html entity, to be skipped
935 ## implying an incoming html entity, to be skipped
936
936
937 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
937 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
938 end
938 end
939
939
940 def no_textile( text )
940 def no_textile( text )
941 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
941 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
942 '\1<notextile>\2</notextile>\3' )
942 '\1<notextile>\2</notextile>\3' )
943 text.gsub!( /^ *==([^=]+.*?)==/m,
943 text.gsub!( /^ *==([^=]+.*?)==/m,
944 '\1<notextile>\2</notextile>\3' )
944 '\1<notextile>\2</notextile>\3' )
945 end
945 end
946
946
947 def clean_white_space( text )
947 def clean_white_space( text )
948 # normalize line breaks
948 # normalize line breaks
949 text.gsub!( /\r\n/, "\n" )
949 text.gsub!( /\r\n/, "\n" )
950 text.gsub!( /\r/, "\n" )
950 text.gsub!( /\r/, "\n" )
951 text.gsub!( /\t/, ' ' )
951 text.gsub!( /\t/, ' ' )
952 text.gsub!( /^ +$/, '' )
952 text.gsub!( /^ +$/, '' )
953 text.gsub!( /\n{3,}/, "\n\n" )
953 text.gsub!( /\n{3,}/, "\n\n" )
954 text.gsub!( /"$/, "\" " )
954 text.gsub!( /"$/, "\" " )
955
955
956 # if entire document is indented, flush
956 # if entire document is indented, flush
957 # to the left side
957 # to the left side
958 flush_left text
958 flush_left text
959 end
959 end
960
960
961 def flush_left( text )
961 def flush_left( text )
962 indt = 0
962 indt = 0
963 if text =~ /^ /
963 if text =~ /^ /
964 while text !~ /^ {#{indt}}\S/
964 while text !~ /^ {#{indt}}\S/
965 indt += 1
965 indt += 1
966 end unless text.empty?
966 end unless text.empty?
967 if indt.nonzero?
967 if indt.nonzero?
968 text.gsub!( /^ {#{indt}}/, '' )
968 text.gsub!( /^ {#{indt}}/, '' )
969 end
969 end
970 end
970 end
971 end
971 end
972
972
973 def footnote_ref( text )
973 def footnote_ref( text )
974 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
974 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
975 '<sup><a href="#fn\1">\1</a></sup>\2' )
975 '<sup><a href="#fn\1">\1</a></sup>\2' )
976 end
976 end
977
977
978 OFFTAGS = /(code|pre|kbd|notextile)/
978 OFFTAGS = /(code|pre|kbd|notextile)/
979 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
979 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
980 OFFTAG_OPEN = /<#{ OFFTAGS }/
980 OFFTAG_OPEN = /<#{ OFFTAGS }/
981 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
981 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
982 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
982 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
983 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
983 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
984
984
985 def glyphs_textile( text, level = 0 )
985 def glyphs_textile( text, level = 0 )
986 if text !~ HASTAG_MATCH
986 if text !~ HASTAG_MATCH
987 pgl text
987 pgl text
988 footnote_ref text
988 footnote_ref text
989 else
989 else
990 codepre = 0
990 codepre = 0
991 text.gsub!( ALLTAG_MATCH ) do |line|
991 text.gsub!( ALLTAG_MATCH ) do |line|
992 ## matches are off if we're between <code>, <pre> etc.
992 ## matches are off if we're between <code>, <pre> etc.
993 if $1
993 if $1
994 if line =~ OFFTAG_OPEN
994 if line =~ OFFTAG_OPEN
995 codepre += 1
995 codepre += 1
996 elsif line =~ OFFTAG_CLOSE
996 elsif line =~ OFFTAG_CLOSE
997 codepre -= 1
997 codepre -= 1
998 codepre = 0 if codepre < 0
998 codepre = 0 if codepre < 0
999 end
999 end
1000 elsif codepre.zero?
1000 elsif codepre.zero?
1001 glyphs_textile( line, level + 1 )
1001 glyphs_textile( line, level + 1 )
1002 else
1002 else
1003 htmlesc( line, :NoQuotes )
1003 htmlesc( line, :NoQuotes )
1004 end
1004 end
1005 # p [level, codepre, line]
1005 # p [level, codepre, line]
1006
1006
1007 line
1007 line
1008 end
1008 end
1009 end
1009 end
1010 end
1010 end
1011
1011
1012 def rip_offtags( text )
1012 def rip_offtags( text )
1013 if text =~ /<.*>/
1013 if text =~ /<.*>/
1014 ## strip and encode <pre> content
1014 ## strip and encode <pre> content
1015 codepre, used_offtags = 0, {}
1015 codepre, used_offtags = 0, {}
1016 text.gsub!( OFFTAG_MATCH ) do |line|
1016 text.gsub!( OFFTAG_MATCH ) do |line|
1017 if $3
1017 if $3
1018 offtag, aftertag = $4, $5
1018 offtag, aftertag = $4, $5
1019 codepre += 1
1019 codepre += 1
1020 used_offtags[offtag] = true
1020 used_offtags[offtag] = true
1021 if codepre - used_offtags.length > 0
1021 if codepre - used_offtags.length > 0
1022 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1022 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1023 @pre_list.last << line
1023 @pre_list.last << line
1024 line = ""
1024 line = ""
1025 else
1025 else
1026 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1026 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1027 line = "<redpre##{ @pre_list.length }>"
1027 line = "<redpre##{ @pre_list.length }>"
1028 @pre_list << "#{ $3 }#{ aftertag }"
1028 @pre_list << "#{ $3 }#{ aftertag }"
1029 end
1029 end
1030 elsif $1 and codepre > 0
1030 elsif $1 and codepre > 0
1031 if codepre - used_offtags.length > 0
1031 if codepre - used_offtags.length > 0
1032 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1032 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1033 @pre_list.last << line
1033 @pre_list.last << line
1034 line = ""
1034 line = ""
1035 end
1035 end
1036 codepre -= 1 unless codepre.zero?
1036 codepre -= 1 unless codepre.zero?
1037 used_offtags = {} if codepre.zero?
1037 used_offtags = {} if codepre.zero?
1038 end
1038 end
1039 line
1039 line
1040 end
1040 end
1041 end
1041 end
1042 text
1042 text
1043 end
1043 end
1044
1044
1045 def smooth_offtags( text )
1045 def smooth_offtags( text )
1046 unless @pre_list.empty?
1046 unless @pre_list.empty?
1047 ## replace <pre> content
1047 ## replace <pre> content
1048 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1048 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1049 end
1049 end
1050 end
1050 end
1051
1051
1052 def inline( text )
1052 def inline( text )
1053 [/^inline_/, /^glyphs_/].each do |meth_re|
1053 [/^inline_/, /^glyphs_/].each do |meth_re|
1054 @rules.each do |rule_name|
1054 @rules.each do |rule_name|
1055 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1055 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1056 end
1056 end
1057 end
1057 end
1058 end
1058 end
1059
1059
1060 def h_align( text )
1060 def h_align( text )
1061 H_ALGN_VALS[text]
1061 H_ALGN_VALS[text]
1062 end
1062 end
1063
1063
1064 def v_align( text )
1064 def v_align( text )
1065 V_ALGN_VALS[text]
1065 V_ALGN_VALS[text]
1066 end
1066 end
1067
1067
1068 def textile_popup_help( name, windowW, windowH )
1068 def textile_popup_help( name, windowW, windowH )
1069 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1069 ' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
1070 end
1070 end
1071
1071
1072 # HTML cleansing stuff
1072 # HTML cleansing stuff
1073 BASIC_TAGS = {
1073 BASIC_TAGS = {
1074 'a' => ['href', 'title'],
1074 'a' => ['href', 'title'],
1075 'img' => ['src', 'alt', 'title'],
1075 'img' => ['src', 'alt', 'title'],
1076 'br' => [],
1076 'br' => [],
1077 'i' => nil,
1077 'i' => nil,
1078 'u' => nil,
1078 'u' => nil,
1079 'b' => nil,
1079 'b' => nil,
1080 'pre' => nil,
1080 'pre' => nil,
1081 'kbd' => nil,
1081 'kbd' => nil,
1082 'code' => ['lang'],
1082 'code' => ['lang'],
1083 'cite' => nil,
1083 'cite' => nil,
1084 'strong' => nil,
1084 'strong' => nil,
1085 'em' => nil,
1085 'em' => nil,
1086 'ins' => nil,
1086 'ins' => nil,
1087 'sup' => nil,
1087 'sup' => nil,
1088 'sub' => nil,
1088 'sub' => nil,
1089 'del' => nil,
1089 'del' => nil,
1090 'table' => nil,
1090 'table' => nil,
1091 'tr' => nil,
1091 'tr' => nil,
1092 'td' => ['colspan', 'rowspan'],
1092 'td' => ['colspan', 'rowspan'],
1093 'th' => nil,
1093 'th' => nil,
1094 'ol' => nil,
1094 'ol' => nil,
1095 'ul' => nil,
1095 'ul' => nil,
1096 'li' => nil,
1096 'li' => nil,
1097 'p' => nil,
1097 'p' => nil,
1098 'h1' => nil,
1098 'h1' => nil,
1099 'h2' => nil,
1099 'h2' => nil,
1100 'h3' => nil,
1100 'h3' => nil,
1101 'h4' => nil,
1101 'h4' => nil,
1102 'h5' => nil,
1102 'h5' => nil,
1103 'h6' => nil,
1103 'h6' => nil,
1104 'blockquote' => ['cite']
1104 'blockquote' => ['cite']
1105 }
1105 }
1106
1106
1107 def clean_html( text, tags = BASIC_TAGS )
1107 def clean_html( text, tags = BASIC_TAGS )
1108 text.gsub!( /<!\[CDATA\[/, '' )
1108 text.gsub!( /<!\[CDATA\[/, '' )
1109 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1109 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1110 raw = $~
1110 raw = $~
1111 tag = raw[2].downcase
1111 tag = raw[2].downcase
1112 if tags.has_key? tag
1112 if tags.has_key? tag
1113 pcs = [tag]
1113 pcs = [tag]
1114 tags[tag].each do |prop|
1114 tags[tag].each do |prop|
1115 ['"', "'", ''].each do |q|
1115 ['"', "'", ''].each do |q|
1116 q2 = ( q != '' ? q : '\s' )
1116 q2 = ( q != '' ? q : '\s' )
1117 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1117 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1118 attrv = $1
1118 attrv = $1
1119 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1119 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1120 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1120 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1121 break
1121 break
1122 end
1122 end
1123 end
1123 end
1124 end if tags[tag]
1124 end if tags[tag]
1125 "<#{raw[1]}#{pcs.join " "}>"
1125 "<#{raw[1]}#{pcs.join " "}>"
1126 else
1126 else
1127 " "
1127 " "
1128 end
1128 end
1129 end
1129 end
1130 end
1130 end
1131 end
1131 end
1132
1132
@@ -1,24 +1,34
1 ---
1 ---
2 wiki_contents_001:
2 wiki_contents_001:
3 text: |-
3 text: |-
4 h1. CookBook documentation
4 h1. CookBook documentation
5
5
6
6
7
7
8 Some updated [[documentation]] here with gzipped history
8 Some updated [[documentation]] here with gzipped history
9 updated_on: 2007-03-07 00:10:51 +01:00
9 updated_on: 2007-03-07 00:10:51 +01:00
10 page_id: 1
10 page_id: 1
11 id: 1
11 id: 1
12 version: 3
12 version: 3
13 author_id: 1
13 author_id: 1
14 comments: Gzip compression activated
14 comments: Gzip compression activated
15 wiki_contents_002:
15 wiki_contents_002:
16 text: |-
16 text: |-
17 h1. Another page
17 h1. Another page
18
18
19 This is a link to a ticket: #2
19 This is a link to a ticket: #2
20 updated_on: 2007-03-08 00:18:07 +01:00
20 updated_on: 2007-03-08 00:18:07 +01:00
21 page_id: 2
21 page_id: 2
22 id: 2
22 id: 2
23 version: 1
23 version: 1
24 author_id: 1 No newline at end of file
24 author_id: 1
25 comments:
26 wiki_contents_003:
27 text: |-
28 h1. Start page
29
30 E-commerce web site start page
31 updated_on: 2007-03-08 00:18:07 +01:00
32 page_id: 3
33 id: 3
34 version: 1
@@ -1,12 +1,17
1 ---
1 ---
2 wiki_pages_001:
2 wiki_pages_001:
3 created_on: 2007-03-07 00:08:07 +01:00
3 created_on: 2007-03-07 00:08:07 +01:00
4 title: CookBook_documentation
4 title: CookBook_documentation
5 id: 1
5 id: 1
6 wiki_id: 1
6 wiki_id: 1
7 wiki_pages_002:
7 wiki_pages_002:
8 created_on: 2007-03-08 00:18:07 +01:00
8 created_on: 2007-03-08 00:18:07 +01:00
9 title: Another_page
9 title: Another_page
10 id: 2
10 id: 2
11 wiki_id: 1
11 wiki_id: 1
12 wiki_pages_003:
13 created_on: 2007-03-08 00:18:07 +01:00
14 title: Start_page
15 id: 3
16 wiki_id: 2
12 No newline at end of file
17
@@ -1,6 +1,12
1 ---
1 ---
2 wikis_001:
2 wikis_001:
3 status: 1
3 status: 1
4 start_page: CookBook documentation
4 start_page: CookBook documentation
5 project_id: 1
5 project_id: 1
6 id: 1
6 id: 1
7 wikis_002:
8 status: 1
9 start_page: Start page
10 project_id: 2
11 id: 2
12 No newline at end of file
@@ -1,112 +1,152
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
23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents
24
24
25 def setup
25 def setup
26 super
26 super
27 end
27 end
28
28
29 def test_auto_links
29 def test_auto_links
30 to_test = {
30 to_test = {
31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
31 '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>',
32 '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>.',
33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 }
38 }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 end
40 end
41
41
42 def test_auto_mailto
42 def test_auto_mailto
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 textilizable('test@foo.bar')
44 textilizable('test@foo.bar')
45 end
45 end
46
46
47 def test_inline_images
47 def test_inline_images
48 to_test = {
48 to_test = {
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 }
53 }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 end
55 end
56
56
57 def test_textile_external_links
57 def test_textile_external_links
58 to_test = {
58 to_test = {
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>'
62 }
62 }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
63 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
64 end
64 end
65
65
66 def test_redmine_links
66 def test_redmine_links
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
67 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
68 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
69
69 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
70 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 1, :rev => 1},
70 :class => 'changeset', :title => 'My very first commit')
71 :class => 'changeset', :title => 'My very first commit')
71
72
73 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
74 :class => 'document')
75
76 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
77 :class => 'version')
78
72 to_test = {
79 to_test = {
73 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
80 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
74 'r1' => changeset_link
81 'r1' => changeset_link,
82 'document#1' => document_link,
83 'document:"Test document"' => document_link,
84 'version#2' => version_link,
85 'version:1.0' => version_link,
86 'version:"1.0"' => version_link,
87 # escaping
88 '!#3.' => '#3.',
89 '!r1' => 'r1',
90 '!document#1' => 'document#1',
91 '!document:"Test document"' => 'document:"Test document"',
92 '!version#2' => 'version#2',
93 '!version:1.0' => 'version:1.0',
94 '!version:"1.0"' => 'version:"1.0"',
95 }
96 @project = Project.find(1)
97 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
98 end
99
100 def test_wiki_links
101 to_test = {
102 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
103 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
104 # page that doesn't exist
105 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
106 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
107 # link to another project wiki
108 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
109 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
110 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
111 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
112 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
113 # escaping
114 '![[Another page|Page]]' => '[[Another page|Page]]',
75 }
115 }
76 @project = Project.find(1)
116 @project = Project.find(1)
77 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
117 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
78 end
118 end
79
119
80 def test_macro_hello_world
120 def test_macro_hello_world
81 text = "{{hello_world}}"
121 text = "{{hello_world}}"
82 assert textilizable(text).match(/Hello world!/)
122 assert textilizable(text).match(/Hello world!/)
83 end
123 end
84
124
85 def test_date_format_default
125 def test_date_format_default
86 today = Date.today
126 today = Date.today
87 Setting.date_format = ''
127 Setting.date_format = ''
88 assert_equal l_date(today), format_date(today)
128 assert_equal l_date(today), format_date(today)
89 end
129 end
90
130
91 def test_date_format
131 def test_date_format
92 today = Date.today
132 today = Date.today
93 Setting.date_format = '%d %m %Y'
133 Setting.date_format = '%d %m %Y'
94 assert_equal today.strftime('%d %m %Y'), format_date(today)
134 assert_equal today.strftime('%d %m %Y'), format_date(today)
95 end
135 end
96
136
97 def test_time_format_default
137 def test_time_format_default
98 now = Time.now
138 now = Time.now
99 Setting.date_format = ''
139 Setting.date_format = ''
100 Setting.time_format = ''
140 Setting.time_format = ''
101 assert_equal l_datetime(now), format_time(now)
141 assert_equal l_datetime(now), format_time(now)
102 assert_equal l_time(now), format_time(now, false)
142 assert_equal l_time(now), format_time(now, false)
103 end
143 end
104
144
105 def test_time_format
145 def test_time_format
106 now = Time.now
146 now = Time.now
107 Setting.date_format = '%d %m %Y'
147 Setting.date_format = '%d %m %Y'
108 Setting.time_format = '%H %M'
148 Setting.time_format = '%H %M'
109 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
149 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
110 assert_equal now.strftime('%H %M'), format_time(now, false)
150 assert_equal now.strftime('%H %M'), format_time(now, false)
111 end
151 end
112 end
152 end
General Comments 0
You need to be logged in to leave comments. Login now