Skip to content

Commit 63bbe09

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 63d6df3 commit 63bbe09

File tree

3 files changed

+385
-69
lines changed

3 files changed

+385
-69
lines changed

src/renderer/html_handlebars/hbs_renderer.rs

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,11 @@ impl HtmlHandlebars {
5959

6060
let content = utils::render_markdown(&ch.content, ctx.html_config.smart_punctuation());
6161

62-
let fixed_content = utils::render_markdown_with_path(
62+
let printed_item = utils::render_markdown_with_path_and_redirects(
6363
&ch.content,
6464
ctx.html_config.smart_punctuation(),
6565
Some(path),
66+
&ctx.html_config.redirect,
6667
);
6768
if !ctx.is_index && ctx.html_config.print.page_break {
6869
// Add page break between chapters
@@ -71,7 +72,25 @@ impl HtmlHandlebars {
7172
print_content
7273
.push_str(r#"<div style="break-before: page; page-break-before: always;"></div>"#);
7374
}
74-
print_content.push_str(&fixed_content);
75+
let print_page_id = {
76+
let mut base = path.display().to_string();
77+
if base.ends_with(".md") {
78+
base.truncate(base.len() - 3);
79+
}
80+
&base
81+
.replace("/", "-")
82+
.replace("\\", "-")
83+
.to_ascii_lowercase()
84+
};
85+
86+
// We have to build header links in advance so that we can know the ranges
87+
// for the headers in one page.
88+
// Insert a dummy div to make sure that we can locate the specific page.
89+
print_content.push_str(&(format!(r#"<div id="{print_page_id}"></div>"#)));
90+
print_content.push_str(&build_header_links(
91+
&build_print_element_id(&printed_item, &print_page_id),
92+
Some(print_page_id),
93+
));
7594

7695
// Update the context with data for this file
7796
let ctx_path = path
@@ -224,7 +243,23 @@ impl HtmlHandlebars {
224243
code_config: &Code,
225244
edition: Option<RustEdition>,
226245
) -> String {
227-
let rendered = build_header_links(&rendered);
246+
let rendered = build_header_links(&rendered, None);
247+
let rendered = self.post_process_common(rendered, &playground_config, code_config, edition);
248+
249+
rendered
250+
}
251+
252+
/// Applies some post-processing to the HTML to apply some adjustments.
253+
///
254+
/// This common function is used for both normal chapters (via
255+
/// `post_process`) and the combined print page.
256+
fn post_process_common(
257+
&self,
258+
rendered: String,
259+
playground_config: &Playground,
260+
code_config: &Code,
261+
edition: Option<RustEdition>,
262+
) -> String {
228263
let rendered = fix_code_blocks(&rendered);
229264
let rendered = add_playground_pre(&rendered, playground_config, edition);
230265
let rendered = hide_lines(&rendered, code_config);
@@ -479,7 +514,7 @@ impl Renderer for HtmlHandlebars {
479514
debug!("Render template");
480515
let rendered = handlebars.render("index", &data)?;
481516

482-
let rendered = self.post_process(
517+
let rendered = self.post_process_common(
483518
rendered,
484519
&html_config.playground,
485520
&html_config.code,
@@ -675,9 +710,35 @@ fn make_data(
675710
Ok(data)
676711
}
677712

713+
/// Go through the rendered print page HTML,
714+
/// add path id prefix to all the elements id as well as footnote links.
715+
fn build_print_element_id(html: &str, print_page_id: &str) -> String {
716+
static ALL_ID: LazyLock<Regex> =
717+
LazyLock::new(|| Regex::new(r#"(<[^>]*?id=")([^"]+?)""#).unwrap());
718+
static FOOTNOTE_ID: LazyLock<Regex> = LazyLock::new(|| {
719+
Regex::new(
720+
r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""##,
721+
)
722+
.unwrap()
723+
});
724+
725+
let temp_html = ALL_ID.replace_all(html, |caps: &Captures<'_>| {
726+
format!("{}{}-{}\"", &caps[1], print_page_id, &caps[2])
727+
});
728+
729+
FOOTNOTE_ID
730+
.replace_all(&temp_html, |caps: &Captures<'_>| {
731+
format!("{}{}-{}\"", &caps[1], print_page_id, &caps[2])
732+
})
733+
.into_owned()
734+
}
735+
678736
/// Goes through the rendered HTML, making sure all header tags have
679737
/// an anchor respectively so people can link to sections directly.
680-
fn build_header_links(html: &str) -> String {
738+
///
739+
/// `print_page_id` should be set to the print page ID prefix when adjusting the
740+
/// print page.
741+
fn build_header_links(html: &str, print_page_id: Option<&str>) -> String {
681742
static BUILD_HEADER_LINKS: LazyLock<Regex> = LazyLock::new(|| {
682743
Regex::new(r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#).unwrap()
683744
});
@@ -706,21 +767,34 @@ fn build_header_links(html: &str) -> String {
706767
caps.get(2).map(|x| x.as_str().to_string()),
707768
caps.get(3).map(|x| x.as_str().to_string()),
708769
&mut id_counter,
770+
print_page_id,
709771
)
710772
})
711773
.into_owned()
712774
}
713775

714776
/// Insert a single link into a header, making sure each link gets its own
715777
/// unique ID by appending an auto-incremented number (if necessary).
778+
///
779+
/// For `print.html`, we will add a path id prefix.
716780
fn insert_link_into_header(
717781
level: usize,
718782
content: &str,
719783
id: Option<String>,
720784
classes: Option<String>,
721785
id_counter: &mut HashMap<String, usize>,
786+
print_page_id: Option<&str>,
722787
) -> String {
723-
let id = id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter));
788+
let id = if let Some(print_page_id) = print_page_id {
789+
let content_id = {
790+
#[allow(deprecated)]
791+
utils::id_from_content(content)
792+
};
793+
let with_prefix = format!("{} {}", print_page_id, content_id);
794+
id.unwrap_or_else(|| utils::unique_id_from_content(&with_prefix, id_counter))
795+
} else {
796+
id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter))
797+
};
724798
let classes = classes
725799
.map(|s| format!(" class=\"{s}\""))
726800
.unwrap_or_default();
@@ -1056,7 +1130,7 @@ mod tests {
10561130
];
10571131

10581132
for (src, should_be) in inputs {
1059-
let got = build_header_links(src);
1133+
let got = build_header_links(src, None);
10601134
assert_eq!(got, should_be);
10611135
}
10621136
}

0 commit comments

Comments
 (0)