Feature #42603 » 0001-Enable-common_mark-alerts.patch
app/assets/stylesheets/application.css | ||
---|---|---|
1262 | 1262 | |
1263 | 1263 |
.conflict-details {font-size:93%;} |
1264 | 1264 | |
1265 |
/***** CommonMark Alerts *****/ |
|
1266 | ||
1267 |
.markdown-alert { |
|
1268 |
border-left: 4px solid; |
|
1269 |
padding: 10px 10px 1px 10px; |
|
1270 |
margin: 10px 0; |
|
1271 |
} |
|
1272 | ||
1273 |
.markdown-alert-title + p { |
|
1274 |
margin-top: 2px; |
|
1275 |
} |
|
1276 | ||
1277 |
.markdown-alert-title { |
|
1278 |
font-weight: bold; |
|
1279 |
margin-bottom: 0.5em; |
|
1280 |
margin: 0px; |
|
1281 |
} |
|
1282 | ||
1283 |
.markdown-alert-tip { border-color: #5db651; } |
|
1284 |
.markdown-alert-tip .markdown-alert-title { color: #005f00; } |
|
1285 | ||
1286 |
.markdown-alert-important { border-color: #800080; } |
|
1287 |
.markdown-alert-important .markdown-alert-title { color: #4b006e; } |
|
1288 | ||
1289 |
.markdown-alert-caution { border-color: #c22; } |
|
1290 |
.markdown-alert-caution .markdown-alert-title { color: #880000; } |
|
1291 | ||
1292 |
.markdown-alert-warning { border-color: #e4bc4b; } |
|
1293 |
.markdown-alert-warning .markdown-alert-title { color: #a7760c; } |
|
1294 | ||
1295 |
.markdown-alert-note { border-color: #169; } |
|
1296 |
.markdown-alert-note .markdown-alert-title { color: #1e40af; } |
|
1297 | ||
1265 | 1298 |
/***** Ajax indicator ******/ |
1266 | 1299 |
#ajax-indicator { |
1267 | 1300 |
position: absolute; /* fixed not supported by IE */ |
lib/redmine/wiki_formatting/common_mark/formatter.rb | ||
---|---|---|
34 | 34 |
header_ids: nil, |
35 | 35 |
tasklist: true, |
36 | 36 |
shortcodes: false, |
37 |
alerts: true, |
|
37 | 38 |
}.freeze, |
38 | 39 | |
39 | 40 |
# https://github.com/gjtorikian/commonmarker#parse-options |
lib/redmine/wiki_formatting/common_mark/sanitization_filter.rb | ||
---|---|---|
68 | 68 |
end |
69 | 69 |
} |
70 | 70 | |
71 |
# Allow class on div and p tags only for alert blocks |
|
72 |
# (must be exactly: "markdown-alert markdown-alert-*" for div, and "markdown-alert-title" for p) |
|
73 |
(allowlist[:attributes]["div"] ||= []) << "class" |
|
74 |
(allowlist[:attributes]["p"] ||= []) << "class" |
|
75 |
allowlist[:transformers].push lambda{|env| |
|
76 |
node = env[:node] |
|
77 |
return unless node.element? |
|
78 | ||
79 |
case node.name |
|
80 |
when 'div' |
|
81 |
unless node['class'] =~ /\Amarkdown-alert markdown-alert-[a-z]+\z/ |
|
82 |
node.remove_attribute('class') |
|
83 |
end |
|
84 |
when 'p' |
|
85 |
unless node['class'] == 'markdown-alert-title' |
|
86 |
node.remove_attribute('class') |
|
87 |
end |
|
88 |
end |
|
89 |
} |
|
90 | ||
71 | 91 |
# Allow table cell alignment by style attribute |
72 | 92 |
# |
73 | 93 |
# Only necessary if we used the TABLE_PREFER_STYLE_ATTRIBUTES |
test/unit/lib/redmine/wiki_formatting/common_mark/formatter_test.rb | ||
---|---|---|
163 | 163 |
# 0 |
164 | 164 |
<<~STR.chomp, |
165 | 165 |
# Title |
166 |
|
|
166 | ||
167 | 167 |
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Maecenas sed libero. |
168 | 168 |
STR |
169 | 169 |
# 1 |
170 | 170 |
<<~STR.chomp, |
171 | 171 |
## Heading 2 |
172 |
|
|
172 | ||
173 | 173 |
~~~ruby |
174 | 174 |
def foo |
175 | 175 |
end |
176 | 176 |
~~~ |
177 |
|
|
177 | ||
178 | 178 |
Morbi facilisis accumsan orci non pharetra. |
179 |
|
|
179 | ||
180 | 180 |
~~~ ruby |
181 | 181 |
def foo |
182 | 182 |
end |
183 | 183 |
~~~ |
184 |
|
|
184 | ||
185 | 185 |
``` |
186 | 186 |
Pre Content: |
187 |
|
|
187 | ||
188 | 188 |
## Inside pre |
189 |
|
|
189 | ||
190 | 190 |
<tag> inside pre block |
191 |
|
|
191 | ||
192 | 192 |
Morbi facilisis accumsan orci non pharetra. |
193 | 193 |
``` |
194 | 194 |
STR |
195 | 195 |
# 2 |
196 | 196 |
<<~STR.chomp, |
197 | 197 |
### Heading 3 |
198 |
|
|
198 | ||
199 | 199 |
Nulla nunc nisi, egestas in ornare vel, posuere ac libero. |
200 | 200 |
STR |
201 | 201 |
] |
... | ... | |
299 | 299 |
assert_equal expected.gsub(%r{[\r\n\t]}, ''), to_html(text).gsub(%r{[\r\n\t]}, '').rstrip |
300 | 300 |
end |
301 | 301 | |
302 |
def test_should_render_alert_blocks |
|
303 |
text = <<~MD |
|
304 |
> [!note] |
|
305 |
> This is a note. |
|
306 | ||
307 |
> [!tip] |
|
308 |
> This is a tip. |
|
309 | ||
310 |
> [!warning] |
|
311 |
> This is a warning. |
|
312 | ||
313 |
> [!caution] |
|
314 |
> This is a caution. |
|
315 | ||
316 |
> [!important] |
|
317 |
> This is a important. |
|
318 |
MD |
|
319 | ||
320 |
html = to_html(text) |
|
321 |
%w[note tip warning caution important].each do |alert| |
|
322 |
assert_include "<div class=\"markdown-alert markdown-alert-note\">\n<p class=\"markdown-alert-title\">Note</p>\n<p>This is a note.</p>\n</div>", html |
|
323 |
end |
|
324 |
end |
|
325 | ||
326 |
def test_should_not_render_unknown_alert_type |
|
327 |
text = <<~MD |
|
328 |
> [!unknown] |
|
329 |
> This should not become an alert. |
|
330 |
MD |
|
331 | ||
332 |
html = to_html(text) |
|
333 | ||
334 |
assert_include "<blockquote>", html |
|
335 |
assert_include "[!unknown]", html |
|
336 |
assert_include "This should not become an alert.", html |
|
337 | ||
338 |
assert_not_include 'markdown-alert', html |
|
339 |
end |
|
340 | ||
302 | 341 |
private |
303 | 342 | |
304 | 343 |
def assert_section_with_hash(expected, text, index) |
test/unit/lib/redmine/wiki_formatting/common_mark/sanitization_filter_test.rb | ||
---|---|---|
71 | 71 |
assert_equal %(<code>foo</code>), filter(input) |
72 | 72 |
end |
73 | 73 | |
74 |
def test_should_allow_valid_alert_div_and_p_classes |
|
75 |
html = <<~HTML |
|
76 |
<div class="markdown-alert markdown-alert-tip"> |
|
77 |
<p class="markdown-alert-title">Tip</p> |
|
78 |
<p>Useful tip.</p> |
|
79 |
</div> |
|
80 |
HTML |
|
81 | ||
82 |
sanitized = filter(html) |
|
83 | ||
84 |
assert_include 'class="markdown-alert markdown-alert-tip"', sanitized |
|
85 |
assert_include 'class="markdown-alert-title"', sanitized |
|
86 |
end |
|
87 | ||
88 |
def test_should_remove_invalid_div_class |
|
89 |
html = '<div class="bad-class">Text</div>' |
|
90 |
sanitized = filter(html) |
|
91 |
refute_include 'bad-class', sanitized |
|
92 |
end |
|
93 | ||
94 |
def test_should_remove_invalid_p_class |
|
95 |
html = '<p class="bad-class">Text</p>' |
|
96 |
sanitized = filter(html) |
|
97 |
assert_not_include 'bad-class', sanitized |
|
98 |
end |
|
99 | ||
74 | 100 |
def test_should_allow_links_with_safe_url_schemes |
75 | 101 |
%w(http https ftp ssh foo).each do |scheme| |
76 | 102 |
input = %(<a href="#{scheme}://example.org/">foo</a>) |