Skip to content

Commit 2b2f0d2

Browse files
authored
Create history.html
1 parent e67e09a commit 2b2f0d2

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

history.html

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Vision History</title>
7+
<script src="https://cdn.tailwindcss.com"></script>
8+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9+
<style>
10+
body {
11+
font-family: 'Inter', sans-serif;
12+
background-color: #0f0f10;
13+
color: #ffffff;
14+
margin: 0;
15+
padding: 0;
16+
height: 100vh;
17+
display: flex;
18+
flex-direction: column;
19+
user-select: none;
20+
}
21+
22+
.header {
23+
background-color: #1c1c1e;
24+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
25+
padding: 1rem 2rem;
26+
display: flex;
27+
align-items: center;
28+
gap: 2rem;
29+
position: sticky;
30+
top: 0;
31+
z-index: 10;
32+
}
33+
34+
.search-box {
35+
background: rgba(255, 255, 255, 0.05);
36+
border: 1px solid rgba(255, 255, 255, 0.1);
37+
border-radius: 8px;
38+
display: flex;
39+
align-items: center;
40+
padding: 0 12px;
41+
flex: 1;
42+
max-width: 600px;
43+
}
44+
45+
.search-box input {
46+
background: transparent;
47+
border: none;
48+
outline: none;
49+
color: white;
50+
width: 100%;
51+
height: 36px;
52+
font-size: 14px;
53+
padding: 0 8px;
54+
}
55+
56+
.history-container {
57+
flex: 1;
58+
overflow-y: auto;
59+
padding: 2rem;
60+
max-width: 1000px;
61+
margin: 0 auto;
62+
width: 100%;
63+
}
64+
65+
.history-day-group {
66+
margin-bottom: 2rem;
67+
}
68+
69+
.day-title {
70+
font-size: 13px;
71+
font-weight: 700;
72+
color: #8b5cf6;
73+
text-transform: uppercase;
74+
letter-spacing: 0.1em;
75+
margin-bottom: 1rem;
76+
padding-bottom: 0.5rem;
77+
border-bottom: 1px solid rgba(139, 92, 246, 0.2);
78+
}
79+
80+
.history-item {
81+
display: flex;
82+
align-items: center;
83+
padding: 10px 14px;
84+
border-radius: 10px;
85+
transition: all 0.2s ease;
86+
cursor: pointer;
87+
gap: 1rem;
88+
margin-bottom: 2px;
89+
}
90+
91+
.history-item:hover {
92+
background: rgba(255, 255, 255, 0.03);
93+
}
94+
95+
.item-time {
96+
font-size: 12px;
97+
color: rgba(255, 255, 255, 0.4);
98+
min-width: 60px;
99+
}
100+
101+
.favicon-container {
102+
width: 18px;
103+
height: 18px;
104+
flex-shrink: 0;
105+
display: flex;
106+
align-items: center;
107+
justify-content: center;
108+
}
109+
110+
.item-favicon {
111+
width: 100%;
112+
height: 100%;
113+
border-radius: 3px;
114+
object-fit: cover;
115+
}
116+
117+
.favicon-fallback {
118+
width: 100%;
119+
height: 100%;
120+
border-radius: 3px;
121+
background: rgba(139, 92, 246, 0.1);
122+
display: flex;
123+
align-items: center;
124+
justify-content: center;
125+
font-size: 8px;
126+
color: #8b5cf6;
127+
font-weight: 900;
128+
}
129+
130+
.item-title {
131+
font-size: 14px;
132+
font-weight: 500;
133+
color: rgba(255, 255, 255, 0.9);
134+
white-space: nowrap;
135+
overflow: hidden;
136+
text-overflow: ellipsis;
137+
flex: 1;
138+
}
139+
140+
.item-url {
141+
font-size: 12px;
142+
color: rgba(255, 255, 255, 0.3);
143+
white-space: nowrap;
144+
overflow: hidden;
145+
text-overflow: ellipsis;
146+
max-width: 250px;
147+
}
148+
149+
.item-actions {
150+
opacity: 0;
151+
transition: opacity 0.2s;
152+
}
153+
154+
.history-item:hover .item-actions {
155+
opacity: 1;
156+
}
157+
158+
.btn-clear {
159+
padding: 8px 16px;
160+
background: #ef4444;
161+
color: white;
162+
border-radius: 6px;
163+
font-size: 12px;
164+
font-weight: 600;
165+
transition: background 0.2s;
166+
}
167+
168+
.btn-clear:hover {
169+
background: #dc2626;
170+
}
171+
172+
.empty-state {
173+
display: flex;
174+
flex-direction: column;
175+
align-items: center;
176+
justify-content: center;
177+
height: 50vh;
178+
color: rgba(255, 255, 255, 0.2);
179+
}
180+
181+
/* Custom scrollbar */
182+
::-webkit-scrollbar { width: 8px; }
183+
::-webkit-scrollbar-track { background: transparent; }
184+
::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.05); border-radius: 4px; }
185+
::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.1); }
186+
</style>
187+
</head>
188+
<body>
189+
<div class="header">
190+
<div class="flex items-center gap-3">
191+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
192+
<h1 class="font-bold text-lg">History</h1>
193+
</div>
194+
195+
<div class="search-box">
196+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
197+
<input type="text" id="search-input" placeholder="Search history">
198+
</div>
199+
200+
<button id="clear-all" class="ml-auto btn-clear">Clear all</button>
201+
</div>
202+
203+
<div id="history-container" class="history-container">
204+
<!-- History items will be injected here -->
205+
</div>
206+
207+
<script>
208+
const container = document.getElementById('history-container');
209+
const searchInput = document.getElementById('search-input');
210+
const clearBtn = document.getElementById('clear-all');
211+
212+
function loadHistory() {
213+
const history = JSON.parse(localStorage.getItem('vision_history') || '[]');
214+
renderHistory(history);
215+
}
216+
217+
function renderHistory(items) {
218+
container.innerHTML = '';
219+
220+
const filter = searchInput.value.toLowerCase();
221+
const filtered = items.filter(item =>
222+
(item.title && item.title.toLowerCase().includes(filter)) ||
223+
(item.url && item.url.toLowerCase().includes(filter))
224+
);
225+
226+
if (filtered.length === 0) {
227+
container.innerHTML = `
228+
<div class="empty-state">
229+
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="mb-4"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
230+
<p>${filter ? 'No entries match your search.' : 'Your browsing history is clean.'}</p>
231+
</div>
232+
`;
233+
return;
234+
}
235+
236+
// Group by date
237+
const groups = {};
238+
filtered.forEach(item => {
239+
const date = new Date(item.time);
240+
const dateStr = date.toLocaleDateString([], { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' });
241+
if (!groups[dateStr]) groups[dateStr] = [];
242+
groups[dateStr].push(item);
243+
});
244+
245+
Object.entries(groups).forEach(([date, dayItems]) => {
246+
const dayGroup = document.createElement('div');
247+
dayGroup.className = 'history-day-group';
248+
dayGroup.innerHTML = `<div class="day-title">${date}</div>`;
249+
250+
dayItems.forEach(item => {
251+
const timeStr = new Date(item.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
252+
const itemEl = document.createElement('div');
253+
itemEl.className = 'history-item';
254+
255+
const faviconHtml = item.favicon
256+
? `<img src="${item.favicon}" class="item-favicon" onerror="this.outerHTML='<div class=\\'favicon-fallback\\'>V</div>'">`
257+
: `<div class="favicon-fallback">V</div>`;
258+
259+
itemEl.innerHTML = `
260+
<div class="item-time">${timeStr}</div>
261+
<div class="favicon-container">${faviconHtml}</div>
262+
<div class="item-title" title="${item.title}">${item.title}</div>
263+
<div class="item-url">${item.url}</div>
264+
<div class="item-actions">
265+
<button class="delete-item p-1 hover:bg-red-500/20 rounded transition-colors text-red-400" data-id="${item.id}">
266+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
267+
</button>
268+
</div>
269+
`;
270+
271+
itemEl.onclick = (e) => {
272+
if (e.target.closest('.delete-item')) return;
273+
window.parent.postMessage({ type: 'navigate', query: item.url }, '*');
274+
};
275+
276+
itemEl.querySelector('.delete-item').onclick = (e) => {
277+
e.stopPropagation();
278+
deleteItem(item.id);
279+
};
280+
281+
dayGroup.appendChild(itemEl);
282+
});
283+
284+
container.appendChild(dayGroup);
285+
});
286+
}
287+
288+
function deleteItem(id) {
289+
const history = JSON.parse(localStorage.getItem('vision_history') || '[]');
290+
const updated = history.filter(item => item.id !== id);
291+
localStorage.setItem('vision_history', JSON.stringify(updated));
292+
renderHistory(updated);
293+
}
294+
295+
clearBtn.onclick = () => {
296+
if (confirm('Are you sure you want to clear your entire history?')) {
297+
localStorage.setItem('vision_history', '[]');
298+
renderHistory([]);
299+
}
300+
};
301+
302+
searchInput.oninput = () => {
303+
const history = JSON.parse(localStorage.getItem('vision_history') || '[]');
304+
renderHistory(history);
305+
};
306+
307+
loadHistory();
308+
window.addEventListener('focus', loadHistory);
309+
</script>
310+
</body>
311+
</html>

0 commit comments

Comments
 (0)