169 lines
5.4 KiB
JavaScript
169 lines
5.4 KiB
JavaScript
/**
|
||
* 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();
|
||
}
|
||
})();
|