Enhance newsletter HTML generation and UI components

- Introduce helper functions `_outlook_font` and `_outlook_link` for better formatting in Outlook-compatible HTML.
- Update `create_outlook_html` to utilize new font and link functions, improving the overall appearance of the newsletter.
- Modify CSS to add styles for a new copy dialog overlay and related components.
- Refactor JavaScript to improve copy functionality and add a dialog for copying HTML content.
- Update dashboard template to enhance user instructions for copying and downloading HTML content.
This commit is contained in:
smueller
2026-07-03 13:17:11 +02:00
parent 90cdb7e922
commit df4424fdf9
4 changed files with 259 additions and 155 deletions

View File

@@ -263,6 +263,28 @@ OUTLOOK_MUTED = "#666666"
OUTLOOK_LINK = "#0563C1"
OUTLOOK_HEADING = "#003366"
OUTLOOK_BORDER = "#DDDDDD"
OUTLOOK_WHITE = "#FFFFFF"
def _outlook_font(
text: str,
*,
color: str = OUTLOOK_TEXT,
size: str = "2",
bold: bool = False,
) -> str:
inner = escape(text)
if bold:
inner = f"<b>{inner}</b>"
return f'<font face="Arial" color="{color}" size="{size}">{inner}</font>'
def _outlook_link(title: str, url: str) -> str:
return (
f'<a href="{escape(url)}">'
f'<font face="Arial" color="{OUTLOOK_LINK}" size="2">{escape(title)}</font>'
f"</a>"
)
def create_outlook_html(
@@ -271,18 +293,18 @@ def create_outlook_html(
display: DisplayOptions | None = None,
category: str = "",
) -> str:
"""Vereinfachtes HTML für Outlook/Word nur Tabellen und Inline-Styles."""
"""Word/Outlook-kompatibles HTML mit font-Tags und bgcolor (überlebt Einfügen in Outlook)."""
display = display or DEFAULT_DISPLAY
new_articles, edited_articles = split_articles_by_type(articles)
period_start, period_end = _period_range(days)
created_at = datetime.now().strftime("%d.%m.%Y %H:%M")
intro = escape(_intro_sentence(period_start, period_end))
intro = _intro_sentence(period_start, period_end)
subject = escape(create_subject(days, category))
if not new_articles and not edited_articles:
body_rows = f"""<tr>
<td style="padding:12px 0;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_MUTED};">
Im gewählten Zeitraum wurden keine passenden Wiki-Änderungen gefunden.
<td bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:12px 0;">
{_outlook_font("Im gewählten Zeitraum wurden keine passenden Wiki-Änderungen gefunden.", color=OUTLOOK_MUTED)}
</td>
</tr>"""
else:
@@ -300,15 +322,18 @@ def create_outlook_html(
category_block = ""
if category.strip():
category_block = f"""<p style="margin:0 0 8px;font-family:{OUTLOOK_FONT};font-size:10pt;color:{OUTLOOK_MUTED};">
<strong style="font-family:{OUTLOOK_FONT};color:{OUTLOOK_HEADING};">Filter:</strong> Kategorie {escape(category.strip())}
</p>"""
category_block = (
f'<p style="margin:0 0 10px;">'
f'{_outlook_font("Filter: ", color=OUTLOOK_HEADING, bold=True)}'
f'{_outlook_font(f"Kategorie {category.strip()}", color=OUTLOOK_MUTED, size="1")}'
f"</p>"
)
total = len(new_articles) + len(edited_articles)
summary_row = f"""<tr>
<td style="padding:16px 0 0;font-family:{OUTLOOK_FONT};font-size:10pt;color:{OUTLOOK_MUTED};border-top:1px solid {OUTLOOK_BORDER};">
<strong style="font-family:{OUTLOOK_FONT};color:{OUTLOOK_HEADING};">Zusammenfassung:</strong>
{len(new_articles)} neu &middot; {len(edited_articles)} bearbeitet &middot; {total} gesamt
<td bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:16px 0 0;border-top:1px solid {OUTLOOK_BORDER};">
{_outlook_font("Zusammenfassung: ", color=OUTLOOK_HEADING, bold=True, size="1")}
{_outlook_font(f"{len(new_articles)} neu · {len(edited_articles)} bearbeitet · {total} gesamt", color=OUTLOOK_MUTED, size="1")}
</td>
</tr>"""
@@ -318,48 +343,42 @@ def create_outlook_html(
<meta charset="utf-8">
<meta name="ProgId" content="Word.Document">
<meta name="Generator" content="Microsoft Word 15">
<meta name="color-scheme" content="light only">
<meta name="supported-color-schemes" content="light">
<title>{subject}</title>
<!--[if gte mso 9]>
<xml>
<w:WordDocument>
<w:View>Print</w:View>
<w:Zoom>100</w:Zoom>
<w:DoNotOptimizeForBrowser/>
</w:WordDocument>
</xml>
<![endif]-->
<!--[if mso]>
<style type="text/css">
body, table, td, p, a, span, strong {{ font-family: Arial, sans-serif !important; }}
body, table, td, p, a, font, span, strong {{ font-family: Arial, sans-serif !important; }}
a {{ color: {OUTLOOK_LINK} !important; }}
</style>
<![endif]-->
</head>
<body style="margin:0;padding:16px;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};background:#FFFFFF;">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" style="width:600px;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};background:#FFFFFF;">
<body bgcolor="{OUTLOOK_WHITE}" style="margin:0;padding:16px;background-color:{OUTLOOK_WHITE};">
<table width="600" cellpadding="0" cellspacing="0" border="0" align="center" bgcolor="{OUTLOOK_WHITE}" style="width:600px;background-color:{OUTLOOK_WHITE};">
<tr>
<td style="padding:16px 20px;background:{OUTLOOK_HEADING};font-family:{OUTLOOK_FONT};">
<p style="margin:0;font-family:{OUTLOOK_FONT};font-size:9pt;color:#A9C6E8;text-transform:uppercase;letter-spacing:1px;">Interner Newsletter</p>
<p style="margin:6px 0 0;font-family:{OUTLOOK_FONT};font-size:18pt;font-weight:bold;color:#FFFFFF;">Thomas-Krenn Wiki Update</p>
<p style="margin:4px 0 0;font-family:{OUTLOOK_FONT};font-size:11pt;color:#D9E8F8;">{escape(ORG_NAME)}</p>
<td bgcolor="{OUTLOOK_HEADING}" style="background-color:{OUTLOOK_HEADING};padding:16px 20px;">
{_outlook_font("Interner Newsletter", color="#A9C6E8", size="1")}<br>
{_outlook_font("Thomas-Krenn Wiki Update", color=OUTLOOK_WHITE, size="5", bold=True)}<br>
{_outlook_font(ORG_NAME, color="#D9E8F8", size="2")}
</td>
</tr>
<tr>
<td style="padding:20px;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">
<p style="margin:0 0 8px;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">Liebe Kolleginnen und Kollegen,</p>
<p style="margin:0 0 12px;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">{intro}</p>
<p style="margin:0 0 16px;font-family:{OUTLOOK_FONT};font-size:9pt;color:{OUTLOOK_MUTED};">Erstellt am: {created_at}</p>
<td bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:20px;">
<p style="margin:0 0 8px;">{_outlook_font("Liebe Kolleginnen und Kollegen,")}</p>
<p style="margin:0 0 12px;">{_outlook_font(intro)}</p>
<p style="margin:0 0 16px;">{_outlook_font(f"Erstellt am: {created_at}", color=OUTLOOK_MUTED, size="1")}</p>
{category_block}
<table width="100%" cellpadding="0" cellspacing="0" border="0" style="font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">
<table width="100%" cellpadding="0" cellspacing="0" border="0" bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};">
{body_rows}
{summary_row}
<tr>
<td style="padding:20px 0 0;font-family:{OUTLOOK_FONT};font-size:11pt;">
<a href="{WIKI_HOME_URL}" style="font-family:{OUTLOOK_FONT};font-size:11pt;font-weight:bold;color:{OUTLOOK_LINK};">Zum Thomas-Krenn-Wiki &rarr;</a>
<td bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:20px 0 0;">
<a href="{WIKI_HOME_URL}"><font face="Arial" color="{OUTLOOK_LINK}" size="2"><b>Zum Thomas-Krenn-Wiki →</b></font></a>
</td>
</tr>
<tr>
<td style="padding:16px 0 0;font-family:{OUTLOOK_FONT};font-size:9pt;color:{OUTLOOK_MUTED};border-top:1px solid {OUTLOOK_BORDER};">
Automatisch erstellter interner Newsletter &middot; Nur für den Gebrauch bei {escape(ORG_NAME)}
<td bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:16px 0 0;border-top:1px solid {OUTLOOK_BORDER};">
{_outlook_font(f"Automatisch erstellter interner Newsletter · Nur für den Gebrauch bei {ORG_NAME}", color=OUTLOOK_MUTED, size="1")}
</td>
</tr>
</table>
@@ -385,20 +404,19 @@ def _outlook_meta_line(item: dict[str, Any], display: DisplayOptions) -> str:
def _outlook_article_rows(items: list[dict[str, Any]], display: DisplayOptions) -> str:
rows: list[str] = []
for idx, item in enumerate(items, 1):
title = escape(item.get("title", "Ohne Titel"))
url = escape(_article_url(item.get("title", "")))
title = item.get("title", "Ohne Titel")
url = _article_url(item.get("title", ""))
meta = _outlook_meta_line(item, display)
meta_html = ""
if meta:
meta_html = (
f'<br><span style="font-family:{OUTLOOK_FONT};font-size:9pt;color:{OUTLOOK_MUTED};">'
f"{escape(meta)}</span>"
)
meta_html = f"<br>{_outlook_font(meta, color=OUTLOOK_MUTED, size='1')}"
rows.append(
f"""<tr>
<td width="28" valign="top" style="padding:6px 8px 10px 0;border-bottom:1px solid {OUTLOOK_BORDER};font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">{idx}.</td>
<td valign="top" style="padding:6px 0 10px;border-bottom:1px solid {OUTLOOK_BORDER};font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_TEXT};">
<a href="{url}" style="font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_LINK};">{title}</a>{meta_html}
<td width="28" valign="top" bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:8px 8px 10px 0;border-bottom:1px solid {OUTLOOK_BORDER};">
{_outlook_font(f"{idx}.")}
</td>
<td valign="top" bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:8px 0 10px;border-bottom:1px solid {OUTLOOK_BORDER};">
{_outlook_link(title, url)}{meta_html}
</td>
</tr>"""
)
@@ -412,13 +430,15 @@ def _outlook_section(
empty_text: str,
) -> str:
header = f"""<tr>
<td colspan="2" style="padding:18px 0 6px;font-family:{OUTLOOK_FONT};font-size:13pt;font-weight:bold;color:{OUTLOOK_HEADING};border-bottom:2px solid {OUTLOOK_HEADING};">
{escape(heading)} ({len(items)})
<td colspan="2" bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:18px 0 8px;border-bottom:2px solid {OUTLOOK_HEADING};">
{_outlook_font(f"{heading} ({len(items)})", color=OUTLOOK_HEADING, size="3", bold=True)}
</td>
</tr>"""
if not items:
return header + f"""<tr>
<td colspan="2" style="padding:8px 0;font-family:{OUTLOOK_FONT};font-size:11pt;color:{OUTLOOK_MUTED};font-style:italic;">{escape(empty_text)}</td>
<td colspan="2" bgcolor="{OUTLOOK_WHITE}" style="background-color:{OUTLOOK_WHITE};padding:8px 0;">
{_outlook_font(empty_text, color=OUTLOOK_MUTED)}
</td>
</tr>"""
return header + _outlook_article_rows(items, display)