diff --git a/CHANGELOG.md b/CHANGELOG.md index 72e5257..604bbbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,20 +7,16 @@ Changelog ist die Upgrade-Anleitung für die Tools. ## [Unreleased] -### @basicbar/ui (→ wird `ui/v0.7.1`) +### @basicbar/ui (→ wird `ui/v0.7.2`) -**A11y-Fix `TranslatableField`** (#11): Der `hint` hatte mit -`text-slate-400` nur ~2,6:1 Kontrast und war nicht mit dem Feld verknüpft. -Jetzt `text-slate-600 dark:text-slate-300` (AA), und Hinweis, -Veraltet-Hinweis und Pflichtfeld-Fehler hängen per `aria-describedby` an der -Eingabe (Screenreader lesen sie mit dem Feld vor); Veraltet- und -Fehlertext ebenfalls auf AA-Kontrast (`amber-700`, `rose-600`). -`renderInput` bekommt dafür `describedBy`, `RichTextEditor` eine gleichnamige -Prop (auch zur Laufzeit aktualisiert). +**Sicherheits-Fix `stripHtml` / `isEmptyHtml`** (#20): Beide parsten über +`innerHTML` auf einem Element des Live-Dokuments — dort lädt der Browser +Bilder und führt Inline-Handler aus, `` lief also, auch +ohne Einhängen. Jetzt wird in einem inerten `DOMParser`-Dokument geparst +(nichts wird ausgeführt oder geladen); `isEmptyHtml` erkennt Bilder über den +geparsten Body statt per Regex. Verhalten sonst unverändert. -Migration: Tarball auf `ui/v0.7.1`. Wer `renderInput` mit `RichTextEditor` -nutzt, reicht zusätzlich `describedBy={describedBy}` durch (sonst bleibt der -Editor ohne Beschreibung — nichts bricht). +Migration: Tarball auf `ui/v0.7.2`. Keine Code-Änderung nötig. ### basicbar-integrations (→ wird `integrations/v0.3.0`) @@ -53,6 +49,23 @@ Migration. - Doku-Drift zum Capabilities-Endpunkt in CLAUDE.md und EXTRAKTIONSPLAN bereinigt. +## ui/v0.7.1 — 2026-10-02 + +### @basicbar/ui + +**A11y-Fix `TranslatableField`** (#11): Der `hint` hatte mit +`text-slate-400` nur ~2,6:1 Kontrast und war nicht mit dem Feld verknüpft. +Jetzt `text-slate-600 dark:text-slate-300` (AA), und Hinweis, +Veraltet-Hinweis und Pflichtfeld-Fehler hängen per `aria-describedby` an der +Eingabe (Screenreader lesen sie mit dem Feld vor); Veraltet- und +Fehlertext ebenfalls auf AA-Kontrast (`amber-700`, `rose-600`). +`renderInput` bekommt dafür `describedBy`, `RichTextEditor` eine gleichnamige +Prop (auch zur Laufzeit aktualisiert). + +Migration: Tarball auf `ui/v0.7.1`. Wer `renderInput` mit `RichTextEditor` +nutzt, reicht zusätzlich `describedBy={describedBy}` durch (sonst bleibt der +Editor ohne Beschreibung — nichts bricht). + ## auth/v0.2.0 — 2026-10-01 ### basicbar-auth diff --git a/packages/ui/package-lock.json b/packages/ui/package-lock.json index a565996..43a6b71 100644 --- a/packages/ui/package-lock.json +++ b/packages/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "@basicbar/ui", - "version": "0.7.1", + "version": "0.7.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@basicbar/ui", - "version": "0.7.1", + "version": "0.7.2", "license": "Apache-2.0", "dependencies": { "@tiptap/extension-bold": "^3.27.2", diff --git a/packages/ui/package.json b/packages/ui/package.json index c9142ae..88e2124 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@basicbar/ui", - "version": "0.7.1", + "version": "0.7.2", "description": "Design-System-Basis der virtUOS -bar-Tools: Tailwind-Preset, Basis-Styles, Theme (Dark Mode), i18n-Bootstrap, contentLang, TranslatableField, Preferences, RichText; RichTextEditor (TipTap) als eigener Entry", "license": "Apache-2.0", "author": "Universität Osnabrück (virtUOS)", diff --git a/packages/ui/src/html.ts b/packages/ui/src/html.ts index 78e8e81..e29ffca 100644 --- a/packages/ui/src/html.ts +++ b/packages/ui/src/html.ts @@ -3,15 +3,19 @@ /** Small helpers for the HTML that rich-text fields store. They parse via the * DOM (not a regex), so entities and nested markup are handled like the - * browser handles them; nothing is executed — `innerHTML` on a detached - * element never runs scripts or loads resources synchronously. */ + * browser handles them. Parsing happens in an inert `DOMParser` document: + * unlike `innerHTML` on an element of the live document — which loads images + * and runs inline handlers such as `` even when detached — + * nothing in the input is executed or fetched (basicbar#20). */ + +function parseBody(html: string): HTMLElement { + return new DOMParser().parseFromString(html, "text/html").body; +} /** The visible text of an HTML fragment, whitespace-trimmed. */ export function stripHtml(html: string): string { if (!html) return ""; - const div = document.createElement("div"); - div.innerHTML = html; - return div.textContent?.trim() ?? ""; + return parseBody(html).textContent?.trim() ?? ""; } /** True when a stored rich-text value carries no content: no visible text and @@ -19,6 +23,7 @@ export function stripHtml(html: string): string { * behind — that is empty; an image-only caption is not. */ export function isEmptyHtml(html: string | null | undefined): boolean { if (!html) return true; - if (stripHtml(html)) return false; - return !/]/i.test(html); + const body = parseBody(html); + if (body.textContent?.trim()) return false; + return !body.querySelector("img"); }