mirror of
https://codeberg.org/listyantidewi/your-everyday-tools.git
synced 2026-07-01 23:17:37 +08:00
Add global search functionality for tools
- Introduced a global search input in the top bar for easy access to tools. - Implemented JavaScript logic to filter and display search results dynamically. - Enhanced CSS for the search input and dropdown results, including styling for empty states. - Updated HTML to include necessary elements for the global search feature.
This commit is contained in:
@@ -163,6 +163,87 @@ a { color: var(--primary); text-decoration: none; }
|
||||
z-index: 50;
|
||||
}
|
||||
.top-title { font-weight: 600; font-size: 1.05rem; }
|
||||
|
||||
.global-search {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
max-width: 360px;
|
||||
}
|
||||
.global-search input {
|
||||
width: 100%;
|
||||
padding: .4rem .8rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: .9rem;
|
||||
font-family: var(--font);
|
||||
outline: none;
|
||||
transition: border-color .15s, box-shadow .15s;
|
||||
}
|
||||
.global-search input:focus {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 3px rgba(67,97,238,.12);
|
||||
}
|
||||
.global-search-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: calc(100% + .4rem);
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow);
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
z-index: 200;
|
||||
}
|
||||
.global-search-dropdown.open { display: block; }
|
||||
.global-search-result {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
padding: .55rem .85rem;
|
||||
color: var(--text);
|
||||
font-size: .875rem;
|
||||
text-decoration: none;
|
||||
transition: background .1s;
|
||||
}
|
||||
.global-search-result:hover,
|
||||
.global-search-result.active { background: var(--bg); color: var(--text); }
|
||||
.global-search-result .result-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .1rem;
|
||||
min-width: 0;
|
||||
}
|
||||
.global-search-result .result-name { font-weight: 500; }
|
||||
.global-search-result .result-desc {
|
||||
font-size: .75rem;
|
||||
color: var(--text-light);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.global-search-result .result-cat {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
font-size: .72rem;
|
||||
color: var(--text-light);
|
||||
white-space: nowrap;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: .1rem .4rem;
|
||||
}
|
||||
.global-search-empty {
|
||||
padding: .8rem;
|
||||
color: var(--text-light);
|
||||
font-size: .85rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.menu-btn {
|
||||
display: none;
|
||||
border: none;
|
||||
|
||||
@@ -90,12 +90,120 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
|
||||
initTheme();
|
||||
initToolSearch();
|
||||
initGlobalSearch();
|
||||
initUploadZone();
|
||||
initToolForm();
|
||||
initDependentOptions();
|
||||
initCapabilityStatus();
|
||||
});
|
||||
|
||||
/* ── Global Toolbar Search ────────────────────── */
|
||||
function initGlobalSearch() {
|
||||
const wrapper = document.getElementById("global-search");
|
||||
const input = document.getElementById("global-search-input");
|
||||
const dropdown = document.getElementById("global-search-dropdown");
|
||||
if (!wrapper || !input || !dropdown) return;
|
||||
|
||||
// Hide on home page; show everywhere else
|
||||
if (window.location.pathname === "/") {
|
||||
wrapper.style.display = "none";
|
||||
return;
|
||||
}
|
||||
wrapper.style.display = "";
|
||||
|
||||
// Build tool list from sidebar nav items (already in DOM)
|
||||
const tools = [];
|
||||
document.querySelectorAll(".nav-category").forEach(cat => {
|
||||
const catBtn = cat.querySelector(".nav-category-btn");
|
||||
const catName = catBtn ? catBtn.textContent.trim() : "";
|
||||
cat.querySelectorAll(".nav-item").forEach(a => {
|
||||
tools.push({
|
||||
name: a.textContent.trim(),
|
||||
href: a.getAttribute("href"),
|
||||
cat: catName,
|
||||
desc: a.dataset.desc || "",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let activeIdx = -1;
|
||||
|
||||
function openDropdown() {
|
||||
dropdown.classList.add("open");
|
||||
input.setAttribute("aria-expanded", "true");
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
dropdown.classList.remove("open");
|
||||
input.setAttribute("aria-expanded", "false");
|
||||
activeIdx = -1;
|
||||
}
|
||||
|
||||
function renderResults(query) {
|
||||
if (!query) { closeDropdown(); return; }
|
||||
|
||||
const matches = tools.filter(t =>
|
||||
t.name.toLowerCase().includes(query) ||
|
||||
t.cat.toLowerCase().includes(query) ||
|
||||
t.desc.includes(query)
|
||||
).slice(0, 8);
|
||||
|
||||
if (matches.length === 0) {
|
||||
dropdown.innerHTML = `<div class="global-search-empty">No tools found.</div>`;
|
||||
openDropdown();
|
||||
return;
|
||||
}
|
||||
|
||||
dropdown.innerHTML = matches.map((t, i) => `
|
||||
<a href="${t.href}" class="global-search-result" role="option" data-idx="${i}">
|
||||
<span class="result-info">
|
||||
<span class="result-name">${escapeHtml(t.name)}</span>
|
||||
<span class="result-desc">${escapeHtml(t.desc)}</span>
|
||||
</span>
|
||||
<span class="result-cat">${escapeHtml(t.cat)}</span>
|
||||
</a>
|
||||
`).join("");
|
||||
activeIdx = -1;
|
||||
openDropdown();
|
||||
}
|
||||
|
||||
function setActive(idx) {
|
||||
const items = dropdown.querySelectorAll(".global-search-result");
|
||||
items.forEach(el => el.classList.remove("active"));
|
||||
if (idx >= 0 && idx < items.length) {
|
||||
items[idx].classList.add("active");
|
||||
items[idx].scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
activeIdx = idx;
|
||||
}
|
||||
|
||||
input.addEventListener("input", () => {
|
||||
renderResults(input.value.trim().toLowerCase());
|
||||
});
|
||||
|
||||
input.addEventListener("keydown", e => {
|
||||
const items = dropdown.querySelectorAll(".global-search-result");
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
setActive(Math.min(activeIdx + 1, items.length - 1));
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setActive(Math.max(activeIdx - 1, 0));
|
||||
} else if (e.key === "Enter") {
|
||||
if (activeIdx >= 0 && items[activeIdx]) {
|
||||
window.location.href = items[activeIdx].getAttribute("href");
|
||||
}
|
||||
} else if (e.key === "Escape") {
|
||||
input.blur();
|
||||
closeDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("click", e => {
|
||||
if (!wrapper.contains(e.target)) closeDropdown();
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Home Page Tool Search ────────────────────── */
|
||||
function initToolSearch() {
|
||||
const input = document.getElementById("tool-search");
|
||||
|
||||
+5
-1
@@ -30,7 +30,7 @@
|
||||
</button>
|
||||
<div class="nav-items">
|
||||
{% for tool in cat.tools %}
|
||||
<a href="/{{ cat.id }}/{{ tool.id }}" class="nav-item">
|
||||
<a href="/{{ cat.id }}/{{ tool.id }}" class="nav-item" data-desc="{{ tool.desc | lower }}">
|
||||
<i class="bi {{ tool.icon }}"></i> {{ tool.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
@@ -46,6 +46,10 @@
|
||||
<header class="top-bar">
|
||||
<button class="menu-btn" onclick="openSidebar()"><i class="bi bi-list"></i></button>
|
||||
<span class="top-title">{% block top_title %}Your Everyday Tools{% endblock %}</span>
|
||||
<div class="global-search" id="global-search">
|
||||
<input type="text" id="global-search-input" placeholder="Search tools..." autocomplete="off" aria-label="Search tools" aria-expanded="false" aria-controls="global-search-dropdown">
|
||||
<div class="global-search-dropdown" id="global-search-dropdown" role="listbox"></div>
|
||||
</div>
|
||||
<div class="theme-switcher" id="theme-switcher" role="group" aria-label="Color theme">
|
||||
<button class="theme-btn" data-theme-mode="system" title="System theme"><i class="bi bi-circle-half"></i></button>
|
||||
<button class="theme-btn" data-theme-mode="light" title="Light theme"><i class="bi bi-sun-fill"></i></button>
|
||||
|
||||
Reference in New Issue
Block a user