rs_web/markdown/transforms/
external_links.rs1use pulldown_cmark::{CowStr, Event, Tag, TagEnd};
2
3use super::AstTransform;
4use crate::markdown::TransformContext;
5
6pub struct ExternalLinksTransform;
8
9impl AstTransform for ExternalLinksTransform {
10 fn name(&self) -> &'static str {
11 "external_links"
12 }
13
14 fn priority(&self) -> i32 {
15 70
16 }
17
18 fn transform<'a>(&self, events: Vec<Event<'a>>, _ctx: &TransformContext<'_>) -> Vec<Event<'a>> {
19 let mut result = Vec::with_capacity(events.len());
20 let mut in_external_link = false;
21
22 for event in events {
23 match &event {
24 Event::Start(Tag::Link {
25 dest_url, title, ..
26 }) => {
27 if is_external_url(dest_url) {
28 in_external_link = true;
29 let html = format!(
31 r#"<a href="{}" target="_blank" rel="noopener noreferrer"{}>"#,
32 html_escape(dest_url),
33 if title.is_empty() {
34 String::new()
35 } else {
36 format!(r#" title="{}""#, html_escape(title))
37 }
38 );
39 result.push(Event::Html(CowStr::from(html)));
40 } else {
41 result.push(event);
42 }
43 }
44 Event::End(TagEnd::Link) if in_external_link => {
45 in_external_link = false;
46 result.push(Event::Html(CowStr::from("</a>")));
47 }
48 _ => {
49 result.push(event);
50 }
51 }
52 }
53
54 result
55 }
56}
57
58fn is_external_url(url: &str) -> bool {
59 url.starts_with("http://") || url.starts_with("https://")
60}
61
62fn html_escape(s: &str) -> String {
63 s.replace('&', "&")
64 .replace('<', "<")
65 .replace('>', ">")
66 .replace('"', """)
67}