use orbital_markdown::{
render_to_html, CitationRef, MentionLinkStyle, OrbitalMarkdownOptions, RenderContext,
};
use crate::types::{HistoryAttachment, HistoryCitation, HistoryMention};
#[derive(Clone, Copy, Debug, Default)]
pub struct HistoryMarkdownRenderOptions {
pub enable_citations: bool,
pub enable_mentions: bool,
pub enable_images: bool,
}
pub fn render_history_markdown(
markdown: &str,
citations: &[HistoryCitation],
_mentions: &[HistoryMention],
attachments: &[HistoryAttachment],
options: HistoryMarkdownRenderOptions,
) -> String {
if markdown.trim().is_empty() {
return String::new();
}
let cite_refs: Vec<CitationRef<'_>> = citations
.iter()
.map(|c| CitationRef {
id: &c.id,
display_index: c.display_index,
})
.collect();
let attachment_urls: Vec<&str> = attachments.iter().map(|a| a.url.as_str()).collect();
let mut html = render_to_html(
markdown,
&OrbitalMarkdownOptions {
enable_citation_refs: options.enable_citations && !citations.is_empty(),
enable_mention_refs: options.enable_mentions,
enable_images: options.enable_images,
citation_style: orbital_markdown::CitationLinkStyle::history(),
mention_style: MentionLinkStyle::history(),
},
&RenderContext {
citations: &cite_refs,
attachment_urls: &attachment_urls,
},
);
if options.enable_images {
html = append_attachment_images(&html, attachments);
}
html
}
fn is_image_attachment(att: &HistoryAttachment) -> bool {
if att
.mime
.as_deref()
.is_some_and(|mime| mime.starts_with("image/"))
{
return true;
}
att.url.rsplit('/').next().is_some_and(|name| {
matches!(
name.rsplit_once('.').map(|(_, ext)| ext.to_ascii_lowercase()),
Some(ext) if matches!(ext.as_str(), "png" | "jpg" | "jpeg" | "gif" | "webp" | "svg")
)
})
}
fn append_attachment_images(html: &str, attachments: &[HistoryAttachment]) -> String {
let mut out = html.to_string();
for att in attachments {
if !is_image_attachment(att) || out.contains(&att.url) {
continue;
}
let alt = att.name.as_deref().unwrap_or("");
out.push_str(&format!(
"<p><img class=\"orbital-markdown__image\" loading=\"lazy\" src=\"{}\" alt=\"{alt}\"></p>",
att.url
));
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bold_markdown() {
let html = render_history_markdown(
"**bold** text",
&[],
&[],
&[],
HistoryMarkdownRenderOptions::default(),
);
assert!(html.contains("<strong>") || html.contains("<b>"));
}
#[test]
fn unresolved_citation_ref_passthrough() {
let html = render_history_markdown(
"See [^audit-1] for the audit trail.",
&[],
&[],
&[],
HistoryMarkdownRenderOptions::default(),
);
assert!(html.contains("[^audit-1]"));
}
#[test]
fn history_citation_anchors() {
let citations = vec![HistoryCitation {
id: "audit-1".into(),
display_index: 1,
}];
let html = render_history_markdown(
"See [^audit-1] here.",
&citations,
&[],
&[],
HistoryMarkdownRenderOptions {
enable_citations: true,
..Default::default()
},
);
assert!(html.contains("history-citation-ref-audit-1"));
assert!(html.contains("orbital-link orbital-link--inline"));
}
#[test]
fn history_mention_anchors() {
let html = render_history_markdown(
"Hi @[Jordan Lee](u1)!",
&[],
&[],
&[],
HistoryMarkdownRenderOptions {
enable_mentions: true,
..Default::default()
},
);
assert!(html.contains("data-mention-id=\"u1\""));
assert!(html.contains("orbital-history__mention-ref"));
assert!(html.contains("orbital-link orbital-link--inline"));
}
#[test]
fn history_markdown_link_uses_orbital_link() {
let html = render_history_markdown(
"Updated the [design doc](https://example.com).",
&[],
&[],
&[],
HistoryMarkdownRenderOptions::default(),
);
assert!(html.contains("orbital-link orbital-link--inline"));
assert!(html.contains("design doc"));
}
#[test]
fn history_markdown_image() {
let html = render_history_markdown(
"",
&[],
&[],
&[],
HistoryMarkdownRenderOptions {
enable_images: true,
..Default::default()
},
);
assert!(html.contains("orbital-markdown__image"));
}
#[test]
fn dedupes_inline_image_when_attachment_url_matches() {
let attachments = vec![HistoryAttachment {
url: "https://example.com/a.png".into(),
name: Some("a.png".into()),
mime: Some("image/png".into()),
}];
let html = render_history_markdown(
"",
&[],
&[],
&attachments,
HistoryMarkdownRenderOptions {
enable_images: true,
..Default::default()
},
);
assert_eq!(html.matches("orbital-markdown__image").count(), 1);
}
}