/** * my-editor.js — VvvebJS 儲存橋接腳本 * 覆蓋 vvvebjs 的 saveAjax 行為,附加 project slug 後送至 Flask /api/save */ (function () { "use strict"; const SLUG = window.VVVEB_PROJECT_SLUG || ""; // ── Toast 通知 ────────────────────────────────────────────────── function showToast(msg, type) { const existing = document.getElementById("vvveb-save-toast"); if (existing) existing.remove(); const toast = document.createElement("div"); toast.id = "vvveb-save-toast"; const bg = type === "error" ? "#7f1d1d" : "#14532d"; const border = type === "error" ? "#ef444460" : "#22c55e60"; Object.assign(toast.style, { position: "fixed", top: "1rem", right: "1rem", zIndex: "99999", background: bg, border: "1px solid " + border, color: "#fff", borderRadius: "8px", padding: "0.65rem 1.1rem", fontSize: "0.85rem", fontFamily: "Inter, system-ui, sans-serif", fontWeight: "500", boxShadow: "0 4px 20px rgba(0,0,0,0.5)", opacity: "0", transform: "translateY(-8px)", transition: "opacity 0.3s ease, transform 0.3s ease", pointerEvents: "none", }); toast.textContent = msg; document.body.appendChild(toast); requestAnimationFrame(() => { toast.style.opacity = "1"; toast.style.transform = "translateY(0)"; }); setTimeout(() => { toast.style.opacity = "0"; toast.style.transform = "translateY(-8px)"; setTimeout(() => toast.remove(), 350); }, 3000); } // ── 覆蓋 saveAjax ─────────────────────────────────────────────── function patchSaveAjax() { if (typeof Vvveb === "undefined" || !Vvveb.Builder) { // 等待 Vvveb 載入 setTimeout(patchSaveAjax, 200); return; } const originalSaveAjax = Vvveb.Builder.saveAjax ? Vvveb.Builder.saveAjax.bind(Vvveb.Builder) : null; Vvveb.Builder.saveAjax = function (saveUrl) { if (!SLUG) { showToast("錯誤:找不到專案識別碼 (slug)", "error"); return; } // 取得目前編輯的頁面檔名 const currentPage = Vvveb.FileManager ? Vvveb.FileManager.getCurrentPage ? Vvveb.FileManager.getCurrentPage() : null : null; let filename = "index.html"; if (currentPage && currentPage.filename) { filename = currentPage.filename; } else if (currentPage && currentPage.file) { // 只取檔名部分 filename = currentPage.file.split("/").pop(); } // 取得 HTML 內容 let html = ""; try { html = Vvveb.Builder.getHtml ? Vvveb.Builder.getHtml() : ""; } catch (e) { console.error("[my-editor] getHtml failed:", e); } if (!html) { showToast("無法取得頁面內容", "error"); return; } // 顯示儲存中狀態 const saveBtn = document.querySelector(".save-btn"); if (saveBtn) { saveBtn.querySelector(".loading")?.classList.remove("d-none"); saveBtn.querySelector(".button-text")?.classList.add("d-none"); } const body = new URLSearchParams({ slug: SLUG, file: filename, html: html, }); fetch("/api/save", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: body.toString(), }) .then((res) => res.json()) .then((data) => { if (data.ok) { showToast("✓ 已儲存:" + (data.saved || filename)); } else { showToast("儲存失敗:" + (data.error || "未知錯誤"), "error"); } }) .catch((err) => { showToast("網路錯誤:" + err.message, "error"); }) .finally(() => { if (saveBtn) { saveBtn.querySelector(".loading")?.classList.add("d-none"); saveBtn.querySelector(".button-text")?.classList.remove("d-none"); } }); }; console.log("[my-editor] saveAjax patched for slug:", SLUG); } // ── 啟用 Save 按鈕(vvvebjs 預設 disabled)────────────────────── function enableSaveBtn() { const btn = document.querySelector(".save-btn"); if (btn) { btn.removeAttribute("disabled"); } else { setTimeout(enableSaveBtn, 300); } } // ── Ctrl+S 快捷鍵 ──────────────────────────────────────────────── document.addEventListener("keydown", function (e) { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); if (typeof Vvveb !== "undefined" && Vvveb.Builder && Vvveb.Builder.saveAjax) { Vvveb.Builder.saveAjax("/api/save"); } } }); // ── 初始化 ──────────────────────────────────────────────────────── if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { patchSaveAjax(); enableSaveBtn(); }); } else { patchSaveAjax(); enableSaveBtn(); } })();