Skip to main content

max / pter

Release 0.1.1: version bump + clippy-clean for publish The DOM depth-cap DoS fix landed in 61ce0de. This prepares the crates.io release: bump 0.1.0 -> 0.1.1, and clear two pre-existing clippy lints so the release ships clean -- move is_hidden ahead of the test module (items_after_test_module) and collapse a nested match arm in replies.rs (collapsible_match). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-07 14:20 UTC
Commit: 1ab712760391bb2b683058722c0f7c561d8bafb8
Parent: 61ce0de
4 files changed, +30 insertions, -32 deletions
M Cargo.lock +1 -1
@@ -702,7 +702,7 @@ dependencies = [
702 702
703 703 [[package]]
704 704 name = "pter"
705 - version = "0.1.0"
705 + version = "0.1.1"
706 706 dependencies = [
707 707 "criterion",
708 708 "proptest",
M Cargo.toml +1 -1
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "pter"
3 - version = "0.1.0"
3 + version = "0.1.1"
4 4 edition = "2024"
5 5 description = "Plain Text Email Renderer — convert HTML email bodies into readable markdown"
6 6 license = "MIT"
M src/elements.rs +26 -26
@@ -128,6 +128,32 @@ pub fn is_tracking_pixel(el: &Element) -> bool {
128 128 false
129 129 }
130 130
131 + /// Check if an element is hidden via inline style.
132 + ///
133 + /// Catches display:none, visibility:hidden, and spacer tricks
134 + /// like font-size:0 or line-height:0 (commonly used in email templates).
135 + pub fn is_hidden(el: &Element) -> bool {
136 + if let Some(style) = el.attr("style") {
137 + let s = style.to_lowercase();
138 + if s.contains("display:none")
139 + || s.contains("display: none")
140 + || s.contains("visibility:hidden")
141 + || s.contains("visibility: hidden")
142 + || s.contains("font-size:0")
143 + || s.contains("font-size: 0")
144 + || s.contains("line-height:0")
145 + || s.contains("line-height: 0")
146 + || (s.contains("height:0") && s.contains("overflow:hidden"))
147 + || (s.contains("height: 0") && s.contains("overflow: hidden"))
148 + || s.contains("max-height:0")
149 + || s.contains("max-height: 0")
150 + {
151 + return true;
152 + }
153 + }
154 + false
155 + }
156 +
131 157 #[cfg(test)]
132 158 mod tests {
133 159 use super::*;
@@ -371,29 +397,3 @@ mod tests {
371 397 }
372 398 }
373 399
374 - /// Check if an element is hidden via inline style.
375 - ///
376 - /// Catches display:none, visibility:hidden, and spacer tricks
377 - /// like font-size:0 or line-height:0 (commonly used in email templates).
378 - pub fn is_hidden(el: &Element) -> bool {
379 - if let Some(style) = el.attr("style") {
380 - let s = style.to_lowercase();
381 - if s.contains("display:none")
382 - || s.contains("display: none")
383 - || s.contains("visibility:hidden")
384 - || s.contains("visibility: hidden")
385 - || s.contains("font-size:0")
386 - || s.contains("font-size: 0")
387 - || s.contains("line-height:0")
388 - || s.contains("line-height: 0")
389 - || (s.contains("height:0") && s.contains("overflow:hidden"))
390 - || (s.contains("height: 0") && s.contains("overflow: hidden"))
391 - || s.contains("max-height:0")
392 - || s.contains("max-height: 0")
393 - {
394 - return true;
395 - }
396 - }
397 - false
398 - }
399 -
M src/replies.rs +2 -4
@@ -142,10 +142,8 @@ fn has_attribution_then_quote(el: ElementRef) -> bool {
142 142
143 143 for child in el.children() {
144 144 match child.value() {
145 - Node::Text(text) => {
146 - if is_attribution_text(text.text.trim()) {
147 - found_attribution = true;
148 - }
145 + Node::Text(text) if is_attribution_text(text.text.trim()) => {
146 + found_attribution = true;
149 147 }
150 148 Node::Element(e) => {
151 149 if found_attribution && e.name() == "blockquote" {