diff --git a/config/photos/thumbnail.png b/config/photos/thumbnail.png deleted file mode 100644 index 9626d55..0000000 Binary files a/config/photos/thumbnail.png and /dev/null differ diff --git a/config/site.yaml b/config/site.yaml index 166b129..0e09717 100644 --- a/config/site.yaml +++ b/config/site.yaml @@ -1,36 +1,26 @@ -# Please change this by your settings. info: - title: - subtitle: - description: - canonical: - keywords: - author: - + title: '' + subtitle: '' + description: '' + canonical: '' + keywords: [] + author: '' social: - instagram_url: - thumbnail: - + instagram_url: '' + thumbnail: '' menu: items: - label: Home href: / - footer: copyright: Copyright © 2025 - legal_link: '/legals/' legal_label: Legal notice - -# Build parameters build: - theme: modern # choose a theme in config/theme folder - convert_images: true # true to enable image conversion - resize_images: true # true to enable image resizing - -# Change this by your legals + theme: modern + convert_images: true + resize_images: true legals: - hoster_name: - hoster_adress: - hoster_contact: - intellectual_property: - - paragraph: "" + hoster_name: '' + hoster_adress: '' + hoster_contact: '' + intellectual_property: [] diff --git a/src/py/webui/webui.py b/src/py/webui/webui.py index ae1c554..4565144 100644 --- a/src/py/webui/webui.py +++ b/src/py/webui/webui.py @@ -181,6 +181,22 @@ def upload_thumbnail(): yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) return jsonify({"status": "ok", "filename": filename}) +@app.route("/api/thumbnail/remove", methods=["POST"]) +def remove_thumbnail(): + PHOTOS_DIR = app.config["PHOTOS_DIR"] + thumbnail_path = PHOTOS_DIR / "thumbnail.png" + # Remove thumbnail file if exists + if thumbnail_path.exists(): + thumbnail_path.unlink() + # Update site.yaml to remove thumbnail key + with open(SITE_YAML, "r") as f: + data = yaml.safe_load(f) + if "social" in data and "thumbnail" in data["social"]: + data["social"]["thumbnail"] = "" + with open(SITE_YAML, "w") as f: + yaml.safe_dump(data, f, sort_keys=False, allow_unicode=True) + return jsonify({"status": "ok"}) + # --- Run server --- if __name__ == "__main__": logging.info("Starting WebUI at http://127.0.0.1:5000") diff --git a/src/webui/js/site-info.js b/src/webui/js/site-info.js index 7e4e9b5..733871f 100644 --- a/src/webui/js/site-info.js +++ b/src/webui/js/site-info.js @@ -1,6 +1,19 @@ +function showToast(message, type = "success", duration = 3000) { + const container = document.getElementById("toast-container"); + if (!container) return; + const toast = document.createElement("div"); + toast.className = `toast ${type}`; + toast.textContent = message; + container.appendChild(toast); + requestAnimationFrame(() => toast.classList.add("show")); + setTimeout(() => { + toast.classList.remove("show"); + toast.addEventListener("transitionend", () => toast.remove()); + }, duration); +} + document.addEventListener("DOMContentLoaded", () => { const form = document.getElementById("site-info-form"); - const status = document.getElementById("site-info-status"); const menuList = document.getElementById("menu-items-list"); const addMenuBtn = document.getElementById("add-menu-item"); @@ -45,7 +58,7 @@ document.addEventListener("DOMContentLoaded", () => { div.style.gap = "8px"; div.style.marginBottom = "6px"; div.innerHTML = ` - + `; ipList.appendChild(div); @@ -53,9 +66,9 @@ document.addEventListener("DOMContentLoaded", () => { } function updateIpParagraphsFromInputs() { - const inputs = ipList.querySelectorAll("input"); - ipParagraphs = Array.from(inputs).map(input => ({ - paragraph: input.value.trim() + const textareas = ipList.querySelectorAll("textarea"); + ipParagraphs = Array.from(textareas).map(textarea => ({ + paragraph: textarea.value.trim() })).filter(item => item.paragraph !== ""); } @@ -66,10 +79,36 @@ document.addEventListener("DOMContentLoaded", () => { // --- Theme select --- const themeSelect = document.getElementById("theme-select"); - // --- Thumbnail upload --- + // --- Thumbnail upload & modal logic --- const thumbnailInput = form?.elements["social.thumbnail"]; const thumbnailUpload = document.getElementById("thumbnail-upload"); + const chooseThumbnailBtn = document.getElementById("choose-thumbnail-btn"); const thumbnailPreview = document.getElementById("thumbnail-preview"); + const removeThumbnailBtn = document.getElementById("remove-thumbnail-btn"); + + // Modal elements + const deleteModal = document.getElementById("delete-modal"); + const deleteModalClose = document.getElementById("delete-modal-close"); + const deleteModalConfirm = document.getElementById("delete-modal-confirm"); + const deleteModalCancel = document.getElementById("delete-modal-cancel"); + + // Show/hide remove button, preview, and choose button + function updateThumbnailPreview(src) { + if (thumbnailPreview) { + thumbnailPreview.src = src || ""; + thumbnailPreview.style.display = src ? "block" : "none"; + } + if (removeThumbnailBtn) { + removeThumbnailBtn.style.display = src ? "inline-block" : "none"; + } + if (chooseThumbnailBtn) { + chooseThumbnailBtn.style.display = src ? "none" : "inline-block"; + } + } + + if (chooseThumbnailBtn && thumbnailUpload) { + chooseThumbnailBtn.addEventListener("click", () => thumbnailUpload.click()); + } if (thumbnailUpload) { thumbnailUpload.addEventListener("change", async (e) => { @@ -81,19 +120,45 @@ document.addEventListener("DOMContentLoaded", () => { const result = await res.json(); if (result.status === "ok") { if (thumbnailInput) thumbnailInput.value = result.filename; - if (thumbnailPreview) { - thumbnailPreview.src = `/photos/${result.filename}`; - thumbnailPreview.style.display = "block"; - } - status.textContent = "✅ Thumbnail uploaded!"; - setTimeout(() => status.textContent = "", 2000); + updateThumbnailPreview(`/photos/${result.filename}`); + showToast("Thumbnail uploaded!", "success"); } else { - status.textContent = "❌ Error uploading thumbnail"; - setTimeout(() => status.textContent = "", 2000); + showToast("Error uploading thumbnail", "error"); } }); } + // Attach modal logic to remove button + if (removeThumbnailBtn) { + removeThumbnailBtn.addEventListener("click", () => { + deleteModal.style.display = "flex"; + }); + } + + // Modal logic + if (deleteModal && deleteModalClose && deleteModalConfirm && deleteModalCancel) { + deleteModalClose.onclick = deleteModalCancel.onclick = () => { + deleteModal.style.display = "none"; + }; + 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 result = await res.json(); + if (result.status === "ok") { + if (thumbnailInput) thumbnailInput.value = ""; + updateThumbnailPreview(""); + showToast("Thumbnail removed!", "success"); + } else { + showToast("Error removing thumbnail", "error"); + } + deleteModal.style.display = "none"; + }; + } + // Fetch theme list and populate select if (themeSelect) { fetch("/api/themes") @@ -134,10 +199,7 @@ document.addEventListener("DOMContentLoaded", () => { form.elements["info.author"].value = data.info?.author || ""; form.elements["social.instagram_url"].value = data.social?.instagram_url || ""; if (thumbnailInput) thumbnailInput.value = data.social?.thumbnail || ""; - if (thumbnailPreview && data.social?.thumbnail) { - thumbnailPreview.src = `/photos/${data.social.thumbnail}`; - thumbnailPreview.style.display = "block"; - } + updateThumbnailPreview(data.social?.thumbnail ? `/photos/${data.social.thumbnail}` : ""); form.elements["footer.copyright"].value = data.footer?.copyright || ""; form.elements["footer.legal_label"].value = data.footer?.legal_label || ""; if (themeSelect) { @@ -207,7 +269,6 @@ document.addEventListener("DOMContentLoaded", () => { updateMenuItemsFromInputs(); updateIpParagraphsFromInputs(); - // --- Build object with checkboxes and theme select --- const build = { theme: themeSelect ? themeSelect.value : "", convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked), @@ -248,8 +309,11 @@ document.addEventListener("DOMContentLoaded", () => { body: JSON.stringify(payload) }); const result = await res.json(); - status.textContent = result.status === "ok" ? "✅ Saved!" : "❌ Error saving"; - setTimeout(() => status.textContent = "", 2000); + if (result.status === "ok") { + showToast("Site info saved!", "success"); + } else { + showToast("Error saving site info", "error"); + } }); } }); \ No newline at end of file diff --git a/src/webui/site-info/index.html b/src/webui/site-info/index.html index ae293a0..5fbd7a1 100644 --- a/src/webui/site-info/index.html +++ b/src/webui/site-info/index.html @@ -31,9 +31,9 @@ @@ -43,107 +43,131 @@

Edit Site Info

+
Info -
+
+
-
-
+
+
-
-
+
+
- -
-
- - -
-
+ +
+
+ + +
+
-
-
+
+
+
+
Social +
- + + + + + +
-
- - -
-
- - - -
+
+
Menu -
+
Footer -
- - -
-
- - -
-
-
- Build -
- - -
-
- - -
-
- - +
+
+ + +
+
+ + +
+
Legals -
- - +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ +
-
- - +
+ +
+ Build +
+
+ + + + +
-
- - -
-
- -
- -
- -
+ - +
+ + + - - + \ No newline at end of file diff --git a/src/webui/style/style.css b/src/webui/style/style.css index 5799845..aafe85b 100644 --- a/src/webui/style/style.css +++ b/src/webui/style/style.css @@ -468,44 +468,145 @@ h1, h2 { /* --- Site Info --- */ -#site-info-form input, #site-info-form textarea { - display: block; - margin-top: 5px; - border: none; - backdrop-filter: blur(20px); - background: #00293054; - border-radius: 5px; - outline: none; - font-size: 14px; - font-weight: 400; - color: #fff; - border-radius: 5px; - border: 1px solid #aaa; - padding: 0 15px; - height: 42px; - margin: 8px 0; +/* --- Site Info Form --- */ +#site-info-form { + max-width: 950px; } -#site-info-form label { - font-size: 12px; - font-weight: 500; +#site-info-form fieldset { + border: none; + margin-bottom: 28px; + padding: 0; + background: rgba(0, 41, 48, 0.45); + border-radius: 18px; + box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.25); + padding: 32px 28px; + margin: 32px auto; + backdrop-filter: blur(12px); +} + +#site-info-form legend { + font-size: 1.2em; + font-weight: 700; + color: #26c4ff; + margin-bottom: 12px; + letter-spacing: 1px; +} + +#site-info-form .fields { + display: flex; + flex-wrap: wrap; + gap: 18px; } #site-info-form .input-field { - display: flex; - width: calc(100% / 3 - 15px); + flex: 1 1 calc(33.333% - 18px); + min-width: 220px; + max-width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; + margin-bottom: 10px; +} + +#site-info-form label { + font-size: 13px; + font-weight: 600; + color: #b8eaff; + margin-bottom: 6px; + letter-spacing: 0.5px; +} + +#site-info-form input, +#site-info-form textarea, +#site-info-form select { + background: rgba(4, 44, 60, 0.55); + color: #fff; + border: 1px solid #26c4ff33; + border-radius: 8px; + font-size: 15px; + font-weight: 400; + padding: 10px 14px; + margin-bottom: 4px; + outline: none; + transition: border-color 0.2s, background 0.2s; + box-shadow: 0 2px 8px rgba(0,0,0,0.07); +} + +#site-info-form input:focus, +#site-info-form textarea:focus, +#site-info-form select:focus { + border-color: #26c4ff; + background: rgba(38, 196, 255, 0.09); +} + +#site-info-form textarea { + min-height: 60px; + resize: vertical; +} + +#site-info-form input[type="file"] { + background: none; + color: #fff; + border: none; + padding: 0; + margin-top: 2px; +} + +#site-info-form img#thumbnail-preview { + margin-top: 8px; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(38,196,255,0.12); + border: 1px solid #26c4ff33; +} + +#site-info-form button[type="submit"] { + background: linear-gradient(135deg, #26c4ff, #016074); + color: #fff; + font-weight: 700; + border: none; + border-radius: 30px; + padding: 12px 32px; + font-size: 1.1em; + margin-top: 18px; + cursor: pointer; + box-shadow: 0 4px 16px rgba(38,196,255,0.15); + transition: background 0.2s; +} + +#site-info-form button[type="submit"]:hover { + background: linear-gradient(135deg, #72d9ff, #26657e); +} + +#site-info-form button[type="button"] { + background: #074053; + color: #fff; + border: none; + border-radius: 18px; + padding: 7px 18px; + font-size: 0.98em; + margin-top: 8px; + cursor: pointer; + box-shadow: 0 2px 8px rgba(38,196,255,0.09); + transition: background 0.2s; +} + +#site-info-form button[type="button"]:hover { + background: #26c4ff; + color: #002a30; +} + +@media (max-width: 900px) { + #site-info-form { + padding: 18px 8px; + } + #site-info-form .fields, + #site-info-form fieldset { flex-direction: column; - margin: 4px 0; -} - -#site-info-form fieldset{ - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - border:none; -} - -h3 { - display: block; + gap: 0; + } + #site-info-form .input-field { + min-width: 100%; + margin-bottom: 12px; + } } \ No newline at end of file