From f1a32120d696459b10548855fa1fb13771ad9fe9 Mon Sep 17 00:00:00 2001 From: NOrtmann1 <145041949+NOrtmann1@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:48:41 +0200 Subject: [PATCH] Update editor.js --- js/editor.js | 205 +++++++++++++++++++++------------------------------ 1 file changed, 83 insertions(+), 122 deletions(-) diff --git a/js/editor.js b/js/editor.js index e666f40..01ee67f 100644 --- a/js/editor.js +++ b/js/editor.js @@ -1,172 +1,133 @@ -console.log("Die JavaScript-Datei wurde erfolgreich geladen!"); +//console.log("editor.js wurde erfolgreich geladen!"); function initEditor() { const form = document.getElementById("editor-form"); if (!form) { - console.error("Skript abgebrochen: Formular nicht gefunden!"); + console.error("editor.js abgebrochen: Formular nicht gefunden!"); return; } 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 plusButton = document.getElementById("plus-button"); 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", () => { popup.classList.toggle("hidden"); }); - // Klick auf eine Block-Option im Pop-up - popup.querySelectorAll("button").forEach(btn => { - btn.addEventListener("click", function() { + // Klick auf "Textblock" / "Bild einfügen": lokal im DOM anlegen statt zum + // Server zu submitten (entlastet den Server, kein Page-Reload nötig). + popup.querySelectorAll('[name="editor_action"]').forEach(btn => { + btn.addEventListener("click", function (e) { + e.preventDefault(); const type = this.getAttribute("data-type"); addBlockElement(type, ""); 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 = "") { + const index = blockIndex++; + const blockDiv = document.createElement("div"); - blockDiv.classList.add("editor-block"); - blockDiv.setAttribute("data-type", type); + blockDiv.classList.add("editor-block", "article-editor-scope"); + blockDiv.setAttribute("data-index", String(index)); - // Wenn es ein existierendes Server-Bild beim Laden ist, Pfad im globalen Array sichern - if (type === "image" && value && typeof value === 'string' && value.startsWith('uploads/')) { - initialImages.push(value); - blockDiv.setAttribute("data-value", value); - } - - // 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); + const typeInput = document.createElement("input"); + typeInput.type = "hidden"; + typeInput.name = `blocks[${index}][type]`; + typeInput.value = type; + blockDiv.appendChild(typeInput); if (type === "text") { const textarea = document.createElement("textarea"); + textarea.name = `blocks[${index}][text]`; textarea.placeholder = "Schreibe deinen Textblock..."; textarea.value = value; blockDiv.appendChild(textarea); } else if (type === "image") { const fileInput = document.createElement("input"); fileInput.type = "file"; + fileInput.name = `blocks[${index}][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(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); + + bindDeleteButton(blockDiv); + bindImageInput(blockDiv); } - // beim Abschicken verbleibende Blöcke auslesen UND gelöschte Bilder ermitteln - form.addEventListener("submit", function(e) { - const blocks = []; - const currentImages = []; - - // alle aktuell im Formular verbliebenen Blöcke scannen - container.querySelectorAll(".editor-block").forEach(blockDiv => { - const type = blockDiv.getAttribute("data-type"); - 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 }); + // Löschen-Button eines Blocks lokal abfangen: entfernt den Block nur aus dem DOM, + // statt das Formular zum Server zu senden. + function bindDeleteButton(blockDiv) { + const btn = blockDiv.querySelector(".delete-block-btn"); + if (!btn) return; + btn.addEventListener("click", (e) => { + e.preventDefault(); + blockDiv.remove(); }); + } - // das reguläre unsichtbare Content-Feld befüllen - hiddenContentInput.value = JSON.stringify(blocks); + // Zeigt bei Auswahl einer Bilddatei sofort eine Vorschau an. Rein optisch – + // der eigentliche Datei-Upload läuft nativ über das . + function bindImageInput(blockDiv) { + const fileInput = blockDiv.querySelector('input[type="file"]'); + if (!fileInput) return; - // Differenz berechnen: Welche Bilder aus 'initialImages' fehlen in 'currentImages' ? - const deletedImages = initialImages.filter(img => !currentImages.includes(img)); - - // das 'deleted_images'-Feld dynamisch befüllen und an den Controller senden - let deletedInput = document.getElementById("deleted-images"); - if (!deletedInput) { - deletedInput = document.createElement("input"); - deletedInput.type = "hidden"; - deletedInput.id = "deleted-images"; - deletedInput.name = "deleted_images"; - form.appendChild(deletedInput); + let imgPreview = blockDiv.querySelector(".block-image-preview"); + if (!imgPreview) { + imgPreview = document.createElement("img"); + imgPreview.classList.add("block-image-preview"); + imgPreview.style.maxWidth = "200px"; + imgPreview.style.display = "none"; + imgPreview.style.marginTop = "10px"; + blockDiv.insertBefore(imgPreview, fileInput.nextSibling); } - deletedInput.value = JSON.stringify(deletedImages); - }); - // Existierende Blöcke laden (stellt alte Daten aus der Session wieder her) - try { - const initialBlocks = JSON.parse(hiddenContentInput.value.trim()); - if (Array.isArray(initialBlocks)) { - initialBlocks.forEach(b => { - if (b.type === "image" && b.value && typeof b.value === 'string' && !b.value.startsWith('data:image/')) { - let cleanPath = b.value.trim().replace(/\\\//g, '/'); // Verwandelt \/ in / - - 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); - } + fileInput.addEventListener("change", function () { + if (this.files && this.files[0]) { + const reader = new FileReader(); + reader.onload = (e) => { + imgPreview.src = e.target.result; + imgPreview.style.display = "block"; + }; + reader.readAsDataURL(this.files[0]); + } + }); } } @@ -176,4 +137,4 @@ if (document.readyState === "loading") { } else { // Falls das DOM schon fertig geladen ist, führen wir es direkt aus initEditor(); -} +} \ No newline at end of file