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 = `
`;
}
}
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: '' };
}
}
}