From 2e1c979127a9619a5584228e4de2e75f2ac2be72 Mon Sep 17 00:00:00 2001 From: Tom Alexander Date: Tue, 19 Dec 2023 17:51:35 -0500 Subject: [PATCH] Add a prefix to footnote IDs. This avoids a conflict with multiple blog posts rendering in the same stream. --- src/command/build/render.rs | 2 + src/context/blog_stream.rs | 99 ++++++++++++++++++------- src/context/footnote_definition.rs | 4 +- src/context/footnote_reference.rs | 9 ++- src/context/render_context.rs | 9 +++ src/intermediate/footnote_definition.rs | 16 +++- src/intermediate/footnote_reference.rs | 18 +++-- 7 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/command/build/render.rs b/src/command/build/render.rs index 6373398..642a386 100644 --- a/src/command/build/render.rs +++ b/src/command/build/render.rs @@ -85,6 +85,7 @@ impl SiteRenderer { config, self.output_directory.as_path(), output_path.as_path(), + None, )?; let render_context = RenderBlogPostPage::new(render_context, &convert_input)?; let rendered_output = renderer_integration.render(render_context)?; @@ -165,6 +166,7 @@ impl SiteRenderer { config, self.output_directory.as_path(), output_file.as_path(), + None, )?; let blog_stream = RenderBlogStream::new(render_context, &convert_input)?; diff --git a/src/context/blog_stream.rs b/src/context/blog_stream.rs index 28f6bb0..9b571cc 100644 --- a/src/context/blog_stream.rs +++ b/src/context/blog_stream.rs @@ -86,7 +86,13 @@ render!( let children = original .original .into_iter() - .map(|blog_post| RenderBlogStreamEntry::new(render_context.clone(), blog_post)) + .enumerate() + .map(|(i, blog_post)| { + RenderBlogStreamEntry::new( + render_context.clone(), + &RenderBlogStreamEntryInput::new(blog_post, i), + ) + }) .collect::, _>>()?; let stream_pagination = if original.older_link.is_some() || original.newer_link.is_some() { @@ -107,6 +113,18 @@ render!( } ); +#[derive(Debug)] +pub(crate) struct RenderBlogStreamEntryInput<'a> { + original: &'a BlogPost, + offset: usize, +} + +impl<'a> RenderBlogStreamEntryInput<'a> { + fn new(original: &'a BlogPost, offset: usize) -> RenderBlogStreamEntryInput<'a> { + RenderBlogStreamEntryInput { original, offset } + } +} + #[derive(Debug, Serialize)] pub(crate) struct RenderBlogStreamEntry { /// The title that will be shown visibly on the page. @@ -119,37 +137,62 @@ pub(crate) struct RenderBlogStreamEntry { footnotes: Vec, } -render!(RenderBlogStreamEntry, BlogPost, original, render_context, { - let link_to_blog_post = get_web_path( - render_context.config, - render_context.output_directory, - render_context.output_file, - render_context - .config - .get_relative_path_to_post(&original.id), - )?; +render!( + RenderBlogStreamEntry, + RenderBlogStreamEntryInput, + original, + render_context, + { + let offset_string = original.offset.to_string(); + let render_context = { + let mut render_context = render_context.clone(); + render_context.id_addition = Some(offset_string.as_str()); + render_context + }; + let link_to_blog_post = get_web_path( + render_context.config, + render_context.output_directory, + render_context.output_file, + render_context + .config + .get_relative_path_to_post(&original.original.id), + )?; - // TODO: Should I guess an index page instead of erroring out? - let index_page = original - .get_index_page() - .ok_or_else(|| format!("Blog post {} needs an index page.", original.id))?; + // TODO: Should I guess an index page instead of erroring out? + let index_page = original + .original + .get_index_page() + .ok_or_else(|| format!("Blog post {} needs an index page.", original.original.id))?; - let title = index_page.title.clone(); + let title = index_page.title.clone(); - // TODO: Handle footnotes. - let children = index_page - .children - .iter() - .map(|child| RenderDocumentElement::new(render_context.clone(), child)) - .collect::, _>>()?; + let children = index_page + .children + .iter() + .map(|child| RenderDocumentElement::new(render_context.clone(), child)) + .collect::, _>>()?; - Ok(RenderBlogStreamEntry { - title, - self_link: Some(link_to_blog_post), - children, - footnotes: Vec::new(), - }) -}); + let footnotes = { + let mut ret = Vec::new(); + + for footnote in index_page.footnotes.iter() { + ret.push(RenderRealFootnoteDefinition::new( + render_context.clone(), + footnote, + )?); + } + + ret + }; + + Ok(RenderBlogStreamEntry { + title, + self_link: Some(link_to_blog_post), + children, + footnotes, + }) + } +); #[derive(Debug, Serialize)] pub(crate) struct RenderBlogStreamPagination { diff --git a/src/context/footnote_definition.rs b/src/context/footnote_definition.rs index de62fd4..8077f33 100644 --- a/src/context/footnote_definition.rs +++ b/src/context/footnote_definition.rs @@ -42,8 +42,8 @@ render!( }; Ok(RenderRealFootnoteDefinition { - definition_id: original.get_definition_id(), - reference_link: format!("#{}", original.get_reference_id()), + definition_id: original.get_definition_id(render_context.id_addition), + reference_link: format!("#{}", original.get_reference_id(render_context.id_addition)), label: original.get_display_label(), contents, }) diff --git a/src/context/footnote_reference.rs b/src/context/footnote_reference.rs index 3d4f00c..28e319d 100644 --- a/src/context/footnote_reference.rs +++ b/src/context/footnote_reference.rs @@ -19,11 +19,14 @@ render!( RenderFootnoteReference, IFootnoteReference, original, - _render_context, + render_context, { Ok(RenderFootnoteReference { - reference_id: original.get_reference_id(), - definition_link: format!("#{}", original.get_definition_id()), + reference_id: original.get_reference_id(render_context.id_addition), + definition_link: format!( + "#{}", + original.get_definition_id(render_context.id_addition) + ), label: original.get_display_label(), }) } diff --git a/src/context/render_context.rs b/src/context/render_context.rs index 4c8fcb7..ce4f933 100644 --- a/src/context/render_context.rs +++ b/src/context/render_context.rs @@ -10,6 +10,13 @@ pub(crate) struct RenderContext<'intermediate> { // TODO: Perhaps rename to output_root_directory. pub(crate) output_directory: &'intermediate Path, pub(crate) output_file: &'intermediate Path, + + /// An optional string that gets added to IDs in HTML. + /// + /// This is useful for cases where you may have conflicting HTML + /// IDs, for example, multiple blog posts with footnotes in a blog + /// stream. + pub(crate) id_addition: Option<&'intermediate str>, } impl<'intermediate> RenderContext<'intermediate> { @@ -17,11 +24,13 @@ impl<'intermediate> RenderContext<'intermediate> { config: &'intermediate Config, output_directory: &'intermediate Path, output_file: &'intermediate Path, + id_addition: Option<&'intermediate str>, ) -> Result, CustomError> { Ok(RenderContext { config, output_directory, output_file, + id_addition, }) } } diff --git a/src/intermediate/footnote_definition.rs b/src/intermediate/footnote_definition.rs index 336b40e..265c215 100644 --- a/src/intermediate/footnote_definition.rs +++ b/src/intermediate/footnote_definition.rs @@ -44,14 +44,22 @@ impl IRealFootnoteDefinition { /// Get an ID to refer to the first reference to this footnote definition. /// /// This ID could, for example, be used for the id attribute in HTML for the reference anchor tag. - pub(crate) fn get_reference_id(&self) -> String { - format!("fnr.{}", self.get_display_label()) + pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String { + let id_addition = id_addition + .map(|id_addition| format!("sec{}.", id_addition)) + .unwrap_or(String::default()); + + format!("{}fnr.{}", id_addition, self.get_display_label()) } /// Get an ID to refer to the footnote definition. /// /// This ID could, for example, be used for the id attribute in HTML for the definition anchor tag. - pub(crate) fn get_definition_id(&self) -> String { - format!("fn.{}", self.get_display_label()) + pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String { + let id_addition = id_addition + .map(|id_addition| format!("sec{}.", id_addition)) + .unwrap_or(String::default()); + + format!("{}fn.{}", id_addition, self.get_display_label()) } } diff --git a/src/intermediate/footnote_reference.rs b/src/intermediate/footnote_reference.rs index 7d3baa3..175edd2 100644 --- a/src/intermediate/footnote_reference.rs +++ b/src/intermediate/footnote_reference.rs @@ -32,20 +32,28 @@ impl IFootnoteReference { /// Get an ID to refer to this footnote reference. /// /// This ID could, for example, be used for the id attribute in HTML for the reference anchor tag. - pub(crate) fn get_reference_id(&self) -> String { + pub(crate) fn get_reference_id(&self, id_addition: Option<&str>) -> String { + let id_addition = id_addition + .map(|id_addition| format!("sec{}.", id_addition)) + .unwrap_or(String::default()); + if self.duplicate_offset == 0 { - format!("fnr.{}", self.get_display_label()) + format!("{}fnr.{}", id_addition, self.get_display_label()) } else { // Org-mode makes all duplicates use "100" but I figure there is no harm in giving each a unique ID. let append = 100 + self.duplicate_offset - 1; - format!("fnr.{}.{}", self.get_display_label(), append) + format!("{}fnr.{}.{}", id_addition, self.get_display_label(), append) } } /// Get an ID to refer to the footnote definition this footnote reference references. /// /// This ID could, for example, be used for the id attribute in HTML for the definition anchor tag. - pub(crate) fn get_definition_id(&self) -> String { - format!("fn.{}", self.get_display_label()) + pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String { + let id_addition = id_addition + .map(|id_addition| format!("sec{}.", id_addition)) + .unwrap_or(String::default()); + + format!("{}fn.{}", id_addition, self.get_display_label()) } }