feat: add new-tab links and removable media previews (1.7.3)
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 11:48:44 +02:00
1 parent b8991ddd5c
commit 6d21c452bd
28 files changed
+496 -46

No files matched your search

+27 -2
View File
@@ -1,5 +1,5 @@
import { EditorHistory, labels, blockLabel, blockIcon, isPluginBlock, setStyle, effectiveStyle, locate, clone, isContainer, canContain, insertBlock, insertBlockAt, insertionTarget, moveBlockTo, duplicateBlock, moveBlock, devices, effectiveDesign, setDesign, blockPath, insertPreset, insertRow, insertRowAt, setEffect, insertMedia, upgradeDocument, CATEGORIES, ROW_COLUMNS, whyNot, dropList } from './editor-model.js';
import { openMedia } from './media.js';
import { openMedia, createMediaPreview } from './media.js';
import { createDesignPanel } from './design-panel.js';
import { createRichText } from './rich-text.js';
import { createCanvasOverlay } from './canvas-overlay.js';
@@ -177,6 +177,7 @@ function renderProperties() {
}
if (block && propertyPanel === 'advanced') { renderAdvanced(block, panel); return; }
if (block && propertyPanel === 'design') { renderDesign(block, panel); return; }
let imagePreview;
const field = (label, key, value, options = {}) => {
const id = `property-${key}`;
const labelEl = document.createElement('label'); labelEl.htmlFor = id; labelEl.textContent = label;
@@ -203,14 +204,17 @@ function renderProperties() {
else current[key] = next;
if (key === 'caption') upgradeDocument(doc, 3);
if (key === 'click' || key === 'lightbox') upgradeDocument(doc, 19);
if (key === 'newTab') upgradeDocument(doc, 20);
if (current.type === 'image' && key === 'src') { delete current.props.sources; delete current.props.mediaId; }
}, `${selected || 'page'}:${key}`);
if (key === 'decorative') renderProperties();
if (key === 'src') imagePreview?.update({ src: input.value });
if (['decorative', 'click'].includes(key)) { renderProperties(); document.getElementById(id)?.focus({ preventScroll: true }); }
renderTree(); controls(); schedulePreview();
});
input.addEventListener('blur', () => { history.mergeKey = null; });
if (options.checkbox) panel.append(labelEl); else panel.append(labelEl, input);
if (options.help) { const help = document.createElement('p'); help.className = 'field-help'; help.id = `${id}-help`; help.textContent = options.help; input.setAttribute('aria-describedby', help.id); panel.append(help); }
if (key === 'href') field('In neuem Tab öffnen', 'newTab', block.props.newTab === true, { checkbox: true });
};
if (!block) {
field('Seitentitel', 'title', history.document.title, { maxLength: 160 });
@@ -334,10 +338,15 @@ function renderProperties() {
if (block.type === 'button') field('Linkziel', 'href', block.props.href, { help: 'Lokaler Pfad (/kontakt), Sprungmarke (#abschnitt) oder HTTPS-Adresse.' });
if (block.type === 'image') {
renderImageWidth(block, panel);
imagePreview = createMediaPreview({ src: block.props.sources?.[0]?.src || block.props.src, label: block.props.alt || block.props.caption });
panel.append(imagePreview.element);
field('Beim Klick', 'click', block.props?.click || (block.props?.href ? 'link' : 'keine'), { choices: { keine: 'Nichts tun', vergroessern: 'Bild vergrößern', link: 'Zu einer Adresse führen' }, help: 'Vergrößern öffnet das Bild über der Seite. Ohne JavaScript öffnet derselbe Klick die große Bilddatei – die Seite bleibt also in jedem Fall benutzbar.' });
if ((block.props?.click || (block.props?.href ? 'link' : 'keine')) === 'link') field('Linkziel', 'href', block.props.href || '', { help: 'Lokaler Pfad (/kontakt), Sprungmarke (#abschnitt) oder HTTPS-Adresse. Für ein Logo im Kopfbereich meist /.' });
const picker = document.createElement('button'); picker.className = 'secondary'; picker.textContent = 'Bild aus Mediathek wählen';
picker.addEventListener('click', () => chooseMedia(false)); panel.append(picker);
const remove = document.createElement('button'); remove.type = 'button'; remove.className = 'text-button danger-text'; remove.textContent = 'Bild entfernen';
remove.title = 'Bild aus diesem Inhalt entfernen. Die Datei bleibt in der Mediathek.';
remove.addEventListener('click', () => { actions.delete(block.id); $('#undo').focus(); }); panel.append(remove);
field('Bildadresse', 'src', block.props.src, { help: 'Lokale Datei oder HTTPS-Adresse. Über die Mediathek wird die passende Bildvariante automatisch gewählt.' });
field('Dekoratives Bild', 'decorative', block.props.decorative, { checkbox: true });
if (!block.props.decorative) field('Alternativtext', 'alt', block.props.alt, { multiline: true });
@@ -351,6 +360,22 @@ function renderProperties() {
const picker = document.createElement('button'); picker.className = 'primary'; picker.textContent = 'Galeriebilder auswählen';
picker.addEventListener('click', () => chooseMedia(true)); panel.append(hint, picker);
}
if (['gallery', 'logos'].includes(block.type)) {
const images = document.createElement('div'); images.className = 'selected-media-list';
for (const child of block.children || []) {
const item = document.createElement('div');
const title = child.props.alt || child.props.caption || 'Bild';
item.append(createMediaPreview({ src: child.props.sources?.[0]?.src || child.props.src, label: title }).element);
const edit = document.createElement('button'); edit.type = 'button'; edit.className = 'text-button'; edit.textContent = 'Bearbeiten'; edit.setAttribute('aria-label', `${title} bearbeiten`);
edit.addEventListener('click', () => choose(child.id));
const remove = document.createElement('button'); remove.type = 'button'; remove.className = 'text-button danger-text'; remove.textContent = 'Entfernen'; remove.setAttribute('aria-label', `${title} entfernen`);
remove.addEventListener('click', () => {
history.change(doc => { const found = locate(doc.blocks, child.id); if (found) found.siblings.splice(found.index, 1); }); changed(); $('#undo').focus();
});
item.append(edit, remove); images.append(item);
}
panel.append(images);
}
if (block.type === 'section') {
field('Farbwelt', 'tone', block.layout?.tone || 'paper', { layout: true, choices: { paper: 'Hell · Papier', mint: 'Hellgrün · Akzent', ink: 'Dunkel · Tinte' } });
field('Inhaltsbreite', 'width', block.layout?.width || 'normal', { layout: true, choices: { normal: 'Websitebreite (aus dem Theme)', narrow: 'Schmal (800 px)', wide: 'Breit (1600 px)', full: 'Randlos (volle Breite)' }, help: 'Randlos nutzt die gesamte Sektion. Text und Überschriften füllen ihren Container; eine eigene Begrenzung stellst du unter Design → Größe ein.' });