/** * my-editor.js — VvvebJS 儲存與新增頁面橋接腳本 * 覆蓋 Vvveb.Builder.saveAjax 行為,使儲存與新增頁面功能無縫串接至 Flask 後端 */ (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); } // ── 覆蓋 Vvveb.Builder.saveAjax ────────────────────────────────── function patchSaveAjax() { if (typeof Vvveb === "undefined" || !Vvveb.Builder) { // 等待 Vvveb 載入 setTimeout(patchSaveAjax, 200); return; } /** * VvvebJS 原始簽名: saveAjax(data, saveUrl, callback, error) */ Vvveb.Builder.saveAjax = function (data, saveUrl, callback, error) { if (!SLUG) { showToast("錯誤:找不到專案識別碼 (slug)", "error"); if (error) error(new Error("Missing slug")); return; } // 確保 data 為物件並填入專案識別碼 if (typeof data !== "object" || data === null) { data = {}; } data.slug = SLUG; // 扁平化處理新檔案檔名(確保所有頁面皆存於專案根目錄下) if (data.file) { const parts = data.file.split("/"); data.file = parts[parts.length - 1]; } // 如果不是新增頁面(即一般的儲存頁面),且未帶有 HTML 內容,則動態獲取目前 HTML const isNewPage = !!data.startTemplateUrl; if (!isNewPage && !data.html) { try { data.html = Vvveb.Builder.getHtml ? Vvveb.Builder.getHtml() : ""; } catch (e) { console.error("[my-editor] getHtml failed:", e); } } // 顯示儲存中狀態(一般儲存時觸發) const saveBtn = document.querySelector(".save-btn"); if (saveBtn && !isNewPage) { saveBtn.querySelector(".loading")?.classList.remove("d-none"); saveBtn.querySelector(".button-text")?.classList.add("d-none"); } // 發送 POST 請求至 Flask API fetch("/api/save", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams(data).toString(), }) .then((res) => { if (!res.ok) { return res.json().then((errData) => { throw new Error(errData.error || "伺服器儲存錯誤"); }); } return res.json(); }) .then((resData) => { if (isNewPage) { // 新增頁面成功:秀出通知並執行 Vvveb 原生 callback 以載入新頁面 showToast("✓ 成功建立新頁面:" + resData.file); if (callback) { callback({ name: resData.name, title: resData.title, file: resData.file, url: resData.url }); } } else { // 一般儲存成功 showToast("✓ 已儲存:" + (resData.saved || data.file)); if (callback) callback(resData); // 停用儲存按鈕(直至下一次變更) document.querySelectorAll("#top-panel .save-btn").forEach((e) => e.setAttribute("disabled", "true") ); } }) .catch((err) => { showToast("儲存失敗:" + err.message, "error"); if (error) error(err); }) .finally(() => { if (saveBtn && !isNewPage) { saveBtn.querySelector(".loading")?.classList.add("d-none"); saveBtn.querySelector(".button-text")?.classList.remove("d-none"); } }); }; console.log("[my-editor] Robust Vvveb.Builder.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) { // 觸發原生儲存按鈕的 click const btn = document.querySelector(".save-btn"); if (btn) { btn.click(); } else { Vvveb.Builder.saveAjax({}); } } } }); // ── 初始化 ──────────────────────────────────────────────────────── if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => { patchSaveAjax(); enableSaveBtn(); }); } else { patchSaveAjax(); enableSaveBtn(); } })();