tietge / silverstripe-element-toolbar
Werkzeugleiste für Elemental-Blöcke im Frontend: Sprung ins CMS und Stufenfelder (z. B. Abstände) direkt am Block stellen – mit sofortiger Vorschau und Speichern im Hintergrund.
Package info
git.innomedia.de/Tietge/silverstripe-element-toolbar
Type:silverstripe-vendormodule
pkg:composer/tietge/silverstripe-element-toolbar
Requires
- php: ^8.3
- dnadesign/silverstripe-elemental: ^6
- silverstripe/framework: ^6
- silverstripe/versioned: ^3
Requires (Dev)
None
Suggests
- tractorcow/silverstripe-fluent: Wird erkannt: Der Endpunkt schreibt dann im Zustand der Standardsprache.
Provides
None
Conflicts
None
Replaces
None
README
Eine Werkzeugleiste für Elemental-Blöcke im Frontend (Silverstripe CMS 6). Angemeldete Redakteure sehen beim Überfahren eines Blocks eine Marke mit Blocktyp, Titel und dem Sprung in den Block im CMS – und je konfigurierter Steuerung einen Stepper an der Kante des Blocks, mit dem sich ein Stufenfeld (typisch: der Abstand oben/unten) direkt am Block stellen lässt. Die Änderung ist sofort zu sehen und wird im Hintergrund gespeichert; ein getöntes Band zeigt beim Überfahren die Fläche, die gerade verstellt wird.
Dazu ein Dock unten links, das beim Scrollen mitläuft: ein Schalter blendet alle Werkzeuge aus (für die Vorführung beim Kunden, ohne sich abzumelden) und verlinkt die Seite im CMS.
Besucher ohne Anmeldung bekommen nichts davon: kein Markup, kein Skript, kein Stylesheet.
Installation
composer require tietge/silverstripe-element-toolbar
vendor/bin/sake flush
composer vendor-expose
Wird das Modul in einem Projekt als Path-Repo mit symlink: false eingebunden (etwa unter
packages/), leitet Composer die Version aus dem Git-Stand ab. Unkommittierte Änderungen am
Paket kopiert composer update deshalb nicht nach vendor/ – dafür
composer reinstall tietge/silverstripe-element-toolbar und danach flush.
Es wird keine Datenbankspalte ergänzt. Das Modul hängt ToolbarExtension an BaseElement und
registriert die Route _element-toolbar/set (nur POST, CSRF-Token, canEdit() je Block). Die
Leiste trägt die Adresse absolut in data-endpoint – ein <base>-Tag ist nicht nötig.
Einbau im Projekt (drei Stellen)
1. Der ElementHolder. Im überschriebenen templates/DNADesign/Elemental/Layout/ElementHolder.ss
die Leiste als letztes Kind des Holders einfügen:
<div class="element $SimpleClassName.LowerCase …" id="$Anchor">
$Element
<% if $ElementToolbarActive && $Element.canEdit %>
<% include Tietge\ElementToolbar\Toolbar %>
<% end_if %>
</div>
Der Holder liegt außerhalb jedes <% cached %>-Blocks – nur deshalb darf das Markup vom
Anmeldezustand abhängen. Die Leiste gehört nie in ein Element-Template.
2. Das Dock in Page.ss, ebenfalls außerhalb jedes Cache-Blocks, z. B. vor </body>:
<% if $ElementToolbarActive %><% include Tietge\ElementToolbar\Dock %><% end_if %>
3. Die Steuerungen. Das Modul weiß nicht, welche Felder ein Block hat. Eine Extension am
Block (oder der Block selbst über provideToolbarControls(): array) liefert Control-Objekte:
use Tietge\ElementToolbar\Control;
public function updateToolbarControls(array &$controls): void
{
$controls[] = new Control(
field: 'SpacingTop',
label: 'Abstand oben',
short: 'Oben',
position: Control::TOP,
options: [
'none' => ['title' => 'Kein Abstand', 'class' => 'space-top-none'],
'sm' => ['title' => 'Klein', 'class' => 'space-top-sm'],
'md' => ['title' => 'Mittel', 'class' => 'space-top-md'],
],
);
}
optionsist geordnet – der Stepper geht die Reihenfolge rauf und runter.classist die Klasse, die der Holder für diesen Wert trägt. Das Skript tauscht sie sofort, der Server bestätigt nach dem Speichern mitgetToolbarClasses(). Wer die Klasse im Holder ausgibt, ist Sache des Projekts (z. B.$SpacingClass).- Je Kante (
top,bottom) eine Steuerung; zwei an derselben Kante lägen übereinander. - Die Felder sollten Enums sein (mit Fluent nicht lokalisiert).
Der Titel in der Marke ist $Title; wer Auszeichnungen entfernen will (TextFormatter o. ä.),
hängt updateToolbarTitle(string &$title) an dieselbe Extension.
Marke auf halber Höhe
Liegt über der oberen Kante eines Blocks etwas Fremdes – ein absolut gesetzter Header über dem Hero –, ist die Marke rechts oben verdeckt und nicht anklickbar. Solche Blöcke setzen sie an den rechten Rand auf halbe Höhe:
HeroElement:
toolbar_badge_position: middle # Vorgabe: corner
Im Einzelfall entscheidet der Hook updateToolbarBadgePosition(string &$position).
Entwurf oder Live
Redakteure sehen im Frontend die Live-Fassung. Deshalb gilt (publish_when_clean: true):
Hatte der Block keine unveröffentlichten Änderungen, wird er nach dem Schreiben veröffentlicht
– der Wert ist live, so wie er auf dem Bildschirm steht. Hatte er welche, bleibt es beim Entwurf
und die Marke sagt „Nur im Entwurf gespeichert". Alles andere hieße, fremde, halb fertige
Änderungen mit zu veröffentlichen. false schreibt immer nur den Entwurf.
Mit tractorcow/silverstripe-fluent schreibt der Endpunkt im Zustand der Standardsprache.
Konfiguration
Tietge\ElementToolbar\State:
permission_code: 'CMS_ACCESS' # wer die Werkzeuge sieht (je Block zusätzlich canEdit)
storage_key: 'element-toolbar' # localStorage-Schlüssel des Schalters „Bearbeitungshilfen"
Tietge\ElementToolbar\ToolbarController:
publish_when_clean: true
Aussehen
Das CSS (client/css/element-toolbar.css) ist absichtlich ungelayert und ohne Framework:
Werkzeuge müssen gegen jedes Projekt-CSS gewinnen. Farben sind bewusst die des CMS (Blau
#0071c4, Grün #008a00) und nicht die des Auftritts – alles Blaue ist Werkzeug. Anpassbar
über Custom Properties auf :root des Projekts (das Modul deklariert dort nichts, ein
Projektwert gewinnt immer):
:root {
--element-toolbar-color: #0071c4;
--element-toolbar-on: #008a00;
--element-toolbar-text: #fff;
--element-toolbar-inset: 10px; /* Abstand der Marken zur Blockkante */
--element-toolbar-ease: cubic-bezier(0.22, 1, 0.36, 1);
--element-toolbar-duration: 500ms; /* Polster- und Bandhöhe */
--element-toolbar-z: 40;
--element-toolbar-font: system-ui, sans-serif;
--element-toolbar-radius: 10px; /* Feld des Docks */
--element-toolbar-dock-bottom: 10px; /* Abstand des Docks zum unteren Rand; Fallback: inset.
Für Projekte mit einer mobilen Leiste am unteren Rand */
}
Wird ein Block schmal (Telefon, halbbreiter Block), teilen sich Marke und oberer Stepper die Kante: Unter 52rem Blockbreite fällt der Titel aus der Marke, unter 40rem rückt der Stepper an die linke Kante – Container-Queries am Block, nicht am Fenster.
Zustände hängen an Attributen: .element:hover blendet die Leiste ein, data-open am Dock
öffnet das Feld, aria-checked am Schalter, data-element-toolbar="hidden" am <html>
blendet alles aus. Das Dock ist auf allen Breiten da; wer unten eine mobile Leiste hat, hebt es
mit --element-toolbar-dock-bottom darüber.
Was das Modul voraussetzt
- Der Holder trägt die Klasse
element(Elemental-Standard) und ist das Element, an dem die Klassen der Steuerungen stehen. Er bekommtposition: relative, sobald er eine Leiste enthält. - Icons (Hugeicons, MIT) liegen inline in den Templates – kein Icon-System nötig.
Bekannte Eigenheit von Silverstripe
Requirements setzt Skripte vor das erste <script> im Body, nicht ans Ende – sobald eine
Seite ein Inline-Script mitbringt (Karten, Einbettungen), läuft element-toolbar.js mitten im
Dokument. Das Skript ist deshalb so gebaut, dass es nichts beim Laden festhält: Dock und Leisten
werden bei jedem Ereignis gesucht, der gemerkte Schalterzustand wird nach DOMContentLoaded
hergestellt. Ein Projekt braucht dafür kein Requirements::set_force_js_to_bottom(true).
Lizenz
BSD-3-Clause.