type HistoryEntry = { id: number; url: string; title: string }; type SearchShortcut = { description: string; url: string }; export class DOMRenderer { private historyListElement: HTMLElement | null = null; private shortcutsElement: HTMLElement | null = null; constructor() { this.historyListElement = document.getElementById('historyList'); this.shortcutsElement = document.getElementById('shortcuts'); } displayHistory(historyEntries: HistoryEntry[]): void { if (!this.historyListElement) return; if (!historyEntries || historyEntries.length === 0) { this.historyListElement.innerHTML = `

No history yet

Use CLI commands to browse websites

`; return; } this.historyListElement.innerHTML = ''; historyEntries.forEach(item => { const historyItem = this.createHistoryItem(item); this.historyListElement!.appendChild(historyItem); }); } displayShortcuts(shortcutsData: Record): void { if (!this.shortcutsElement) return; if (!shortcutsData || Object.keys(shortcutsData).length === 0) { this.shortcutsElement.innerHTML = `

No shortcuts configured

Add shortcuts to your config file

`; return; } this.shortcutsElement.innerHTML = ''; Object.entries(shortcutsData).forEach(([key, shortcut]) => { const shortcutEl = this.createShortcutElement(key, shortcut); this.shortcutsElement!.appendChild(shortcutEl); }); } showLoading(container: 'history' | 'shortcuts'): void { const element = container === 'history' ? this.historyListElement : this.shortcutsElement; if (element) { element.innerHTML = '
Loading...
'; } } showError(container: 'history' | 'shortcuts', message: string): void { const element = container === 'history' ? this.historyListElement : this.shortcutsElement; if (element) { element.innerHTML = `

Error

${message}

`; } } private createHistoryItem(item: HistoryEntry): HTMLElement { const historyItem = document.createElement('div'); historyItem.className = 'history-item'; const parsed = this.safeParseURL(item.url); const title = item.title && item.title.trim() !== '' ? item.title : (parsed.host || 'Untitled'); historyItem.innerHTML = `
${this.escapeHtml(item.url)}
${this.escapeHtml(title)}
`; historyItem.addEventListener('click', () => { this.navigateToUrl(item.url); }); return historyItem; } private createShortcutElement(key: string, shortcut: SearchShortcut): HTMLElement { const shortcutEl = document.createElement('div'); shortcutEl.className = 'shortcut'; shortcutEl.innerHTML = `
${this.escapeHtml(key)}:
${this.escapeHtml(shortcut.description)}
`; shortcutEl.addEventListener('click', () => { // Navigate to the base URL of the shortcut (remove %s template parameter) const baseUrl = this.extractBaseUrl(shortcut.url); this.navigateToUrl(baseUrl); }); return shortcutEl; } private async navigateToUrl(url: string): Promise { console.log('Navigating to:', url); window.location.href = url; } private extractBaseUrl(templateUrl: string): string { try { const url = new URL(templateUrl); // Remove query parameters that contain %s template const params = new URLSearchParams(url.search); for (const [key, value] of params.entries()) { if (value.includes('%s')) { params.delete(key); } } url.search = params.toString(); return url.toString(); } catch (error) { // If URL parsing fails, return the original URL console.warn('Failed to parse shortcut URL:', templateUrl); return templateUrl; } } private async copyToClipboard(text: string): Promise { try { await navigator.clipboard.writeText(text); console.log('Copied to clipboard:', text); } catch (err) { console.error('Failed to copy:', err); } } private escapeHtml(text: string): string { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } private safeParseURL(raw: string): { host: string } { try { const u = new URL(raw); return { host: u.host }; } catch { return { host: '' }; } } }