import type { Task } from "@/lib/weekly/types"; /** * Darstellungs-Helfer der Wochenansicht: Text, Farben, Ganztags-Erkennung, Slot-Anzahl. * Reine Funktionen ohne Komponenten-State — lagen bis v1.117.7 in WeeklyView.tsx. */ export const stripHtml = (html: string) => html.replace(/<[^>]*>/g, '').trim(); export const DEFAULT_SOMEDAY_SLOT_COUNT = 5; export const getSomedaySlotCount = (tasks: Task[]) => { const maxIdx = tasks.reduce((max, t) => { if (t.somedaySlotIndex !== null && t.somedaySlotIndex !== undefined) { return Math.max(max, t.somedaySlotIndex); } return max; }, -1); // Add 1 extra slot if more than 4 tasks exist, or at least 5 slots total. // "add 5 rows and then when 4 are taken add another row" // Let's ensure there's always at least one empty slot at the bottom. return Math.max(DEFAULT_SOMEDAY_SLOT_COUNT, maxIdx + 2); }; /** * Ein Textabschnitt: entweder normaler Text oder eine erkannte URL. * * Rein für die Erkennung/Aufteilung zuständig — nichts hier weiß etwas von React oder * vom DOM. Das eigentliche Rendern (als ``-Tag, mit `stopPropagation` gegen das * Toggle-auf-Klick der umgebenden Aufgabe) übernimmt `LinkifiedText.tsx`. */ export interface TextSegment { type: "text" | "url"; value: string; } // http(s)://... oder www.-Adressen, aber ohne einen abschließenden Satzzeichen-Rest // (".", ",", ")", "!", "?") mitzureißen -- sonst würde "siehe https://x.de." den Punkt // noch in den Link ziehen. const URL_PATTERN = /(https?:\/\/[^\s<>"]+|www\.[^\s<>"]+)/gi; const TRAILING_PUNCTUATION = /[.,!?;:)\]]+$/; /** * Zerlegt einen Aufgabentitel in Text- und URL-Abschnitte, damit eine im Titel * getippte Adresse (z. B. "Rechnung siehe https://example.com/invoice pruefen") als * anklickbarer Link dargestellt werden kann, statt als reiner Text. */ export function splitTextAndUrls(text: string): TextSegment[] { if (!text) return []; const segments: TextSegment[] = []; let lastIndex = 0; for (const match of text.matchAll(URL_PATTERN)) { const start = match.index ?? 0; let url = match[0]; // Satzzeichen, die grammatisch zum umgebenden Satz gehören, nicht zur Adresse. const trailing = url.match(TRAILING_PUNCTUATION); if (trailing) { url = url.slice(0, url.length - trailing[0].length); } if (!url) continue; if (start > lastIndex) { segments.push({ type: "text", value: text.slice(lastIndex, start) }); } segments.push({ type: "url", value: url }); lastIndex = start + url.length; } if (lastIndex < text.length) { segments.push({ type: "text", value: text.slice(lastIndex) }); } return segments; } export function invertColor(hex: string): string { if (!hex) return hex; let color = hex.startsWith("#") ? hex.slice(1) : hex; if (color.length === 3) { color = color .split("") .map((c) => c + c) .join(""); } if (color.length !== 6) return hex; try { const r = (255 - parseInt(color.slice(0, 2), 16)) .toString(16) .padStart(2, "0"); const g = (255 - parseInt(color.slice(2, 4), 16)) .toString(16) .padStart(2, "0"); const b = (255 - parseInt(color.slice(4, 6), 16)) .toString(16) .padStart(2, "0"); return `#${r}${g}${b}`; } catch (e) { return hex; } }