##// END OF EJS Templates
Adds Trac-Like anchors on wiki headings (#1647)....
Jean-Philippe Lang -
r1696:2dbc3d294364
parent child
Show More
@@ -1,176 +1,184
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 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 if tag =~ /^h(\d)$/
69 @toc << [$1.to_i, content]
68 # removes wiki links from the item
69 toc_item = content.gsub(/(\[\[|\]\])/, '')
70 # removes styles
71 # eg. %{color:red}Triggers% => Triggers
72 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
73
74 # replaces non word caracters by dashes
75 anchor = toc_item.gsub(%r{[^\w\s\-]}, '').gsub(%r{\s+(\-+\s*)?}, '-')
76
77 unless anchor.blank?
78 if tag =~ /^h(\d)$/
79 @toc << [$1.to_i, anchor, toc_item]
80 end
81 atts << " id=\"#{anchor}\""
82 content = content + "<a href=\"##{anchor}\" class=\"wiki-anchor\">&para;</a>"
70 83 end
71 content = "<a name=\"#{@toc.length}\" class=\"wiki-page\"></a>" + content
72 84 textile_p(tag, atts, cite, content)
73 85 end
74 86
75 87 alias :textile_h1 :textile_p_withtoc
76 88 alias :textile_h2 :textile_p_withtoc
77 89 alias :textile_h3 :textile_p_withtoc
78 90
79 91 def inline_toc(text)
80 92 text.gsub!(/<p>\{\{([<>]?)toc\}\}<\/p>/i) do
81 93 div_class = 'toc'
82 94 div_class << ' right' if $1 == '>'
83 95 div_class << ' left' if $1 == '<'
84 96 out = "<ul class=\"#{div_class}\">"
85 @toc.each_with_index do |heading, index|
86 # remove wiki links from the item
87 toc_item = heading.last.gsub(/(\[\[|\]\])/, '')
88 # remove styles
89 # eg. %{color:red}Triggers% => Triggers
90 toc_item.gsub! %r[%\{[^\}]*\}([^%]+)%], '\\1'
91 out << "<li class=\"heading#{heading.first}\"><a href=\"##{index+1}\">#{toc_item}</a></li>\n"
97 @toc.each do |heading|
98 level, anchor, toc_item = heading
99 out << "<li class=\"heading#{level}\"><a href=\"##{anchor}\">#{toc_item}</a></li>\n"
92 100 end
93 101 out << '</ul>'
94 102 out
95 103 end
96 104 end
97 105
98 106 MACROS_RE = /
99 107 (!)? # escaping
100 108 (
101 109 \{\{ # opening tag
102 110 ([\w]+) # macro name
103 111 (\(([^\}]*)\))? # optional arguments
104 112 \}\} # closing tag
105 113 )
106 114 /x unless const_defined?(:MACROS_RE)
107 115
108 116 def inline_macros(text)
109 117 text.gsub!(MACROS_RE) do
110 118 esc, all, macro = $1, $2, $3.downcase
111 119 args = ($5 || '').split(',').each(&:strip)
112 120 if esc.nil?
113 121 begin
114 122 @macros_runner.call(macro, args)
115 123 rescue => e
116 124 "<div class=\"flash error\">Error executing the <strong>#{macro}</strong> macro (#{e})</div>"
117 125 end || all
118 126 else
119 127 all
120 128 end
121 129 end
122 130 end
123 131
124 132 AUTO_LINK_RE = %r{
125 133 ( # leading text
126 134 <\w+.*?>| # leading HTML tag, or
127 135 [^=<>!:'"/]| # leading punctuation, or
128 136 ^ # beginning of line
129 137 )
130 138 (
131 139 (?:https?://)| # protocol spec, or
132 140 (?:ftp://)|
133 141 (?:www\.) # www.*
134 142 )
135 143 (
136 144 (\S+?) # url
137 145 (\/)? # slash
138 146 )
139 147 ([^\w\=\/;]*?) # post
140 148 (?=<|\s|$)
141 149 }x unless const_defined?(:AUTO_LINK_RE)
142 150
143 151 # Turns all urls into clickable links (code from Rails).
144 152 def inline_auto_link(text)
145 153 text.gsub!(AUTO_LINK_RE) do
146 154 all, leading, proto, url, post = $&, $1, $2, $3, $6
147 155 if leading =~ /<a\s/i || leading =~ /![<>=]?/
148 156 # don't replace URL's that are already linked
149 157 # and URL's prefixed with ! !> !< != (textile images)
150 158 all
151 159 else
152 160 %(#{leading}<a class="external" href="#{proto=="www."?"http://www.":proto}#{url}">#{proto + url}</a>#{post})
153 161 end
154 162 end
155 163 end
156 164
157 165 # Turns all email addresses into clickable links (code from Rails).
158 166 def inline_auto_mailto(text)
159 167 text.gsub!(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
160 168 mail = $1
161 169 if text.match(/<a\b[^>]*>(.*)(#{Regexp.escape(mail)})(.*)<\/a>/)
162 170 mail
163 171 else
164 172 %{<a href="mailto:#{mail}" class="email">#{mail}</a>}
165 173 end
166 174 end
167 175 end
168 176 end
169 177
170 178 public
171 179
172 180 def self.to_html(text, options = {}, &block)
173 181 TextileFormatter.new(text).to_html(&block)
174 182 end
175 183 end
176 184 end
@@ -1,618 +1,622
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 { 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.entry span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;}
101 101 tr.entry.open span.expander {background-image: url(../images/bullet_toggle_minus.png);}
102 102 tr.entry.file td.filename a { margin-left: 16px; }
103 103
104 104 tr.changeset td.author { text-align: center; width: 15%; }
105 105 tr.changeset td.committed_on { text-align: center; width: 15%; }
106 106
107 107 tr.message { height: 2.6em; }
108 108 tr.message td.last_message { font-size: 80%; }
109 109 tr.message.locked td.subject a { background-image: url(../images/locked.png); }
110 110 tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; }
111 111
112 112 tr.user td { width:13%; }
113 113 tr.user td.email { width:18%; }
114 114 tr.user td { white-space: nowrap; }
115 115 tr.user.locked, tr.user.registered { color: #aaa; }
116 116 tr.user.locked a, tr.user.registered a { color: #aaa; }
117 117
118 118 tr.time-entry { text-align: center; white-space: nowrap; }
119 119 tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; }
120 120 td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; }
121 121 td.hours .hours-dec { font-size: 0.9em; }
122 122
123 123 table.list tbody tr:hover { background-color:#ffffdd; }
124 124 table td {padding:2px;}
125 125 table p {margin:0;}
126 126 .odd {background-color:#f6f7f8;}
127 127 .even {background-color: #fff;}
128 128
129 129 .highlight { background-color: #FCFD8D;}
130 130 .highlight.token-1 { background-color: #faa;}
131 131 .highlight.token-2 { background-color: #afa;}
132 132 .highlight.token-3 { background-color: #aaf;}
133 133
134 134 .box{
135 135 padding:6px;
136 136 margin-bottom: 10px;
137 137 background-color:#f6f6f6;
138 138 color:#505050;
139 139 line-height:1.5em;
140 140 border: 1px solid #e4e4e4;
141 141 }
142 142
143 143 div.square {
144 144 border: 1px solid #999;
145 145 float: left;
146 146 margin: .3em .4em 0 .4em;
147 147 overflow: hidden;
148 148 width: .6em; height: .6em;
149 149 }
150 150 .contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;}
151 151 .contextual input {font-size:0.9em;}
152 152
153 153 .splitcontentleft{float:left; width:49%;}
154 154 .splitcontentright{float:right; width:49%;}
155 155 form {display: inline;}
156 156 input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;}
157 157 fieldset {border: 1px solid #e4e4e4; margin:0;}
158 158 legend {color: #484848;}
159 159 hr { width: 100%; height: 1px; background: #ccc; border: 0;}
160 160 blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;}
161 161 blockquote blockquote { margin-left: 0;}
162 162 textarea.wiki-edit { width: 99%; }
163 163 li p {margin-top: 0;}
164 164 div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;}
165 165 p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;}
166 166 p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; }
167 167
168 168 fieldset#filters { padding: 0.7em; }
169 169 fieldset#filters p { margin: 1.2em 0 0.8em 2px; }
170 170 fieldset#filters .buttons { font-size: 0.9em; }
171 171 fieldset#filters table { border-collapse: collapse; }
172 172 fieldset#filters table td { padding: 0; vertical-align: middle; }
173 173 fieldset#filters tr.filter { height: 2em; }
174 174 fieldset#filters td.add-filter { text-align: right; vertical-align: top; }
175 175
176 176 div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;}
177 177 div#issue-changesets .changeset { padding: 4px;}
178 178 div#issue-changesets .changeset { border-bottom: 1px solid #ddd; }
179 179 div#issue-changesets p { margin-top: 0; margin-bottom: 1em;}
180 180
181 181 div#activity dl, #search-results { margin-left: 2em; }
182 182 div#activity dd, #search-results dd { margin-bottom: 1em; padding-left: 18px; font-size: 0.9em; }
183 183 div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; }
184 184 div#activity dt.me .time { border-bottom: 1px solid #999; }
185 185 div#activity dt .time { color: #777; font-size: 80%; }
186 186 div#activity dd .description, #search-results dd .description { font-style: italic; }
187 187 div#activity span.project:after, #search-results span.project:after { content: " -"; }
188 188 div#activity dd span.description, #search-results dd span.description { display:block; }
189 189
190 190 #search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; }
191 191 div#search-results-counts {float:right;}
192 192 div#search-results-counts ul { margin-top: 0.5em; }
193 193 div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; }
194 194
195 195 dt.issue { background-image: url(../images/ticket.png); }
196 196 dt.issue-edit { background-image: url(../images/ticket_edit.png); }
197 197 dt.issue-closed { background-image: url(../images/ticket_checked.png); }
198 198 dt.issue-note { background-image: url(../images/ticket_note.png); }
199 199 dt.changeset { background-image: url(../images/changeset.png); }
200 200 dt.news { background-image: url(../images/news.png); }
201 201 dt.message { background-image: url(../images/message.png); }
202 202 dt.reply { background-image: url(../images/comments.png); }
203 203 dt.wiki-page { background-image: url(../images/wiki_edit.png); }
204 204 dt.attachment { background-image: url(../images/attachment.png); }
205 205 dt.document { background-image: url(../images/document.png); }
206 206 dt.project { background-image: url(../images/projects.png); }
207 207
208 208 div#roadmap fieldset.related-issues { margin-bottom: 1em; }
209 209 div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; }
210 210 div#roadmap .wiki h1:first-child { display: none; }
211 211 div#roadmap .wiki h1 { font-size: 120%; }
212 212 div#roadmap .wiki h2 { font-size: 110%; }
213 213
214 214 div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; }
215 215 div#version-summary fieldset { margin-bottom: 1em; }
216 216 div#version-summary .total-hours { text-align: right; }
217 217
218 218 table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; }
219 219 table#time-report tbody tr { font-style: italic; color: #777; }
220 220 table#time-report tbody tr.last-level { font-style: normal; color: #555; }
221 221 table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; }
222 222 table#time-report .hours-dec { font-size: 0.9em; }
223 223
224 224 ul.properties {padding:0; font-size: 0.9em; color: #777;}
225 225 ul.properties li {list-style-type:none;}
226 226 ul.properties li span {font-style:italic;}
227 227
228 228 .total-hours { font-size: 110%; font-weight: bold; }
229 229 .total-hours span.hours-int { font-size: 120%; }
230 230
231 231 .autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;}
232 232 #user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; }
233 233
234 234 .pagination {font-size: 90%}
235 235 p.pagination {margin-top:8px;}
236 236
237 237 /***** Tabular forms ******/
238 238 .tabular p{
239 239 margin: 0;
240 240 padding: 5px 0 8px 0;
241 241 padding-left: 180px; /*width of left column containing the label elements*/
242 242 height: 1%;
243 243 clear:left;
244 244 }
245 245
246 246 html>body .tabular p {overflow:hidden;}
247 247
248 248 .tabular label{
249 249 font-weight: bold;
250 250 float: left;
251 251 text-align: right;
252 252 margin-left: -180px; /*width of left column*/
253 253 width: 175px; /*width of labels. Should be smaller than left column to create some right
254 254 margin*/
255 255 }
256 256
257 257 .tabular label.floating{
258 258 font-weight: normal;
259 259 margin-left: 0px;
260 260 text-align: left;
261 261 width: 200px;
262 262 }
263 263
264 264 input#time_entry_comments { width: 90%;}
265 265
266 266 #preview fieldset {margin-top: 1em; background: url(../images/draft.png)}
267 267
268 268 .tabular.settings p{ padding-left: 300px; }
269 269 .tabular.settings label{ margin-left: -300px; width: 295px; }
270 270
271 271 .required {color: #bb0000;}
272 272 .summary {font-style: italic;}
273 273
274 274 #attachments_fields input[type=text] {margin-left: 8px; }
275 275
276 276 div.attachments p { margin:4px 0 2px 0; }
277 277 div.attachments img { vertical-align: middle; }
278 278 div.attachments span.author { font-size: 0.9em; color: #888; }
279 279
280 280 p.other-formats { text-align: right; font-size:0.9em; color: #666; }
281 281 .other-formats span + span:before { content: "| "; }
282 282
283 283 a.feed { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; }
284 284
285 285 /***** Flash & error messages ****/
286 286 #errorExplanation, div.flash, .nodata, .warning {
287 287 padding: 4px 4px 4px 30px;
288 288 margin-bottom: 12px;
289 289 font-size: 1.1em;
290 290 border: 2px solid;
291 291 }
292 292
293 293 div.flash {margin-top: 8px;}
294 294
295 295 div.flash.error, #errorExplanation {
296 296 background: url(../images/false.png) 8px 5px no-repeat;
297 297 background-color: #ffe3e3;
298 298 border-color: #dd0000;
299 299 color: #550000;
300 300 }
301 301
302 302 div.flash.notice {
303 303 background: url(../images/true.png) 8px 5px no-repeat;
304 304 background-color: #dfffdf;
305 305 border-color: #9fcf9f;
306 306 color: #005f00;
307 307 }
308 308
309 309 .nodata, .warning {
310 310 text-align: center;
311 311 background-color: #FFEBC1;
312 312 border-color: #FDBF3B;
313 313 color: #A6750C;
314 314 }
315 315
316 316 #errorExplanation ul { font-size: 0.9em;}
317 317
318 318 /***** Ajax indicator ******/
319 319 #ajax-indicator {
320 320 position: absolute; /* fixed not supported by IE */
321 321 background-color:#eee;
322 322 border: 1px solid #bbb;
323 323 top:35%;
324 324 left:40%;
325 325 width:20%;
326 326 font-weight:bold;
327 327 text-align:center;
328 328 padding:0.6em;
329 329 z-index:100;
330 330 filter:alpha(opacity=50);
331 331 opacity: 0.5;
332 332 }
333 333
334 334 html>body #ajax-indicator { position: fixed; }
335 335
336 336 #ajax-indicator span {
337 337 background-position: 0% 40%;
338 338 background-repeat: no-repeat;
339 339 background-image: url(../images/loading.gif);
340 340 padding-left: 26px;
341 341 vertical-align: bottom;
342 342 }
343 343
344 344 /***** Calendar *****/
345 345 table.cal {border-collapse: collapse; width: 100%; margin: 8px 0 6px 0;border: 1px solid #d7d7d7;}
346 346 table.cal thead th {width: 14%;}
347 347 table.cal tbody tr {height: 100px;}
348 348 table.cal th { background-color:#EEEEEE; padding: 4px; }
349 349 table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;}
350 350 table.cal td p.day-num {font-size: 1.1em; text-align:right;}
351 351 table.cal td.odd p.day-num {color: #bbb;}
352 352 table.cal td.today {background:#ffffdd;}
353 353 table.cal td.today p.day-num {font-weight: bold;}
354 354
355 355 /***** Tooltips ******/
356 356 .tooltip{position:relative;z-index:24;}
357 357 .tooltip:hover{z-index:25;color:#000;}
358 358 .tooltip span.tip{display: none; text-align:left;}
359 359
360 360 div.tooltip:hover span.tip{
361 361 display:block;
362 362 position:absolute;
363 363 top:12px; left:24px; width:270px;
364 364 border:1px solid #555;
365 365 background-color:#fff;
366 366 padding: 4px;
367 367 font-size: 0.8em;
368 368 color:#505050;
369 369 }
370 370
371 371 /***** Progress bar *****/
372 372 table.progress {
373 373 border: 1px solid #D7D7D7;
374 374 border-collapse: collapse;
375 375 border-spacing: 0pt;
376 376 empty-cells: show;
377 377 text-align: center;
378 378 float:left;
379 379 margin: 1px 6px 1px 0px;
380 380 }
381 381
382 382 table.progress td { height: 0.9em; }
383 383 table.progress td.closed { background: #BAE0BA none repeat scroll 0%; }
384 384 table.progress td.done { background: #DEF0DE none repeat scroll 0%; }
385 385 table.progress td.open { background: #FFF none repeat scroll 0%; }
386 386 p.pourcent {font-size: 80%;}
387 387 p.progress-info {clear: left; font-style: italic; font-size: 80%;}
388 388
389 389 /***** Tabs *****/
390 390 #content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;}
391 391 #content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;}
392 392 #content .tabs>ul { bottom:-1px; } /* others */
393 393 #content .tabs ul li {
394 394 float:left;
395 395 list-style-type:none;
396 396 white-space:nowrap;
397 397 margin-right:8px;
398 398 background:#fff;
399 399 }
400 400 #content .tabs ul li a{
401 401 display:block;
402 402 font-size: 0.9em;
403 403 text-decoration:none;
404 404 line-height:1.3em;
405 405 padding:4px 6px 4px 6px;
406 406 border: 1px solid #ccc;
407 407 border-bottom: 1px solid #bbbbbb;
408 408 background-color: #eeeeee;
409 409 color:#777;
410 410 font-weight:bold;
411 411 }
412 412
413 413 #content .tabs ul li a:hover {
414 414 background-color: #ffffdd;
415 415 text-decoration:none;
416 416 }
417 417
418 418 #content .tabs ul li a.selected {
419 419 background-color: #fff;
420 420 border: 1px solid #bbbbbb;
421 421 border-bottom: 1px solid #fff;
422 422 }
423 423
424 424 #content .tabs ul li a.selected:hover {
425 425 background-color: #fff;
426 426 }
427 427
428 428 /***** Diff *****/
429 429 .diff_out { background: #fcc; }
430 430 .diff_in { background: #cfc; }
431 431
432 432 /***** Wiki *****/
433 433 div.wiki table {
434 434 border: 1px solid #505050;
435 435 border-collapse: collapse;
436 436 margin-bottom: 1em;
437 437 }
438 438
439 439 div.wiki table, div.wiki td, div.wiki th {
440 440 border: 1px solid #bbb;
441 441 padding: 4px;
442 442 }
443 443
444 444 div.wiki .external {
445 445 background-position: 0% 60%;
446 446 background-repeat: no-repeat;
447 447 padding-left: 12px;
448 448 background-image: url(../images/external.png);
449 449 }
450 450
451 451 div.wiki a.new {
452 452 color: #b73535;
453 453 }
454 454
455 455 div.wiki pre {
456 456 margin: 1em 1em 1em 1.6em;
457 457 padding: 2px;
458 458 background-color: #fafafa;
459 459 border: 1px solid #dadada;
460 460 width:95%;
461 461 overflow-x: auto;
462 462 }
463 463
464 464 div.wiki ul.toc {
465 465 background-color: #ffffdd;
466 466 border: 1px solid #e4e4e4;
467 467 padding: 4px;
468 468 line-height: 1.2em;
469 469 margin-bottom: 12px;
470 470 margin-right: 12px;
471 471 margin-left: 0;
472 472 display: table
473 473 }
474 474 * html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */
475 475
476 476 div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; }
477 477 div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; }
478 478 div.wiki ul.toc li { list-style-type:none;}
479 479 div.wiki ul.toc li.heading2 { margin-left: 6px; }
480 480 div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; }
481 481
482 482 div.wiki ul.toc a {
483 483 font-size: 0.9em;
484 484 font-weight: normal;
485 485 text-decoration: none;
486 486 color: #606060;
487 487 }
488 488 div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;}
489 489
490 a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; }
491 a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; }
492 h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; }
493
490 494 /***** My page layout *****/
491 495 .block-receiver {
492 496 border:1px dashed #c0c0c0;
493 497 margin-bottom: 20px;
494 498 padding: 15px 0 15px 0;
495 499 }
496 500
497 501 .mypage-box {
498 502 margin:0 0 20px 0;
499 503 color:#505050;
500 504 line-height:1.5em;
501 505 }
502 506
503 507 .handle {
504 508 cursor: move;
505 509 }
506 510
507 511 a.close-icon {
508 512 display:block;
509 513 margin-top:3px;
510 514 overflow:hidden;
511 515 width:12px;
512 516 height:12px;
513 517 background-repeat: no-repeat;
514 518 cursor:pointer;
515 519 background-image:url('../images/close.png');
516 520 }
517 521
518 522 a.close-icon:hover {
519 523 background-image:url('../images/close_hl.png');
520 524 }
521 525
522 526 /***** Gantt chart *****/
523 527 .gantt_hdr {
524 528 position:absolute;
525 529 top:0;
526 530 height:16px;
527 531 border-top: 1px solid #c0c0c0;
528 532 border-bottom: 1px solid #c0c0c0;
529 533 border-right: 1px solid #c0c0c0;
530 534 text-align: center;
531 535 overflow: hidden;
532 536 }
533 537
534 538 .task {
535 539 position: absolute;
536 540 height:8px;
537 541 font-size:0.8em;
538 542 color:#888;
539 543 padding:0;
540 544 margin:0;
541 545 line-height:0.8em;
542 546 }
543 547
544 548 .task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
545 549 .task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }
546 550 .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
547 551 .milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
548 552
549 553 /***** Icons *****/
550 554 .icon {
551 555 background-position: 0% 40%;
552 556 background-repeat: no-repeat;
553 557 padding-left: 20px;
554 558 padding-top: 2px;
555 559 padding-bottom: 3px;
556 560 }
557 561
558 562 .icon22 {
559 563 background-position: 0% 40%;
560 564 background-repeat: no-repeat;
561 565 padding-left: 26px;
562 566 line-height: 22px;
563 567 vertical-align: middle;
564 568 }
565 569
566 570 .icon-add { background-image: url(../images/add.png); }
567 571 .icon-edit { background-image: url(../images/edit.png); }
568 572 .icon-copy { background-image: url(../images/copy.png); }
569 573 .icon-del { background-image: url(../images/delete.png); }
570 574 .icon-move { background-image: url(../images/move.png); }
571 575 .icon-save { background-image: url(../images/save.png); }
572 576 .icon-cancel { background-image: url(../images/cancel.png); }
573 577 .icon-file { background-image: url(../images/file.png); }
574 578 .icon-folder { background-image: url(../images/folder.png); }
575 579 .open .icon-folder { background-image: url(../images/folder_open.png); }
576 580 .icon-package { background-image: url(../images/package.png); }
577 581 .icon-home { background-image: url(../images/home.png); }
578 582 .icon-user { background-image: url(../images/user.png); }
579 583 .icon-mypage { background-image: url(../images/user_page.png); }
580 584 .icon-admin { background-image: url(../images/admin.png); }
581 585 .icon-projects { background-image: url(../images/projects.png); }
582 586 .icon-logout { background-image: url(../images/logout.png); }
583 587 .icon-help { background-image: url(../images/help.png); }
584 588 .icon-attachment { background-image: url(../images/attachment.png); }
585 589 .icon-index { background-image: url(../images/index.png); }
586 590 .icon-history { background-image: url(../images/history.png); }
587 591 .icon-time { background-image: url(../images/time.png); }
588 592 .icon-stats { background-image: url(../images/stats.png); }
589 593 .icon-warning { background-image: url(../images/warning.png); }
590 594 .icon-fav { background-image: url(../images/fav.png); }
591 595 .icon-fav-off { background-image: url(../images/fav_off.png); }
592 596 .icon-reload { background-image: url(../images/reload.png); }
593 597 .icon-lock { background-image: url(../images/locked.png); }
594 598 .icon-unlock { background-image: url(../images/unlock.png); }
595 599 .icon-checked { background-image: url(../images/true.png); }
596 600 .icon-details { background-image: url(../images/zoom_in.png); }
597 601 .icon-report { background-image: url(../images/report.png); }
598 602
599 603 .icon22-projects { background-image: url(../images/22x22/projects.png); }
600 604 .icon22-users { background-image: url(../images/22x22/users.png); }
601 605 .icon22-tracker { background-image: url(../images/22x22/tracker.png); }
602 606 .icon22-role { background-image: url(../images/22x22/role.png); }
603 607 .icon22-workflow { background-image: url(../images/22x22/workflow.png); }
604 608 .icon22-options { background-image: url(../images/22x22/options.png); }
605 609 .icon22-notifications { background-image: url(../images/22x22/notifications.png); }
606 610 .icon22-authent { background-image: url(../images/22x22/authent.png); }
607 611 .icon22-info { background-image: url(../images/22x22/info.png); }
608 612 .icon22-comment { background-image: url(../images/22x22/comment.png); }
609 613 .icon22-package { background-image: url(../images/22x22/package.png); }
610 614 .icon22-settings { background-image: url(../images/22x22/settings.png); }
611 615 .icon22-plugin { background-image: url(../images/22x22/plugin.png); }
612 616
613 617 /***** Media print specific styles *****/
614 618 @media print {
615 619 #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; }
616 620 #main { background: #fff; }
617 621 #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; }
618 622 }
@@ -1,342 +1,342
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 'This is a link: http://foo.bar.' => 'This is a link: <a class="external" href="http://foo.bar">http://foo.bar</a>.',
35 35 'A link (eg. http://foo.bar).' => 'A link (eg. <a class="external" href="http://foo.bar">http://foo.bar</a>).',
36 36 'http://foo.bar/foo.bar#foo.bar.' => '<a class="external" href="http://foo.bar/foo.bar#foo.bar">http://foo.bar/foo.bar#foo.bar</a>.',
37 37 'www.foo.bar' => '<a class="external" href="http://www.foo.bar">www.foo.bar</a>',
38 38 'http://foo.bar/page?p=1&t=z&s=' => '<a class="external" href="http://foo.bar/page?p=1&#38;t=z&#38;s=">http://foo.bar/page?p=1&#38;t=z&#38;s=</a>',
39 39 'http://foo.bar/page#125' => '<a class="external" href="http://foo.bar/page#125">http://foo.bar/page#125</a>',
40 40 'http://foo@www.bar.com' => '<a class="external" href="http://foo@www.bar.com">http://foo@www.bar.com</a>',
41 41 'ftp://foo.bar' => '<a class="external" href="ftp://foo.bar">ftp://foo.bar</a>',
42 42 }
43 43 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
44 44 end
45 45
46 46 def test_auto_mailto
47 47 assert_equal '<p><a href="mailto:test@foo.bar" class="email">test@foo.bar</a></p>',
48 48 textilizable('test@foo.bar')
49 49 end
50 50
51 51 def test_inline_images
52 52 to_test = {
53 53 '!http://foo.bar/image.jpg!' => '<img src="http://foo.bar/image.jpg" alt="" />',
54 54 'floating !>http://foo.bar/image.jpg!' => 'floating <div style="float:right"><img src="http://foo.bar/image.jpg" alt="" /></div>',
55 55 'with class !(some-class)http://foo.bar/image.jpg!' => 'with class <img src="http://foo.bar/image.jpg" class="some-class" alt="" />',
56 56 'with style !{width:100px;height100px}http://foo.bar/image.jpg!' => 'with style <img src="http://foo.bar/image.jpg" style="width:100px;height100px;" alt="" />',
57 57 }
58 58 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
59 59 end
60 60
61 61 def test_textile_external_links
62 62 to_test = {
63 63 'This is a "link":http://foo.bar' => 'This is a <a href="http://foo.bar" class="external">link</a>',
64 64 'This is an intern "link":/foo/bar' => 'This is an intern <a href="/foo/bar">link</a>',
65 65 '"link (Link title)":http://foo.bar' => '<a href="http://foo.bar" title="Link title" class="external">link</a>',
66 66 "This is not a \"Link\":\n\nAnother paragraph" => "This is not a \"Link\":</p>\n\n\n\t<p>Another paragraph",
67 67 # no multiline link text
68 68 "This is a double quote \"on the first line\nand another on a second line\":test" => "This is a double quote \"on the first line<br />\nand another on a second line\":test"
69 69 }
70 70 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
71 71 end
72 72
73 73 def test_redmine_links
74 74 issue_link = link_to('#3', {:controller => 'issues', :action => 'show', :id => 3},
75 75 :class => 'issue', :title => 'Error 281 when updating a recipe (New)')
76 76
77 77 changeset_link = link_to('r1', {:controller => 'repositories', :action => 'revision', :id => 'ecookbook', :rev => 1},
78 78 :class => 'changeset', :title => 'My very first commit')
79 79
80 80 document_link = link_to('Test document', {:controller => 'documents', :action => 'show', :id => 1},
81 81 :class => 'document')
82 82
83 83 version_link = link_to('1.0', {:controller => 'versions', :action => 'show', :id => 2},
84 84 :class => 'version')
85 85
86 86 source_url = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file']}
87 87 source_url_with_ext = {:controller => 'repositories', :action => 'entry', :id => 'ecookbook', :path => ['some', 'file.ext']}
88 88
89 89 to_test = {
90 90 # tickets
91 91 '#3, #3 and #3.' => "#{issue_link}, #{issue_link} and #{issue_link}.",
92 92 # changesets
93 93 'r1' => changeset_link,
94 94 # documents
95 95 'document#1' => document_link,
96 96 'document:"Test document"' => document_link,
97 97 # versions
98 98 'version#2' => version_link,
99 99 'version:1.0' => version_link,
100 100 'version:"1.0"' => version_link,
101 101 # source
102 102 'source:/some/file' => link_to('source:/some/file', source_url, :class => 'source'),
103 103 'source:/some/file.' => link_to('source:/some/file', source_url, :class => 'source') + ".",
104 104 'source:/some/file.ext.' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
105 105 'source:/some/file. ' => link_to('source:/some/file', source_url, :class => 'source') + ".",
106 106 'source:/some/file.ext. ' => link_to('source:/some/file.ext', source_url_with_ext, :class => 'source') + ".",
107 107 'source:/some/file, ' => link_to('source:/some/file', source_url, :class => 'source') + ",",
108 108 'source:/some/file@52' => link_to('source:/some/file@52', source_url.merge(:rev => 52), :class => 'source'),
109 109 'source:/some/file.ext@52' => link_to('source:/some/file.ext@52', source_url_with_ext.merge(:rev => 52), :class => 'source'),
110 110 'source:/some/file#L110' => link_to('source:/some/file#L110', source_url.merge(:anchor => 'L110'), :class => 'source'),
111 111 'source:/some/file.ext#L110' => link_to('source:/some/file.ext#L110', source_url_with_ext.merge(:anchor => 'L110'), :class => 'source'),
112 112 'source:/some/file@52#L110' => link_to('source:/some/file@52#L110', source_url.merge(:rev => 52, :anchor => 'L110'), :class => 'source'),
113 113 'export:/some/file' => link_to('export:/some/file', source_url.merge(:format => 'raw'), :class => 'source download'),
114 114 # escaping
115 115 '!#3.' => '#3.',
116 116 '!r1' => 'r1',
117 117 '!document#1' => 'document#1',
118 118 '!document:"Test document"' => 'document:"Test document"',
119 119 '!version#2' => 'version#2',
120 120 '!version:1.0' => 'version:1.0',
121 121 '!version:"1.0"' => 'version:"1.0"',
122 122 '!source:/some/file' => 'source:/some/file',
123 123 # invalid expressions
124 124 'source:' => 'source:',
125 125 # url hash
126 126 "http://foo.bar/FAQ#3" => '<a class="external" href="http://foo.bar/FAQ#3">http://foo.bar/FAQ#3</a>',
127 127 }
128 128 @project = Project.find(1)
129 129 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
130 130 end
131 131
132 132 def test_wiki_links
133 133 to_test = {
134 134 '[[CookBook documentation]]' => '<a href="/wiki/ecookbook/CookBook_documentation" class="wiki-page">CookBook documentation</a>',
135 135 '[[Another page|Page]]' => '<a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a>',
136 136 # page that doesn't exist
137 137 '[[Unknown page]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">Unknown page</a>',
138 138 '[[Unknown page|404]]' => '<a href="/wiki/ecookbook/Unknown_page" class="wiki-page new">404</a>',
139 139 # link to another project wiki
140 140 '[[onlinestore:]]' => '<a href="/wiki/onlinestore/" class="wiki-page">onlinestore</a>',
141 141 '[[onlinestore:|Wiki]]' => '<a href="/wiki/onlinestore/" class="wiki-page">Wiki</a>',
142 142 '[[onlinestore:Start page]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Start page</a>',
143 143 '[[onlinestore:Start page|Text]]' => '<a href="/wiki/onlinestore/Start_page" class="wiki-page">Text</a>',
144 144 '[[onlinestore:Unknown page]]' => '<a href="/wiki/onlinestore/Unknown_page" class="wiki-page new">Unknown page</a>',
145 145 # striked through link
146 146 '-[[Another page|Page]]-' => '<del><a href="/wiki/ecookbook/Another_page" class="wiki-page">Page</a></del>',
147 147 # escaping
148 148 '![[Another page|Page]]' => '[[Another page|Page]]',
149 149 }
150 150 @project = Project.find(1)
151 151 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
152 152 end
153 153
154 154 def test_html_tags
155 155 to_test = {
156 156 "<div>content</div>" => "<p>&lt;div&gt;content&lt;/div&gt;</p>",
157 157 "<div class=\"bold\">content</div>" => "<p>&lt;div class=\"bold\"&gt;content&lt;/div&gt;</p>",
158 158 "<script>some script;</script>" => "<p>&lt;script&gt;some script;&lt;/script&gt;</p>",
159 159 # do not escape pre/code tags
160 160 "<pre>\nline 1\nline2</pre>" => "<pre>\nline 1\nline2</pre>",
161 161 "<pre><code>\nline 1\nline2</code></pre>" => "<pre><code>\nline 1\nline2</code></pre>",
162 162 "<pre><div>content</div></pre>" => "<pre>&lt;div&gt;content&lt;/div&gt;</pre>",
163 163 "HTML comment: <!-- no comments -->" => "<p>HTML comment: &lt;!-- no comments --&gt;</p>",
164 164 "<!-- opening comment" => "<p>&lt;!-- opening comment</p>"
165 165 }
166 166 to_test.each { |text, result| assert_equal result, textilizable(text) }
167 167 end
168 168
169 169 def test_allowed_html_tags
170 170 to_test = {
171 171 "<pre>preformatted text</pre>" => "<pre>preformatted text</pre>",
172 172 "<notextile>no *textile* formatting</notextile>" => "no *textile* formatting",
173 173 }
174 174 to_test.each { |text, result| assert_equal result, textilizable(text) }
175 175 end
176 176
177 177 def test_wiki_links_in_tables
178 178 to_test = {"|[[Page|Link title]]|[[Other Page|Other title]]|\n|Cell 21|[[Last page]]|" =>
179 179 '<tr><td><a href="/wiki/ecookbook/Page" class="wiki-page new">Link title</a></td>' +
180 180 '<td><a href="/wiki/ecookbook/Other_Page" class="wiki-page new">Other title</a></td>' +
181 181 '</tr><tr><td>Cell 21</td><td><a href="/wiki/ecookbook/Last_page" class="wiki-page new">Last page</a></td></tr>'
182 182 }
183 183 @project = Project.find(1)
184 184 to_test.each { |text, result| assert_equal "<table>#{result}</table>", textilizable(text).gsub(/[\t\n]/, '') }
185 185 end
186 186
187 187 def test_text_formatting
188 188 to_test = {'*_+bold, italic and underline+_*' => '<strong><em><ins>bold, italic and underline</ins></em></strong>',
189 189 '(_text within parentheses_)' => '(<em>text within parentheses</em>)'
190 190 }
191 191 to_test.each { |text, result| assert_equal "<p>#{result}</p>", textilizable(text) }
192 192 end
193 193
194 194 def test_wiki_horizontal_rule
195 195 assert_equal '<hr />', textilizable('---')
196 196 assert_equal '<p>Dashes: ---</p>', textilizable('Dashes: ---')
197 197 end
198 198
199 199 def test_table_of_content
200 200 raw = <<-RAW
201 201 {{toc}}
202 202
203 203 h1. Title
204 204
205 205 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
206 206
207 207 h2. Subtitle
208 208
209 209 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
210 210
211 211 h2. Subtitle with %{color:red}red text%
212 212
213 213 h1. Another title
214 214
215 215 RAW
216 216
217 217 expected = '<ul class="toc">' +
218 '<li class="heading1"><a href="#1">Title</a></li>' +
219 '<li class="heading2"><a href="#2">Subtitle</a></li>' +
220 '<li class="heading2"><a href="#3">Subtitle with red text</a></li>' +
221 '<li class="heading1"><a href="#4">Another title</a></li>' +
218 '<li class="heading1"><a href="#Title">Title</a></li>' +
219 '<li class="heading2"><a href="#Subtitle">Subtitle</a></li>' +
220 '<li class="heading2"><a href="#Subtitle-with-red-text">Subtitle with red text</a></li>' +
221 '<li class="heading1"><a href="#Another-title">Another title</a></li>' +
222 222 '</ul>'
223 223
224 224 assert textilizable(raw).gsub("\n", "").include?(expected)
225 225 end
226 226
227 227 def test_blockquote
228 228 # orig raw text
229 229 raw = <<-RAW
230 230 John said:
231 231 > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
232 232 > Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
233 233 > * Donec odio lorem,
234 234 > * sagittis ac,
235 235 > * malesuada in,
236 236 > * adipiscing eu, dolor.
237 237 >
238 238 > >Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.
239 239 > Proin a tellus. Nam vel neque.
240 240
241 241 He's right.
242 242 RAW
243 243
244 244 # expected html
245 245 expected = <<-EXPECTED
246 246 <p>John said:</p>
247 247 <blockquote>
248 248 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero.
249 249 Nullam commodo metus accumsan nulla. Curabitur lobortis dui id dolor.
250 250 <ul>
251 251 <li>Donec odio lorem,</li>
252 252 <li>sagittis ac,</li>
253 253 <li>malesuada in,</li>
254 254 <li>adipiscing eu, dolor.</li>
255 255 </ul>
256 256 <blockquote>
257 257 <p>Nulla varius pulvinar diam. Proin id arcu id lorem scelerisque condimentum. Proin vehicula turpis vitae lacus.</p>
258 258 </blockquote>
259 259 <p>Proin a tellus. Nam vel neque.</p>
260 260 </blockquote>
261 261 <p>He's right.</p>
262 262 EXPECTED
263 263
264 264 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
265 265 end
266 266
267 267 def test_table
268 268 raw = <<-RAW
269 269 This is a table with empty cells:
270 270
271 271 |cell11|cell12||
272 272 |cell21||cell23|
273 273 |cell31|cell32|cell33|
274 274 RAW
275 275
276 276 expected = <<-EXPECTED
277 277 <p>This is a table with empty cells:</p>
278 278
279 279 <table>
280 280 <tr><td>cell11</td><td>cell12</td><td></td></tr>
281 281 <tr><td>cell21</td><td></td><td>cell23</td></tr>
282 282 <tr><td>cell31</td><td>cell32</td><td>cell33</td></tr>
283 283 </table>
284 284 EXPECTED
285 285
286 286 assert_equal expected.gsub(%r{\s+}, ''), textilizable(raw).gsub(%r{\s+}, '')
287 287 end
288 288
289 289 def test_macro_hello_world
290 290 text = "{{hello_world}}"
291 291 assert textilizable(text).match(/Hello world!/)
292 292 # escaping
293 293 text = "!{{hello_world}}"
294 294 assert_equal '<p>{{hello_world}}</p>', textilizable(text)
295 295 end
296 296
297 297 def test_macro_include
298 298 @project = Project.find(1)
299 299 # include a page of the current project wiki
300 300 text = "{{include(Another page)}}"
301 301 assert textilizable(text).match(/This is a link to a ticket/)
302 302
303 303 @project = nil
304 304 # include a page of a specific project wiki
305 305 text = "{{include(ecookbook:Another page)}}"
306 306 assert textilizable(text).match(/This is a link to a ticket/)
307 307
308 308 text = "{{include(ecookbook:)}}"
309 309 assert textilizable(text).match(/CookBook documentation/)
310 310
311 311 text = "{{include(unknowidentifier:somepage)}}"
312 312 assert textilizable(text).match(/Unknow project/)
313 313 end
314 314
315 315 def test_date_format_default
316 316 today = Date.today
317 317 Setting.date_format = ''
318 318 assert_equal l_date(today), format_date(today)
319 319 end
320 320
321 321 def test_date_format
322 322 today = Date.today
323 323 Setting.date_format = '%d %m %Y'
324 324 assert_equal today.strftime('%d %m %Y'), format_date(today)
325 325 end
326 326
327 327 def test_time_format_default
328 328 now = Time.now
329 329 Setting.date_format = ''
330 330 Setting.time_format = ''
331 331 assert_equal l_datetime(now), format_time(now)
332 332 assert_equal l_time(now), format_time(now, false)
333 333 end
334 334
335 335 def test_time_format
336 336 now = Time.now
337 337 Setting.date_format = '%d %m %Y'
338 338 Setting.time_format = '%H %M'
339 339 assert_equal now.strftime('%d %m %Y %H %M'), format_time(now)
340 340 assert_equal now.strftime('%H %M'), format_time(now, false)
341 341 end
342 342 end
General Comments 0
You need to be logged in to leave comments. Login now