Update editor.js

This commit is contained in:
NOrtmann1
2026-07-19 12:48:41 +02:00
parent 97fa1de999
commit f1a32120d6
+83 -122
View File
@@ -1,172 +1,133 @@
console.log("Die JavaScript-Datei wurde erfolgreich geladen!"); //console.log("editor.js wurde erfolgreich geladen!");
function initEditor() { function initEditor() {
const form = document.getElementById("editor-form"); const form = document.getElementById("editor-form");
if (!form) { if (!form) {
console.error("Skript abgebrochen: Formular nicht gefunden!"); console.error("editor.js abgebrochen: Formular nicht gefunden!");
return; return;
} else { } else {
console.log("Formular gefunden und Editor initialisiert:", form); console.log("Formular gefunden und editor.js initialisiert:", form);
} }
const container = document.getElementById("block-container"); const container = document.getElementById("block-container");
const plusButton = document.getElementById("plus-button"); const plusButton = document.getElementById("plus-button");
const popup = document.getElementById("block-popup"); const popup = document.getElementById("block-popup");
const hiddenContentInput = document.getElementById("content");
const initialImages = []; // Fortlaufender Zähler für eindeutige Block-Indizes. Wird nie wiederverwendet
// (auch nicht nach dem Löschen eines Blocks), damit sich neue und übrig
// gebliebene Blöcke beim finalen Submit nie einen Namen teilen.
let blockIndex = container.querySelectorAll(".editor-block").length;
// Ohne JS sind Textblock-/Bild-Button im Pop-up immer sichtbar und ganz normale
// Submit-Buttons. Erst mit JS blenden wir das Pop-up standardmäßig aus und
// steuern die Sichtbarkeit über den Plus-Button.
popup.classList.add("hidden");
// Pop-up umschalten bei Klick auf das Plus
plusButton.addEventListener("click", () => { plusButton.addEventListener("click", () => {
popup.classList.toggle("hidden"); popup.classList.toggle("hidden");
}); });
// Klick auf eine Block-Option im Pop-up // Klick auf "Textblock" / "Bild einfügen": lokal im DOM anlegen statt zum
popup.querySelectorAll("button").forEach(btn => { // Server zu submitten (entlastet den Server, kein Page-Reload nötig).
btn.addEventListener("click", function() { popup.querySelectorAll('[name="editor_action"]').forEach(btn => {
btn.addEventListener("click", function (e) {
e.preventDefault();
const type = this.getAttribute("data-type"); const type = this.getAttribute("data-type");
addBlockElement(type, ""); addBlockElement(type, "");
popup.classList.add("hidden"); popup.classList.add("hidden");
}); });
}); });
// Erstellt ein visuelles HTML-Element im Editor // Bereits vom Server gerenderte Blöcke (z.B. beim Bearbeiten eines bestehenden
// Artikels oder nach einem Validierungsfehler) ebenfalls mit JS-Verhalten ausstatten.
container.querySelectorAll(".editor-block").forEach(blockDiv => {
bindDeleteButton(blockDiv);
bindImageInput(blockDiv);
});
// Erstellt einen neuen Block inkl. echter, benannter Formularfelder
// (blocks[i][type], blocks[i][text] bzw. blocks[i][image]). Diese Felder werden
// beim finalen Submit ganz normal vom Browser als multipart/form-data verschickt
// es ist kein manuelles Zusammenbauen von JSON beim Absenden mehr nötig.
function addBlockElement(type, value = "") { function addBlockElement(type, value = "") {
const index = blockIndex++;
const blockDiv = document.createElement("div"); const blockDiv = document.createElement("div");
blockDiv.classList.add("editor-block"); blockDiv.classList.add("editor-block", "article-editor-scope");
blockDiv.setAttribute("data-type", type); blockDiv.setAttribute("data-index", String(index));
// Wenn es ein existierendes Server-Bild beim Laden ist, Pfad im globalen Array sichern const typeInput = document.createElement("input");
if (type === "image" && value && typeof value === 'string' && value.startsWith('uploads/')) { typeInput.type = "hidden";
initialImages.push(value); typeInput.name = `blocks[${index}][type]`;
blockDiv.setAttribute("data-value", value); typeInput.value = type;
} blockDiv.appendChild(typeInput);
// Löschen-Button
const deleteBtn = document.createElement("button");
deleteBtn.type = "button";
deleteBtn.innerHTML = "✕";
deleteBtn.classList.add("delete-block-btn");
deleteBtn.addEventListener("click", () => {
// ANPASSUNG 2B: Logik hier komplett geleert. Das '✕' entfernt den Block jetzt nur noch sicher aus dem HTML.
blockDiv.remove();
});
blockDiv.appendChild(deleteBtn);
if (type === "text") { if (type === "text") {
const textarea = document.createElement("textarea"); const textarea = document.createElement("textarea");
textarea.name = `blocks[${index}][text]`;
textarea.placeholder = "Schreibe deinen Textblock..."; textarea.placeholder = "Schreibe deinen Textblock...";
textarea.value = value; textarea.value = value;
blockDiv.appendChild(textarea); blockDiv.appendChild(textarea);
} else if (type === "image") { } else if (type === "image") {
const fileInput = document.createElement("input"); const fileInput = document.createElement("input");
fileInput.type = "file"; fileInput.type = "file";
fileInput.name = `blocks[${index}][image]`;
fileInput.accept = "image/*"; fileInput.accept = "image/*";
const imgPreview = document.createElement("img");
imgPreview.style.maxWidth = "200px";
imgPreview.style.display = "block";
imgPreview.style.marginTop = "10px";
if (value && typeof value === 'string') {
if (value.startsWith('uploads/') || value.startsWith('data:image/')) {
imgPreview.src = value;
blockDiv.setAttribute("data-value", value);
}
}
fileInput.addEventListener("change", function() {
if (this.files && this.files[0]) {
const reader = new FileReader();
reader.onload = function(e) {
imgPreview.src = e.target.result;
blockDiv.setAttribute("data-value", e.target.result);
}
reader.readAsDataURL(this.files[0]);
}
});
blockDiv.appendChild(fileInput); blockDiv.appendChild(fileInput);
blockDiv.appendChild(imgPreview);
} }
const deleteBtn = document.createElement("button");
deleteBtn.type = "submit";
deleteBtn.name = "editor_action";
deleteBtn.value = `delete_block:${index}`;
deleteBtn.classList.add("delete-block-btn");
deleteBtn.innerHTML = "✕";
blockDiv.appendChild(deleteBtn);
container.appendChild(blockDiv); container.appendChild(blockDiv);
bindDeleteButton(blockDiv);
bindImageInput(blockDiv);
} }
// beim Abschicken verbleibende Blöcke auslesen UND gelöschte Bilder ermitteln // Löschen-Button eines Blocks lokal abfangen: entfernt den Block nur aus dem DOM,
form.addEventListener("submit", function(e) { // statt das Formular zum Server zu senden.
const blocks = []; function bindDeleteButton(blockDiv) {
const currentImages = []; const btn = blockDiv.querySelector(".delete-block-btn");
if (!btn) return;
// alle aktuell im Formular verbliebenen Blöcke scannen btn.addEventListener("click", (e) => {
container.querySelectorAll(".editor-block").forEach(blockDiv => { e.preventDefault();
const type = blockDiv.getAttribute("data-type"); blockDiv.remove();
let value = "";
if (type === "text") {
value = blockDiv.querySelector("textarea").value;
} else if (type === "image") {
const imgTag = blockDiv.querySelector("img");
if (imgTag) {
const srcValue = imgTag.getAttribute("src") || "";
// Wenn es ein neues Bild ist, nutzen wir das data-value (Base64)
if (srcValue.startsWith('data:image/')) {
value = blockDiv.getAttribute("data-value") || "";
} else {
value = srcValue;
}
}
// Pfade sammeln, die der Nutzer NICHT gelöscht hat (für den Abgleich)
if (value && value.startsWith('uploads/')) {
currentImages.push(value);
}
}
blocks.push({ type: type, value: value });
}); });
}
// das reguläre unsichtbare Content-Feld befüllen // Zeigt bei Auswahl einer Bilddatei sofort eine Vorschau an. Rein optisch
hiddenContentInput.value = JSON.stringify(blocks); // der eigentliche Datei-Upload läuft nativ über das <input type="file">.
function bindImageInput(blockDiv) {
const fileInput = blockDiv.querySelector('input[type="file"]');
if (!fileInput) return;
// Differenz berechnen: Welche Bilder aus 'initialImages' fehlen in 'currentImages' ? let imgPreview = blockDiv.querySelector(".block-image-preview");
const deletedImages = initialImages.filter(img => !currentImages.includes(img)); if (!imgPreview) {
imgPreview = document.createElement("img");
// das 'deleted_images'-Feld dynamisch befüllen und an den Controller senden imgPreview.classList.add("block-image-preview");
let deletedInput = document.getElementById("deleted-images"); imgPreview.style.maxWidth = "200px";
if (!deletedInput) { imgPreview.style.display = "none";
deletedInput = document.createElement("input"); imgPreview.style.marginTop = "10px";
deletedInput.type = "hidden"; blockDiv.insertBefore(imgPreview, fileInput.nextSibling);
deletedInput.id = "deleted-images";
deletedInput.name = "deleted_images";
form.appendChild(deletedInput);
} }
deletedInput.value = JSON.stringify(deletedImages);
});
// Existierende Blöcke laden (stellt alte Daten aus der Session wieder her) fileInput.addEventListener("change", function () {
try { if (this.files && this.files[0]) {
const initialBlocks = JSON.parse(hiddenContentInput.value.trim()); const reader = new FileReader();
if (Array.isArray(initialBlocks)) { reader.onload = (e) => {
initialBlocks.forEach(b => { imgPreview.src = e.target.result;
if (b.type === "image" && b.value && typeof b.value === 'string' && !b.value.startsWith('data:image/')) { imgPreview.style.display = "block";
let cleanPath = b.value.trim().replace(/\\\//g, '/'); // Verwandelt \/ in / };
reader.readAsDataURL(this.files[0]);
initialImages.push(cleanPath); }
addBlockElement(b.type, cleanPath); });
} else {
addBlockElement(b.type, b.value);
}
});
console.log("Erfolgreich registrierte Start-Bilder:", initialImages);
}
} catch(e) {
if (hiddenContentInput.value.trim() !== "") {
addBlockElement("text", hiddenContentInput.value);
}
} }
} }
@@ -176,4 +137,4 @@ if (document.readyState === "loading") {
} else { } else {
// Falls das DOM schon fertig geladen ist, führen wir es direkt aus // Falls das DOM schon fertig geladen ist, führen wir es direkt aus
initEditor(); initEditor();
} }