Skip to content

Commit 601ed3c

Browse files
Merge #1129
1129: Update comrak to 0.2.3, use ext_header_ids r=carols10cents Fixes #1037. Use latest comrak, and use the header IDs extension to generate GitHub-compatible anchors. /cc @kejadlen @treiff
2 parents be8ebc8 + 0301237 commit 601ed3c

File tree

5 files changed

+114
-29
lines changed

5 files changed

+114
-29
lines changed

Cargo.lock

Lines changed: 24 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ serde_derive = "1.0.0"
5454
serde = "1.0.0"
5555
clippy = { version = "=0.0.162", optional = true }
5656
chrono = { version = "0.4.0", features = ["serde"] }
57-
comrak = { version = "0.1.9", default-features = false }
58-
ammonia = "0.7.0"
57+
comrak = { version = "0.2.3", default-features = false }
58+
ammonia = { git = "https://github.com/notriddle/ammonia" }
5959
docopt = "0.8.1"
6060
itertools = "0.6.0"
6161
lettre = "0.6"

app/controllers/crate/version.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import Controller from '@ember/controller';
22
import PromiseProxyMixin from '@ember/object/promise-proxy-mixin';
33
import ArrayProxy from '@ember/array/proxy';
4-
import { computed } from '@ember/object';
4+
import { computed, observer } from '@ember/object';
55
import { later } from '@ember/runloop';
6+
import $ from 'jquery';
67
import moment from 'moment';
78

89
const NUM_VERSIONS = 5;
@@ -173,4 +174,8 @@ export default Controller.extend({
173174
},
174175
},
175176

177+
report: observer('crate.readme', function() {
178+
setTimeout(() => $(window).trigger('hashchange'));
179+
}),
180+
176181
});

app/initializers/hashchange.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import $ from 'jquery';
2+
3+
function decodeFragmentValue(hash) {
4+
try {
5+
return decodeURIComponent(hash.slice(1));
6+
} catch(_) {
7+
return '';
8+
}
9+
}
10+
11+
function findElementByFragmentName(document, name) {
12+
if (name === '') {
13+
return;
14+
}
15+
16+
return document.getElementById(name) || document.getElementsByName(name)[0];
17+
}
18+
19+
function hashchange() {
20+
if (document.querySelector(':target')) {
21+
return;
22+
}
23+
24+
const hash = decodeFragmentValue(location.hash);
25+
const target = findElementByFragmentName(document, `user-content-${hash}`);
26+
if (target) {
27+
target.scrollIntoView();
28+
}
29+
}
30+
31+
export function initialize() {
32+
$(window).on('hashchange', hashchange);
33+
34+
// If clicking on a link to the same fragment as currently in the address bar,
35+
// hashchange won't be fired, so we need to manually trigger rescroll.
36+
$(document).on('a[href]', 'click', function(event) {
37+
if (this.href === location.href && location.hash.length > 1) {
38+
setTimeout(function() {
39+
if (!event.defaultPrevented) {
40+
hashchange();
41+
}
42+
});
43+
}
44+
});
45+
}
46+
47+
export default {
48+
name: 'app.hashchange',
49+
initialize
50+
};

src/render.rs

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
use ammonia::Ammonia;
1+
use ammonia::Builder;
22
use comrak;
33

44
use util::CargoResult;
55

66
/// Context for markdown to HTML rendering.
77
#[allow(missing_debug_implementations)]
88
pub struct MarkdownRenderer<'a> {
9-
html_sanitizer: Ammonia<'a>,
9+
html_sanitizer: Builder<'a>,
1010
}
1111

1212
impl<'a> MarkdownRenderer<'a> {
@@ -53,8 +53,7 @@ impl<'a> MarkdownRenderer<'a> {
5353
.cloned()
5454
.collect();
5555
let tag_attributes = [
56-
("a", ["href", "target"].iter().cloned().collect()),
57-
("code", ["class"].iter().cloned().collect()),
56+
("a", ["href", "id", "target"].iter().cloned().collect()),
5857
(
5958
"img",
6059
["width", "height", "src", "alt", "align"]
@@ -94,14 +93,13 @@ impl<'a> MarkdownRenderer<'a> {
9493
].iter()
9594
.cloned()
9695
.collect();
97-
let html_sanitizer = Ammonia {
98-
link_rel: Some("nofollow noopener noreferrer"),
99-
keep_cleaned_elements: true,
100-
tags: tags,
101-
tag_attributes: tag_attributes,
102-
allowed_classes: allowed_classes,
103-
..Ammonia::default()
104-
};
96+
let mut html_sanitizer = Builder::new();
97+
html_sanitizer
98+
.link_rel(Some("nofollow noopener noreferrer"))
99+
.tags(tags)
100+
.tag_attributes(tag_attributes)
101+
.allowed_classes(allowed_classes)
102+
.id_prefix(Some("user-content-"));
105103
MarkdownRenderer {
106104
html_sanitizer: html_sanitizer,
107105
}
@@ -115,10 +113,11 @@ impl<'a> MarkdownRenderer<'a> {
115113
ext_table: true,
116114
ext_tagfilter: true,
117115
ext_tasklist: true,
116+
ext_header_ids: Some("user-content-".to_string()),
118117
..comrak::ComrakOptions::default()
119118
};
120119
let rendered = comrak::markdown_to_html(text, &options);
121-
Ok(self.html_sanitizer.clean(&rendered))
120+
Ok(self.html_sanitizer.clean(&rendered).to_string())
122121
}
123122
}
124123

@@ -218,4 +217,24 @@ mod tests {
218217
let result = markdown_to_html(text).unwrap();
219218
assert_eq!(result, "<p>Hello World!</p>\n");
220219
}
220+
221+
#[test]
222+
fn header_has_tags() {
223+
let text = "# My crate\n\nHello, world!\n";
224+
let result = markdown_to_html(text).unwrap();
225+
assert_eq!(
226+
result,
227+
"<h1><a href=\"#my-crate\" id=\"user-content-my-crate\" rel=\"nofollow noopener noreferrer\"></a>My crate</h1>\n<p>Hello, world!</p>\n"
228+
);
229+
}
230+
231+
#[test]
232+
fn manual_anchor_is_sanitized() {
233+
let text = "<h1><a href=\"#my-crate\" id=\"my-crate\"></a>My crate</h1>\n<p>Hello, world!</p>\n";
234+
let result = markdown_to_html(text).unwrap();
235+
assert_eq!(
236+
result,
237+
"<h1><a href=\"#my-crate\" id=\"user-content-my-crate\" rel=\"nofollow noopener noreferrer\"></a>My crate</h1>\n<p>Hello, world!</p>\n"
238+
);
239+
}
221240
}

0 commit comments

Comments
 (0)