Update editor.js
This commit is contained in:
+83
-122
@@ -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();
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user