Beta-2.1 - The clearer, the faster #22

Merged
Djeex merged 27 commits from Beta-2.1 into main 2025-09-04 12:50:26 +02:00
24 changed files with 1307 additions and 907 deletions
Showing only changes of commit 69d303c7a1 - Show all commits

View File

@ -12,7 +12,6 @@ function showToast(message, type = "success", duration = 3000) {
}, duration); }, duration);
} }
// --- Loader helpers ---
function showLoader(text = "Uploading...") { function showLoader(text = "Uploading...") {
const loader = document.getElementById("global-loader"); const loader = document.getElementById("global-loader");
if (loader) { if (loader) {
@ -27,77 +26,65 @@ function hideLoader() {
document.addEventListener("DOMContentLoaded", () => { document.addEventListener("DOMContentLoaded", () => {
// --- Section Forms --- // --- Section Forms ---
const infoForm = document.getElementById("info-form"); const forms = {
const socialForm = document.getElementById("social-form"); info: document.getElementById("info-form"),
const menuForm = document.getElementById("menu-form"); social: document.getElementById("social-form"),
const footerForm = document.getElementById("footer-form"); menu: document.getElementById("menu-form"),
const legalsForm = document.getElementById("legals-form"); footer: document.getElementById("footer-form"),
const buildForm = document.getElementById("build-form"); legals: document.getElementById("legals-form"),
build: document.getElementById("build-form")
};
// --- Menu logic --- // --- Menu logic ---
const menuList = document.getElementById("menu-items-list"); const menuList = document.getElementById("menu-items-list");
const addMenuBtn = document.getElementById("add-menu-item"); const addMenuBtn = document.getElementById("add-menu-item");
let menuItems = []; let menuItems = [];
function renderMenuItems() { function renderMenuItems() {
menuList.innerHTML = ""; menuList.innerHTML = "";
menuItems.forEach((item, idx) => { menuItems.forEach((item, idx) => {
const div = document.createElement("div"); menuList.innerHTML += `
div.style.display = "flex"; <div style="display:flex;gap:8px;margin-bottom:6px;">
div.style.gap = "8px";
div.style.marginBottom = "6px";
div.innerHTML = `
<input type="text" placeholder="Label" value="${item.label || ""}" style="flex:1;" data-idx="${idx}" data-type="label" required> <input type="text" placeholder="Label" value="${item.label || ""}" style="flex:1;" data-idx="${idx}" data-type="label" required>
<input type="text" placeholder="?tag=tag1,tag2" value="${item.href || ""}" style="flex:2;" data-idx="${idx}" data-type="href" required> <input type="text" placeholder="?tag=tag1,tag2" value="${item.href || ""}" style="flex:2;" data-idx="${idx}" data-type="href" required>
<button type="button" class="remove-menu-item" data-idx="${idx}">🗑</button> <button type="button" class="remove-menu-item" data-idx="${idx}">🗑</button>
</div>
`; `;
menuList.appendChild(div);
}); });
} }
function updateMenuItemsFromInputs() { function updateMenuItemsFromInputs() {
const inputs = menuList.querySelectorAll("input"); const inputs = menuList.querySelectorAll("input");
const items = []; menuItems = [];
for (let i = 0; i < inputs.length; i += 2) { for (let i = 0; i < inputs.length; i += 2) {
const label = inputs[i].value.trim(); const label = inputs[i].value.trim();
const href = inputs[i + 1].value.trim(); const href = inputs[i + 1].value.trim();
if (label || href) items.push({ label, href }); if (label || href) menuItems.push({ label, href });
} }
menuItems = items;
} }
// --- Intellectual property paragraphs logic --- // --- Intellectual property paragraphs logic ---
const ipList = document.getElementById("ip-list"); const ipList = document.getElementById("ip-list");
const addIpBtn = document.getElementById("add-ip-paragraph"); const addIpBtn = document.getElementById("add-ip-paragraph");
let ipParagraphs = []; let ipParagraphs = [];
function renderIpParagraphs() { function renderIpParagraphs() {
ipList.innerHTML = ""; ipList.innerHTML = "";
ipParagraphs.forEach((item, idx) => { ipParagraphs.forEach((item, idx) => {
const div = document.createElement("div"); ipList.innerHTML += `
div.style.display = "flex"; <div style="display:flex;gap:8px;margin-bottom:6px;">
div.style.gap = "8px";
div.style.marginBottom = "6px";
div.innerHTML = `
<textarea placeholder="Paragraph" required style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea> <textarea placeholder="Paragraph" required style="flex:1;" data-idx="${idx}">${item.paragraph || ""}</textarea>
<button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button> <button type="button" class="remove-ip-paragraph" data-idx="${idx}">🗑</button>
</div>
`; `;
ipList.appendChild(div);
}); });
} }
function updateIpParagraphsFromInputs() { function updateIpParagraphsFromInputs() {
const textareas = ipList.querySelectorAll("textarea"); ipParagraphs = Array.from(ipList.querySelectorAll("textarea"))
ipParagraphs = Array.from(textareas).map(textarea => ({ .map(textarea => ({ paragraph: textarea.value.trim() }))
paragraph: textarea.value.trim() .filter(item => item.paragraph !== "");
})).filter(item => item.paragraph !== "");
} }
// --- Build options --- // --- Build options & Theme select ---
const convertImagesCheckbox = document.getElementById("convert-images-checkbox"); const convertImagesCheckbox = document.getElementById("convert-images-checkbox");
const resizeImagesCheckbox = document.getElementById("resize-images-checkbox"); const resizeImagesCheckbox = document.getElementById("resize-images-checkbox");
// --- Theme select ---
const themeSelect = document.getElementById("theme-select"); const themeSelect = document.getElementById("theme-select");
// --- Thumbnail upload and modal logic --- // --- Thumbnail upload and modal logic ---
@ -107,40 +94,30 @@ document.addEventListener("DOMContentLoaded", () => {
const thumbnailPreview = document.getElementById("thumbnail-preview"); const thumbnailPreview = document.getElementById("thumbnail-preview");
const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn"); const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn");
// --- Modal elements for delete confirmation --- // --- Modal helpers ---
const deleteModal = document.getElementById("delete-modal"); function setupModal(modal, closeBtn, confirmBtn, cancelBtn, onConfirm) {
const deleteModalClose = document.getElementById("delete-modal-close"); if (!modal) return;
const deleteModalConfirm = document.getElementById("delete-modal-confirm"); if (closeBtn) closeBtn.onclick = () => modal.style.display = "none";
const deleteModalCancel = document.getElementById("delete-modal-cancel"); if (cancelBtn) cancelBtn.onclick = () => modal.style.display = "none";
window.addEventListener("click", (e) => {
if (e.target === modal) modal.style.display = "none";
});
if (confirmBtn && onConfirm) confirmBtn.onclick = onConfirm;
}
// --- Modal elements for theme deletion --- // --- Thumbnail preview logic ---
const deleteThemeModal = document.getElementById("delete-theme-modal");
const deleteThemeModalClose = document.getElementById("delete-theme-modal-close");
const deleteThemeModalConfirm = document.getElementById("delete-theme-modal-confirm");
const deleteThemeModalCancel = document.getElementById("delete-theme-modal-cancel");
const deleteThemeModalText = document.getElementById("delete-theme-modal-text");
let themeToDelete = null;
// --- Show/hide thumbnail preview, remove button, and choose button ---
function updateThumbnailPreview(src) { function updateThumbnailPreview(src) {
if (thumbnailPreview) { if (thumbnailPreview) {
thumbnailPreview.src = src || ""; thumbnailPreview.src = src || "";
thumbnailPreview.style.display = src ? "block" : "none"; thumbnailPreview.style.display = src ? "block" : "none";
} }
if (removeThumbnailBtn) { if (removeThumbnailBtn) removeThumbnailBtn.style.display = src ? "inline-block" : "none";
removeThumbnailBtn.style.display = src ? "inline-block" : "none"; if (chooseThumbnailBtn) chooseThumbnailBtn.style.display = src ? "none" : "inline-block";
}
if (chooseThumbnailBtn) {
chooseThumbnailBtn.style.display = src ? "none" : "inline-block";
}
} }
// --- Choose thumbnail button triggers file input ---
if (chooseThumbnailBtn && thumbnailUpload) { if (chooseThumbnailBtn && thumbnailUpload) {
chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click()); chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click());
} }
// --- Handle thumbnail upload and refresh preview (with cache busting) ---
if (thumbnailUpload) { if (thumbnailUpload) {
thumbnailUpload.addEventListener("change", async (e) => { thumbnailUpload.addEventListener("change", async (e) => {
const file = e.target.files[0]; const file = e.target.files[0];
@ -161,25 +138,17 @@ document.addEventListener("DOMContentLoaded", () => {
updateSectionStatus("social"); updateSectionStatus("social");
}); });
} }
// --- Remove thumbnail button triggers modal ---
if (removeThumbnailBtn) { if (removeThumbnailBtn) {
removeThumbnailBtn.addEventListener("click", () => { removeThumbnailBtn.addEventListener("click", () => {
deleteModal.style.display = "flex"; document.getElementById("delete-modal").style.display = "flex";
}); });
} }
setupModal(
// --- Modal logic for thumbnail deletion --- document.getElementById("delete-modal"),
if (deleteModal && deleteModalClose && deleteModalConfirm && deleteModalCancel) { document.getElementById("delete-modal-close"),
deleteModalClose.onclick = deleteModalCancel.onclick = () => { document.getElementById("delete-modal-confirm"),
deleteModal.style.display = "none"; document.getElementById("delete-modal-cancel"),
}; async () => {
window.onclick = function(event) {
if (event.target === deleteModal) {
deleteModal.style.display = "none";
}
};
deleteModalConfirm.onclick = async () => {
const res = await fetch("/api/thumbnail/remove", { method: "POST" }); const res = await fetch("/api/thumbnail/remove", { method: "POST" });
const result = await res.json(); const result = await res.json();
if (result.status === "ok") { if (result.status === "ok") {
@ -189,41 +158,28 @@ document.addEventListener("DOMContentLoaded", () => {
} else { } else {
showToast("❌ Error removing thumbnail", "error"); showToast("❌ Error removing thumbnail", "error");
} }
deleteModal.style.display = "none"; document.getElementById("delete-modal").style.display = "none";
updateSectionStatus("social"); updateSectionStatus("social");
};
} }
);
// --- Theme upload logic (custom theme folder) --- // --- Theme upload logic ---
const themeUpload = document.getElementById("theme-upload"); const themeUpload = document.getElementById("theme-upload");
const chooseThemeBtn = document.getElementById("choose-theme-btn"); const chooseThemeBtn = document.getElementById("choose-theme-btn");
if (chooseThemeBtn && themeUpload) { if (chooseThemeBtn && themeUpload) {
chooseThemeBtn.addEventListener("click", () => themeUpload.click()); chooseThemeBtn.addEventListener("click", () => themeUpload.click());
themeUpload.addEventListener("change", async (e) => { themeUpload.addEventListener("change", async (e) => {
const files = Array.from(e.target.files); const files = Array.from(e.target.files);
if (files.length === 0) return; if (!files.length) return;
showLoader("Uploading theme..."); showLoader("Uploading theme...");
const formData = new FormData(); const formData = new FormData();
files.forEach(file => { files.forEach(file => formData.append("files", file, file.webkitRelativePath || file.name));
formData.append("files", file, file.webkitRelativePath || file.name);
});
const res = await fetch("/api/theme/upload", { method: "POST", body: formData }); const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
const result = await res.json(); const result = await res.json();
hideLoader(); hideLoader();
if (result.status === "ok") { if (result.status === "ok") {
showToast("✅ Theme uploaded!", "success"); showToast("✅ Theme uploaded!", "success");
// Refresh theme select after upload refreshThemes();
fetch("/api/themes")
.then(res => res.json())
.then(themes => {
themeSelect.innerHTML = "";
themes.forEach(theme => {
const option = document.createElement("option");
option.value = theme;
option.textContent = theme;
themeSelect.appendChild(option);
});
});
} else { } else {
showToast("❌ Error uploading theme", "error"); showToast("❌ Error uploading theme", "error");
} }
@ -231,8 +187,9 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} }
// --- Remove theme button triggers modal --- // --- Remove theme logic ---
const removeThemeBtn = document.getElementById("remove-theme-btn"); const removeThemeBtn = document.getElementById("remove-theme-btn");
let themeToDelete = null;
if (removeThemeBtn && themeSelect) { if (removeThemeBtn && themeSelect) {
removeThemeBtn.addEventListener("click", () => { removeThemeBtn.addEventListener("click", () => {
const theme = themeSelect.value; const theme = themeSelect.value;
@ -242,24 +199,16 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
themeToDelete = theme; themeToDelete = theme;
deleteThemeModalText.textContent = `Are you sure you want to remove theme "${theme}"?`; document.getElementById("delete-theme-modal-text").textContent = `Are you sure you want to remove theme "${theme}"?`;
deleteThemeModal.style.display = "flex"; document.getElementById("delete-theme-modal").style.display = "flex";
}); });
} }
setupModal(
// --- Modal logic for theme deletion --- document.getElementById("delete-theme-modal"),
if (deleteThemeModal && deleteThemeModalClose && deleteThemeModalConfirm && deleteThemeModalCancel) { document.getElementById("delete-theme-modal-close"),
deleteThemeModalClose.onclick = deleteThemeModalCancel.onclick = () => { document.getElementById("delete-theme-modal-confirm"),
deleteThemeModal.style.display = "none"; document.getElementById("delete-theme-modal-cancel"),
themeToDelete = null; async () => {
};
window.onclick = function(event) {
if (event.target === deleteThemeModal) {
deleteThemeModal.style.display = "none";
themeToDelete = null;
}
};
deleteThemeModalConfirm.onclick = async () => {
if (!themeToDelete) return; if (!themeToDelete) return;
showLoader("Removing theme..."); showLoader("Removing theme...");
const res = await fetch("/api/theme/remove", { const res = await fetch("/api/theme/remove", {
@ -271,7 +220,18 @@ document.addEventListener("DOMContentLoaded", () => {
hideLoader(); hideLoader();
if (result.status === "ok") { if (result.status === "ok") {
showToast("✅ Theme removed!", "success"); showToast("✅ Theme removed!", "success");
// Refresh theme select refreshThemes();
} else {
showToast(result.error || "❌ Error removing theme", "error");
}
document.getElementById("delete-theme-modal").style.display = "none";
themeToDelete = null;
updateSectionStatus("build");
}
);
// --- Theme select refresh ---
function refreshThemes() {
fetch("/api/themes") fetch("/api/themes")
.then(res => res.json()) .then(res => res.json())
.then(themes => { .then(themes => {
@ -282,17 +242,11 @@ document.addEventListener("DOMContentLoaded", () => {
option.textContent = theme; option.textContent = theme;
themeSelect.appendChild(option); themeSelect.appendChild(option);
}); });
loadConfigAndUpdateBuildStatus();
}); });
} else {
showToast(result.error || "❌ Error removing theme", "error");
}
deleteThemeModal.style.display = "none";
themeToDelete = null;
updateSectionStatus("build");
};
} }
// --- Fetch theme list and populate select, then load config and update build status --- // --- Config loading ---
let loadedConfig = {}; let loadedConfig = {};
function loadConfigAndUpdateBuildStatus() { function loadConfigAndUpdateBuildStatus() {
fetch("/api/site-info") fetch("/api/site-info")
@ -300,17 +254,17 @@ document.addEventListener("DOMContentLoaded", () => {
.then(data => { .then(data => {
loadedConfig = data; loadedConfig = data;
// Info // Info
if (infoForm) { if (forms.info) {
infoForm.elements["info.title"].value = data.info?.title || ""; forms.info.elements["info.title"].value = data.info?.title || "";
infoForm.elements["info.subtitle"].value = data.info?.subtitle || ""; forms.info.elements["info.subtitle"].value = data.info?.subtitle || "";
infoForm.elements["info.description"].value = data.info?.description || ""; forms.info.elements["info.description"].value = data.info?.description || "";
infoForm.elements["info.canonical"].value = data.info?.canonical || ""; forms.info.elements["info.canonical"].value = data.info?.canonical || "";
infoForm.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || ""); forms.info.elements["info.keywords"].value = Array.isArray(data.info?.keywords) ? data.info.keywords.join(", ") : (data.info?.keywords || "");
infoForm.elements["info.author"].value = data.info?.author || ""; forms.info.elements["info.author"].value = data.info?.author || "";
} }
// Social // Social
if (socialForm) { if (forms.social) {
socialForm.elements["social.instagram_url"].value = data.social?.instagram_url || ""; forms.social.elements["social.instagram_url"].value = data.social?.instagram_url || "";
if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || ""; if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || "";
updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : ""); updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}?t=${Date.now()}` : "");
} }
@ -318,66 +272,37 @@ document.addEventListener("DOMContentLoaded", () => {
menuItems = Array.isArray(data.menu?.items) ? data.menu.items : []; menuItems = Array.isArray(data.menu?.items) ? data.menu.items : [];
renderMenuItems(); renderMenuItems();
// Footer // Footer
if (footerForm) { if (forms.footer) {
footerForm.elements["footer.copyright"].value = data.footer?.copyright || ""; forms.footer.elements["footer.copyright"].value = data.footer?.copyright || "";
footerForm.elements["footer.legal_label"].value = data.footer?.legal_label || ""; forms.footer.elements["footer.legal_label"].value = data.footer?.legal_label || "";
} }
// Legals // Legals
ipParagraphs = Array.isArray(data.legals?.intellectual_property) ipParagraphs = Array.isArray(data.legals?.intellectual_property)
? data.legals.intellectual_property ? data.legals.intellectual_property
: []; : [];
renderIpParagraphs(); renderIpParagraphs();
if (legalsForm) { if (forms.legals) {
legalsForm.elements["legals.hoster_name"].value = data.legals?.hoster_name || ""; forms.legals.elements["legals.hoster_name"].value = data.legals?.hoster_name || "";
legalsForm.elements["legals.hoster_address"].value = data.legals?.hoster_address || ""; forms.legals.elements["legals.hoster_address"].value = data.legals?.hoster_address || "";
legalsForm.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || ""; forms.legals.elements["legals.hoster_contact"].value = data.legals?.hoster_contact || "";
} }
// Build // Build
if (themeSelect) { if (themeSelect) themeSelect.value = data.build?.theme || "";
themeSelect.value = data.build?.theme || ""; if (convertImagesCheckbox) convertImagesCheckbox.checked = !!data.build?.convert_images;
} if (resizeImagesCheckbox) resizeImagesCheckbox.checked = !!data.build?.resize_images;
if (convertImagesCheckbox) {
convertImagesCheckbox.checked = !!data.build?.convert_images;
}
if (resizeImagesCheckbox) {
resizeImagesCheckbox.checked = !!data.build?.resize_images;
}
// Initial status update for all sections except build
["info", "social", "menu", "footer", "legals"].forEach(updateSectionStatus); ["info", "social", "menu", "footer", "legals"].forEach(updateSectionStatus);
// For build, update status after theme select is set
updateSectionStatus("build"); updateSectionStatus("build");
}); });
} }
if (themeSelect) refreshThemes();
else loadConfigAndUpdateBuildStatus();
if (themeSelect) { // --- Add/remove menu items ---
fetch("/api/themes") if (addMenuBtn) addMenuBtn.addEventListener("click", () => {
.then(res => res.json())
.then(themes => {
themeSelect.innerHTML = "";
themes.forEach(theme => {
const option = document.createElement("option");
option.value = theme;
option.textContent = theme;
themeSelect.appendChild(option);
});
// Now load config and update build status after theme select is ready
loadConfigAndUpdateBuildStatus();
});
} else {
// If no theme select, just load config
loadConfigAndUpdateBuildStatus();
}
// --- Add menu item ---
if (addMenuBtn) {
addMenuBtn.addEventListener("click", () => {
menuItems.push({ label: "", href: "" }); menuItems.push({ label: "", href: "" });
renderMenuItems(); renderMenuItems();
updateSectionStatus("menu"); updateSectionStatus("menu");
}); });
}
// --- Remove menu item ---
menuList.addEventListener("click", (e) => { menuList.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-menu-item")) { if (e.target.classList.contains("remove-menu-item")) {
const idx = parseInt(e.target.getAttribute("data-idx")); const idx = parseInt(e.target.getAttribute("data-idx"));
@ -386,23 +311,17 @@ document.addEventListener("DOMContentLoaded", () => {
updateSectionStatus("menu"); updateSectionStatus("menu");
} }
}); });
// --- Update menuItems on input change ---
menuList.addEventListener("input", () => { menuList.addEventListener("input", () => {
updateMenuItemsFromInputs(); updateMenuItemsFromInputs();
updateSectionStatus("menu"); updateSectionStatus("menu");
}); });
// --- Add paragraph --- // --- Add/remove IP paragraphs ---
if (addIpBtn) { if (addIpBtn) addIpBtn.addEventListener("click", () => {
addIpBtn.addEventListener("click", () => {
ipParagraphs.push({ paragraph: "" }); ipParagraphs.push({ paragraph: "" });
renderIpParagraphs(); renderIpParagraphs();
updateSectionStatus("legals"); updateSectionStatus("legals");
}); });
}
// --- Remove paragraph ---
ipList.addEventListener("click", (e) => { ipList.addEventListener("click", (e) => {
if (e.target.classList.contains("remove-ip-paragraph")) { if (e.target.classList.contains("remove-ip-paragraph")) {
const idx = parseInt(e.target.getAttribute("data-idx")); const idx = parseInt(e.target.getAttribute("data-idx"));
@ -411,8 +330,6 @@ document.addEventListener("DOMContentLoaded", () => {
updateSectionStatus("legals"); updateSectionStatus("legals");
} }
}); });
// --- Update ipParagraphs on input change ---
ipList.addEventListener("input", () => { ipList.addEventListener("input", () => {
updateIpParagraphsFromInputs(); updateIpParagraphsFromInputs();
updateSectionStatus("legals"); updateSectionStatus("legals");
@ -423,16 +340,16 @@ document.addEventListener("DOMContentLoaded", () => {
switch (section) { switch (section) {
case "info": case "info":
return { return {
title: infoForm.elements["info.title"].value, title: forms.info.elements["info.title"].value,
subtitle: infoForm.elements["info.subtitle"].value, subtitle: forms.info.elements["info.subtitle"].value,
description: infoForm.elements["info.description"].value, description: forms.info.elements["info.description"].value,
canonical: infoForm.elements["info.canonical"].value, canonical: forms.info.elements["info.canonical"].value,
keywords: infoForm.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean), keywords: forms.info.elements["info.keywords"].value.split(",").map(i => i.trim()).filter(Boolean),
author: infoForm.elements["info.author"].value author: forms.info.elements["info.author"].value
}; };
case "social": case "social":
return { return {
instagram_url: socialForm.elements["social.instagram_url"].value, instagram_url: forms.social.elements["social.instagram_url"].value,
thumbnail: thumbnailInput ? thumbnailInput.value : "" thumbnail: thumbnailInput ? thumbnailInput.value : ""
}; };
case "menu": case "menu":
@ -440,15 +357,15 @@ document.addEventListener("DOMContentLoaded", () => {
return { items: menuItems }; return { items: menuItems };
case "footer": case "footer":
return { return {
copyright: footerForm.elements["footer.copyright"].value, copyright: forms.footer.elements["footer.copyright"].value,
legal_label: footerForm.elements["footer.legal_label"].value legal_label: forms.footer.elements["footer.legal_label"].value
}; };
case "legals": case "legals":
updateIpParagraphsFromInputs(); updateIpParagraphsFromInputs();
return { return {
hoster_name: legalsForm.elements["legals.hoster_name"].value, hoster_name: forms.legals.elements["legals.hoster_name"].value,
hoster_address: legalsForm.elements["legals.hoster_address"].value, hoster_address: forms.legals.elements["legals.hoster_address"].value,
hoster_contact: legalsForm.elements["legals.hoster_contact"].value, hoster_contact: forms.legals.elements["legals.hoster_contact"].value,
intellectual_property: ipParagraphs intellectual_property: ipParagraphs
}; };
case "build": case "build":
@ -533,7 +450,7 @@ document.addEventListener("DOMContentLoaded", () => {
values.intellectual_property.every(ip => ip.paragraph) values.intellectual_property.every(ip => ip.paragraph)
); );
case "build": case "build":
return !!values.theme; // Only check theme is present return !!values.theme;
default: default:
return true; return true;
} }
@ -561,46 +478,22 @@ document.addEventListener("DOMContentLoaded", () => {
} }
// --- Listen for changes in each section --- // --- Listen for changes in each section ---
[ Object.entries(forms).forEach(([section, form]) => {
{ form: infoForm, section: "info" },
{ form: socialForm, section: "social" },
{ form: menuForm, section: "menu" },
{ form: footerForm, section: "footer" },
{ form: legalsForm, section: "legals" },
{ form: buildForm, section: "build" }
].forEach(({ form, section }) => {
if (!form) return; if (!form) return;
form.addEventListener("input", () => updateSectionStatus(section)); form.addEventListener("input", () => updateSectionStatus(section));
form.addEventListener("change", () => updateSectionStatus(section)); form.addEventListener("change", () => updateSectionStatus(section));
});
// --- Save section handler (form submit) ---
[
{ form: infoForm, section: "info" },
{ form: socialForm, section: "social" },
{ form: menuForm, section: "menu" },
{ form: footerForm, section: "footer" },
{ form: legalsForm, section: "legals" },
{ form: buildForm, section: "build" }
].forEach(({ form, section }) => {
if (!form) return;
form.addEventListener("submit", async (e) => { form.addEventListener("submit", async (e) => {
e.preventDefault(); e.preventDefault();
// Native browser validation
if (!form.reportValidity()) { if (!form.reportValidity()) {
showToast("❌ Please fill all required fields before saving.", "error"); showToast("❌ Please fill all required fields before saving.", "error");
updateSectionStatus(section); updateSectionStatus(section);
return; return;
} }
// Social section: check thumbnail if (section === "social" && (!thumbnailInput || !thumbnailInput.value)) {
if (section === "social") {
if (!thumbnailInput || !thumbnailInput.value) {
showToast("❌ Thumbnail is required.", "error"); showToast("❌ Thumbnail is required.", "error");
updateSectionStatus(section); updateSectionStatus(section);
return; return;
} }
}
// Menu section: check all menu items
if (section === "menu") { if (section === "menu") {
updateMenuItemsFromInputs(); updateMenuItemsFromInputs();
if (!menuItems.length || !menuItems.every(item => item.label && item.href)) { if (!menuItems.length || !menuItems.every(item => item.label && item.href)) {
@ -609,7 +502,6 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
} }
// Legals section: check all paragraphs
if (section === "legals") { if (section === "legals") {
updateIpParagraphsFromInputs(); updateIpParagraphsFromInputs();
if (!ipParagraphs.length || !ipParagraphs.every(ip => ip.paragraph)) { if (!ipParagraphs.length || !ipParagraphs.every(ip => ip.paragraph)) {
@ -618,10 +510,8 @@ document.addEventListener("DOMContentLoaded", () => {
return; return;
} }
} }
// Build payload for this section only
let payload = {}; let payload = {};
payload[section] = getSectionValues(section); payload[section] = getSectionValues(section);
const res = await fetch("/api/site-info", { const res = await fetch("/api/site-info", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@ -630,7 +520,6 @@ document.addEventListener("DOMContentLoaded", () => {
const result = await res.json(); const result = await res.json();
if (result.status === "ok") { if (result.status === "ok") {
showToast("✅ Section saved!", "success"); showToast("✅ Section saved!", "success");
// Reload config for this section
fetch("/api/site-info") fetch("/api/site-info")
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
@ -642,5 +531,4 @@ document.addEventListener("DOMContentLoaded", () => {
} }
}); });
}); });
}); });