##// END OF EJS Templates
Adds multi-levels blockquotes support by using > at the beginning of lines....
Jean-Philippe Lang -
r1465:88dea1a06d83
parent child
Show More
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -1,1140 +1,1165
1 1 # vim:ts=4:sw=4:
2 2 # = RedCloth - Textile and Markdown Hybrid for Ruby
3 3 #
4 4 # Homepage:: http://whytheluckystiff.net/ruby/redcloth/
5 5 # Author:: why the lucky stiff (http://whytheluckystiff.net/)
6 6 # Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
7 7 # License:: BSD
8 8 #
9 9 # (see http://hobix.com/textile/ for a Textile Reference.)
10 10 #
11 11 # Based on (and also inspired by) both:
12 12 #
13 13 # PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
14 14 # Textism for PHP: http://www.textism.com/tools/textile/
15 15 #
16 16 #
17 17
18 18 # = RedCloth
19 19 #
20 20 # RedCloth is a Ruby library for converting Textile and/or Markdown
21 21 # into HTML. You can use either format, intermingled or separately.
22 22 # You can also extend RedCloth to honor your own custom text stylings.
23 23 #
24 24 # RedCloth users are encouraged to use Textile if they are generating
25 25 # HTML and to use Markdown if others will be viewing the plain text.
26 26 #
27 27 # == What is Textile?
28 28 #
29 29 # Textile is a simple formatting style for text
30 30 # documents, loosely based on some HTML conventions.
31 31 #
32 32 # == Sample Textile Text
33 33 #
34 34 # h2. This is a title
35 35 #
36 36 # h3. This is a subhead
37 37 #
38 38 # This is a bit of paragraph.
39 39 #
40 40 # bq. This is a blockquote.
41 41 #
42 42 # = Writing Textile
43 43 #
44 44 # A Textile document consists of paragraphs. Paragraphs
45 45 # can be specially formatted by adding a small instruction
46 46 # to the beginning of the paragraph.
47 47 #
48 48 # h[n]. Header of size [n].
49 49 # bq. Blockquote.
50 50 # # Numeric list.
51 51 # * Bulleted list.
52 52 #
53 53 # == Quick Phrase Modifiers
54 54 #
55 55 # Quick phrase modifiers are also included, to allow formatting
56 56 # of small portions of text within a paragraph.
57 57 #
58 58 # \_emphasis\_
59 59 # \_\_italicized\_\_
60 60 # \*strong\*
61 61 # \*\*bold\*\*
62 62 # ??citation??
63 63 # -deleted text-
64 64 # +inserted text+
65 65 # ^superscript^
66 66 # ~subscript~
67 67 # @code@
68 68 # %(classname)span%
69 69 #
70 70 # ==notextile== (leave text alone)
71 71 #
72 72 # == Links
73 73 #
74 74 # To make a hypertext link, put the link text in "quotation
75 75 # marks" followed immediately by a colon and the URL of the link.
76 76 #
77 77 # Optional: text in (parentheses) following the link text,
78 78 # but before the closing quotation mark, will become a Title
79 79 # attribute for the link, visible as a tool tip when a cursor is above it.
80 80 #
81 81 # Example:
82 82 #
83 83 # "This is a link (This is a title) ":http://www.textism.com
84 84 #
85 85 # Will become:
86 86 #
87 87 # <a href="http://www.textism.com" title="This is a title">This is a link</a>
88 88 #
89 89 # == Images
90 90 #
91 91 # To insert an image, put the URL for the image inside exclamation marks.
92 92 #
93 93 # Optional: text that immediately follows the URL in (parentheses) will
94 94 # be used as the Alt text for the image. Images on the web should always
95 95 # have descriptive Alt text for the benefit of readers using non-graphical
96 96 # browsers.
97 97 #
98 98 # Optional: place a colon followed by a URL immediately after the
99 99 # closing ! to make the image into a link.
100 100 #
101 101 # Example:
102 102 #
103 103 # !http://www.textism.com/common/textist.gif(Textist)!
104 104 #
105 105 # Will become:
106 106 #
107 107 # <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
108 108 #
109 109 # With a link:
110 110 #
111 111 # !/common/textist.gif(Textist)!:http://textism.com
112 112 #
113 113 # Will become:
114 114 #
115 115 # <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
116 116 #
117 117 # == Defining Acronyms
118 118 #
119 119 # HTML allows authors to define acronyms via the tag. The definition appears as a
120 120 # tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
121 121 # this should be used at least once for each acronym in documents where they appear.
122 122 #
123 123 # To quickly define an acronym in Textile, place the full text in (parentheses)
124 124 # immediately following the acronym.
125 125 #
126 126 # Example:
127 127 #
128 128 # ACLU(American Civil Liberties Union)
129 129 #
130 130 # Will become:
131 131 #
132 132 # <acronym title="American Civil Liberties Union">ACLU</acronym>
133 133 #
134 134 # == Adding Tables
135 135 #
136 136 # In Textile, simple tables can be added by seperating each column by
137 137 # a pipe.
138 138 #
139 139 # |a|simple|table|row|
140 140 # |And|Another|table|row|
141 141 #
142 142 # Attributes are defined by style definitions in parentheses.
143 143 #
144 144 # table(border:1px solid black).
145 145 # (background:#ddd;color:red). |{}| | | |
146 146 #
147 147 # == Using RedCloth
148 148 #
149 149 # RedCloth is simply an extension of the String class, which can handle
150 150 # Textile formatting. Use it like a String and output HTML with its
151 151 # RedCloth#to_html method.
152 152 #
153 153 # doc = RedCloth.new "
154 154 #
155 155 # h2. Test document
156 156 #
157 157 # Just a simple test."
158 158 #
159 159 # puts doc.to_html
160 160 #
161 161 # By default, RedCloth uses both Textile and Markdown formatting, with
162 162 # Textile formatting taking precedence. If you want to turn off Markdown
163 163 # formatting, to boost speed and limit the processor:
164 164 #
165 165 # class RedCloth::Textile.new( str )
166 166
167 167 class RedCloth < String
168 168
169 169 VERSION = '3.0.4'
170 170 DEFAULT_RULES = [:textile, :markdown]
171 171
172 172 #
173 173 # Two accessor for setting security restrictions.
174 174 #
175 175 # This is a nice thing if you're using RedCloth for
176 176 # formatting in public places (e.g. Wikis) where you
177 177 # don't want users to abuse HTML for bad things.
178 178 #
179 179 # If +:filter_html+ is set, HTML which wasn't
180 180 # created by the Textile processor will be escaped.
181 181 #
182 182 # If +:filter_styles+ is set, it will also disable
183 183 # the style markup specifier. ('{color: red}')
184 184 #
185 185 attr_accessor :filter_html, :filter_styles
186 186
187 187 #
188 188 # Accessor for toggling hard breaks.
189 189 #
190 190 # If +:hard_breaks+ is set, single newlines will
191 191 # be converted to HTML break tags. This is the
192 192 # default behavior for traditional RedCloth.
193 193 #
194 194 attr_accessor :hard_breaks
195 195
196 196 # Accessor for toggling lite mode.
197 197 #
198 198 # In lite mode, block-level rules are ignored. This means
199 199 # that tables, paragraphs, lists, and such aren't available.
200 200 # Only the inline markup for bold, italics, entities and so on.
201 201 #
202 202 # r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
203 203 # r.to_html
204 204 # #=> "And then? She <strong>fell</strong>!"
205 205 #
206 206 attr_accessor :lite_mode
207 207
208 208 #
209 209 # Accessor for toggling span caps.
210 210 #
211 211 # Textile places `span' tags around capitalized
212 212 # words by default, but this wreaks havoc on Wikis.
213 213 # If +:no_span_caps+ is set, this will be
214 214 # suppressed.
215 215 #
216 216 attr_accessor :no_span_caps
217 217
218 218 #
219 219 # Establishes the markup predence. Available rules include:
220 220 #
221 221 # == Textile Rules
222 222 #
223 223 # The following textile rules can be set individually. Or add the complete
224 224 # set of rules with the single :textile rule, which supplies the rule set in
225 225 # the following precedence:
226 226 #
227 227 # refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
228 228 # block_textile_table:: Textile table block structures
229 229 # block_textile_lists:: Textile list structures
230 230 # block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
231 231 # inline_textile_image:: Textile inline images
232 232 # inline_textile_link:: Textile inline links
233 233 # inline_textile_span:: Textile inline spans
234 234 # glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
235 235 #
236 236 # == Markdown
237 237 #
238 238 # refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
239 239 # block_markdown_setext:: Markdown setext headers
240 240 # block_markdown_atx:: Markdown atx headers
241 241 # block_markdown_rule:: Markdown horizontal rules
242 242 # block_markdown_bq:: Markdown blockquotes
243 243 # block_markdown_lists:: Markdown lists
244 244 # inline_markdown_link:: Markdown links
245 245 attr_accessor :rules
246 246
247 247 # Returns a new RedCloth object, based on _string_ and
248 248 # enforcing all the included _restrictions_.
249 249 #
250 250 # r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
251 251 # r.to_html
252 252 # #=>"<h1>A &lt;b&gt;bold&lt;/b&gt; man</h1>"
253 253 #
254 254 def initialize( string, restrictions = [] )
255 255 restrictions.each { |r| method( "#{ r }=" ).call( true ) }
256 256 super( string )
257 257 end
258 258
259 259 #
260 260 # Generates HTML from the Textile contents.
261 261 #
262 262 # r = RedCloth.new( "And then? She *fell*!" )
263 263 # r.to_html( true )
264 264 # #=>"And then? She <strong>fell</strong>!"
265 265 #
266 266 def to_html( *rules )
267 267 rules = DEFAULT_RULES if rules.empty?
268 268 # make our working copy
269 269 text = self.dup
270 270
271 271 @urlrefs = {}
272 272 @shelf = []
273 273 textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
274 274 :block_textile_prefix, :inline_textile_image, :inline_textile_link,
275 275 :inline_textile_code, :inline_textile_span]
276 276 markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
277 277 :block_markdown_bq, :block_markdown_lists,
278 278 :inline_markdown_reflink, :inline_markdown_link]
279 279 @rules = rules.collect do |rule|
280 280 case rule
281 281 when :markdown
282 282 markdown_rules
283 283 when :textile
284 284 textile_rules
285 285 else
286 286 rule
287 287 end
288 288 end.flatten
289 289
290 290 # standard clean up
291 291 incoming_entities text
292 292 clean_white_space text
293 293
294 294 # start processor
295 295 @pre_list = []
296 296 rip_offtags text
297 297 no_textile text
298 298 escape_html_tags text
299 299 hard_break text
300 300 unless @lite_mode
301 301 refs text
302 # need to do this before text is split by #blocks
303 block_textile_quotes text
302 304 blocks text
303 305 end
304 306 inline text
305 307 smooth_offtags text
306 308
307 309 retrieve text
308 310
309 311 text.gsub!( /<\/?notextile>/, '' )
310 312 text.gsub!( /x%x%/, '&#38;' )
311 313 clean_html text if filter_html
312 314 text.strip!
313 315 text
314 316
315 317 end
316 318
317 319 #######
318 320 private
319 321 #######
320 322 #
321 323 # Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
322 324 # (from PyTextile)
323 325 #
324 326 TEXTILE_TAGS =
325 327
326 328 [[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
327 329 [134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
328 330 [140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
329 331 [147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
330 332 [153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
331 333
332 334 collect! do |a, b|
333 335 [a.chr, ( b.zero? and "" or "&#{ b };" )]
334 336 end
335 337
336 338 #
337 339 # Regular expressions to convert to HTML.
338 340 #
339 341 A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
340 342 A_VLGN = /[\-^~]/
341 343 C_CLAS = '(?:\([^)]+\))'
342 344 C_LNGE = '(?:\[[^\]]+\])'
343 345 C_STYL = '(?:\{[^}]+\})'
344 346 S_CSPN = '(?:\\\\\d+)'
345 347 S_RSPN = '(?:/\d+)'
346 348 A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
347 349 S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
348 350 C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
349 351 # PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
350 352 PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
351 353 PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
352 354 PUNCT_Q = Regexp::quote( '*-_+^~%' )
353 355 HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
354 356
355 357 # Text markup tags, don't conflict with block tags
356 358 SIMPLE_HTML_TAGS = [
357 359 'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
358 360 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
359 361 'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
360 362 ]
361 363
362 364 QTAGS = [
363 365 ['**', 'b', :limit],
364 366 ['*', 'strong', :limit],
365 367 ['??', 'cite', :limit],
366 368 ['-', 'del', :limit],
367 369 ['__', 'i', :limit],
368 370 ['_', 'em', :limit],
369 371 ['%', 'span', :limit],
370 372 ['+', 'ins', :limit],
371 373 ['^', 'sup', :limit],
372 374 ['~', 'sub', :limit]
373 375 ]
374 376 QTAGS.collect! do |rc, ht, rtype|
375 377 rcq = Regexp::quote rc
376 378 re =
377 379 case rtype
378 380 when :limit
379 381 /(^|[>\s\(])
380 382 (#{rcq})
381 383 (#{C})
382 384 (?::(\S+?))?
383 385 ([^\s\-].*?[^\s\-]|\w)
384 386 #{rcq}
385 387 (?=[[:punct:]]|\s|\)|$)/x
386 388 else
387 389 /(#{rcq})
388 390 (#{C})
389 391 (?::(\S+))?
390 392 ([^\s\-].*?[^\s\-]|\w)
391 393 #{rcq}/xm
392 394 end
393 395 [rc, ht, re, rtype]
394 396 end
395 397
396 398 # Elements to handle
397 399 GLYPHS = [
398 400 # [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1&#8217;\2' ], # single closing
399 401 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1&#8217;' ], # single closing
400 402 # [ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '&#8217;' ], # single closing
401 403 # [ /\'/, '&#8216;' ], # single opening
402 404 [ /</, '&lt;' ], # less-than
403 405 [ />/, '&gt;' ], # greater-than
404 406 # [ /([^\s\[{(])?"(\s|:|$)/, '\1&#8221;\2' ], # double closing
405 407 # [ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1&#8221;' ], # double closing
406 408 # [ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '&#8221;' ], # double closing
407 409 # [ /"/, '&#8220;' ], # double opening
408 410 [ /\b( )?\.{3}/, '\1&#8230;' ], # ellipsis
409 411 [ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
410 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
411 413 [ /(\.\s)?\s?--\s?/, '\1&#8212;' ], # em dash
412 414 [ /\s->\s/, ' &rarr; ' ], # right arrow
413 415 [ /\s-\s/, ' &#8211; ' ], # en dash
414 416 [ /(\d+) ?x ?(\d+)/, '\1&#215;\2' ], # dimension sign
415 417 [ /\b ?[(\[]TM[\])]/i, '&#8482;' ], # trademark
416 418 [ /\b ?[(\[]R[\])]/i, '&#174;' ], # registered
417 419 [ /\b ?[(\[]C[\])]/i, '&#169;' ] # copyright
418 420 ]
419 421
420 422 H_ALGN_VALS = {
421 423 '<' => 'left',
422 424 '=' => 'center',
423 425 '>' => 'right',
424 426 '<>' => 'justify'
425 427 }
426 428
427 429 V_ALGN_VALS = {
428 430 '^' => 'top',
429 431 '-' => 'middle',
430 432 '~' => 'bottom'
431 433 }
432 434
433 435 #
434 436 # Flexible HTML escaping
435 437 #
436 438 def htmlesc( str, mode )
437 439 str.gsub!( '&', '&amp;' )
438 440 str.gsub!( '"', '&quot;' ) if mode != :NoQuotes
439 441 str.gsub!( "'", '&#039;' ) if mode == :Quotes
440 442 str.gsub!( '<', '&lt;')
441 443 str.gsub!( '>', '&gt;')
442 444 end
443 445
444 446 # Search and replace for Textile glyphs (quotes, dashes, other symbols)
445 447 def pgl( text )
446 448 GLYPHS.each do |re, resub, tog|
447 449 next if tog and method( tog ).call
448 450 text.gsub! re, resub
449 451 end
450 452 end
451 453
452 454 # Parses Textile attribute lists and builds an HTML attribute string
453 455 def pba( text_in, element = "" )
454 456
455 457 return '' unless text_in
456 458
457 459 style = []
458 460 text = text_in.dup
459 461 if element == 'td'
460 462 colspan = $1 if text =~ /\\(\d+)/
461 463 rowspan = $1 if text =~ /\/(\d+)/
462 464 style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
463 465 end
464 466
465 467 style << "#{ $1 };" if not filter_styles and
466 468 text.sub!( /\{([^}]*)\}/, '' )
467 469
468 470 lang = $1 if
469 471 text.sub!( /\[([^)]+?)\]/, '' )
470 472
471 473 cls = $1 if
472 474 text.sub!( /\(([^()]+?)\)/, '' )
473 475
474 476 style << "padding-left:#{ $1.length }em;" if
475 477 text.sub!( /([(]+)/, '' )
476 478
477 479 style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
478 480
479 481 style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
480 482
481 483 cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
482 484
483 485 atts = ''
484 486 atts << " style=\"#{ style.join }\"" unless style.empty?
485 487 atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
486 488 atts << " lang=\"#{ lang }\"" if lang
487 489 atts << " id=\"#{ id }\"" if id
488 490 atts << " colspan=\"#{ colspan }\"" if colspan
489 491 atts << " rowspan=\"#{ rowspan }\"" if rowspan
490 492
491 493 atts
492 494 end
493 495
494 496 TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
495 497
496 498 # Parses a Textile table block, building HTML from the result.
497 499 def block_textile_table( text )
498 500 text.gsub!( TABLE_RE ) do |matches|
499 501
500 502 tatts, fullrow = $~[1..2]
501 503 tatts = pba( tatts, 'table' )
502 504 tatts = shelve( tatts ) if tatts
503 505 rows = []
504 506
505 507 fullrow.
506 508 split( /\|$/m ).
507 509 delete_if { |x| x.empty? }.
508 510 each do |row|
509 511
510 512 ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
511 513
512 514 cells = []
513 515 #row.split( /\(?!\[\[[^\]])|(?![^\[]\]\])/ ).each do |cell|
514 516 row.split( /\|(?![^\[\|]*\]\])/ ).each do |cell|
515 517 ctyp = 'd'
516 518 ctyp = 'h' if cell =~ /^_/
517 519
518 520 catts = ''
519 521 catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
520 522
521 523 unless cell.strip.empty?
522 524 catts = shelve( catts ) if catts
523 525 cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
524 526 end
525 527 end
526 528 ratts = shelve( ratts ) if ratts
527 529 rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
528 530 end
529 531 "\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
530 532 end
531 533 end
532 534
533 535 LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
534 536 LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
535 537
536 538 # Parses Textile lists and generates HTML
537 539 def block_textile_lists( text )
538 540 text.gsub!( LISTS_RE ) do |match|
539 541 lines = match.split( /\n/ )
540 542 last_line = -1
541 543 depth = []
542 544 lines.each_with_index do |line, line_id|
543 545 if line =~ LISTS_CONTENT_RE
544 546 tl,atts,content = $~[1..3]
545 547 if depth.last
546 548 if depth.last.length > tl.length
547 549 (depth.length - 1).downto(0) do |i|
548 550 break if depth[i].length == tl.length
549 551 lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
550 552 depth.pop
551 553 end
552 554 end
553 555 if depth.last and depth.last.length == tl.length
554 556 lines[line_id - 1] << '</li>'
555 557 end
556 558 end
557 559 unless depth.last == tl
558 560 depth << tl
559 561 atts = pba( atts )
560 562 atts = shelve( atts ) if atts
561 563 lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
562 564 else
563 565 lines[line_id] = "\t\t<li>#{ content }"
564 566 end
565 567 last_line = line_id
566 568
567 569 else
568 570 last_line = line_id
569 571 end
570 572 if line_id - last_line > 1 or line_id == lines.length - 1
571 573 depth.delete_if do |v|
572 574 lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
573 575 end
574 576 end
575 577 end
576 578 lines.join( "\n" )
577 579 end
578 580 end
581
582 QUOTES_RE = /(^>+([^\n]*?)\n?)+/m
583 QUOTES_CONTENT_RE = /^([> ]+)(.*)$/m
584
585 def block_textile_quotes( text )
586 text.gsub!( QUOTES_RE ) do |match|
587 lines = match.split( /\n/ )
588 quotes = ''
589 indent = 0
590 lines.each do |line|
591 line =~ QUOTES_CONTENT_RE
592 bq,content = $1, $2
593 l = bq.count('>')
594 if l != indent
595 quotes << ("\n\n" + (l>indent ? '<blockquote>' * (l-indent) : '</blockquote>' * (indent-l)) + "\n\n")
596 indent = l
597 end
598 quotes << (content + "\n")
599 end
600 quotes << ("\n" + '</blockquote>' * indent + "\n\n")
601 quotes
602 end
603 end
579 604
580 605 CODE_RE = /(\W)
581 606 @
582 607 (?:\|(\w+?)\|)?
583 608 (.+?)
584 609 @
585 610 (?=\W)/x
586 611
587 612 def inline_textile_code( text )
588 613 text.gsub!( CODE_RE ) do |m|
589 614 before,lang,code,after = $~[1..4]
590 615 lang = " lang=\"#{ lang }\"" if lang
591 616 rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
592 617 end
593 618 end
594 619
595 620 def lT( text )
596 621 text =~ /\#$/ ? 'o' : 'u'
597 622 end
598 623
599 624 def hard_break( text )
600 625 text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
601 626 end
602 627
603 628 BLOCKS_GROUP_RE = /\n{2,}(?! )/m
604 629
605 630 def blocks( text, deep_code = false )
606 631 text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
607 632 plain = blk !~ /\A[#*> ]/
608 633
609 634 # skip blocks that are complex HTML
610 635 if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
611 636 blk
612 637 else
613 638 # search for indentation levels
614 639 blk.strip!
615 640 if blk.empty?
616 641 blk
617 642 else
618 643 code_blk = nil
619 644 blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
620 645 flush_left iblk
621 646 blocks iblk, plain
622 647 iblk.gsub( /^(\S)/, "\t\\1" )
623 648 if plain
624 649 code_blk = iblk; ""
625 650 else
626 651 iblk
627 652 end
628 653 end
629 654
630 655 block_applied = 0
631 656 @rules.each do |rule_name|
632 657 block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
633 658 end
634 659 if block_applied.zero?
635 660 if deep_code
636 661 blk = "\t<pre><code>#{ blk }</code></pre>"
637 662 else
638 663 blk = "\t<p>#{ blk }</p>"
639 664 end
640 665 end
641 666 # hard_break blk
642 667 blk + "\n#{ code_blk }"
643 668 end
644 669 end
645 670
646 671 end.join( "\n\n" ) )
647 672 end
648 673
649 674 def textile_bq( tag, atts, cite, content )
650 675 cite, cite_title = check_refs( cite )
651 676 cite = " cite=\"#{ cite }\"" if cite
652 677 atts = shelve( atts ) if atts
653 678 "\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
654 679 end
655 680
656 681 def textile_p( tag, atts, cite, content )
657 682 atts = shelve( atts ) if atts
658 683 "\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
659 684 end
660 685
661 686 alias textile_h1 textile_p
662 687 alias textile_h2 textile_p
663 688 alias textile_h3 textile_p
664 689 alias textile_h4 textile_p
665 690 alias textile_h5 textile_p
666 691 alias textile_h6 textile_p
667 692
668 693 def textile_fn_( tag, num, atts, cite, content )
669 694 atts << " id=\"fn#{ num }\""
670 695 content = "<sup>#{ num }</sup> #{ content }"
671 696 atts = shelve( atts ) if atts
672 697 "\t<p#{ atts }>#{ content }</p>"
673 698 end
674 699
675 700 BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
676 701
677 702 def block_textile_prefix( text )
678 703 if text =~ BLOCK_RE
679 704 tag,tagpre,num,atts,cite,content = $~[1..6]
680 705 atts = pba( atts )
681 706
682 707 # pass to prefix handler
683 708 if respond_to? "textile_#{ tag }", true
684 709 text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
685 710 elsif respond_to? "textile_#{ tagpre }_", true
686 711 text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
687 712 end
688 713 end
689 714 end
690 715
691 716 SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
692 717 def block_markdown_setext( text )
693 718 if text =~ SETEXT_RE
694 719 tag = if $2 == "="; "h1"; else; "h2"; end
695 720 blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
696 721 blocks cont
697 722 text.replace( blk + cont )
698 723 end
699 724 end
700 725
701 726 ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
702 727 [ ]*
703 728 (.+?) # $2 = Header text
704 729 [ ]*
705 730 \#* # optional closing #'s (not counted)
706 731 $/x
707 732 def block_markdown_atx( text )
708 733 if text =~ ATX_RE
709 734 tag = "h#{ $1.length }"
710 735 blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
711 736 blocks cont
712 737 text.replace( blk + cont )
713 738 end
714 739 end
715 740
716 741 MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
717 742
718 743 def block_markdown_bq( text )
719 744 text.gsub!( MARKDOWN_BQ_RE ) do |blk|
720 745 blk.gsub!( /^ *> ?/, '' )
721 746 flush_left blk
722 747 blocks blk
723 748 blk.gsub!( /^(\S)/, "\t\\1" )
724 749 "<blockquote>\n#{ blk }\n</blockquote>\n\n"
725 750 end
726 751 end
727 752
728 753 MARKDOWN_RULE_RE = /^(#{
729 754 ['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
730 755 })$/
731 756
732 757 def block_markdown_rule( text )
733 758 text.gsub!( MARKDOWN_RULE_RE ) do |blk|
734 759 "<hr />"
735 760 end
736 761 end
737 762
738 763 # XXX TODO XXX
739 764 def block_markdown_lists( text )
740 765 end
741 766
742 767 def inline_textile_span( text )
743 768 QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
744 769 text.gsub!( qtag_re ) do |m|
745 770
746 771 case rtype
747 772 when :limit
748 773 sta,qtag,atts,cite,content = $~[1..5]
749 774 else
750 775 qtag,atts,cite,content = $~[1..4]
751 776 sta = ''
752 777 end
753 778 atts = pba( atts )
754 779 atts << " cite=\"#{ cite }\"" if cite
755 780 atts = shelve( atts ) if atts
756 781
757 782 "#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
758 783
759 784 end
760 785 end
761 786 end
762 787
763 788 LINK_RE = /
764 789 ([\s\[{(]|[#{PUNCT}])? # $pre
765 790 " # start
766 791 (#{C}) # $atts
767 792 ([^"\n]+?) # $text
768 793 \s?
769 794 (?:\(([^)]+?)\)(?="))? # $title
770 795 ":
771 796 (\S+?) # $url
772 797 (\/)? # $slash
773 798 ([^\w\/;]*?) # $post
774 799 (?=<|\s|$)
775 800 /x
776 801
777 802 def inline_textile_link( text )
778 803 text.gsub!( LINK_RE ) do |m|
779 804 pre,atts,text,title,url,slash,post = $~[1..7]
780 805
781 806 url, url_title = check_refs( url )
782 807 title ||= url_title
783 808
784 809 atts = pba( atts )
785 810 atts = " href=\"#{ url }#{ slash }\"#{ atts }"
786 811 atts << " title=\"#{ title }\"" if title
787 812 atts = shelve( atts ) if atts
788 813
789 814 external = (url =~ /^https?:\/\//) ? ' class="external"' : ''
790 815
791 816 "#{ pre }<a#{ atts }#{ external }>#{ text }</a>#{ post }"
792 817 end
793 818 end
794 819
795 820 MARKDOWN_REFLINK_RE = /
796 821 \[([^\[\]]+)\] # $text
797 822 [ ]? # opt. space
798 823 (?:\n[ ]*)? # one optional newline followed by spaces
799 824 \[(.*?)\] # $id
800 825 /x
801 826
802 827 def inline_markdown_reflink( text )
803 828 text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
804 829 text, id = $~[1..2]
805 830
806 831 if id.empty?
807 832 url, title = check_refs( text )
808 833 else
809 834 url, title = check_refs( id )
810 835 end
811 836
812 837 atts = " href=\"#{ url }\""
813 838 atts << " title=\"#{ title }\"" if title
814 839 atts = shelve( atts )
815 840
816 841 "<a#{ atts }>#{ text }</a>"
817 842 end
818 843 end
819 844
820 845 MARKDOWN_LINK_RE = /
821 846 \[([^\[\]]+)\] # $text
822 847 \( # open paren
823 848 [ \t]* # opt space
824 849 <?(.+?)>? # $href
825 850 [ \t]* # opt space
826 851 (?: # whole title
827 852 (['"]) # $quote
828 853 (.*?) # $title
829 854 \3 # matching quote
830 855 )? # title is optional
831 856 \)
832 857 /x
833 858
834 859 def inline_markdown_link( text )
835 860 text.gsub!( MARKDOWN_LINK_RE ) do |m|
836 861 text, url, quote, title = $~[1..4]
837 862
838 863 atts = " href=\"#{ url }\""
839 864 atts << " title=\"#{ title }\"" if title
840 865 atts = shelve( atts )
841 866
842 867 "<a#{ atts }>#{ text }</a>"
843 868 end
844 869 end
845 870
846 871 TEXTILE_REFS_RE = /(^ *)\[([^\[\n]+?)\](#{HYPERLINK})(?=\s|$)/
847 872 MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
848 873
849 874 def refs( text )
850 875 @rules.each do |rule_name|
851 876 method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
852 877 end
853 878 end
854 879
855 880 def refs_textile( text )
856 881 text.gsub!( TEXTILE_REFS_RE ) do |m|
857 882 flag, url = $~[2..3]
858 883 @urlrefs[flag.downcase] = [url, nil]
859 884 nil
860 885 end
861 886 end
862 887
863 888 def refs_markdown( text )
864 889 text.gsub!( MARKDOWN_REFS_RE ) do |m|
865 890 flag, url = $~[2..3]
866 891 title = $~[6]
867 892 @urlrefs[flag.downcase] = [url, title]
868 893 nil
869 894 end
870 895 end
871 896
872 897 def check_refs( text )
873 898 ret = @urlrefs[text.downcase] if text
874 899 ret || [text, nil]
875 900 end
876 901
877 902 IMAGE_RE = /
878 903 (<p>|.|^) # start of line?
879 904 \! # opening
880 905 (\<|\=|\>)? # optional alignment atts
881 906 (#{C}) # optional style,class atts
882 907 (?:\. )? # optional dot-space
883 908 ([^\s(!]+?) # presume this is the src
884 909 \s? # optional space
885 910 (?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
886 911 \! # closing
887 912 (?::#{ HYPERLINK })? # optional href
888 913 /x
889 914
890 915 def inline_textile_image( text )
891 916 text.gsub!( IMAGE_RE ) do |m|
892 917 stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
893 918 atts = pba( atts )
894 919 atts = " src=\"#{ url }\"#{ atts }"
895 920 atts << " title=\"#{ title }\"" if title
896 921 atts << " alt=\"#{ title }\""
897 922 # size = @getimagesize($url);
898 923 # if($size) $atts.= " $size[3]";
899 924
900 925 href, alt_title = check_refs( href ) if href
901 926 url, url_title = check_refs( url )
902 927
903 928 out = ''
904 929 out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
905 930 out << "<img#{ shelve( atts ) } />"
906 931 out << "</a>#{ href_a1 }#{ href_a2 }" if href
907 932
908 933 if algn
909 934 algn = h_align( algn )
910 935 if stln == "<p>"
911 936 out = "<p style=\"float:#{ algn }\">#{ out }"
912 937 else
913 938 out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
914 939 end
915 940 else
916 941 out = stln + out
917 942 end
918 943
919 944 out
920 945 end
921 946 end
922 947
923 948 def shelve( val )
924 949 @shelf << val
925 950 " :redsh##{ @shelf.length }:"
926 951 end
927 952
928 953 def retrieve( text )
929 954 @shelf.each_with_index do |r, i|
930 955 text.gsub!( " :redsh##{ i + 1 }:", r )
931 956 end
932 957 end
933 958
934 959 def incoming_entities( text )
935 960 ## turn any incoming ampersands into a dummy character for now.
936 961 ## This uses a negative lookahead for alphanumerics followed by a semicolon,
937 962 ## implying an incoming html entity, to be skipped
938 963
939 964 text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
940 965 end
941 966
942 967 def no_textile( text )
943 968 text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
944 969 '\1<notextile>\2</notextile>\3' )
945 970 text.gsub!( /^ *==([^=]+.*?)==/m,
946 971 '\1<notextile>\2</notextile>\3' )
947 972 end
948 973
949 974 def clean_white_space( text )
950 975 # normalize line breaks
951 976 text.gsub!( /\r\n/, "\n" )
952 977 text.gsub!( /\r/, "\n" )
953 978 text.gsub!( /\t/, ' ' )
954 979 text.gsub!( /^ +$/, '' )
955 980 text.gsub!( /\n{3,}/, "\n\n" )
956 981 text.gsub!( /"$/, "\" " )
957 982
958 983 # if entire document is indented, flush
959 984 # to the left side
960 985 flush_left text
961 986 end
962 987
963 988 def flush_left( text )
964 989 indt = 0
965 990 if text =~ /^ /
966 991 while text !~ /^ {#{indt}}\S/
967 992 indt += 1
968 993 end unless text.empty?
969 994 if indt.nonzero?
970 995 text.gsub!( /^ {#{indt}}/, '' )
971 996 end
972 997 end
973 998 end
974 999
975 1000 def footnote_ref( text )
976 1001 text.gsub!( /\b\[([0-9]+?)\](\s)?/,
977 1002 '<sup><a href="#fn\1">\1</a></sup>\2' )
978 1003 end
979 1004
980 1005 OFFTAGS = /(code|pre|kbd|notextile)/
981 1006 OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
982 1007 OFFTAG_OPEN = /<#{ OFFTAGS }/
983 1008 OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
984 1009 HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
985 1010 ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
986 1011
987 1012 def glyphs_textile( text, level = 0 )
988 1013 if text !~ HASTAG_MATCH
989 1014 pgl text
990 1015 footnote_ref text
991 1016 else
992 1017 codepre = 0
993 1018 text.gsub!( ALLTAG_MATCH ) do |line|
994 1019 ## matches are off if we're between <code>, <pre> etc.
995 1020 if $1
996 1021 if line =~ OFFTAG_OPEN
997 1022 codepre += 1
998 1023 elsif line =~ OFFTAG_CLOSE
999 1024 codepre -= 1
1000 1025 codepre = 0 if codepre < 0
1001 1026 end
1002 1027 elsif codepre.zero?
1003 1028 glyphs_textile( line, level + 1 )
1004 1029 else
1005 1030 htmlesc( line, :NoQuotes )
1006 1031 end
1007 1032 # p [level, codepre, line]
1008 1033
1009 1034 line
1010 1035 end
1011 1036 end
1012 1037 end
1013 1038
1014 1039 def rip_offtags( text )
1015 1040 if text =~ /<.*>/
1016 1041 ## strip and encode <pre> content
1017 1042 codepre, used_offtags = 0, {}
1018 1043 text.gsub!( OFFTAG_MATCH ) do |line|
1019 1044 if $3
1020 1045 offtag, aftertag = $4, $5
1021 1046 codepre += 1
1022 1047 used_offtags[offtag] = true
1023 1048 if codepre - used_offtags.length > 0
1024 1049 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1025 1050 @pre_list.last << line
1026 1051 line = ""
1027 1052 else
1028 1053 htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
1029 1054 line = "<redpre##{ @pre_list.length }>"
1030 1055 @pre_list << "#{ $3 }#{ aftertag }"
1031 1056 end
1032 1057 elsif $1 and codepre > 0
1033 1058 if codepre - used_offtags.length > 0
1034 1059 htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
1035 1060 @pre_list.last << line
1036 1061 line = ""
1037 1062 end
1038 1063 codepre -= 1 unless codepre.zero?
1039 1064 used_offtags = {} if codepre.zero?
1040 1065 end
1041 1066 line
1042 1067 end
1043 1068 end
1044 1069 text
1045 1070 end
1046 1071
1047 1072 def smooth_offtags( text )
1048 1073 unless @pre_list.empty?
1049 1074 ## replace <pre> content
1050 1075 text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
1051 1076 end
1052 1077 end
1053 1078
1054 1079 def inline( text )
1055 1080 [/^inline_/, /^glyphs_/].each do |meth_re|
1056 1081 @rules.each do |rule_name|
1057 1082 method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
1058 1083 end
1059 1084 end
1060 1085 end
1061 1086
1062 1087 def h_align( text )
1063 1088 H_ALGN_VALS[text]
1064 1089 end
1065 1090
1066 1091 def v_align( text )
1067 1092 V_ALGN_VALS[text]
1068 1093 end
1069 1094
1070 1095 def textile_popup_help( name, windowW, windowH )
1071 1096 ' <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 />'
1072 1097 end
1073 1098
1074 1099 # HTML cleansing stuff
1075 1100 BASIC_TAGS = {
1076 1101 'a' => ['href', 'title'],
1077 1102 'img' => ['src', 'alt', 'title'],
1078 1103 'br' => [],
1079 1104 'i' => nil,
1080 1105 'u' => nil,
1081 1106 'b' => nil,
1082 1107 'pre' => nil,
1083 1108 'kbd' => nil,
1084 1109 'code' => ['lang'],
1085 1110 'cite' => nil,
1086 1111 'strong' => nil,
1087 1112 'em' => nil,
1088 1113 'ins' => nil,
1089 1114 'sup' => nil,
1090 1115 'sub' => nil,
1091 1116 'del' => nil,
1092 1117 'table' => nil,
1093 1118 'tr' => nil,
1094 1119 'td' => ['colspan', 'rowspan'],
1095 1120 'th' => nil,
1096 1121 'ol' => nil,
1097 1122 'ul' => nil,
1098 1123 'li' => nil,
1099 1124 'p' => nil,
1100 1125 'h1' => nil,
1101 1126 'h2' => nil,
1102 1127 'h3' => nil,
1103 1128 'h4' => nil,
1104 1129 'h5' => nil,
1105 1130 'h6' => nil,
1106 1131 'blockquote' => ['cite']
1107 1132 }
1108 1133
1109 1134 def clean_html( text, tags = BASIC_TAGS )
1110 1135 text.gsub!( /<!\[CDATA\[/, '' )
1111 1136 text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
1112 1137 raw = $~
1113 1138 tag = raw[2].downcase
1114 1139 if tags.has_key? tag
1115 1140 pcs = [tag]
1116 1141 tags[tag].each do |prop|
1117 1142 ['"', "'", ''].each do |q|
1118 1143 q2 = ( q != '' ? q : '\s' )
1119 1144 if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
1120 1145 attrv = $1
1121 1146 next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
1122 1147 pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
1123 1148 break
1124 1149 end
1125 1150 end
1126 1151 end if tags[tag]
1127 1152 "<#{raw[1]}#{pcs.join " "}>"
1128 1153 else
1129 1154 " "
1130 1155 end
1131 1156 end
1132 1157 end
1133 1158
1134 1159 ALLOWED_TAGS = %w(redpre pre code)
1135 1160
1136 1161 def escape_html_tags(text)
1137 1162 text.gsub!(%r{<(\/?([!\w]+)[^<>\n]*)(>?)}) {|m| ALLOWED_TAGS.include?($2) ? "<#{$1}#{$3}" : "&lt;#{$1}#{'&gt;' unless $3.blank?}" }
1138 1163 end
1139 1164 end
1140 1165
@@ -1,168 +1,168
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'redcloth'
19 19 require 'coderay'
20 20
21 21 module Redmine
22 22 module WikiFormatting
23 23
24 24 private
25 25
26 26 class TextileFormatter < RedCloth
27 27
28 28 # auto_link rule after textile rules so that it doesn't break !image_url! tags
29 29 RULES = [:textile, :block_markdown_rule, :inline_auto_link, :inline_auto_mailto, :inline_toc, :inline_macros]
30 30
31 31 def initialize(*args)
32 32 super
33 33 self.hard_breaks=true
34 34 self.no_span_caps=true
35 35 end
36 36
37 37 def to_html(*rules, &block)
38 38 @toc = []
39 39 @macros_runner = block
40 40 super(*RULES).to_s
41 41 end
42 42
43 43 private
44 44
45 45 # Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
46 46 # <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
47 47 def hard_break( text )
48 text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
48 text.gsub!( /(.)\n(?!\n|\Z|>| *(>? *[#*=]+(\s|$)|[{|]))/, "\\1<br />\n" ) if hard_breaks
49 49 end
50 50
51 51 # Patch to add code highlighting support to RedCloth
52 52 def smooth_offtags( text )
53 53 unless @pre_list.empty?
54 54 ## replace <pre> content
55 55 text.gsub!(/<redpre#(\d+)>/) do
56 56 content = @pre_list[$1.to_i]
57 57 if content.match(/<code\s+class="(\w+)">\s?(.+)/m)
58 58 content = "<code class=\"#{$1} CodeRay\">" +
59 59 CodeRay.scan($2, $1.downcase).html(:escape => false, :line_numbers => :inline)
60 60 end
61 61 content
62 62 end
63 63 end
64 64 end
65 65
66 66 # Patch to add 'table of content' support to RedCloth
67 67 def textile_p_withtoc(tag, atts, cite, content)
68 68 if tag =~ /^h(\d)$/
69 69 @toc << [$1.to_i, content]
70 70 end
71 71 content = "<a name=\"#{@toc.length}\" class=\"wiki-page\"></a>" + content
72 72 textile_p(tag, atts, cite, content)
73 73 end
74 74
75 75 alias :textile_h1 :textile_p_withtoc
76 76 alias :textile_h2 :textile_p_withtoc
77 77 alias :textile_h3 :textile_p_withtoc
78 78
79 79 def inline_toc(text)
80 80 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
81 81 div_class = 'toc'
82 82 div_class << ' right' if $1 == '>'
83 83 div_class << ' left' if $1 == '<'
84 84 out = "<div class=\"#{div_class}\">"
85 85 @toc.each_with_index do |heading, index|
86 86 # remove wiki links from the item
87 87 toc_item = heading.last.gsub(/(\[\[|\]\])/, '')
88 88 out << "<a href=\"##{index+1}\" class=\"heading#{heading.first}\">#{toc_item}</a>"
89 89 end
90 90 out << '</div>'
91 91 out
92 92 end
93 93 end
94 94
95 95 MACROS_RE = /
96 96 (!)? # escaping
97 97 (
98 98 \{\{ # opening tag
99 99 ([\w]+) # macro name
100 100 (\(([^\}]*)\))? # optional arguments
101 101 \}\} # closing tag
102 102 )
103 103 /x unless const_defined?(:MACROS_RE)
104 104
105 105 def inline_macros(text)
106 106 text.gsub!(MACROS_RE) do
107 107 esc, all, macro = $1, $2, $3.downcase
108 108 args = ($5 || '').split(',').each(&:strip)
109 109 if esc.nil?
110 110 begin
111 111 @macros_runner.call(macro, args)
112 112 rescue => e
113 113 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
114 114 end || all
115 115 else
116 116 all
117 117 end
118 118 end
119 119 end
120 120
121 121 AUTO_LINK_RE = %r{
122 122 ( # leading text
123 123 <\w+.*?>| # leading HTML tag, or
124 124 [^=<>!:'"/]| # leading punctuation, or
125 125 ^ # beginning of line
126 126 )
127 127 (
128 128 (?:https?://)| # protocol spec, or
129 129 (?:www\.) # www.*
130 130 )
131 131 (
132 132 (\S+?) # url
133 133 (\/)? # slash
134 134 )
135 135 ([^\w\=\/;]*?) # post
136 136 (?=<|\s|$)
137 137 }x unless const_defined?(:AUTO_LINK_RE)
138 138
139 139 # Turns all urls into clickable links (code from Rails).
140 140 def inline_auto_link(text)
141 141 text.gsub!(AUTO_LINK_RE) do
142 142 all, leading, proto, url, post = $&, $1, $2, $3, $6
143 143 if leading =~ /<a\s/i || leading =~ /![<>=]?/
144 144 # don't replace URL's that are already linked
145 145 # and URL's prefixed with ! !> !< != (textile images)
146 146 all
147 147 else
148 148 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
149 149 end
150 150 end
151 151 end
152 152
153 153 # Turns all email addresses into clickable links (code from Rails).
154 154 def inline_auto_mailto(text)
155 155 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
156 156 text = $1
157 157 %{<a href="mailto:#{$1}" class="email">#{text}</a>}
158 158 end
159 159 end
160 160 end
161 161
162 162 public
163 163
164 164 def self.to_html(text, options = {}, &block)
165 165 TextileFormatter.new(text).to_html(&block)
166 166 end
167 167 end
168 168 end
@@ -1,528 +1,559
1 1 /* ***** BEGIN LICENSE BLOCK *****
2 2 * This file is part of DotClear.
3 3 * Copyright (c) 2005 Nicolas Martin & Olivier Meunier and contributors. All
4 4 * rights reserved.
5 5 *
6 6 * DotClear is free software; you can redistribute it and/or modify
7 7 * it under the terms of the GNU General Public License as published by
8 8 * the Free Software Foundation; either version 2 of the License, or
9 9 * (at your option) any later version.
10 10 *
11 11 * DotClear is distributed in the hope that it will be useful,
12 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 * GNU General Public License for more details.
15 15 *
16 16 * You should have received a copy of the GNU General Public License
17 17 * along with DotClear; if not, write to the Free Software
18 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 19 *
20 20 * ***** END LICENSE BLOCK *****
21 21 */
22 22
23 23 /* Modified by JP LANG for textile formatting */
24 24
25 25 function jsToolBar(textarea) {
26 26 if (!document.createElement) { return; }
27 27
28 28 if (!textarea) { return; }
29 29
30 30 if ((typeof(document["selection"]) == "undefined")
31 31 && (typeof(textarea["setSelectionRange"]) == "undefined")) {
32 32 return;
33 33 }
34 34
35 35 this.textarea = textarea;
36 36
37 37 this.editor = document.createElement('div');
38 38 this.editor.className = 'jstEditor';
39 39
40 40 this.textarea.parentNode.insertBefore(this.editor,this.textarea);
41 41 this.editor.appendChild(this.textarea);
42 42
43 43 this.toolbar = document.createElement("div");
44 44 this.toolbar.className = 'jstElements';
45 45 this.editor.parentNode.insertBefore(this.toolbar,this.editor);
46 46
47 47 // Dragable resizing (only for gecko)
48 48 if (this.editor.addEventListener)
49 49 {
50 50 this.handle = document.createElement('div');
51 51 this.handle.className = 'jstHandle';
52 52 var dragStart = this.resizeDragStart;
53 53 var This = this;
54 54 this.handle.addEventListener('mousedown',function(event) { dragStart.call(This,event); },false);
55 55 // fix memory leak in Firefox (bug #241518)
56 56 window.addEventListener('unload',function() {
57 57 var del = This.handle.parentNode.removeChild(This.handle);
58 58 delete(This.handle);
59 59 },false);
60 60
61 61 this.editor.parentNode.insertBefore(this.handle,this.editor.nextSibling);
62 62 }
63 63
64 64 this.context = null;
65 65 this.toolNodes = {}; // lorsque la toolbar est dessinée , cet objet est garni
66 66 // de raccourcis vers les éléments DOM correspondants aux outils.
67 67 }
68 68
69 69 function jsButton(title, fn, scope, className) {
70 70 if(typeof jsToolBar.strings == 'undefined') {
71 71 this.title = title || null;
72 72 } else {
73 73 this.title = jsToolBar.strings[title] || title || null;
74 74 }
75 75 this.fn = fn || function(){};
76 76 this.scope = scope || null;
77 77 this.className = className || null;
78 78 }
79 79 jsButton.prototype.draw = function() {
80 80 if (!this.scope) return null;
81 81
82 82 var button = document.createElement('button');
83 83 button.setAttribute('type','button');
84 84 button.tabIndex = 200;
85 85 if (this.className) button.className = this.className;
86 86 button.title = this.title;
87 87 var span = document.createElement('span');
88 88 span.appendChild(document.createTextNode(this.title));
89 89 button.appendChild(span);
90 90
91 91 if (this.icon != undefined) {
92 92 button.style.backgroundImage = 'url('+this.icon+')';
93 93 }
94 94 if (typeof(this.fn) == 'function') {
95 95 var This = this;
96 96 button.onclick = function() { try { This.fn.apply(This.scope, arguments) } catch (e) {} return false; };
97 97 }
98 98 return button;
99 99 }
100 100
101 101 function jsSpace(id) {
102 102 this.id = id || null;
103 103 this.width = null;
104 104 }
105 105 jsSpace.prototype.draw = function() {
106 106 var span = document.createElement('span');
107 107 if (this.id) span.id = this.id;
108 108 span.appendChild(document.createTextNode(String.fromCharCode(160)));
109 109 span.className = 'jstSpacer';
110 110 if (this.width) span.style.marginRight = this.width+'px';
111 111
112 112 return span;
113 113 }
114 114
115 115 function jsCombo(title, options, scope, fn, className) {
116 116 this.title = title || null;
117 117 this.options = options || null;
118 118 this.scope = scope || null;
119 119 this.fn = fn || function(){};
120 120 this.className = className || null;
121 121 }
122 122 jsCombo.prototype.draw = function() {
123 123 if (!this.scope || !this.options) return null;
124 124
125 125 var select = document.createElement('select');
126 126 if (this.className) select.className = className;
127 127 select.title = this.title;
128 128
129 129 for (var o in this.options) {
130 130 //var opt = this.options[o];
131 131 var option = document.createElement('option');
132 132 option.value = o;
133 133 option.appendChild(document.createTextNode(this.options[o]));
134 134 select.appendChild(option);
135 135 }
136 136
137 137 var This = this;
138 138 select.onchange = function() {
139 139 try {
140 140 This.fn.call(This.scope, this.value);
141 141 } catch (e) { alert(e); }
142 142
143 143 return false;
144 144 }
145 145
146 146 return select;
147 147 }
148 148
149 149
150 150 jsToolBar.prototype = {
151 151 base_url: '',
152 152 mode: 'wiki',
153 153 elements: {},
154 154 help_link: '',
155 155
156 156 getMode: function() {
157 157 return this.mode;
158 158 },
159 159
160 160 setMode: function(mode) {
161 161 this.mode = mode || 'wiki';
162 162 },
163 163
164 164 switchMode: function(mode) {
165 165 mode = mode || 'wiki';
166 166 this.draw(mode);
167 167 },
168 168
169 169 setHelpLink: function(link) {
170 170 this.help_link = link;
171 171 },
172 172
173 173 button: function(toolName) {
174 174 var tool = this.elements[toolName];
175 175 if (typeof tool.fn[this.mode] != 'function') return null;
176 176 var b = new jsButton(tool.title, tool.fn[this.mode], this, 'jstb_'+toolName);
177 177 if (tool.icon != undefined) b.icon = tool.icon;
178 178 return b;
179 179 },
180 180 space: function(toolName) {
181 181 var tool = new jsSpace(toolName)
182 182 if (this.elements[toolName].width !== undefined)
183 183 tool.width = this.elements[toolName].width;
184 184 return tool;
185 185 },
186 186 combo: function(toolName) {
187 187 var tool = this.elements[toolName];
188 188 var length = tool[this.mode].list.length;
189 189
190 190 if (typeof tool[this.mode].fn != 'function' || length == 0) {
191 191 return null;
192 192 } else {
193 193 var options = {};
194 194 for (var i=0; i < length; i++) {
195 195 var opt = tool[this.mode].list[i];
196 196 options[opt] = tool.options[opt];
197 197 }
198 198 return new jsCombo(tool.title, options, this, tool[this.mode].fn);
199 199 }
200 200 },
201 201 draw: function(mode) {
202 202 this.setMode(mode);
203 203
204 204 // Empty toolbar
205 205 while (this.toolbar.hasChildNodes()) {
206 206 this.toolbar.removeChild(this.toolbar.firstChild)
207 207 }
208 208 this.toolNodes = {}; // vide les raccourcis DOM/**/
209 209
210 210 var h = document.createElement('div');
211 211 h.className = 'help'
212 212 h.innerHTML = this.help_link;
213 213 '<a href="/help/wiki_syntax.html" onclick="window.open(\'/help/wiki_syntax.html\', \'\', \'resizable=yes, location=no, width=300, height=640, menubar=no, status=no, scrollbars=yes\'); return false;">Aide</a>';
214 214 this.toolbar.appendChild(h);
215 215
216 216 // Draw toolbar elements
217 217 var b, tool, newTool;
218 218
219 219 for (var i in this.elements) {
220 220 b = this.elements[i];
221 221
222 222 var disabled =
223 223 b.type == undefined || b.type == ''
224 224 || (b.disabled != undefined && b.disabled)
225 225 || (b.context != undefined && b.context != null && b.context != this.context);
226 226
227 227 if (!disabled && typeof this[b.type] == 'function') {
228 228 tool = this[b.type](i);
229 229 if (tool) newTool = tool.draw();
230 230 if (newTool) {
231 231 this.toolNodes[i] = newTool; //mémorise l'accès DOM pour usage éventuel ultérieur
232 232 this.toolbar.appendChild(newTool);
233 233 }
234 234 }
235 235 }
236 236 },
237 237
238 238 singleTag: function(stag,etag) {
239 239 stag = stag || null;
240 240 etag = etag || stag;
241 241
242 242 if (!stag || !etag) { return; }
243 243
244 244 this.encloseSelection(stag,etag);
245 245 },
246 246
247 247 encloseLineSelection: function(prefix, suffix, fn) {
248 248 this.textarea.focus();
249 249
250 250 prefix = prefix || '';
251 251 suffix = suffix || '';
252 252
253 253 var start, end, sel, scrollPos, subst, res;
254 254
255 255 if (typeof(document["selection"]) != "undefined") {
256 256 sel = document.selection.createRange().text;
257 257 } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
258 258 start = this.textarea.selectionStart;
259 259 end = this.textarea.selectionEnd;
260 260 scrollPos = this.textarea.scrollTop;
261 261 // go to the start of the line
262 262 start = this.textarea.value.substring(0, start).replace(/[^\r\n]*$/g,'').length;
263 263 // go to the end of the line
264 264 end = this.textarea.value.length - this.textarea.value.substring(end, this.textarea.value.length).replace(/^[^\r\n]*/, '').length;
265 265 sel = this.textarea.value.substring(start, end);
266 266 }
267 267
268 268 if (sel.match(/ $/)) { // exclude ending space char, if any
269 269 sel = sel.substring(0, sel.length - 1);
270 270 suffix = suffix + " ";
271 271 }
272 272
273 273 if (typeof(fn) == 'function') {
274 274 res = (sel) ? fn.call(this,sel) : fn('');
275 275 } else {
276 276 res = (sel) ? sel : '';
277 277 }
278 278
279 279 subst = prefix + res + suffix;
280 280
281 281 if (typeof(document["selection"]) != "undefined") {
282 282 document.selection.createRange().text = subst;
283 283 var range = this.textarea.createTextRange();
284 284 range.collapse(false);
285 285 range.move('character', -suffix.length);
286 286 range.select();
287 287 } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
288 288 this.textarea.value = this.textarea.value.substring(0, start) + subst +
289 289 this.textarea.value.substring(end);
290 290 if (sel) {
291 291 this.textarea.setSelectionRange(start + subst.length, start + subst.length);
292 292 } else {
293 293 this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
294 294 }
295 295 this.textarea.scrollTop = scrollPos;
296 296 }
297 297 },
298 298
299 299 encloseSelection: function(prefix, suffix, fn) {
300 300 this.textarea.focus();
301 301
302 302 prefix = prefix || '';
303 303 suffix = suffix || '';
304 304
305 305 var start, end, sel, scrollPos, subst, res;
306 306
307 307 if (typeof(document["selection"]) != "undefined") {
308 308 sel = document.selection.createRange().text;
309 309 } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
310 310 start = this.textarea.selectionStart;
311 311 end = this.textarea.selectionEnd;
312 312 scrollPos = this.textarea.scrollTop;
313 313 sel = this.textarea.value.substring(start, end);
314 314 }
315 315
316 316 if (sel.match(/ $/)) { // exclude ending space char, if any
317 317 sel = sel.substring(0, sel.length - 1);
318 318 suffix = suffix + " ";
319 319 }
320 320
321 321 if (typeof(fn) == 'function') {
322 322 res = (sel) ? fn.call(this,sel) : fn('');
323 323 } else {
324 324 res = (sel) ? sel : '';
325 325 }
326 326
327 327 subst = prefix + res + suffix;
328 328
329 329 if (typeof(document["selection"]) != "undefined") {
330 330 document.selection.createRange().text = subst;
331 331 var range = this.textarea.createTextRange();
332 332 range.collapse(false);
333 333 range.move('character', -suffix.length);
334 334 range.select();
335 335 // this.textarea.caretPos -= suffix.length;
336 336 } else if (typeof(this.textarea["setSelectionRange"]) != "undefined") {
337 337 this.textarea.value = this.textarea.value.substring(0, start) + subst +
338 338 this.textarea.value.substring(end);
339 339 if (sel) {
340 340 this.textarea.setSelectionRange(start + subst.length, start + subst.length);
341 341 } else {
342 342 this.textarea.setSelectionRange(start + prefix.length, start + prefix.length);
343 343 }
344 344 this.textarea.scrollTop = scrollPos;
345 345 }
346 346 },
347 347
348 348 stripBaseURL: function(url) {
349 349 if (this.base_url != '') {
350 350 var pos = url.indexOf(this.base_url);
351 351 if (pos == 0) {
352 352 url = url.substr(this.base_url.length);
353 353 }
354 354 }
355 355
356 356 return url;
357 357 }
358 358 };
359 359
360 360 /** Resizer
361 361 -------------------------------------------------------- */
362 362 jsToolBar.prototype.resizeSetStartH = function() {
363 363 this.dragStartH = this.textarea.offsetHeight + 0;
364 364 };
365 365 jsToolBar.prototype.resizeDragStart = function(event) {
366 366 var This = this;
367 367 this.dragStartY = event.clientY;
368 368 this.resizeSetStartH();
369 369 document.addEventListener('mousemove', this.dragMoveHdlr=function(event){This.resizeDragMove(event);}, false);
370 370 document.addEventListener('mouseup', this.dragStopHdlr=function(event){This.resizeDragStop(event);}, false);
371 371 };
372 372
373 373 jsToolBar.prototype.resizeDragMove = function(event) {
374 374 this.textarea.style.height = (this.dragStartH+event.clientY-this.dragStartY)+'px';
375 375 };
376 376
377 377 jsToolBar.prototype.resizeDragStop = function(event) {
378 378 document.removeEventListener('mousemove', this.dragMoveHdlr, false);
379 379 document.removeEventListener('mouseup', this.dragStopHdlr, false);
380 380 };
381 381
382 382 // Elements definition ------------------------------------
383 383
384 384 // strong
385 385 jsToolBar.prototype.elements.strong = {
386 386 type: 'button',
387 387 title: 'Strong',
388 388 fn: {
389 389 wiki: function() { this.singleTag('*') }
390 390 }
391 391 }
392 392
393 393 // em
394 394 jsToolBar.prototype.elements.em = {
395 395 type: 'button',
396 396 title: 'Italic',
397 397 fn: {
398 398 wiki: function() { this.singleTag("_") }
399 399 }
400 400 }
401 401
402 402 // ins
403 403 jsToolBar.prototype.elements.ins = {
404 404 type: 'button',
405 405 title: 'Underline',
406 406 fn: {
407 407 wiki: function() { this.singleTag('+') }
408 408 }
409 409 }
410 410
411 411 // del
412 412 jsToolBar.prototype.elements.del = {
413 413 type: 'button',
414 414 title: 'Deleted',
415 415 fn: {
416 416 wiki: function() { this.singleTag('-') }
417 417 }
418 418 }
419 419
420 420 // code
421 421 jsToolBar.prototype.elements.code = {
422 422 type: 'button',
423 423 title: 'Code',
424 424 fn: {
425 425 wiki: function() { this.singleTag('@') }
426 426 }
427 427 }
428 428
429 429 // spacer
430 430 jsToolBar.prototype.elements.space1 = {type: 'space'}
431 431
432 432 // headings
433 433 jsToolBar.prototype.elements.h1 = {
434 434 type: 'button',
435 435 title: 'Heading 1',
436 436 fn: {
437 437 wiki: function() {
438 438 this.encloseLineSelection('h1. ', '',function(str) {
439 439 str = str.replace(/^h\d+\.\s+/, '')
440 440 return str;
441 441 });
442 442 }
443 443 }
444 444 }
445 445 jsToolBar.prototype.elements.h2 = {
446 446 type: 'button',
447 447 title: 'Heading 2',
448 448 fn: {
449 449 wiki: function() {
450 450 this.encloseLineSelection('h2. ', '',function(str) {
451 451 str = str.replace(/^h\d+\.\s+/, '')
452 452 return str;
453 453 });
454 454 }
455 455 }
456 456 }
457 457 jsToolBar.prototype.elements.h3 = {
458 458 type: 'button',
459 459 title: 'Heading 3',
460 460 fn: {
461 461 wiki: function() {
462 462 this.encloseLineSelection('h3. ', '',function(str) {
463 463 str = str.replace(/^h\d+\.\s+/, '')
464 464 return str;
465 465 });
466 466 }
467 467 }
468 468 }
469 469
470 470 // spacer
471 471 jsToolBar.prototype.elements.space2 = {type: 'space'}
472 472
473 473 // ul
474 474 jsToolBar.prototype.elements.ul = {
475 475 type: 'button',
476 476 title: 'Unordered list',
477 477 fn: {
478 478 wiki: function() {
479 479 this.encloseLineSelection('','',function(str) {
480 480 str = str.replace(/\r/g,'');
481 481 return str.replace(/(\n|^)[#-]?\s*/g,"$1* ");
482 482 });
483 483 }
484 484 }
485 485 }
486 486
487 487 // ol
488 488 jsToolBar.prototype.elements.ol = {
489 489 type: 'button',
490 490 title: 'Ordered list',
491 491 fn: {
492 492 wiki: function() {
493 493 this.encloseLineSelection('','',function(str) {
494 494 str = str.replace(/\r/g,'');
495 495 return str.replace(/(\n|^)[*-]?\s*/g,"$1# ");
496 496 });
497 497 }
498 498 }
499 499 }
500 500
501 // spacer
502 jsToolBar.prototype.elements.space3 = {type: 'space'}
503
504 // bq
505 jsToolBar.prototype.elements.bq = {
506 type: 'button',
507 title: 'Quote',
508 fn: {
509 wiki: function() {
510 this.encloseLineSelection('','',function(str) {
511 str = str.replace(/\r/g,'');
512 return str.replace(/(\n|^) *([^\n]*)/g,"$1> $2");
513 });
514 }
515 }
516 }
517
518 // unbq
519 jsToolBar.prototype.elements.unbq = {
520 type: 'button',
521 title: 'Unquote',
522 fn: {
523 wiki: function() {
524 this.encloseLineSelection('','',function(str) {
525 str = str.replace(/\r/g,'');
526 return str.replace(/(\n|^) *[>]? *([^\n]*)/g,"$1$2");
527 });
528 }
529 }
530 }
531
501 532 // pre
502 533 jsToolBar.prototype.elements.pre = {
503 534 type: 'button',
504 535 title: 'Preformatted text',
505 536 fn: {
506 537 wiki: function() { this.encloseLineSelection('<pre>\n', '\n</pre>') }
507 538 }
508 539 }
509 540
510 541 // spacer
511 jsToolBar.prototype.elements.space3 = {type: 'space'}
542 jsToolBar.prototype.elements.space4 = {type: 'space'}
512 543
513 544 // wiki page
514 545 jsToolBar.prototype.elements.link = {
515 546 type: 'button',
516 547 title: 'Wiki link',
517 548 fn: {
518 549 wiki: function() { this.encloseSelection("[[", "]]") }
519 550 }
520 551 }
521 552 // image
522 553 jsToolBar.prototype.elements.img = {
523 554 type: 'button',
524 555 title: 'Image',
525 556 fn: {
526 557 wiki: function() { this.encloseSelection("!", "!") }
527 558 }
528 559 }
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Tučné';
3 3 jsToolBar.strings['Italic'] = 'Kurzíva';
4 4 jsToolBar.strings['Underline'] = 'Podtržené';
5 5 jsToolBar.strings['Deleted'] = 'Přeškrtnuté ';
6 6 jsToolBar.strings['Code'] = 'Zobrazení kódu';
7 7 jsToolBar.strings['Heading 1'] = 'Záhlaví 1';
8 8 jsToolBar.strings['Heading 2'] = 'Záhlaví 2';
9 9 jsToolBar.strings['Heading 3'] = 'Záhlaví 3';
10 10 jsToolBar.strings['Unordered list'] = 'Seznam';
11 11 jsToolBar.strings['Ordered list'] = 'Uspořádaný seznam';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Předformátovaný text';
13 15 jsToolBar.strings['Wiki link'] = 'Vložit odkaz na Wiki stránku';
14 16 jsToolBar.strings['Image'] = 'Vložit obrázek';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Fed';
3 3 jsToolBar.strings['Italic'] = 'Kursiv';
4 4 jsToolBar.strings['Underline'] = 'Underskrevet';
5 5 jsToolBar.strings['Deleted'] = 'Slettet';
6 6 jsToolBar.strings['Code'] = 'Inline Kode';
7 7 jsToolBar.strings['Heading 1'] = 'Overskrift 1';
8 8 jsToolBar.strings['Heading 2'] = 'Overskrift 2';
9 9 jsToolBar.strings['Heading 3'] = 'Overskrift 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unummereret list';
11 11 jsToolBar.strings['Ordered list'] = 'Nummereret list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatteret tekst';
13 15 jsToolBar.strings['Wiki link'] = 'Link til en Wiki side';
14 16 jsToolBar.strings['Image'] = 'Billede';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Fett';
3 3 jsToolBar.strings['Italic'] = 'Kursiv';
4 4 jsToolBar.strings['Underline'] = 'Unterstrichen';
5 5 jsToolBar.strings['Deleted'] = 'Durchgestrichen';
6 6 jsToolBar.strings['Code'] = 'Quelltext';
7 7 jsToolBar.strings['Heading 1'] = 'Überschrift 1. Ordnung';
8 8 jsToolBar.strings['Heading 2'] = 'Überschrift 2. Ordnung';
9 9 jsToolBar.strings['Heading 3'] = 'Überschrift 3. Ordnung';
10 10 jsToolBar.strings['Unordered list'] = 'Aufzählungsliste';
11 11 jsToolBar.strings['Ordered list'] = 'Nummerierte Liste';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Präformatierter Text';
13 15 jsToolBar.strings['Wiki link'] = 'Verweis (Link) zu einer Wiki-Seite';
14 16 jsToolBar.strings['Image'] = 'Grafik';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Lihavoitu';
3 3 jsToolBar.strings['Italic'] = 'Kursivoitu';
4 4 jsToolBar.strings['Underline'] = 'Alleviivattu';
5 5 jsToolBar.strings['Deleted'] = 'Yliviivattu';
6 6 jsToolBar.strings['Code'] = 'Koodi näkymä';
7 7 jsToolBar.strings['Heading 1'] = 'Otsikko 1';
8 8 jsToolBar.strings['Heading 2'] = 'Otsikko 2';
9 9 jsToolBar.strings['Heading 3'] = 'Otsikko 3';
10 10 jsToolBar.strings['Unordered list'] = 'Järjestämätön lista';
11 11 jsToolBar.strings['Ordered list'] = 'Järjestetty lista';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Ennaltamuotoiltu teksti';
13 15 jsToolBar.strings['Wiki link'] = 'Linkki Wiki sivulle';
14 16 jsToolBar.strings['Image'] = 'Kuva';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Gras';
3 3 jsToolBar.strings['Italic'] = 'Italique';
4 4 jsToolBar.strings['Underline'] = 'Souligné';
5 5 jsToolBar.strings['Deleted'] = 'Rayé';
6 6 jsToolBar.strings['Code'] = 'Code en ligne';
7 7 jsToolBar.strings['Heading 1'] = 'Titre niveau 1';
8 8 jsToolBar.strings['Heading 2'] = 'Titre niveau 2';
9 9 jsToolBar.strings['Heading 3'] = 'Titre niveau 3';
10 10 jsToolBar.strings['Unordered list'] = 'Liste à puces';
11 11 jsToolBar.strings['Ordered list'] = 'Liste numérotée';
12 jsToolBar.strings['Quote'] = 'Citer';
13 jsToolBar.strings['Unquote'] = 'Supprimer citation';
12 14 jsToolBar.strings['Preformatted text'] = 'Texte préformaté';
13 15 jsToolBar.strings['Wiki link'] = 'Lien vers une page Wiki';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Félkövér';
3 3 jsToolBar.strings['Italic'] = 'Dőlt';
4 4 jsToolBar.strings['Underline'] = 'Aláhúzott';
5 5 jsToolBar.strings['Deleted'] = 'Törölt';
6 6 jsToolBar.strings['Code'] = 'Kód sorok';
7 7 jsToolBar.strings['Heading 1'] = 'Fejléc 1';
8 8 jsToolBar.strings['Heading 2'] = 'Fejléc 2';
9 9 jsToolBar.strings['Heading 3'] = 'Fejléc 3';
10 10 jsToolBar.strings['Unordered list'] = 'Felsorolás';
11 11 jsToolBar.strings['Ordered list'] = 'Számozott lista';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Előreformázott szöveg';
13 15 jsToolBar.strings['Wiki link'] = 'Link egy Wiki oldalra';
14 16 jsToolBar.strings['Image'] = 'Kép';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = '強調';
3 3 jsToolBar.strings['Italic'] = '斜体';
4 4 jsToolBar.strings['Underline'] = '下線';
5 5 jsToolBar.strings['Deleted'] = '取り消し線';
6 6 jsToolBar.strings['Code'] = 'コード';
7 7 jsToolBar.strings['Heading 1'] = '見出し 1';
8 8 jsToolBar.strings['Heading 2'] = '見出し 2';
9 9 jsToolBar.strings['Heading 3'] = '見出し 3';
10 10 jsToolBar.strings['Unordered list'] = '順不同リスト';
11 11 jsToolBar.strings['Ordered list'] = '番号つきリスト';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = '整形済みテキスト';
13 15 jsToolBar.strings['Wiki link'] = 'Wiki ページへのリンク';
14 16 jsToolBar.strings['Image'] = '画像';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Pastorinti';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Pabraukti';
5 5 jsToolBar.strings['Deleted'] = 'Užbraukti';
6 6 jsToolBar.strings['Code'] = 'Kodas';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Nenumeruotas sąrašas';
11 11 jsToolBar.strings['Ordered list'] = 'Numeruotas sąrašas';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatuotas tekstas';
13 15 jsToolBar.strings['Wiki link'] = 'Nuoroda į Wiki puslapį';
14 16 jsToolBar.strings['Image'] = 'Paveikslas';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Fet';
3 3 jsToolBar.strings['Italic'] = 'Kursiv';
4 4 jsToolBar.strings['Underline'] = 'Understreking';
5 5 jsToolBar.strings['Deleted'] = 'Slettet';
6 6 jsToolBar.strings['Code'] = 'Kode';
7 7 jsToolBar.strings['Heading 1'] = 'Overskrift 1';
8 8 jsToolBar.strings['Heading 2'] = 'Overskrift 2';
9 9 jsToolBar.strings['Heading 3'] = 'Overskrift 3';
10 10 jsToolBar.strings['Unordered list'] = 'Punktliste';
11 11 jsToolBar.strings['Ordered list'] = 'Nummerert liste';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatert tekst';
13 15 jsToolBar.strings['Wiki link'] = 'Lenke til Wiki-side';
14 16 jsToolBar.strings['Image'] = 'Bilde';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,16 +1,18
1 1 // Translated by: Alexandre da Silva <simpsomboy@gmail.com>
2 2
3 3 jsToolBar.strings = {};
4 4 jsToolBar.strings['Strong'] = 'Negrito';
5 5 jsToolBar.strings['Italic'] = 'Itálico';
6 6 jsToolBar.strings['Underline'] = 'Sublinhado';
7 7 jsToolBar.strings['Deleted'] = 'Excluído';
8 8 jsToolBar.strings['Code'] = 'Código Inline';
9 9 jsToolBar.strings['Heading 1'] = 'Cabeçalho 1';
10 10 jsToolBar.strings['Heading 2'] = 'Cabeçalho 2';
11 11 jsToolBar.strings['Heading 3'] = 'Cabeçalho 3';
12 12 jsToolBar.strings['Unordered list'] = 'Lista não ordenada';
13 13 jsToolBar.strings['Ordered list'] = 'Lista ordenada';
14 jsToolBar.strings['Quote'] = 'Quote';
15 jsToolBar.strings['Unquote'] = 'Remove Quote';
14 16 jsToolBar.strings['Preformatted text'] = 'Texto pré-formatado';
15 17 jsToolBar.strings['Wiki link'] = 'Link para uma página Wiki';
16 18 jsToolBar.strings['Image'] = 'Imagem';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Жирный';
3 3 jsToolBar.strings['Italic'] = 'Курсив';
4 4 jsToolBar.strings['Underline'] = 'Подчеркнутый';
5 5 jsToolBar.strings['Deleted'] = 'Зачеркнутый';
6 6 jsToolBar.strings['Code'] = 'Вставка кода';
7 7 jsToolBar.strings['Heading 1'] = 'Заголовок 1';
8 8 jsToolBar.strings['Heading 2'] = 'Заголовок 2';
9 9 jsToolBar.strings['Heading 3'] = 'Заголовок 3';
10 10 jsToolBar.strings['Unordered list'] = 'Маркированный список';
11 11 jsToolBar.strings['Ordered list'] = 'Нумерованный список';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Заранее форматированный текст';
13 15 jsToolBar.strings['Wiki link'] = 'Ссылка на страницу в Wiki';
14 16 jsToolBar.strings['Image'] = 'Вставка изображения';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'หนา';
3 3 jsToolBar.strings['Italic'] = 'เอียง';
4 4 jsToolBar.strings['Underline'] = 'ขีดเส้นใต้';
5 5 jsToolBar.strings['Deleted'] = 'ขีดฆ่า';
6 6 jsToolBar.strings['Code'] = 'โค๊ดโปรแกรม';
7 7 jsToolBar.strings['Heading 1'] = 'หัวข้อ 1';
8 8 jsToolBar.strings['Heading 2'] = 'หัวข้อ 2';
9 9 jsToolBar.strings['Heading 3'] = 'หัวข้อ 3';
10 10 jsToolBar.strings['Unordered list'] = 'รายการ';
11 11 jsToolBar.strings['Ordered list'] = 'ลำดับเลข';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'รูปแบบข้อความคงที่';
13 15 jsToolBar.strings['Wiki link'] = 'เชื่อมโยงไปหน้า Wiki อื่น';
14 16 jsToolBar.strings['Image'] = 'รูปภาพ';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = '粗體';
3 3 jsToolBar.strings['Italic'] = '斜體';
4 4 jsToolBar.strings['Underline'] = '底線';
5 5 jsToolBar.strings['Deleted'] = '刪除線';
6 6 jsToolBar.strings['Code'] = '程式碼';
7 7 jsToolBar.strings['Heading 1'] = '標題 1';
8 8 jsToolBar.strings['Heading 2'] = '標題 2';
9 9 jsToolBar.strings['Heading 3'] = '標題 3';
10 10 jsToolBar.strings['Unordered list'] = '項目清單';
11 11 jsToolBar.strings['Ordered list'] = '編號清單';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = '格式化文字';
13 15 jsToolBar.strings['Wiki link'] = '連結至 Wiki 頁面';
14 16 jsToolBar.strings['Image'] = '圖片';
@@ -1,14 +1,16
1 1 jsToolBar.strings = {};
2 2 jsToolBar.strings['Strong'] = 'Strong';
3 3 jsToolBar.strings['Italic'] = 'Italic';
4 4 jsToolBar.strings['Underline'] = 'Underline';
5 5 jsToolBar.strings['Deleted'] = 'Deleted';
6 6 jsToolBar.strings['Code'] = 'Inline Code';
7 7 jsToolBar.strings['Heading 1'] = 'Heading 1';
8 8 jsToolBar.strings['Heading 2'] = 'Heading 2';
9 9 jsToolBar.strings['Heading 3'] = 'Heading 3';
10 10 jsToolBar.strings['Unordered list'] = 'Unordered list';
11 11 jsToolBar.strings['Ordered list'] = 'Ordered list';
12 jsToolBar.strings['Quote'] = 'Quote';
13 jsToolBar.strings['Unquote'] = 'Remove Quote';
12 14 jsToolBar.strings['Preformatted text'] = 'Preformatted text';
13 15 jsToolBar.strings['Wiki link'] = 'Link to a Wiki page';
14 16 jsToolBar.strings['Image'] = 'Image';
@@ -1,602 +1,604
1 1 body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; }
2 2
3 3 h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;}
4 4 h1 {margin:0; padding:0; font-size: 24px;}
5 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 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 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 9 /***** Layout *****/
10 10 #wrapper {background: white;}
11 11
12 12 #top-menu {background: #2C4056; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
13 13 #top-menu ul {margin: 0; padding: 0;}
14 14 #top-menu li {
15 15 float:left;
16 16 list-style-type:none;
17 17 margin: 0px 0px 0px 0px;
18 18 padding: 0px 0px 0px 0px;
19 19 white-space:nowrap;
20 20 }
21 21 #top-menu a {color: #fff; padding-right: 8px; font-weight: bold;}
22 22 #top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; }
23 23
24 24 #account {float:right;}
25 25
26 26 #header {height:5.3em;margin:0;background-color:#507AAA;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;}
27 27 #header a {color:#f8f8f8;}
28 28 #quick-search {float:right;}
29 29
30 30 #main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px;}
31 31 #main-menu ul {margin: 0; padding: 0;}
32 32 #main-menu li {
33 33 float:left;
34 34 list-style-type:none;
35 35 margin: 0px 2px 0px 0px;
36 36 padding: 0px 0px 0px 0px;
37 37 white-space:nowrap;
38 38 }
39 39 #main-menu li a {
40 40 display: block;
41 41 color: #fff;
42 42 text-decoration: none;
43 43 font-weight: bold;
44 44 margin: 0;
45 45 padding: 4px 10px 4px 10px;
46 46 }
47 47 #main-menu li a:hover {background:#759FCF; color:#fff;}
48 48 #main-menu li a.selected, #main-menu li a.selected:hover {background:#fff; color:#555;}
49 49
50 50 #main {background-color:#EEEEEE;}
51 51
52 52 #sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;}
53 53 * html #sidebar{ width: 17%; }
54 54 #sidebar h3{ font-size: 14px; margin-top:14px; color: #666; }
55 55 #sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; }
56 56 * html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; }
57 57
58 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 59 * html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;}
60 60 html>body #content { height: auto; min-height: 600px; overflow: auto; }
61 61
62 62 #main.nosidebar #sidebar{ display: none; }
63 63 #main.nosidebar #content{ width: auto; border-right: 0; }
64 64
65 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 67 #login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; }
68 68 #login-form table td {padding: 6px;}
69 69 #login-form label {font-weight: bold;}
70 70
71 71 .clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; }
72 72
73 73 /***** Links *****/
74 74 a, a:link, a:visited{ color: #2A5685; text-decoration: none; }
75 75 a:hover, a:active{ color: #c61a1a; text-decoration: underline;}
76 76 a img{ border: 0; }
77 77
78 78 a.issue.closed, .issue.closed a { text-decoration: line-through; }
79 79
80 80 /***** Tables *****/
81 81 table.list { border: 1px solid #e4e4e4; border-collapse: collapse; width: 100%; margin-bottom: 4px; }
82 82 table.list th { background-color:#EEEEEE; padding: 4px; white-space:nowrap; }
83 83 table.list td { vertical-align: top; }
84 84 table.list td.id { width: 2%; text-align: center;}
85 85 table.list td.checkbox { width: 15px; padding: 0px;}
86 86
87 87 table.list.issues { margin-top: 10px; }
88 88 tr.issue { text-align: center; white-space: nowrap; }
89 89 tr.issue td.subject, tr.issue td.category { white-space: normal; }
90 90 tr.issue td.subject { text-align: left; }
91 91 tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;}
92 92
93 93 tr.entry { border: 1px solid #f8f8f8; }
94 94 tr.entry td { white-space: nowrap; }
95 95 tr.entry td.filename { width: 30%; }
96 96 tr.entry td.size { text-align: right; font-size: 90%; }
97 97 tr.entry td.revision, tr.entry td.author { text-align: center; }
98 98 tr.entry td.age { text-align: right; }
99 99
100 100 tr.changeset td.author { text-align: center; width: 15%; }
101 101 tr.changeset td.committed_on { text-align: center; width: 15%; }
102 102
103 103 tr.message { height: 2.6em; }
104 104 tr.message td.last_message { font-size: 80%; }
105 105 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
106 106 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
107 107
108 108 tr.user td { width:13%; }
109 109 tr.user td.email { width:18%; }
110 110 tr.user td { white-space: nowrap; }
111 111 tr.user.locked, tr.user.registered { color: #aaa; }
112 112 tr.user.locked a, tr.user.registered a { color: #aaa; }
113 113
114 114 tr.time-entry { text-align: center; white-space: nowrap; }
115 115 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
116 116 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
117 117 td.hours .hours-dec { font-size: 0.9em; }
118 118
119 119 table.list tbody tr:hover { background-color:#ffffdd; }
120 120 table td {padding:2px;}
121 121 table p {margin:0;}
122 122 .odd {background-color:#f6f7f8;}
123 123 .even {background-color: #fff;}
124 124
125 125 .highlight { background-color: #FCFD8D;}
126 126 .highlight.token-1 { background-color: #faa;}
127 127 .highlight.token-2 { background-color: #afa;}
128 128 .highlight.token-3 { background-color: #aaf;}
129 129
130 130 .box{
131 131 padding:6px;
132 132 margin-bottom: 10px;
133 133 background-color:#f6f6f6;
134 134 color:#505050;
135 135 line-height:1.5em;
136 136 border: 1px solid #e4e4e4;
137 137 }
138 138
139 139 div.square {
140 140 border: 1px solid #999;
141 141 float: left;
142 142 margin: .3em .4em 0 .4em;
143 143 overflow: hidden;
144 144 width: .6em; height: .6em;
145 145 }
146 146 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
147 147 .contextual input {font-size:0.9em;}
148 148
149 149 .splitcontentleft{float:left; width:49%;}
150 150 .splitcontentright{float:right; width:49%;}
151 151 form {display: inline;}
152 152 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
153 153 fieldset {border: 1px solid #e4e4e4; margin:0;}
154 154 legend {color: #484848;}
155 155 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
156 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
157 blockquote blockquote { margin-left: 0;}
156 158 textarea.wiki-edit { width: 99%; }
157 159 li p {margin-top: 0;}
158 160 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
159 161 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
160 162 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
161 163
162 164 fieldset#filters { padding: 0.7em; }
163 165 fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
164 166 fieldset#filters .buttons { font-size: 0.9em; }
165 167 fieldset#filters table { border-collapse: collapse; }
166 168 fieldset#filters table td { padding: 0; vertical-align: middle; }
167 169 fieldset#filters tr.filter { height: 2em; }
168 170 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
169 171
170 172 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
171 173 div#issue-changesets .changeset { padding: 4px;}
172 174 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
173 175 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
174 176
175 177 div#activity dl, #search-results { margin-left: 2em; }
176 178 div#activity dd { margin-bottom: 1em; padding-left: 18px; }
177 179 div#activity dt, #search-results dt { margin-bottom: 1px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
178 180 div#activity dt .time { color: #777; font-size: 80%; }
179 181 div#activity dd .description, #search-results dd .description { font-style: italic; }
180 182 div#activity span.project:after, #search-results span.project:after { content: " -"; }
181 183 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px;}
182 184 div#activity dd span.description, #search-results dd span.description { display:block; }
183 185
184 186 dt.issue { background-image: url(../images/ticket.png); }
185 187 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
186 188 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
187 189 dt.changeset { background-image: url(../images/changeset.png); }
188 190 dt.news { background-image: url(../images/news.png); }
189 191 dt.message { background-image: url(../images/message.png); }
190 192 dt.reply { background-image: url(../images/comments.png); }
191 193 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
192 194 dt.attachment { background-image: url(../images/attachment.png); }
193 195 dt.document { background-image: url(../images/document.png); }
194 196 dt.project { background-image: url(../images/projects.png); }
195 197
196 198 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
197 199 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
198 200 div#roadmap .wiki h1:first-child { display: none; }
199 201 div#roadmap .wiki h1 { font-size: 120%; }
200 202 div#roadmap .wiki h2 { font-size: 110%; }
201 203
202 204 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
203 205 div#version-summary fieldset { margin-bottom: 1em; }
204 206 div#version-summary .total-hours { text-align: right; }
205 207
206 208 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
207 209 table#time-report tbody tr { font-style: italic; color: #777; }
208 210 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
209 211 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
210 212 table#time-report .hours-dec { font-size: 0.9em; }
211 213
212 214 .total-hours { font-size: 110%; font-weight: bold; }
213 215 .total-hours span.hours-int { font-size: 120%; }
214 216
215 217 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
216 218 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
217 219
218 220 .pagination {font-size: 90%}
219 221 p.pagination {margin-top:8px;}
220 222
221 223 /***** Tabular forms ******/
222 224 .tabular p{
223 225 margin: 0;
224 226 padding: 5px 0 8px 0;
225 227 padding-left: 180px; /*width of left column containing the label elements*/
226 228 height: 1%;
227 229 clear:left;
228 230 }
229 231
230 232 html>body .tabular p {overflow:auto;}
231 233
232 234 .tabular label{
233 235 font-weight: bold;
234 236 float: left;
235 237 text-align: right;
236 238 margin-left: -180px; /*width of left column*/
237 239 width: 175px; /*width of labels. Should be smaller than left column to create some right
238 240 margin*/
239 241 }
240 242
241 243 .tabular label.floating{
242 244 font-weight: normal;
243 245 margin-left: 0px;
244 246 text-align: left;
245 247 width: 200px;
246 248 }
247 249
248 250 input#time_entry_comments { width: 90%;}
249 251
250 252 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
251 253
252 254 .tabular.settings p{ padding-left: 300px; }
253 255 .tabular.settings label{ margin-left: -300px; width: 295px; }
254 256
255 257 .required {color: #bb0000;}
256 258 .summary {font-style: italic;}
257 259
258 260 #attachments_fields input[type=text] {margin-left: 8px; }
259 261
260 262 div.attachments p { margin:4px 0 2px 0; }
261 263 div.attachments img { vertical-align: middle; }
262 264 div.attachments span.author { font-size: 0.9em; color: #888; }
263 265
264 266 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
265 267 .other-formats span + span:before { content: "| "; }
266 268
267 269 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
268 270
269 271 /***** Flash & error messages ****/
270 272 #errorExplanation, div.flash, .nodata, .warning {
271 273 padding: 4px 4px 4px 30px;
272 274 margin-bottom: 12px;
273 275 font-size: 1.1em;
274 276 border: 2px solid;
275 277 }
276 278
277 279 div.flash {margin-top: 8px;}
278 280
279 281 div.flash.error, #errorExplanation {
280 282 background: url(../images/false.png) 8px 5px no-repeat;
281 283 background-color: #ffe3e3;
282 284 border-color: #dd0000;
283 285 color: #550000;
284 286 }
285 287
286 288 div.flash.notice {
287 289 background: url(../images/true.png) 8px 5px no-repeat;
288 290 background-color: #dfffdf;
289 291 border-color: #9fcf9f;
290 292 color: #005f00;
291 293 }
292 294
293 295 .nodata, .warning {
294 296 text-align: center;
295 297 background-color: #FFEBC1;
296 298 border-color: #FDBF3B;
297 299 color: #A6750C;
298 300 }
299 301
300 302 #errorExplanation ul { font-size: 0.9em;}
301 303
302 304 /***** Ajax indicator ******/
303 305 #ajax-indicator {
304 306 position: absolute; /* fixed not supported by IE */
305 307 background-color:#eee;
306 308 border: 1px solid #bbb;
307 309 top:35%;
308 310 left:40%;
309 311 width:20%;
310 312 font-weight:bold;
311 313 text-align:center;
312 314 padding:0.6em;
313 315 z-index:100;
314 316 filter:alpha(opacity=50);
315 317 opacity: 0.5;
316 318 }
317 319
318 320 html>body #ajax-indicator { position: fixed; }
319 321
320 322 #ajax-indicator span {
321 323 background-position: 0% 40%;
322 324 background-repeat: no-repeat;
323 325 background-image: url(../images/loading.gif);
324 326 padding-left: 26px;
325 327 vertical-align: bottom;
326 328 }
327 329
328 330 /***** Calendar *****/
329 331 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
330 332 table.cal thead th {width: 14%;}
331 333 table.cal tbody tr {height: 100px;}
332 334 table.cal th { background-color:#EEEEEE; padding: 4px; }
333 335 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
334 336 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
335 337 table.cal td.odd p.day-num {color: #bbb;}
336 338 table.cal td.today {background:#ffffdd;}
337 339 table.cal td.today p.day-num {font-weight: bold;}
338 340
339 341 /***** Tooltips ******/
340 342 .tooltip{position:relative;z-index:24;}
341 343 .tooltip:hover{z-index:25;color:#000;}
342 344 .tooltip span.tip{display: none; text-align:left;}
343 345
344 346 div.tooltip:hover span.tip{
345 347 display:block;
346 348 position:absolute;
347 349 top:12px; left:24px; width:270px;
348 350 border:1px solid #555;
349 351 background-color:#fff;
350 352 padding: 4px;
351 353 font-size: 0.8em;
352 354 color:#505050;
353 355 }
354 356
355 357 /***** Progress bar *****/
356 358 table.progress {
357 359 border: 1px solid #D7D7D7;
358 360 border-collapse: collapse;
359 361 border-spacing: 0pt;
360 362 empty-cells: show;
361 363 text-align: center;
362 364 float:left;
363 365 margin: 1px 6px 1px 0px;
364 366 }
365 367
366 368 table.progress td { height: 0.9em; }
367 369 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
368 370 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
369 371 table.progress td.open { background: #FFF none repeat scroll 0%; }
370 372 p.pourcent {font-size: 80%;}
371 373 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
372 374
373 375 /***** Tabs *****/
374 376 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
375 377 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
376 378 #content .tabs>ul { bottom:-1px; } /* others */
377 379 #content .tabs ul li {
378 380 float:left;
379 381 list-style-type:none;
380 382 white-space:nowrap;
381 383 margin-right:8px;
382 384 background:#fff;
383 385 }
384 386 #content .tabs ul li a{
385 387 display:block;
386 388 font-size: 0.9em;
387 389 text-decoration:none;
388 390 line-height:1.3em;
389 391 padding:4px 6px 4px 6px;
390 392 border: 1px solid #ccc;
391 393 border-bottom: 1px solid #bbbbbb;
392 394 background-color: #eeeeee;
393 395 color:#777;
394 396 font-weight:bold;
395 397 }
396 398
397 399 #content .tabs ul li a:hover {
398 400 background-color: #ffffdd;
399 401 text-decoration:none;
400 402 }
401 403
402 404 #content .tabs ul li a.selected {
403 405 background-color: #fff;
404 406 border: 1px solid #bbbbbb;
405 407 border-bottom: 1px solid #fff;
406 408 }
407 409
408 410 #content .tabs ul li a.selected:hover {
409 411 background-color: #fff;
410 412 }
411 413
412 414 /***** Diff *****/
413 415 .diff_out { background: #fcc; }
414 416 .diff_in { background: #cfc; }
415 417
416 418 /***** Wiki *****/
417 419 div.wiki table {
418 420 border: 1px solid #505050;
419 421 border-collapse: collapse;
420 422 margin-bottom: 1em;
421 423 }
422 424
423 425 div.wiki table, div.wiki td, div.wiki th {
424 426 border: 1px solid #bbb;
425 427 padding: 4px;
426 428 }
427 429
428 430 div.wiki .external {
429 431 background-position: 0% 60%;
430 432 background-repeat: no-repeat;
431 433 padding-left: 12px;
432 434 background-image: url(../images/external.png);
433 435 }
434 436
435 437 div.wiki a.new {
436 438 color: #b73535;
437 439 }
438 440
439 441 div.wiki pre {
440 442 margin: 1em 1em 1em 1.6em;
441 443 padding: 2px;
442 444 background-color: #fafafa;
443 445 border: 1px solid #dadada;
444 446 width:95%;
445 447 overflow-x: auto;
446 448 }
447 449
448 450 div.wiki div.toc {
449 451 background-color: #ffffdd;
450 452 border: 1px solid #e4e4e4;
451 453 padding: 4px;
452 454 line-height: 1.2em;
453 455 margin-bottom: 12px;
454 456 margin-right: 12px;
455 457 display: table
456 458 }
457 459 * html div.wiki div.toc { width: 50%; } /* IE6 doesn't autosize div */
458 460
459 461 div.wiki div.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
460 462 div.wiki div.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
461 463
462 464 div.wiki div.toc a {
463 465 display: block;
464 466 font-size: 0.9em;
465 467 font-weight: normal;
466 468 text-decoration: none;
467 469 color: #606060;
468 470 }
469 471 div.wiki div.toc a:hover { color: #c61a1a; text-decoration: underline;}
470 472
471 473 div.wiki div.toc a.heading2 { margin-left: 6px; }
472 474 div.wiki div.toc a.heading3 { margin-left: 12px; font-size: 0.8em; }
473 475
474 476 /***** My page layout *****/
475 477 .block-receiver {
476 478 border:1px dashed #c0c0c0;
477 479 margin-bottom: 20px;
478 480 padding: 15px 0 15px 0;
479 481 }
480 482
481 483 .mypage-box {
482 484 margin:0 0 20px 0;
483 485 color:#505050;
484 486 line-height:1.5em;
485 487 }
486 488
487 489 .handle {
488 490 cursor: move;
489 491 }
490 492
491 493 a.close-icon {
492 494 display:block;
493 495 margin-top:3px;
494 496 overflow:hidden;
495 497 width:12px;
496 498 height:12px;
497 499 background-repeat: no-repeat;
498 500 cursor:pointer;
499 501 background-image:url('../images/close.png');
500 502 }
501 503
502 504 a.close-icon:hover {
503 505 background-image:url('../images/close_hl.png');
504 506 }
505 507
506 508 /***** Gantt chart *****/
507 509 .gantt_hdr {
508 510 position:absolute;
509 511 top:0;
510 512 height:16px;
511 513 border-top: 1px solid #c0c0c0;
512 514 border-bottom: 1px solid #c0c0c0;
513 515 border-right: 1px solid #c0c0c0;
514 516 text-align: center;
515 517 overflow: hidden;
516 518 }
517 519
518 520 .task {
519 521 position: absolute;
520 522 height:8px;
521 523 font-size:0.8em;
522 524 color:#888;
523 525 padding:0;
524 526 margin:0;
525 527 line-height:0.8em;
526 528 }
527 529
528 530 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
529 531 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
530 532 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
531 533 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
532 534
533 535 /***** Icons *****/
534 536 .icon {
535 537 background-position: 0% 40%;
536 538 background-repeat: no-repeat;
537 539 padding-left: 20px;
538 540 padding-top: 2px;
539 541 padding-bottom: 3px;
540 542 }
541 543
542 544 .icon22 {
543 545 background-position: 0% 40%;
544 546 background-repeat: no-repeat;
545 547 padding-left: 26px;
546 548 line-height: 22px;
547 549 vertical-align: middle;
548 550 }
549 551
550 552 .icon-add { background-image: url(../images/add.png); }
551 553 .icon-edit { background-image: url(../images/edit.png); }
552 554 .icon-copy { background-image: url(../images/copy.png); }
553 555 .icon-del { background-image: url(../images/delete.png); }
554 556 .icon-move { background-image: url(../images/move.png); }
555 557 .icon-save { background-image: url(../images/save.png); }
556 558 .icon-cancel { background-image: url(../images/cancel.png); }
557 559 .icon-file { background-image: url(../images/file.png); }
558 560 .icon-folder { background-image: url(../images/folder.png); }
559 561 .open .icon-folder { background-image: url(../images/folder_open.png); }
560 562 .icon-package { background-image: url(../images/package.png); }
561 563 .icon-home { background-image: url(../images/home.png); }
562 564 .icon-user { background-image: url(../images/user.png); }
563 565 .icon-mypage { background-image: url(../images/user_page.png); }
564 566 .icon-admin { background-image: url(../images/admin.png); }
565 567 .icon-projects { background-image: url(../images/projects.png); }
566 568 .icon-logout { background-image: url(../images/logout.png); }
567 569 .icon-help { background-image: url(../images/help.png); }
568 570 .icon-attachment { background-image: url(../images/attachment.png); }
569 571 .icon-index { background-image: url(../images/index.png); }
570 572 .icon-history { background-image: url(../images/history.png); }
571 573 .icon-time { background-image: url(../images/time.png); }
572 574 .icon-stats { background-image: url(../images/stats.png); }
573 575 .icon-warning { background-image: url(../images/warning.png); }
574 576 .icon-fav { background-image: url(../images/fav.png); }
575 577 .icon-fav-off { background-image: url(../images/fav_off.png); }
576 578 .icon-reload { background-image: url(../images/reload.png); }
577 579 .icon-lock { background-image: url(../images/locked.png); }
578 580 .icon-unlock { background-image: url(../images/unlock.png); }
579 581 .icon-checked { background-image: url(../images/true.png); }
580 582 .icon-details { background-image: url(../images/zoom_in.png); }
581 583 .icon-report { background-image: url(../images/report.png); }
582 584
583 585 .icon22-projects { background-image: url(../images/22x22/projects.png); }
584 586 .icon22-users { background-image: url(../images/22x22/users.png); }
585 587 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
586 588 .icon22-role { background-image: url(../images/22x22/role.png); }
587 589 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
588 590 .icon22-options { background-image: url(../images/22x22/options.png); }
589 591 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
590 592 .icon22-authent { background-image: url(../images/22x22/authent.png); }
591 593 .icon22-info { background-image: url(../images/22x22/info.png); }
592 594 .icon22-comment { background-image: url(../images/22x22/comment.png); }
593 595 .icon22-package { background-image: url(../images/22x22/package.png); }
594 596 .icon22-settings { background-image: url(../images/22x22/settings.png); }
595 597 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
596 598
597 599 /***** Media print specific styles *****/
598 600 @media print {
599 601 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
600 602 #main { background: #fff; }
601 603 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
602 604 }
@@ -1,95 +1,101
1 1 .jstEditor {
2 2 padding-left: 0px;
3 3 }
4 4 .jstEditor textarea, .jstEditor iframe {
5 5 margin: 0;
6 6 }
7 7
8 8 .jstHandle {
9 9 height: 10px;
10 10 font-size: 0.1em;
11 11 cursor: s-resize;
12 12 /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/
13 13 }
14 14
15 15 .jstElements {
16 16 padding: 3px 3px;
17 17 }
18 18
19 19 .jstElements button {
20 20 margin-right : 6px;
21 21 width : 24px;
22 22 height: 24px;
23 23 padding: 4px;
24 24 border-style: solid;
25 25 border-width: 1px;
26 26 border-color: #ddd;
27 27 background-color : #f7f7f7;
28 28 background-position : 50% 50%;
29 29 background-repeat: no-repeat;
30 30 }
31 31 .jstElements button:hover {
32 32 border-color : #000;
33 33 }
34 34 .jstElements button span {
35 35 display : none;
36 36 }
37 37 .jstElements span {
38 38 display : inline;
39 39 }
40 40
41 41 .jstSpacer {
42 42 width : 0px;
43 43 font-size: 1px;
44 44 margin-right: 4px;
45 45 }
46 46
47 47 .jstElements .help { float: right; margin-right: 1em; padding-top: 8px; font-size: 0.9em; }
48 48
49 49 /* Buttons
50 50 -------------------------------------------------------- */
51 51 .jstb_strong {
52 52 background-image: url(../images/jstoolbar/bt_strong.png);
53 53 }
54 54 .jstb_em {
55 55 background-image: url(../images/jstoolbar/bt_em.png);
56 56 }
57 57 .jstb_ins {
58 58 background-image: url(../images/jstoolbar/bt_ins.png);
59 59 }
60 60 .jstb_del {
61 61 background-image: url(../images/jstoolbar/bt_del.png);
62 62 }
63 63 .jstb_quote {
64 64 background-image: url(../images/jstoolbar/bt_quote.png);
65 65 }
66 66 .jstb_code {
67 67 background-image: url(../images/jstoolbar/bt_code.png);
68 68 }
69 69 .jstb_br {
70 70 background-image: url(../images/jstoolbar/bt_br.png);
71 71 }
72 72 .jstb_h1 {
73 73 background-image: url(../images/jstoolbar/bt_h1.png);
74 74 }
75 75 .jstb_h2 {
76 76 background-image: url(../images/jstoolbar/bt_h2.png);
77 77 }
78 78 .jstb_h3 {
79 79 background-image: url(../images/jstoolbar/bt_h3.png);
80 80 }
81 81 .jstb_ul {
82 82 background-image: url(../images/jstoolbar/bt_ul.png);
83 83 }
84 84 .jstb_ol {
85 85 background-image: url(../images/jstoolbar/bt_ol.png);
86 86 }
87 .jstb_bq {
88 background-image: url(../images/jstoolbar/bt_bq.png);
89 }
90 .jstb_unbq {
91 background-image: url(../images/jstoolbar/bt_bq_remove.png);
92 }
87 93 .jstb_pre {
88 94 background-image: url(../images/jstoolbar/bt_pre.png);
89 95 }
90 96 .jstb_link {
91 97 background-image: url(../images/jstoolbar/bt_link.png);
92 98 }
93 99 .jstb_img {
94 100 background-image: url(../images/jstoolbar/bt_img.png);
95 101 }
@@ -1,234 +1,274
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.dirname(__FILE__) + '/../../test_helper'
19 19
20 20 class ApplicationHelperTest < HelperTestCase
21 21 include ApplicationHelper
22 22 include ActionView::Helpers::TextHelper
23 23 fixtures :projects, :repositories, :changesets, :trackers, :issue_statuses, :issues, :documents, :versions, :wikis, :wiki_pages, :wiki_contents, :roles, :enabled_modules
24 24
25 25 def setup
26 26 super
27 27 end
28 28
29 29 def test_auto_links
30 30 to_test = {
31 31 'http://foo.bar' => '<a class="external" href="http://foo.bar">http://foo.bar</a>',
32 32 'http://foo.bar/~user' => '<a class="external" href="http://foo.bar/~user">http://foo.bar/~user</a>',
33 33 'http://foo.bar.' => '<a class="external" href="http://foo.bar">http://foo.bar</a>.',
34 34 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
35 35 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
36 36 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
37 37 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>'
38 38 }
39 39 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
40 40 end
41 41
42 42 def test_auto_mailto
43 43 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
44 44 textilizable('test@foo.bar')
45 45 end
46 46
47 47 def test_inline_images
48 48 to_test = {
49 49 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
50 50 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
51 51 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
52 52 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
53 53 }
54 54 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
55 55 end
56 56
57 57 def test_textile_external_links
58 58 to_test = {
59 59 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
60 60 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
61 61 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
62 62 # no multiline link text
63 63 "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"
64 64 }
65 65 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
66 66 end
67 67
68 68 def test_redmine_links
69 69 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
70 70 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
71 71
72 72 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
73 73 :class => 'changeset', :title => 'My very first commit')
74 74
75 75 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
76 76 :class => 'document')
77 77
78 78 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
79 79 :class => 'version')
80 80
81 81 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => 'some/file'}
82 82
83 83 to_test = {
84 84 # tickets
85 85 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
86 86 # changesets
87 87 'r1' => changeset_link,
88 88 # documents
89 89 'document#1' => document_link,
90 90 'document:"Test document"' => document_link,
91 91 # versions
92 92 'version#2' => version_link,
93 93 'version:1.0' => version_link,
94 94 'version:"1.0"' => version_link,
95 95 # source
96 96 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
97 97 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
98 98 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
99 99 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
100 100 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
101 101 # escaping
102 102 '!#3.' => '#3.',
103 103 '!r1' => 'r1',
104 104 '!document#1' => 'document#1',
105 105 '!document:"Test document"' => 'document:"Test document"',
106 106 '!version#2' => 'version#2',
107 107 '!version:1.0' => 'version:1.0',
108 108 '!version:"1.0"' => 'version:"1.0"',
109 109 '!source:/some/file' => 'source:/some/file',
110 110 # invalid expressions
111 111 'source:' => 'source:'
112 112 }
113 113 @project = Project.find(1)
114 114 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
115 115 end
116 116
117 117 def test_wiki_links
118 118 to_test = {
119 119 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
120 120 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
121 121 # page that doesn't exist
122 122 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
123 123 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
124 124 # link to another project wiki
125 125 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
126 126 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
127 127 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
128 128 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
129 129 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
130 130 # striked through link
131 131 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
132 132 # escaping
133 133 '![[Another page|Page]]' => '[[Another page|Page]]',
134 134 }
135 135 @project = Project.find(1)
136 136 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
137 137 end
138 138
139 139 def test_html_tags
140 140 to_test = {
141 141 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
142 142 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
143 143 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
144 144 # do not escape pre/code tags
145 145 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
146 146 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
147 147 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
148 148 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
149 149 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
150 150 }
151 151 to_test.each { |text, result| assert_equal result, textilizable(text) }
152 152 end
153 153
154 154 def test_wiki_links_in_tables
155 155 to_test = {"|Cell 11|Cell 12|Cell 13|\n|Cell 21|Cell 22||\n|Cell 31||Cell 33|" =>
156 156 '<tr><td>Cell 11</td><td>Cell 12</td><td>Cell 13</td></tr>' +
157 157 '<tr><td>Cell 21</td><td>Cell 22</td></tr>' +
158 158 '<tr><td>Cell 31</td><td>Cell 33</td></tr>',
159 159
160 160 "|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
161 161 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
162 162 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
163 163 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
164 164 }
165 165 @project = Project.find(1)
166 166 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
167 167 end
168 168
169 169 def test_text_formatting
170 170 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
171 171 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
172 172 }
173 173 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
174 174 end
175 175
176 176 def test_wiki_horizontal_rule
177 177 assert_equal '<hr />', textilizable('---')
178 178 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
179 179 end
180 180
181 def test_blockquote
182 # orig raw text
183 raw = <<-RAW
184 John said:
185 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
186 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
187 > * Donec odio lorem,
188 > * sagittis ac,
189 > * malesuada in,
190 > * adipiscing eu, dolor.
191 >
192 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
193 > Proin a tellus. Nam vel neque.
194
195 He's right.
196 RAW
197
198 # expected html
199 expected = <<-EXPECTED
200 <p>John said:</p>
201 <blockquote>
202 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
203 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
204 <ul>
205 <li>Donec odio lorem,</li>
206 <li>sagittis ac,</li>
207 <li>malesuada in,</li>
208 <li>adipiscing eu, dolor.</li>
209 </ul>
210 <blockquote>
211 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
212 </blockquote>
213 <p>Proin a tellus. Nam vel neque.</p>
214 </blockquote>
215 <p>He's right.</p>
216 EXPECTED
217
218 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
219 end
220
181 221 def test_macro_hello_world
182 222 text = "{{hello_world}}"
183 223 assert textilizable(text).match(/Hello world!/)
184 224 # escaping
185 225 text = "!{{hello_world}}"
186 226 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
187 227 end
188 228
189 229 def test_macro_include
190 230 @project = Project.find(1)
191 231 # include a page of the current project wiki
192 232 text = "{{include(Another page)}}"
193 233 assert textilizable(text).match(/This is a link to a ticket/)
194 234
195 235 @project = nil
196 236 # include a page of a specific project wiki
197 237 text = "{{include(ecookbook:Another page)}}"
198 238 assert textilizable(text).match(/This is a link to a ticket/)
199 239
200 240 text = "{{include(ecookbook:)}}"
201 241 assert textilizable(text).match(/CookBook documentation/)
202 242
203 243 text = "{{include(unknowidentifier:somepage)}}"
204 244 assert textilizable(text).match(/Unknow project/)
205 245 end
206 246
207 247 def test_date_format_default
208 248 today = Date.today
209 249 Setting.date_format = ''
210 250 assert_equal l_date(today), format_date(today)
211 251 end
212 252
213 253 def test_date_format
214 254 today = Date.today
215 255 Setting.date_format = '%d %m %Y'
216 256 assert_equal today.strftime('%d %m %Y'), format_date(today)
217 257 end
218 258
219 259 def test_time_format_default
220 260 now = Time.now
221 261 Setting.date_format = ''
222 262 Setting.time_format = ''
223 263 assert_equal l_datetime(now), format_time(now)
224 264 assert_equal l_time(now), format_time(now, false)
225 265 end
226 266
227 267 def test_time_format
228 268 now = Time.now
229 269 Setting.date_format = '%d %m %Y'
230 270 Setting.time_format = '%H %M'
231 271 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
232 272 assert_equal now.strftime('%H %M'), format_time(now, false)
233 273 end
234 274 end
General Comments 0
You need to be logged in to leave comments. Login now