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;
|
z-index: 50;
|
||||||
}
|
}
|
||||||
.top-title { font-weight: 600; font-size: 1.05rem; }
|
.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 {
|
.menu-btn {
|
||||||
display: none;
|
display: none;
|
||||||
border: none;
|
border: none;
|
||||||
|
|||||||
@@ -90,12 +90,120 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||||||
|
|
||||||
initTheme();
|
initTheme();
|
||||||
initToolSearch();
|
initToolSearch();
|
||||||
|
initGlobalSearch();
|
||||||
initUploadZone();
|
initUploadZone();
|
||||||
initToolForm();
|
initToolForm();
|
||||||
initDependentOptions();
|
initDependentOptions();
|
||||||
initCapabilityStatus();
|
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 ────────────────────── */
|
/* ── Home Page Tool Search ────────────────────── */
|
||||||
function initToolSearch() {
|
function initToolSearch() {
|
||||||
const input = document.getElementById("tool-search");
|
const input = document.getElementById("tool-search");
|
||||||
|
|||||||
+5
-1
@@ -30,7 +30,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="nav-items">
|
<div class="nav-items">
|
||||||
{% for tool in cat.tools %}
|
{% 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 }}
|
<i class="bi {{ tool.icon }}"></i> {{ tool.name }}
|
||||||
</a>
|
</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -46,6 +46,10 @@
|
|||||||
<header class="top-bar">
|
<header class="top-bar">
|
||||||
<button class="menu-btn" onclick="openSidebar()"><i class="bi bi-list"></i></button>
|
<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>
|
<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">
|
<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="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>
|
<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