FluentUI: Editoren und Code-Darstellung
1. Übersicht und Zweck
Status: gemischt — fünf Bausteine [Live] (je eigener Träger,
darunter codeEditor() und htmlSplitEditor()) plus die Editor-/Markup-Umschaltung
(WYSIWYG-Umschaltung, bewusst außerhalb dieser Seite geführt — Träger
wvdsmd), je Abschnitt vermerkt.
Diese Seite ordnet fünf verschiedene Editor-/Code-Darstellungs-Bausteine ein und verlinkt die jeweilige Quelle der Wahrheit — sie ersetzt keine Plugin-Doku. Zielgruppe: jeder Entwickler, der entscheiden muss, welcher Baustein für „Code zeigen„ vs. „Code bearbeiten“ vs. „Wiki-Inhalt bearbeiten„ der richtige ist (Abschnitt 5).
2. Voraussetzungen
- Für
codeEditor()undhtmlSplitEditor(): DokuWiki mit aktiviertem Pluginwkfluentuiundhelper_plugin_wkfluentui_widgetsgeladen. - Für GeSHi: keine — Core-Funktion, immer verfügbar.
- Für den Markdown-Editor: Plugin
wvdsmdaktiviert, Seite trägt den Markdown-Marker (Autodetect).
3. Konzepte
Gemeinsames Muster aller fünf Bausteine: progressive Erweiterung über einer funktionsfähigen Textarea — dieselbe Konvention wie bei den JS-Erweiterungen der Helper-API (kein zweiter Code-Pfad, Markup bleibt das Speicherformat).
| Baustein | Was es ist | Verwendung | Träger / Quelle der Wahrheit | Status |
|---|---|---|---|---|
| GeSHi (Code-Rendering) | Syntax-Highlighting beim Rendern — kein Editor, keine eigene API | <code sql>…</code>/<file> im Wiki-Text und Fenced-Code-Blöcke (```lang) in Markdown-Embeds ({{md>…}} / <markdown>); Sprachkennung = GeSHi-Sprachname | DokuWiki-Core (inc/parser/xhtml.php → vendored GeSHi); Optik über --wk-wiki-code-*-Tokens | umgesetzt (Core) |
| Quellcode-Editor | CodeMirror, progressiv über einer normalen <textarea> — ohne JavaScript bleibt die Textarea voll funktionsfähig | jedes Admin-Modul über codeEditor() (Erstkonsument: SQL-Workbench in wksqliteds) | gemeinsame Kopie unter wkfluentui/cm/ + Widget codeEditor() (Vertrag: Abschnitt 6/7 unten) | umgesetzt |
| Geteilter HTML-Editor | Quelltext und bearbeitbare gerenderte Vorschau nebeneinander; die Quelle bleibt maßgeblich, die Vorschau schreibt neu formatiert zurück | Admin-Flächen, die HTML-Fragmente pflegen (Erstkonsument: Snippet-Verwaltung in wksnippet) | Widget htmlSplitEditor() + scripts/htmlsplit.js, Vorschau in einem Sandbox-iframe (Vertrag: Abschnitt 8/9 unten) | umgesetzt |
| DokuWiki-Standard-Editor | Cores Seiten-Editor: Toolbar (Fett/Link/…) + Textarea + Vorschau, Draft-/Lock-Fluss | jede Wiki-Seite, do=edit | DokuWiki-Core | umgesetzt (Core) |
| Markdown-Editor | vendored EasyMDE ersetzt die Textarea auf Markdown-Seiten; Marker-Zeile unsichtbar, DokuWiki-Save/Draft-Fluss unverändert; No-JS-Fallback = Textarea | Seiten mit Markdown-Marker (Autodetect) | Plugin wvdsmd — Quelle der Wahrheit: Markdown (wvdsmd) | umgesetzt |
4. Erste Schritte
Minimalbeispiel: CodeMirror-Editor über eine bestehende SQL-Textarea aktivieren.
/** @var helper_plugin_wkfluentui_widgets $widgets */
$widgets = plugin_load('helper', 'wkfluentui_widgets');
?>
<textarea id="sql-input" name="sql"></textarea>
<?php echo $widgets->codeEditor('sql-input', 'sql'); ?>
Falsch: codeEditor() auf eine Textarea zeigen lassen, die zum
Zeitpunkt des Aufrufs noch nicht im DOM steht (z. B. Aufruf vor dem
<textarea>-Tag). Das Aktivierungs-Markup verweist per id auf ein
Element, das existieren muss — die Reihenfolge im Beispiel oben (Textarea
zuerst) ist verbindlich.
5. Verwendung: Leitfaden „welcher Baustein wofür"
| Bedarf | Baustein |
|---|---|
| Code im Inhalt zeigen | <code lang> (GeSHi) — nie einen Editor einbetten |
| Code/SQL im Admin-Modul bearbeiten | codeEditor() (ein Aufruf neben der Textarea, kein plugin-lokales Vendoring nötig) |
| HTML-Fragment mit Sichtprüfung bearbeiten | htmlSplitEditor() (umschließt dieselbe Textarea; enthält codeEditor() für die Quellseite) |
| Wiki-Inhalt bearbeiten | Core-Editor; Markdown-Seiten automatisch via wvdsmd |
| Kein Nachbau von Monaco/VS-Code-Editoren | bewusste Grenze wie bei der Layout-Mechanik (layout) |
6. API-Referenz: codeEditor()
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
public function codeEditor(string $textareaId, string $language, array $opts = []): string
Zusammenfassung: Liefert das <script>-freie
Aktivierungs-Markup (data-wk-code-editor-Attribute) für eine
bestehende, benannte Textarea; scripts/codeeditor.js (progressive
Erweiterung) ersetzt sie beim Laden durch eine CodeMirror-Instanz.
Rückgabewert: string — Aktivierungs-Markup. Leerer String bei
unbekannter $language-Kennung oder leerer $textareaId — die Textarea
bleibt in beiden Fällen unangetastet eine gewöhnliche Textarea, kein Fehler.
7. Parameter, Optionen und Zustände
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
$textareaId | string | Ja | id der bestehenden Ziel-Textarea |
$language | string | Ja | eine von sql|php|css|javascript|markdown|html; jede andere Kennung liefert den leeren String |
$opts['submitId'] | string | Nein | id des Submit-Buttons, den Strg+Enter klickt — der Editor synchronisiert vorher in die Textarea |
Instanz-Zugriff (Client): wkCodeEditor.get(textareaId) — null,
solange/falls CodeMirror nicht geladen hat; zur Klick-Zeit auflösen, nicht
beim Seitenaufbau. Event wk-codeeditor-ready feuert zusätzlich an der
Textarea.
Lazy-Load: Vendor-Assets laden nur auf Seiten mit mindestens einem Aktivierungs-Marker nach — kein site-weiter Payload-Anstieg.
Vendoring (begründete Spez-Korrektur): die gemeinsame CodeMirror-Kopie
(5.65.16 + Modi sql/php/css/javascript/markdown/htmlmixed samt Abhängigkeiten
clike/xml/htmlmixed, MIT-Lizenzdatei) liegt unter wkfluentui/cm/ —
nicht im ursprünglich skizzierten vendor/cm/, weil DokuWikis
Web-Schicht direkten Zugriff auf vendor/-Verzeichnisse verweigert (auf
dieser Instanz empirisch verifiziert, 404).
Fehlerverhalten:
| Bedingung | Verhalten |
|---|---|
unbekannte $language-Kennung | Rückgabe leerer String, Textarea bleibt unangetastet |
leere $textareaId | Rückgabe leerer String |
| Ziel-Textarea existiert nicht im DOM | kein PHP-Fehler (das Widget prüft das DOM nicht); scripts/codeeditor.js findet beim Laden kein Ziel und aktiviert nichts — stille clientseitige Degradation |
8. API-Referenz: htmlSplitEditor()
| Metadatum | Wert |
|---|---|
| Sprache | PHP |
| Namespace | helper_plugin_wkfluentui_widgets |
| Datei | lib/plugins/wkfluentui/helper/widgets.php |
| Träger der Interaktion | lib/plugins/wkfluentui/scripts/htmlsplit.js |
| Sichtbarkeit | public |
| Stabilität | stabil (Tier 1) |
public function htmlSplitEditor(string $textareaHtml, string $textareaId, array $opts = []): string
Zusammenfassung: Umschließt die fertige, vom Aufrufer erzeugte
Textarea mit einem geteilten Editor: links der Quelltext (CodeMirror,
Sprache html — derselbe Baustein wie in Abschnitt 6), rechts die
gerenderte Vorschau. Die Vorschau ist bearbeitbar; was dort geändert wird,
landet neu formatiert in der Quelle.
Warum der Aufrufer die Textarea behält: Diese Bibliothek hat bewusst kein nacktes Textarea-Widget — Name, Größe, Pflichtfeld-Status und Label eines Formularfelds gehören zum Bildschirm, nicht zum Entwurfssystem. Das Widget bekommt deshalb fertiges, vom Aufrufer escaptes Markup und legt nur die Umgebung darum. Folge: der abgeschickte POST ist mit und ohne diesen Baustein identisch.
| Parameter | Typ | Pflicht | Beschreibung |
|---|---|---|---|
$textareaHtml | string | Ja | vollständiges <textarea …>-Markup des Aufrufers, von diesem escapt |
$textareaId | string | Ja | deren id; leer ⇒ $textareaHtml wird unverändert zurückgegeben |
$opts['mode'] | string | Nein | Startansicht source|split|preview (Standard split); eine zuvor gespeicherte Benutzeransicht gewinnt |
$opts['editable'] | bool | Nein | false ⇒ Vorschau nur lesbar, Formatiergruppe entfällt vollständig (Standard true) |
$opts['storageKey'] | string | Nein | Namensraum der gespeicherten Ansicht (wk-htmlsplit:<storageKey> in localStorage); leer ⇒ Ansicht wird nicht gemerkt |
$opts['submitId'] | string | Nein | id des Speichern-Knopfs — an codeEditor() weitergegeben, also Strg+Enter |
$opts['lang'] | string | Nein | Inhaltssprache der Vorschau (Standard $conf['lang']) |
Ohne JavaScript: die Werkzeugleiste trägt serverseitig hidden (ihre
Ansichts- und Formatierknöpfe wären sonst tote Bedienelemente), die
Vorschaufläche ist leer und hidden. Übrig bleibt genau die Textarea des
Aufrufers. Alle Knöpfe des Bausteins sind type=„button“, und das
Adressfeld der Verknüpfung trägt kein name — es kann also nichts
mitgeschickt werden.
Vorschau als Sandbox-iframe: sandbox=„allow-same-origin“, ohne
allow-scripts (beide zusammen sind die dokumentierte
Sandbox-Ausnahme und damit keine Absicherung). Das ist notwendig, weil
gepflegte HTML-Fragmente <style> und <script> enthalten
dürfen: inline gerendert würde ein <style> die Admin-Seite ringsum
umstylen. Verifiziert auf dieser Instanz — das <script> eines
Fragments läuft weder im Rahmen noch im Elterndokument, sein
<style> wirkt innerhalb des Rahmens und die Admin-Seite bleibt
messbar unverändert. Die Stylesheets der Wirtsseite werden in das
Vorschaudokument kopiert, damit die Vorschau die echte Kaskade zeigt und
nicht eine handgepflegte Näherung.
Platzhalter-Chips: Plugin-Platzhalter ({{wk:lang>…}},
{{wk:image>…}}) sind kein HTML. In der Vorschau werden sie zu je
einem unteilbaren, nicht bearbeitbaren Chip, der das Token wörtlich
trägt und wörtlich zurückgeschrieben wird — sonst zerlegt ein einzelner
Tastendruck ein Token, oder die Textkodierung macht aus dem < in
{{wk:lang>de:< 24 Stunden}} ein <, womit das Muster des
konsumierenden Plugins nicht mehr passt. Die Chips entstehen aus dem
Quelltext, nicht aus dem geparsten Baum: nur dort existieren die
Rohbytes noch, nur dort ist ein Platzhalter, der selbst Markup enthält
(…Industrie-<br/>erfahrung…), ein zusammenhängendes Token, und nur
dort ist Text- von Attributposition unterscheidbar. Platzhalter in
Attributwerten bleiben unangetastet.
Speicherweg: Die Textarea ist und bleibt das Formularfeld. Eine
Vorschau-Änderung, die beim Klick auf Speichern noch in ihrer
Entprellzeit steckt, wird in der Capture-Phase am document
nachgezogen — vor dem Übertrag von CodeMirror in die Textarea, der am
Formular selbst hängt. Ohne diese Reihenfolge gehen die letzten
Tastendrücke der Vorschau beim Speichern verloren, und zwar stumm.
Erstkonsument: Snippet-Verwaltung (wksnippet,
do=admin&page=wksnippet&edit=<key>).
9. Rundlauf: was eine Vorschau-Bearbeitung an der Quelle ändert
Eine Bearbeitung in der Vorschau bedeutet, dass der Browser das Markup neu serialisiert. Das ist verlustbehaftet und deshalb hier beziffert statt beschwiegen. Wer nur links im Quelltext arbeitet, löst nichts davon aus.
Gemessen über alle 55 Snippet-Dateien dieser Instanz (Quelle → Vorschau → Bearbeitung → Quelle):
| Ergebnis | Dateien |
|---|---|
| Dokument parst gleich (Elemente, Attribute, Text) | 55 / 55 |
| Platzhalter byteweise identisch (430 Stück) | 55 / 55 |
<script>-/<style>-Inhalte byteweise identisch | 55 / 55 |
| Datei als Ganzes byteweise identisch | 28 / 55 |
| Unterschied ausschließlich in der Einrückung | 23 / 55 |
| Unterschied darüber hinaus | 4 / 55 |
Die vier Abweichungen sind darstellungsneutral: Zeichenreferenzen für
druckbare Zeichen werden zum Zeichen selbst (✓ → Häkchen,
» → Guillemet), und der Schrägstrich eines leeren Elements
entfällt (<br/> → <br>).
Rücknahme: Das Zurückschreiben ist eine CodeMirror-Änderung — ein
einzelnes Strg+Z im Quelltextfeld nimmt die komplette Neuformatierung
zurück. Die Werkzeugleiste sagt das auch dort, wo es passiert.
Einrückung: vier Leerzeichen, und ein Element, dessen Kinder alle inline
sind, bleibt einzeilig, solange die Zeile 110 Zeichen nicht überschreitet.
Inhalte von <script>, <style>, <pre> und
<textarea> werden nie umgebrochen oder neu eingerückt — dort
ändert Einrücken die Bedeutung, nicht das Aussehen.
10. Vollständige Beispiele: Editor ↔ Markup-Umschaltung [Live]
Träger wvdsmd (Vollausbau 2026-07-14) —
Quelle: lib/plugins/wvdsmd/script.js.
Auf Markdown-Seiten trägt die EasyMDE-Toolbar einen Umschalter zwischen
Editor-Ansicht (EasyMDE mit Format-Toolbar und Vorschau) und
Markup-Ansicht (die rohe Textarea inklusive sichtbarer Marker-Zeile —
identisch zum No-JS-Zustand). Der Wechsel ist jederzeit in beide
Richtungen möglich und wirkt sofort, ohne Speichern; die zuletzt gewählte
Ansicht wird pro Benutzer gemerkt (localStorage,
wk-editor-view:<login>); jeder Wechsel wird über eine
aria-live-Region angesagt. Eigenschaften:
- Markup bleibt Speicherformat (Markdown) — beide Ansichten editieren dieselbe zugrunde liegende Textarea, es gibt keine Rückserialisierung und damit konstruktionsbedingt verlustfreie Wechsel (byte-gleicher Quelltext, im Verifikationslauf nachgewiesen).
- No-JS-Fallback bleibt die rohe Textarea; der Umschalter existiert nur in der JS-erweiterten Ansicht.
- DokuWiki-Syntax-Seiten behalten den Core-Editor mit seiner Syntax-Toolbar und dem „Vorschau“-Button — dort gibt es nur eine Bearbeitungsansicht, ein Umschalter wäre bedeutungslos.
Vertiefung — konkrete Toolbar-Befehle: zwei Unterseiten dokumentieren, welche DokuWiki-Syntax/Markdown-Tokens die Editor-Toolbars einfügen (EasyMDE für Markdown, Core-Toolbar für DokuWiki-Syntax), nach dem Kategorienschema der DevExpress-ASPxHtmlEditor-Demo-Suite (https://demos.devexpress.com/ASPxHTMLEditorDemos/Default.aspx), aber auf das tatsächliche Ziel-Format dieser Bibliothek übertragen statt generischer HTML-Tags:
- Formatierung — Zeichen-/Absatzformate als Syntax-Tokens
- Tabellen und Medien — Tabellen-, Medien- und Link-Syntax
11. Einschränkungen und Randfälle
- GeSHi bestimmt die Sprachliste; unbekannte Kennungen rendern als Klartext (kein Fehler). In Markdown-Embeds gilt dasselbe:
wvdsdwmdsParsedownRendererhebt Fenced-Blöcke mit bekannter Sprachkennung über DokuWikis GeSHi-Pfad hervor (p_xhtml_cached_geshi()); Blöcke ohne/mit unbekannter Kennung bleiben Parsedown-Roh-<pre>— degradiert, nie verloren. Engine-Kennungparsedown-1.7.4-geshi1invalidiert Alt-Caches. - CodeMirror/EasyMDE sind vendored (offline, keine CDN-Abhängigkeit) — Updates sind bewusste, versionierte Plugin-Änderungen.
- Editor-Flächen liegen im Content-Panel und folgen dem Panel-Füll-Rezept aus datagrid (Zutaten 1–3 gelten identisch für Editoren).
- Kein Nachbau von Monaco/VS-Code-Editoren (Abschnitt 5).
12. Accessibility und Kompatibilität
- Vollständiger Überblick: Accessibility (konsolidiert, nicht hier dupliziert).
- Ohne JavaScript bleibt jeder Baustein eine gewöhnliche, vollständig zugängliche Textarea.
- CodeMirror/EasyMDE sind vendored — Browserkompatibilität folgt den jeweiligen Vendor-Release-Notes (nicht separat in dieser Bibliothek getestet).
13. Troubleshooting
Symptom: codeEditor() rendert nichts (leerer String), obwohl
Textarea und Sprache korrekt aussehen.
Ursache: $language enthält eine Kennung außerhalb von
sql/php/css/javascript/markdown/html (Groß-/Kleinschreibung
beachten — die Prüfung ist exakt, keine Normalisierung).
Lösung: Kennung gegen die exakte Liste in Abschnitt 7 prüfen.
Symptom: Markdown-Fenced-Code-Block wird nicht hervorgehoben.
Ursache: unbekannte oder fehlende Sprachkennung nach den drei Backticks, oder der Cache trägt noch die alte Engine-Kennung.
Lösung: Sprachkennung gegen GeSHis Liste prüfen; bei Verdacht auf
veralteten Cache: Engine-Kennung parsedown-1.7.4-geshi1 erzwingt
Invalidierung.
Symptom: Beim geteilten HTML-Editor fehlt die Werkzeugleiste, oder die Vorschau bleibt leer, obwohl das Markup im Quelltext steht.
Ursache: Der ausgelieferte Skript-Bund ist veraltet.
lib/exe/js.php bündelt die Dateien aus scripts/ über
/* DOKUWIKI:include */ und stützt seinen Zwischenspeicher nicht auf
deren Änderungszeit — eine Änderung an scripts/htmlsplit.js allein
erneuert den Bund also nicht, und der Browser hält zusätzlich seine eigene
Kopie unter unveränderter tseed-Kennung.
Lösung: script.js des Plugins anfassen (touch) und im Browser
hart neu laden (Strg+Umschalt+R). Gilt für jede Änderung an einer
eingebundenen Skriptdatei, nicht nur für diesen Baustein.
14. Verwandte Themen
- DW-Editor: Formatierung · DW-Editor: Tabellen und Medien — Vertiefung der Formatierungs-Toolbar
- DW-Editor: Adaptivity · DW-Editor: Accessibility — Adaptivity/Accessibility
- Markdown (wvdsmd) — Markdown-Authoring (Marker, EasyMDE, Import/Export)
- SQLite Data Studio: Admin-Oberfläche — SQL-Workbench (CodeMirror-Konsument)
- FluentUI: Desktop-DataGrid — Demo-Übersicht · FluentUI: Admin-Bereich (vertieft) — Panels, in denen Editoren leben
- FluentUI: Öffentliche Helper-API — Konvention „progressive Erweiterung„