// src/utils/uiHelpers.ts - Consolidated UI utilities // This file consolidates all scattered utility functions declare global { interface Window { createToolSlug: (toolName: string) => string; findToolByIdentifier: (tools: Tool[], identifier: string) => Tool | undefined; isToolHosted: (tool: Tool) => boolean; scrollToElement: (element: Element | null, options?: ScrollIntoViewOptions) => void; scrollToElementById: (elementId: string, options?: ScrollIntoViewOptions) => void; scrollToElementBySelector: (selector: string, options?: ScrollIntoViewOptions) => void; prioritizeSearchResults: (tools: Tool[], searchTerm: string) => Tool[]; shareArticle: (button: HTMLElement, url: string, title: string) => Promise; shareCurrentArticle: (button: HTMLElement) => Promise; } } export interface Tool { name: string; type?: 'software' | 'method' | 'concept'; projectUrl?: string | null; license?: string; knowledgebase?: boolean; domains?: string[]; phases?: string[]; platforms?: string[]; skillLevel?: string; description?: string; tags?: string[]; related_concepts?: string[]; related_software?: string[]; url?: string; // Add missing property accessType?: string; // Add missing property } // CORE TOOL UTILITIES (consolidates clientUtils.ts + toolHelpers.ts) export function createToolSlug(toolName: string): string { if (!toolName || typeof toolName !== 'string') { console.warn('[uiHelpers] Invalid toolName provided to createToolSlug:', toolName); return ''; } return toolName.toLowerCase() .replace(/[^a-z0-9\s-]/g, '') // Remove special characters .replace(/\s+/g, '-') // Replace spaces with hyphens .replace(/-+/g, '-') // Remove duplicate hyphens .replace(/^-|-$/g, ''); // Remove leading/trailing hyphens } export function findToolByIdentifier(tools: Tool[], identifier: string): Tool | undefined { if (!identifier || !Array.isArray(tools)) return undefined; return tools.find(tool => tool.name === identifier || createToolSlug(tool.name) === identifier.toLowerCase() ); } export function isToolHosted(tool: Tool): boolean { return tool.projectUrl !== undefined && tool.projectUrl !== null && tool.projectUrl !== "" && tool.projectUrl.trim() !== ""; } // TEXT UTILITIES (consolidates scattered text functions) export function escapeHtml(text: string): string { if (typeof text !== 'string') return String(text); const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } export function truncateText(text: string, maxLength: number): string { if (!text || text.length <= maxLength) return text; return text.slice(0, maxLength) + '...'; } export function sanitizeText(text: string): string { if (typeof text !== 'string') return ''; return text .replace(/^#{1,6}\s+/gm, '') .replace(/^\s*[-*+]\s+/gm, '') .replace(/^\s*\d+\.\s+/gm, '') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/```[\s\S]*?```/g, '[CODE BLOCK]') .replace(/`([^`]+)`/g, '$1') .replace(/<[^>]+>/g, '') .replace(/\n\s*\n\s*\n/g, '\n\n') .trim(); } export function summarizeData(data: any): string { if (data === null || data === undefined) return 'null'; if (typeof data === 'string') { return data.length > 100 ? data.slice(0, 100) + '...' : data; } if (typeof data === 'number' || typeof data === 'boolean') { return data.toString(); } if (Array.isArray(data)) { if (data.length === 0) return '[]'; if (data.length <= 3) return JSON.stringify(data); return `[${data.slice(0, 3).map(i => typeof i === 'string' ? i : JSON.stringify(i)).join(', ')}, ...+${data.length - 3}]`; } if (typeof data === 'object') { const keys = Object.keys(data); if (keys.length === 0) return '{}'; if (keys.length <= 3) { return '{' + keys.map(k => `${k}: ${typeof data[k] === 'string' ? data[k].slice(0, 20) + (data[k].length > 20 ? '...' : '') : JSON.stringify(data[k])}`).join(', ') + '}'; } return `{${keys.slice(0, 3).join(', ')}, ...+${keys.length - 3} keys}`; } return String(data); } // SCROLL UTILITIES (consolidates BaseLayout.astro scroll functions) export function scrollToElement(element: Element | null, options: ScrollIntoViewOptions = {}): void { if (!element) return; setTimeout(() => { const headerHeight = document.querySelector('nav')?.offsetHeight || 80; const elementRect = element.getBoundingClientRect(); const absoluteElementTop = elementRect.top + window.pageYOffset; const targetPosition = absoluteElementTop - headerHeight - 20; window.scrollTo({ top: targetPosition, behavior: 'smooth', ...options }); }, 100); } export function scrollToElementById(elementId: string, options: ScrollIntoViewOptions = {}): void { const element = document.getElementById(elementId); if (element) { scrollToElement(element, options); } } export function scrollToElementBySelector(selector: string, options: ScrollIntoViewOptions = {}): void { const element = document.querySelector(selector); if (element) { scrollToElement(element, options); } } // SEARCH UTILITIES (consolidates BaseLayout.astro search functions) export function prioritizeSearchResults(tools: Tool[], searchTerm: string): Tool[] { if (!searchTerm || !searchTerm.trim()) { return tools; } const lowerSearchTerm = searchTerm.toLowerCase().trim(); return tools.sort((a, b) => { const aTagsLower = (a.tags || []).map((tag: string) => tag.toLowerCase()); const bTagsLower = (b.tags || []).map((tag: string) => tag.toLowerCase()); const aExactTag = aTagsLower.includes(lowerSearchTerm); const bExactTag = bTagsLower.includes(lowerSearchTerm); if (aExactTag && !bExactTag) return -1; if (!aExactTag && bExactTag) return 1; return a.name.localeCompare(b.name); }); } // URL UTILITIES (consolidates ToolMatrix.astro share functions) export function generateShareURL(toolName: string, view: string, modal?: string): string { const toolSlug = createToolSlug(toolName); const baseUrl = window.location.origin + window.location.pathname; const params = new URLSearchParams(); params.set('tool', toolSlug); params.set('view', view); if (modal) params.set('modal', modal); return `${baseUrl}?${params.toString()}`; } // CLIPBOARD UTILITIES (consolidates ToolMatrix.astro + BaseLayout.astro clipboard functions) export async function copyToClipboard(text: string, button: HTMLElement): Promise { try { await navigator.clipboard.writeText(text); const originalHTML = button.innerHTML; button.innerHTML = ` Kopiert! `; button.style.color = 'var(--color-accent)'; setTimeout(() => { button.innerHTML = originalHTML; button.style.color = ''; }, 2000); } catch (err) { // Fallback for older browsers const textArea = document.createElement('textarea'); textArea.value = text; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); const originalHTML = button.innerHTML; button.innerHTML = 'Kopiert!'; setTimeout(() => { button.innerHTML = originalHTML; }, 2000); } } // TIME UTILITIES (consolidates audit trail time formatting) export function formatDuration(ms: number): string { if (ms < 1000) return '< 1s'; if (ms < 60000) return `${Math.ceil(ms / 1000)}s`; const minutes = Math.floor(ms / 60000); const seconds = Math.ceil((ms % 60000) / 1000); return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`; } // CONFIDENCE UTILITIES (consolidates AI confidence color logic) export function getConfidenceColor(confidence: number): string { if (confidence >= 80) return 'var(--color-accent)'; if (confidence >= 60) return 'var(--color-warning)'; return 'var(--color-error)'; } // Make all functions available globally for backward compatibility export function registerGlobalUtilities(): void { if (typeof window !== 'undefined') { // Tool utilities window.createToolSlug = createToolSlug; window.findToolByIdentifier = findToolByIdentifier; window.isToolHosted = isToolHosted; // Scroll utilities window.scrollToElement = scrollToElement; window.scrollToElementById = scrollToElementById; window.scrollToElementBySelector = scrollToElementBySelector; // Search utilities window.prioritizeSearchResults = prioritizeSearchResults; // Share utilities window.shareArticle = async (button: HTMLElement, url: string, title: string) => { const fullUrl = window.location.origin + url; await copyToClipboard(fullUrl, button); }; window.shareCurrentArticle = async (button: HTMLElement) => { await copyToClipboard(window.location.href, button); }; console.log('[UI Helpers] Global utilities registered'); } }