Skip to content

Commit 655f0a8

Browse files
committed
Make print page (print.html) links link to anchors on the print page
Let all the anchors id on the print page to have a path id prefix to help locate. e.g. bar/foo.md#abc -> #bar-foo-abc Also append a dummy div to the start of the original page to make sure that original page links without an anchor can also be located. Fix to remove all the `./` in the normalized path id so that for "./foo/bar.html#abc" we still get "#foo-bar-abc" Add support for redirect link anchors in print page so that anchors can also be redirected, also handle URL redirect links on print page Handle all the elements id to add a path prefix, also make path id to all be the lower case Fix for print page footnote links by adding the path id prefix Signed-off-by: Hollow Man <[email protected]>
1 parent 313bbc2 commit 655f0a8

File tree

3 files changed

+282
-34
lines changed

3 files changed

+282
-34
lines changed

src/renderer/html_handlebars/hbs_renderer.rs

+85-10
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,38 @@ impl HtmlHandlebars {
5757
let content = ch.content.clone();
5858
let content = utils::render_markdown(&content, ctx.html_config.curly_quotes);
5959

60-
let fixed_content =
61-
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path));
60+
let fixed_content = utils::render_markdown_with_path(
61+
&ch.content,
62+
ctx.html_config.curly_quotes,
63+
Some(path),
64+
ctx.html_config.redirect,
65+
);
6266
if !ctx.is_index && ctx.html_config.print.page_break {
6367
// Add page break between chapters
6468
// See https://developer.mozilla.org/en-US/docs/Web/CSS/break-before and https://developer.mozilla.org/en-US/docs/Web/CSS/page-break-before
6569
// Add both two CSS properties because of the compatibility issue
6670
print_content
6771
.push_str(r#"<div style="break-before: page; page-break-before: always;"></div>"#);
6872
}
69-
print_content.push_str(&fixed_content);
73+
let path_id = {
74+
let mut base = path.display().to_string();
75+
if base.ends_with(".md") {
76+
base.replace_range(base.len() - 3.., "");
77+
}
78+
&base
79+
.replace("/", "-")
80+
.replace("\\", "-")
81+
.to_ascii_lowercase()
82+
};
83+
84+
// We have to build header links in advance so that we can know the ranges
85+
// for the headers in one page.
86+
// Insert a dummy div to make sure that we can locate the specific page.
87+
print_content.push_str(&(format!(r#"<div id="{}"></div>"#, &path_id)));
88+
print_content.push_str(&build_header_links(
89+
&build_print_element_id(&fixed_content, &path_id),
90+
Some(path_id),
91+
));
7092

7193
// Update the context with data for this file
7294
let ctx_path = path
@@ -191,19 +213,31 @@ impl HtmlHandlebars {
191213
}
192214

193215
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
194-
fn post_process(
216+
fn post_process_print(
195217
&self,
196218
rendered: String,
197219
playground_config: &Playground,
198220
edition: Option<RustEdition>,
199221
) -> String {
200-
let rendered = build_header_links(&rendered);
201222
let rendered = fix_code_blocks(&rendered);
202223
let rendered = add_playground_pre(&rendered, playground_config, edition);
203224

204225
rendered
205226
}
206227

228+
#[cfg_attr(feature = "cargo-clippy", allow(clippy::let_and_return))]
229+
fn post_process(
230+
&self,
231+
rendered: String,
232+
playground_config: &Playground,
233+
edition: Option<RustEdition>,
234+
) -> String {
235+
let rendered = build_header_links(&rendered, None);
236+
let rendered = self.post_process_print(rendered, &playground_config, edition);
237+
238+
rendered
239+
}
240+
207241
fn copy_static_files(
208242
&self,
209243
destination: &Path,
@@ -564,7 +598,7 @@ impl Renderer for HtmlHandlebars {
564598
let rendered = handlebars.render("index", &data)?;
565599

566600
let rendered =
567-
self.post_process(rendered, &html_config.playground, ctx.config.rust.edition);
601+
self.post_process_print(rendered, &html_config.playground, ctx.config.rust.edition);
568602

569603
utils::fs::write_file(destination, "print.html", rendered.as_bytes())?;
570604
debug!("Creating print.html ✓");
@@ -764,9 +798,43 @@ fn make_data(
764798
Ok(data)
765799
}
766800

801+
/// Goes through part of the rendered print page HTML,
802+
/// add path id prefix to all the elements id as well as footnote links.
803+
fn build_print_element_id(html: &str, path_id: &str) -> String {
804+
let all_id = Regex::new(r#"(<[^>]*?id=")([^"]+?)""#).unwrap();
805+
let footnote_id = Regex::new(
806+
r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""##,
807+
)
808+
.unwrap();
809+
810+
if path_id.is_empty() {
811+
return html.to_string();
812+
}
813+
814+
let temp_html = all_id
815+
.replace_all(html, |caps: &Captures<'_>| {
816+
let mut fixed = String::new();
817+
fixed.push_str(&path_id);
818+
fixed.push_str("-");
819+
fixed.push_str(&caps[2]);
820+
format!("{}{}\"", &caps[1], fixed)
821+
})
822+
.into_owned();
823+
824+
footnote_id
825+
.replace_all(&temp_html, |caps: &Captures<'_>| {
826+
let mut fixed = String::new();
827+
fixed.push_str(&path_id);
828+
fixed.push_str("-");
829+
fixed.push_str(&caps[2]);
830+
format!("{}{}\"", &caps[1], fixed)
831+
})
832+
.into_owned()
833+
}
834+
767835
/// Goes through the rendered HTML, making sure all header tags have
768836
/// an anchor respectively so people can link to sections directly.
769-
fn build_header_links(html: &str) -> String {
837+
fn build_header_links(html: &str, path_id: Option<&str>) -> String {
770838
static BUILD_HEADER_LINKS: Lazy<Regex> =
771839
Lazy::new(|| Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap());
772840

@@ -778,19 +846,26 @@ fn build_header_links(html: &str) -> String {
778846
.parse()
779847
.expect("Regex should ensure we only ever get numbers here");
780848

781-
insert_link_into_header(level, &caps[2], &mut id_counter)
849+
insert_link_into_header(level, &caps[2], &mut id_counter, path_id)
782850
})
783851
.into_owned()
784852
}
785853

786854
/// Insert a sinle link into a header, making sure each link gets its own
787855
/// unique ID by appending an auto-incremented number (if necessary).
856+
///
857+
/// For `print.html`, we will add a path id prefix.
788858
fn insert_link_into_header(
789859
level: usize,
790860
content: &str,
791861
id_counter: &mut HashMap<String, usize>,
862+
path_id: Option<&str>,
792863
) -> String {
793-
let id = utils::unique_id_from_content(content, id_counter);
864+
let id = if let Some(path_id) = path_id {
865+
utils::unique_id_from_content_with_path(content, id_counter, path_id)
866+
} else {
867+
utils::unique_id_from_content(content, id_counter)
868+
};
794869

795870
format!(
796871
r##"<h{level} id="{id}"><a class="header" href="#{id}">{text}</a></h{level}>"##,
@@ -996,7 +1071,7 @@ mod tests {
9961071
];
9971072

9981073
for (src, should_be) in inputs {
999-
let got = build_header_links(src);
1074+
let got = build_header_links(src, None);
10001075
assert_eq!(got, should_be);
10011076
}
10021077
}

0 commit comments

Comments
 (0)