Poprawka botto bar i wersje językowe
This commit is contained in:
@@ -7,7 +7,19 @@ let itemModalIndex = -1;
|
||||
let itemModalTouchStart = null;
|
||||
let itemModalDragging = false;
|
||||
let itemModalAnimating = false;
|
||||
let menuLangChangeBound = false;
|
||||
|
||||
function escapeHtml(value) {
|
||||
return String(value ?? "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
}
|
||||
|
||||
function escapeAttr(value) {
|
||||
return escapeHtml(value).replace(/`/g, "`");
|
||||
}
|
||||
|
||||
function resetItemModalPane() {
|
||||
const pane = document.getElementById("itemModalPane");
|
||||
@@ -77,10 +89,10 @@ function renderMenuListImage(url) {
|
||||
const hasUrl = isValidMenuImageUrl(url);
|
||||
const imgClass = hasUrl ? "rmc-image" : "rmc-image hidden";
|
||||
const placeholderClass = hasUrl ? "menu-image-placeholder hidden" : "menu-image-placeholder";
|
||||
const srcAttr = hasUrl ? ` src="${url}"` : "";
|
||||
const srcAttr = hasUrl ? ` src="${escapeAttr(url)}"` : "";
|
||||
const onerror = hasUrl ? ' onerror="handleMenuImageError(this)"' : "";
|
||||
|
||||
return `<div class="rmc-image-wrap"><img class="${imgClass}"${srcAttr} alt="" loading="lazy"${onerror}><div class="${placeholderClass}" aria-hidden="true"><svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span>${t("menu.no_image")}</span></div></div>`;
|
||||
return `<div class="rmc-image-wrap"><img class="${imgClass}"${srcAttr} alt="" loading="lazy"${onerror}><div class="${placeholderClass}" aria-hidden="true"><svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg><span>${escapeHtml(t("menu.no_image"))}</span></div></div>`;
|
||||
}
|
||||
|
||||
function findMenuItem(categoryId, position) {
|
||||
@@ -250,13 +262,13 @@ function renderCategoryNav(categories) {
|
||||
if (!ul) return;
|
||||
|
||||
const list = Array.isArray(categories) ? categories : [];
|
||||
let html = `<li><a href="#" class="active" data-category-badge="0" onclick="showCategory(0); return false;">${t("menu.all")}</a></li>`;
|
||||
let html = `<li><a href="#" class="active" data-category-badge="0" onclick="showCategory(0); return false;">${escapeHtml(t("menu.all"))}</a></li>`;
|
||||
|
||||
list.forEach((cat) => {
|
||||
const id = String(cat?.id ?? "").trim();
|
||||
const name = String(cat?.name ?? "").trim();
|
||||
if (!id || !name) return;
|
||||
html += `<li><a href="#" data-category-badge="${id}" onclick="showCategory('${id}'); return false;">${name}</a></li>`;
|
||||
html += `<li><a href="#" data-category-badge="${escapeAttr(id)}" onclick="showCategory('${escapeAttr(id)}'); return false;">${escapeHtml(name)}</a></li>`;
|
||||
});
|
||||
|
||||
ul.innerHTML = html;
|
||||
@@ -264,11 +276,19 @@ function renderCategoryNav(categories) {
|
||||
|
||||
function showMenuLoadError(container) {
|
||||
if (!container) return;
|
||||
container.innerHTML = `<p style="text-align:center; padding: 20px; color: var(--text-muted);">${t("menu.load_error")}</p>`;
|
||||
container.innerHTML = `<p style="text-align:center; padding: 20px; color: var(--text-muted);">${escapeHtml(t("menu.load_error"))}</p>`;
|
||||
}
|
||||
|
||||
function getActiveCategoryId() {
|
||||
return (
|
||||
document.querySelector(".menu-categories-nav a.active")?.getAttribute("data-category-badge") ||
|
||||
"0"
|
||||
);
|
||||
}
|
||||
|
||||
export async function loadMenu(lang = getLang()) {
|
||||
const container = document.getElementById("menuContainer");
|
||||
const previousCategoryId = getActiveCategoryId();
|
||||
try {
|
||||
const response = await fetch(`${endpoints.menu}?lang=${encodeURIComponent(lang)}`);
|
||||
if (!response.ok) throw new Error(t("menu.load_error"));
|
||||
@@ -292,17 +312,19 @@ export async function loadMenu(lang = getLang()) {
|
||||
catDiv.className = "rm-category";
|
||||
catDiv.setAttribute("data-cat-id", catId);
|
||||
|
||||
let html = `<div class="restaurant-menu-category">${section.categoryName || ""}</div>
|
||||
let html = `<div class="restaurant-menu-category">${escapeHtml(section.categoryName || "")}</div>
|
||||
<div class="rmc-positions">`;
|
||||
|
||||
items.forEach((item) => {
|
||||
const categoryId = escapeAttr(item.categoryId);
|
||||
const position = escapeAttr(item.position);
|
||||
html += `
|
||||
<div class="rmc-position" data-position="${item.position}" data-category-id="${item.categoryId}" onclick="openItemModal('${item.categoryId}', '${item.position}')" style="cursor: pointer;">
|
||||
<div class="rmc-position" data-position="${position}" data-category-id="${categoryId}" onclick="openItemModal('${categoryId}', '${position}')" style="cursor: pointer;">
|
||||
${renderMenuListImage(item.image)}
|
||||
<div class="rmc-title">
|
||||
<h4>${item.title}<span>${item.description || ""}</span></h4>
|
||||
<h4>${escapeHtml(item.title)}<span>${escapeHtml(item.description || "")}</span></h4>
|
||||
</div>
|
||||
<div class="rmc-other"><span>${item.price}</span></div>
|
||||
<div class="rmc-other"><span>${escapeHtml(item.price)}</span></div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
@@ -313,7 +335,12 @@ export async function loadMenu(lang = getLang()) {
|
||||
});
|
||||
|
||||
renderCategoryNav(payload.categories);
|
||||
showCategory(0);
|
||||
|
||||
const safePrev = String(previousCategoryId).replace(/"/g, "");
|
||||
const categoryStillExists =
|
||||
safePrev === "0" ||
|
||||
!!document.querySelector(`.menu-categories-nav a[data-category-badge="${safePrev}"]`);
|
||||
showCategory(categoryStillExists ? previousCategoryId : 0);
|
||||
|
||||
const searchInput = document.getElementById("menuSearchInput");
|
||||
if (searchInput?.value) {
|
||||
@@ -325,13 +352,7 @@ export async function loadMenu(lang = getLang()) {
|
||||
}
|
||||
}
|
||||
|
||||
export function setMenuLanguageReload() {
|
||||
if (menuLangChangeBound) return;
|
||||
menuLangChangeBound = true;
|
||||
onLangChange(() => loadMenu());
|
||||
}
|
||||
|
||||
setMenuLanguageReload();
|
||||
onLangChange((lang) => loadMenu(lang));
|
||||
|
||||
export function openItemModal(categoryId, position) {
|
||||
itemModalKeys = buildVisibleMenuItemKeys();
|
||||
@@ -370,7 +391,8 @@ export function closeItemModal() {
|
||||
}
|
||||
|
||||
export function filterMenu() {
|
||||
const query = document.getElementById("menuSearchInput").value.toLowerCase();
|
||||
const searchInput = document.getElementById("menuSearchInput");
|
||||
const query = (searchInput?.value || "").toLowerCase();
|
||||
const now = Date.now();
|
||||
if (query.length >= 2 && (!window.lastMenuSearchEventAt || now - window.lastMenuSearchEventAt > 8000)) {
|
||||
window.lastMenuSearchEventAt = now;
|
||||
@@ -383,7 +405,7 @@ export function filterMenu() {
|
||||
const items = category.querySelectorAll(".rmc-position");
|
||||
|
||||
items.forEach((item) => {
|
||||
const title = item.querySelector(".rmc-title h4").textContent.toLowerCase();
|
||||
const title = item.querySelector(".rmc-title h4")?.textContent?.toLowerCase() || "";
|
||||
if (title.includes(query)) {
|
||||
item.style.display = "";
|
||||
hasVisibleItems = true;
|
||||
|
||||
Reference in New Issue
Block a user