Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 25 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, `<img src=x onerror=…>` 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`)

Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
@@ -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)",
Expand Down
19 changes: 12 additions & 7 deletions packages/ui/src/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@

/** 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 `<img onerror=…>` 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
* no image. An editor that was opened and closed again leaves `<p></p>`
* 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 !/<img[\s>]/i.test(html);
const body = parseBody(html);
if (body.textContent?.trim()) return false;
return !body.querySelector("img");
}
Loading