- Add a new preview banner to indicate the live newsletter display. - Introduce `copyFromPreviewFrame` function to enable copying the newsletter preview directly. - Update copy status messages for clarity and improved user guidance. - Revise button labels in the dashboard for better usability. - Modify CSS for the newsletter preview frame and add styles for the new preview banner.
390 lines
13 KiB
JavaScript
390 lines
13 KiB
JavaScript
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 copyFromPreviewFrame() {
|
||
const frame = document.getElementById("newsletter-preview-frame");
|
||
const doc = frame?.contentDocument;
|
||
const win = frame?.contentWindow;
|
||
if (!doc?.body || !win || !doc.body.innerHTML.trim()) {
|
||
return false;
|
||
}
|
||
|
||
const range = doc.createRange();
|
||
range.selectNodeContents(doc.body);
|
||
const selection = win.getSelection();
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
|
||
let ok = false;
|
||
try {
|
||
ok = doc.execCommand("copy");
|
||
} catch (err) {
|
||
console.warn("Copy from preview frame failed.", err);
|
||
} finally {
|
||
selection.removeAllRanges();
|
||
}
|
||
return ok;
|
||
}
|
||
|
||
function loadPreviewFrame() {
|
||
const frame = document.getElementById("newsletter-preview-frame");
|
||
const html = getNewsletterOutlookHtml();
|
||
if (frame && html) {
|
||
frame.srcdoc = html;
|
||
}
|
||
}
|
||
|
||
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 (copyFromPreviewFrame()) {
|
||
setCopyStatus("Vorschau kopiert – in Outlook mit Strg+V einfügen (Rechtsklick → „Format beibehalten“).");
|
||
return;
|
||
}
|
||
|
||
if (copyViaIframe(html)) {
|
||
setCopyStatus("Newsletter kopiert – in Outlook mit Strg+V einfügen (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 mit Strg+V einfügen.");
|
||
return;
|
||
} catch (err) {
|
||
console.warn("Clipboard API HTML copy failed.", err);
|
||
}
|
||
}
|
||
|
||
setCopyStatus("Kopieren fehlgeschlagen. Bitte „Im Browser öffnen“ nutzen.", 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("Im neuen Tab: Alles markieren (Strg+A) → kopieren (Strg+C) → in Outlook einfügen.");
|
||
}
|
||
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
loadPreviewFrame();
|
||
|
||
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);
|
||
});
|