From c6679053f73fd37aa5851bb5e8fe24160a1b1987 Mon Sep 17 00:00:00 2001 From: Hugo McNally Date: Sun, 9 Apr 2023 21:12:01 +0100 Subject: [PATCH] Fixed bug with index page links. If there is no index page in the source directory, mdbook uses the first chapter as the index page. When doing so, mdbook doesn't render the page any differently. This causes relative links on the page to break, if the first chapter is within a sub-directory. To fix this, a redirect to the first chapter is used instead of the copy of the page. --- src/renderer/html_handlebars/hbs_renderer.rs | 52 ++++++++++---------- tests/rendered_output.rs | 8 +-- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/renderer/html_handlebars/hbs_renderer.rs b/src/renderer/html_handlebars/hbs_renderer.rs index c701729fcb..4f93cb21eb 100644 --- a/src/renderer/html_handlebars/hbs_renderer.rs +++ b/src/renderer/html_handlebars/hbs_renderer.rs @@ -32,12 +32,12 @@ impl HtmlHandlebars { item: &BookItem, mut ctx: RenderItemContext<'_>, print_content: &mut String, - ) -> Result<()> { + ) -> Result> { // FIXME: This should be made DRY-er and rely less on mutable state let (ch, path) = match item { BookItem::Chapter(ch) if !ch.is_draft_chapter() => (ch, ch.path.as_ref().unwrap()), - _ => return Ok(()), + _ => return Ok(None), }; if let Some(ref edit_url_template) = ctx.html_config.edit_url_template { @@ -58,7 +58,7 @@ impl HtmlHandlebars { let fixed_content = utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path)); - if !ctx.is_index && ctx.html_config.print.page_break { + if !ctx.is_first_chapter && ctx.html_config.print.page_break { // Add page break between chapters // 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 // Add both two CSS properties because of the compatibility issue @@ -120,22 +120,7 @@ impl HtmlHandlebars { debug!("Creating {}", filepath.display()); utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?; - if ctx.is_index { - ctx.data.insert("path".to_owned(), json!("index.md")); - ctx.data.insert("path_to_root".to_owned(), json!("")); - ctx.data.insert("is_index".to_owned(), json!(true)); - let rendered_index = ctx.handlebars.render("index", &ctx.data)?; - let rendered_index = self.post_process( - rendered_index, - &ctx.html_config.playground, - &ctx.html_config.code, - ctx.edition, - ); - debug!("Creating index.html from {}", ctx_path); - utils::fs::write_file(&ctx.destination, "index.html", rendered_index.as_bytes())?; - } - - Ok(()) + Ok(Some(filepath)) } fn render_404( @@ -484,7 +469,7 @@ impl Renderer for HtmlHandlebars { fn render(&self, ctx: &RenderContext) -> Result<()> { let book_config = &ctx.config.book; - let html_config = ctx.config.html_config().unwrap_or_default(); + let mut html_config = ctx.config.html_config().unwrap_or_default(); let src_dir = ctx.root.join(&ctx.config.book.src); let destination = &ctx.destination; let book = &ctx.book; @@ -535,21 +520,36 @@ impl Renderer for HtmlHandlebars { fs::create_dir_all(destination) .with_context(|| "Unexpected error when constructing destination path")?; - let mut is_index = true; + let mut seen_index = false; + let mut first_non_draft = None; for item in book.iter() { let ctx = RenderItemContext { handlebars: &handlebars, destination: destination.to_path_buf(), data: data.clone(), - is_index, + is_first_chapter: first_non_draft.is_none(), book_config: book_config.clone(), html_config: html_config.clone(), edition: ctx.config.rust.edition, chapter_titles: &ctx.chapter_titles, }; - self.render_item(item, ctx, &mut print_content)?; - // Only the first non-draft chapter item should be treated as the "index" - is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter()); + if let Some(rendered_file) = self.render_item(item, ctx, &mut print_content)? { + seen_index |= rendered_file == Path::new("index.html"); + if first_non_draft.is_none() { + first_non_draft = Some(rendered_file); + } + } + } + if !seen_index { + if let Some(first_file) = first_non_draft { + let redirect_to = first_file + .to_str() + .with_context(|| "Could not convert path to str")? + .to_string(); + html_config + .redirect + .insert(String::from("/index.html"), redirect_to); + } } // Render 404 page @@ -1053,7 +1053,7 @@ struct RenderItemContext<'a> { handlebars: &'a Handlebars<'a>, destination: PathBuf, data: serde_json::Map, - is_index: bool, + is_first_chapter: bool, book_config: BookConfig, html_config: HtmlConfig, edition: Option, diff --git a/tests/rendered_output.rs b/tests/rendered_output.rs index 7626b9e8ac..a83eefdd73 100644 --- a/tests/rendered_output.rs +++ b/tests/rendered_output.rs @@ -467,7 +467,7 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() { } #[test] -fn first_chapter_is_copied_as_index_even_if_not_first_elem() { +fn redirect_to_first_chapter_if_no_index() { let temp = DummyBook::new().build().unwrap(); let mut cfg = Config::default(); cfg.set("book.src", "index_html_test") @@ -476,9 +476,9 @@ fn first_chapter_is_copied_as_index_even_if_not_first_elem() { md.build().unwrap(); let root = temp.path().join("book"); - let chapter = fs::read_to_string(root.join("chapter_1.html")).expect("read chapter 1"); let index = fs::read_to_string(root.join("index.html")).expect("read index"); - pretty_assertions::assert_eq!(chapter, index); + + assert!(index.contains(r#""#)) } #[test] @@ -874,7 +874,7 @@ fn custom_fonts() { actual }; let has_fonts_css = |path: &Path| -> bool { - let contents = fs::read_to_string(path.join("book/index.html")).unwrap(); + let contents = fs::read_to_string(path.join("book/chapter_1.html")).unwrap(); contents.contains("fonts/fonts.css") };