diff --git a/CHANGELOG.md b/CHANGELOG.md index 01fdd4d..f580a50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,61 @@ Changelog ist die Upgrade-Anleitung für die Tools. ## [Unreleased] +### @basicbar/ui (→ wird `ui/v0.7.0`) + +**TipTap raus aus den Bundles, die keinen Editor rendern** (Framework-Review): +`RichTextEditor` liegt jetzt in einem eigenen Entry +`@basicbar/ui/rich-text-editor`; das Paket ist `sideEffects: false` und wird +mit Code-Splitting gebaut. Der Haupt-Entry `@basicbar/ui` importiert kein +`@tiptap/*` mehr. Nagelprobe am gerenderten Template (kein Editor): Bundle +575 kB → 280 kB (gzip 185 → 91 kB). Die `@tiptap/*`-Pakete bleiben bewusst +`dependencies` (nicht optionale Peers): sie kosten nur Installationszeit, und +die eine Pin-Stelle für alle Tools bleibt erhalten. + +Außerdem: + +- `stripHtml(html)` und `isEmptyHtml(html)` sind exportiert (abstimmbar + hatte vier Kopien). `isEmptyHtml` zählt ein Editor-Leer-`

` als + leer, einen reinen Bild-Inhalt aber als gefüllt; `TranslatableField` mit + `format="html"` nutzt das für seine Ausgefüllt-Punkte (vorher galt ein + Bild ohne Text als „nicht übersetzt“). +- `richTextClass` (der Prosa-Default von `RichText`) ist exportiert, damit + Aufrufer ihn ergänzen können statt zu kopieren; `className` ersetzt + weiterhin. +- `RichTextEditor` hat `editable?: boolean` (Default `true`): schreibgeschützt + ohne Toolbar und Cursor, Drop/Paste ignoriert; Umschalten zur Laufzeit via + `setEditable`. +- `TranslationFormProvider` hat `controlsClassName?` für die Position der + Floating-Controls (Default `fixed bottom-6 right-6 z-40`) — ersetzt den + CSS-Override auf die Utility-Klassen in abstimmbar. +- `renderInput` von `TranslatableField` bekommt `onBlur` durchgereicht. +- Performance: `TranslatableField` berechnet die Sprachreihenfolge und den + Ausgefüllt-Status pro Render nur noch einmal (vorher pro Tab/Lookup, bei + HTML jeweils mit DOM-Parse); `targets` im Provider memoisiert. +- A11y: die Sprach-Tabs tragen den Status („translated“ …) zusätzlich als + `sr-only`-Text (der Punkt war farb-only, WCAG 1.4.1); `PreferencesMenu` + fokussiert beim Öffnen die erste Zeile und unterstützt Pfeiltasten, + Home/End (WAI-ARIA-Menü-Pattern), `role="menu"` hat einen Namen. +- README: Abschnitt „Übersetzungs-Keys“ mit allen Keys des Pakets. + +**Migration:** + +1. `package.json`: Tarball auf `ui/v0.7.0`. +2. Jeden `RichTextEditor`-Import auf den neuen Pfad umstellen: + `import { RichTextEditor } from "@basicbar/ui/rich-text-editor";` + (`RichText`, `RichTextEditorProps`-Typ: `RichText` bleibt in + `@basicbar/ui`, der Props-Typ kommt aus dem Editor-Entry). Betroffen: + ausleihbar (`AdminWelcomePage`, `AdminPagesPage`, `AdminPoolsPage`), + abstimmbar (`components/TranslatableField.tsx`). Tools ohne Editor + (erkennbar, modulierbar) ändern nichts. +3. Optional, empfohlen: lokale `stripHtml`-Kopien durch den Export ersetzen + (abstimmbar: `RoomsPage`, `ResultsPage`, `SetPage`, `QuestionPage` — dort + `!stripHtml(x) && !/`-Sync nach WCAG 3.1.1). Die Kataloge bleiben im Tool. - **`contentLang`** — `localizedText`/`localizedMap`/`setLocalizedLang` & Co. für `{ lang: text }`-Inhalte (Spiegel des Backend-`resolve_translated_text`). +- **`TranslatableField` / `TranslationFormProvider`** — Sprach-Tabs pro + Feld plus „alle Felder übersetzen“ (siehe `TranslatableField.tsx`). +- **`RichText` / `stripHtml` / `isEmptyHtml`** — Rendern und Prüfen des + gespeicherten Rich-HTML; **`RichTextEditor`** (TipTap) als **eigener Entry** + `@basicbar/ui/rich-text-editor`, damit TipTap/ProseMirror nur in Bundles + landet, die den Editor wirklich rendern (siehe „Rich text“). ## Einbinden @@ -68,6 +74,19 @@ Link, Überschriften H2/H3, optional Bilder) und die passende Renderkomponente für das gespeicherte HTML. Aus AbstimmBAR in die Basis verschoben (modulierbar#5), damit alle -bar-Tools eine Implementierung teilen. +**Import-Pfade:** der Editor kommt aus dem eigenen Entry +`@basicbar/ui/rich-text-editor`; `RichText`, `stripHtml` und `isEmptyHtml` +aus `@basicbar/ui`. Grund: TipTap + ProseMirror sind ~300 kB (≈95 kB gzip) +und sollen nur in Bundles landen, die den Editor rendern — ein Tool, das nur +`RichText` anzeigt oder gar kein Rich-Text hat, zahlt sonst mit. Wer den +Editor nur auf Admin-Seiten braucht, lädt ihn zusätzlich per `React.lazy` +nach, dann liegt er in einem eigenen Chunk. + +```tsx +import { RichText, stripHtml, isEmptyHtml } from "@basicbar/ui"; +import { RichTextEditor } from "@basicbar/ui/rich-text-editor"; +``` + **Sanitizing-Vertrag:** die Komponenten selbst sanitizen nichts — die Sicherheitsgrenze ist das Backend. Jedes Rich-Text-Feld muss beim Speichern (und beim Import) durch einen Allowlist-Sanitizer laufen, z. B. @@ -90,7 +109,7 @@ Drag&Drop/Einfügen aus der Zwischenablage werden ignoriert; bestehende ``-Inhalte bleiben trotzdem sichtbar): ```tsx -import { RichTextEditor } from "@basicbar/ui"; +import { RichTextEditor } from "@basicbar/ui/rich-text-editor"; ``` +**Schreibgeschützt** mit `editable={false}`: kein Toolbar, kein Cursor, +Dateien per Drop/Paste werden ignoriert — z. B. während ein Formular +speichert oder für Nutzer ohne Schreibrecht. Für die reine Anzeige +gespeicherten HTMLs ist `RichText` das richtige Werkzeug (kostet kein TipTap). + **Editor mit Bildern** — `onUploadImage` lädt hoch und liefert die relative URL als String; scheitert der Upload, zeigt der Editor `t("Image upload failed")` (plus die Fehlermeldung, falls vorhanden) per @@ -143,20 +167,31 @@ unangetastet. **Rendern** des serverseitig sanitisierten HTML: ```tsx -import { RichText } from "@basicbar/ui"; +import { RichText, richTextClass } from "@basicbar/ui"; // eigene Klassen statt des Prosa-Defaults (ersetzt, nicht ergänzt): +// Prosa-Default ergänzen statt kopieren: + ``` +**Prüfen** des gespeicherten HTML — `stripHtml(html)` liefert den sichtbaren +Text (für Listen, Suchtreffer, Platzhalter „kein Fragetext“), `isEmptyHtml(html)` +ist `true` für leere Werte und das `

`, das ein geöffneter Editor +hinterlässt, aber `false` für reine Bild-Inhalte. Beide parsen per DOM (keine +Regex), ohne etwas auszuführen. `TranslatableField` mit `format="html"` +benutzt `isEmptyHtml` für seine Ausgefüllt-Punkte. + **Integration in `TranslatableField`** über `renderInput` (pro Sprache ein Editor, mit `format="html"` bleiben die Ausgefüllt-Punkte markup-blind und die Maschinenübersetzung erhält die Tags). `renderInput` bekommt neben `id` auch `labelId` — die Id von `TranslatableField`s eigenem sichtbaren `