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.
This commit is contained in:
Hugo McNally 2023-04-09 21:12:01 +01:00
parent 090eba0db5
commit c6679053f7
No known key found for this signature in database
GPG Key ID: 32477E97F48658E6
2 changed files with 30 additions and 30 deletions

View File

@ -32,12 +32,12 @@ impl HtmlHandlebars {
item: &BookItem, item: &BookItem,
mut ctx: RenderItemContext<'_>, mut ctx: RenderItemContext<'_>,
print_content: &mut String, print_content: &mut String,
) -> Result<()> { ) -> Result<Option<PathBuf>> {
// FIXME: This should be made DRY-er and rely less on mutable state // FIXME: This should be made DRY-er and rely less on mutable state
let (ch, path) = match item { let (ch, path) = match item {
BookItem::Chapter(ch) if !ch.is_draft_chapter() => (ch, ch.path.as_ref().unwrap()), 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 { if let Some(ref edit_url_template) = ctx.html_config.edit_url_template {
@ -58,7 +58,7 @@ impl HtmlHandlebars {
let fixed_content = let fixed_content =
utils::render_markdown_with_path(&ch.content, ctx.html_config.curly_quotes, Some(path)); 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 // 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 // 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 // Add both two CSS properties because of the compatibility issue
@ -120,22 +120,7 @@ impl HtmlHandlebars {
debug!("Creating {}", filepath.display()); debug!("Creating {}", filepath.display());
utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?; utils::fs::write_file(&ctx.destination, &filepath, rendered.as_bytes())?;
if ctx.is_index { Ok(Some(filepath))
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(())
} }
fn render_404( fn render_404(
@ -484,7 +469,7 @@ impl Renderer for HtmlHandlebars {
fn render(&self, ctx: &RenderContext) -> Result<()> { fn render(&self, ctx: &RenderContext) -> Result<()> {
let book_config = &ctx.config.book; 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 src_dir = ctx.root.join(&ctx.config.book.src);
let destination = &ctx.destination; let destination = &ctx.destination;
let book = &ctx.book; let book = &ctx.book;
@ -535,21 +520,36 @@ impl Renderer for HtmlHandlebars {
fs::create_dir_all(destination) fs::create_dir_all(destination)
.with_context(|| "Unexpected error when constructing destination path")?; .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() { for item in book.iter() {
let ctx = RenderItemContext { let ctx = RenderItemContext {
handlebars: &handlebars, handlebars: &handlebars,
destination: destination.to_path_buf(), destination: destination.to_path_buf(),
data: data.clone(), data: data.clone(),
is_index, is_first_chapter: first_non_draft.is_none(),
book_config: book_config.clone(), book_config: book_config.clone(),
html_config: html_config.clone(), html_config: html_config.clone(),
edition: ctx.config.rust.edition, edition: ctx.config.rust.edition,
chapter_titles: &ctx.chapter_titles, chapter_titles: &ctx.chapter_titles,
}; };
self.render_item(item, ctx, &mut print_content)?; if let Some(rendered_file) = self.render_item(item, ctx, &mut print_content)? {
// Only the first non-draft chapter item should be treated as the "index" seen_index |= rendered_file == Path::new("index.html");
is_index &= !matches!(item, BookItem::Chapter(ch) if !ch.is_draft_chapter()); 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 // Render 404 page
@ -1053,7 +1053,7 @@ struct RenderItemContext<'a> {
handlebars: &'a Handlebars<'a>, handlebars: &'a Handlebars<'a>,
destination: PathBuf, destination: PathBuf,
data: serde_json::Map<String, serde_json::Value>, data: serde_json::Map<String, serde_json::Value>,
is_index: bool, is_first_chapter: bool,
book_config: BookConfig, book_config: BookConfig,
html_config: HtmlConfig, html_config: HtmlConfig,
edition: Option<RustEdition>, edition: Option<RustEdition>,

View File

@ -467,7 +467,7 @@ fn by_default_mdbook_use_index_preprocessor_to_convert_readme_to_index() {
} }
#[test] #[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 temp = DummyBook::new().build().unwrap();
let mut cfg = Config::default(); let mut cfg = Config::default();
cfg.set("book.src", "index_html_test") 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(); md.build().unwrap();
let root = temp.path().join("book"); 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"); let index = fs::read_to_string(root.join("index.html")).expect("read index");
pretty_assertions::assert_eq!(chapter, index);
assert!(index.contains(r#"<meta http-equiv="refresh" content="0; URL=chapter_1.html">"#))
} }
#[test] #[test]
@ -874,7 +874,7 @@ fn custom_fonts() {
actual actual
}; };
let has_fonts_css = |path: &Path| -> bool { 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") contents.contains("fonts/fonts.css")
}; };