Jeder Entwickler baut irgendwann etwas, weil nichts anderes genau passt. Für mich war das ein Symfony-Bundle für einen Rich-Markdown-Editor, der korrekt mit arabischen (RTL) Inhalten funktioniert. Das Ergebnis ist runio/mde, verfügbar auf Packagist.
Dieser Beitrag erklärt das Warum und das Wie. Für den breiteren Kontext — warum ich das überhaupt brauchte — siehe Ein mehrsprachiges Portfolio mit Symfony entwickeln.
Was runio/mde leistet
Im Kern verpackt runio/mde den Toast UI Editor in einem ordentlichen Symfony-Bundle. Der Toast UI Editor ist eine ausgereifte, aktiv gewartete JavaScript-Bibliothek, die Folgendes unterstützt:
- WYSIWYG- und Raw-Markdown-Bearbeitungsmodi, zur Laufzeit umschaltbar
- Split-View-Vorschau (Markdown-Quelle links, gerendertes HTML rechts)
- Tabellen, Aufgabenlisten, Code-Blöcke, Bilder, Links
- Eine saubere, zugängliche Toolbar
Das Bundle fügt hinzu, was der rohen Bibliothek in einem Symfony-Kontext fehlt:
- Symfony-Formulartyp (
MarkdownEditorType) mit allen konfigurierbaren Optionen - Twig-Filter
|markdownzum Rendern gespeicherter Markdown auf Anzeige-Seiten - RTL/LTR-CSS-Schicht für korrekte Darstellung von Arabisch, Hebräisch, Persisch, Urdu
- Alle Assets lokal bereitgestellt — kein
uicdn.toast.com, keinfonts.googleapis.com - Symfony Asset Mapper-Kompatibilität
- Symfony UX Turbo-Unterstützung
- Mobile-responsive Layout über CSS-Media-Queries
- Korrekte Service-Registrierung (keine manuellen
services.yaml-Workarounds)
Installation
composer require runio/mde
php bin/console assets:install --symlink
Falls Ihr Projekt Asset Mapper verwendet (Symfony 6.3+), fügen Sie den Asset-Pfad des Bundles zu config/packages/asset_mapper.yaml hinzu:
framework:
asset_mapper:
paths:
assets/: ''
vendor/runio/mde/public/: bundles/runiomarkdowneditor/
Dann kompilieren:
php bin/console asset-map:compile
Registrieren Sie das Formular-Theme in config/packages/twig.yaml:
twig:
form_themes:
- '@RunioMarkdownEditor/form/markdown_editor_widget.html.twig'
Den Formulartyp verwenden
use Runio\MarkdownEditorBundle\Form\Type\MarkdownEditorType;
$builder->add('inhalt', MarkdownEditorType::class, [
'editor_mode' => 'wysiwyg', // wysiwyg | markdown | both
'rtl_enabled' => false,
'language' => 'de',
'toolbar' => 'full', // full | basic | minimal
'preview_style' => 'vertical', // vertical | tab
'theme' => 'light', // light | dark
'editor_height' => '500px',
'show_mode_switch' => true,
]);
// Für arabische Inhalte:
$builder->add('inhaltAr', MarkdownEditorType::class, [
'editor_mode' => 'wysiwyg',
'rtl_enabled' => true,
'language' => 'ar',
]);
Gespeichertes Markdown rendern
In Ihren Twig-Templates verwenden Sie den |markdown-Filter:
<div class="prose">
{{ beitrag.inhalt | markdown }}
</div>
Der Filter verarbeitet den Inhalt durch CommonMark und eine Bereinigungsschicht, die unsichere Tags und Attribute entfernt. Die Bereinigung ist konfigurierbar:
# config/packages/runio_markdown_editor.yaml
runio_markdown_editor:
sanitization:
enabled: true
allowed_tags: [p, br, strong, em, blockquote, ul, ol, li, a, img, h1, h2, h3, h4, h5, h6, pre, code, table, thead, tbody, tr, th, td, hr]
allowed_attributes: [href, src, alt, title, class, id, dir, lang]
Architektur
Die PHP-Schicht
Das Bundle stellt drei Services bereit:
MarkdownParser — konvertiert Markdown in HTML mit league/commonmark. Konfigurierbar über den markdown-Abschnitt der Bundle-Konfiguration:
runio_markdown_editor:
markdown:
html_input: escape # strip | allow | escape
allow_unsafe_links: false
enable_table_extension: true
enable_strikethrough_extension: true
enable_tasklist_extension: true
enable_autolink_extension: true
RTLTextDetector — erkennt, ob ein String RTL-Zeichen enthält (Arabisch, Hebräisch, Persisch, Urdu Unicode-Bereiche). Wird für die automatische Erkennung der Textrichtung verwendet.
SanitizationService — entfernt nicht erlaubte HTML-Tags und Attribute aus der geparsten Ausgabe.
MarkdownRuntime — die Twig-Runtime, die die |markdown- und |markdown_inline-Filter registriert. Dieser ist als ordentlicher Service in der services.yaml des Bundles registriert — keine manuelle Registrierung in der Anwendung erforderlich.
Die JavaScript-Schicht
Das JavaScript (public/js/markdown-editor.js) ist ein dünnes Wrapper um toastui.Editor. Es:
- Prüft, ob
toastuiverfügbar ist, bevor es initialisiert — fällt elegant auf ein einfaches Textarea zurück, wenn das JS nicht geladen werden kann - Wählt responsive Höhe und Vorschau-Stil basierend auf dem Viewport beim ersten Laden
- Synchronisiert den versteckten
<textarea>-Wert bei jeder Editor-Änderung und beim Formular-Submit - Handhabt Bild-Uploads über
fetch, wenn eine Upload-URL konfiguriert ist - Unterstützt Turbo-Navigation über
turbo:load- undturbo:before-cache-Lifecycle-Events
Der Editor initialisiert sich automatisch auf jedem .markdown-editor-rtl-wrapper-Element im DOM und re-initialisiert sich bei dynamisch hinzugefügtem Inhalt über MutationObserver.
Die CSS-Schicht
Das RTL-CSS (public/css/markdown-editor-rtl.css) wendet korrekte Stile an, wenn der Wrapper dir="rtl" hat:
- Textausrichtung und -richtung für alle Inhaltsbereiche
- Zitatrahmen auf die rechte Seite gespiegelt
- Listen-Einrückung auf die rechte Seite gespiegelt
- Code-Blöcke immer LTR, unabhängig von der umgebenden Richtung
- Splitter (Split-View-Trennlinie) korrekt positioniert — das war die wichtigste Bug-Behebung
Der Mobile-Responsive-Abschnitt behandelt:
- Toolbar-Icon-Größenanpassung bei
<768pxund<480px - Dropdown-Toolbar auf Editor-Breite beschränkt (verhindert Überlauf)
- Split-View-Panels stapeln sich vertikal auf Mobilgeräten
- Popup-Positionierung für kleine Viewports korrigiert
Die Bugs, die ich behoben habe
Die vorherige Open-Source-Symfony-Integration für Toast UI hatte zwei bedeutende Probleme:
1. Unsichtbarer RTL-Splitter
Im Split-View-Modus mit aktiviertem RTL war die Trennlinie zwischen dem Markdown-Eingabefeld und der Vorschau unsichtbar. Ursache: Eine CSS-Regel, die left: auto; right: 0 auf .toastui-editor-md-splitter setzte. Das verschob das Element auf left: 859px in einem 860px-Container — ein Pixel außerhalb des Bildschirms. Das Entfernen des Overrides lässt Toast UI es korrekt bei 50% positionieren.
2. Nicht registrierte Twig-Runtime
Die services.yaml im vorherigen Bundle hatte die MarkdownRuntime-Service-Registrierung auskommentiert. Der |markdown-Filter schien zu existieren (die Extension war registriert), aber beim Aufruf wurde ein Laufzeitfehler ausgelöst. Die Lösung ist einfach sicherzustellen, dass der Service korrekt registriert ist — eine Zeile in services.yaml.
Mehrsprachige Konfiguration
Die Standardkonfiguration zielt auf arabische Inhalte ab, aber jede RTL-Sprache funktioniert:
runio_markdown_editor:
default_config:
rtl_enabled: true
language: ar
rtl:
languages: [ar, he, fa, ur]
auto_detect: true
arabic_font: 'Noto Naskh Arabic'
Pro-Feld-Overrides ermöglichen die Mischung von RTL- und LTR-Editoren im selben Formular — genau das, was ich im Blog-Beitrags-Formular von sym-port mache — ein Editor pro Sprache, jeweils entsprechend konfiguriert.
Ausblick
Das Bundle ist funktionsfähig und wird aktiv auf meinem Portfolio verwendet. Geplante Verbesserungen:
- Bild-Upload-UI — ein ordentliches Upload-Dialog statt eines externen Endpunkts
- Benutzerdefinierte Toolbar-Buttons — Anwendungen können zusätzliche Toolbar-Elemente registrieren
- Atom-Feed-Helfer — eine Twig-Funktion zum Rendern von Markdown-Inhalten in Feed-Kontexten
Der Quellcode ist auf GitHub und das Paket ist auf Packagist unter runio/mde verfügbar. Issues und Pull Requests sind willkommen.
Autor Amer Malik Mohammed
Full-Stack Entwickler mit 2+ Jahren Erfahrung in objektorientierter PHP-Entwicklung (Symfony), JavaScript und MySQL. Spezialisiert auf E-Commerce-Lösungen (Shopware 5/6), REST-API-Entwicklung und Prozessautomatisierung in agilen Teams.
Autor kontaktieren