Designer: 93 Eigenschaften, neun Einblendungen, drei neue Bausteine
Umut fragte nach Zeilen, nach Rahmen und Rundungen - und ob der Designer so aufgestellt ist wie Flatsome. Der Vergleich steht in docs/DESIGNER-VERGLEICH.md, mit der Einschraenkung, dass Flatsome hier nicht installierbar war: das ist Wissen, keine Messung. Zwei echte Fehler kamen dabei heraus, beide vorher unbemerkt. Eine Rahmenstaerke je Seite wurde zu border-width-top. Das ist kein CSS, der Browser verwirft die Zeile stillschweigend - Rahmenstaerken je Seite hatten nie eine Wirkung, und es gab keine Meldung, die darauf hingewiesen haette. Die Schreibweise steht jetzt je Eigenschaft im Schema, weil padding-top dem Namen folgt und border-top-width nicht. Und eine Rundung auf einem Bildblock landete auf dem figure, waehrend das Bild darin eckig blieb. Eigenschaften koennen jetzt auf das Bild im Block zielen (target 'img' im Schema, eine zweite Regel im Compiler) - deshalb tut object-fit ueberhaupt etwas, denn auf einem figure tut es nichts. Von 50 auf 93 Eigenschaften: Bild (Ausschnitt, Bildmitte, Seitenverhaeltnis, Hoehe, Ecken, Rahmen), Positionierung mit z-index, getrennte Zeilen- und Spaltenabstaende, Flex je Element, Innen- und Textschatten, acht Bildfilter als eine filter-Deklaration in fester Reihenfolge, Mischmodus, Zusatzrahmen, Mauszeiger. Neun Einblendungen mit Verzoegerung, sieben Hover-Effekte, alle innerhalb von prefers-reduced-motion. Drei neue Bausteine (Dokumentschema v15): Symbol als eigenes Element, Fortschritt mit role=meter und dem Wert im Markup statt nur in der Balkenbreite, und ein Slider als Scrollbereich mit Einrasten - ohne JavaScript, ohne Autoplay, mit Finger, Trackpad, Mausrad und Tastatur. Zeilen waren nicht unauffindbar, sondern unbrauchbar leer: eine Zeile ohne Spalten sieht aus wie nichts. Jetzt legt "Zeile, 2/3/4 Spalten" beides auf einen Klick an, und im Werkzeugkasten steht, wie der Aufbau gemeint ist. 315 PHP-Tests / 1309 Assertions, 16 JS-Modelltests, axe auf dreizehn Bildschirmen ohne Verstoesse. 16 Datensaetze rechnen jede Eigenschaftsform gegen die erwartete Deklaration nach - die Pruefung, die die beiden Fehler oben gefunden haette, wenn es sie vorher gegeben haette. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
30d8d58bbf
commit
7251b82f09
18 files changed
+777
-27
No files matched your search
@@ -1,12 +1,12 @@
|
||||
export const labels = { section: 'Sektion', row: 'Zeile', column: 'Spalte', heading: 'Überschrift', text: 'Text', image: 'Bild', button: 'Button', gallery: 'Galerie', postlist: 'Beitragsliste', iconcard: 'Symbolkarte', stat: 'Kennzahl', quote: 'Kundenstimme', logos: 'Logoleiste', cta: 'Aufruf-Band', contact: 'Kontaktdaten', divider: 'Trennlinie', spacer: 'Abstand', accordion: 'Akkordeon', accordionitem: 'Akkordeon-Eintrag', tabs: 'Tabs', tabitem: 'Tab', form: 'Formular', breadcrumb: 'Brotkrumen', menu: 'Navigation', partialref: 'Verknüpfte Sektion', embed: 'Externer Inhalt' };
|
||||
export const icons = { section: '▣', row: '☰', column: '▥', heading: 'H', text: 'T', image: '▧', button: '↗', gallery: '▦', postlist: '☷', iconcard: '◈', stat: '№', quote: '❝', logos: '⬚', cta: '◉', contact: '✆', divider: '—', spacer: '⇳', accordion: '⌵', accordionitem: '·', tabs: '▭', tabitem: '▫', form: '✉', breadcrumb: '⟩', menu: '☰', partialref: '⧉', embed: '⬓' };
|
||||
export const labels = { section: 'Sektion', row: 'Zeile', column: 'Spalte', heading: 'Überschrift', text: 'Text', image: 'Bild', button: 'Button', gallery: 'Galerie', postlist: 'Beitragsliste', iconcard: 'Symbolkarte', stat: 'Kennzahl', quote: 'Kundenstimme', logos: 'Logoleiste', cta: 'Aufruf-Band', contact: 'Kontaktdaten', divider: 'Trennlinie', spacer: 'Abstand', accordion: 'Akkordeon', accordionitem: 'Akkordeon-Eintrag', tabs: 'Tabs', tabitem: 'Tab', form: 'Formular', breadcrumb: 'Brotkrumen', menu: 'Navigation', partialref: 'Verknüpfte Sektion', embed: 'Externer Inhalt', icon: 'Symbol', progress: 'Fortschritt', slider: 'Slider' };
|
||||
export const icons = { section: '▣', row: '☰', column: '▥', heading: 'H', text: 'T', image: '▧', button: '↗', gallery: '▦', postlist: '☷', iconcard: '◈', stat: '№', quote: '❝', logos: '⬚', cta: '◉', contact: '✆', divider: '—', spacer: '⇳', accordion: '⌵', accordionitem: '·', tabs: '▭', tabitem: '▫', form: '✉', breadcrumb: '⟩', menu: '☰', partialref: '⧉', embed: '⬓', icon: '◆', progress: '▰', slider: '⇄' };
|
||||
// A namespaced type belongs to a plugin this installation may not have. Such blocks stay in the
|
||||
// document, stay selectable and movable, but the editor never edits or fills them.
|
||||
export const PLUGIN_TYPE = /^[a-z][a-z0-9]{1,30}\.[a-z][a-z0-9-]{1,40}$/;
|
||||
export const isPluginBlock = block => typeof block?.type === 'string' && PLUGIN_TYPE.test(block.type);
|
||||
export const blockLabel = block => labels[block?.type] ?? (isPluginBlock(block) ? `Baustein ${block.type}` : 'Unbekannt');
|
||||
export const blockIcon = block => icons[block?.type] ?? '❔';
|
||||
export const isContainer = block => ['section', 'row', 'column', 'gallery', 'logos', 'accordion', 'accordionitem', 'tabs', 'tabitem'].includes(block?.type);
|
||||
export const isContainer = block => ['section', 'row', 'column', 'gallery', 'logos', 'accordion', 'accordionitem', 'tabs', 'tabitem', 'slider'].includes(block?.type);
|
||||
// Nothing may be dropped into a plugin block: the core does not know its layout contract.
|
||||
// Containers that accept only one kind of child, mirrored by the server side validator.
|
||||
export const ONLY_CHILD = { gallery: 'image', logos: 'image', accordion: 'accordionitem', tabs: 'tabitem' };
|
||||
@@ -50,6 +50,9 @@ export function createBlock(type) {
|
||||
if (type === 'partialref') block.props = { partial: '' };
|
||||
// An embed starts empty and blocked: no address, so nothing loads before someone enters one.
|
||||
if (type === 'embed') block.props = { group: 'external', title: '', provider: '', url: '', hint: '', height: 400 };
|
||||
if (type === 'icon') block.props = { icon: 'star', size: 48, decorative: true, label: '', href: '' };
|
||||
if (type === 'progress') block.props = { label: 'Fortschritt', value: 70, showValue: true };
|
||||
if (type === 'slider') block.props = { desktopVisible: 3, tabletVisible: 2, mobileVisible: 1, snap: true };
|
||||
if (type === 'image') block.props = { src: '/assets/image-placeholder.svg', alt: 'Abstrakte Landschaft als Platzhalter', decorative: false, width: 1000, height: 600 };
|
||||
return block;
|
||||
}
|
||||
@@ -64,6 +67,7 @@ export function insertBlock(document, type, selected) {
|
||||
if (type === 'breadcrumb') upgradeDocument(document, 9);
|
||||
if (type === 'menu' || type === 'partialref') upgradeDocument(document, 11);
|
||||
if (type === 'embed') upgradeDocument(document, 14);
|
||||
if (['icon', 'progress', 'slider'].includes(type)) upgradeDocument(document, 15);
|
||||
let target = locate(document.blocks, selected);
|
||||
if (target?.block.type === 'gallery' && type !== 'image') { target.siblings.splice(target.index + 1, 0, block); return block.id; }
|
||||
if (target?.parent?.type === 'gallery' && type !== 'image') target = locate(document.blocks, target.parent.id);
|
||||
@@ -168,6 +172,26 @@ export function insertMedia(document, selected, items) {
|
||||
return parent.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A row with a given number of columns, inserted where the selection is.
|
||||
*
|
||||
* A row on its own looks like nothing, which is why "add a row" was hard to find: what people
|
||||
* want is a row that already has columns in it.
|
||||
*/
|
||||
export function insertRow(document, columns, selected) {
|
||||
const count = Math.max(1, Math.min(6, Number(columns) || 2));
|
||||
const names = { 1: 'one', 2: 'two', 3: 'three', 4: 'four' };
|
||||
const row = createBlock('row');
|
||||
row.layout = { design: { desktop: { columns: names[count] ?? 'auto', gap: 'medium' }, mobile: { columns: 'one' } } };
|
||||
row.children = Array.from({ length: count }, () => createBlock('column'));
|
||||
upgradeDocument(document);
|
||||
const target = locate(document.blocks, selected);
|
||||
if (!target) document.blocks.push(row);
|
||||
else if (canContain(target.block, row)) (target.block.children ??= []).push(row);
|
||||
else target.siblings.splice(target.index + 1, 0, row);
|
||||
return row.id;
|
||||
}
|
||||
|
||||
export function insertPreset(document, name, selected) {
|
||||
if (!['intro', 'columns', 'cta'].includes(name)) throw new Error('Unbekannte Vorlage.');
|
||||
const section = createBlock('section');
|
||||
|
||||
Reference in new issue
Block a user