first deployment of kikomiko with url photos
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
8d98304137
commit
ae3ed7c7b7
Vendored
+149
@@ -0,0 +1,149 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user