Files
TK-Wiki-Newsletter/app/static/js/newsletter.js
smueller df4424fdf9 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.
2026-07-03 13:17:11 +02:00

358 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function setCopyStatus(message, isError) {
const status = document.getElementById("copy-status");
if (!status) return;
status.textContent = message;
status.className = isError ? "copy-status error" : "copy-status success";
}
function getNewsletterHtml() {
const htmlSource = document.getElementById("newsletter-html-source");
const htmlDisplay = document.getElementById("newsletter-html-display");
return htmlSource?.value.trim() || htmlDisplay?.value.trim() || "";
}
function getNewsletterOutlookHtml() {
const outlookSource = document.getElementById("newsletter-outlook-source");
const outlook = outlookSource ? outlookSource.value.trim() : "";
if (outlook) return outlook;
return getNewsletterHtml();
}
function getNewsletterPlain() {
const plainSource = document.getElementById("newsletter-plain-source");
return plainSource ? plainSource.value : "";
}
function byteLength(text) {
return new TextEncoder().encode(text).length;
}
function wrapCfHtml(html) {
const fragmentStart = "<!--StartFragment-->";
const fragmentEnd = "<!--EndFragment-->";
const bodyMatch = html.match(/<body[^>]*>([\s\S]*)<\/body>/i);
const inner = bodyMatch ? bodyMatch[1].trim() : html;
const documentHtml = `<html><body>${fragmentStart}${inner}${fragmentEnd}</body></html>`;
const pad = (value) => String(value).padStart(10, "0");
const headerTemplate =
"Version:0.9\r\n" +
"StartHTML:0000000000\r\n" +
"EndHTML:0000000000\r\n" +
"StartFragment:0000000000\r\n" +
"EndFragment:0000000000\r\n";
const startHtml = byteLength(headerTemplate);
const endHtml = startHtml + byteLength(documentHtml);
const startFragment = startHtml + byteLength(documentHtml.slice(0, documentHtml.indexOf(fragmentStart) + fragmentStart.length));
const endFragment = startHtml + byteLength(documentHtml.slice(0, documentHtml.indexOf(fragmentEnd)));
return (
"Version:0.9\r\n" +
`StartHTML:${pad(startHtml)}\r\n` +
`EndHTML:${pad(endHtml)}\r\n` +
`StartFragment:${pad(startFragment)}\r\n` +
`EndFragment:${pad(endFragment)}\r\n` +
documentHtml
);
}
function selectTextarea(textarea) {
if (!textarea || !textarea.value) return false;
textarea.scrollIntoView({ behavior: "smooth", block: "center" });
textarea.focus({ preventScroll: true });
textarea.select();
textarea.setSelectionRange(0, textarea.value.length);
return true;
}
function copyFromTextarea(textarea) {
if (!textarea || !textarea.value) return false;
const wasReadonly = textarea.hasAttribute("readonly");
if (wasReadonly) textarea.removeAttribute("readonly");
selectTextarea(textarea);
let ok = false;
try {
ok = document.execCommand("copy");
} catch (err) {
console.warn("execCommand copy from textarea failed.", err);
}
if (wasReadonly) textarea.setAttribute("readonly", "");
if (!ok) {
selectTextarea(textarea);
}
return ok;
}
function copyTextWithFallback(text) {
if (!text) return false;
const pageTextareas = [
document.getElementById("newsletter-html-display"),
document.getElementById("newsletter-html-source"),
document.getElementById("newsletter-outlook-source"),
document.getElementById("newsletter-plain-source"),
];
for (const textarea of pageTextareas) {
if (textarea && textarea.value === text && copyFromTextarea(textarea)) {
return true;
}
}
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.setAttribute("aria-hidden", "true");
textarea.style.cssText = "position:fixed;top:0;left:0;width:2px;height:2px;opacity:0;border:none;padding:0;";
document.body.appendChild(textarea);
let ok = false;
try {
selectTextarea(textarea);
ok = document.execCommand("copy");
} catch (err) {
console.warn("execCommand copy from temporary textarea failed.", err);
} finally {
document.body.removeChild(textarea);
}
return ok;
}
let copyDialogKeyHandler = null;
function closeCopyDialog() {
const overlay = document.getElementById("copy-dialog-overlay");
if (overlay) overlay.remove();
if (copyDialogKeyHandler) {
document.removeEventListener("keydown", copyDialogKeyHandler);
copyDialogKeyHandler = null;
}
}
function showCopyDialog(text, title) {
closeCopyDialog();
const overlay = document.createElement("div");
overlay.id = "copy-dialog-overlay";
overlay.className = "copy-dialog-overlay";
overlay.innerHTML = `
<div class="copy-dialog" role="dialog" aria-modal="true" aria-labelledby="copy-dialog-title">
<h4 id="copy-dialog-title">${title}</h4>
<p class="copy-dialog-hint">Der Text ist markiert. Jetzt <strong>Strg+C</strong> drücken (Mac: <strong>Cmd+C</strong>).</p>
<textarea class="copy-dialog-textarea" spellcheck="false"></textarea>
<div class="copy-dialog-actions">
<button type="button" class="btn-secondary" id="copy-dialog-select-btn">Alles markieren</button>
<button type="button" class="btn-secondary" id="copy-dialog-close-btn">Schließen</button>
</div>
</div>
`;
document.body.appendChild(overlay);
const textarea = overlay.querySelector(".copy-dialog-textarea");
textarea.value = text;
selectTextarea(textarea);
let copied = false;
try {
copied = document.execCommand("copy");
} catch (err) {
console.warn("execCommand copy in dialog failed.", err);
}
if (copied) {
setCopyStatus("HTML-Quelltext kopiert.");
} else {
setCopyStatus("Bitte im Fenster Strg+C drücken.", true);
}
overlay.querySelector("#copy-dialog-close-btn").addEventListener("click", closeCopyDialog);
overlay.querySelector("#copy-dialog-select-btn").addEventListener("click", () => selectTextarea(textarea));
overlay.addEventListener("click", (event) => {
if (event.target === overlay) closeCopyDialog();
});
copyDialogKeyHandler = (event) => {
if (event.key === "Escape") closeCopyDialog();
};
document.addEventListener("keydown", copyDialogKeyHandler);
}
function copyViaIframe(html) {
const iframe = document.createElement("iframe");
iframe.setAttribute("aria-hidden", "true");
iframe.style.cssText = "position:fixed;width:700px;height:500px;left:-9999px;top:0;border:none;";
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
doc.open();
doc.write(html);
doc.close();
const range = doc.createRange();
range.selectNodeContents(doc.body);
const selection = iframe.contentWindow.getSelection();
selection.removeAllRanges();
selection.addRange(range);
let ok = false;
try {
ok = doc.execCommand("copy");
} catch (err) {
console.warn("execCommand copy via iframe failed.", err);
} finally {
selection.removeAllRanges();
document.body.removeChild(iframe);
}
return ok;
}
function copyHtmlWithFallback(html) {
if (copyViaIframe(html)) return true;
const container = document.createElement("div");
container.innerHTML = html;
container.contentEditable = "true";
container.setAttribute("aria-hidden", "true");
container.style.cssText =
"position:fixed;left:0;top:0;width:700px;max-height:1px;overflow:hidden;opacity:0;pointer-events:none;background:#FFFFFF;";
document.body.appendChild(container);
const range = document.createRange();
range.selectNodeContents(container);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
let ok = false;
try {
ok = document.execCommand("copy");
} catch (err) {
console.warn("execCommand HTML copy failed.", err);
} finally {
selection.removeAllRanges();
document.body.removeChild(container);
}
return ok;
}
async function copyForOutlook() {
const html = getNewsletterOutlookHtml();
const plain = getNewsletterPlain();
if (!html) {
setCopyStatus("Bitte zuerst einen Newsletter generieren.", true);
return;
}
if (copyHtmlWithFallback(html)) {
setCopyStatus("Newsletter kopiert. In Outlook: Strg+V, dann ggf. Rechtsklick → „Format beibehalten“.");
return;
}
const cfHtml = wrapCfHtml(html);
if (navigator.clipboard && window.ClipboardItem && window.isSecureContext) {
try {
await navigator.clipboard.write([
new ClipboardItem({
"text/html": new Blob([cfHtml], { type: "text/html" }),
"text/plain": new Blob([plain], { type: "text/plain" }),
}),
]);
setCopyStatus("Newsletter kopiert. In Outlook: Strg+V, dann ggf. Rechtsklick → „Format beibehalten“.");
return;
} catch (err) {
console.warn("Clipboard API HTML copy failed.", err);
}
}
setCopyStatus("Bitte „Im Browser öffnen“ nutzen und dort Strg+A → Strg+C.", true);
}
function copyHtmlSource() {
const html = getNewsletterHtml();
if (!html) {
setCopyStatus("Bitte zuerst einen Newsletter generieren.", true);
return;
}
showCopyDialog(html, "HTML-Quelltext kopieren");
}
function downloadHtml() {
const html = getNewsletterOutlookHtml();
if (!html) {
setCopyStatus("Bitte zuerst einen Newsletter generieren.", true);
return;
}
downloadFile(html, `wiki-newsletter-${new Date().toISOString().slice(0, 10)}.html`, "text/html;charset=utf-8");
setCopyStatus("Outlook-HTML heruntergeladen.");
}
function downloadHtmlSource() {
const html = getNewsletterHtml();
if (!html) {
setCopyStatus("Bitte zuerst einen Newsletter generieren.", true);
return;
}
downloadFile(html, `wiki-newsletter-source-${new Date().toISOString().slice(0, 10)}.html`, "text/html;charset=utf-8");
setCopyStatus("HTML-Quelltext heruntergeladen.");
}
function downloadFile(content, filename, mimeType) {
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
function openOutlookInBrowser() {
const html = getNewsletterOutlookHtml();
if (!html) {
setCopyStatus("Bitte zuerst einen Newsletter generieren.", true);
return;
}
const popup = window.open("", "_blank");
if (!popup) {
setCopyStatus("Pop-up blockiert. Bitte Pop-ups erlauben oder „HTML herunterladen“ nutzen.", true);
return;
}
popup.document.open();
popup.document.write(html);
popup.document.close();
popup.document.title = "Wiki-Newsletter zum Kopieren";
setCopyStatus("Newsletter im Browser geöffnet: Strg+A → Strg+C, dann in Outlook einfügen.");
}
document.addEventListener("DOMContentLoaded", () => {
const outlookSource = document.getElementById("newsletter-outlook-source");
const htmlSource = document.getElementById("newsletter-html-source");
const frame = document.getElementById("newsletter-preview-frame");
const previewHtml = outlookSource?.value.trim() || htmlSource?.value.trim() || "";
if (frame && previewHtml) {
frame.srcdoc = previewHtml;
}
const htmlDisplay = document.getElementById("newsletter-html-display");
htmlDisplay?.addEventListener("focus", () => selectTextarea(htmlDisplay));
htmlDisplay?.addEventListener("click", () => selectTextarea(htmlDisplay));
document.getElementById("copy-outlook-btn")?.addEventListener("click", copyForOutlook);
document.getElementById("open-outlook-btn")?.addEventListener("click", openOutlookInBrowser);
document.getElementById("copy-html-source-btn")?.addEventListener("click", copyHtmlSource);
document.getElementById("copy-html-source-btn-2")?.addEventListener("click", copyHtmlSource);
document.getElementById("download-html-btn")?.addEventListener("click", downloadHtml);
document.getElementById("download-html-source-btn")?.addEventListener("click", downloadHtmlSource);
});