Skip to content

Make print page (print.html) links link to anchors on the print page #1738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/mdbook-core/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn normalize_id(content: &str) -> String {

/// Generate an ID for use with anchors which is derived from a "normalised"
/// string.
fn id_from_content(content: &str) -> String {
pub fn id_from_content(content: &str) -> String {
let mut content = content.to_string();

// Skip any tags or html-encoded stuff
Expand Down
85 changes: 79 additions & 6 deletions crates/mdbook-html/src/html_handlebars/hbs_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,25 @@ impl HtmlHandlebars {
print_content
.push_str(r#"<div style="break-before: page; page-break-before: always;"></div>"#);
}
print_content.push_str(&fixed_content);
let print_page_id = {
let mut base = path.display().to_string();
if base.ends_with(".md") {
base.truncate(base.len() - 3);
}
&base
.replace("/", "-")
.replace("\\", "-")
.to_ascii_lowercase()
};

// We have to build header links in advance so that we can know the ranges
// for the headers in one page.
// Insert a dummy div to make sure that we can locate the specific page.
print_content.push_str(&(format!(r#"<div id="{print_page_id}"></div>"#)));
print_content.push_str(&build_header_links(
&build_print_element_id(&fixed_content, &print_page_id),
Some(print_page_id),
));

// Update the context with data for this file
let ctx_path = path
Expand Down Expand Up @@ -239,7 +257,23 @@ impl HtmlHandlebars {
code_config: &Code,
edition: Option<RustEdition>,
) -> String {
let rendered = build_header_links(&rendered);
let rendered = build_header_links(&rendered, None);
let rendered = self.post_process_common(rendered, &playground_config, code_config, edition);

rendered
}

/// Applies some post-processing to the HTML to apply some adjustments.
///
/// This common function is used for both normal chapters (via
/// `post_process`) and the combined print page.
fn post_process_common(
&self,
rendered: String,
playground_config: &Playground,
code_config: &Code,
edition: Option<RustEdition>,
) -> String {
let rendered = fix_code_blocks(&rendered);
let rendered = add_playground_pre(&rendered, playground_config, edition);
let rendered = hide_lines(&rendered, code_config);
Expand Down Expand Up @@ -497,7 +531,7 @@ impl Renderer for HtmlHandlebars {
debug!("Render template");
let rendered = handlebars.render("index", &data)?;

let rendered = self.post_process(
let rendered = self.post_process_common(
rendered,
&html_config.playground,
&html_config.code,
Expand Down Expand Up @@ -695,9 +729,35 @@ fn make_data(
Ok(data)
}

/// Go through the rendered print page HTML,
/// add path id prefix to all the elements id as well as footnote links.
fn build_print_element_id(html: &str, print_page_id: &str) -> String {
static ALL_ID: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r#"(<[^>]*?id=")([^"]+?)""#).unwrap());
static FOOTNOTE_ID: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(
r##"(<sup [^>]*?class="footnote-reference"[^>]*?>[^<]*?<a [^>]*?href="#)([^"]+?)""##,
)
.unwrap()
});

let temp_html = ALL_ID.replace_all(html, |caps: &Captures<'_>| {
format!("{}{}-{}\"", &caps[1], print_page_id, &caps[2])
});

FOOTNOTE_ID
.replace_all(&temp_html, |caps: &Captures<'_>| {
format!("{}{}-{}\"", &caps[1], print_page_id, &caps[2])
})
.into_owned()
}

/// Goes through the rendered HTML, making sure all header tags have
/// an anchor respectively so people can link to sections directly.
fn build_header_links(html: &str) -> String {
///
/// `print_page_id` should be set to the print page ID prefix when adjusting the
/// print page.
fn build_header_links(html: &str, print_page_id: Option<&str>) -> String {
static BUILD_HEADER_LINKS: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r#"<h(\d)(?: id="([^"]+)")?(?: class="([^"]+)")?>(.*?)</h\d>"#).unwrap()
});
Expand Down Expand Up @@ -726,21 +786,34 @@ fn build_header_links(html: &str) -> String {
caps.get(2).map(|x| x.as_str().to_string()),
caps.get(3).map(|x| x.as_str().to_string()),
&mut id_counter,
print_page_id,
)
})
.into_owned()
}

/// Insert a single link into a header, making sure each link gets its own
/// unique ID by appending an auto-incremented number (if necessary).
///
/// For `print.html`, we will add a path id prefix.
fn insert_link_into_header(
level: usize,
content: &str,
id: Option<String>,
classes: Option<String>,
id_counter: &mut HashMap<String, usize>,
print_page_id: Option<&str>,
) -> String {
let id = id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter));
let id = if let Some(print_page_id) = print_page_id {
let content_id = {
#[allow(deprecated)]
utils::id_from_content(content)
};
let with_prefix = format!("{} {}", print_page_id, content_id);
id.unwrap_or_else(|| utils::unique_id_from_content(&with_prefix, id_counter))
} else {
id.unwrap_or_else(|| utils::unique_id_from_content(content, id_counter))
};
let classes = classes
.map(|s| format!(" class=\"{s}\""))
.unwrap_or_default();
Expand Down Expand Up @@ -1123,7 +1196,7 @@ mod tests {
];

for (src, should_be) in inputs {
let got = build_header_links(src);
let got = build_header_links(src, None);
assert_eq!(got, should_be);
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/mdbook-html/src/html_handlebars/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ fn render_item(
.with_context(|| "Could not convert HTML path to str")?;
let anchor_base = utils::fs::normalize_path(filepath);

let options = HtmlRenderOptions::new(&chapter_path);
let redirect = HashMap::new();
let options = HtmlRenderOptions::new(&chapter_path, &redirect);
let mut p = new_cmark_parser(&chapter.content, &options.markdown_options).peekable();

let mut in_heading = false;
Expand Down
4 changes: 2 additions & 2 deletions crates/mdbook-html/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ use std::path::Path;
/// Creates an [`HtmlRenderOptions`] from the given config.
pub fn html_render_options_from_config<'a>(
path: &'a Path,
config: &HtmlConfig,
config: &'a HtmlConfig,
) -> HtmlRenderOptions<'a> {
let mut options = HtmlRenderOptions::new(path);
let mut options = HtmlRenderOptions::new(path, &config.redirect);
options.markdown_options.smart_punctuation = config.smart_punctuation;
options
}
Loading
Loading