File size: 3,356 Bytes
4463908 f503bad 4463908 f503bad 795fd69 f503bad 4463908 f503bad 4463908 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | /* ================================================================
TRTOI 就地編輯(inline editing)
登入後:按「編輯此頁」→ 頁面上的文字直接可改 → 按「儲存」寫回。
每個可編輯元素帶 data-e="JSON 路徑";data-html="1" 者保留 HTML。
================================================================ */
(function () {
"use strict";
var nodes = [].slice.call(document.querySelectorAll("[data-e]"));
if (!nodes.length) return;
var fab = document.getElementById("editToggle");
var bar = document.getElementById("editbar");
var saveBtn = document.getElementById("saveBtn");
var cancelBtn = document.getElementById("cancelBtn");
var editing = false;
var backup = {};
function readVal(n) {
return n.getAttribute("data-html") ? n.innerHTML.trim() : n.textContent.trim();
}
function writeVal(n, v) {
if (n.getAttribute("data-html")) n.innerHTML = v; else n.textContent = v;
}
function enter() {
editing = true;
document.body.classList.add("edit-mode");
nodes.forEach(function (n) {
backup[n.getAttribute("data-e")] = readVal(n);
n.setAttribute("contenteditable", "true");
});
bar.classList.add("show");
fab.style.display = "none";
}
function leave() {
editing = false;
document.body.classList.remove("edit-mode");
nodes.forEach(function (n) { n.removeAttribute("contenteditable"); });
bar.classList.remove("show");
fab.style.display = "";
}
// 編輯時避免點到連結就跳走
document.addEventListener("click", function (e) {
if (!editing) return;
var a = e.target.closest ? e.target.closest("a") : null;
if (a) e.preventDefault();
}, true);
fab.addEventListener("click", enter);
cancelBtn.addEventListener("click", function () {
nodes.forEach(function (n) {
var k = n.getAttribute("data-e");
if (k in backup) writeVal(n, backup[k]);
});
leave();
});
saveBtn.addEventListener("click", function () {
// 依目標分頁分組:預設寫回目前頁面(window.SLUG),
// 帶 data-slug 者寫回指定目標(如側欄註記 → 站台層級 "site")。
var groups = {};
nodes.forEach(function (n) {
var slug = n.getAttribute("data-slug") || window.SLUG;
(groups[slug] || (groups[slug] = {}))[n.getAttribute("data-e")] = readVal(n);
});
saveBtn.disabled = true;
saveBtn.textContent = "儲存中…";
Promise.all(Object.keys(groups).map(function (slug) {
return fetch("/admin/save-inline/" + slug, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ patches: groups[slug], lang: window.LANG })
}).then(function (r) { return r.json(); });
}))
.then(function (results) {
if (results.every(function (res) { return res && res.ok; })) {
location.reload();
} else {
var bad = results.filter(function (r) { return !(r && r.ok); })[0];
alert("儲存失敗:" + ((bad && bad.error) || "未知錯誤"));
saveBtn.disabled = false;
saveBtn.textContent = "儲存";
}
})
.catch(function () {
alert("儲存失敗(無法連線)");
saveBtn.disabled = false;
saveBtn.textContent = "儲存";
});
});
})();
|