SITE_ORIGIN alone decides whether the site is www or not: CanonicalHostSubscriber sends the other spelling there with 301, cms:site:origin writes the setting, the system screen shows it. The roles now match the role help (editor: drafts, publisher: live content, admin: design, settings, users, system). The installer no longer reopens on an unreachable database, the session cookie follows ADMIN_PATH, percent-encoded credentials no longer break the container, consent only accepts same-site decisions, JSON-LD is HTML-safe, own CSS stays in its block, plus the editor, updater, download and public-output findings from the full review (ADR 099, ADR 100). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
102 lines
7.9 KiB
Twig
102 lines
7.9 KiB
Twig
{# The frame around every management screen. One sidebar, always the same, so each area is one
|
|
click away and the current one is visible. Screens fill in heading, lead, actions and content. #}
|
|
{% extends 'admin/base.html.twig' %}
|
|
{% block body_class %}admin-shell{% endblock %}
|
|
{# Solange jemand hier arbeitet, laeuft der Aufgabenplan - auch auf einer Website ohne Besucher.
|
|
Das Skript fragt im Minutentakt an; was faellig ist, entscheidet der Server. #}
|
|
{% block worker %}
|
|
<meta name="kant-tick" content="{{ path('admin_tasks_tick') }}">
|
|
<meta name="kant-tick-token" content="{{ csrf_token('tasks-tick') }}">
|
|
<script src="/assets/task-worker.js?v={{ kant_version() }}" defer></script>
|
|
{% endblock %}
|
|
{% block body %}
|
|
<header class="shell-nav">
|
|
<a class="brand" href="{{ path('admin_pages') }}"><span class="brand-icon" aria-hidden="true">k.</span>kant<span>sites</span></a>
|
|
{# Auf dem Telefon ein Klappmenue, am Schreibtisch die gewohnte Spalte. Ein details-Element
|
|
kann beides, ohne eine Zeile JavaScript: die Zusammenfassung wird auf grossen Bildschirmen
|
|
ausgeblendet und der Inhalt dauerhaft gezeigt. Zwoelf Eintraege in einer Wischleiste zu
|
|
verstecken war die schlechtere Loesung - man sieht nie, was man nicht trifft. #}
|
|
<details class="shell-menu">
|
|
<summary>
|
|
<svg class="shell-menu-icon" viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 6h16M4 12h16M4 18h16"/></svg>
|
|
<span class="shell-menu-label">{{ {
|
|
'pages': 'Seiten', 'posts': 'Beiträge', 'partials': 'Bereiche', 'menus': 'Menüs', 'media': 'Mediathek',
|
|
'forms': 'Formulare', 'statistics': 'Statistik', 'theme': 'Design',
|
|
'settings': 'Einstellungen', 'system': 'System', 'tasks': 'Aufgaben', 'users': 'Benutzer',
|
|
'account': 'Mein Konto'
|
|
}[nav|default('')]|default('Verwaltung') }}</span>
|
|
</summary>
|
|
<nav aria-label="Verwaltung">
|
|
<p class="nav-label" id="nav-content">INHALT</p>
|
|
<ul class="nav-list" aria-labelledby="nav-content">
|
|
{% set entries = [
|
|
{id: 'pages', route: 'admin_pages', icon: 'pages', label: 'Seiten'},
|
|
{id: 'posts', route: 'admin_posts', icon: 'pages', label: 'Beiträge'},
|
|
{id: 'partials', route: 'admin_partials', icon: 'partials', label: 'Bereiche', role: 'ROLE_PUBLISHER'},
|
|
{id: 'menus', route: 'admin_menus', icon: 'menus', label: 'Menüs', role: 'ROLE_PUBLISHER'},
|
|
{id: 'media', route: 'admin_media', icon: 'media', label: 'Mediathek'},
|
|
{id: 'forms', route: 'admin_forms', icon: 'forms', label: 'Formulare', role: 'ROLE_PUBLISHER'},
|
|
{id: 'statistics', route: 'admin_statistics', icon: 'statistics', label: 'Statistik'}
|
|
] %}
|
|
{% for entry in entries %}{% if entry.role is not defined or is_granted(entry.role) %}
|
|
<li><a class="nav-item" href="{{ path(entry.route) }}"{{ nav|default('') == entry.id ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: entry.icon} only %}{{ entry.label }}</a></li>
|
|
{% endif %}{% endfor %}
|
|
</ul>
|
|
<p class="nav-label" id="nav-site">WEBSITE</p>
|
|
<ul class="nav-list" aria-labelledby="nav-site">
|
|
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_theme') }}"{{ nav|default('') == 'theme' ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: 'theme'} only %}Design</a></li>{% endif %}
|
|
{% if is_granted('ROLE_PUBLISHER') %}<li><a class="nav-item" href="{{ path('admin_settings') }}"{{ nav|default('') == 'settings' ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: 'settings'} only %}Einstellungen</a></li>{% endif %}
|
|
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_system') }}"{{ nav|default('') == 'system' ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: 'system'} only %}System</a></li>{% endif %}
|
|
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_tasks') }}"{{ nav|default('') == 'tasks' ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: 'tasks'} only %}Aufgaben</a></li>{% endif %}
|
|
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_users') }}"{{ nav|default('') == 'users' ? ' aria-current="page"' : '' }}>{% include 'admin/icon.html.twig' with {name: 'users'} only %}Benutzer</a></li>{% endif %}
|
|
<li><a class="nav-item nav-out" href="/">{% include 'admin/icon.html.twig' with {name: 'external'} only %}Website ansehen</a></li>
|
|
</ul>
|
|
</nav>
|
|
</details>
|
|
{# Der Fuss der Leiste: eine Kontokarte mit Namen und Rolle, daneben der Abmeldeknopf als
|
|
Symbol mit Beschriftung fuer Vorlesesoftware, darunter die Fassung. Vorher standen drei
|
|
Zeilen lose untereinander - Fassung, Name, ein grauer Knopf; jetzt ist es eine Einheit. #}
|
|
<div class="shell-foot">
|
|
<div class="shell-account">
|
|
<a class="shell-user" href="{{ path('admin_account') }}" aria-label="Mein Konto: {{ app.user.userIdentifier }}"{{ nav|default('') == 'account' ? ' aria-current="page"' : '' }}>
|
|
<span class="shell-avatar" aria-hidden="true">{{ app.user.userIdentifier|first|upper }}</span>
|
|
<span class="shell-user-text"><strong>{{ app.user.userIdentifier }}</strong><span>{{ is_granted('ROLE_ADMIN') ? 'Administrator' : (is_granted('ROLE_PUBLISHER') ? 'Veröffentlicher' : 'Redakteur') }}</span></span>
|
|
</a>
|
|
<form action="{{ path('admin_logout') }}" method="post">
|
|
<input type="hidden" name="_csrf_token" value="{{ csrf_token('logout') }}">
|
|
<button class="shell-logout" type="submit" title="Abmelden">{% include 'admin/icon.html.twig' with {name: 'logout'} only %}<span class="visually-hidden">Abmelden</span></button>
|
|
</form>
|
|
</div>
|
|
{% set updateVersion = is_granted('ROLE_ADMIN') ? kant_update_version() : null %}
|
|
<p class="shell-version">
|
|
{% if is_granted('ROLE_ADMIN') %}<a href="{{ path('admin_system') }}">KantSites {{ kant_version() }}</a>{% else %}<span>KantSites {{ kant_version() }}</span>{% endif %}
|
|
{% if updateVersion %}<a class="version-update" href="{{ path('admin_system', {pruefen: 1}) }}" aria-label="Update {{ updateVersion }} verfügbar. Aktualisierung öffnen.">Update {{ updateVersion }} verfügbar</a>{% endif %}
|
|
</p>
|
|
</div>
|
|
</header>
|
|
<main id="admin-main" class="shell-main" tabindex="-1">
|
|
<div class="shell-top">
|
|
<div class="shell-heading">
|
|
<h1>{% block heading %}Verwaltung{% endblock %}</h1>
|
|
<p class="muted">{% block lead %}{% endblock %}</p>
|
|
</div>
|
|
<div class="shell-actions">{% block actions %}{% endblock %}</div>
|
|
</div>
|
|
<div class="shell-body stack">
|
|
{# Offene Handlungen, auf jedem Bildschirm gleich: fehlende Schriften, eine neue Fassung.
|
|
Sie stehen hier statt nur auf ihrer eigenen Seite - einen Hinweis findet nur, wer schon
|
|
weiss, wo er steht. Jeder traegt den Knopf, der ihn erledigt. #}
|
|
{% for hinweis in kant_admin_notices() %}{% if not hinweis.admin or is_granted('ROLE_ADMIN') %}
|
|
<p class="shell-notice" role="status">
|
|
<span>{{ hinweis.text }}</span>
|
|
<a class="secondary" href="{{ path(hinweis.route) }}">{{ hinweis.action }} →</a>
|
|
</p>
|
|
{% endif %}{% endfor %}
|
|
{% 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 %}
|
|
{% block content %}{% endblock %}
|
|
</div>
|
|
</main>
|
|
{% endblock %}
|