2.0 - WebUI builder ("Cielight" merge) #9
@@ -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:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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."""
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -170,28 +170,28 @@ document.addEventListener("DOMContentLoaded", async () => {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (fontUploadInput) {
 | 
					  if (fontUploadInput) {
 | 
				
			||||||
  fontUploadInput.addEventListener("change", async (e) => {
 | 
					    fontUploadInput.addEventListener("change", async (e) => {
 | 
				
			||||||
    const file = e.target.files[0];
 | 
					      const file = e.target.files[0];
 | 
				
			||||||
    if (!file) return;
 | 
					      if (!file) return;
 | 
				
			||||||
    const ext = file.name.split('.').pop().toLowerCase();
 | 
					      const ext = file.name.split('.').pop().toLowerCase();
 | 
				
			||||||
    if (!["woff", "woff2"].includes(ext)) {
 | 
					      if (!["woff", "woff2"].includes(ext)) {
 | 
				
			||||||
      showToast("Only .woff and .woff2 fonts are allowed.", "error");
 | 
					        showToast("Only .woff and .woff2 fonts are allowed.", "error");
 | 
				
			||||||
      return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
    const formData = new FormData();
 | 
					      const formData = new FormData();
 | 
				
			||||||
    formData.append("file", file);
 | 
					      formData.append("file", file);
 | 
				
			||||||
    formData.append("theme", themeInfo.theme_name);
 | 
					      formData.append("theme", themeInfo.theme_name);
 | 
				
			||||||
    const res = await fetch("/api/font/upload", { method: "POST", body: formData });
 | 
					      const res = await fetch("/api/font/upload", { method: "POST", body: formData });
 | 
				
			||||||
    const result = await res.json();
 | 
					      const result = await res.json();
 | 
				
			||||||
    if (result.status === "ok") {
 | 
					      if (result.status === "ok") {
 | 
				
			||||||
      showToast("✅ Font uploaded!", "success");
 | 
					        showToast("✅ Font uploaded!", "success");
 | 
				
			||||||
      localFonts = await fetchLocalFonts(themeInfo.theme_name);
 | 
					        localFonts = await fetchLocalFonts(themeInfo.theme_name);
 | 
				
			||||||
      refreshLocalFonts();
 | 
					        refreshLocalFonts();
 | 
				
			||||||
    } else {
 | 
					      } else {
 | 
				
			||||||
      showToast("Error uploading font.", "error");
 | 
					        showToast("Error uploading font.", "error");
 | 
				
			||||||
    }
 | 
					      }
 | 
				
			||||||
  });
 | 
					    });
 | 
				
			||||||
}
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Remove font button triggers modal
 | 
					  // Remove font button triggers modal
 | 
				
			||||||
  if (localFontsList) {
 | 
					  if (localFontsList) {
 | 
				
			||||||
@@ -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();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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">×</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">
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user