mirror of
https://github.com/lexmount/moli.git
synced 2026-09-26 16:01:30 +00:00
fix(fetch): preserve details and discard empty SVG placeholders
This commit is contained in:
@@ -234,7 +234,20 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
|
||||
.attribute(node, "src")
|
||||
.filter(|src| !src.is_empty())
|
||||
{
|
||||
self.writer().image(alt, src, title);
|
||||
// Lazy-image placeholders carry no visible image. Keep
|
||||
// accessible alternate text without emitting a data URI.
|
||||
if is_empty_svg_placeholder(src) {
|
||||
if !alt.is_empty()
|
||||
&& !self
|
||||
.dom
|
||||
.attribute(node, "aria-hidden")
|
||||
.is_some_and(|value| value.trim().eq_ignore_ascii_case("true"))
|
||||
{
|
||||
self.writer().text(alt);
|
||||
}
|
||||
} else {
|
||||
self.writer().image(alt, src, title);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -428,6 +441,13 @@ impl<'a, D: Dom + ?Sized> Machine<'a, D> {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_empty_svg_placeholder(src: &str) -> bool {
|
||||
let lower = src.to_ascii_lowercase();
|
||||
lower.starts_with("data:image/svg+xml,%3csvg")
|
||||
&& lower.ends_with("%3c/svg%3e")
|
||||
&& lower.matches("%3c").count() == 2
|
||||
}
|
||||
|
||||
fn class_language(class: Option<&str>) -> Option<&str> {
|
||||
class?
|
||||
.split_ascii_whitespace()
|
||||
|
||||
@@ -225,6 +225,21 @@ fn images_without_sources_do_not_emit_placeholder_markdown() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_svg_lazy_image_placeholders_do_not_emit_data_uris() {
|
||||
let placeholder = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20720%20960'%3E%3C/svg%3E";
|
||||
let html = format!("before<img alt='Recipe photo' src=\"{placeholder}\">after");
|
||||
assert_eq!(markdown(&html, false), "beforeRecipe photoafter");
|
||||
|
||||
let html =
|
||||
format!("before<img alt='Recipe photo' aria-hidden='true' src=\"{placeholder}\">after");
|
||||
assert_eq!(markdown(&html, false), "beforeafter");
|
||||
|
||||
let real_svg = "data:image/svg+xml,%3Csvg%3E%3Cpath%20d='M0%200'/%3E%3C/svg%3E";
|
||||
let html = format!("<img alt='Logo' src=\"{real_svg}\">");
|
||||
assert!(markdown(&html, false).contains(real_svg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_paragraphs_and_code_blocks_require_blank_lines_between_items() {
|
||||
for (html, expected) in [
|
||||
|
||||
@@ -157,8 +157,6 @@ fn apply_strip_options(dom: &mut NativeDom, strip: RendererPageDumpStripOptions)
|
||||
| "dialog"
|
||||
| "menu"
|
||||
| "menuitem"
|
||||
| "details"
|
||||
| "summary"
|
||||
));
|
||||
if should_remove {
|
||||
remove.push(node_id);
|
||||
|
||||
@@ -129,6 +129,33 @@ async fn render_page_dump_default_html_uses_renderer_live_serialize() -> Result<
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn strip_ui_keeps_content_inside_details() -> Result<()> {
|
||||
let (_browser, mut page, http_server) = load_page(
|
||||
"<!doctype html><html><body><nav>Menu</nav><main><details><summary>Product details</summary><p>Critical product description</p></details></main></body></html>",
|
||||
)
|
||||
.await?;
|
||||
let rendered = render_page_dump_with_options_async(
|
||||
&mut page,
|
||||
DumpFormat::Markdown,
|
||||
StripOptions {
|
||||
js: false,
|
||||
css: false,
|
||||
ui: true,
|
||||
},
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
assert!(!rendered.contains("Menu"));
|
||||
assert!(rendered.contains("Product details"));
|
||||
assert!(rendered.contains("Critical product description"));
|
||||
http_server.abort();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn render_page_dump_postprocessed_html_uses_renderer_live_dump() -> Result<()> {
|
||||
let (_browser, mut page, http_server) = load_page(
|
||||
|
||||
Reference in New Issue
Block a user