Poprawka botto bar i wersje językowe

This commit is contained in:
2026-09-25 17:39:44 +02:00
parent 3056eb6c3d
commit a381ad09d8
21 changed files with 770 additions and 428 deletions
+42 -20
View File
@@ -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, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function escapeAttr(value) {
return escapeHtml(value).replace(/`/g, "&#96;");
}
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;