/** * note-task-list — reine Hilfsfunktionen + Kaestchen-Komponente fuer das * Notiz-Widget (quick-260916-iex). * * `rehypeSanitize` (hast-util-sanitize) erzwingt bei `input`-Elementen * grundsaetzlich `disabled=true`. Die Loesung ersetzt NICHT das * Sanitize-Verhalten, sondern das gerenderte Element selbst: react-markdown * (ueber `@uiw/react-md-editor`s `previewOptions.components`) laesst uns das * `input`-Tag durch eine eigene Komponente ersetzen, die NACH dem Sanitize * ausgefuehrt wird und deshalb selbst entscheidet, ob sie `disabled` setzt. * `rehypeSanitize` bleibt aktiv (XSS-Schranke fuer den Rest des Markdowns, * T-IEX-01). * * Die Zuordnung Klick -> Zeile laeuft ueber einen Index (n-tes Kaestchen im * DOM == n-te Aufgabenzeile im Markdown). Damit das stimmt, MUSS * `isTaskLine` exakt dieselben Zeilen als Aufgabe erkennen wie die * GFM-Erkennung von react-markdown (remark-gfm / * micromark-extension-gfm-task-list-item): nach der schliessenden Klammer * muss Leerraum UND mindestens ein Nicht-Leerraum-Zeichen folgen. Eine * laxere Regex wuerde bei einer noch leeren Aufgabenzeile (`- [ ]`, typisch * beim Tippen einer neuen Aufgabe) den Index verschieben und beim Klick die * falsche Zeile kippen. * * Vorbild: `toggleMarkdownCheckbox` im alten persoenlichen Dashboard des * Anwenders (user-files/personal-dashboard/src/app/page.tsx, ~Z. 1399). */ /** * Erkennt eine Markdown-Aufgabenzeile (`- [ ] Text`, `* [x] Text`, * `+ [X] Text`, `1. [ ] Text`, `1) [ ] Text`, auch eingerueckt). Bewusst * streng: nach `]` muss Leerraum und danach mindestens ein * Nicht-Leerraum-Zeichen folgen (siehe Modulkommentar). */ export const TASK_LINE_RE = /^(\s*(?:[-*+]|\d+[.)])\s+\[)([ \txX])(\]\s+\S.*)$/; const FENCE_RE = /^\s*(```|~~~)/; export function isTaskLine(line: string): boolean { return TASK_LINE_RE.test(line); } /** * Kippt die `index`-te Aufgabenzeile (0-basiert, Zeilen in Code-Zaeunen * zaehlen nicht mit) zwischen `[ ]` und `[x]`. Gibt bei ungueltigem Index * (< 0 oder >= Anzahl Aufgabenzeilen) dieselbe Referenz wie `content` * zurueck, damit Aufrufer per `===` erkennen, dass nichts zu speichern ist. */ export function toggleTaskLine(content: string, index: number): string { if (index < 0) return content; const lines = content.split('\n'); let inFence = false; let taskCounter = 0; let changed = false; const nextLines = lines.map((line) => { if (FENCE_RE.test(line)) { inFence = !inFence; return line; } if (inFence) return line; if (!isTaskLine(line)) return line; const isTarget = taskCounter === index; taskCounter += 1; if (!isTarget) return line; const match = line.match(TASK_LINE_RE); if (!match) return line; const [, prefix, mark, suffix] = match; const newMark = mark === ' ' || mark === '\t' ? 'x' : ' '; changed = true; return `${prefix}${newMark}${suffix}`; }); if (!changed) return content; return nextLines.join('\n'); } /** * Ersetzt das von react-markdown gerenderte `` einer Aufgabenzeile. * Zieht bewusst NUR `checked` aus den Props (react-markdown reicht * zusaetzlich `node`, `disabled`, `type` durch) — sonst landen fremde * Attribute (insbesondere `node`) im DOM. Kein `disabled`: das Kaestchen * bleibt anklickbar. `readOnly` unterdrueckt Reacts Warnung * "checked ohne onChange" — der Klick wird nicht am Kaestchen selbst, * sondern delegiert am Vorschau-Container verarbeitet. */ export function NoteCheckbox({ checked }: { checked?: boolean }) { return ( ); }