Der Playwright-Editortest war durch den Editorumbau kaputt: er steuerte die abgeschafften Auswahllisten. Umgeschrieben auf die freien Bedienelemente - Einheitenfelder, Vererbung über drei Geräte, einzelnes Zurücksetzen, Palettenfarbe, Abstand je Seite, Hover-Regel, freie Rasterspalten und die weiter vorhandenen Bewegungseffekte. Dabei kamen drei Fehler heraus, die ohne Browserlauf unsichtbar sind: 1. Die frei gestalteten Werte wirkten in der Editorvorschau gar nicht. Ein srcdoc-Iframe erbt die Richtlinie des einbettenden Dokuments, und die Verwaltung verbietet Inline-Styles - das erzeugte style-Element wurde stillschweigend verworfen. Ausgeliefert wurde es korrekt, nur die Vorschau log. Jetzt als inertes template plus adoptedStyleSheets. 2. Die Einrückung der Unterseiten in der Seitenliste hat nie funktioniert; sie lief über ein style-Attribut, das dieselbe Richtlinie verwirft. Jetzt über Klassen. 3. Das Theme setzte alle Links auf die Akzentfarbe und überschrieb die kontextabhängigen Farben: 3,08:1 auf dunklem Grund. Die Regel gilt jetzt nur für Links in Textblöcken, die voreingestellte Linkfarbe liegt auf allen hellen Tönen über 4,5:1. Geprüft: 155 PHP-Tests / 579 Assertions unter PHP 8.2.12 und 8.4.22, 15 JavaScript-Modelltests, vollständiger Editortest mit axe (null Verstöße) und Lighthouse 13.4.1 mit je drei Läufen mobil und Desktop, alle fünf Kategorien 100. Offen: npm run test:browser schlägt wegen eines Inhalts fehl - /grundlagen trägt einen im Editor gesetzten Textblock mit 2,75:1. Nicht verändert, weil es eine Nutzerentscheidung ist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
121 lines
9.0 KiB
Twig
121 lines
9.0 KiB
Twig
{% extends 'admin/base.html.twig' %}
|
|
{% block title %}Deine Seiten{% endblock %}
|
|
{% block body %}
|
|
<header class="admin-header">
|
|
<a class="brand" href="{{ path('admin_pages') }}"><span class="brand-icon" aria-hidden="true">k.</span>kant<span>cms</span></a>
|
|
<div class="header-actions"><a href="/">Website ansehen ↗</a><span class="user-name">{{ app.user.userIdentifier }}</span><form action="{{ path('admin_logout') }}" method="post"><input type="hidden" name="_csrf_token" value="{{ csrf_token('logout') }}"><button class="subtle" type="submit">Abmelden</button></form></div>
|
|
</header>
|
|
<main id="admin-main" class="pages-main" tabindex="-1">
|
|
<p class="eyebrow">DEINE WEBSITE</p><h1>Gute Inhalte beginnen hier.</h1>
|
|
<p class="muted">Wähle eine Seite. Gestalte deinen Entwurf. Veröffentliche, wenn alles passt.</p>
|
|
<p class="admin-links"><a class="secondary" href="{{ path('admin_media') }}">Medienverwaltung</a> <a class="secondary" href="{{ path('admin_menus') }}">Menüs</a> <a class="secondary" href="{{ path('admin_forms') }}">Formulare</a> <a class="secondary" href="{{ path('admin_theme') }}">Design</a> <a class="secondary" href="{{ path('admin_settings') }}">Einstellungen</a> <a class="secondary" href="/beitraege">Beitragsübersicht ↗</a></p>
|
|
|
|
{% for type in ['error', 'success'] %}{% for message in app.flashes(type) %}
|
|
<p class="notice {{ type == 'error' ? 'error' : 'ok' }}" role="{{ type == 'error' ? 'alert' : 'status' }}">{{ message }}</p>
|
|
{% endfor %}{% endfor %}
|
|
|
|
{% if is_granted('ROLE_EDITOR') %}
|
|
<details class="create-panel"{% if app.flashes is not empty %} open{% endif %}>
|
|
<summary>Neue Seite oder neuen Beitrag anlegen</summary>
|
|
<form action="{{ path('admin_create') }}" method="post" class="create-form">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('pages') }}">
|
|
<div class="create-grid">
|
|
<div>
|
|
<label for="new-title">Titel</label>
|
|
<input id="new-title" name="title" maxlength="160" required autocomplete="off">
|
|
<p class="field-help">Wird als Überschrift und als Seitentitel übernommen.</p>
|
|
</div>
|
|
<div>
|
|
<label for="new-slug">Adresse</label>
|
|
<input id="new-slug" name="slug" maxlength="80" pattern="[a-z0-9]+(-[a-z0-9]+)*" autocomplete="off" aria-describedby="new-slug-help">
|
|
<p class="field-help" id="new-slug-help">Kleinbuchstaben, Ziffern und Bindestriche, zum Beispiel <code>ueber-uns</code>. Leer lassen, um sie aus dem Titel zu bilden.</p>
|
|
</div>
|
|
<div>
|
|
<label for="new-type">Art</label>
|
|
<select id="new-type" name="type">
|
|
<option value="page">Seite</option>
|
|
<option value="post">Beitrag</option>
|
|
</select>
|
|
<p class="field-help">Beiträge sind für Blogartikel gedacht und haben keine Unterseiten.</p>
|
|
</div>
|
|
<div>
|
|
<label for="new-parent">Übergeordnete Seite</label>
|
|
<select id="new-parent" name="parent_id">
|
|
<option value="">— keine, direkt unter der Startseite —</option>
|
|
{% for parent in parents %}<option value="{{ parent.id }}">{{ parent.title }} (/{{ parent.path }})</option>{% endfor %}
|
|
</select>
|
|
<p class="field-help">Bestimmt die Adresse, zum Beispiel <code>/leistungen/webdesign</code>.</p>
|
|
</div>
|
|
</div>
|
|
<button class="primary" type="submit">Anlegen und bearbeiten</button>
|
|
</form>
|
|
</details>
|
|
{% endif %}
|
|
|
|
<div class="section-heading"><h2>Seiten <span class="count">{{ pages|length }}</span></h2><span class="muted">Entwürfe bleiben bis zur Veröffentlichung privat.</span></div>
|
|
<div class="page-list">
|
|
{% for page in pages %}
|
|
{# A class, not a style attribute: the administration forbids inline styles, so an attribute
|
|
here would simply be dropped and the hierarchy would look flat. #}
|
|
<article class="page-card depth-{{ min(page.depth, 3) }}">
|
|
<div class="page-icon" aria-hidden="true">{{ page.type == 'post' ? '✎' : '▤' }}</div>
|
|
<div class="page-info">
|
|
<h3><a href="{{ path('admin_editor', {id: page.id}) }}">{{ page.title }}</a></h3>
|
|
<p>/{{ page.path == 'home' ? '' : page.path }} <span>· {{ page.locale|upper }} · {{ page.type == 'post' ? 'Beitrag' : 'Seite' }}</span></p>
|
|
</div>
|
|
<span class="badge {{ page.published_revision_id == page.draft_revision_id ? 'published' : '' }}">{{ page.published_revision_id is null ? 'Nicht veröffentlicht' : (page.published_revision_id == page.draft_revision_id ? 'Veröffentlicht' : 'Entwurf geändert') }}</span>
|
|
<a class="secondary" href="{{ path('admin_editor', {id: page.id}) }}" aria-label="{{ page.title }} bearbeiten">Bearbeiten <span aria-hidden="true">→</span></a>
|
|
{% if is_granted('ROLE_EDITOR') %}
|
|
<details class="meta-panel">
|
|
<summary aria-label="Seitendaten von {{ page.title }}">{{ page.type == 'post' ? 'Beitragsdaten' : 'Seitendaten' }}</summary>
|
|
<form action="{{ path('admin_post_meta', {id: page.id}) }}" method="post">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('pages') }}">
|
|
<input type="hidden" name="expectedVersion" value="{{ page.version }}">
|
|
<label for="robots-{{ page.id }}">Suchmaschinen</label>
|
|
<select id="robots-{{ page.id }}" name="robots">
|
|
<option value="index"{{ page.robots != 'noindex' ? ' selected' : '' }}>Darf indexiert werden</option>
|
|
<option value="noindex"{{ page.robots == 'noindex' ? ' selected' : '' }}>Nicht indexieren</option>
|
|
</select>
|
|
<p class="field-help">Nicht indexierte Seiten erscheinen auch nicht in der Sitemap.</p>
|
|
<label for="og-{{ page.id }}">Vorschaubild beim Teilen</label>
|
|
<input id="og-{{ page.id }}" name="ogMediaId" maxlength="32" value="{{ page.og_media_id }}" placeholder="ID aus der Mediathek">
|
|
{% if page.type == 'post' %}
|
|
<label for="date-{{ page.id }}">Veröffentlicht am</label>
|
|
<input id="date-{{ page.id }}" name="publishedAt" type="date" value="{{ page.published_at ? page.published_at|date('Y-m-d') : '' }}">
|
|
<label for="author-{{ page.id }}">Autor</label>
|
|
<input id="author-{{ page.id }}" name="author" maxlength="120" value="{{ page.author }}">
|
|
<label for="excerpt-{{ page.id }}">Auszug</label>
|
|
<textarea id="excerpt-{{ page.id }}" name="excerpt" maxlength="500" rows="3">{{ page.excerpt }}</textarea>
|
|
<p class="field-help">Erscheint in der Übersicht und im Feed. Leer lassen, um die Seitenbeschreibung zu verwenden.</p>
|
|
<label for="categories-{{ page.id }}">Kategorien</label>
|
|
<input id="categories-{{ page.id }}" name="categories" value="{{ page.terms.category }}" placeholder="Projekte, Referenzen">
|
|
<label for="tags-{{ page.id }}">Schlagwörter</label>
|
|
<input id="tags-{{ page.id }}" name="tags" value="{{ page.terms.tag }}" placeholder="WordPress, Migration">
|
|
<p class="field-help">Mehrere durch Komma trennen. Unbekannte Namen werden angelegt.</p>
|
|
<label for="media-{{ page.id }}">Beitragsbild</label>
|
|
<input id="media-{{ page.id }}" name="featuredMediaId" maxlength="32" value="{{ page.featured_media_id }}" placeholder="ID aus der Mediathek">
|
|
{% endif %}
|
|
<button class="primary" type="submit">Speichern</button>
|
|
</form>
|
|
</details>
|
|
{% endif %}
|
|
{% if is_granted('ROLE_ADMIN') %}
|
|
<details class="delete-panel">
|
|
<summary aria-label="{{ page.title }} löschen">Löschen</summary>
|
|
<form action="{{ path('admin_delete', {id: page.id}) }}" method="post">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('pages') }}">
|
|
<input type="hidden" name="expectedVersion" value="{{ page.version }}">
|
|
<p>Löscht „{{ page.title }}“ mit allen gespeicherten Ständen. Das lässt sich nicht rückgängig machen.</p>
|
|
<button class="secondary danger-text" type="submit">Endgültig löschen</button>
|
|
</form>
|
|
</details>
|
|
{% endif %}
|
|
</article>
|
|
{% else %}
|
|
<div class="empty-state"><h3>Noch keine Seiten vorhanden.</h3><p>Lege oben deine erste Seite an.</p></div>
|
|
{% endfor %}
|
|
</div>
|
|
<p class="footnote">Kant CMS · Visueller Editor, erster Prototyp</p>
|
|
</main>
|
|
{% endblock %}
|