Skip to content

Commit 069f764

Browse files
fix: preserve search history
1 parent eebc6f6 commit 069f764

3 files changed

Lines changed: 202 additions & 14 deletions

File tree

web-app/css/styles.css

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,111 @@ body.sidebar-active .sidebar-dock {
22942294
opacity: 1;
22952295
}
22962296

2297+
2298+
.recent-clear-all-btn {
2299+
background: none;
2300+
border: none;
2301+
color: var(--accent);
2302+
cursor: pointer;
2303+
font-size: 0.85rem;
2304+
padding: 0 6px;
2305+
margin-left: 12px;
2306+
display: inline-flex;
2307+
align-items: center;
2308+
gap: 8px;
2309+
transition: color var(--duration-fast) ease, text-shadow var(--duration-fast) ease;
2310+
}
2311+
2312+
.recent-clear-all-btn:hover {
2313+
color: var(--accent-glow);
2314+
text-shadow: 0 0 8px var(--accent-glow);
2315+
}
2316+
2317+
.dropdown-clear-all {
2318+
padding: 12px 16px;
2319+
border-top: 1px solid var(--border);
2320+
justify-content: center;
2321+
}
2322+
2323+
.dropdown-clear-all-btn {
2324+
background: none;
2325+
border: none;
2326+
color: var(--text-secondary);
2327+
cursor: pointer;
2328+
font-size: 0.8rem;
2329+
padding: 0;
2330+
display: flex;
2331+
align-items: center;
2332+
gap: 6px;
2333+
transition: color var(--duration-fast) ease;
2334+
}
2335+
2336+
.dropdown-clear-all-btn:hover {
2337+
color: var(--accent);
2338+
}
2339+
2340+
/* Confirm modal styles */
2341+
.confirm-modal-overlay {
2342+
position: fixed;
2343+
inset: 0;
2344+
display: none;
2345+
align-items: center;
2346+
justify-content: center;
2347+
background: rgba(0,0,0,0.45);
2348+
z-index: 1200;
2349+
}
2350+
2351+
.confirm-modal-overlay.active {
2352+
display: flex;
2353+
}
2354+
2355+
.confirm-modal-content {
2356+
background: var(--surface);
2357+
color: var(--text);
2358+
border-radius: 10px;
2359+
padding: 18px;
2360+
width: 320px;
2361+
max-width: calc(100% - 40px);
2362+
box-shadow: 0 8px 30px rgba(2,6,23,0.6);
2363+
text-align: left;
2364+
}
2365+
2366+
.confirm-modal-content h3 {
2367+
margin: 0 0 8px 0;
2368+
font-size: 1.05rem;
2369+
}
2370+
2371+
.confirm-modal-content p {
2372+
margin: 0 0 16px 0;
2373+
color: var(--text-secondary);
2374+
font-size: 0.95rem;
2375+
}
2376+
2377+
.confirm-modal-actions {
2378+
display: flex;
2379+
justify-content: flex-end;
2380+
gap: 8px;
2381+
}
2382+
2383+
.btn {
2384+
padding: 8px 12px;
2385+
border-radius: 6px;
2386+
border: none;
2387+
cursor: pointer;
2388+
}
2389+
2390+
.btn-primary {
2391+
background: var(--accent);
2392+
color: var(--bg-on-accent, #fff);
2393+
}
2394+
2395+
.btn-primary:hover { box-shadow: 0 0 10px var(--accent-glow); }
2396+
2397+
.btn-secondary {
2398+
background: transparent;
2399+
color: var(--text-secondary);
2400+
}
2401+
22972402
.tips-section p {
22982403
font-size: 0.85rem;
22992404
color: var(--text-secondary);

web-app/index.html

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,10 @@
9090
<div class="dropdown-content" id="searchDropdownContent">
9191
<div class="search-loader" id="searchLoader" style="display:none"><div class="spinner"></div></div>
9292
<div class="recent-searches-section" id="recentSearchesSection">
93-
<div class="dropdown-section-title">Recent</div>
93+
<div class="dropdown-section-title">
94+
<span>Recent</span>
95+
<button id="clearRecentBtn" class="recent-clear-all-btn" aria-label="Clear recent searches">Clear All</button>
96+
</div>
9497
<div class="dropdown-list" id="recentSearchesList"></div>
9598
</div>
9699
<div class="results-section" id="resultsSection" style="display:none">
@@ -1138,5 +1141,17 @@ <h3 id="infoModalTitle">How to Play</h3>
11381141
</div>
11391142
</div>
11401143

1144+
<!-- Themed Confirm Modal (used for Clear All) -->
1145+
<div id="confirmModalOverlay" class="confirm-modal-overlay" aria-hidden="true">
1146+
<div class="confirm-modal-content" role="dialog" aria-modal="true" aria-labelledby="confirmModalTitle">
1147+
<h3 id="confirmModalTitle">Confirm</h3>
1148+
<p id="confirmModalMessage"></p>
1149+
<div class="confirm-modal-actions">
1150+
<button id="confirmCancelBtn" class="btn btn-secondary">Cancel</button>
1151+
<button id="confirmOkBtn" class="btn btn-primary">Confirm</button>
1152+
</div>
1153+
</div>
1154+
</div>
1155+
11411156
</body>
11421157
</html>

web-app/js/main.js

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,42 @@ function showInfoModal(title, steps) {
100100
overlay.addEventListener("click", overlayClick);
101101
}
102102

103+
// Themed confirmation modal (in-page) helper
104+
function showConfirm(message, onConfirm, onCancel) {
105+
var overlay = document.getElementById('confirmModalOverlay');
106+
var msg = document.getElementById('confirmModalMessage');
107+
var okBtn = document.getElementById('confirmOkBtn');
108+
var cancelBtn = document.getElementById('confirmCancelBtn');
109+
if (!overlay || !msg || !okBtn || !cancelBtn) {
110+
// fallback to window.confirm
111+
var ok = window.confirm(message);
112+
if (ok && typeof onConfirm === 'function') onConfirm();
113+
else if (!ok && typeof onCancel === 'function') onCancel();
114+
return;
115+
}
116+
117+
msg.textContent = message;
118+
overlay.classList.add('active');
119+
120+
function cleanup() {
121+
overlay.classList.remove('active');
122+
okBtn.removeEventListener('click', okHandler);
123+
cancelBtn.removeEventListener('click', cancelHandler);
124+
overlay.removeEventListener('click', overlayClick);
125+
document.removeEventListener('keydown', keyHandler);
126+
}
127+
128+
function okHandler(e) { e.stopPropagation(); cleanup(); if (typeof onConfirm === 'function') onConfirm(); }
129+
function cancelHandler(e) { e.stopPropagation(); cleanup(); if (typeof onCancel === 'function') onCancel(); }
130+
function overlayClick(e) { if (e.target === overlay) { cancelHandler(e); } }
131+
function keyHandler(e) { if (e.key === 'Escape') cancelHandler(e); }
132+
133+
okBtn.addEventListener('click', okHandler);
134+
cancelBtn.addEventListener('click', cancelHandler);
135+
overlay.addEventListener('click', overlayClick);
136+
document.addEventListener('keydown', keyHandler);
137+
}
138+
103139
var currentProjectName = "";
104140

105141
function setupModalInfoButton(projectName) {
@@ -797,7 +833,7 @@ document.addEventListener("DOMContentLoaded", function () {
797833
label.addEventListener("click", function () {
798834
syncSearchInputs(search, searchInput);
799835
currentSearchQuery = search;
800-
performSearch();
836+
performSearch(true);
801837
closeDropdown();
802838
});
803839

@@ -813,9 +849,27 @@ document.addEventListener("DOMContentLoaded", function () {
813849
renderRecentSearches();
814850
});
815851

816-
recentSearchesList.appendChild(item);
817-
});
818-
}
852+
recentSearchesList.appendChild(item);
853+
});
854+
855+
// Wire up header Clear All button (if present)
856+
var headerClearBtn = document.getElementById('clearRecentBtn');
857+
if (headerClearBtn) {
858+
headerClearBtn.style.display = recentSearches.length ? 'inline-flex' : 'none';
859+
headerClearBtn.onclick = function (e) {
860+
e.stopPropagation();
861+
if (!recentSearches || recentSearches.length === 0) return;
862+
showConfirm('Clear all recent searches? This cannot be undone.', function () {
863+
recentSearches = [];
864+
localStorage.setItem('recentSearches', JSON.stringify(recentSearches));
865+
renderRecentSearches();
866+
closeDropdown();
867+
}, function () {
868+
// cancelled
869+
});
870+
};
871+
}
872+
}
819873

820874
recentSearchesSection.style.display = "block";
821875
if (resultsSection) resultsSection.style.display = "none";
@@ -897,7 +951,7 @@ document.addEventListener("DOMContentLoaded", function () {
897951
if (!searchInput) return;
898952
searchInput.value = title;
899953
currentSearchQuery = title.toLowerCase();
900-
performSearch();
954+
performSearch(true);
901955
closeDropdown();
902956
if (projectsSection) {
903957
projectsSection.scrollIntoView({
@@ -907,7 +961,8 @@ document.addEventListener("DOMContentLoaded", function () {
907961
}
908962
}
909963

910-
function performSearch() {
964+
function performSearch(commit) {
965+
if (commit === undefined) commit = true;
911966
var query = currentSearchQuery;
912967
if (!query) {
913968
applyCategoryFilter(currentCategory);
@@ -923,12 +978,14 @@ document.addEventListener("DOMContentLoaded", function () {
923978
syncStickyTabs("all");
924979
}
925980

926-
recentSearches = recentSearches.filter(function (s) {
927-
return s !== query;
928-
});
929-
recentSearches.unshift(query);
930-
recentSearches = recentSearches.slice(0, 10);
931-
localStorage.setItem("recentSearches", JSON.stringify(recentSearches));
981+
if (commit) {
982+
recentSearches = recentSearches.filter(function (s) {
983+
return s !== query;
984+
});
985+
recentSearches.unshift(query);
986+
recentSearches = recentSearches.slice(0, 10);
987+
localStorage.setItem("recentSearches", JSON.stringify(recentSearches));
988+
}
932989

933990
var visibleCount = 0;
934991
var favorites = JSON.parse(localStorage.getItem("favorites") || "[]");
@@ -982,7 +1039,7 @@ document.addEventListener("DOMContentLoaded", function () {
9821039
currentSearchQuery = query;
9831040
if (searchLoader) searchLoader.style.display = query ? "block" : "none";
9841041
debouncedSearch(query);
985-
performSearch();
1042+
performSearch(false);
9861043
});
9871044

9881045
input.addEventListener("focus", function () {
@@ -992,11 +1049,22 @@ document.addEventListener("DOMContentLoaded", function () {
9921049
renderRecentSearches();
9931050
});
9941051

1052+
input.addEventListener("blur", function () {
1053+
if (currentSearchQuery && currentSearchQuery.trim()) {
1054+
performSearch(true);
1055+
}
1056+
});
1057+
9951058
input.addEventListener("keydown", function (e) {
9961059
if (e.key === "Escape") {
9971060
closeDropdown();
9981061
input.blur();
9991062
}
1063+
if (e.key === "Enter") {
1064+
e.preventDefault();
1065+
performSearch(true);
1066+
closeDropdown();
1067+
}
10001068
});
10011069
});
10021070
}

0 commit comments

Comments
 (0)