dom.ts RawHistoryFolder
1type HistoryEntry = { id: number; url: string; title: string };
2type SearchShortcut = { description: string; url: string };
3
4export class DOMRenderer {
5 private historyListElement: HTMLElement | null = null;
6 private shortcutsElement: HTMLElement | null = null;
7
8 constructor() {
9 this.historyListElement = document.getElementById('historyList');
10 this.shortcutsElement = document.getElementById('shortcuts');
11 }
12
13 displayHistory(historyEntries: HistoryEntry[]): void {
14 if (!this.historyListElement) return;
15
16 if (!historyEntries || historyEntries.length === 0) {
17 this.historyListElement.innerHTML = `
18 <div class="empty-state">
19 <h3>No history yet</h3>
20 <p>Use CLI commands to browse websites</p>
21 </div>
22 `;
23 return;
24 }
25
26 this.historyListElement.innerHTML = '';
27 historyEntries.forEach(item => {
28 const historyItem = this.createHistoryItem(item);
29 this.historyListElement!.appendChild(historyItem);
30 });
31 }
32
33 displayShortcuts(shortcutsData: Record<string, SearchShortcut>): void {
34 if (!this.shortcutsElement) return;
35
36 if (!shortcutsData || Object.keys(shortcutsData).length === 0) {
37 this.shortcutsElement.innerHTML = `
38 <div class="empty-state">
39 <h3>No shortcuts configured</h3>
40 <p>Add shortcuts to your config file</p>
41 </div>
42 `;
43 return;
44 }
45
46 this.shortcutsElement.innerHTML = '';
47 Object.entries(shortcutsData).forEach(([key, shortcut]) => {
48 const shortcutEl = this.createShortcutElement(key, shortcut);
49 this.shortcutsElement!.appendChild(shortcutEl);
50 });
51 }
52
53 showLoading(container: 'history' | 'shortcuts'): void {
54 const element = container === 'history' ? this.historyListElement : this.shortcutsElement;
55 if (element) {
56 element.innerHTML = '<div class="loading">Loading...</div>';
57 }
58 }
59
60 showError(container: 'history' | 'shortcuts', message: string): void {
61 const element = container === 'history' ? this.historyListElement : this.shortcutsElement;
62 if (element) {
63 element.innerHTML = `
64 <div class="empty-state">
65 <h3>Error</h3>
66 <p>${message}</p>
67 </div>
68 `;
69 }
70 }
71
72 private createHistoryItem(item: HistoryEntry): HTMLElement {
73 const historyItem = document.createElement('div');
74 historyItem.className = 'history-item';
75 const parsed = this.safeParseURL(item.url);
76 const title = item.title && item.title.trim() !== '' ? item.title : (parsed.host || 'Untitled');
77 historyItem.innerHTML = `
78 <div class="history-url">${this.escapeHtml(item.url)}</div>
79 <div class="history-title">${this.escapeHtml(title)}</div>
80 `;
81
82 historyItem.addEventListener('click', () => {
83 this.navigateToUrl(item.url);
84 });
85
86 return historyItem;
87 }
88
89 private createShortcutElement(key: string, shortcut: SearchShortcut): HTMLElement {
90 const shortcutEl = document.createElement('div');
91 shortcutEl.className = 'shortcut';
92 shortcutEl.innerHTML = `
93 <div class="shortcut-key">${this.escapeHtml(key)}:</div>
94 <div class="shortcut-desc">${this.escapeHtml(shortcut.description)}</div>
95 `;
96
97 shortcutEl.addEventListener('click', () => {
98 // Navigate to the base URL of the shortcut (remove %s template parameter)
99 const baseUrl = this.extractBaseUrl(shortcut.url);
100 this.navigateToUrl(baseUrl);
101 });
102
103 return shortcutEl;
104 }
105
106 private async navigateToUrl(url: string): Promise<void> {
107 console.log('Navigating to:', url);
108 window.location.href = url;
109 }
110
111 private extractBaseUrl(templateUrl: string): string {
112 try {
113 const url = new URL(templateUrl);
114 // Remove query parameters that contain %s template
115 const params = new URLSearchParams(url.search);
116 for (const [key, value] of params.entries()) {
117 if (value.includes('%s')) {
118 params.delete(key);
119 }
120 }
121 url.search = params.toString();
122 return url.toString();
123 } catch (error) {
124 // If URL parsing fails, return the original URL
125 console.warn('Failed to parse shortcut URL:', templateUrl);
126 return templateUrl;
127 }
128 }
129
130 private async copyToClipboard(text: string): Promise<void> {
131 try {
132 await navigator.clipboard.writeText(text);
133 console.log('Copied to clipboard:', text);
134 } catch (err) {
135 console.error('Failed to copy:', err);
136 }
137 }
138
139 private escapeHtml(text: string): string {
140 const div = document.createElement('div');
141 div.textContent = text;
142 return div.innerHTML;
143 }
144
145 private safeParseURL(raw: string): { host: string } {
146 try {
147 const u = new URL(raw);
148 return { host: u.host };
149 } catch {
150 return { host: '' };
151 }
152 }
153}