Skip to content

Commit f0ba018

Browse files
committed
Improve HTML email formatter design
- Add 800px max-width to main container for better readability - Update container padding to 24px top, 32px left/right, 32px bottom - Match all borders to status color (red/yellow/green) when color is set - Adjust left padding to 28px when colored border is applied (to account for 4px thicker left border) - Set fact list (questions/answers) left column to fixed 160px width - Add 8px extra top and bottom margin to fact list blocks - Add max-height (400px) and scroll to code blocks to prevent excessive vertical space - Fix expandable block arrow to rotate 90° when opened (▶ → ▼) with smooth transition - Update test fixtures to reflect all design changes - Add code_block_long.html fixture to demonstrate scrollable code blocks These changes only affect HTML email formatting and do not impact Slack (Block Kit) or Teams (Adaptive Cards) integrations.
1 parent f130eac commit f0ba018

8 files changed

Lines changed: 65 additions & 20 deletions

File tree

elementary/messages/formats/html.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ class HTMLFormatter:
4343
"background-color:#ffffff",
4444
"border:1px solid #e5e7eb",
4545
"border-radius:6px",
46-
"padding:16px",
46+
"padding:24px 32px 32px 32px",
47+
"max-width:800px",
4748
]
4849

4950
_SECTION_MARGIN = "margin:0 0 12px"
@@ -72,7 +73,8 @@ def format_message_block(self, block: MessageBlock | ExpandableBlock) -> str:
7273
'<pre style="margin:0;padding:12px;'
7374
"background-color:#f8fafc;border-radius:4px;"
7475
"font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;"
75-
'font-size:13px;line-height:1.5;white-space:pre-wrap;">'
76+
'font-size:13px;line-height:1.5;white-space:pre-wrap;'
77+
'max-height:400px;overflow-y:auto;">'
7678
f"{code_html}</pre>"
7779
)
7880
elif isinstance(block, LinesBlock):
@@ -243,14 +245,15 @@ def _format_fact_list_block(self, block: FactListBlock) -> str:
243245
+ "".join(rows)
244246
+ "</table>"
245247
)
246-
return self._wrap_section(table_html)
248+
# Use custom margin with +8px on top and bottom
249+
return f'<div style="margin:8px 0 20px">{table_html}</div>'
247250

248251
def _format_fact_row(self, fact: FactBlock) -> str:
249252
title_html = self._format_line_block(fact.title)
250253
value_html = self._format_line_block(fact.value)
251254
title_style = ( # noqa: E231,E702
252255
"padding:4px 12px;font-weight:600;font-size:14px;color:#111827;"
253-
"max-width:200px;white-space:nowrap;"
256+
"width:160px;white-space:nowrap;"
254257
)
255258
value_weight = "700" if fact.primary else "400"
256259
value_style = f"padding:4px 12px;font-weight:{value_weight};font-size:14px;" # noqa: E231,E702
@@ -297,22 +300,29 @@ def _format_expandable_block(self, block: ExpandableBlock) -> str:
297300
"cursor:pointer;font-size:14px;user-select:none;-webkit-user-select:none;"
298301
"list-style:none;"
299302
)
300-
# Show appropriate arrow based on expanded state
301-
arrow = "▼" if block.expanded else "▶"
303+
# Always use right arrow - CSS will rotate it when opened
304+
arrow = "▶"
302305
arrow_style = (
303-
"margin-right:8px;color:#6b7280;font-size:10px;" # noqa: E231,E702
306+
"display:inline-block;margin-right:8px;color:#6b7280;font-size:10px;"
307+
"transition:transform 0.2s ease;" # noqa: E231,E702
304308
)
305309
body_style = (
306310
"padding:12px 16px;border-top:1px solid #e5e7eb;" # noqa: E231,E702
307311
)
308312
open_attr = " open" if block.expanded else ""
309-
# Need inline style to hide webkit disclosure marker
313+
# Add CSS to rotate arrow when details is open and hide webkit disclosure marker
314+
css_style = (
315+
"<style>"
316+
"summary::-webkit-details-marker{display:none;}"
317+
"details[open] > summary > span{transform:rotate(90deg);}"
318+
"</style>"
319+
)
310320
summary_with_marker_hidden = (
311321
f'<summary style="{summary_style}">'
312322
f'<span style="{arrow_style}">{arrow}</span>'
313323
f"{title_html}"
314324
"</summary>"
315-
"<style>summary::-webkit-details-marker{display:none;}</style>"
325+
f"{css_style}"
316326
)
317327
return (
318328
f'<details style="{container_style}"{open_attr}>'
@@ -329,8 +339,13 @@ def _coerce_table_cell(self, cell: object) -> str:
329339
def _build_container_style(self, color: Color | None) -> str:
330340
styles: list[str] = list(self._CONTAINER_STYLES)
331341
if color and color in COLOR_MAP:
342+
# Replace the default border color with the status color
343+
styles = [
344+
s if not s.startswith("border:") else f"border:1px solid {COLOR_MAP[color]}"
345+
for s in styles
346+
]
332347
styles.append(f"border-left:4px solid {COLOR_MAP[color]}") # noqa: E231
333-
styles.append("padding-left:12px") # noqa: E231
348+
styles.append("padding-left:28px") # noqa: E231
334349
return ";".join(styles)
335350

336351

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:14px;line-height:1.5;color:#1f2937;background-color:#ffffff;border:1px solid #e5e7eb;border-radius:6px;padding:16px;border-left:4px solid #33b989;padding-left:12px"><div style="margin:0 0 12px"><h1 style="margin:0;font-size:18px;line-height:1.4;">Main Header</h1></div><div style="margin:0 0 12px"><div style="margin:0;">Normal text <strong>Bold text</strong> <em>Italic text</em></div></div><div style="margin:0 0 12px"><pre style="margin:0;padding:12px;background-color:#f8fafc;border-radius:4px;font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;font-size:13px;line-height:1.5;white-space:pre-wrap;">def hello_world():
2-
print(&#x27;Hello, World!&#x27;)</pre></div><hr style="border:none;border-top:1px solid #e5e7eb;margin:16px 0;" /><div style="margin:0 0 12px"><ul style="margin:0;padding-left:24px;list-style-position:outside;"><li style="margin:0 0 4px;">First bullet point</li><li style="margin:0 0 4px;">Second bullet point</li></ul></div><div style="margin:0 0 12px"><ul style="margin:0;padding-left:24px;list-style-position:outside;"><li style="margin:0 0 4px;list-style:none;"><span style="margin-right:6px;"><span style="margin-right:4px;"></span></span>Check item</li></ul></div><div style="margin:0 0 12px"><table style="width:100%;border-collapse:collapse;"><tr><td style="padding:4px 12px;font-weight:600;font-size:14px;color:#111827;max-width:200px;white-space:nowrap;">Status</td><td style="padding:4px 12px;font-weight:400;font-size:14px;">Passed</td></tr><tr><td style="padding:4px 12px;font-weight:600;font-size:14px;color:#111827;max-width:200px;white-space:nowrap;">Tags</td><td style="padding:4px 12px;font-weight:400;font-size:14px;">test, example</td></tr></table></div><table style="width:100%;border-collapse:collapse;margin:0 0 12px;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden;"><thead><tr><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 1</th><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 2</th><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 3</th></tr></thead><tbody><tr><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 1</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 2</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 3</td></tr><tr><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 1</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 2</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 3</td></tr></tbody></table><details style="border:1px solid #e5e7eb;border-radius:6px;margin:16px 0;"><summary style="padding:12px 16px;font-weight:600;background-color:#f8fafc;cursor:pointer;font-size:14px;user-select:none;-webkit-user-select:none;list-style:none;"><span style="margin-right:8px;color:#6b7280;font-size:10px;"></span>Show Details</summary><style>summary::-webkit-details-marker{display:none;}</style><div style="padding:12px 16px;border-top:1px solid #e5e7eb;"><div style="margin:0 0 12px"><div style="margin:0;"><span style="margin-right:4px;">🔎</span> <strong>Details Section</strong></div><div style="margin:0;">Here&#x27;s some content with a <a style="color:#2563eb;text-decoration:none;" href="https://example.com" target="_blank" rel="noopener noreferrer">link</a></div></div></div></details><div style="margin:0 0 12px"><div style="margin:0;"><span style="color:#0ea5e9;">user1</span> <code style="font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;background-color:#eef2ff;border-radius:3px;padding:1px 4px;font-size:12px;">select 1</code></div></div></div>
1+
<div style="font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;font-size:14px;line-height:1.5;color:#1f2937;background-color:#ffffff;border:1px solid #33b989;border-radius:6px;padding:24px 32px 32px 32px;max-width:800px;border-left:4px solid #33b989;padding-left:28px"><div style="margin:0 0 12px"><h1 style="margin:0;font-size:18px;line-height:1.4;">Main Header</h1></div><div style="margin:0 0 12px"><div style="margin:0;">Normal text <strong>Bold text</strong> <em>Italic text</em></div></div><div style="margin:0 0 12px"><pre style="margin:0;padding:12px;background-color:#f8fafc;border-radius:4px;font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;font-size:13px;line-height:1.5;white-space:pre-wrap;max-height:400px;overflow-y:auto;">def hello_world():
2+
print(&#x27;Hello, World!&#x27;)</pre></div><hr style="border:none;border-top:1px solid #e5e7eb;margin:16px 0;" /><div style="margin:0 0 12px"><ul style="margin:0;padding-left:24px;list-style-position:outside;"><li style="margin:0 0 4px;">First bullet point</li><li style="margin:0 0 4px;">Second bullet point</li></ul></div><div style="margin:0 0 12px"><ul style="margin:0;padding-left:24px;list-style-position:outside;"><li style="margin:0 0 4px;list-style:none;"><span style="margin-right:6px;"><span style="margin-right:4px;"></span></span>Check item</li></ul></div><div style="margin:8px 0 20px"><table style="width:100%;border-collapse:collapse;"><tr><td style="padding:4px 12px;font-weight:600;font-size:14px;color:#111827;width:160px;white-space:nowrap;">Status</td><td style="padding:4px 12px;font-weight:400;font-size:14px;">Passed</td></tr><tr><td style="padding:4px 12px;font-weight:600;font-size:14px;color:#111827;width:160px;white-space:nowrap;">Tags</td><td style="padding:4px 12px;font-weight:400;font-size:14px;">test, example</td></tr></table></div><table style="width:100%;border-collapse:collapse;margin:0 0 12px;border:1px solid #e5e7eb;border-radius:6px;overflow:hidden;"><thead><tr><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 1</th><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 2</th><th style="text-align:left;padding:8px;border-bottom:1px solid #e5e7eb;font-weight:600;font-size:14px;background-color:#f8fafc;">Column 3</th></tr></thead><tbody><tr><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 1</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 2</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 1 Col 3</td></tr><tr><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 1</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 2</td><td style="padding:8px;border-bottom:1px solid #f3f4f6;vertical-align:top;font-size:14px;">Row 2 Col 3</td></tr></tbody></table><details style="border:1px solid #e5e7eb;border-radius:6px;margin:16px 0;"><summary style="padding:12px 16px;font-weight:600;background-color:#f8fafc;cursor:pointer;font-size:14px;user-select:none;-webkit-user-select:none;list-style:none;"><span style="display:inline-block;margin-right:8px;color:#6b7280;font-size:10px;transition:transform 0.2s ease;"></span>Show Details</summary><style>summary::-webkit-details-marker{display:none;}details[open] > summary > span{transform:rotate(90deg);}</style><div style="padding:12px 16px;border-top:1px solid #e5e7eb;"><div style="margin:0 0 12px"><div style="margin:0;"><span style="margin-right:4px;">🔎</span> <strong>Details Section</strong></div><div style="margin:0;">Here&#x27;s some content with a <a style="color:#2563eb;text-decoration:none;" href="https://example.com" target="_blank" rel="noopener noreferrer">link</a></div></div></div></details><div style="margin:0 0 12px"><div style="margin:0;"><span style="color:#0ea5e9;">user1</span> <code style="font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;background-color:#eef2ff;border-radius:3px;padding:1px 4px;font-size:12px;">select 1</code></div></div></div>

0 commit comments

Comments
 (0)