Fassung 1.4.3: Update ohne Git, Verwaltung auf dem Telefon, eigene Symbole
Vier Wuensche und ein Fund beim Nachmessen. - Farbwaehler im Designbereich. Das Textfeld bleibt massgeblich, damit "@akzent" ein Verweis auf die Palette bleibt und nicht beim ersten Klick zu einem festen Hexwert erstarrt (ADR 066). - Die Verwaltung auf dem Telefon: das Menue klappt unter 760 px zu und nennt den Bereich, in dem man steht; Ziele 44 px, kein Skript. - Eigene SVG-Symbole statt Unicode-Zeichen - ein Telefon ersetzt die sonst durch Emoji in fremdem Stil. Zwei Eintraege trugen dabei denselben Pfad; ein Test findet beides (ADR 067). - Update aus dem Releasearchiv: der Weg fuer Installationen ohne Git. Zwei Schritte, weil ein Prozess, der sich selbst ausgetauscht hat, mit dem alten Code weiterarbeitet. var/, .env, hochgeladene Schriften und das erzeugte Buendel bleiben unberuehrt; public/ wird dazu Datei fuer Datei zusammengefuehrt. Geprueft ohne Netz gegen eine Releaseseite im Speicher (ADR 068). Beim Nachmessen der echten Startseite statt einer Testseite: das groesste Element ueber der Falz ist dort kein <img>, sondern der Hintergrund des ersten Abschnitts - und der steht nur im Stylesheet. Drei Vorabverweise mit denselben Geraetegrenzen wie der Compiler holen genau die Variante, die die Regel auch nimmt. LCP 1,3 s -> 1,1 s. Dabei gefunden: dasselbe Bild wurde je Geraeteregel neu abgefragt - Startseite 19 -> 15 Abfragen (ADR 069). Geprueft: 428 Tests auf PHP 8.2 und 8.4, Editor-, Browser- und Linkpruefung, Lighthouse auf drei echten Seiten (alles ausser der Leistung der Startseite 100). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea3dd4ddf2
commit
d6d476177a
26 files changed
+1740
-26
No files matched your search
@@ -7,6 +7,7 @@
|
||||
<title>{% block title %}Verwaltung{% endblock %} · KantSites</title>
|
||||
{% include 'favicon.html.twig' %}
|
||||
<link rel="stylesheet" href="/assets/admin.css?v={{ kant_version() }}">
|
||||
<script src="/assets/color-field.js?v={{ kant_version() }}" defer></script>
|
||||
{% block styles %}{% endblock %}
|
||||
{% block scripts %}{% endblock %}
|
||||
</head>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{# Die Symbole der Verwaltung, als Inline-SVG.
|
||||
|
||||
Vorher standen hier Unicode-Zeichen (▤ ◫ ☰ ▩ ⚙). Auf dem Schreibtisch sehen sie wie Symbole
|
||||
aus, auf einem Telefon ersetzt das Betriebssystem sie durch Emoji - buntes Zahnrad, buntes
|
||||
Briefsymbol, jedes in einem anderen Stil. Eigene Pfade lösen das endgültig: eine Linienstärke,
|
||||
eine Farbe (currentColor), kein zusätzlicher Request, keine Schriftdatei. #}
|
||||
{% set paths = {
|
||||
pages: 'M6 3h8l4 4v14H6Zm8 0v4h4M9 12h6M9 16h6',
|
||||
partials: 'M4 5h16v14H4Zm0 5h16M9 10v9',
|
||||
menus: 'M4 6h16M4 12h16M4 18h10',
|
||||
media: 'M4 5h16v14H4Zm0 10 4-4 4 4 3-3 5 5M8.5 9.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z',
|
||||
forms: 'M3 6h18v12H3Zm0 1 9 7 9-7',
|
||||
statistics: 'M4 20h16M7 20v-6m5 6V6m5 14v-9',
|
||||
theme: 'M12 3a9 9 0 1 0 0 18Zm0 0a9 9 0 0 1 0 18',
|
||||
settings: 'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Zm8-3a8 8 0 0 0-.2-1.7l2-1.5-2-3.5-2.3 1a8 8 0 0 0-3-1.7L14 2h-4l-.5 2.6a8 8 0 0 0-3 1.7l-2.3-1-2 3.5 2 1.5a8 8 0 0 0 0 3.4l-2 1.5 2 3.5 2.3-1a8 8 0 0 0 3 1.7L10 22h4l.5-2.6a8 8 0 0 0 3-1.7l2.3 1 2-3.5-2-1.5c.1-.6.2-1.1.2-1.7Z',
|
||||
system: 'M8 8h8v8H8Zm-4 2h4m-4 4h4m8-4h4m-4 4h4M10 4v4m4-4v4m-4 8v4m4-4v4',
|
||||
users: 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-8 8c0-3.3 3.6-5 8-5s8 1.7 8 5',
|
||||
external: 'M14 4h6v6m0-6-8 8M18 14v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h5'
|
||||
} %}
|
||||
<svg class="nav-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="{{ paths[name]|default(paths.pages) }}"/></svg>
|
||||
@@ -109,11 +109,11 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="scrollTopColor">Farbe</label>
|
||||
<input id="scrollTopColor" name="scrollTopColor" value="{{ settings.scrollTopColor }}" placeholder="@accent">
|
||||
<input id="scrollTopColor" name="scrollTopColor" value="{{ settings.scrollTopColor }}" placeholder="@accent" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="scrollTopInk">Farbe des Pfeils</label>
|
||||
<input id="scrollTopInk" name="scrollTopInk" value="{{ settings.scrollTopInk }}" placeholder="#ffffff">
|
||||
<input id="scrollTopInk" name="scrollTopInk" value="{{ settings.scrollTopInk }}" placeholder="#ffffff" data-color data-palette="{{ palette|json_encode|e('html_attr') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="scrollTopLabel">Beschriftung für Vorlesesoftware</label>
|
||||
|
||||
@@ -5,31 +5,46 @@
|
||||
{% 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', 'partials': 'Bereiche', 'menus': 'Menüs', 'media': 'Mediathek',
|
||||
'forms': 'Formulare', 'statistics': 'Statistik', 'theme': 'Design',
|
||||
'settings': 'Einstellungen', 'system': 'System', '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: '▤', label: 'Seiten'},
|
||||
{id: 'partials', route: 'admin_partials', icon: '◫', label: 'Bereiche'},
|
||||
{id: 'menus', route: 'admin_menus', icon: '☰', label: 'Menüs'},
|
||||
{id: 'media', route: 'admin_media', icon: '▩', label: 'Mediathek'},
|
||||
{id: 'forms', route: 'admin_forms', icon: '✉', label: 'Formulare'},
|
||||
{id: 'statistics', route: 'admin_statistics', icon: '▁', label: 'Statistik'}
|
||||
{id: 'pages', route: 'admin_pages', icon: 'pages', label: 'Seiten'},
|
||||
{id: 'partials', route: 'admin_partials', icon: 'partials', label: 'Bereiche'},
|
||||
{id: 'menus', route: 'admin_menus', icon: 'menus', label: 'Menüs'},
|
||||
{id: 'media', route: 'admin_media', icon: 'media', label: 'Mediathek'},
|
||||
{id: 'forms', route: 'admin_forms', icon: 'forms', label: 'Formulare'},
|
||||
{id: 'statistics', route: 'admin_statistics', icon: 'statistics', label: 'Statistik'}
|
||||
] %}
|
||||
{% for entry in entries %}
|
||||
<li><a class="nav-item" href="{{ path(entry.route) }}"{{ nav|default('') == entry.id ? ' aria-current="page"' : '' }}><span class="nav-icon" aria-hidden="true">{{ entry.icon }}</span>{{ entry.label }}</a></li>
|
||||
<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>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<p class="nav-label" id="nav-site">WEBSITE</p>
|
||||
<ul class="nav-list" aria-labelledby="nav-site">
|
||||
<li><a class="nav-item" href="{{ path('admin_theme') }}"{{ nav|default('') == 'theme' ? ' aria-current="page"' : '' }}><span class="nav-icon" aria-hidden="true">◐</span>Design</a></li>
|
||||
<li><a class="nav-item" href="{{ path('admin_settings') }}"{{ nav|default('') == 'settings' ? ' aria-current="page"' : '' }}><span class="nav-icon" aria-hidden="true">⚙</span>Einstellungen</a></li>
|
||||
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_system') }}"{{ nav|default('') == 'system' ? ' aria-current="page"' : '' }}><span class="nav-icon" aria-hidden="true">⌬</span>System</a></li>{% endif %}
|
||||
{% if is_granted('ROLE_ADMIN') %}<li><a class="nav-item" href="{{ path('admin_users') }}"{{ nav|default('') == 'users' ? ' aria-current="page"' : '' }}><span class="nav-icon" aria-hidden="true">☺</span>Benutzer</a></li>{% endif %}
|
||||
<li><a class="nav-item nav-out" href="/">Website ansehen</a></li>
|
||||
<li><a class="nav-item nav-out" href="/beitraege">Beitragsübersicht</a></li>
|
||||
<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>
|
||||
<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>
|
||||
{% 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_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>
|
||||
<li><a class="nav-item nav-out" href="/beitraege">{% include 'admin/icon.html.twig' with {name: 'external'} only %}Beitragsübersicht</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</details>
|
||||
<div class="shell-foot">
|
||||
<p class="shell-version">Fassung {{ kant_version() }}</p>
|
||||
<a class="shell-user" href="{{ path('admin_account') }}"{{ nav|default('') == 'account' ? ' aria-current="page"' : '' }}><span class="shell-avatar" aria-hidden="true">{{ app.user.userIdentifier|first|upper }}</span><strong>{{ app.user.userIdentifier }}</strong></a>
|
||||
|
||||
@@ -63,6 +63,44 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head">
|
||||
<h2>Update aus dem Releasearchiv</h2>
|
||||
<span class="muted">{{ state.git ? 'Alternative zum Weg über Git' : 'der Weg ohne Git' }}</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if archivePending %}
|
||||
<p class="notice">Die Dateien der Fassung <strong>{{ archivePending.fassung }}</strong> liegen bereits (seit {{ archivePending.seit|slice(0, 16)|replace({"T": " "}) }} UTC). <strong>Es fehlen noch die Migrationen</strong> – bis dahin bleibt die Website im Wartungszustand.</p>
|
||||
<form action="{{ path('admin_system_archive') }}" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
|
||||
<input type="hidden" name="finish" value="1">
|
||||
<button class="primary" type="submit">Migrationen ausführen und freigeben →</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<p class="muted">Dieser Weg braucht kein Git: Er lädt das Archiv der neuen Fassung von der Releaseseite, vergleicht die Prüfsumme, sichert Datenbank und Medien, schaltet den Wartungszustand ein und ersetzt die Dateien der Anwendung. <strong>Unberührt bleiben <code>var/</code>, <code>.env</code>, <code>.env.local</code>, die geladenen Schriften und das erzeugte Stylesheet-Bündel.</strong> Der vorherige Stand wird beiseitegelegt, nicht gelöscht.</p>
|
||||
<p class="field-help">Danach folgt ein zweiter Knopf für die Migrationen. Das ist kein Umweg: Erst eine neue Anfrage startet mit dem Code der neuen Fassung – ein Prozess, der sich selbst ausgetauscht hat, arbeitet mit dem alten weiter.</p>
|
||||
{% if archiveVersion %}
|
||||
<p class="notice done">Verfügbar: <strong>{{ archiveVersion }}</strong> (installiert: {{ state.version }}).</p>
|
||||
{% elseif asked %}
|
||||
<p class="field-help">Die Releaseseite nennt nichts Neueres als {{ state.version }}.</p>
|
||||
{% else %}
|
||||
<p class="field-help"><a href="{{ path('admin_system', {pruefen: 1}) }}">Nachsehen, ob es etwas Neues gibt</a> – das fragt die Releaseseite und dauert einen Moment.</p>
|
||||
{% endif %}
|
||||
<form action="{{ path('admin_system_archive') }}" method="post">
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token('system') }}">
|
||||
<div class="fields">
|
||||
<div>
|
||||
<label for="archive-target">Fassung</label>
|
||||
<input id="archive-target" name="target" maxlength="20" placeholder="{{ archiveVersion ?: '1.4.2' }}" pattern="[0-9]+\.[0-9]+\.[0-9]+">
|
||||
<p class="field-help">Leer lassen für die neueste veröffentlichte Fassung.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button class="primary" type="submit">Update aus dem Archiv einspielen →</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-head"><h2>Update einspielen</h2><span class="muted">{{ ready ? 'Vorprüfung bestanden' : 'erst die Vorprüfung erfüllen' }}</span></div>
|
||||
<div class="card-body">
|
||||
|
||||
@@ -96,15 +96,15 @@
|
||||
</div>
|
||||
<div>
|
||||
<label for="base-color">Textfarbe</label>
|
||||
<input id="base-color" name="base[color]" value="{{ theme.base.color }}" placeholder="@ink">
|
||||
<input id="base-color" name="base[color]" value="{{ theme.base.color }}" placeholder="@ink" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="base-bg">Hintergrundfarbe</label>
|
||||
<input id="base-bg" name="base[backgroundColor]" value="{{ theme.base.backgroundColor }}" placeholder="@paper">
|
||||
<input id="base-bg" name="base[backgroundColor]" value="{{ theme.base.backgroundColor }}" placeholder="@paper" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="base-link">Linkfarbe</label>
|
||||
<input id="base-link" name="base[linkColor]" value="{{ theme.base.linkColor }}" placeholder="@accent">
|
||||
<input id="base-link" name="base[linkColor]" value="{{ theme.base.linkColor }}" placeholder="@accent" data-color data-palette="{{ theme.colors|json_encode|e('html_attr') }}">
|
||||
</div>
|
||||
</div>
|
||||
<p class="field-help">Farben entweder als <code>@schlüssel</code> aus der Palette oder als Wert wie <code>#1a2b3c</code>.</p>
|
||||
|
||||
Reference in new issue
Block a user