Add support for lazy loading images (#2211)
* Add optional decoding="async" loading="lazy" for img In theory, they can make the page load faster and show content faster. There’s one problem: CommonMark allows arbitrary inline elements in alt text. If I want to get the correct alt text, I need to match every inline event. I think most people will only use plain text, so I only match Event::Text. * Add very basic test for img This is the reason why we should use plain text when lazy_async_image is enabled. * Explain lazy_async_image in documentation * Add test with empty alt and special characters I totaly forgot one can leave the alt text empty. I thought I need to eliminate the alt attribute in that case, but actually empty alt text is better than not having an alt attribute at all: https://www.w3.org/TR/WCAG20-TECHS/H67.html https://www.boia.org/blog/images-that-dont-need-alternative-text-still-need-alt-attributes Thus I will leave the empty alt text. Another test is added to ensure alt text is properly escaped. I will remove the redundant escaping code after this commit. * Remove manually escaping alt text After removing the if-else inside the arm of Event::Text(text), the alt text is still escaped. Indeed they are redundant. * Use insta for snapshot testing `cargo insta review` looks cool! I wanted to dedup the cases variable, but my Rust skill is not good enough to declare a global vector.
This commit is contained in:
parent
0a9bfa16c2
commit
22dc32a589
@ -51,6 +51,8 @@ pub struct Markdown {
|
||||
/// The compiled extra themes into a theme set
|
||||
#[serde(skip_serializing, skip_deserializing)] // not a typo, 2 are need
|
||||
pub extra_theme_set: Arc<Option<ThemeSet>>,
|
||||
/// Add loading="lazy" decoding="async" to img tags. When turned on, the alt text must be plain text. Defaults to false
|
||||
pub lazy_async_image: bool,
|
||||
}
|
||||
|
||||
impl Markdown {
|
||||
@ -204,6 +206,7 @@ impl Default for Markdown {
|
||||
extra_syntaxes_and_themes: vec![],
|
||||
extra_syntax_set: None,
|
||||
extra_theme_set: Arc::new(None),
|
||||
lazy_async_image: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -252,6 +252,8 @@ pub fn markdown_to_html(
|
||||
|
||||
let mut stop_next_end_p = false;
|
||||
|
||||
let lazy_async_image = context.config.markdown.lazy_async_image;
|
||||
|
||||
let mut opts = Options::empty();
|
||||
let mut has_summary = false;
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
@ -387,13 +389,35 @@ pub fn markdown_to_html(
|
||||
events.push(Event::Html("</code></pre>\n".into()));
|
||||
}
|
||||
Event::Start(Tag::Image(link_type, src, title)) => {
|
||||
if is_colocated_asset_link(&src) {
|
||||
let link = if is_colocated_asset_link(&src) {
|
||||
let link = format!("{}{}", context.current_page_permalink, &*src);
|
||||
events.push(Event::Start(Tag::Image(link_type, link.into(), title)));
|
||||
link.into()
|
||||
} else {
|
||||
events.push(Event::Start(Tag::Image(link_type, src, title)));
|
||||
}
|
||||
src
|
||||
};
|
||||
|
||||
events.push(if lazy_async_image {
|
||||
let mut img_before_alt: String = "<img src=\"".to_string();
|
||||
cmark::escape::escape_href(&mut img_before_alt, &link)
|
||||
.expect("Could not write to buffer");
|
||||
if !title.is_empty() {
|
||||
img_before_alt
|
||||
.write_str("\" title=\"")
|
||||
.expect("Could not write to buffer");
|
||||
cmark::escape::escape_href(&mut img_before_alt, &title)
|
||||
.expect("Could not write to buffer");
|
||||
}
|
||||
img_before_alt.write_str("\" alt=\"").expect("Could not write to buffer");
|
||||
Event::Html(img_before_alt.into())
|
||||
} else {
|
||||
Event::Start(Tag::Image(link_type, link, title))
|
||||
});
|
||||
}
|
||||
Event::End(Tag::Image(..)) => events.push(if lazy_async_image {
|
||||
Event::Html("\" loading=\"lazy\" decoding=\"async\" />".into())
|
||||
} else {
|
||||
event
|
||||
}),
|
||||
Event::Start(Tag::Link(link_type, link, title)) if link.is_empty() => {
|
||||
error = Some(Error::msg("There is a link that is missing a URL"));
|
||||
events.push(Event::Start(Tag::Link(link_type, "#".into(), title)));
|
||||
|
33
components/markdown/tests/img.rs
Normal file
33
components/markdown/tests/img.rs
Normal file
@ -0,0 +1,33 @@
|
||||
mod common;
|
||||
use config::Config;
|
||||
|
||||
#[test]
|
||||
fn can_transform_image() {
|
||||
let cases = vec![
|
||||
"![haha](https://example.com/abc.jpg)",
|
||||
"![](https://example.com/abc.jpg)",
|
||||
"![ha\"h>a](https://example.com/abc.jpg)",
|
||||
"![__ha__*ha*](https://example.com/abc.jpg)",
|
||||
"![ha[ha](https://example.com)](https://example.com/abc.jpg)",
|
||||
];
|
||||
|
||||
let body = common::render(&cases.join("\n")).unwrap().body;
|
||||
insta::assert_snapshot!(body);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_add_lazy_loading_and_async_decoding() {
|
||||
let cases = vec![
|
||||
"![haha](https://example.com/abc.jpg)",
|
||||
"![](https://example.com/abc.jpg)",
|
||||
"![ha\"h>a](https://example.com/abc.jpg)",
|
||||
"![__ha__*ha*](https://example.com/abc.jpg)",
|
||||
"![ha[ha](https://example.com)](https://example.com/abc.jpg)",
|
||||
];
|
||||
|
||||
let mut config = Config::default_for_test();
|
||||
config.markdown.lazy_async_image = true;
|
||||
|
||||
let body = common::render_with_config(&cases.join("\n"), config).unwrap().body;
|
||||
insta::assert_snapshot!(body);
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
source: components/markdown/tests/img.rs
|
||||
expression: body
|
||||
---
|
||||
<p><img src="https://example.com/abc.jpg" alt="haha" loading="lazy" decoding="async" />
|
||||
<img src="https://example.com/abc.jpg" alt="" loading="lazy" decoding="async" />
|
||||
<img src="https://example.com/abc.jpg" alt="ha"h>a" loading="lazy" decoding="async" />
|
||||
<img src="https://example.com/abc.jpg" alt="<strong>ha</strong><em>ha</em>" loading="lazy" decoding="async" />
|
||||
<img src="https://example.com/abc.jpg" alt="ha<a href="https://example.com">ha</a>" loading="lazy" decoding="async" /></p>
|
||||
|
@ -0,0 +1,10 @@
|
||||
---
|
||||
source: components/markdown/tests/img.rs
|
||||
expression: body
|
||||
---
|
||||
<p><img src="https://example.com/abc.jpg" alt="haha" />
|
||||
<img src="https://example.com/abc.jpg" alt="" />
|
||||
<img src="https://example.com/abc.jpg" alt="ha"h>a" />
|
||||
<img src="https://example.com/abc.jpg" alt="haha" />
|
||||
<img src="https://example.com/abc.jpg" alt="haha" /></p>
|
||||
|
@ -125,6 +125,11 @@ external_links_no_referrer = false
|
||||
# For example, `...` into `…`, `"quote"` into `“curly”` etc
|
||||
smart_punctuation = false
|
||||
|
||||
# Whether to set decoding="async" and loading="lazy" for all images
|
||||
# When turned on, the alt text must be plain text.
|
||||
# For example, `![xx](...)` is ok but `![*x*x](...)` isn’t ok
|
||||
lazy_async_image = false
|
||||
|
||||
# Configuration of the link checker.
|
||||
[link_checker]
|
||||
# Skip link checking for external URLs that start with these prefixes
|
||||
|
Loading…
Reference in New Issue
Block a user