Skip to main content

orbital_markdown/
mentions.rs

1use regex::Regex;
2
3use crate::links::ORBITAL_LINK_INLINE_CLASS;
4use crate::mention_style::MentionLinkStyle;
5
6/// Mention id + display label for `@[label](id)` resolution.
7#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct MentionRef<'a> {
9    pub id: &'a str,
10    pub display_label: &'a str,
11}
12
13/// Sentinel href prefix for mention links produced by [`prepare_mention_markdown`].
14pub const MENTION_HREF_PREFIX: &str = "mention-ref:";
15
16/// Rewrite `@[label](id)` to markdown links the parser understands.
17pub fn prepare_mention_markdown(markdown: &str) -> String {
18    let Ok(re) = Regex::new(r"@\[([^\]]+)\]\(([^)]+)\)") else {
19        return markdown.to_string();
20    };
21    re.replace_all(markdown, |caps: &regex::Captures| {
22        let label = caps.get(1).map(|m| m.as_str()).unwrap_or("");
23        let id = caps.get(2).map(|m| m.as_str()).unwrap_or("");
24        format!("[@{label}]({MENTION_HREF_PREFIX}{id})")
25    })
26    .to_string()
27}
28
29/// Style mention links injected during markdown preparation.
30pub fn replace_mention_links(html: &str, style: MentionLinkStyle) -> String {
31    let Ok(re) = Regex::new(&format!(
32        r#"(?i)<a href="{prefix}([^"]+)">([^<]*)</a>"#,
33        prefix = regex::escape(MENTION_HREF_PREFIX)
34    )) else {
35        return html.to_string();
36    };
37
38    re.replace_all(html, |caps: &regex::Captures| {
39        let id = caps.get(1).map(|m| m.as_str()).unwrap_or("");
40        let label = caps.get(2).map(|m| m.as_str()).unwrap_or("");
41        format!(
42            r##"<a href="#mention-{id}" class="{class} {link_class}" data-mention-id="{id}">{label}</a>"##,
43            class = style.anchor_class,
44            link_class = ORBITAL_LINK_INLINE_CLASS,
45            id = id,
46            label = label,
47        )
48    })
49    .to_string()
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn prepares_and_replaces_mention_links() {
58        let md = prepare_mention_markdown("Hi @[Jordan Lee](u1)!");
59        assert!(md.contains("mention-ref:u1"));
60        let html = "<p>Hi <a href=\"mention-ref:u1\">@Jordan Lee</a>!</p>".to_string();
61        let out = replace_mention_links(&html, MentionLinkStyle::history());
62        assert!(out.contains("data-mention-id=\"u1\""));
63        assert!(out.contains("orbital-history__mention-ref"));
64        assert!(out.contains("orbital-link orbital-link--inline"));
65    }
66}