150 lines
4.3 KiB
JavaScript
150 lines
4.3 KiB
JavaScript
import {
|
|
displayName,
|
|
escapeSearchHtml,
|
|
formatSearchPrice,
|
|
loadSearchCatalog,
|
|
searchCatalogProducts,
|
|
} from "/js/search-catalog.js?v=persianCopyRtl2";
|
|
|
|
const PREVIEW_COUNT = 4;
|
|
const MIN_CHARS = 2;
|
|
const DEBOUNCE_MS = 180;
|
|
|
|
function debounce(fn, ms) {
|
|
let t = 0;
|
|
return (...args) => {
|
|
window.clearTimeout(t);
|
|
t = window.setTimeout(() => fn(...args), ms);
|
|
};
|
|
}
|
|
|
|
function suggestItemHtml(p) {
|
|
const title = displayName(p);
|
|
const href = `/products/${escapeSearchHtml(p.slug)}/`;
|
|
const img = p.heroImage
|
|
? `<img src="${escapeSearchHtml(p.heroImage)}" alt="" width="40" height="50" loading="lazy" />`
|
|
: `<span class="search-suggest-ph" aria-hidden="true"></span>`;
|
|
return `<a class="search-suggest-item" role="option" href="${href}">
|
|
${img}
|
|
<span class="search-suggest-meta">
|
|
<span class="search-suggest-name">${escapeSearchHtml(title)}</span>
|
|
<span class="search-suggest-price">${formatSearchPrice(p.price)}</span>
|
|
</span>
|
|
</a>`;
|
|
}
|
|
|
|
function bindSearchInput(input, panel) {
|
|
if (!input || !panel) return;
|
|
|
|
let products = null;
|
|
let active = -1;
|
|
|
|
loadSearchCatalog()
|
|
.then((list) => {
|
|
products = list;
|
|
})
|
|
.catch(() => {
|
|
products = [];
|
|
});
|
|
|
|
function close() {
|
|
panel.hidden = true;
|
|
panel.innerHTML = "";
|
|
active = -1;
|
|
input.setAttribute("aria-expanded", "false");
|
|
}
|
|
|
|
function open() {
|
|
panel.hidden = false;
|
|
input.setAttribute("aria-expanded", "true");
|
|
}
|
|
|
|
function render(q) {
|
|
if (!products) return;
|
|
const hits = searchCatalogProducts(products, q, PREVIEW_COUNT);
|
|
if (!hits.length) {
|
|
close();
|
|
return;
|
|
}
|
|
const allHref = `/search/?q=${encodeURIComponent(q.trim())}`;
|
|
panel.innerHTML =
|
|
hits.map(suggestItemHtml).join("") +
|
|
`<a class="search-suggest-all" href="${allHref}">مشاهده همه نتایج</a>`;
|
|
open();
|
|
active = -1;
|
|
}
|
|
|
|
const onInput = debounce(() => {
|
|
const q = input.value.trim();
|
|
if (q.length < MIN_CHARS) {
|
|
close();
|
|
return;
|
|
}
|
|
render(q);
|
|
}, DEBOUNCE_MS);
|
|
|
|
input.addEventListener("input", onInput);
|
|
input.addEventListener("focus", () => {
|
|
const q = input.value.trim();
|
|
if (q.length >= MIN_CHARS) render(q);
|
|
});
|
|
|
|
input.addEventListener("keydown", (e) => {
|
|
const items = [...panel.querySelectorAll(".search-suggest-item, .search-suggest-all")];
|
|
if (!items.length || panel.hidden) return;
|
|
|
|
if (e.key === "ArrowDown") {
|
|
e.preventDefault();
|
|
active = Math.min(active + 1, items.length - 1);
|
|
items.forEach((el, i) => el.classList.toggle("is-active", i === active));
|
|
items[active]?.scrollIntoView({ block: "nearest" });
|
|
} else if (e.key === "ArrowUp") {
|
|
e.preventDefault();
|
|
active = Math.max(active - 1, 0);
|
|
items.forEach((el, i) => el.classList.toggle("is-active", i === active));
|
|
items[active]?.scrollIntoView({ block: "nearest" });
|
|
} else if (e.key === "Enter" && active >= 0) {
|
|
e.preventDefault();
|
|
items[active].click();
|
|
} else if (e.key === "Escape") {
|
|
close();
|
|
input.blur();
|
|
}
|
|
});
|
|
|
|
document.addEventListener("click", (e) => {
|
|
if (!(e.target instanceof Element)) return;
|
|
if (input.contains(e.target) || panel.contains(e.target)) return;
|
|
close();
|
|
});
|
|
}
|
|
|
|
export function initSearchSuggest() {
|
|
document.querySelectorAll(".search-shell").forEach((shell) => {
|
|
const input = shell.querySelector('input[type="search"]');
|
|
const panel = shell.querySelector("[data-search-suggest]");
|
|
bindSearchInput(input, panel);
|
|
});
|
|
|
|
document.querySelectorAll(".mobile-search").forEach((form) => {
|
|
const input = form.querySelector('input[type="search"]');
|
|
let panel = form.querySelector("[data-search-suggest]");
|
|
if (!panel) {
|
|
panel = document.createElement("div");
|
|
panel.className = "search-suggest search-suggest--mobile";
|
|
panel.setAttribute("data-search-suggest", "");
|
|
panel.hidden = true;
|
|
form.appendChild(panel);
|
|
}
|
|
bindSearchInput(input, panel);
|
|
});
|
|
|
|
const params = new URLSearchParams(location.search);
|
|
const q = params.get("q");
|
|
if (q && location.pathname.replace(/\/+$/, "").endsWith("/search")) {
|
|
document.querySelectorAll('.search-form input[name="q"], .mobile-search input[name="q"]').forEach((input) => {
|
|
input.value = q;
|
|
});
|
|
}
|
|
}
|