Build system with zip download
This commit is contained in:
		@@ -10,12 +10,6 @@
 | 
			
		||||
  <!-- Top bar -->	
 | 
			
		||||
  <div class="nav-bar">
 | 
			
		||||
    <div class="content-inner nav">
 | 
			
		||||
      <div class="nav-cta">
 | 
			
		||||
        <div class="arrow">→</div>
 | 
			
		||||
        <a class="button" href="#" target="_blank">
 | 
			
		||||
          <span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input type="checkbox" id="nav-check">
 | 
			
		||||
      <div class="nav-header">
 | 
			
		||||
        <div class="nav-title">
 | 
			
		||||
@@ -31,9 +25,12 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="nav-links">
 | 
			
		||||
        <ul class="nav-list">
 | 
			
		||||
          <li class="nav-item appear2"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="/theme-editor">Theme info</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="#">Gallery</a>
 | 
			
		||||
          <li class="nav-item"><a href="/gallery-editor">Gallery</a>
 | 
			
		||||
          <li class="nav-item"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item"><a href="/theme-editor">Theme info</a></li>
 | 
			
		||||
          <li class="nav-item">
 | 
			
		||||
            <button id="build-btn" class="button">Build !</button>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -73,6 +70,7 @@
 | 
			
		||||
    </div>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/main.js') }}" defer></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/upload.js') }}" defer></script>
 | 
			
		||||
    <script src="{{ url_for('static', filename='js/build.js') }}" defer></script>
 | 
			
		||||
  </div>
 | 
			
		||||
  <!-- Delete confirmation modal -->
 | 
			
		||||
  <div id="delete-modal" class="modal" style="display:none;">
 | 
			
		||||
@@ -86,5 +84,15 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <!-- Build success modal -->
 | 
			
		||||
  <div id="build-success-modal" class="modal" style="display:none;">
 | 
			
		||||
    <div class="modal-content">
 | 
			
		||||
      <span id="build-success-modal-close" class="modal-close">×</span>
 | 
			
		||||
      <h3>✅ Build completed!</h3>
 | 
			
		||||
      <p>Your files are available in the output folder.</p>
 | 
			
		||||
      <button id="download-zip-btn" class="modal-btn">Download ZIP</button>
 | 
			
		||||
      <div id="zip-loader" style="display:none;">Creating ZIP...</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										81
									
								
								src/webui/js/build.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/webui/js/build.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
/**
 | 
			
		||||
 * Show a toast notification.
 | 
			
		||||
 * @param {string} message - The message to display.
 | 
			
		||||
 * @param {string} type - "success" or "error".
 | 
			
		||||
 * @param {number} duration - Duration in ms.
 | 
			
		||||
 */
 | 
			
		||||
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");
 | 
			
		||||
    setTimeout(() => container.removeChild(toast), 300);
 | 
			
		||||
  }, duration);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
  // Get build button and modal elements
 | 
			
		||||
  const buildBtn = document.getElementById("build-btn");
 | 
			
		||||
  const buildModal = document.getElementById("build-success-modal");
 | 
			
		||||
  const buildModalClose = document.getElementById("build-success-modal-close");
 | 
			
		||||
  const downloadZipBtn = document.getElementById("download-zip-btn");
 | 
			
		||||
  const zipLoader = document.getElementById("zip-loader");
 | 
			
		||||
 | 
			
		||||
  // Handle build button click
 | 
			
		||||
  if (buildBtn) {
 | 
			
		||||
    buildBtn.addEventListener("click", async () => {
 | 
			
		||||
      // Trigger build on backend
 | 
			
		||||
      const res = await fetch("/api/build", { method: "POST" });
 | 
			
		||||
      const result = await res.json();
 | 
			
		||||
      if (result.status === "ok") {
 | 
			
		||||
        // Show build success modal
 | 
			
		||||
        if (buildModal) buildModal.style.display = "flex";
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast(result.message || "❌ Build failed!", "error");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Handle download zip button click
 | 
			
		||||
  if (downloadZipBtn) {
 | 
			
		||||
    downloadZipBtn.addEventListener("click", async () => {
 | 
			
		||||
      if (zipLoader) zipLoader.style.display = "block";
 | 
			
		||||
      downloadZipBtn.disabled = true;
 | 
			
		||||
 | 
			
		||||
      // Request zip creation and download from backend
 | 
			
		||||
      const res = await fetch("/download-output-zip", { method: "POST" });
 | 
			
		||||
      if (res.ok) {
 | 
			
		||||
        const blob = await res.blob();
 | 
			
		||||
        const url = window.URL.createObjectURL(blob);
 | 
			
		||||
        const a = document.createElement("a");
 | 
			
		||||
        a.href = url;
 | 
			
		||||
        a.download = "site_output.zip";
 | 
			
		||||
        document.body.appendChild(a);
 | 
			
		||||
        a.click();
 | 
			
		||||
        a.remove();
 | 
			
		||||
        window.URL.revokeObjectURL(url);
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast("❌ Error creating ZIP", "error");
 | 
			
		||||
      }
 | 
			
		||||
      if (zipLoader) zipLoader.style.display = "none";
 | 
			
		||||
      downloadZipBtn.disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Modal close logic
 | 
			
		||||
  if (buildModal && buildModalClose) {
 | 
			
		||||
    buildModalClose.onclick = () => {
 | 
			
		||||
      buildModal.style.display = "none";
 | 
			
		||||
    };
 | 
			
		||||
    window.onclick = function(event) {
 | 
			
		||||
      if (event.target === buildModal) {
 | 
			
		||||
        buildModal.style.display = "none";
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
@@ -63,7 +63,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
      div.style.gap = "8px";
 | 
			
		||||
      div.style.marginBottom = "6px";
 | 
			
		||||
      div.innerHTML = `
 | 
			
		||||
        <textarea placeholder="Paragraph" 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>
 | 
			
		||||
      `;
 | 
			
		||||
      ipList.appendChild(div);
 | 
			
		||||
@@ -129,9 +129,9 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
      if (result.status === "ok") {
 | 
			
		||||
        if (thumbnailInput) thumbnailInput.value = result.filename;
 | 
			
		||||
        updateThumbnailPreview(`/photos/${result.filename}?t=${Date.now()}`);
 | 
			
		||||
        showToast("Thumbnail uploaded!", "success");
 | 
			
		||||
        showToast("✅ Thumbnail uploaded!", "success");
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast("Error uploading thumbnail", "error");
 | 
			
		||||
        showToast("❌ Error uploading thumbnail", "error");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@@ -159,9 +159,9 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
      if (result.status === "ok") {
 | 
			
		||||
        if (thumbnailInput) thumbnailInput.value = "";
 | 
			
		||||
        updateThumbnailPreview("");
 | 
			
		||||
        showToast("Thumbnail removed!", "success");
 | 
			
		||||
        showToast("✅ Thumbnail removed!", "success");
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast("Error removing thumbnail", "error");
 | 
			
		||||
        showToast("❌ Error removing thumbnail", "error");
 | 
			
		||||
      }
 | 
			
		||||
      deleteModal.style.display = "none";
 | 
			
		||||
    };
 | 
			
		||||
@@ -182,7 +182,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
      const res = await fetch("/api/theme/upload", { method: "POST", body: formData });
 | 
			
		||||
      const result = await res.json();
 | 
			
		||||
      if (result.status === "ok") {
 | 
			
		||||
        showToast("Theme uploaded!", "success");
 | 
			
		||||
        showToast("✅ Theme uploaded!", "success");
 | 
			
		||||
        // Refresh theme select after upload
 | 
			
		||||
        fetch("/api/themes")
 | 
			
		||||
          .then(res => res.json())
 | 
			
		||||
@@ -196,7 +196,7 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
            });
 | 
			
		||||
          });
 | 
			
		||||
      } else {
 | 
			
		||||
        showToast("Error uploading theme", "error");
 | 
			
		||||
        showToast("❌ Error uploading theme", "error");
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
@@ -311,6 +311,12 @@ document.addEventListener("DOMContentLoaded", () => {
 | 
			
		||||
      updateMenuItemsFromInputs();
 | 
			
		||||
      updateIpParagraphsFromInputs();
 | 
			
		||||
 | 
			
		||||
      // Check if thumbnail is set before saving (uploaded or present in input)
 | 
			
		||||
      if (!thumbnailInput || !thumbnailInput.value) {
 | 
			
		||||
        showToast("❌ Thumbnail is required.", "error");
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const build = {
 | 
			
		||||
        theme: themeSelect ? themeSelect.value : "",
 | 
			
		||||
        convert_images: !!(convertImagesCheckbox && convertImagesCheckbox.checked),
 | 
			
		||||
 
 | 
			
		||||
@@ -10,12 +10,6 @@
 | 
			
		||||
  <!-- Top bar -->	
 | 
			
		||||
  <div class="nav-bar">
 | 
			
		||||
    <div class="content-inner nav">
 | 
			
		||||
      <div class="nav-cta">
 | 
			
		||||
        <div class="arrow">→</div>
 | 
			
		||||
        <a class="button" href="#" target="_blank">
 | 
			
		||||
          <span id="step">🚀 Build !<i class="fa-solid fa-envelope"></i></span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input type="checkbox" id="nav-check">
 | 
			
		||||
      <div class="nav-header">
 | 
			
		||||
        <div class="nav-title">
 | 
			
		||||
@@ -31,9 +25,12 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="nav-links">
 | 
			
		||||
        <ul class="nav-list">
 | 
			
		||||
          <li class="nav-item appear2"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="/theme-editor">Theme info</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="#">Gallery</a></li>
 | 
			
		||||
          <li class="nav-item"><a href="/gallery-editor">Gallery</a>
 | 
			
		||||
          <li class="nav-item"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item"><a href="/theme-editor">Theme info</a></li>
 | 
			
		||||
          <li class="nav-item">
 | 
			
		||||
            <button id="build-btn" class="button">Build !</button>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -81,9 +78,10 @@
 | 
			
		||||
            <label>Instagram URL</label>
 | 
			
		||||
            <input type="text" name="social.instagram_url" placeholder="https://instagram.com/yourprofile" required>
 | 
			
		||||
            <label class="thumbnail-form-label">Thumbnail</label>
 | 
			
		||||
            <input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;" required>
 | 
			
		||||
            <input type="file" id="thumbnail-upload" accept="image/png,image/jpeg,image/webp" style="display:none;">
 | 
			
		||||
            <button type="button" id="choose-thumbnail-btn" class="up-btn">📸 Upload a photo</button>
 | 
			
		||||
            <div class="thumbnail-form">
 | 
			
		||||
            <input type="hidden" name="social.thumbnail" id="social-thumbnail">
 | 
			
		||||
            <img id="thumbnail-preview" src="" alt="Thumbnail preview" style="max-width:100px;display:none;">
 | 
			
		||||
            <button type="button" id="remove-thumbnail-btn" class="remove-btn up-btn danger" style="display:none;">Remove</button>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -120,15 +118,15 @@
 | 
			
		||||
        <div class="fields">
 | 
			
		||||
          <div class="input-field">
 | 
			
		||||
            <label>Hoster Name</label>
 | 
			
		||||
            <input type="text" name="legals.hoster_name" placeholder="Name">
 | 
			
		||||
            <input type="text" name="legals.hoster_name" placeholder="Name" required>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="input-field">
 | 
			
		||||
            <label>Hoster Address</label>
 | 
			
		||||
            <input type="text" name="legals.hoster_address" placeholder="Street, Postal Code, City, Country">
 | 
			
		||||
            <input type="text" name="legals.hoster_address" placeholder="Street, Postal Code, City, Country" required>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="input-field">
 | 
			
		||||
            <label>Hoster Contact</label>
 | 
			
		||||
            <input type="text" name="legals.hoster_contact" placeholder="Email or/and Phone">
 | 
			
		||||
            <input type="text" name="legals.hoster_contact" placeholder="Email or/and Phone" required>
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="input-field" style="flex: 1 1 100%;">
 | 
			
		||||
            <label>Intellectual Property</label>
 | 
			
		||||
@@ -173,6 +171,17 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <!-- Build success modal -->
 | 
			
		||||
  <div id="build-success-modal" class="modal" style="display:none;">
 | 
			
		||||
    <div class="modal-content">
 | 
			
		||||
      <span id="build-success-modal-close" class="modal-close">×</span>
 | 
			
		||||
      <h3>✅ Build completed!</h3>
 | 
			
		||||
      <p>Your files are available in the output folder.</p>
 | 
			
		||||
      <button id="download-zip-btn" class="modal-btn">Download ZIP</button>
 | 
			
		||||
      <div id="zip-loader" style="display:none;">Creating ZIP...</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/site-info.js')}}"></script>
 | 
			
		||||
<script src="{{ url_for('static', filename='js/build.js') }}"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
@@ -7,6 +7,7 @@ body {
 | 
			
		||||
  color: #FBFBFB;
 | 
			
		||||
  min-height: 100vh;
 | 
			
		||||
  margin:0px;
 | 
			
		||||
  padding-top: 70px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.content-inner {
 | 
			
		||||
@@ -256,12 +257,14 @@ h2 {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-bar {
 | 
			
		||||
  height: 70px;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 70px;
 | 
			
		||||
  background-color: #0c0d0c29;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  z-index: 1;
 | 
			
		||||
  backdrop-filter:  blur(20px);
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
  backdrop-filter: blur(20px);
 | 
			
		||||
  border-bottom: 1px solid #21212157;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -287,6 +290,15 @@ h2 {
 | 
			
		||||
  display: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-btn label {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: column;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  width: 32px;
 | 
			
		||||
  height: 32px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav > .nav-links {
 | 
			
		||||
  display: inline;
 | 
			
		||||
  float: right;
 | 
			
		||||
@@ -345,7 +357,7 @@ h2 {
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-cta > .button {
 | 
			
		||||
.nav-bar .button {
 | 
			
		||||
  padding: 10px 25px;
 | 
			
		||||
  border-radius: 40px;
 | 
			
		||||
  margin: 15px 20px 15px 10px;
 | 
			
		||||
@@ -356,17 +368,29 @@ h2 {
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  font-weight: 700;
 | 
			
		||||
  border: none;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-cta > .button:hover {
 | 
			
		||||
.nav-bar .button:hover {
 | 
			
		||||
  background: linear-gradient(135deg, #72d9ff, #26657e);
 | 
			
		||||
  transition: all 0.2s ease;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-links > ul {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nav-btn span {
 | 
			
		||||
  display: block;
 | 
			
		||||
  height: 4px;
 | 
			
		||||
  width: 28px;
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  margin: 4px 0;
 | 
			
		||||
  border-radius: 2px;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
/* --- Custom Upload Buttons --- */
 | 
			
		||||
.up-btn {
 | 
			
		||||
  display: inline-block;
 | 
			
		||||
@@ -717,4 +741,4 @@ fieldset p {
 | 
			
		||||
 | 
			
		||||
#theme-editor-form button[type="button"]#choose-font-btn {
 | 
			
		||||
  margin-top: 16px;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,13 @@
 | 
			
		||||
  <link rel="stylesheet" href="{{ url_for('static', filename='style/style.css') }}">
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
  <!-- Top bar -->
 | 
			
		||||
  <!-- Top bar -->	
 | 
			
		||||
  <div class="nav-bar">
 | 
			
		||||
    <div class="content-inner nav">
 | 
			
		||||
      <div class="nav-cta">
 | 
			
		||||
        <div class="arrow">→</div>
 | 
			
		||||
        <a class="button" href="/site-info">
 | 
			
		||||
          <span id="step">← Back to Site Info</span>
 | 
			
		||||
        </a>
 | 
			
		||||
      </div>
 | 
			
		||||
      <input type="checkbox" id="nav-check">
 | 
			
		||||
      <div class="nav-header">
 | 
			
		||||
        <div class="nav-title">
 | 
			
		||||
          <img src="../img/logo.svg">
 | 
			
		||||
          <img src="{{ url_for('static', filename='img/logo.svg') }}">
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="nav-btn">
 | 
			
		||||
@@ -31,9 +25,12 @@
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="nav-links">
 | 
			
		||||
        <ul class="nav-list">
 | 
			
		||||
          <li class="nav-item appear2"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="/theme-editor">Theme editor</a></li>
 | 
			
		||||
          <li class="nav-item appear2"><a href="#">Gallery</a></li>
 | 
			
		||||
          <li class="nav-item"><a href="/gallery-editor">Gallery</a>
 | 
			
		||||
          <li class="nav-item"><a href="/site-info">Site info</a></li>
 | 
			
		||||
          <li class="nav-item"><a href="/theme-editor">Theme info</a></li>
 | 
			
		||||
          <li class="nav-item">
 | 
			
		||||
            <button id="build-btn" class="button">Build !</button>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
@@ -197,6 +194,17 @@
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <!-- Build success modal -->
 | 
			
		||||
  <div id="build-success-modal" class="modal" style="display:none;">
 | 
			
		||||
    <div class="modal-content">
 | 
			
		||||
      <span id="build-success-modal-close" class="modal-close">×</span>
 | 
			
		||||
      <h3>✅ Build completed!</h3>
 | 
			
		||||
      <p>Your files are available in the output folder.</p>
 | 
			
		||||
      <button id="download-zip-btn" class="modal-btn">Download ZIP</button>
 | 
			
		||||
      <div id="zip-loader" style="display:none;">Creating ZIP...</div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/theme-editor.js') }}"></script>
 | 
			
		||||
  <script src="{{ url_for('static', filename='js/build.js') }}"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
		Reference in New Issue
	
	Block a user