Files
stiftung-management-system/app/static/stiftung/js/briefvorlage_editor.js
SysAdmin Agent 709903e627 Baseline für Vision 2026: Veranstaltungsmodul + ausstehende Änderungen
Alle bestehenden, nicht commiteten Änderungen als Ausgangsbasis für den
vision-2026 Branch übernommen (Veranstaltungsmodul, Serienbrief, etc.).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 08:51:48 +00:00

251 lines
11 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.

/**
* Briefvorlage-Editor: Minimal-WYSIWYG + Vorschau-Panel + Vorlagen-Loader
* für Django Admin keine externen Abhängigkeiten.
*/
(function () {
"use strict";
// Warte auf DOM
document.addEventListener("DOMContentLoaded", function () {
var textareas = document.querySelectorAll("textarea.briefvorlage-textarea");
textareas.forEach(function (textarea) {
initEditor(textarea);
});
});
function initEditor(textarea) {
var wrapper = document.createElement("div");
wrapper.style.cssText = "border:1px solid #ccc;border-radius:4px;overflow:hidden;margin-top:4px;";
// ---- Toolbar ----
var toolbar = document.createElement("div");
toolbar.style.cssText = "background:#f5f5f5;border-bottom:1px solid #ccc;padding:5px 8px;display:flex;flex-wrap:wrap;gap:4px;align-items:center;";
var buttons = [
{ label: "B", cmd: "bold", title: "Fett (Strg+B)", style: "font-weight:bold;" },
{ label: "I", cmd: "italic", title: "Kursiv (Strg+I)", style: "font-style:italic;" },
{ label: "U", cmd: "underline", title: "Unterstrichen (Strg+U)", style: "text-decoration:underline;" },
{ label: "¶", cmd: "insertParagraph", title: "Absatz einfügen" },
{ label: "• Liste", cmd: "insertUnorderedList", title: "Aufzählung" },
{ label: "1. Liste", cmd: "insertOrderedList", title: "Nummerierte Liste" },
];
buttons.forEach(function (b) {
var btn = document.createElement("button");
btn.type = "button";
btn.title = b.title;
btn.innerHTML = b.label;
btn.style.cssText = "padding:3px 8px;cursor:pointer;border:1px solid #ccc;border-radius:3px;background:#fff;font-size:13px;" + (b.style || "");
btn.addEventListener("click", function (e) {
e.preventDefault();
editor.focus();
document.execCommand(b.cmd, false, null);
syncToTextarea();
});
toolbar.appendChild(btn);
});
// Trennlinie
var sep = document.createElement("span");
sep.style.cssText = "border-left:1px solid #ccc;height:20px;margin:0 4px;";
toolbar.appendChild(sep);
// Tab-Buttons: Editor / HTML / Vorschau
var tabEditor = createTabBtn("Editor", true);
var tabHtml = createTabBtn("HTML", false);
var tabVorschau = createTabBtn("Vorschau", false);
toolbar.appendChild(tabEditor);
toolbar.appendChild(tabHtml);
toolbar.appendChild(tabVorschau);
// Vorlage-Loader (nur wenn BriefVorlage-API verfügbar)
var sep2 = document.createElement("span");
sep2.style.cssText = "border-left:1px solid #ccc;height:20px;margin:0 4px;";
toolbar.appendChild(sep2);
var vorlagenSelect = document.createElement("select");
vorlagenSelect.style.cssText = "font-size:12px;padding:2px 6px;border:1px solid #ccc;border-radius:3px;max-width:200px;";
var defaultOption = document.createElement("option");
defaultOption.value = "";
defaultOption.textContent = " Vorlage laden ";
vorlagenSelect.appendChild(defaultOption);
toolbar.appendChild(vorlagenSelect);
// Vorlagen asynchron laden
loadVorlagen(vorlagenSelect);
var ladeBtn = document.createElement("button");
ladeBtn.type = "button";
ladeBtn.textContent = "Laden";
ladeBtn.title = "Ausgewählte Vorlage in den Editor laden";
ladeBtn.style.cssText = "padding:3px 8px;cursor:pointer;border:1px solid #0d6efd;border-radius:3px;background:#0d6efd;color:#fff;font-size:12px;";
ladeBtn.addEventListener("click", function (e) {
e.preventDefault();
var val = vorlagenSelect.value;
if (!val) return;
var opt = vorlagenSelect.querySelector("option[value='" + val + "']");
if (!opt) return;
var html = opt.dataset.briefvorlage || "";
var betreff = opt.dataset.betreff || "";
if (confirm("Vorlage \"" + opt.textContent + "\" laden?\nDer aktuelle Brieftext wird überschrieben.")) {
editor.innerHTML = html;
textarea.value = html;
// Betreff-Feld befüllen falls vorhanden und nicht leer
if (betreff) {
var betreffField = document.getElementById("id_betreff");
if (betreffField && !betreffField.value) {
betreffField.value = betreff;
}
}
updatePreview();
}
});
toolbar.appendChild(ladeBtn);
// ---- Editor-Div (WYSIWYG) ----
var editor = document.createElement("div");
editor.contentEditable = "true";
editor.style.cssText = "min-height:300px;padding:12px;font-family:Times New Roman,serif;font-size:11pt;line-height:1.4;outline:none;background:#fff;";
editor.innerHTML = textarea.value;
editor.addEventListener("input", syncToTextarea);
editor.addEventListener("keyup", syncToTextarea);
// ---- HTML-Textarea (Quelltext) ----
textarea.style.cssText += "display:none;width:100%;box-sizing:border-box;border:none;padding:12px;font-family:monospace;font-size:13px;";
textarea.addEventListener("input", function () {
editor.innerHTML = textarea.value;
updatePreview();
});
// ---- Vorschau-Panel ----
var preview = document.createElement("div");
preview.style.cssText = "display:none;min-height:300px;padding:12px;background:#fff;font-family:'Times New Roman',serif;font-size:11pt;line-height:1.4;";
// Tab-Logik
function showTab(which) {
editor.style.display = "none";
textarea.style.display = "none";
preview.style.display = "none";
tabEditor.style.background = "#f5f5f5";
tabHtml.style.background = "#f5f5f5";
tabVorschau.style.background = "#f5f5f5";
if (which === "editor") {
editor.style.display = "block";
tabEditor.style.background = "#fff";
tabEditor.style.fontWeight = "bold";
} else if (which === "html") {
textarea.style.display = "block";
tabHtml.style.background = "#fff";
tabHtml.style.fontWeight = "bold";
} else {
preview.style.display = "block";
tabVorschau.style.background = "#fff";
tabVorschau.style.fontWeight = "bold";
updatePreview();
}
}
tabEditor.addEventListener("click", function (e) { e.preventDefault(); showTab("editor"); });
tabHtml.addEventListener("click", function (e) {
e.preventDefault();
syncToTextarea();
showTab("html");
});
tabVorschau.addEventListener("click", function (e) { e.preventDefault(); showTab("vorschau"); });
// Zusammenbauen
wrapper.appendChild(toolbar);
wrapper.appendChild(editor);
wrapper.appendChild(preview);
// Textarea hinter Editor platzieren
textarea.parentNode.insertBefore(wrapper, textarea);
wrapper.appendChild(textarea);
// Initial: Editor-Tab aktiv
showTab("editor");
// ---- Hilfsfunktionen ----
function syncToTextarea() {
textarea.value = editor.innerHTML;
}
function updatePreview() {
// Platzhalter durch Beispielwerte ersetzen für Vorschau
var html = textarea.value;
var replacements = {
"{{ anrede }}": "Frau",
"{{ vorname }}": "Maria",
"{{ nachname }}": "Mustermann",
"{{ strasse }}": "Musterstraße 12",
"{{ plz }}": "46499",
"{{ ort }}": "Hamminkeln",
"{{ datum }}": "Freitag, 17. April 2026",
"{{ uhrzeit }}": "19:00 Uhr",
"{{ veranstaltungsort }}": "Marienthaler Gasthof",
"{{ gasthaus_adresse }}": "Pastor-Winkelmann-Str. 2, 46499 Hamminkeln",
};
for (var key in replacements) {
html = html.split(key).join(replacements[key]);
}
preview.innerHTML = html || "<em style='color:#999;'>Kein Brieftext eingegeben.</em>";
}
function createTabBtn(label, active) {
var btn = document.createElement("button");
btn.type = "button";
btn.textContent = label;
btn.style.cssText = "padding:3px 10px;cursor:pointer;border:1px solid #ccc;border-radius:3px;font-size:12px;background:" + (active ? "#fff" : "#f5f5f5") + ";";
if (active) btn.style.fontWeight = "bold";
return btn;
}
}
function loadVorlagen(selectEl) {
// Lese Vorlagen über einfachen Admin-API-Aufruf
fetch("/admin/stiftung/briefvorlage/?format=json", {
headers: { "X-Requested-With": "XMLHttpRequest" }
})
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
if (!data || !data.results) return;
data.results.forEach(function (v) {
var opt = document.createElement("option");
opt.value = v.id || v.pk;
opt.textContent = v.name || v.fields && v.fields.name;
opt.dataset.briefvorlage = v.briefvorlage || v.fields && v.fields.briefvorlage || "";
opt.dataset.betreff = v.betreff || v.fields && v.fields.betreff || "";
selectEl.appendChild(opt);
});
})
.catch(function () {
// Kein API-Endpunkt Vorlage-Loader deaktivieren
});
// Alternativ: REST-API
fetch("/api/v1/briefvorlagen/", {
headers: { "X-Requested-With": "XMLHttpRequest" }
})
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (data) {
if (!data) return;
var results = Array.isArray(data) ? data : data.results;
if (!results) return;
// Bereits vorhandene Optionen nicht doppeln
var existing = Array.from(selectEl.options).map(function (o) { return o.value; });
results.forEach(function (v) {
var id = String(v.id || v.pk || "");
if (existing.includes(id)) return;
var opt = document.createElement("option");
opt.value = id;
opt.textContent = v.name;
opt.dataset.briefvorlage = v.briefvorlage || "";
opt.dataset.betreff = v.betreff || "";
selectEl.appendChild(opt);
});
})
.catch(function () { /* kein REST-Endpunkt */ });
}
})();