export function generateSlug(text: string): string { return text .toString() .toLowerCase() .trim() .normalize('NFD') // remove accents .replace(/[\u0300-\u036f]/g, '') .replace(/\s+/g, '-') // replace spaces with - .replace(/[^\w-]+/g, '') // remove all non-word chars .replace(/--+/g, '-') // replace multiple - with single - .replace(/^-+/, '') // trim - from start of text .replace(/-+$/, ''); // trim - from end of text } /** * Validates external or relative URLs to prevent javascript:/data:/vbscript: XSS in href attributes. */ export function safeUrl(url?: string | null): string { if (!url || typeof url !== 'string') return '#'; const trimmed = url.trim(); if (!trimmed) return '#'; // Allow safe relative paths (starting with single `/` and not `//` or `/\`) if (trimmed.startsWith('/') && !trimmed.startsWith('//') && !trimmed.startsWith('/\\')) { return trimmed; } try { const parsed = new URL(trimmed); if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return parsed.toString(); } } catch { // Invalid URL } return '#'; }