rs_web/markdown/transforms/
lazy_images.rs1use pulldown_cmark::{CowStr, Event, Tag, TagEnd};
2
3use super::AstTransform;
4use crate::markdown::TransformContext;
5
6pub struct LazyImagesTransform;
8
9impl AstTransform for LazyImagesTransform {
10 fn name(&self) -> &'static str {
11 "lazy_images"
12 }
13
14 fn priority(&self) -> i32 {
15 50
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() + 10);
20 let mut in_image = false;
21 let mut image_info: Option<(CowStr<'a>, CowStr<'a>, CowStr<'a>)> = None;
22
23 for event in events {
24 match &event {
25 Event::Start(Tag::Image {
26 link_type,
27 dest_url,
28 title,
29 id,
30 }) => {
31 in_image = true;
32 image_info = Some((dest_url.clone(), title.clone(), id.clone()));
33 let _ = link_type; }
36 Event::End(TagEnd::Image) if in_image => {
37 in_image = false;
38 if let Some((dest_url, title, _id)) = image_info.take() {
39 let src = if !dest_url.starts_with("http") && !dest_url.ends_with(".webp") {
41 if let Some(pos) = dest_url.rfind('.') {
43 format!("{}.webp", &dest_url[..pos])
44 } else {
45 dest_url.to_string()
46 }
47 } else {
48 dest_url.to_string()
49 };
50
51 let html = format!(
52 r#"<img src="{}" alt="{}" loading="lazy" decoding="async">"#,
53 src,
54 html_escape(&title)
55 );
56 result.push(Event::Html(CowStr::from(html)));
57 }
58 }
59 Event::Text(text) if in_image => {
60 if let Some((dest, _title, id)) = image_info.take() {
62 image_info = Some((dest, text.clone(), id));
63 }
64 }
65 _ => {
66 result.push(event);
67 }
68 }
69 }
70
71 result
72 }
73}
74
75fn html_escape(s: &str) -> String {
76 s.replace('&', "&")
77 .replace('<', "<")
78 .replace('>', ">")
79 .replace('"', """)
80}