Fassung 1.7.0: verlässliche Installation, übersichtliche Verwaltung und bedienbare Bewegung
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

This commit is contained in:
kantarci committed 2026-09-14 10:49:54 +02:00
1 parent b164c4edfe
commit 495b26896e
55 files changed
+1632 -423

No files matched your search

+26 -5
View File
@@ -5,6 +5,8 @@ import { createRichText } from './rich-text.js';
import { createCanvasOverlay } from './canvas-overlay.js';
import { preview, rowPreview } from './library-previews.js';
import { decorateContainers } from './preview-containers.js';
import { initMotion } from './motion.js';
import { initSliders } from './slider.js';
// Mirrors DocumentValidator::ICONS; the server rejects anything outside this list.
const ICONS = ['app', 'web', 'design', 'network', 'hardware', 'shop', 'support', 'shield', 'clock', 'star', 'phone', 'mail', 'pin', 'check'];
@@ -16,6 +18,7 @@ const $ = selector => document.querySelector(selector);
// same document, draft and preview endpoints, so this file does not care which one it drives.
const api = root.dataset.api;
const editingArea = 'page' !== root.dataset.kind;
const adminBase = document.querySelector('meta[name="admin-base"]').content;
const mayScript = 'true' === root.dataset.canScript;
let history, selected = null, version, revisionId, publishedRevisionId, csrfToken;
let busy = false, conflict = false, previewTimer, previewController, previewSequence = 0;
@@ -26,6 +29,21 @@ let previewScale = 1, overlay = null, pendingInsert = null;
const collapsed = new Set();
let renderedProperties = null;
let revealAfterPreview = null;
let previewPlayback = false, stopPlayback = () => {};
function playPreview() {
stopPlayback(); stopPlayback = () => {};
const button = $('#preview-motion');
button.setAttribute('aria-pressed', String(previewPlayback));
button.textContent = previewPlayback ? 'Bewegung stoppen' : 'Bewegung testen';
const doc = $('#preview').contentDocument;
if (previewPlayback && doc?.body) {
const stopMotion = initMotion(doc), stopSliders = initSliders(doc);
stopPlayback = () => { stopMotion(); stopSliders(); };
}
}
$('#preview-motion').addEventListener('click', () => { previewPlayback = !previewPlayback; playPreview(); });
function notify(message = '', recover = false) {
$('#message').textContent = message;
@@ -56,7 +74,7 @@ async function request(path, body, signal) {
function controls() {
const ready = Boolean(history);
$('#save-status').textContent = busy ? 'Wird gespeichert …' : conflict ? 'Speicherkonflikt' : !ready ? 'Wird geladen …' : history.dirty ? 'Ungespeicherte Änderungen' : revisionId === publishedRevisionId ? 'Veröffentlicht' : 'Entwurf gespeichert';
$('#save-status').textContent = busy ? 'Wird gespeichert …' : conflict ? 'Speicherkonflikt' : !ready ? 'Wird geladen …' : history.dirty ? 'Ungespeicherte Änderungen' : editingArea ? 'Bereich gespeichert' : revisionId === publishedRevisionId ? 'Veröffentlicht' : 'Entwurf gespeichert';
$('#save').disabled = !ready || busy || conflict || !history.dirty;
if ($('#publish')) $('#publish').disabled = !ready || busy || conflict || history.dirty || revisionId === publishedRevisionId;
$('#undo').disabled = !ready || busy || !history.past.length;
@@ -77,6 +95,7 @@ function controls() {
$('#select-page').disabled = !ready || busy;
$('#add-root-section').disabled = !ready || busy;
$('#open-media-library').disabled = !ready || busy;
$('#preview-motion').disabled = !ready || busy;
}
function renderTree() {
@@ -440,7 +459,7 @@ function renderLegacyEffects(block, panel) {
none: 'Keiner', lift: 'Leicht anheben', sink: 'Leicht absenken', zoom: 'Bild vergrößern',
glow: 'Leuchten', tilt: 'Leicht kippen', 'grow-border': 'Rahmen einfahren',
}],
['entrance', 'Einblendung beim Laden', {
['entrance', 'Einblendung beim Erscheinen', {
none: 'Keine', fade: 'Sanft einblenden', rise: 'Von unten', sink: 'Von oben', left: 'Von links',
right: 'Von rechts', zoom: 'Heranzoomen', flip: 'Kippen', blur: 'Aus der Unschärfe',
}],
@@ -453,7 +472,7 @@ function renderLegacyEffects(block, panel) {
select.addEventListener('change', () => { history.change(doc => setEffect(doc, selected, key, select.value)); controls(); schedulePreview(); });
panel.append(labelEl, select);
}
const note = document.createElement('p'); note.className = 'field-help'; note.textContent = 'Bewegungseffekte werden bei „Bewegung reduzieren“ ausgeschaltet. Zoom wirkt auf enthaltene Bilder. Einblendungen laufen beim Laden der Seite, nicht beim Scrollen: eine öffentliche Seite führt kein JavaScript aus.'; panel.append(note);
const note = document.createElement('p'); note.className = 'field-help'; note.textContent = 'Einblendungen starten, sobald das Element im Bildschirm erscheint. Mit „Bewegung testen“ lässt sich das in der Vorschau prüfen. Die Einstellung „Bewegung reduzieren“ wird berücksichtigt.'; panel.append(note);
}
function chooseMedia(multiple) {
@@ -654,6 +673,7 @@ async function updatePreview() {
highlight(revealAfterPreview === selected); revealAfterPreview = null;
markInsertTarget();
frame.style.visibility = '';
playPreview();
$('#preview-status').textContent = loaded
? 'Entwurf · Klicken wählt aus, Doppelklick bearbeitet Text, Elemente lassen sich hierher ziehen.'
: 'Editor-Hilfsflächen konnten nicht geladen werden. Bitte den Editor neu laden.';
@@ -663,6 +683,7 @@ async function updatePreview() {
};
// Solange die Gestaltung fehlt, bleibt die Fläche leer statt falsch.
frame.style.visibility = 'hidden';
stopPlayback();
frame.srcdoc = html;
// Sobald der Kopf da ist, wird gestaltet - geprüft Bild für Bild statt auf ein Ereignis zu
// warten, das es für srcdoc nicht zuverlässig gibt.
@@ -696,7 +717,7 @@ function changed() {
async function loadDesignSchema() {
try {
const response = await fetch('/admin/api/design', { credentials: 'same-origin' });
const response = await fetch(adminBase + '/api/design', { credentials: 'same-origin' });
if (!response.ok) return;
designSchema = await response.json();
designPanel = createDesignPanel({
@@ -1024,7 +1045,7 @@ $('#confirm-preset').addEventListener('click', async () => {
const name = $('#preset-name').value.trim();
const error = $('#preset-error');
try {
const response = await fetch('/admin/api/style-presets', {
const response = await fetch(adminBase + '/api/style-presets', {
method: 'POST', credentials: 'same-origin',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': designSchema.csrfToken },
body: JSON.stringify({ name, style: pendingPreset }),