4 steps OK

This commit is contained in:
Djeex
2025-08-20 20:19:24 +02:00
parent f6e6a11fc1
commit cb91b92555
6 changed files with 206 additions and 32 deletions

View File

@ -8,8 +8,8 @@ colors:
secondary: '#00b0f0' secondary: '#00b0f0'
accent: '#ffc700' accent: '#ffc700'
text_dark: '#616161' text_dark: '#616161'
background: '#fff' background: '#ffffff'
browser_color: '#fff' browser_color: '#ffffff'
favicon: favicon:
path: favicon.png path: favicon.png
google_fonts: google_fonts:

View File

@ -8,8 +8,8 @@ colors:
secondary: '#00b0f0' secondary: '#00b0f0'
accent: '#ffc700' accent: '#ffc700'
text_dark: '#616161' text_dark: '#616161'
background: '#fff' background: '#ffffff'
browser_color: '#fff' browser_color: '#ffffff'
favicon: favicon:
path: favicon.png path: favicon.png
fonts: fonts:

View File

@ -259,6 +259,25 @@ def upload_theme():
file.save(dest_path) file.save(dest_path)
return jsonify({"status": "ok", "theme": folder_name}) return jsonify({"status": "ok", "theme": folder_name})
@app.route("/api/theme/remove", methods=["POST"])
def remove_theme():
"""Remove a custom theme folder."""
data = request.get_json()
theme_name = data.get("theme")
if not theme_name:
return jsonify({"error": "❌ Missing theme"}), 400
themes_dir = Path(__file__).resolve().parents[3] / "config" / "themes"
theme_folder = themes_dir / theme_name
if not theme_folder.exists() or not theme_folder.is_dir():
return jsonify({"error": "❌ Theme not found"}), 404
# Prevent removing default themes
if theme_name in ["modern", "classic"]:
return jsonify({"error": "❌ Cannot remove default theme"}), 400
# Remove folder and all contents
import shutil
shutil.rmtree(theme_folder)
return jsonify({"status": "ok"})
# --- Theme editor page & API --- # --- Theme editor page & API ---
@app.route("/theme-editor") @app.route("/theme-editor")
def theme_editor(): def theme_editor():
@ -284,6 +303,20 @@ def api_theme_info():
save_theme_yaml(theme_name, theme_yaml) save_theme_yaml(theme_name, theme_yaml)
return jsonify({"status": "ok"}) return jsonify({"status": "ok"})
@app.route("/api/theme-google-fonts", methods=["POST"])
def update_theme_google_fonts():
"""Update only google_fonts in theme.yaml for current theme."""
data = request.get_json()
theme_name = data.get("theme_name")
google_fonts = data.get("google_fonts", [])
theme_yaml_path = Path(__file__).resolve().parents[3] / "config" / "themes" / theme_name / "theme.yaml"
with open(theme_yaml_path, "r") as f:
theme_yaml = yaml.safe_load(f)
theme_yaml["google_fonts"] = google_fonts
with open(theme_yaml_path, "w") as f:
yaml.safe_dump(theme_yaml, f, sort_keys=False, allow_unicode=True)
return jsonify({"status": "ok"})
@app.route("/api/local-fonts") @app.route("/api/local-fonts")
def api_local_fonts(): def api_local_fonts():
"""List local fonts for a theme.""" """List local fonts for a theme."""

View File

@ -98,6 +98,14 @@ document.addEventListener("DOMContentLoaded", () => {
const deleteModalConfirm = document.getElementById("delete-modal-confirm"); const deleteModalConfirm = document.getElementById("delete-modal-confirm");
const deleteModalCancel = document.getElementById("delete-modal-cancel"); const deleteModalCancel = document.getElementById("delete-modal-cancel");
// Modal elements for theme deletion
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 // Show/hide thumbnail preview, remove button, and choose button
function updateThumbnailPreview(src) { function updateThumbnailPreview(src) {
if (thumbnailPreview) { if (thumbnailPreview) {
@ -201,6 +209,64 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
} }
// Remove theme button triggers modal
const removeThemeBtn = document.getElementById("remove-theme-btn");
if (removeThemeBtn && themeSelect) {
removeThemeBtn.addEventListener("click", () => {
const theme = themeSelect.value;
if (!theme) return showToast("❌ No theme selected", "error");
if (["modern", "classic"].includes(theme)) {
showToast("❌ Cannot remove default theme", "error");
return;
}
themeToDelete = theme;
deleteThemeModalText.textContent = `Are you sure you want to remove theme "${theme}"?`;
deleteThemeModal.style.display = "flex";
});
}
// Modal logic for theme deletion
if (deleteThemeModal && deleteThemeModalClose && deleteThemeModalConfirm && deleteThemeModalCancel) {
deleteThemeModalClose.onclick = deleteThemeModalCancel.onclick = () => {
deleteThemeModal.style.display = "none";
themeToDelete = null;
};
window.onclick = function(event) {
if (event.target === deleteThemeModal) {
deleteThemeModal.style.display = "none";
themeToDelete = null;
}
};
deleteThemeModalConfirm.onclick = async () => {
if (!themeToDelete) return;
const res = await fetch("/api/theme/remove", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ theme: themeToDelete })
});
const result = await res.json();
if (result.status === "ok") {
showToast("✅ Theme removed!", "success");
// Refresh theme select
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 {
showToast(result.error || "❌ Error removing theme", "error");
}
deleteThemeModal.style.display = "none";
themeToDelete = null;
};
}
// Fetch theme list and populate select // Fetch theme list and populate select
if (themeSelect) { if (themeSelect) {
fetch("/api/themes") fetch("/api/themes")

View File

@ -333,20 +333,75 @@ document.addEventListener("DOMContentLoaded", async () => {
// Add Google Font // Add Google Font
const addGoogleFontBtn = document.getElementById("add-google-font"); const addGoogleFontBtn = document.getElementById("add-google-font");
if (addGoogleFontBtn) { if (addGoogleFontBtn) {
addGoogleFontBtn.addEventListener("click", () => { addGoogleFontBtn.addEventListener("click", async () => {
googleFonts.push({ family: "", weights: [] }); googleFonts.push({ family: "", weights: [] });
// Save immediately to backend
await fetch("/api/theme-google-fonts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
});
// Fetch updated theme info and refresh dropdowns
const updatedThemeInfo = await fetchThemeInfo();
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
googleFonts.length = 0;
googleFonts.push(...updatedGoogleFonts);
renderGoogleFonts(googleFonts); renderGoogleFonts(googleFonts);
refreshFontDropdowns();
}); });
} }
// Remove Google Font
const googleFontsFields = document.getElementById("google-fonts-fields"); const googleFontsFields = document.getElementById("google-fonts-fields");
if (googleFontsFields) { if (googleFontsFields) {
googleFontsFields.addEventListener("click", (e) => { // Save on blur for family/weights fields
if (e.target.classList.contains("remove-google-font")) { googleFontsFields.addEventListener("blur", async (e) => {
const idx = parseInt(e.target.dataset.idx, 10); if (
googleFonts.splice(idx, 1); e.target.name &&
(e.target.name.endsWith("[family]") || e.target.name.endsWith("[weights]"))
) {
// Update googleFonts array from the form fields
const fontFields = googleFontsFields.querySelectorAll(".input-field");
googleFonts.length = 0;
fontFields.forEach(field => {
const family = field.querySelector('input[name^="google_fonts"][name$="[family]"]').value.trim();
const weights = field.querySelector('input[name^="google_fonts"][name$="[weights]"]').value
.split(",").map(w => w.trim()).filter(Boolean);
googleFonts.push({ family, weights });
});
// Save immediately to backend
await fetch("/api/theme-google-fonts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
});
// Fetch updated theme info and refresh dropdowns
const updatedThemeInfo = await fetchThemeInfo();
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
googleFonts.length = 0;
googleFonts.push(...updatedGoogleFonts);
renderGoogleFonts(googleFonts); renderGoogleFonts(googleFonts);
refreshFontDropdowns();
}
}, true); // Use capture phase to catch blur from children
// Delegate remove button click for Google Fonts
googleFontsFields.addEventListener("click", async (e) => {
if (e.target.classList.contains("remove-google-font")) {
const idx = Number(e.target.dataset.idx);
googleFonts.splice(idx, 1);
// Save immediately to backend
await fetch("/api/theme-google-fonts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ theme_name: themeInfo.theme_name, google_fonts: googleFonts })
});
// Fetch updated theme info and refresh dropdowns
const updatedThemeInfo = await fetchThemeInfo();
const updatedGoogleFonts = updatedThemeInfo.theme_yaml.google_fonts || [];
googleFonts.length = 0;
googleFonts.push(...updatedGoogleFonts);
renderGoogleFonts(googleFonts);
refreshFontDropdowns();
} }
}); });
} }

View File

@ -43,6 +43,7 @@
<!-- Info Section --> <!-- Info Section -->
<fieldset> <fieldset>
<h2>Info</h2> <h2>Info</h2>
<p>Set the basic information for your site and SEO</p>
<div class="fields"> <div class="fields">
<div class="input-field"> <div class="input-field">
<label>Title</label> <label>Title</label>
@ -73,6 +74,7 @@
<!-- Social Section --> <!-- Social Section -->
<fieldset> <fieldset>
<h2>Social</h2> <h2>Social</h2>
<p>Set your social media links and thumbnail for link sharing</p>
<div class="fields"> <div class="fields">
<div class="input-field"> <div class="input-field">
<label>Instagram URL</label> <label>Instagram URL</label>
@ -91,6 +93,7 @@
<!-- Menu Section --> <!-- Menu Section -->
<fieldset> <fieldset>
<h2>Menu</h2> <h2>Menu</h2>
<p>Manage your site menu items. You can use tag combination to propose custom filters</p>
<div class="fields"> <div class="fields">
<div class="input-field" style="flex: 1 1 100%;"> <div class="input-field" style="flex: 1 1 100%;">
<div id="menu-items-list"></div> <div id="menu-items-list"></div>
@ -101,6 +104,7 @@
<!-- Footer Section --> <!-- Footer Section -->
<fieldset> <fieldset>
<h2>Footer</h2> <h2>Footer</h2>
<p>Set your copyright informations and legal link name</p>
<div class="fields"> <div class="fields">
<div class="input-field"> <div class="input-field">
<label>Copyright</label> <label>Copyright</label>
@ -115,6 +119,7 @@
<!-- Legals Section --> <!-- Legals Section -->
<fieldset> <fieldset>
<h2>Legals</h2> <h2>Legals</h2>
<p>Set your legal informations</p>
<div class="fields"> <div class="fields">
<div class="input-field"> <div class="input-field">
<label>Hoster Name</label> <label>Hoster Name</label>
@ -138,13 +143,16 @@
<!-- Build Section --> <!-- Build Section -->
<fieldset> <fieldset>
<h2>Build</h2> <h2>Build</h2>
<p>Select a theme from the dropdown menu or add your custom theme folder</p>
<div class="fields"> <div class="fields">
<div class="input-field"> <div class="input-field">
<label>Theme</label> <label>Theme</label>
<select name="build.theme" id="theme-select" required></select> <select name="build.theme" id="theme-select" required></select>
<button type="button" id="remove-theme-btn" class="remove-btn up-btn danger">🗑 Remove selected theme</button>
<input type="file" id="theme-upload" webkitdirectory directory multiple style="display:none;"> <input type="file" id="theme-upload" webkitdirectory directory multiple style="display:none;">
<button type="button" id="choose-theme-btn" class="up-btn">📂 Upload custom theme folder</button> <button type="button" id="choose-theme-btn" class="up-btn">📂 Upload custom theme folder</button>
<label class="thumbnail-form-label">Images processing</label> <label class="thumbnail-form-label">Images processing</label>
<p>If checked, images will be converted for web and resized to fit the theme</p>
<label> <label>
<input class="thumbnail-form-label" type="checkbox" name="build.convert_images" id="convert-images-checkbox"> <input class="thumbnail-form-label" type="checkbox" name="build.convert_images" id="convert-images-checkbox">
Convert images Convert images
@ -171,6 +179,18 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Delete theme confirmation modal -->
<div id="delete-theme-modal" class="modal" style="display:none;">
<div class="modal-content">
<span id="delete-theme-modal-close" class="modal-close">&times;</span>
<h3>Confirm Theme Deletion</h3>
<p id="delete-theme-modal-text">Are you sure you want to remove this theme?</p>
<div class="modal-actions">
<button id="delete-theme-modal-confirm" class="modal-btn danger">Remove</button>
<button id="delete-theme-modal-cancel" class="modal-btn">Cancel</button>
</div>
</div>
</div>
<!-- Build success modal --> <!-- Build success modal -->
<div id="build-success-modal" class="modal" style="display:none;"> <div id="build-success-modal" class="modal" style="display:none;">
<div class="modal-content"> <div class="modal-content">