Files
vvveb-cms/static/js/my-editor.js
2026-05-17 22:44:11 +08:00

187 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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();
}
})();