diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs index 1a9f751ccf..2f80c21f23 100644 --- a/crates/mdbook-html/front-end/templates/toc.js.hbs +++ b/crates/mdbook-html/front-end/templates/toc.js.hbs @@ -14,6 +14,19 @@ class MDBookSidebarScrollbox extends HTMLElement { if (current_page.endsWith('/')) { current_page += 'index.html'; } + // Normalize a URL for comparison by stripping .html suffix. + // This ensures TOC highlighting works on hosts that use "pretty URLs" + // (e.g. Cloudflare Pages) which strip the .html extension. + function normalizeUrl(url) { + if (url.endsWith('/index.html')) { + return url.slice(0, -'/index.html'.length + 1); + } + if (url.endsWith('.html')) { + return url.slice(0, -'.html'.length); + } + return url; + } + const normalizedCurrentPage = normalizeUrl(current_page); const links = Array.prototype.slice.call(this.querySelectorAll('a')); const l = links.length; for (let i = 0; i < l; ++i) { @@ -24,6 +37,7 @@ class MDBookSidebarScrollbox extends HTMLElement { } // The 'index' page is supposed to alias the first chapter in the book. if (link.href === current_page + || normalizeUrl(link.href) === normalizedCurrentPage || i === 0 && path_to_root === '' && current_page.endsWith('/index.html')) { diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs index 40bb0001e2..72981c2c1d 100644 --- a/tests/testsuite/toc.rs +++ b/tests/testsuite/toc.rs @@ -141,6 +141,16 @@ fn check_link_target_fallback() { ); } +/// The generated toc.js should normalize URLs for comparison so that TOC +/// highlighting works on hosts that use "pretty URLs" (stripping .html). +/// See . +#[test] +fn toc_js_normalizes_urls_for_pretty_urls() { + BookTest::from_dir("toc/basic_toc") + .check_file_contains("book/toc*.js", "normalizeUrl") + .check_file_contains("book/toc*.js", "normalizeUrl(link.href) === normalizedCurrentPage"); +} + // Checks formatting of summary names with inline elements. #[test] fn summary_with_markdown_formatting() {