Compare commits

...
4 Commits
Author SHA1 Message Date
kantarciandClaude Opus 5 6fb6d30e8d Release 1.22.5
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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:21:35 +02:00
kantarciandClaude Opus 5 ace6c50743 Raise loaded documents to the schema their blocks need, and bring the old browser checks back
The kant-it.de header used the hamburger switch at schema 19, so the server refused its
preview and save; the editor now raises a loaded document to the version its blocks and
properties require. The admin, button and navigation checks select blocks through the
structure tab, find the motion button by id and expect what the site's motion setting says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 12:21:35 +02:00
kantarciandClaude Opus 5 82a74277cb Release 1.22.4
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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 10:59:21 +02:00
kantarciandClaude Opus 5 5d55a65c22 Put the menu drag handle next to the summary instead of inside it
A button inside a summary is an interactive element in an interactive element; screen
readers announce it unreliably. The handle keeps its look and its mouse, touch and arrow
key behaviour. Also corrects the anchor hints and waits for the menu error in the admin check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 10:59:21 +02:00
12 changed files with 131 additions and 21 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
1.22.3
1.22.5
+20
View File
@@ -1,5 +1,25 @@
# Projektstatus
## Fassung 1.22.5: alte Browserchecks wieder grün, Kopfbereich von kant-it.de wieder bearbeitbar (22.09.2026)
**Anlass:** Umut: „räum den Strukturbaum-Fehler auch auf und Release 1.22.5“.
- **Strukturbaum:** `test:admin`, `test:buttons` und `test:navigation-ux` klickten direkt in den Strukturbaum. Seit 1.11 zeigt die Seitenleiste aber eine Ansicht auf einmal, Standard „Elemente“, und der Baum war versteckt. Jetzt wählen die Checks den Reiter „Struktur“, dann den Baustein und „Bearbeiten“, wie `editor-check.mjs`. Der Editor selbst war richtig.
- **Echter Fehler, dabei gefunden:** Der Kopfbereich in `var/kantit.db` (und damit vermutlich auf kant-it.de) trägt die Hamburger-Umschaltung (`collapse`) bei Schema v19, erlaubt ist sie erst ab v22. Die Website zeigt ihn, aber der Editor bekam für die Vorschau „Unbekannte Hamburger-Umschaltung oder Dokumentschema vor v22“, und Speichern wäre ebenso abgelehnt worden: Der Bereich war nicht bearbeitbar. Jetzt hebt `repairSchema()` (editor-model.js) ein geladenes Dokument auf die Fassung, die seine Bausteine und Eigenschaften verlangen. Das ist dieselbe Hebung, die der Editor beim Einstellen ohnehin macht. Die Umstellung auf formatierten Text (v12/v17) kommt nie von selbst, weil sie die Bedeutung ändert. Beim nächsten Speichern ist das Dokument korrigiert. Alle anderen lokalen Installationen hatten keine solchen Dokumente (geprüft mit dem Validator über alle Seiten und Bereiche).
- **Weitere veraltete Stellen in `test:admin`:** Der Knopf „Bewegung testen“ heißt seit 1.11 „Bewegung in der Vorschau abspielen“ (`#preview-motion`), auch ein Hilfetext im Editor nannte noch den alten Namen. Der Slider-Teil erwartete, dass „Bewegung reduzieren“ die Automatik anhält. Seit 1.11 gilt das nur mit „Systemvorgabe beachten“. Der Check prüft jetzt das Verhalten, das die Einstellung der Website verspricht.
**Geprüft:** `test:admin` (vollständig, auch der öffentliche Teil), `test:buttons`, `test:navigation-ux` und `test:editor` grün. Neuer Einheitstest für `repairSchema` in `tests/editor-model.test.mjs`.
## Fassung 1.22.4: Ziehknopf im Menü neben der Zusammenfassung (22.09.2026)
**Anlass:** Umut: „ja räum das auf und Release 1.22.4“ – die bekannte axe-Meldung `nested-interactive` im Menü-Editor.
- Der Ziehknopf eines Menüeintrags stand in der `<summary>`, also ein Knopf in einem Knopf. Screenreader lesen so etwas unzuverlässig vor, und die Tastatur erreicht es nicht sauber. Jetzt steht er als Geschwister vor `<details>`, per CSS über die linke Ecke der Zusammenfassung gelegt (`menu-entry.html.twig`, `createRow` in `menu-editor.js`). Aussehen und Bedienung bleiben gleich: Ziehen mit Maus, Stift und Finger, Pfeiltasten auf dem Knopf.
- Der Hinweis zu fehlenden Sprungzielen im Menü und die Hilfe zum Feld „Sprungziel“ im Editor nennen jetzt die richtige Stelle (Sektion, Zeile oder Spalte) und den Weg über die Auswahl im Menü.
- `test:admin`: Die Fehlermeldung nach einem abgewiesenen Speichern wurde direkt nach der Serverantwort abgefragt, sie erscheint aber erst, nachdem die Antwort gelesen ist. Jetzt wartet der Test auf sie. Der Check scheitert weiterhin an der älteren Stelle beim Klick in den Strukturbaum des Editors.
**Geprüft:** Die Editor-Prüfung prüft die ganze Menüseite mit axe, ohne Ausnahme. Sie stellt sicher, dass in keiner Zusammenfassung ein Knopf steht, und verschiebt einen Eintrag per Pfeiltaste (Fokus bleibt auf dem Knopf). Bildschirmfoto `reports/editor/menu-editor.png` angesehen.
## Fassung 1.22.3: die übrigen abgetippten Adressen (22.09.2026)
**Anlass:** Umut: „die offenen Punkte auch umsetzen und Release 1.22.3“ – die drei Reste aus 1.22.2.
+6
View File
@@ -998,3 +998,9 @@ details.card > summary:hover { background: #fafbf8 }
.term-chip { border: 1px solid var(--line); background: #fff; color: var(--ink); border-radius: 999px; padding: 4px 11px; font-size: 12px; cursor: pointer }
.term-chip[aria-pressed="true"] { background: #edf4dc; border-color: var(--accent); font-weight: 650 }
.term-chip[aria-pressed="true"]::before { content: '✓ '; }
/* Der Ziehknopf eines Menüeintrags liegt über der linken Ecke der Zusammenfassung, gehört aber
nicht zu ihr - sonst stünde ein Knopf in einem Knopf. */
.menu-entry { position: relative }
.menu-entry > .menu-drag-handle { position: absolute; left: 14px; top: 10px; z-index: 1 }
.menu-entry summary { padding-left: 58px; min-height: 60px }
@media (max-width: 560px) { .menu-entry > .menu-drag-handle { left: 8px; top: 8px } .menu-entry summary { padding-left: 50px; min-height: 56px } }
+29
View File
@@ -152,6 +152,35 @@ export function createBlock(type) {
return block;
}
/**
* Hebt ein geladenes Dokument auf die Fassung, die seine Bausteine und Eigenschaften verlangen.
*
* Normalerweise tut das der Editor in dem Moment, in dem jemand etwas einstellt. Ein Dokument,
* das ein Skript geschrieben hat, kann aber eine Eigenschaft tragen, die seine Fassung noch nicht
* kennt - der Kopfbereich von kant-it.de hatte die Hamburger-Umschaltung bei Fassung 19. Die
* Website zeigt so etwas, der Server lehnt Vorschau und Speichern aber ab, und der Bereich war im
* Editor nicht mehr zu bearbeiten. Nur Fassungen, die etwas hinzufügen, werden hier gehoben; die
* Umstellung auf formatierten Text (12, 17) ändert die Bedeutung und bleibt eine bewusste Wahl.
*/
export function repairSchema(document) {
const walk = blocks => {
for (const block of blocks || []) {
upgradeForType(document, block.type);
const props = block.props || {};
if ('caption' in props) upgradeDocument(document, 3);
if ('click' in props || 'lightbox' in props) upgradeDocument(document, 19);
if ('newTab' in props) upgradeDocument(document, 20);
if (Object.values(block.style || {}).some(values => values?.backgroundImage === 'none')) upgradeDocument(document, 20);
if (block.anchor) upgradeDocument(document, 21);
if ('collapse' in props) upgradeDocument(document, 22);
if ('faq' in props) upgradeDocument(document, 23);
walk(block.children);
}
};
walk(document.blocks);
return document;
}
/** Raises the document schema to the version that knows this block type. */
export function upgradeForType(document, type) {
if (type === 'gallery') upgradeDocument(document, 3);
+6 -4
View File
@@ -9,7 +9,7 @@ import { preview, rowPreview } from './library-previews.js';
import { decorateContainers } from './preview-containers.js';
import { initMotion } from './motion.js';
import { initSliders } from './slider.js';
import { VISIBILITY, setVisibility, registerPlugins, pluginDefinition, PLUGIN_PRESETS } from './editor-model.js';
import { VISIBILITY, setVisibility, registerPlugins, pluginDefinition, PLUGIN_PRESETS, repairSchema } from './editor-model.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', 'people', 'music', 'home'];
@@ -530,7 +530,7 @@ function renderContent(block, panel) {
add.textContent = '+ Eintrag hinzufügen'; add.addEventListener('click', () => addAccordionEntry(block.id));
panel.append(hint, entries, add);
}
if (['section', 'row', 'column'].includes(block.type)) field('Sprungziel', 'anchor', block.anchor, { anchor: true, maxLength: 64, help: 'Zum Beispiel kontakt eingeben. Buttons und Menülinks mit #kontakt springen dann hierher; von anderen Seiten mit /seitenadresse#kontakt.' });
if (['section', 'row', 'column'].includes(block.type)) field('Sprungziel', 'anchor', block.anchor, { anchor: true, maxLength: 64, help: 'Zum Beispiel kontakt. Im Menü lässt es sich dann als Sprungziel dieser Seite auswählen, ein Button springt mit #kontakt hierher.' });
if (block.type === 'accordionitem') {
field('Titel', 'title', block.props.title, { maxLength: 160 });
field('Beim Laden geöffnet', 'open', block.props.open, { checkbox: true, help: 'Gilt für die veröffentlichte Seite. Im Editor bleiben alle Einträge zum Bearbeiten geöffnet.' });
@@ -768,7 +768,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 = '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);
const note = document.createElement('p'); note.className = 'field-help'; note.textContent = 'Einblendungen starten, sobald das Element im Bildschirm erscheint. Mit dem Abspielknopf über der Vorschau lässt sich das prüfen. Die Einstellung „Bewegung reduzieren“ wird berücksichtigt.'; panel.append(note);
}
/**
@@ -1096,7 +1096,9 @@ async function load() {
designTokens = await response.json();
}
if (!designSchema) await loadDesignSchema();
history = new EditorHistory(data.document);
// Ein Dokument mit Eigenschaften einer neueren Fassung wird beim Laden gehoben, sonst lehnt
// der Server schon die Vorschau ab (siehe repairSchema).
history = new EditorHistory(repairSchema(data.document));
({ version, revisionId, publishedRevisionId, csrfToken } = data);
// Die Bausteine der eingeschalteten Plugins kommen mit dem Dokument; der Werkzeugkasten
// bekommt dafür eine eigene Gruppe.
+4 -3
View File
@@ -75,8 +75,9 @@ function createRow(data) {
for (const key of ['position', 'level', 'type']) field(key, data[key] ?? 0, '', 'hidden');
const details = el('details'); const summary = el('summary');
const { handle, chevron } = chrome();
summary.append(handle, el('strong', 'menu-entry-title', data.label), el('span', 'menu-entry-kind', data.type === 'page' ? 'Seitenlink' : 'Eigener Link'), chevron);
const body = el('div', 'menu-entry-fields'); details.append(summary, body); row.append(details);
// Der Ziehknopf steht neben der Zusammenfassung, nicht in ihr - kein Knopf in einem Knopf.
summary.append(el('strong', 'menu-entry-title', data.label), el('span', 'menu-entry-kind', data.type === 'page' ? 'Seitenlink' : 'Eigener Link'), chevron);
const body = el('div', 'menu-entry-fields'); details.append(summary, body); row.append(handle, details);
const label = field('label', data.label, 'Beschriftung'); label.required = true; label.maxLength = 80;
if (data.type === 'page') {
const picker = form.querySelector('.menu-add .content-picker').cloneNode(true); delete picker.dataset.ready;
@@ -136,7 +137,7 @@ function anchorChoice(row) {
for (const item of items) select.append(new Option(item.label, item.anchor));
if (current && !items.some(item => item.anchor === current)) select.append(new Option(`#${current} (auf der Seite nicht gefunden)`, current));
select.value = current;
help.textContent = items.length ? '' : 'Diese Seite hat keine Sprungziele. Im Editor bekommt eine Sektion unter „Erweitert“ einen Anker.';
help.textContent = items.length ? '' : 'Diese Seite hat keine Sprungziele. Im Editor lässt sich einer Sektion, Zeile oder Spalte ein Sprungziel geben.';
} catch {
if (mine === request) help.textContent = 'Sprungziele konnten nicht geladen werden.';
}
+18 -6
View File
@@ -76,9 +76,15 @@ try {
await page.mouse.move(1, 1); await page.waitForTimeout(7700);
assert.ok(Math.abs((await geometry()).left - manual.left) > 20, 'play resumes after manual operation');
await page.emulateMedia({ reducedMotion: 'reduce' });
assert.equal(await controls.getByRole('button', { name: 'Wiedergabe: automatisch weiterblättern' }).isDisabled(), true);
// Seit 1.11 entscheidet die Website (Einstellungen → Bewegung): Standard ist „immer abspielen“,
// nur mit „Systemvorgabe beachten“ (Klasse motion-respect) hält „Bewegung reduzieren“ die
// Automatik an. Geprüft wird das Verhalten, das die Einstellung verspricht.
const respects = await page.evaluate(() => document.documentElement.classList.contains('motion-respect'));
const play = controls.locator('.slider-play');
assert.equal(await play.isDisabled(), respects, 'the play button follows the motion setting');
const reduced = await geometry(); await page.waitForTimeout(7100);
assert.ok(Math.abs((await geometry()).left - reduced.left) < 2, 'reduced motion stops autoplay');
if (respects) assert.ok(Math.abs((await geometry()).left - reduced.left) < 2, 'reduced motion stops autoplay when the site respects it');
else assert.ok(Math.abs((await geometry()).left - reduced.left) > 20, 'autoplay keeps running when the site plays motion always');
await controls.getByRole('button', { name: 'Vorherige Folie', exact: true }).click();
assert.ok(Math.abs((await geometry()).left - reduced.left) > 20, 'manual navigation works with reduced motion');
await page.emulateMedia({ reducedMotion: 'no-preference' });
@@ -143,7 +149,7 @@ try {
await page.getByRole('button', { name: 'Link hinzufügen', exact: true }).click();
assert.equal((await menuSave()).status(), 422);
assert.equal(await entries.last().locator('[data-key="url"]').inputValue(), 'javascript:alert(1)');
assert.equal(await page.locator('#menu-error').isVisible(), true);
await page.locator('#menu-error').waitFor({ state: 'visible' });
await entries.last().locator('summary').click();
await entries.last().getByLabel('Adresse', { exact: true }).fill('/#kontakt');
assert.equal((await menuSave()).status(), 200);
@@ -155,7 +161,7 @@ try {
await page.getByLabel('Name des Menüs').fill('QA lokaler Stand');
assert.equal((await menuSave()).status(), 409);
assert.equal(await page.getByLabel('Name des Menüs').inputValue(), 'QA lokaler Stand');
assert.equal(await page.locator('#menu-error').isVisible(), true);
await page.locator('#menu-error').waitFor({ state: 'visible' });
checks.push({ name: 'menu search by keyboard, add/nest/remove/undo/save, invalid URL and conflict retain input', passed: true });
await page.goto(origin + '/admin/forms');
@@ -172,7 +178,10 @@ try {
await page.getByLabel('Name', { exact: true }).fill('QA Gemeinsame Sektion');
await page.getByRole('button', { name: 'Anlegen und bearbeiten', exact: true }).click();
await page.locator('#preview-status').filter({ hasText: 'Entwurf ·' }).waitFor();
// Die Sektion im Reiter „Struktur“ wählen, dann den Text aus „Elemente“ hineinsetzen.
await page.getByRole('tab', { name: 'Struktur', exact: true }).click();
await page.locator('#structure .tree-item').first().click();
await page.getByRole('tab', { name: 'Elemente', exact: true }).click();
await page.locator('#library [data-add="text"]').click();
await page.getByLabel('Text', { exact: true }).fill('Dieser Inhalt wird als gemeinsame Sektion gespeichert.');
await page.getByLabel('Text', { exact: true }).press('Tab');
@@ -197,7 +206,7 @@ try {
await page.goto(origin + '/admin/pages/' + fixture.homeId);
await page.locator('#preview-status').filter({ hasText: 'Entwurf ·' }).waitFor();
await page.getByRole('button', { name: 'Bewegung testen', exact: true }).click();
await page.locator('#preview-motion').click();
const preview = page.frameLocator('#preview'), previewSlider = preview.locator('#hero-slider');
// Measure in the outer page: Playwright's in-frame animation stability check can stall in
// a no-script sandbox while a Web Animation runs. Use a real pointer on verified geometry.
@@ -210,7 +219,10 @@ try {
await page.waitForTimeout(800);
assert.ok(await previewSlider.evaluate(slider => slider.scrollLeft > 20));
assert.equal(await page.locator('#preview').getAttribute('sandbox'), 'allow-same-origin');
await page.getByRole('button', { name: 'Bewegung stoppen', exact: true }).click();
// Derselbe Knopf stoppt wieder; er sagt es in seiner Beschriftung und in aria-pressed.
assert.equal(await page.locator('#preview-motion').getAttribute('aria-label'), 'Bewegung in der Vorschau stoppen');
await page.locator('#preview-motion').click();
assert.equal(await page.locator('#preview-motion').getAttribute('aria-pressed'), 'false');
assert.equal(await preview.locator('.slider-controls').count(), 0);
checks.push({ name: 'sandboxed editor preview has opt-in working slider controls', passed: true });
+9 -2
View File
@@ -30,6 +30,13 @@ try {
browser = await chromium.launch();
const context = await browser.newContext({ viewport: { width: 1600, height: 1050 } });
page = await context.newPage(); page.setDefaultTimeout(15000); page.on('pageerror', error => errors.push(error.message));
// Ein Baustein wird im Reiter „Struktur“ gewählt und mit „Bearbeiten“ geöffnet - die
// Seitenleiste zeigt seit 1.11 nur eine Ansicht auf einmal.
const selectBlock = async id => {
await page.getByRole('tab', { name: 'Struktur', exact: true }).click();
await page.locator(`#structure [data-block-id="${id}"]`).click();
await page.locator(`#structure [data-edit-block="${id}"]`).click();
};
await page.goto(origin + '/admin/login');
await page.getByLabel('Benutzername').fill(fixture.username); await page.getByLabel('Passwort', { exact: true }).fill(fixture.password);
await page.getByRole('button', { name: 'Anmelden', exact: true }).click(); await page.waitForURL(origin + '/admin');
@@ -53,7 +60,7 @@ try {
checks.push('global typography, visual presets, normal/hover preview and actual form save including empty palette rows');
await page.goto(`${origin}/admin/pages/${fixture.homeId}`);
await page.locator('#structure [data-block-id="qa-button"]').click();
await selectBlock('qa-button');
await page.getByRole('button', { name: 'Button-Vorlage Abgerundet', exact: true }).click();
const waitVariant = async (id, radius, background) => page.waitForFunction(({id,radius,background}) => {
const doc = document.querySelector('#preview')?.contentDocument, el = doc?.getElementById(id);
@@ -75,7 +82,7 @@ try {
await page.locator('[data-viewport="desktop"]').click(); await waitVariant('qa-button', '8px', false);
await page.locator('.button-designer').screenshot({ path: `${report}/editor-controls.png` });
await page.locator('#save').click(); await page.locator('#save-status').filter({ hasText: 'Entwurf gespeichert' }).waitFor();
await page.reload(); await page.locator('#structure [data-block-id="qa-button"]').click(); await waitVariant('qa-button', '8px', false);
await page.reload(); await selectBlock('qa-button'); await waitVariant('qa-button', '8px', false);
const stored = (await (await context.request.get(api)).json()).document;
assert.equal(stored.schemaVersion, 21);
assert.deepEqual(stored.blocks[0].children[1].props, { text: 'Mehr erfahren', href: '/kontakt' });
+16 -4
View File
@@ -1189,6 +1189,8 @@ try {
await page.locator('dialog.page-dialog [role="option"]').first().click();
await page.locator('dialog.page-dialog').waitFor({ state: 'detached' });
assert.match(await page.locator('#link-url').inputValue(), /^\/[a-z0-9\-/]*$/);
await page.locator('#link-label').fill('Eigener Prüflink');
await page.locator('#menu-add-url').click();
await page.locator('#menu-search').focus();
await page.locator('#menu-search-results [role="option"]').first().click();
await page.locator('#menu-add-page').click();
@@ -1196,11 +1198,21 @@ try {
const sprungziel = neuerEintrag.getByLabel('Sprungziel', { exact: true });
assert.equal(await sprungziel.evaluate(node => node.tagName), 'SELECT', 'the anchor is a choice');
assert.equal(await sprungziel.locator('option').first().textContent(), 'Seitenanfang');
// Nur die neuen Felder: die Zusammenfassung der Menüeinträge trägt seit jeher einen Ziehknopf
// (nested-interactive), das ist eine eigene, bekannte Baustelle.
// Die ganze Seite: seit der Ziehknopf neben der Zusammenfassung steht, gibt es keinen Knopf
// mehr in einem Knopf.
await neuerEintrag.locator('summary').click();
const menuAxe = await new AxeBuilder({ page }).include('#menu-items .menu-entry:last-child .menu-entry-fields').include('.menu-add').withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']).analyze();
assert.deepEqual(menuAxe.violations.map(v => v.id), [], 'anchor choice and link field');
await axe('menu editor');
await page.screenshot({ path: 'reports/editor/menu-editor.png' });
assert.equal(await neuerEintrag.locator('summary [data-menu-drag]').count(), 0, 'no button inside the summary');
// Ziehknopf per Tastatur: Pfeil nach oben verschiebt den Eintrag.
const eintraege = page.locator('#menu-items .menu-entry');
const anzahl = await eintraege.count();
assert.ok(anzahl >= 2);
const titel = await neuerEintrag.locator('.menu-entry-title').textContent();
await eintraege.nth(anzahl - 1).locator('[data-menu-drag]').focus();
await page.keyboard.press('ArrowUp');
assert.equal(await eintraege.nth(anzahl - 2).locator('.menu-entry-title').textContent(), titel, 'the entry moved up');
assert.equal(await eintraege.nth(anzahl - 2).locator('[data-menu-drag]').evaluate(node => node === document.activeElement), true, 'the handle keeps focus');
checks.push({ name: 'menu: page search for own links, anchors as a choice', passed: true });
// Beitrag: vorhandene Kategorien stehen zum Anklicken am Feld.
await page.goto(origin + '/admin/posts');
+3
View File
@@ -34,7 +34,10 @@ try {
data.document.blocks.push({ id: 'qa-anchor', type: 'section', children: [{ id: 'qa-anchor-title', type: 'heading', props: { text: 'Kontaktbereich', level: 2 } }] });
assert.equal((await context.request.post(api + '/draft', { headers: { 'X-CSRF-Token': data.csrfToken }, data: { document: data.document, expectedVersion: data.version } })).status(), 200);
await page.goto(`${origin}/admin/pages/${fixture.homeId}`);
// Im Reiter „Struktur“ wählen und mit „Bearbeiten“ öffnen, wie im Editor selbst.
await page.getByRole('tab', { name: 'Struktur', exact: true }).click();
await page.locator('#structure [data-block-id="qa-anchor"]').click();
await page.locator('#structure [data-edit-block="qa-anchor"]').click();
await page.locator('#property-anchor').fill('#kontakt-check'); await page.locator('#property-anchor').blur();
await page.locator('#save').click(); await page.locator('#save-status').filter({ hasText: 'Entwurf gespeichert' }).waitFor();
await page.locator('#publish').click(); await page.locator('#confirm-publish').click(); await page.locator('#save-status').filter({ hasText: 'Veröffentlicht' }).waitFor();
+4 -1
View File
@@ -2,8 +2,11 @@
<input type="hidden" data-key="position" name="items[{{ n }}][position]" value="{{ n }}">
<input type="hidden" data-key="level" name="items[{{ n }}][level]" value="{{ row.level }}">
<input type="hidden" data-key="type" name="items[{{ n }}][type]" value="{{ row.type }}">
{# Der Ziehknopf steht neben der Zusammenfassung, nicht in ihr: ein Knopf in einem Knopf ist
für Screenreader und Tastatur nicht bedienbar (axe: nested-interactive). #}
<button class="menu-drag-handle" type="button" data-menu-drag aria-label="{{ row.label }} verschieben" title="Ziehen · Pfeiltasten ändern Reihenfolge und Ebene"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="currentColor"><circle cx="8" cy="5" r="1.6"/><circle cx="16" cy="5" r="1.6"/><circle cx="8" cy="12" r="1.6"/><circle cx="16" cy="12" r="1.6"/><circle cx="8" cy="19" r="1.6"/><circle cx="16" cy="19" r="1.6"/></svg></button>
<details>
<summary><button class="menu-drag-handle" type="button" data-menu-drag aria-label="{{ row.label }} verschieben" title="Ziehen · Pfeiltasten ändern Reihenfolge und Ebene"><svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="currentColor"><circle cx="8" cy="5" r="1.6"/><circle cx="16" cy="5" r="1.6"/><circle cx="8" cy="12" r="1.6"/><circle cx="16" cy="12" r="1.6"/><circle cx="8" cy="19" r="1.6"/><circle cx="16" cy="19" r="1.6"/></svg></button><strong class="menu-entry-title">{{ row.label }}</strong><span class="menu-entry-kind">{{ row.type == 'page' ? 'Seitenlink' : 'Eigener Link' }}</span><svg class="menu-chevron" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg></summary>
<summary><strong class="menu-entry-title">{{ row.label }}</strong><span class="menu-entry-kind">{{ row.type == 'page' ? 'Seitenlink' : 'Eigener Link' }}</span><svg class="menu-chevron" viewBox="0 0 24 24" width="20" height="20" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg></summary>
<div class="menu-entry-fields">
<label for="menu-label-{{ n }}">Beschriftung</label><input id="menu-label-{{ n }}" data-key="label" name="items[{{ n }}][label]" maxlength="80" required value="{{ row.label }}">
{% if row.type == 'page' %}
+15
View File
@@ -642,3 +642,18 @@ test('registered plugin blocks get labels, defaults, container rules and presets
assert.equal(pluginDefinition(bild), null);
assert.equal(isContainer(galerie), false);
});
test('a loaded document is raised to the schema its blocks and properties need', async () => {
const { repairSchema } = await import('../public/assets/editor-model.js');
// So stand der Kopfbereich von kant-it.de da: Hamburger-Umschaltung bei Fassung 19.
const header = { schemaVersion: 19, title: 'Kopf', description: '', blocks: [
{ id: 's', type: 'section', anchor: 'oben', children: [{ id: 'm', type: 'menu', props: { slot: 'header', collapse: 'tablet' } }] },
] };
assert.equal(repairSchema(header).schemaVersion, 22);
// Nichts Neues darin: die Fassung bleibt - auch die Umstellung auf formatierten Text (12)
// kommt nie von allein.
const plain = { schemaVersion: 5, title: 'Alt', description: '', blocks: [{ id: 't', type: 'text', props: { text: 'a < b' } }] };
assert.equal(repairSchema(plain).schemaVersion, 5);
const faq = { schemaVersion: 10, title: 'F', description: '', blocks: [{ id: 'a', type: 'accordion', props: { faq: true }, children: [] }] };
assert.equal(repairSchema(faq).schemaVersion, 23);
});