##// END OF EJS Templates
Fixed: textile footnotes no longer work after r1113 (#974)....
Jean-Philippe Lang -
r1939:ff449b197f28
parent child
Show More
@@ -1,1164 +1,1164
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 RedCloth3 < String
167 class RedCloth3 < 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]
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 escape_html_tags text
298 escape_html_tags text
299 hard_break text
299 hard_break text
300 unless @lite_mode
300 unless @lite_mode
301 refs text
301 refs text
302 # need to do this before text is split by #blocks
302 # need to do this before text is split by #blocks
303 block_textile_quotes text
303 block_textile_quotes text
304 blocks text
304 blocks text
305 end
305 end
306 inline text
306 inline text
307 smooth_offtags text
307 smooth_offtags text
308
308
309 retrieve text
309 retrieve text
310
310
311 text.gsub!( /<\/?notextile>/, '' )
311 text.gsub!( /<\/?notextile>/, '' )
312 text.gsub!( /x%x%/, '&#38;' )
312 text.gsub!( /x%x%/, '&#38;' )
313 clean_html text if filter_html
313 clean_html text if filter_html
314 text.strip!
314 text.strip!
315 text
315 text
316
316
317 end
317 end
318
318
319 #######
319 #######
320 private
320 private
321 #######
321 #######
322 #
322 #
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
324 # (from PyTextile)
324 # (from PyTextile)
325 #
325 #
326 TEXTILE_TAGS =
326 TEXTILE_TAGS =
327
327
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
333
333
334 collect! do |a, b|
334 collect! do |a, b|
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
336 end
336 end
337
337
338 #
338 #
339 # Regular expressions to convert to HTML.
339 # Regular expressions to convert to HTML.
340 #
340 #
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
342 A_VLGN = /[\-^~]/
342 A_VLGN = /[\-^~]/
343 C_CLAS = '(?:\([^)]+\))'
343 C_CLAS = '(?:\([^)]+\))'
344 C_LNGE = '(?:\[[^\]]+\])'
344 C_LNGE = '(?:\[[^\]]+\])'
345 C_STYL = '(?:\{[^}]+\})'
345 C_STYL = '(?:\{[^}]+\})'
346 S_CSPN = '(?:\\\\\d+)'
346 S_CSPN = '(?:\\\\\d+)'
347 S_RSPN = '(?:/\d+)'
347 S_RSPN = '(?:/\d+)'
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
356
356
357 # Text markup tags, don't conflict with block tags
357 # Text markup tags, don't conflict with block tags
358 SIMPLE_HTML_TAGS = [
358 SIMPLE_HTML_TAGS = [
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
362 ]
362 ]
363
363
364 QTAGS = [
364 QTAGS = [
365 ['**', 'b', :limit],
365 ['**', 'b', :limit],
366 ['*', 'strong', :limit],
366 ['*', 'strong', :limit],
367 ['??', 'cite', :limit],
367 ['??', 'cite', :limit],
368 ['-', 'del', :limit],
368 ['-', 'del', :limit],
369 ['__', 'i', :limit],
369 ['__', 'i', :limit],
370 ['_', 'em', :limit],
370 ['_', 'em', :limit],
371 ['%', 'span', :limit],
371 ['%', 'span', :limit],
372 ['+', 'ins', :limit],
372 ['+', 'ins', :limit],
373 ['^', 'sup', :limit],
373 ['^', 'sup', :limit],
374 ['~', 'sub', :limit]
374 ['~', 'sub', :limit]
375 ]
375 ]
376 QTAGS.collect! do |rc, ht, rtype|
376 QTAGS.collect! do |rc, ht, rtype|
377 rcq = Regexp::quote rc
377 rcq = Regexp::quote rc
378 re =
378 re =
379 case rtype
379 case rtype
380 when :limit
380 when :limit
381 /(^|[>\s\(])
381 /(^|[>\s\(])
382 (#{rcq})
382 (#{rcq})
383 (#{C})
383 (#{C})
384 (?::(\S+?))?
384 (?::(\S+?))?
385 ([^\s\-].*?[^\s\-]|\w)
385 ([^\s\-].*?[^\s\-]|\w)
386 #{rcq}
386 #{rcq}
387 (?=[[:punct:]]|\s|\)|$)/x
387 (?=[[:punct:]]|\s|\)|$)/x
388 else
388 else
389 /(#{rcq})
389 /(#{rcq})
390 (#{C})
390 (#{C})
391 (?::(\S+))?
391 (?::(\S+))?
392 ([^\s\-].*?[^\s\-]|\w)
392 ([^\s\-].*?[^\s\-]|\w)
393 #{rcq}/xm
393 #{rcq}/xm
394 end
394 end
395 [rc, ht, re, rtype]
395 [rc, ht, re, rtype]
396 end
396 end
397
397
398 # Elements to handle
398 # Elements to handle
399 GLYPHS = [
399 GLYPHS = [
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
403 # [ /\'/, '&#8216;' ], # single opening
403 # [ /\'/, '&#8216;' ], # single opening
404 [ /</, '&lt;' ], # less-than
404 [ /</, '&lt;' ], # less-than
405 [ />/, '&gt;' ], # greater-than
405 [ />/, '&gt;' ], # greater-than
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
409 # [ /"/, '&#8220;' ], # double opening
409 # [ /"/, '&#8220;' ], # double opening
410 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
410 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
412 [ /(^|[^"][>\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
412 [ /(^|[^"][>\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
413 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
413 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
414 [ /\s->\s/, ' &rarr; ' ], # right arrow
414 [ /\s->\s/, ' &rarr; ' ], # right arrow
415 [ /\s-\s/, ' &#8211; ' ], # en dash
415 [ /\s-\s/, ' &#8211; ' ], # en dash
416 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
416 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
417 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
417 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
418 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
418 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
419 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
419 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
420 ]
420 ]
421
421
422 H_ALGN_VALS = {
422 H_ALGN_VALS = {
423 '<' => 'left',
423 '<' => 'left',
424 '=' => 'center',
424 '=' => 'center',
425 '>' => 'right',
425 '>' => 'right',
426 '<>' => 'justify'
426 '<>' => 'justify'
427 }
427 }
428
428
429 V_ALGN_VALS = {
429 V_ALGN_VALS = {
430 '^' => 'top',
430 '^' => 'top',
431 '-' => 'middle',
431 '-' => 'middle',
432 '~' => 'bottom'
432 '~' => 'bottom'
433 }
433 }
434
434
435 #
435 #
436 # Flexible HTML escaping
436 # Flexible HTML escaping
437 #
437 #
438 def htmlesc( str, mode )
438 def htmlesc( str, mode )
439 str.gsub!( '&', '&amp;' )
439 str.gsub!( '&', '&amp;' )
440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
442 str.gsub!( '<', '&lt;')
442 str.gsub!( '<', '&lt;')
443 str.gsub!( '>', '&gt;')
443 str.gsub!( '>', '&gt;')
444 end
444 end
445
445
446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
447 def pgl( text )
447 def pgl( text )
448 GLYPHS.each do |re, resub, tog|
448 GLYPHS.each do |re, resub, tog|
449 next if tog and method( tog ).call
449 next if tog and method( tog ).call
450 text.gsub! re, resub
450 text.gsub! re, resub
451 end
451 end
452 end
452 end
453
453
454 # Parses Textile attribute lists and builds an HTML attribute string
454 # Parses Textile attribute lists and builds an HTML attribute string
455 def pba( text_in, element = "" )
455 def pba( text_in, element = "" )
456
456
457 return '' unless text_in
457 return '' unless text_in
458
458
459 style = []
459 style = []
460 text = text_in.dup
460 text = text_in.dup
461 if element == 'td'
461 if element == 'td'
462 colspan = $1 if text =~ /\\(\d+)/
462 colspan = $1 if text =~ /\\(\d+)/
463 rowspan = $1 if text =~ /\/(\d+)/
463 rowspan = $1 if text =~ /\/(\d+)/
464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
465 end
465 end
466
466
467 style << "#{ $1 };" if not filter_styles and
467 style << "#{ $1 };" if not filter_styles and
468 text.sub!( /\{([^}]*)\}/, '' )
468 text.sub!( /\{([^}]*)\}/, '' )
469
469
470 lang = $1 if
470 lang = $1 if
471 text.sub!( /\[([^)]+?)\]/, '' )
471 text.sub!( /\[([^)]+?)\]/, '' )
472
472
473 cls = $1 if
473 cls = $1 if
474 text.sub!( /\(([^()]+?)\)/, '' )
474 text.sub!( /\(([^()]+?)\)/, '' )
475
475
476 style << "padding-left:#{ $1.length }em;" if
476 style << "padding-left:#{ $1.length }em;" if
477 text.sub!( /([(]+)/, '' )
477 text.sub!( /([(]+)/, '' )
478
478
479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
480
480
481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
482
482
483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
484
484
485 atts = ''
485 atts = ''
486 atts << " style=\"#{ style.join }\"" unless style.empty?
486 atts << " style=\"#{ style.join }\"" unless style.empty?
487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
488 atts << " lang=\"#{ lang }\"" if lang
488 atts << " lang=\"#{ lang }\"" if lang
489 atts << " id=\"#{ id }\"" if id
489 atts << " id=\"#{ id }\"" if id
490 atts << " colspan=\"#{ colspan }\"" if colspan
490 atts << " colspan=\"#{ colspan }\"" if colspan
491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
492
492
493 atts
493 atts
494 end
494 end
495
495
496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
497
497
498 # Parses a Textile table block, building HTML from the result.
498 # Parses a Textile table block, building HTML from the result.
499 def block_textile_table( text )
499 def block_textile_table( text )
500 text.gsub!( TABLE_RE ) do |matches|
500 text.gsub!( TABLE_RE ) do |matches|
501
501
502 tatts, fullrow = $~[1..2]
502 tatts, fullrow = $~[1..2]
503 tatts = pba( tatts, 'table' )
503 tatts = pba( tatts, 'table' )
504 tatts = shelve( tatts ) if tatts
504 tatts = shelve( tatts ) if tatts
505 rows = []
505 rows = []
506
506
507 fullrow.each_line do |row|
507 fullrow.each_line do |row|
508 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
508 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
509 cells = []
509 cells = []
510 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
510 row.split( /(\|)(?![^\[\|]*\]\])/ )[1..-2].each do |cell|
511 next if cell == '|'
511 next if cell == '|'
512 ctyp = 'd'
512 ctyp = 'd'
513 ctyp = 'h' if cell =~ /^_/
513 ctyp = 'h' if cell =~ /^_/
514
514
515 catts = ''
515 catts = ''
516 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
516 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
517
517
518 catts = shelve( catts ) if catts
518 catts = shelve( catts ) if catts
519 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
519 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
520 end
520 end
521 ratts = shelve( ratts ) if ratts
521 ratts = shelve( ratts ) if ratts
522 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
522 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
523 end
523 end
524 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
524 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
525 end
525 end
526 end
526 end
527
527
528 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
528 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
529 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
529 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
530
530
531 # Parses Textile lists and generates HTML
531 # Parses Textile lists and generates HTML
532 def block_textile_lists( text )
532 def block_textile_lists( text )
533 text.gsub!( LISTS_RE ) do |match|
533 text.gsub!( LISTS_RE ) do |match|
534 lines = match.split( /\n/ )
534 lines = match.split( /\n/ )
535 last_line = -1
535 last_line = -1
536 depth = []
536 depth = []
537 lines.each_with_index do |line, line_id|
537 lines.each_with_index do |line, line_id|
538 if line =~ LISTS_CONTENT_RE
538 if line =~ LISTS_CONTENT_RE
539 tl,atts,content = $~[1..3]
539 tl,atts,content = $~[1..3]
540 if depth.last
540 if depth.last
541 if depth.last.length > tl.length
541 if depth.last.length > tl.length
542 (depth.length - 1).downto(0) do |i|
542 (depth.length - 1).downto(0) do |i|
543 break if depth[i].length == tl.length
543 break if depth[i].length == tl.length
544 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
544 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
545 depth.pop
545 depth.pop
546 end
546 end
547 end
547 end
548 if depth.last and depth.last.length == tl.length
548 if depth.last and depth.last.length == tl.length
549 lines[line_id - 1] << '</li>'
549 lines[line_id - 1] << '</li>'
550 end
550 end
551 end
551 end
552 unless depth.last == tl
552 unless depth.last == tl
553 depth << tl
553 depth << tl
554 atts = pba( atts )
554 atts = pba( atts )
555 atts = shelve( atts ) if atts
555 atts = shelve( atts ) if atts
556 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
556 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
557 else
557 else
558 lines[line_id] = "\t\t<li>#{ content }"
558 lines[line_id] = "\t\t<li>#{ content }"
559 end
559 end
560 last_line = line_id
560 last_line = line_id
561
561
562 else
562 else
563 last_line = line_id
563 last_line = line_id
564 end
564 end
565 if line_id - last_line > 1 or line_id == lines.length - 1
565 if line_id - last_line > 1 or line_id == lines.length - 1
566 depth.delete_if do |v|
566 depth.delete_if do |v|
567 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
567 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
568 end
568 end
569 end
569 end
570 end
570 end
571 lines.join( "\n" )
571 lines.join( "\n" )
572 end
572 end
573 end
573 end
574
574
575 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
575 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
576 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
576 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
577
577
578 def block_textile_quotes( text )
578 def block_textile_quotes( text )
579 text.gsub!( QUOTES_RE ) do |match|
579 text.gsub!( QUOTES_RE ) do |match|
580 lines = match.split( /\n/ )
580 lines = match.split( /\n/ )
581 quotes = ''
581 quotes = ''
582 indent = 0
582 indent = 0
583 lines.each do |line|
583 lines.each do |line|
584 line =~ QUOTES_CONTENT_RE
584 line =~ QUOTES_CONTENT_RE
585 bq,content = $1, $2
585 bq,content = $1, $2
586 l = bq.count('>')
586 l = bq.count('>')
587 if l != indent
587 if l != indent
588 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
588 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
589 indent = l
589 indent = l
590 end
590 end
591 quotes << (content + "\n")
591 quotes << (content + "\n")
592 end
592 end
593 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
593 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
594 quotes
594 quotes
595 end
595 end
596 end
596 end
597
597
598 CODE_RE = /(\W)
598 CODE_RE = /(\W)
599 @
599 @
600 (?:\|(\w+?)\|)?
600 (?:\|(\w+?)\|)?
601 (.+?)
601 (.+?)
602 @
602 @
603 (?=\W)/x
603 (?=\W)/x
604
604
605 def inline_textile_code( text )
605 def inline_textile_code( text )
606 text.gsub!( CODE_RE ) do |m|
606 text.gsub!( CODE_RE ) do |m|
607 before,lang,code,after = $~[1..4]
607 before,lang,code,after = $~[1..4]
608 lang = " lang=\"#{ lang }\"" if lang
608 lang = " lang=\"#{ lang }\"" if lang
609 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
609 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
610 end
610 end
611 end
611 end
612
612
613 def lT( text )
613 def lT( text )
614 text =~ /\#$/ ? 'o' : 'u'
614 text =~ /\#$/ ? 'o' : 'u'
615 end
615 end
616
616
617 def hard_break( text )
617 def hard_break( text )
618 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
618 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
619 end
619 end
620
620
621 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
621 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
622
622
623 def blocks( text, deep_code = false )
623 def blocks( text, deep_code = false )
624 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
624 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
625 plain = blk !~ /\A[#*> ]/
625 plain = blk !~ /\A[#*> ]/
626
626
627 # skip blocks that are complex HTML
627 # skip blocks that are complex HTML
628 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
628 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
629 blk
629 blk
630 else
630 else
631 # search for indentation levels
631 # search for indentation levels
632 blk.strip!
632 blk.strip!
633 if blk.empty?
633 if blk.empty?
634 blk
634 blk
635 else
635 else
636 code_blk = nil
636 code_blk = nil
637 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
637 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
638 flush_left iblk
638 flush_left iblk
639 blocks iblk, plain
639 blocks iblk, plain
640 iblk.gsub( /^(\S)/, "\t\\1" )
640 iblk.gsub( /^(\S)/, "\t\\1" )
641 if plain
641 if plain
642 code_blk = iblk; ""
642 code_blk = iblk; ""
643 else
643 else
644 iblk
644 iblk
645 end
645 end
646 end
646 end
647
647
648 block_applied = 0
648 block_applied = 0
649 @rules.each do |rule_name|
649 @rules.each do |rule_name|
650 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
650 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
651 end
651 end
652 if block_applied.zero?
652 if block_applied.zero?
653 if deep_code
653 if deep_code
654 blk = "\t<pre><code>#{ blk }</code></pre>"
654 blk = "\t<pre><code>#{ blk }</code></pre>"
655 else
655 else
656 blk = "\t<p>#{ blk }</p>"
656 blk = "\t<p>#{ blk }</p>"
657 end
657 end
658 end
658 end
659 # hard_break blk
659 # hard_break blk
660 blk + "\n#{ code_blk }"
660 blk + "\n#{ code_blk }"
661 end
661 end
662 end
662 end
663
663
664 end.join( "\n\n" ) )
664 end.join( "\n\n" ) )
665 end
665 end
666
666
667 def textile_bq( tag, atts, cite, content )
667 def textile_bq( tag, atts, cite, content )
668 cite, cite_title = check_refs( cite )
668 cite, cite_title = check_refs( cite )
669 cite = " cite=\"#{ cite }\"" if cite
669 cite = " cite=\"#{ cite }\"" if cite
670 atts = shelve( atts ) if atts
670 atts = shelve( atts ) if atts
671 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
671 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
672 end
672 end
673
673
674 def textile_p( tag, atts, cite, content )
674 def textile_p( tag, atts, cite, content )
675 atts = shelve( atts ) if atts
675 atts = shelve( atts ) if atts
676 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
676 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
677 end
677 end
678
678
679 alias textile_h1 textile_p
679 alias textile_h1 textile_p
680 alias textile_h2 textile_p
680 alias textile_h2 textile_p
681 alias textile_h3 textile_p
681 alias textile_h3 textile_p
682 alias textile_h4 textile_p
682 alias textile_h4 textile_p
683 alias textile_h5 textile_p
683 alias textile_h5 textile_p
684 alias textile_h6 textile_p
684 alias textile_h6 textile_p
685
685
686 def textile_fn_( tag, num, atts, cite, content )
686 def textile_fn_( tag, num, atts, cite, content )
687 atts << " id=\"fn#{ num }\""
687 atts << " id=\"fn#{ num }\" class=\"footnote\""
688 content = "<sup>#{ num }</sup> #{ content }"
688 content = "<sup>#{ num }</sup> #{ content }"
689 atts = shelve( atts ) if atts
689 atts = shelve( atts ) if atts
690 "\t<p#{ atts }>#{ content }</p>"
690 "\t<p#{ atts }>#{ content }</p>"
691 end
691 end
692
692
693 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
693 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
694
694
695 def block_textile_prefix( text )
695 def block_textile_prefix( text )
696 if text =~ BLOCK_RE
696 if text =~ BLOCK_RE
697 tag,tagpre,num,atts,cite,content = $~[1..6]
697 tag,tagpre,num,atts,cite,content = $~[1..6]
698 atts = pba( atts )
698 atts = pba( atts )
699
699
700 # pass to prefix handler
700 # pass to prefix handler
701 if respond_to? "textile_#{ tag }", true
701 if respond_to? "textile_#{ tag }", true
702 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
702 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
703 elsif respond_to? "textile_#{ tagpre }_", true
703 elsif respond_to? "textile_#{ tagpre }_", true
704 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
704 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
705 end
705 end
706 end
706 end
707 end
707 end
708
708
709 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
709 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
710 def block_markdown_setext( text )
710 def block_markdown_setext( text )
711 if text =~ SETEXT_RE
711 if text =~ SETEXT_RE
712 tag = if $2 == "="; "h1"; else; "h2"; end
712 tag = if $2 == "="; "h1"; else; "h2"; end
713 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
713 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
714 blocks cont
714 blocks cont
715 text.replace( blk + cont )
715 text.replace( blk + cont )
716 end
716 end
717 end
717 end
718
718
719 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
719 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
720 [ ]*
720 [ ]*
721 (.+?) # $2 = Header text
721 (.+?) # $2 = Header text
722 [ ]*
722 [ ]*
723 \#* # optional closing #'s (not counted)
723 \#* # optional closing #'s (not counted)
724 $/x
724 $/x
725 def block_markdown_atx( text )
725 def block_markdown_atx( text )
726 if text =~ ATX_RE
726 if text =~ ATX_RE
727 tag = "h#{ $1.length }"
727 tag = "h#{ $1.length }"
728 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
728 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
729 blocks cont
729 blocks cont
730 text.replace( blk + cont )
730 text.replace( blk + cont )
731 end
731 end
732 end
732 end
733
733
734 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
734 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
735
735
736 def block_markdown_bq( text )
736 def block_markdown_bq( text )
737 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
737 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
738 blk.gsub!( /^ *> ?/, '' )
738 blk.gsub!( /^ *> ?/, '' )
739 flush_left blk
739 flush_left blk
740 blocks blk
740 blocks blk
741 blk.gsub!( /^(\S)/, "\t\\1" )
741 blk.gsub!( /^(\S)/, "\t\\1" )
742 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
742 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
743 end
743 end
744 end
744 end
745
745
746 MARKDOWN_RULE_RE = /^(#{
746 MARKDOWN_RULE_RE = /^(#{
747 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
747 ['*', '-', '_'].collect { |ch| ' ?(' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
748 })$/
748 })$/
749
749
750 def block_markdown_rule( text )
750 def block_markdown_rule( text )
751 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
751 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
752 "<hr />"
752 "<hr />"
753 end
753 end
754 end
754 end
755
755
756 # XXX TODO XXX
756 # XXX TODO XXX
757 def block_markdown_lists( text )
757 def block_markdown_lists( text )
758 end
758 end
759
759
760 def inline_textile_span( text )
760 def inline_textile_span( text )
761 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
761 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
762 text.gsub!( qtag_re ) do |m|
762 text.gsub!( qtag_re ) do |m|
763
763
764 case rtype
764 case rtype
765 when :limit
765 when :limit
766 sta,qtag,atts,cite,content = $~[1..5]
766 sta,qtag,atts,cite,content = $~[1..5]
767 else
767 else
768 qtag,atts,cite,content = $~[1..4]
768 qtag,atts,cite,content = $~[1..4]
769 sta = ''
769 sta = ''
770 end
770 end
771 atts = pba( atts )
771 atts = pba( atts )
772 atts << " cite=\"#{ cite }\"" if cite
772 atts << " cite=\"#{ cite }\"" if cite
773 atts = shelve( atts ) if atts
773 atts = shelve( atts ) if atts
774
774
775 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
775 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
776
776
777 end
777 end
778 end
778 end
779 end
779 end
780
780
781 LINK_RE = /
781 LINK_RE = /
782 ([\s\[{(]|[#{PUNCT}])? # $pre
782 ([\s\[{(]|[#{PUNCT}])? # $pre
783 " # start
783 " # start
784 (#{C}) # $atts
784 (#{C}) # $atts
785 ([^"\n]+?) # $text
785 ([^"\n]+?) # $text
786 \s?
786 \s?
787 (?:\(([^)]+?)\)(?="))? # $title
787 (?:\(([^)]+?)\)(?="))? # $title
788 ":
788 ":
789 ([\w\/]\S+?) # $url
789 ([\w\/]\S+?) # $url
790 (\/)? # $slash
790 (\/)? # $slash
791 ([^\w\=\/;\(\)]*?) # $post
791 ([^\w\=\/;\(\)]*?) # $post
792 (?=<|\s|$)
792 (?=<|\s|$)
793 /x
793 /x
794 #"
794 #"
795 def inline_textile_link( text )
795 def inline_textile_link( text )
796 text.gsub!( LINK_RE ) do |m|
796 text.gsub!( LINK_RE ) do |m|
797 pre,atts,text,title,url,slash,post = $~[1..7]
797 pre,atts,text,title,url,slash,post = $~[1..7]
798
798
799 url, url_title = check_refs( url )
799 url, url_title = check_refs( url )
800 title ||= url_title
800 title ||= url_title
801
801
802 # Idea below : an URL with unbalanced parethesis and
802 # Idea below : an URL with unbalanced parethesis and
803 # ending by ')' is put into external parenthesis
803 # ending by ')' is put into external parenthesis
804 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
804 if ( url[-1]==?) and ((url.count("(") - url.count(")")) < 0 ) )
805 url=url[0..-2] # discard closing parenth from url
805 url=url[0..-2] # discard closing parenth from url
806 post = ")"+post # add closing parenth to post
806 post = ")"+post # add closing parenth to post
807 end
807 end
808 atts = pba( atts )
808 atts = pba( atts )
809 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
809 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
810 atts << " title=\"#{ title }\"" if title
810 atts << " title=\"#{ title }\"" if title
811 atts = shelve( atts ) if atts
811 atts = shelve( atts ) if atts
812
812
813 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
813 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
814
814
815 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
815 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
816 end
816 end
817 end
817 end
818
818
819 MARKDOWN_REFLINK_RE = /
819 MARKDOWN_REFLINK_RE = /
820 \[([^\[\]]+)\] # $text
820 \[([^\[\]]+)\] # $text
821 [ ]? # opt. space
821 [ ]? # opt. space
822 (?:\n[ ]*)? # one optional newline followed by spaces
822 (?:\n[ ]*)? # one optional newline followed by spaces
823 \[(.*?)\] # $id
823 \[(.*?)\] # $id
824 /x
824 /x
825
825
826 def inline_markdown_reflink( text )
826 def inline_markdown_reflink( text )
827 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
827 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
828 text, id = $~[1..2]
828 text, id = $~[1..2]
829
829
830 if id.empty?
830 if id.empty?
831 url, title = check_refs( text )
831 url, title = check_refs( text )
832 else
832 else
833 url, title = check_refs( id )
833 url, title = check_refs( id )
834 end
834 end
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 MARKDOWN_LINK_RE = /
844 MARKDOWN_LINK_RE = /
845 \[([^\[\]]+)\] # $text
845 \[([^\[\]]+)\] # $text
846 \( # open paren
846 \( # open paren
847 [ \t]* # opt space
847 [ \t]* # opt space
848 <?(.+?)>? # $href
848 <?(.+?)>? # $href
849 [ \t]* # opt space
849 [ \t]* # opt space
850 (?: # whole title
850 (?: # whole title
851 (['"]) # $quote
851 (['"]) # $quote
852 (.*?) # $title
852 (.*?) # $title
853 \3 # matching quote
853 \3 # matching quote
854 )? # title is optional
854 )? # title is optional
855 \)
855 \)
856 /x
856 /x
857
857
858 def inline_markdown_link( text )
858 def inline_markdown_link( text )
859 text.gsub!( MARKDOWN_LINK_RE ) do |m|
859 text.gsub!( MARKDOWN_LINK_RE ) do |m|
860 text, url, quote, title = $~[1..4]
860 text, url, quote, title = $~[1..4]
861
861
862 atts = " href=\"#{ url }\""
862 atts = " href=\"#{ url }\""
863 atts << " title=\"#{ title }\"" if title
863 atts << " title=\"#{ title }\"" if title
864 atts = shelve( atts )
864 atts = shelve( atts )
865
865
866 "<a#{ atts }>#{ text }</a>"
866 "<a#{ atts }>#{ text }</a>"
867 end
867 end
868 end
868 end
869
869
870 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
870 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
871 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
871 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
872
872
873 def refs( text )
873 def refs( text )
874 @rules.each do |rule_name|
874 @rules.each do |rule_name|
875 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
875 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
876 end
876 end
877 end
877 end
878
878
879 def refs_textile( text )
879 def refs_textile( text )
880 text.gsub!( TEXTILE_REFS_RE ) do |m|
880 text.gsub!( TEXTILE_REFS_RE ) do |m|
881 flag, url = $~[2..3]
881 flag, url = $~[2..3]
882 @urlrefs[flag.downcase] = [url, nil]
882 @urlrefs[flag.downcase] = [url, nil]
883 nil
883 nil
884 end
884 end
885 end
885 end
886
886
887 def refs_markdown( text )
887 def refs_markdown( text )
888 text.gsub!( MARKDOWN_REFS_RE ) do |m|
888 text.gsub!( MARKDOWN_REFS_RE ) do |m|
889 flag, url = $~[2..3]
889 flag, url = $~[2..3]
890 title = $~[6]
890 title = $~[6]
891 @urlrefs[flag.downcase] = [url, title]
891 @urlrefs[flag.downcase] = [url, title]
892 nil
892 nil
893 end
893 end
894 end
894 end
895
895
896 def check_refs( text )
896 def check_refs( text )
897 ret = @urlrefs[text.downcase] if text
897 ret = @urlrefs[text.downcase] if text
898 ret || [text, nil]
898 ret || [text, nil]
899 end
899 end
900
900
901 IMAGE_RE = /
901 IMAGE_RE = /
902 (<p>|.|^) # start of line?
902 (<p>|.|^) # start of line?
903 \! # opening
903 \! # opening
904 (\<|\=|\>)? # optional alignment atts
904 (\<|\=|\>)? # optional alignment atts
905 (#{C}) # optional style,class atts
905 (#{C}) # optional style,class atts
906 (?:\. )? # optional dot-space
906 (?:\. )? # optional dot-space
907 ([^\s(!]+?) # presume this is the src
907 ([^\s(!]+?) # presume this is the src
908 \s? # optional space
908 \s? # optional space
909 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
909 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
910 \! # closing
910 \! # closing
911 (?::#{ HYPERLINK })? # optional href
911 (?::#{ HYPERLINK })? # optional href
912 /x
912 /x
913
913
914 def inline_textile_image( text )
914 def inline_textile_image( text )
915 text.gsub!( IMAGE_RE ) do |m|
915 text.gsub!( IMAGE_RE ) do |m|
916 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
916 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
917 atts = pba( atts )
917 atts = pba( atts )
918 atts = " src=\"#{ url }\"#{ atts }"
918 atts = " src=\"#{ url }\"#{ atts }"
919 atts << " title=\"#{ title }\"" if title
919 atts << " title=\"#{ title }\"" if title
920 atts << " alt=\"#{ title }\""
920 atts << " alt=\"#{ title }\""
921 # size = @getimagesize($url);
921 # size = @getimagesize($url);
922 # if($size) $atts.= " $size[3]";
922 # if($size) $atts.= " $size[3]";
923
923
924 href, alt_title = check_refs( href ) if href
924 href, alt_title = check_refs( href ) if href
925 url, url_title = check_refs( url )
925 url, url_title = check_refs( url )
926
926
927 out = ''
927 out = ''
928 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
928 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
929 out << "<img#{ shelve( atts ) } />"
929 out << "<img#{ shelve( atts ) } />"
930 out << "</a>#{ href_a1 }#{ href_a2 }" if href
930 out << "</a>#{ href_a1 }#{ href_a2 }" if href
931
931
932 if algn
932 if algn
933 algn = h_align( algn )
933 algn = h_align( algn )
934 if stln == "<p>"
934 if stln == "<p>"
935 out = "<p style=\"float:#{ algn }\">#{ out }"
935 out = "<p style=\"float:#{ algn }\">#{ out }"
936 else
936 else
937 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
937 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
938 end
938 end
939 else
939 else
940 out = stln + out
940 out = stln + out
941 end
941 end
942
942
943 out
943 out
944 end
944 end
945 end
945 end
946
946
947 def shelve( val )
947 def shelve( val )
948 @shelf << val
948 @shelf << val
949 " :redsh##{ @shelf.length }:"
949 " :redsh##{ @shelf.length }:"
950 end
950 end
951
951
952 def retrieve( text )
952 def retrieve( text )
953 @shelf.each_with_index do |r, i|
953 @shelf.each_with_index do |r, i|
954 text.gsub!( " :redsh##{ i + 1 }:", r )
954 text.gsub!( " :redsh##{ i + 1 }:", r )
955 end
955 end
956 end
956 end
957
957
958 def incoming_entities( text )
958 def incoming_entities( text )
959 ## turn any incoming ampersands into a dummy character for now.
959 ## turn any incoming ampersands into a dummy character for now.
960 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
960 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
961 ## implying an incoming html entity, to be skipped
961 ## implying an incoming html entity, to be skipped
962
962
963 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
963 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
964 end
964 end
965
965
966 def no_textile( text )
966 def no_textile( text )
967 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
967 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
968 '\1<notextile>\2</notextile>\3' )
968 '\1<notextile>\2</notextile>\3' )
969 text.gsub!( /^ *==([^=]+.*?)==/m,
969 text.gsub!( /^ *==([^=]+.*?)==/m,
970 '\1<notextile>\2</notextile>\3' )
970 '\1<notextile>\2</notextile>\3' )
971 end
971 end
972
972
973 def clean_white_space( text )
973 def clean_white_space( text )
974 # normalize line breaks
974 # normalize line breaks
975 text.gsub!( /\r\n/, "\n" )
975 text.gsub!( /\r\n/, "\n" )
976 text.gsub!( /\r/, "\n" )
976 text.gsub!( /\r/, "\n" )
977 text.gsub!( /\t/, ' ' )
977 text.gsub!( /\t/, ' ' )
978 text.gsub!( /^ +$/, '' )
978 text.gsub!( /^ +$/, '' )
979 text.gsub!( /\n{3,}/, "\n\n" )
979 text.gsub!( /\n{3,}/, "\n\n" )
980 text.gsub!( /"$/, "\" " )
980 text.gsub!( /"$/, "\" " )
981
981
982 # if entire document is indented, flush
982 # if entire document is indented, flush
983 # to the left side
983 # to the left side
984 flush_left text
984 flush_left text
985 end
985 end
986
986
987 def flush_left( text )
987 def flush_left( text )
988 indt = 0
988 indt = 0
989 if text =~ /^ /
989 if text =~ /^ /
990 while text !~ /^ {#{indt}}\S/
990 while text !~ /^ {#{indt}}\S/
991 indt += 1
991 indt += 1
992 end unless text.empty?
992 end unless text.empty?
993 if indt.nonzero?
993 if indt.nonzero?
994 text.gsub!( /^ {#{indt}}/, '' )
994 text.gsub!( /^ {#{indt}}/, '' )
995 end
995 end
996 end
996 end
997 end
997 end
998
998
999 def footnote_ref( text )
999 def footnote_ref( text )
1000 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1000 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
1001 '<sup><a href="#fn\1">\1</a></sup>\2' )
1001 '<sup><a href="#fn\1">\1</a></sup>\2' )
1002 end
1002 end
1003
1003
1004 OFFTAGS = /(code|pre|kbd|notextile)/
1004 OFFTAGS = /(code|pre|kbd|notextile)/
1005 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1005 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
1006 OFFTAG_OPEN = /<#{ OFFTAGS }/
1006 OFFTAG_OPEN = /<#{ OFFTAGS }/
1007 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1007 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
1008 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1008 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
1009 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1009 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
1010
1010
1011 def glyphs_textile( text, level = 0 )
1011 def glyphs_textile( text, level = 0 )
1012 if text !~ HASTAG_MATCH
1012 if text !~ HASTAG_MATCH
1013 pgl text
1013 #pgl text
1014 footnote_ref text
1014 footnote_ref text
1015 else
1015 else
1016 codepre = 0
1016 codepre = 0
1017 text.gsub!( ALLTAG_MATCH ) do |line|
1017 text.gsub!( ALLTAG_MATCH ) do |line|
1018 ## matches are off if we're between <code>, <pre> etc.
1018 ## matches are off if we're between <code>, <pre> etc.
1019 if $1
1019 if $1
1020 if line =~ OFFTAG_OPEN
1020 if line =~ OFFTAG_OPEN
1021 codepre += 1
1021 codepre += 1
1022 elsif line =~ OFFTAG_CLOSE
1022 elsif line =~ OFFTAG_CLOSE
1023 codepre -= 1
1023 codepre -= 1
1024 codepre = 0 if codepre < 0
1024 codepre = 0 if codepre < 0
1025 end
1025 end
1026 elsif codepre.zero?
1026 elsif codepre.zero?
1027 glyphs_textile( line, level + 1 )
1027 glyphs_textile( line, level + 1 )
1028 else
1028 else
1029 htmlesc( line, :NoQuotes )
1029 htmlesc( line, :NoQuotes )
1030 end
1030 end
1031 # p [level, codepre, line]
1031 # p [level, codepre, line]
1032
1032
1033 line
1033 line
1034 end
1034 end
1035 end
1035 end
1036 end
1036 end
1037
1037
1038 def rip_offtags( text )
1038 def rip_offtags( text )
1039 if text =~ /<.*>/
1039 if text =~ /<.*>/
1040 ## strip and encode <pre> content
1040 ## strip and encode <pre> content
1041 codepre, used_offtags = 0, {}
1041 codepre, used_offtags = 0, {}
1042 text.gsub!( OFFTAG_MATCH ) do |line|
1042 text.gsub!( OFFTAG_MATCH ) do |line|
1043 if $3
1043 if $3
1044 offtag, aftertag = $4, $5
1044 offtag, aftertag = $4, $5
1045 codepre += 1
1045 codepre += 1
1046 used_offtags[offtag] = true
1046 used_offtags[offtag] = true
1047 if codepre - used_offtags.length > 0
1047 if codepre - used_offtags.length > 0
1048 htmlesc( line, :NoQuotes )
1048 htmlesc( line, :NoQuotes )
1049 @pre_list.last << line
1049 @pre_list.last << line
1050 line = ""
1050 line = ""
1051 else
1051 else
1052 htmlesc( aftertag, :NoQuotes ) if aftertag
1052 htmlesc( aftertag, :NoQuotes ) if aftertag
1053 line = "<redpre##{ @pre_list.length }>"
1053 line = "<redpre##{ @pre_list.length }>"
1054 @pre_list << "#{ $3.gsub(/<(#{ OFFTAGS })[^>]*>/, '<\\1>') }#{ aftertag }"
1054 @pre_list << "#{ $3.gsub(/<(#{ OFFTAGS })[^>]*>/, '<\\1>') }#{ aftertag }"
1055 end
1055 end
1056 elsif $1 and codepre > 0
1056 elsif $1 and codepre > 0
1057 if codepre - used_offtags.length > 0
1057 if codepre - used_offtags.length > 0
1058 htmlesc( line, :NoQuotes )
1058 htmlesc( line, :NoQuotes )
1059 @pre_list.last << line
1059 @pre_list.last << line
1060 line = ""
1060 line = ""
1061 end
1061 end
1062 codepre -= 1 unless codepre.zero?
1062 codepre -= 1 unless codepre.zero?
1063 used_offtags = {} if codepre.zero?
1063 used_offtags = {} if codepre.zero?
1064 end
1064 end
1065 line
1065 line
1066 end
1066 end
1067 end
1067 end
1068 text
1068 text
1069 end
1069 end
1070
1070
1071 def smooth_offtags( text )
1071 def smooth_offtags( text )
1072 unless @pre_list.empty?
1072 unless @pre_list.empty?
1073 ## replace <pre> content
1073 ## replace <pre> content
1074 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1074 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1075 end
1075 end
1076 end
1076 end
1077
1077
1078 def inline( text )
1078 def inline( text )
1079 [/^inline_/, /^glyphs_/].each do |meth_re|
1079 [/^inline_/, /^glyphs_/].each do |meth_re|
1080 @rules.each do |rule_name|
1080 @rules.each do |rule_name|
1081 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1081 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1082 end
1082 end
1083 end
1083 end
1084 end
1084 end
1085
1085
1086 def h_align( text )
1086 def h_align( text )
1087 H_ALGN_VALS[text]
1087 H_ALGN_VALS[text]
1088 end
1088 end
1089
1089
1090 def v_align( text )
1090 def v_align( text )
1091 V_ALGN_VALS[text]
1091 V_ALGN_VALS[text]
1092 end
1092 end
1093
1093
1094 def textile_popup_help( name, windowW, windowH )
1094 def textile_popup_help( name, windowW, windowH )
1095 ' <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 />'
1095 ' <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 />'
1096 end
1096 end
1097
1097
1098 # HTML cleansing stuff
1098 # HTML cleansing stuff
1099 BASIC_TAGS = {
1099 BASIC_TAGS = {
1100 'a' => ['href', 'title'],
1100 'a' => ['href', 'title'],
1101 'img' => ['src', 'alt', 'title'],
1101 'img' => ['src', 'alt', 'title'],
1102 'br' => [],
1102 'br' => [],
1103 'i' => nil,
1103 'i' => nil,
1104 'u' => nil,
1104 'u' => nil,
1105 'b' => nil,
1105 'b' => nil,
1106 'pre' => nil,
1106 'pre' => nil,
1107 'kbd' => nil,
1107 'kbd' => nil,
1108 'code' => ['lang'],
1108 'code' => ['lang'],
1109 'cite' => nil,
1109 'cite' => nil,
1110 'strong' => nil,
1110 'strong' => nil,
1111 'em' => nil,
1111 'em' => nil,
1112 'ins' => nil,
1112 'ins' => nil,
1113 'sup' => nil,
1113 'sup' => nil,
1114 'sub' => nil,
1114 'sub' => nil,
1115 'del' => nil,
1115 'del' => nil,
1116 'table' => nil,
1116 'table' => nil,
1117 'tr' => nil,
1117 'tr' => nil,
1118 'td' => ['colspan', 'rowspan'],
1118 'td' => ['colspan', 'rowspan'],
1119 'th' => nil,
1119 'th' => nil,
1120 'ol' => nil,
1120 'ol' => nil,
1121 'ul' => nil,
1121 'ul' => nil,
1122 'li' => nil,
1122 'li' => nil,
1123 'p' => nil,
1123 'p' => nil,
1124 'h1' => nil,
1124 'h1' => nil,
1125 'h2' => nil,
1125 'h2' => nil,
1126 'h3' => nil,
1126 'h3' => nil,
1127 'h4' => nil,
1127 'h4' => nil,
1128 'h5' => nil,
1128 'h5' => nil,
1129 'h6' => nil,
1129 'h6' => nil,
1130 'blockquote' => ['cite']
1130 'blockquote' => ['cite']
1131 }
1131 }
1132
1132
1133 def clean_html( text, tags = BASIC_TAGS )
1133 def clean_html( text, tags = BASIC_TAGS )
1134 text.gsub!( /<!\[CDATA\[/, '' )
1134 text.gsub!( /<!\[CDATA\[/, '' )
1135 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1135 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1136 raw = $~
1136 raw = $~
1137 tag = raw[2].downcase
1137 tag = raw[2].downcase
1138 if tags.has_key? tag
1138 if tags.has_key? tag
1139 pcs = [tag]
1139 pcs = [tag]
1140 tags[tag].each do |prop|
1140 tags[tag].each do |prop|
1141 ['"', "'", ''].each do |q|
1141 ['"', "'", ''].each do |q|
1142 q2 = ( q != '' ? q : '\s' )
1142 q2 = ( q != '' ? q : '\s' )
1143 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1143 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1144 attrv = $1
1144 attrv = $1
1145 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1145 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1146 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1146 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1147 break
1147 break
1148 end
1148 end
1149 end
1149 end
1150 end if tags[tag]
1150 end if tags[tag]
1151 "<#{raw[1]}#{pcs.join " "}>"
1151 "<#{raw[1]}#{pcs.join " "}>"
1152 else
1152 else
1153 " "
1153 " "
1154 end
1154 end
1155 end
1155 end
1156 end
1156 end
1157
1157
1158 ALLOWED_TAGS = %w(redpre pre code notextile)
1158 ALLOWED_TAGS = %w(redpre pre code notextile)
1159
1159
1160 def escape_html_tags(text)
1160 def escape_html_tags(text)
1161 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1161 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1162 end
1162 end
1163 end
1163 end
1164
1164
@@ -1,621 +1,623
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2
2
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 h1 {margin:0; padding:0; font-size: 24px;}
4 h1 {margin:0; padding:0; font-size: 24px;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
5 h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
6 h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
7 h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;}
8
8
9 /***** Layout *****/
9 /***** Layout *****/
10 #wrapper {background: white;}
10 #wrapper {background: white;}
11
11
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 #top-menu ul {margin: 0; padding: 0;}
13 #top-menu ul {margin: 0; padding: 0;}
14 #top-menu li {
14 #top-menu li {
15 float:left;
15 float:left;
16 list-style-type:none;
16 list-style-type:none;
17 margin: 0px 0px 0px 0px;
17 margin: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
18 padding: 0px 0px 0px 0px;
19 white-space:nowrap;
19 white-space:nowrap;
20 }
20 }
21 #top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
21 #top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23
23
24 #account {float:right;}
24 #account {float:right;}
25
25
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 #header a {color:#f8f8f8;}
27 #header a {color:#f8f8f8;}
28 #quick-search {float:right;}
28 #quick-search {float:right;}
29
29
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
31 #main-menu ul {margin: 0; padding: 0;}
31 #main-menu ul {margin: 0; padding: 0;}
32 #main-menu li {
32 #main-menu li {
33 float:left;
33 float:left;
34 list-style-type:none;
34 list-style-type:none;
35 margin: 0px 2px 0px 0px;
35 margin: 0px 2px 0px 0px;
36 padding: 0px 0px 0px 0px;
36 padding: 0px 0px 0px 0px;
37 white-space:nowrap;
37 white-space:nowrap;
38 }
38 }
39 #main-menu li a {
39 #main-menu li a {
40 display: block;
40 display: block;
41 color: #fff;
41 color: #fff;
42 text-decoration: none;
42 text-decoration: none;
43 font-weight: bold;
43 font-weight: bold;
44 margin: 0;
44 margin: 0;
45 padding: 4px 10px 4px 10px;
45 padding: 4px 10px 4px 10px;
46 }
46 }
47 #main-menu li a:hover {background:#759FCF; color:#fff;}
47 #main-menu li a:hover {background:#759FCF; color:#fff;}
48 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
48 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
49
49
50 #main {background-color:#EEEEEE;}
50 #main {background-color:#EEEEEE;}
51
51
52 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
52 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
53 * html #sidebar{ width: 17%; }
53 * html #sidebar{ width: 17%; }
54 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
54 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
55 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
55 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
56 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
56 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
57
57
58 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
58 #content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; height:600px; min-height: 600px;}
59 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
59 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
60 html>body #content { height: auto; min-height: 600px; overflow: auto; }
60 html>body #content { height: auto; min-height: 600px; overflow: auto; }
61
61
62 #main.nosidebar #sidebar{ display: none; }
62 #main.nosidebar #sidebar{ display: none; }
63 #main.nosidebar #content{ width: auto; border-right: 0; }
63 #main.nosidebar #content{ width: auto; border-right: 0; }
64
64
65 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
65 #footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;}
66
66
67 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
67 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
68 #login-form table td {padding: 6px;}
68 #login-form table td {padding: 6px;}
69 #login-form label {font-weight: bold;}
69 #login-form label {font-weight: bold;}
70
70
71 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
71 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
72
72
73 /***** Links *****/
73 /***** Links *****/
74 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
74 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
75 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
75 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
76 a img{ border: 0; }
76 a img{ border: 0; }
77
77
78 a.issue.closed { text-decoration: line-through; }
78 a.issue.closed { text-decoration: line-through; }
79
79
80 /***** Tables *****/
80 /***** Tables *****/
81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
83 table.list td { vertical-align: top; }
83 table.list td { vertical-align: top; }
84 table.list td.id { width: 2%; text-align: center;}
84 table.list td.id { width: 2%; text-align: center;}
85 table.list td.checkbox { width: 15px; padding: 0px;}
85 table.list td.checkbox { width: 15px; padding: 0px;}
86
86
87 tr.issue { text-align: center; white-space: nowrap; }
87 tr.issue { text-align: center; white-space: nowrap; }
88 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
88 tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; }
89 tr.issue td.subject { text-align: left; }
89 tr.issue td.subject { text-align: left; }
90 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
90 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
91
91
92 tr.entry { border: 1px solid #f8f8f8; }
92 tr.entry { border: 1px solid #f8f8f8; }
93 tr.entry td { white-space: nowrap; }
93 tr.entry td { white-space: nowrap; }
94 tr.entry td.filename { width: 30%; }
94 tr.entry td.filename { width: 30%; }
95 tr.entry td.size { text-align: right; font-size: 90%; }
95 tr.entry td.size { text-align: right; font-size: 90%; }
96 tr.entry td.revision, tr.entry td.author { text-align: center; }
96 tr.entry td.revision, tr.entry td.author { text-align: center; }
97 tr.entry td.age { text-align: right; }
97 tr.entry td.age { text-align: right; }
98
98
99 tr.entry span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
99 tr.entry span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
100 tr.entry.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
100 tr.entry.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
101 tr.entry.file td.filename a { margin-left: 16px; }
101 tr.entry.file td.filename a { margin-left: 16px; }
102
102
103 tr.changeset td.author { text-align: center; width: 15%; }
103 tr.changeset td.author { text-align: center; width: 15%; }
104 tr.changeset td.committed_on { text-align: center; width: 15%; }
104 tr.changeset td.committed_on { text-align: center; width: 15%; }
105
105
106 tr.message { height: 2.6em; }
106 tr.message { height: 2.6em; }
107 tr.message td.last_message { font-size: 80%; }
107 tr.message td.last_message { font-size: 80%; }
108 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
108 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
109 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
109 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
110
110
111 tr.user td { width:13%; }
111 tr.user td { width:13%; }
112 tr.user td.email { width:18%; }
112 tr.user td.email { width:18%; }
113 tr.user td { white-space: nowrap; }
113 tr.user td { white-space: nowrap; }
114 tr.user.locked, tr.user.registered { color: #aaa; }
114 tr.user.locked, tr.user.registered { color: #aaa; }
115 tr.user.locked a, tr.user.registered a { color: #aaa; }
115 tr.user.locked a, tr.user.registered a { color: #aaa; }
116
116
117 tr.time-entry { text-align: center; white-space: nowrap; }
117 tr.time-entry { text-align: center; white-space: nowrap; }
118 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
118 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
119 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
119 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
120 td.hours .hours-dec { font-size: 0.9em; }
120 td.hours .hours-dec { font-size: 0.9em; }
121
121
122 table.list tbody tr:hover { background-color:#ffffdd; }
122 table.list tbody tr:hover { background-color:#ffffdd; }
123 table td {padding:2px;}
123 table td {padding:2px;}
124 table p {margin:0;}
124 table p {margin:0;}
125 .odd {background-color:#f6f7f8;}
125 .odd {background-color:#f6f7f8;}
126 .even {background-color: #fff;}
126 .even {background-color: #fff;}
127
127
128 .highlight { background-color: #FCFD8D;}
128 .highlight { background-color: #FCFD8D;}
129 .highlight.token-1 { background-color: #faa;}
129 .highlight.token-1 { background-color: #faa;}
130 .highlight.token-2 { background-color: #afa;}
130 .highlight.token-2 { background-color: #afa;}
131 .highlight.token-3 { background-color: #aaf;}
131 .highlight.token-3 { background-color: #aaf;}
132
132
133 .box{
133 .box{
134 padding:6px;
134 padding:6px;
135 margin-bottom: 10px;
135 margin-bottom: 10px;
136 background-color:#f6f6f6;
136 background-color:#f6f6f6;
137 color:#505050;
137 color:#505050;
138 line-height:1.5em;
138 line-height:1.5em;
139 border: 1px solid #e4e4e4;
139 border: 1px solid #e4e4e4;
140 }
140 }
141
141
142 div.square {
142 div.square {
143 border: 1px solid #999;
143 border: 1px solid #999;
144 float: left;
144 float: left;
145 margin: .3em .4em 0 .4em;
145 margin: .3em .4em 0 .4em;
146 overflow: hidden;
146 overflow: hidden;
147 width: .6em; height: .6em;
147 width: .6em; height: .6em;
148 }
148 }
149 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
149 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
150 .contextual input {font-size:0.9em;}
150 .contextual input {font-size:0.9em;}
151
151
152 .splitcontentleft{float:left; width:49%;}
152 .splitcontentleft{float:left; width:49%;}
153 .splitcontentright{float:right; width:49%;}
153 .splitcontentright{float:right; width:49%;}
154 form {display: inline;}
154 form {display: inline;}
155 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
155 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
156 fieldset {border: 1px solid #e4e4e4; margin:0;}
156 fieldset {border: 1px solid #e4e4e4; margin:0;}
157 legend {color: #484848;}
157 legend {color: #484848;}
158 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
158 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
159 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
159 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
160 blockquote blockquote { margin-left: 0;}
160 blockquote blockquote { margin-left: 0;}
161 textarea.wiki-edit { width: 99%; }
161 textarea.wiki-edit { width: 99%; }
162 li p {margin-top: 0;}
162 li p {margin-top: 0;}
163 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
163 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
164 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
164 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
165 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
165 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
166 p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; }
166
167
167 fieldset#filters, fieldset#date-range { padding: 0.7em; margin-bottom: 8px; }
168 fieldset#filters, fieldset#date-range { padding: 0.7em; margin-bottom: 8px; }
168 fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
169 fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
169 fieldset#filters table { border-collapse: collapse; }
170 fieldset#filters table { border-collapse: collapse; }
170 fieldset#filters table td { padding: 0; vertical-align: middle; }
171 fieldset#filters table td { padding: 0; vertical-align: middle; }
171 fieldset#filters tr.filter { height: 2em; }
172 fieldset#filters tr.filter { height: 2em; }
172 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
173 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
173 .buttons { font-size: 0.9em; }
174 .buttons { font-size: 0.9em; }
174
175
175 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
176 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
176 div#issue-changesets .changeset { padding: 4px;}
177 div#issue-changesets .changeset { padding: 4px;}
177 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
178 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
178 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
179 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
179
180
180 div#activity dl, #search-results { margin-left: 2em; }
181 div#activity dl, #search-results { margin-left: 2em; }
181 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
182 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
182 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
183 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
183 div#activity dt.me .time { border-bottom: 1px solid #999; }
184 div#activity dt.me .time { border-bottom: 1px solid #999; }
184 div#activity dt .time { color: #777; font-size: 80%; }
185 div#activity dt .time { color: #777; font-size: 80%; }
185 div#activity dd .description, #search-results dd .description { font-style: italic; }
186 div#activity dd .description, #search-results dd .description { font-style: italic; }
186 div#activity span.project:after, #search-results span.project:after { content: " -"; }
187 div#activity span.project:after, #search-results span.project:after { content: " -"; }
187 div#activity dd span.description, #search-results dd span.description { display:block; }
188 div#activity dd span.description, #search-results dd span.description { display:block; }
188
189
189 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
190 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
190 div#search-results-counts {float:right;}
191 div#search-results-counts {float:right;}
191 div#search-results-counts ul { margin-top: 0.5em; }
192 div#search-results-counts ul { margin-top: 0.5em; }
192 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
193 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
193
194
194 dt.issue { background-image: url(../images/ticket.png); }
195 dt.issue { background-image: url(../images/ticket.png); }
195 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
196 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
196 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
197 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
197 dt.issue-note { background-image: url(../images/ticket_note.png); }
198 dt.issue-note { background-image: url(../images/ticket_note.png); }
198 dt.changeset { background-image: url(../images/changeset.png); }
199 dt.changeset { background-image: url(../images/changeset.png); }
199 dt.news { background-image: url(../images/news.png); }
200 dt.news { background-image: url(../images/news.png); }
200 dt.message { background-image: url(../images/message.png); }
201 dt.message { background-image: url(../images/message.png); }
201 dt.reply { background-image: url(../images/comments.png); }
202 dt.reply { background-image: url(../images/comments.png); }
202 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
203 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
203 dt.attachment { background-image: url(../images/attachment.png); }
204 dt.attachment { background-image: url(../images/attachment.png); }
204 dt.document { background-image: url(../images/document.png); }
205 dt.document { background-image: url(../images/document.png); }
205 dt.project { background-image: url(../images/projects.png); }
206 dt.project { background-image: url(../images/projects.png); }
206
207
207 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
208 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
208 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
209 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
209 div#roadmap .wiki h1:first-child { display: none; }
210 div#roadmap .wiki h1:first-child { display: none; }
210 div#roadmap .wiki h1 { font-size: 120%; }
211 div#roadmap .wiki h1 { font-size: 120%; }
211 div#roadmap .wiki h2 { font-size: 110%; }
212 div#roadmap .wiki h2 { font-size: 110%; }
212
213
213 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
214 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
214 div#version-summary fieldset { margin-bottom: 1em; }
215 div#version-summary fieldset { margin-bottom: 1em; }
215 div#version-summary .total-hours { text-align: right; }
216 div#version-summary .total-hours { text-align: right; }
216
217
217 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
218 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
218 table#time-report tbody tr { font-style: italic; color: #777; }
219 table#time-report tbody tr { font-style: italic; color: #777; }
219 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
220 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
220 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
221 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
221 table#time-report .hours-dec { font-size: 0.9em; }
222 table#time-report .hours-dec { font-size: 0.9em; }
222
223
223 ul.properties {padding:0; font-size: 0.9em; color: #777;}
224 ul.properties {padding:0; font-size: 0.9em; color: #777;}
224 ul.properties li {list-style-type:none;}
225 ul.properties li {list-style-type:none;}
225 ul.properties li span {font-style:italic;}
226 ul.properties li span {font-style:italic;}
226
227
227 .total-hours { font-size: 110%; font-weight: bold; }
228 .total-hours { font-size: 110%; font-weight: bold; }
228 .total-hours span.hours-int { font-size: 120%; }
229 .total-hours span.hours-int { font-size: 120%; }
229
230
230 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
231 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
231 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
232 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
232
233
233 .pagination {font-size: 90%}
234 .pagination {font-size: 90%}
234 p.pagination {margin-top:8px;}
235 p.pagination {margin-top:8px;}
235
236
236 /***** Tabular forms ******/
237 /***** Tabular forms ******/
237 .tabular p{
238 .tabular p{
238 margin: 0;
239 margin: 0;
239 padding: 5px 0 8px 0;
240 padding: 5px 0 8px 0;
240 padding-left: 180px; /*width of left column containing the label elements*/
241 padding-left: 180px; /*width of left column containing the label elements*/
241 height: 1%;
242 height: 1%;
242 clear:left;
243 clear:left;
243 }
244 }
244
245
245 html>body .tabular p {overflow:hidden;}
246 html>body .tabular p {overflow:hidden;}
246
247
247 .tabular label{
248 .tabular label{
248 font-weight: bold;
249 font-weight: bold;
249 float: left;
250 float: left;
250 text-align: right;
251 text-align: right;
251 margin-left: -180px; /*width of left column*/
252 margin-left: -180px; /*width of left column*/
252 width: 175px; /*width of labels. Should be smaller than left column to create some right
253 width: 175px; /*width of labels. Should be smaller than left column to create some right
253 margin*/
254 margin*/
254 }
255 }
255
256
256 .tabular label.floating{
257 .tabular label.floating{
257 font-weight: normal;
258 font-weight: normal;
258 margin-left: 0px;
259 margin-left: 0px;
259 text-align: left;
260 text-align: left;
260 width: 200px;
261 width: 200px;
261 }
262 }
262
263
263 input#time_entry_comments { width: 90%;}
264 input#time_entry_comments { width: 90%;}
264
265
265 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
266 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
266
267
267 .tabular.settings p{ padding-left: 300px; }
268 .tabular.settings p{ padding-left: 300px; }
268 .tabular.settings label{ margin-left: -300px; width: 295px; }
269 .tabular.settings label{ margin-left: -300px; width: 295px; }
269
270
270 .required {color: #bb0000;}
271 .required {color: #bb0000;}
271 .summary {font-style: italic;}
272 .summary {font-style: italic;}
272
273
273 #attachments_fields input[type=text] {margin-left: 8px; }
274 #attachments_fields input[type=text] {margin-left: 8px; }
274
275
276 div.attachments { margin-top: 12px; }
275 div.attachments p { margin:4px 0 2px 0; }
277 div.attachments p { margin:4px 0 2px 0; }
276 div.attachments img { vertical-align: middle; }
278 div.attachments img { vertical-align: middle; }
277 div.attachments span.author { font-size: 0.9em; color: #888; }
279 div.attachments span.author { font-size: 0.9em; color: #888; }
278
280
279 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
281 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
280 .other-formats span + span:before { content: "| "; }
282 .other-formats span + span:before { content: "| "; }
281
283
282 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
284 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
283
285
284 /***** Flash & error messages ****/
286 /***** Flash & error messages ****/
285 #errorExplanation, div.flash, .nodata, .warning {
287 #errorExplanation, div.flash, .nodata, .warning {
286 padding: 4px 4px 4px 30px;
288 padding: 4px 4px 4px 30px;
287 margin-bottom: 12px;
289 margin-bottom: 12px;
288 font-size: 1.1em;
290 font-size: 1.1em;
289 border: 2px solid;
291 border: 2px solid;
290 }
292 }
291
293
292 div.flash {margin-top: 8px;}
294 div.flash {margin-top: 8px;}
293
295
294 div.flash.error, #errorExplanation {
296 div.flash.error, #errorExplanation {
295 background: url(../images/false.png) 8px 5px no-repeat;
297 background: url(../images/false.png) 8px 5px no-repeat;
296 background-color: #ffe3e3;
298 background-color: #ffe3e3;
297 border-color: #dd0000;
299 border-color: #dd0000;
298 color: #550000;
300 color: #550000;
299 }
301 }
300
302
301 div.flash.notice {
303 div.flash.notice {
302 background: url(../images/true.png) 8px 5px no-repeat;
304 background: url(../images/true.png) 8px 5px no-repeat;
303 background-color: #dfffdf;
305 background-color: #dfffdf;
304 border-color: #9fcf9f;
306 border-color: #9fcf9f;
305 color: #005f00;
307 color: #005f00;
306 }
308 }
307
309
308 .nodata, .warning {
310 .nodata, .warning {
309 text-align: center;
311 text-align: center;
310 background-color: #FFEBC1;
312 background-color: #FFEBC1;
311 border-color: #FDBF3B;
313 border-color: #FDBF3B;
312 color: #A6750C;
314 color: #A6750C;
313 }
315 }
314
316
315 #errorExplanation ul { font-size: 0.9em;}
317 #errorExplanation ul { font-size: 0.9em;}
316
318
317 /***** Ajax indicator ******/
319 /***** Ajax indicator ******/
318 #ajax-indicator {
320 #ajax-indicator {
319 position: absolute; /* fixed not supported by IE */
321 position: absolute; /* fixed not supported by IE */
320 background-color:#eee;
322 background-color:#eee;
321 border: 1px solid #bbb;
323 border: 1px solid #bbb;
322 top:35%;
324 top:35%;
323 left:40%;
325 left:40%;
324 width:20%;
326 width:20%;
325 font-weight:bold;
327 font-weight:bold;
326 text-align:center;
328 text-align:center;
327 padding:0.6em;
329 padding:0.6em;
328 z-index:100;
330 z-index:100;
329 filter:alpha(opacity=50);
331 filter:alpha(opacity=50);
330 opacity: 0.5;
332 opacity: 0.5;
331 }
333 }
332
334
333 html>body #ajax-indicator { position: fixed; }
335 html>body #ajax-indicator { position: fixed; }
334
336
335 #ajax-indicator span {
337 #ajax-indicator span {
336 background-position: 0% 40%;
338 background-position: 0% 40%;
337 background-repeat: no-repeat;
339 background-repeat: no-repeat;
338 background-image: url(../images/loading.gif);
340 background-image: url(../images/loading.gif);
339 padding-left: 26px;
341 padding-left: 26px;
340 vertical-align: bottom;
342 vertical-align: bottom;
341 }
343 }
342
344
343 /***** Calendar *****/
345 /***** Calendar *****/
344 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
346 table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;}
345 table.cal thead th {width: 14%;}
347 table.cal thead th {width: 14%;}
346 table.cal tbody tr {height: 100px;}
348 table.cal tbody tr {height: 100px;}
347 table.cal th { background-color:#EEEEEE; padding: 4px; }
349 table.cal th { background-color:#EEEEEE; padding: 4px; }
348 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
350 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
349 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
351 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
350 table.cal td.odd p.day-num {color: #bbb;}
352 table.cal td.odd p.day-num {color: #bbb;}
351 table.cal td.today {background:#ffffdd;}
353 table.cal td.today {background:#ffffdd;}
352 table.cal td.today p.day-num {font-weight: bold;}
354 table.cal td.today p.day-num {font-weight: bold;}
353
355
354 /***** Tooltips ******/
356 /***** Tooltips ******/
355 .tooltip{position:relative;z-index:24;}
357 .tooltip{position:relative;z-index:24;}
356 .tooltip:hover{z-index:25;color:#000;}
358 .tooltip:hover{z-index:25;color:#000;}
357 .tooltip span.tip{display: none; text-align:left;}
359 .tooltip span.tip{display: none; text-align:left;}
358
360
359 div.tooltip:hover span.tip{
361 div.tooltip:hover span.tip{
360 display:block;
362 display:block;
361 position:absolute;
363 position:absolute;
362 top:12px; left:24px; width:270px;
364 top:12px; left:24px; width:270px;
363 border:1px solid #555;
365 border:1px solid #555;
364 background-color:#fff;
366 background-color:#fff;
365 padding: 4px;
367 padding: 4px;
366 font-size: 0.8em;
368 font-size: 0.8em;
367 color:#505050;
369 color:#505050;
368 }
370 }
369
371
370 /***** Progress bar *****/
372 /***** Progress bar *****/
371 table.progress {
373 table.progress {
372 border: 1px solid #D7D7D7;
374 border: 1px solid #D7D7D7;
373 border-collapse: collapse;
375 border-collapse: collapse;
374 border-spacing: 0pt;
376 border-spacing: 0pt;
375 empty-cells: show;
377 empty-cells: show;
376 text-align: center;
378 text-align: center;
377 float:left;
379 float:left;
378 margin: 1px 6px 1px 0px;
380 margin: 1px 6px 1px 0px;
379 }
381 }
380
382
381 table.progress td { height: 0.9em; }
383 table.progress td { height: 0.9em; }
382 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
384 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
383 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
385 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
384 table.progress td.open { background: #FFF none repeat scroll 0%; }
386 table.progress td.open { background: #FFF none repeat scroll 0%; }
385 p.pourcent {font-size: 80%;}
387 p.pourcent {font-size: 80%;}
386 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
388 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
387
389
388 /***** Tabs *****/
390 /***** Tabs *****/
389 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
391 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
390 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
392 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
391 #content .tabs>ul { bottom:-1px; } /* others */
393 #content .tabs>ul { bottom:-1px; } /* others */
392 #content .tabs ul li {
394 #content .tabs ul li {
393 float:left;
395 float:left;
394 list-style-type:none;
396 list-style-type:none;
395 white-space:nowrap;
397 white-space:nowrap;
396 margin-right:8px;
398 margin-right:8px;
397 background:#fff;
399 background:#fff;
398 }
400 }
399 #content .tabs ul li a{
401 #content .tabs ul li a{
400 display:block;
402 display:block;
401 font-size: 0.9em;
403 font-size: 0.9em;
402 text-decoration:none;
404 text-decoration:none;
403 line-height:1.3em;
405 line-height:1.3em;
404 padding:4px 6px 4px 6px;
406 padding:4px 6px 4px 6px;
405 border: 1px solid #ccc;
407 border: 1px solid #ccc;
406 border-bottom: 1px solid #bbbbbb;
408 border-bottom: 1px solid #bbbbbb;
407 background-color: #eeeeee;
409 background-color: #eeeeee;
408 color:#777;
410 color:#777;
409 font-weight:bold;
411 font-weight:bold;
410 }
412 }
411
413
412 #content .tabs ul li a:hover {
414 #content .tabs ul li a:hover {
413 background-color: #ffffdd;
415 background-color: #ffffdd;
414 text-decoration:none;
416 text-decoration:none;
415 }
417 }
416
418
417 #content .tabs ul li a.selected {
419 #content .tabs ul li a.selected {
418 background-color: #fff;
420 background-color: #fff;
419 border: 1px solid #bbbbbb;
421 border: 1px solid #bbbbbb;
420 border-bottom: 1px solid #fff;
422 border-bottom: 1px solid #fff;
421 }
423 }
422
424
423 #content .tabs ul li a.selected:hover {
425 #content .tabs ul li a.selected:hover {
424 background-color: #fff;
426 background-color: #fff;
425 }
427 }
426
428
427 /***** Diff *****/
429 /***** Diff *****/
428 .diff_out { background: #fcc; }
430 .diff_out { background: #fcc; }
429 .diff_in { background: #cfc; }
431 .diff_in { background: #cfc; }
430
432
431 /***** Wiki *****/
433 /***** Wiki *****/
432 div.wiki table {
434 div.wiki table {
433 border: 1px solid #505050;
435 border: 1px solid #505050;
434 border-collapse: collapse;
436 border-collapse: collapse;
435 margin-bottom: 1em;
437 margin-bottom: 1em;
436 }
438 }
437
439
438 div.wiki table, div.wiki td, div.wiki th {
440 div.wiki table, div.wiki td, div.wiki th {
439 border: 1px solid #bbb;
441 border: 1px solid #bbb;
440 padding: 4px;
442 padding: 4px;
441 }
443 }
442
444
443 div.wiki .external {
445 div.wiki .external {
444 background-position: 0% 60%;
446 background-position: 0% 60%;
445 background-repeat: no-repeat;
447 background-repeat: no-repeat;
446 padding-left: 12px;
448 padding-left: 12px;
447 background-image: url(../images/external.png);
449 background-image: url(../images/external.png);
448 }
450 }
449
451
450 div.wiki a.new {
452 div.wiki a.new {
451 color: #b73535;
453 color: #b73535;
452 }
454 }
453
455
454 div.wiki pre {
456 div.wiki pre {
455 margin: 1em 1em 1em 1.6em;
457 margin: 1em 1em 1em 1.6em;
456 padding: 2px;
458 padding: 2px;
457 background-color: #fafafa;
459 background-color: #fafafa;
458 border: 1px solid #dadada;
460 border: 1px solid #dadada;
459 width:95%;
461 width:95%;
460 overflow-x: auto;
462 overflow-x: auto;
461 }
463 }
462
464
463 div.wiki ul.toc {
465 div.wiki ul.toc {
464 background-color: #ffffdd;
466 background-color: #ffffdd;
465 border: 1px solid #e4e4e4;
467 border: 1px solid #e4e4e4;
466 padding: 4px;
468 padding: 4px;
467 line-height: 1.2em;
469 line-height: 1.2em;
468 margin-bottom: 12px;
470 margin-bottom: 12px;
469 margin-right: 12px;
471 margin-right: 12px;
470 margin-left: 0;
472 margin-left: 0;
471 display: table
473 display: table
472 }
474 }
473 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
475 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
474
476
475 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
477 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
476 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
478 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
477 div.wiki ul.toc li { list-style-type:none;}
479 div.wiki ul.toc li { list-style-type:none;}
478 div.wiki ul.toc li.heading2 { margin-left: 6px; }
480 div.wiki ul.toc li.heading2 { margin-left: 6px; }
479 div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; }
481 div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; }
480
482
481 div.wiki ul.toc a {
483 div.wiki ul.toc a {
482 font-size: 0.9em;
484 font-size: 0.9em;
483 font-weight: normal;
485 font-weight: normal;
484 text-decoration: none;
486 text-decoration: none;
485 color: #606060;
487 color: #606060;
486 }
488 }
487 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
489 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
488
490
489 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
491 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
490 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
492 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
491 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
493 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
492
494
493 /***** My page layout *****/
495 /***** My page layout *****/
494 .block-receiver {
496 .block-receiver {
495 border:1px dashed #c0c0c0;
497 border:1px dashed #c0c0c0;
496 margin-bottom: 20px;
498 margin-bottom: 20px;
497 padding: 15px 0 15px 0;
499 padding: 15px 0 15px 0;
498 }
500 }
499
501
500 .mypage-box {
502 .mypage-box {
501 margin:0 0 20px 0;
503 margin:0 0 20px 0;
502 color:#505050;
504 color:#505050;
503 line-height:1.5em;
505 line-height:1.5em;
504 }
506 }
505
507
506 .handle {
508 .handle {
507 cursor: move;
509 cursor: move;
508 }
510 }
509
511
510 a.close-icon {
512 a.close-icon {
511 display:block;
513 display:block;
512 margin-top:3px;
514 margin-top:3px;
513 overflow:hidden;
515 overflow:hidden;
514 width:12px;
516 width:12px;
515 height:12px;
517 height:12px;
516 background-repeat: no-repeat;
518 background-repeat: no-repeat;
517 cursor:pointer;
519 cursor:pointer;
518 background-image:url('../images/close.png');
520 background-image:url('../images/close.png');
519 }
521 }
520
522
521 a.close-icon:hover {
523 a.close-icon:hover {
522 background-image:url('../images/close_hl.png');
524 background-image:url('../images/close_hl.png');
523 }
525 }
524
526
525 /***** Gantt chart *****/
527 /***** Gantt chart *****/
526 .gantt_hdr {
528 .gantt_hdr {
527 position:absolute;
529 position:absolute;
528 top:0;
530 top:0;
529 height:16px;
531 height:16px;
530 border-top: 1px solid #c0c0c0;
532 border-top: 1px solid #c0c0c0;
531 border-bottom: 1px solid #c0c0c0;
533 border-bottom: 1px solid #c0c0c0;
532 border-right: 1px solid #c0c0c0;
534 border-right: 1px solid #c0c0c0;
533 text-align: center;
535 text-align: center;
534 overflow: hidden;
536 overflow: hidden;
535 }
537 }
536
538
537 .task {
539 .task {
538 position: absolute;
540 position: absolute;
539 height:8px;
541 height:8px;
540 font-size:0.8em;
542 font-size:0.8em;
541 color:#888;
543 color:#888;
542 padding:0;
544 padding:0;
543 margin:0;
545 margin:0;
544 line-height:0.8em;
546 line-height:0.8em;
545 }
547 }
546
548
547 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
549 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
548 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
550 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
549 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
551 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
550 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
552 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
551
553
552 /***** Icons *****/
554 /***** Icons *****/
553 .icon {
555 .icon {
554 background-position: 0% 40%;
556 background-position: 0% 40%;
555 background-repeat: no-repeat;
557 background-repeat: no-repeat;
556 padding-left: 20px;
558 padding-left: 20px;
557 padding-top: 2px;
559 padding-top: 2px;
558 padding-bottom: 3px;
560 padding-bottom: 3px;
559 }
561 }
560
562
561 .icon22 {
563 .icon22 {
562 background-position: 0% 40%;
564 background-position: 0% 40%;
563 background-repeat: no-repeat;
565 background-repeat: no-repeat;
564 padding-left: 26px;
566 padding-left: 26px;
565 line-height: 22px;
567 line-height: 22px;
566 vertical-align: middle;
568 vertical-align: middle;
567 }
569 }
568
570
569 .icon-add { background-image: url(../images/add.png); }
571 .icon-add { background-image: url(../images/add.png); }
570 .icon-edit { background-image: url(../images/edit.png); }
572 .icon-edit { background-image: url(../images/edit.png); }
571 .icon-copy { background-image: url(../images/copy.png); }
573 .icon-copy { background-image: url(../images/copy.png); }
572 .icon-del { background-image: url(../images/delete.png); }
574 .icon-del { background-image: url(../images/delete.png); }
573 .icon-move { background-image: url(../images/move.png); }
575 .icon-move { background-image: url(../images/move.png); }
574 .icon-save { background-image: url(../images/save.png); }
576 .icon-save { background-image: url(../images/save.png); }
575 .icon-cancel { background-image: url(../images/cancel.png); }
577 .icon-cancel { background-image: url(../images/cancel.png); }
576 .icon-file { background-image: url(../images/file.png); }
578 .icon-file { background-image: url(../images/file.png); }
577 .icon-folder { background-image: url(../images/folder.png); }
579 .icon-folder { background-image: url(../images/folder.png); }
578 .open .icon-folder { background-image: url(../images/folder_open.png); }
580 .open .icon-folder { background-image: url(../images/folder_open.png); }
579 .icon-package { background-image: url(../images/package.png); }
581 .icon-package { background-image: url(../images/package.png); }
580 .icon-home { background-image: url(../images/home.png); }
582 .icon-home { background-image: url(../images/home.png); }
581 .icon-user { background-image: url(../images/user.png); }
583 .icon-user { background-image: url(../images/user.png); }
582 .icon-mypage { background-image: url(../images/user_page.png); }
584 .icon-mypage { background-image: url(../images/user_page.png); }
583 .icon-admin { background-image: url(../images/admin.png); }
585 .icon-admin { background-image: url(../images/admin.png); }
584 .icon-projects { background-image: url(../images/projects.png); }
586 .icon-projects { background-image: url(../images/projects.png); }
585 .icon-help { background-image: url(../images/help.png); }
587 .icon-help { background-image: url(../images/help.png); }
586 .icon-attachment { background-image: url(../images/attachment.png); }
588 .icon-attachment { background-image: url(../images/attachment.png); }
587 .icon-index { background-image: url(../images/index.png); }
589 .icon-index { background-image: url(../images/index.png); }
588 .icon-history { background-image: url(../images/history.png); }
590 .icon-history { background-image: url(../images/history.png); }
589 .icon-time { background-image: url(../images/time.png); }
591 .icon-time { background-image: url(../images/time.png); }
590 .icon-stats { background-image: url(../images/stats.png); }
592 .icon-stats { background-image: url(../images/stats.png); }
591 .icon-warning { background-image: url(../images/warning.png); }
593 .icon-warning { background-image: url(../images/warning.png); }
592 .icon-fav { background-image: url(../images/fav.png); }
594 .icon-fav { background-image: url(../images/fav.png); }
593 .icon-fav-off { background-image: url(../images/fav_off.png); }
595 .icon-fav-off { background-image: url(../images/fav_off.png); }
594 .icon-reload { background-image: url(../images/reload.png); }
596 .icon-reload { background-image: url(../images/reload.png); }
595 .icon-lock { background-image: url(../images/locked.png); }
597 .icon-lock { background-image: url(../images/locked.png); }
596 .icon-unlock { background-image: url(../images/unlock.png); }
598 .icon-unlock { background-image: url(../images/unlock.png); }
597 .icon-checked { background-image: url(../images/true.png); }
599 .icon-checked { background-image: url(../images/true.png); }
598 .icon-details { background-image: url(../images/zoom_in.png); }
600 .icon-details { background-image: url(../images/zoom_in.png); }
599 .icon-report { background-image: url(../images/report.png); }
601 .icon-report { background-image: url(../images/report.png); }
600 .icon-comment { background-image: url(../images/comment.png); }
602 .icon-comment { background-image: url(../images/comment.png); }
601
603
602 .icon22-projects { background-image: url(../images/22x22/projects.png); }
604 .icon22-projects { background-image: url(../images/22x22/projects.png); }
603 .icon22-users { background-image: url(../images/22x22/users.png); }
605 .icon22-users { background-image: url(../images/22x22/users.png); }
604 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
606 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
605 .icon22-role { background-image: url(../images/22x22/role.png); }
607 .icon22-role { background-image: url(../images/22x22/role.png); }
606 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
608 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
607 .icon22-options { background-image: url(../images/22x22/options.png); }
609 .icon22-options { background-image: url(../images/22x22/options.png); }
608 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
610 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
609 .icon22-authent { background-image: url(../images/22x22/authent.png); }
611 .icon22-authent { background-image: url(../images/22x22/authent.png); }
610 .icon22-info { background-image: url(../images/22x22/info.png); }
612 .icon22-info { background-image: url(../images/22x22/info.png); }
611 .icon22-comment { background-image: url(../images/22x22/comment.png); }
613 .icon22-comment { background-image: url(../images/22x22/comment.png); }
612 .icon22-package { background-image: url(../images/22x22/package.png); }
614 .icon22-package { background-image: url(../images/22x22/package.png); }
613 .icon22-settings { background-image: url(../images/22x22/settings.png); }
615 .icon22-settings { background-image: url(../images/22x22/settings.png); }
614 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
616 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
615
617
616 /***** Media print specific styles *****/
618 /***** Media print specific styles *****/
617 @media print {
619 @media print {
618 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
620 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
619 #main { background: #fff; }
621 #main { background: #fff; }
620 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
622 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
621 }
623 }
@@ -1,387 +1,402
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.dirname(__FILE__) + '/../../test_helper'
18 require File.dirname(__FILE__) + '/../../test_helper'
19
19
20 class ApplicationHelperTest < HelperTestCase
20 class ApplicationHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include ActionView::Helpers::TextHelper
22 include ActionView::Helpers::TextHelper
23 fixtures :projects, :roles, :enabled_modules,
23 fixtures :projects, :roles, :enabled_modules,
24 :repositories, :changesets,
24 :repositories, :changesets,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
25 :trackers, :issue_statuses, :issues, :versions, :documents,
26 :wikis, :wiki_pages, :wiki_contents,
26 :wikis, :wiki_pages, :wiki_contents,
27 :boards, :messages
27 :boards, :messages
28
28
29 def setup
29 def setup
30 super
30 super
31 end
31 end
32
32
33 def test_auto_links
33 def test_auto_links
34 to_test = {
34 to_test = {
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
35 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
36 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
37 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
38 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
39 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
40 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
40 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
41 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
41 'http://www.foo.bar/Test_(foobar)' => '<a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>',
42 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
42 '(see inline link : http://www.foo.bar/Test_(foobar))' => '(see inline link : <a class="external" href="http://www.foo.bar/Test_(foobar)">http://www.foo.bar/Test_(foobar)</a>)',
43 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
43 '(see inline link : http://www.foo.bar/Test)' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>)',
44 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
44 '(see inline link : http://www.foo.bar/Test).' => '(see inline link : <a class="external" href="http://www.foo.bar/Test">http://www.foo.bar/Test</a>).',
45 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
45 '(see "inline link":http://www.foo.bar/Test_(foobar))' => '(see <a href="http://www.foo.bar/Test_(foobar)" class="external">inline link</a>)',
46 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
46 '(see "inline link":http://www.foo.bar/Test)' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>)',
47 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
47 '(see "inline link":http://www.foo.bar/Test).' => '(see <a href="http://www.foo.bar/Test" class="external">inline link</a>).',
48 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
48 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
49 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
49 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
50 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
50 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
51 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
51 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
52 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
52 'http://foo:bar@www.bar.com' => '<a class="external" href="http://foo:bar@www.bar.com">http://foo:bar@www.bar.com</a>',
53 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
53 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
54 }
54 }
55 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
56 end
56 end
57
57
58 def test_auto_mailto
58 def test_auto_mailto
59 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
59 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
60 textilizable('test@foo.bar')
60 textilizable('test@foo.bar')
61 end
61 end
62
62
63 def test_inline_images
63 def test_inline_images
64 to_test = {
64 to_test = {
65 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
65 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
66 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
66 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
67 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
67 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
68 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
68 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
69 }
69 }
70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 end
71 end
72
72
73 def test_textile_external_links
73 def test_textile_external_links
74 to_test = {
74 to_test = {
75 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
75 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
76 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
76 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
77 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
77 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
78 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
78 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
79 # no multiline link text
79 # no multiline link text
80 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
80 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
81 }
81 }
82 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
82 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
83 end
83 end
84
84
85 def test_redmine_links
85 def test_redmine_links
86 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
86 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
87 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
87 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
88
88
89 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
89 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
90 :class => 'changeset', :title => 'My very first commit')
90 :class => 'changeset', :title => 'My very first commit')
91
91
92 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
92 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
93 :class => 'document')
93 :class => 'document')
94
94
95 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
95 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
96 :class => 'version')
96 :class => 'version')
97
97
98 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
98 message_url = {:controller => 'messages', :action => 'show', :board_id => 1, :id => 4}
99
99
100 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
100 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
101 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
101 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
102
102
103 to_test = {
103 to_test = {
104 # tickets
104 # tickets
105 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
105 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
106 # changesets
106 # changesets
107 'r1' => changeset_link,
107 'r1' => changeset_link,
108 # documents
108 # documents
109 'document#1' => document_link,
109 'document#1' => document_link,
110 'document:"Test document"' => document_link,
110 'document:"Test document"' => document_link,
111 # versions
111 # versions
112 'version#2' => version_link,
112 'version#2' => version_link,
113 'version:1.0' => version_link,
113 'version:1.0' => version_link,
114 'version:"1.0"' => version_link,
114 'version:"1.0"' => version_link,
115 # source
115 # source
116 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
116 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
117 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
117 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
118 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
118 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
119 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
119 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
120 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
120 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
121 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
121 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
122 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
122 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
123 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
123 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
124 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
124 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
125 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
125 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
126 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
126 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
127 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
127 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
128 # message
128 # message
129 'message#4' => link_to('Post 2', message_url, :class => 'message'),
129 'message#4' => link_to('Post 2', message_url, :class => 'message'),
130 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
130 'message#5' => link_to('RE: post 2', message_url.merge(:anchor => 'message-5'), :class => 'message'),
131 # escaping
131 # escaping
132 '!#3.' => '#3.',
132 '!#3.' => '#3.',
133 '!r1' => 'r1',
133 '!r1' => 'r1',
134 '!document#1' => 'document#1',
134 '!document#1' => 'document#1',
135 '!document:"Test document"' => 'document:"Test document"',
135 '!document:"Test document"' => 'document:"Test document"',
136 '!version#2' => 'version#2',
136 '!version#2' => 'version#2',
137 '!version:1.0' => 'version:1.0',
137 '!version:1.0' => 'version:1.0',
138 '!version:"1.0"' => 'version:"1.0"',
138 '!version:"1.0"' => 'version:"1.0"',
139 '!source:/some/file' => 'source:/some/file',
139 '!source:/some/file' => 'source:/some/file',
140 # invalid expressions
140 # invalid expressions
141 'source:' => 'source:',
141 'source:' => 'source:',
142 # url hash
142 # url hash
143 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
143 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
144 }
144 }
145 @project = Project.find(1)
145 @project = Project.find(1)
146 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
146 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
147 end
147 end
148
148
149 def test_wiki_links
149 def test_wiki_links
150 to_test = {
150 to_test = {
151 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
151 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
152 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
152 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
153 # link with anchor
153 # link with anchor
154 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
154 '[[CookBook documentation#One-section]]' => '<a href="/wiki/ecookbook/CookBook_documentation#One-section" class="wiki-page">CookBook documentation</a>',
155 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
155 '[[Another page#anchor|Page]]' => '<a href="/wiki/ecookbook/Another_page#anchor" class="wiki-page">Page</a>',
156 # page that doesn't exist
156 # page that doesn't exist
157 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
157 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
158 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
158 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
159 # link to another project wiki
159 # link to another project wiki
160 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
160 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
161 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
161 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
162 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
162 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
163 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
163 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
164 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
164 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
165 # striked through link
165 # striked through link
166 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
166 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
167 # escaping
167 # escaping
168 '![[Another page|Page]]' => '[[Another page|Page]]',
168 '![[Another page|Page]]' => '[[Another page|Page]]',
169 }
169 }
170 @project = Project.find(1)
170 @project = Project.find(1)
171 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
171 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
172 end
172 end
173
173
174 def test_html_tags
174 def test_html_tags
175 to_test = {
175 to_test = {
176 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
176 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
177 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
177 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
178 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
178 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
179 # do not escape pre/code tags
179 # do not escape pre/code tags
180 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
180 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
181 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
181 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
182 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
182 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
183 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
183 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
184 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
184 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>",
185 # remove attributes
185 # remove attributes
186 "<pre class='foo'>some text</pre>" => "<pre>some text</pre>",
186 "<pre class='foo'>some text</pre>" => "<pre>some text</pre>",
187 }
187 }
188 to_test.each { |text, result| assert_equal result, textilizable(text) }
188 to_test.each { |text, result| assert_equal result, textilizable(text) }
189 end
189 end
190
190
191 def test_allowed_html_tags
191 def test_allowed_html_tags
192 to_test = {
192 to_test = {
193 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
193 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
194 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
194 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
195 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
195 "<notextile>this is <tag>a tag</tag></notextile>" => "this is &lt;tag&gt;a tag&lt;/tag&gt;"
196 }
196 }
197 to_test.each { |text, result| assert_equal result, textilizable(text) }
197 to_test.each { |text, result| assert_equal result, textilizable(text) }
198 end
198 end
199
199
200 def test_wiki_links_in_tables
200 def test_wiki_links_in_tables
201 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
201 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
202 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
202 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
203 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
203 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
204 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
204 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
205 }
205 }
206 @project = Project.find(1)
206 @project = Project.find(1)
207 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
207 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
208 end
208 end
209
209
210 def test_text_formatting
210 def test_text_formatting
211 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
211 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
212 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
212 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
213 }
213 }
214 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
214 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
215 end
215 end
216
216
217 def test_wiki_horizontal_rule
217 def test_wiki_horizontal_rule
218 assert_equal '<hr />', textilizable('---')
218 assert_equal '<hr />', textilizable('---')
219 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
219 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
220 end
220 end
221
221
222 def test_footnotes
223 raw = <<-RAW
224 This is some text[1].
225
226 fn1. This is the foot note
227 RAW
228
229 expected = <<-EXPECTED
230 <p>This is some text<sup><a href=\"#fn1\">1</a></sup>.</p>
231 <p id="fn1" class="footnote"><sup>1</sup> This is the foot note</p>
232 EXPECTED
233
234 assert_equal expected.gsub(%r{[\r\n\t]}, ''), textilizable(raw).gsub(%r{[\r\n\t]}, '')
235 end
236
222 def test_table_of_content
237 def test_table_of_content
223 raw = <<-RAW
238 raw = <<-RAW
224 {{toc}}
239 {{toc}}
225
240
226 h1. Title
241 h1. Title
227
242
228 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
243 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
229
244
230 h2. Subtitle
245 h2. Subtitle
231
246
232 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
247 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
233
248
234 h2. Subtitle with %{color:red}red text%
249 h2. Subtitle with %{color:red}red text%
235
250
236 h1. Another title
251 h1. Another title
237
252
238 RAW
253 RAW
239
254
240 expected = '<ul class="toc">' +
255 expected = '<ul class="toc">' +
241 '<li class="heading1"><a href="#Title">Title</a></li>' +
256 '<li class="heading1"><a href="#Title">Title</a></li>' +
242 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
257 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
243 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
258 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
244 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
259 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
245 '</ul>'
260 '</ul>'
246
261
247 assert textilizable(raw).gsub("\n", "").include?(expected)
262 assert textilizable(raw).gsub("\n", "").include?(expected)
248 end
263 end
249
264
250 def test_blockquote
265 def test_blockquote
251 # orig raw text
266 # orig raw text
252 raw = <<-RAW
267 raw = <<-RAW
253 John said:
268 John said:
254 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
269 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
255 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
270 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
256 > * Donec odio lorem,
271 > * Donec odio lorem,
257 > * sagittis ac,
272 > * sagittis ac,
258 > * malesuada in,
273 > * malesuada in,
259 > * adipiscing eu, dolor.
274 > * adipiscing eu, dolor.
260 >
275 >
261 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
276 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
262 > Proin a tellus. Nam vel neque.
277 > Proin a tellus. Nam vel neque.
263
278
264 He's right.
279 He's right.
265 RAW
280 RAW
266
281
267 # expected html
282 # expected html
268 expected = <<-EXPECTED
283 expected = <<-EXPECTED
269 <p>John said:</p>
284 <p>John said:</p>
270 <blockquote>
285 <blockquote>
271 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
286 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
272 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
287 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
273 <ul>
288 <ul>
274 <li>Donec odio lorem,</li>
289 <li>Donec odio lorem,</li>
275 <li>sagittis ac,</li>
290 <li>sagittis ac,</li>
276 <li>malesuada in,</li>
291 <li>malesuada in,</li>
277 <li>adipiscing eu, dolor.</li>
292 <li>adipiscing eu, dolor.</li>
278 </ul>
293 </ul>
279 <blockquote>
294 <blockquote>
280 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
295 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
281 </blockquote>
296 </blockquote>
282 <p>Proin a tellus. Nam vel neque.</p>
297 <p>Proin a tellus. Nam vel neque.</p>
283 </blockquote>
298 </blockquote>
284 <p>He's right.</p>
299 <p>He's right.</p>
285 EXPECTED
300 EXPECTED
286
301
287 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
302 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
288 end
303 end
289
304
290 def test_table
305 def test_table
291 raw = <<-RAW
306 raw = <<-RAW
292 This is a table with empty cells:
307 This is a table with empty cells:
293
308
294 |cell11|cell12||
309 |cell11|cell12||
295 |cell21||cell23|
310 |cell21||cell23|
296 |cell31|cell32|cell33|
311 |cell31|cell32|cell33|
297 RAW
312 RAW
298
313
299 expected = <<-EXPECTED
314 expected = <<-EXPECTED
300 <p>This is a table with empty cells:</p>
315 <p>This is a table with empty cells:</p>
301
316
302 <table>
317 <table>
303 <tr><td>cell11</td><td>cell12</td><td></td></tr>
318 <tr><td>cell11</td><td>cell12</td><td></td></tr>
304 <tr><td>cell21</td><td></td><td>cell23</td></tr>
319 <tr><td>cell21</td><td></td><td>cell23</td></tr>
305 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
320 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
306 </table>
321 </table>
307 EXPECTED
322 EXPECTED
308
323
309 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
324 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
310 end
325 end
311
326
312 def test_macro_hello_world
327 def test_macro_hello_world
313 text = "{{hello_world}}"
328 text = "{{hello_world}}"
314 assert textilizable(text).match(/Hello world!/)
329 assert textilizable(text).match(/Hello world!/)
315 # escaping
330 # escaping
316 text = "!{{hello_world}}"
331 text = "!{{hello_world}}"
317 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
332 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
318 end
333 end
319
334
320 def test_macro_include
335 def test_macro_include
321 @project = Project.find(1)
336 @project = Project.find(1)
322 # include a page of the current project wiki
337 # include a page of the current project wiki
323 text = "{{include(Another page)}}"
338 text = "{{include(Another page)}}"
324 assert textilizable(text).match(/This is a link to a ticket/)
339 assert textilizable(text).match(/This is a link to a ticket/)
325
340
326 @project = nil
341 @project = nil
327 # include a page of a specific project wiki
342 # include a page of a specific project wiki
328 text = "{{include(ecookbook:Another page)}}"
343 text = "{{include(ecookbook:Another page)}}"
329 assert textilizable(text).match(/This is a link to a ticket/)
344 assert textilizable(text).match(/This is a link to a ticket/)
330
345
331 text = "{{include(ecookbook:)}}"
346 text = "{{include(ecookbook:)}}"
332 assert textilizable(text).match(/CookBook documentation/)
347 assert textilizable(text).match(/CookBook documentation/)
333
348
334 text = "{{include(unknowidentifier:somepage)}}"
349 text = "{{include(unknowidentifier:somepage)}}"
335 assert textilizable(text).match(/Unknow project/)
350 assert textilizable(text).match(/Unknow project/)
336 end
351 end
337
352
338 def test_date_format_default
353 def test_date_format_default
339 today = Date.today
354 today = Date.today
340 Setting.date_format = ''
355 Setting.date_format = ''
341 assert_equal l_date(today), format_date(today)
356 assert_equal l_date(today), format_date(today)
342 end
357 end
343
358
344 def test_date_format
359 def test_date_format
345 today = Date.today
360 today = Date.today
346 Setting.date_format = '%d %m %Y'
361 Setting.date_format = '%d %m %Y'
347 assert_equal today.strftime('%d %m %Y'), format_date(today)
362 assert_equal today.strftime('%d %m %Y'), format_date(today)
348 end
363 end
349
364
350 def test_time_format_default
365 def test_time_format_default
351 now = Time.now
366 now = Time.now
352 Setting.date_format = ''
367 Setting.date_format = ''
353 Setting.time_format = ''
368 Setting.time_format = ''
354 assert_equal l_datetime(now), format_time(now)
369 assert_equal l_datetime(now), format_time(now)
355 assert_equal l_time(now), format_time(now, false)
370 assert_equal l_time(now), format_time(now, false)
356 end
371 end
357
372
358 def test_time_format
373 def test_time_format
359 now = Time.now
374 now = Time.now
360 Setting.date_format = '%d %m %Y'
375 Setting.date_format = '%d %m %Y'
361 Setting.time_format = '%H %M'
376 Setting.time_format = '%H %M'
362 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
377 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
363 assert_equal now.strftime('%H %M'), format_time(now, false)
378 assert_equal now.strftime('%H %M'), format_time(now, false)
364 end
379 end
365
380
366 def test_utc_time_format
381 def test_utc_time_format
367 now = Time.now.utc
382 now = Time.now.utc
368 Setting.date_format = '%d %m %Y'
383 Setting.date_format = '%d %m %Y'
369 Setting.time_format = '%H %M'
384 Setting.time_format = '%H %M'
370 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
385 assert_equal Time.now.strftime('%d %m %Y %H %M'), format_time(now)
371 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
386 assert_equal Time.now.strftime('%H %M'), format_time(now, false)
372 end
387 end
373
388
374 def test_due_date_distance_in_words
389 def test_due_date_distance_in_words
375 to_test = { Date.today => 'Due in 0 days',
390 to_test = { Date.today => 'Due in 0 days',
376 Date.today + 1 => 'Due in 1 day',
391 Date.today + 1 => 'Due in 1 day',
377 Date.today + 100 => 'Due in 100 days',
392 Date.today + 100 => 'Due in 100 days',
378 Date.today + 20000 => 'Due in 20000 days',
393 Date.today + 20000 => 'Due in 20000 days',
379 Date.today - 1 => '1 day late',
394 Date.today - 1 => '1 day late',
380 Date.today - 100 => '100 days late',
395 Date.today - 100 => '100 days late',
381 Date.today - 20000 => '20000 days late',
396 Date.today - 20000 => '20000 days late',
382 }
397 }
383 to_test.each do |date, expected|
398 to_test.each do |date, expected|
384 assert_equal expected, due_date_distance_in_words(date)
399 assert_equal expected, due_date_distance_in_words(date)
385 end
400 end
386 end
401 end
387 end
402 end
General Comments 0
You need to be logged in to leave comments. Login now