Add a prefix to footnote IDs.

This avoids a conflict with multiple blog posts rendering in the same stream.
This commit is contained in:
Tom Alexander 2023-12-19 17:51:35 -05:00
parent d4b290ebe6
commit 2e1c979127
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
7 changed files with 115 additions and 42 deletions

View File

@ -85,6 +85,7 @@ impl SiteRenderer {
config, config,
self.output_directory.as_path(), self.output_directory.as_path(),
output_path.as_path(), output_path.as_path(),
None,
)?; )?;
let render_context = RenderBlogPostPage::new(render_context, &convert_input)?; let render_context = RenderBlogPostPage::new(render_context, &convert_input)?;
let rendered_output = renderer_integration.render(render_context)?; let rendered_output = renderer_integration.render(render_context)?;
@ -165,6 +166,7 @@ impl SiteRenderer {
config, config,
self.output_directory.as_path(), self.output_directory.as_path(),
output_file.as_path(), output_file.as_path(),
None,
)?; )?;
let blog_stream = RenderBlogStream::new(render_context, &convert_input)?; let blog_stream = RenderBlogStream::new(render_context, &convert_input)?;

View File

@ -86,7 +86,13 @@ render!(
let children = original let children = original
.original .original
.into_iter() .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::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let stream_pagination = if original.older_link.is_some() || original.newer_link.is_some() { 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)] #[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamEntry { pub(crate) struct RenderBlogStreamEntry {
/// The title that will be shown visibly on the page. /// The title that will be shown visibly on the page.
@ -119,37 +137,62 @@ pub(crate) struct RenderBlogStreamEntry {
footnotes: Vec<RenderRealFootnoteDefinition>, footnotes: Vec<RenderRealFootnoteDefinition>,
} }
render!(RenderBlogStreamEntry, BlogPost, original, render_context, { render!(
let link_to_blog_post = get_web_path( RenderBlogStreamEntry,
render_context.config, RenderBlogStreamEntryInput,
render_context.output_directory, original,
render_context.output_file, render_context,
render_context {
.config let offset_string = original.offset.to_string();
.get_relative_path_to_post(&original.id), 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? // TODO: Should I guess an index page instead of erroring out?
let index_page = original let index_page = original
.get_index_page() .original
.ok_or_else(|| format!("Blog post {} needs an index page.", original.id))?; .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
let children = index_page .children
.children .iter()
.iter() .map(|child| RenderDocumentElement::new(render_context.clone(), child))
.map(|child| RenderDocumentElement::new(render_context.clone(), child)) .collect::<Result<Vec<_>, _>>()?;
.collect::<Result<Vec<_>, _>>()?;
Ok(RenderBlogStreamEntry { let footnotes = {
title, let mut ret = Vec::new();
self_link: Some(link_to_blog_post),
children, for footnote in index_page.footnotes.iter() {
footnotes: Vec::new(), ret.push(RenderRealFootnoteDefinition::new(
}) render_context.clone(),
}); footnote,
)?);
}
ret
};
Ok(RenderBlogStreamEntry {
title,
self_link: Some(link_to_blog_post),
children,
footnotes,
})
}
);
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub(crate) struct RenderBlogStreamPagination { pub(crate) struct RenderBlogStreamPagination {

View File

@ -42,8 +42,8 @@ render!(
}; };
Ok(RenderRealFootnoteDefinition { Ok(RenderRealFootnoteDefinition {
definition_id: original.get_definition_id(), definition_id: original.get_definition_id(render_context.id_addition),
reference_link: format!("#{}", original.get_reference_id()), reference_link: format!("#{}", original.get_reference_id(render_context.id_addition)),
label: original.get_display_label(), label: original.get_display_label(),
contents, contents,
}) })

View File

@ -19,11 +19,14 @@ render!(
RenderFootnoteReference, RenderFootnoteReference,
IFootnoteReference, IFootnoteReference,
original, original,
_render_context, render_context,
{ {
Ok(RenderFootnoteReference { Ok(RenderFootnoteReference {
reference_id: original.get_reference_id(), reference_id: original.get_reference_id(render_context.id_addition),
definition_link: format!("#{}", original.get_definition_id()), definition_link: format!(
"#{}",
original.get_definition_id(render_context.id_addition)
),
label: original.get_display_label(), label: original.get_display_label(),
}) })
} }

View File

@ -10,6 +10,13 @@ pub(crate) struct RenderContext<'intermediate> {
// TODO: Perhaps rename to output_root_directory. // TODO: Perhaps rename to output_root_directory.
pub(crate) output_directory: &'intermediate Path, pub(crate) output_directory: &'intermediate Path,
pub(crate) output_file: &'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> { impl<'intermediate> RenderContext<'intermediate> {
@ -17,11 +24,13 @@ impl<'intermediate> RenderContext<'intermediate> {
config: &'intermediate Config, config: &'intermediate Config,
output_directory: &'intermediate Path, output_directory: &'intermediate Path,
output_file: &'intermediate Path, output_file: &'intermediate Path,
id_addition: Option<&'intermediate str>,
) -> Result<RenderContext<'intermediate>, CustomError> { ) -> Result<RenderContext<'intermediate>, CustomError> {
Ok(RenderContext { Ok(RenderContext {
config, config,
output_directory, output_directory,
output_file, output_file,
id_addition,
}) })
} }
} }

View File

@ -44,14 +44,22 @@ impl IRealFootnoteDefinition {
/// Get an ID to refer to the first reference to this footnote definition. /// 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. /// 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 {
format!("fnr.{}", self.get_display_label()) 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. /// 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. /// 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 { pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
format!("fn.{}", self.get_display_label()) let id_addition = id_addition
.map(|id_addition| format!("sec{}.", id_addition))
.unwrap_or(String::default());
format!("{}fn.{}", id_addition, self.get_display_label())
} }
} }

View File

@ -32,20 +32,28 @@ impl IFootnoteReference {
/// Get an ID to refer to this footnote reference. /// 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. /// 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 { if self.duplicate_offset == 0 {
format!("fnr.{}", self.get_display_label()) format!("{}fnr.{}", id_addition, self.get_display_label())
} else { } else {
// Org-mode makes all duplicates use "100" but I figure there is no harm in giving each a unique ID. // 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; 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. /// 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. /// 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 { pub(crate) fn get_definition_id(&self, id_addition: Option<&str>) -> String {
format!("fn.{}", self.get_display_label()) let id_addition = id_addition
.map(|id_addition| format!("sec{}.", id_addition))
.unwrap_or(String::default());
format!("{}fn.{}", id_addition, self.get_display_label())
} }
} }