feat(modal): add cancellable shared runtime
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+60
-135
@@ -5,126 +5,51 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function restBase() {
|
||||
var aksrt = window.aksrtData || window.aksrt || {};
|
||||
var siteUrl = aksrt.siteUrl || aksrt.homeUrl || (window.location.origin + "/");
|
||||
if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") {
|
||||
siteUrl = "https:" + siteUrl.substring(5);
|
||||
}
|
||||
var raw = aksrt.apiUrl || aksrt.restUrl || aksrt.rest_url || "";
|
||||
if (raw) {
|
||||
return String(raw).replace(/\/$/, "");
|
||||
}
|
||||
return siteUrl.replace(/\/$/, "") + "/index.php?rest_route=/aksrt/v1";
|
||||
}
|
||||
|
||||
/**
|
||||
* Join REST root + path [+ query].
|
||||
* Pretty: /wp-json/aksrt/v1/pay/vip/modal?vip_level=1
|
||||
* Plain: index.php?rest_route=/aksrt/v1/pay/vip/modal&vip_level=1
|
||||
* Never put a second "?" after rest_route= (that 404s).
|
||||
*/
|
||||
function restUrl(path, query) {
|
||||
path = String(path || "");
|
||||
if (path.charAt(0) !== "/") path = "/" + path;
|
||||
var base = restBase();
|
||||
var qstr = "";
|
||||
if (query && typeof query === "object") {
|
||||
var parts = [];
|
||||
Object.keys(query).forEach(function (k) {
|
||||
if (query[k] === undefined || query[k] === null) return;
|
||||
parts.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(query[k])));
|
||||
});
|
||||
qstr = parts.join("&");
|
||||
} else if (typeof query === "string" && query) {
|
||||
qstr = query.replace(/^\?/, "");
|
||||
}
|
||||
|
||||
if (base.indexOf("rest_route=") !== -1) {
|
||||
var url = base + path;
|
||||
if (qstr) url += "&" + qstr;
|
||||
return url;
|
||||
}
|
||||
|
||||
var pretty = base + path;
|
||||
if (qstr) pretty += (pretty.indexOf("?") >= 0 ? "&" : "?") + qstr;
|
||||
return pretty;
|
||||
}
|
||||
|
||||
function nonceHeaders() {
|
||||
var aksrt = window.aksrtData || window.aksrt || {};
|
||||
var n = aksrt.restNonce || aksrt.nonce || "";
|
||||
return n ? { "X-WP-Nonce": n } : {};
|
||||
}
|
||||
|
||||
function api(path, options) {
|
||||
var opts = Object.assign({}, options || {});
|
||||
var query = opts.query;
|
||||
if (Object.prototype.hasOwnProperty.call(opts, "query")) {
|
||||
delete opts.query;
|
||||
}
|
||||
var onlyPath = String(path || "");
|
||||
var inlineQ = null;
|
||||
var qi = onlyPath.indexOf("?");
|
||||
if (qi >= 0) {
|
||||
inlineQ = onlyPath.slice(qi + 1);
|
||||
onlyPath = onlyPath.slice(0, qi);
|
||||
}
|
||||
var url = restUrl(onlyPath, query || inlineQ);
|
||||
|
||||
var merged = Object.assign(
|
||||
{
|
||||
credentials: "same-origin",
|
||||
headers: Object.assign({ "Content-Type": "application/json" }, nonceHeaders()),
|
||||
},
|
||||
opts
|
||||
);
|
||||
return fetch(url, merged).then(function (r) {
|
||||
return r
|
||||
.json()
|
||||
.catch(function () {
|
||||
return { message: "Invalid JSON (HTTP " + r.status + ")" };
|
||||
})
|
||||
.then(function (j) {
|
||||
return { ok: r.ok, status: r.status, json: j };
|
||||
var request = window.AksrtRequest && typeof window.AksrtRequest.request === "function"
|
||||
? window.AksrtRequest.request(path, opts)
|
||||
: fetch(path, Object.assign({ credentials: "same-origin" }, opts)).then(function (response) {
|
||||
return response.json().then(function (json) {
|
||||
return { ok: response.ok, status: response.status, json: json };
|
||||
});
|
||||
});
|
||||
|
||||
return request.then(function (response) {
|
||||
return {
|
||||
ok: response.ok,
|
||||
status: response.status,
|
||||
json: response.json || {},
|
||||
};
|
||||
}).catch(function (error) {
|
||||
var response = error && error.response ? error.response : {};
|
||||
return {
|
||||
ok: false,
|
||||
status: error && error.status ? error.status : (response.status || 0),
|
||||
json: error && error.json ? error.json : (response.json || { message: error && error.message }),
|
||||
};
|
||||
});
|
||||
}
|
||||
function ensureModalShell() {
|
||||
var el = document.getElementById("modal_pay_uservip");
|
||||
if (el) return el;
|
||||
el = document.createElement("div");
|
||||
el.id = "modal_pay_uservip";
|
||||
el.className = "aksrt-vip-modal-overlay";
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
el.innerHTML =
|
||||
'<div class="aksrt-vip-modal-dialog" role="dialog" aria-modal="true">' +
|
||||
'<div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body></div></div>';
|
||||
document.body.appendChild(el);
|
||||
el.addEventListener("click", function (e) {
|
||||
if (e.target === el || e.target.closest(".aksrt-vip-modal-close")) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") closeModal();
|
||||
});
|
||||
return el;
|
||||
}
|
||||
|
||||
var activeVipShell = null;
|
||||
|
||||
function openModal() {
|
||||
var el = ensureModalShell();
|
||||
el.classList.add("is-open");
|
||||
el.setAttribute("aria-hidden", "false");
|
||||
document.body.classList.add("aksrt-vip-modal-open");
|
||||
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== "function") return null;
|
||||
activeVipShell = window.AksrtModalRuntime.open({
|
||||
className: "aksrt-vip-modal-shell",
|
||||
mobileBottom: false,
|
||||
skeleton: '<div class="aksrt-vip-modal-dialog"><div class="aksrt-vip-modal-content" data-aksrt-vip-modal-body><div class="text-center padding-20 muted-2-color">加载中…</div></div></div>',
|
||||
telemetry: false,
|
||||
kind: "vip",
|
||||
});
|
||||
return activeVipShell;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
var el = document.getElementById("modal_pay_uservip");
|
||||
if (!el) return;
|
||||
el.classList.remove("is-open");
|
||||
el.setAttribute("aria-hidden", "true");
|
||||
document.body.classList.remove("aksrt-vip-modal-open");
|
||||
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === "function") {
|
||||
window.AksrtModalRuntime.close("vip");
|
||||
}
|
||||
activeVipShell = null;
|
||||
}
|
||||
|
||||
function bindModalUi(root) {
|
||||
@@ -226,11 +151,17 @@
|
||||
}
|
||||
var data = r.json && r.json.data ? r.json.data : r.json;
|
||||
if (data && data.redirect && (data.payment_url || data.qr_content)) {
|
||||
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
|
||||
window.AksrtModalRuntime.invalidate(["vip", "orders"]);
|
||||
}
|
||||
if (data.payment_url) window.open(data.payment_url, "_blank");
|
||||
setTip(pane, data.message || "请完成支付,支付成功后将自动刷新。");
|
||||
if (data.order_number) pollOrder(data.order_number);
|
||||
return;
|
||||
}
|
||||
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
|
||||
window.AksrtModalRuntime.invalidate(["vip", "orders"]);
|
||||
}
|
||||
setTip(pane, (data && data.message) || "开通成功,正在刷新…");
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
@@ -257,10 +188,13 @@
|
||||
function telemetryMark(event) {
|
||||
if (telemetry && telemetryId && typeof telemetry.mark === "function") telemetry.mark(telemetryId, event);
|
||||
}
|
||||
var shell = ensureModalShell();
|
||||
var shell = openModal();
|
||||
if (!shell) {
|
||||
loading = false;
|
||||
telemetryMark("error");
|
||||
return;
|
||||
}
|
||||
var body = shell.querySelector("[data-aksrt-vip-modal-body]");
|
||||
if (body) body.innerHTML = '<div class="text-center padding-20 muted-2-color">加载中…</div>';
|
||||
openModal();
|
||||
telemetryMark("shell");
|
||||
|
||||
api("/pay/vip/modal", { query: { vip_level: level } })
|
||||
@@ -286,6 +220,9 @@
|
||||
if (body) {
|
||||
body.innerHTML = r.json.html;
|
||||
telemetryMark("first-content");
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === "function") {
|
||||
window.AksrtFrontend.rehydrate(body, "modal");
|
||||
}
|
||||
bindModalUi(body);
|
||||
telemetryMark("ready");
|
||||
}
|
||||
@@ -318,31 +255,19 @@
|
||||
document.addEventListener("click", onDocClick, true);
|
||||
}
|
||||
|
||||
// Prefer module registry, but always bind once as hard fallback
|
||||
// (float rail must work even if AksrtFrontend.rehydrate is a no-op).
|
||||
if (window.__aksrtPayVipBound) {
|
||||
// already initialized
|
||||
} else if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") {
|
||||
window.AksrtFrontend.register("aksrt-pay-vip", function () {
|
||||
if (window.__aksrtPayVipBound) return;
|
||||
window.__aksrtPayVipBound = true;
|
||||
init();
|
||||
});
|
||||
// Also bind immediately — float button is in footer, available now.
|
||||
if (!window.__aksrtPayVipBound) {
|
||||
window.__aksrtPayVipBound = true;
|
||||
init();
|
||||
}
|
||||
} else if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
if (window.__aksrtPayVipBound) return;
|
||||
window.__aksrtPayVipBound = true;
|
||||
init();
|
||||
});
|
||||
} else {
|
||||
// This handler is delegated from document, so registry rehydration is not
|
||||
// needed. Bind exactly once and keep dynamically injected triggers working.
|
||||
function bindOnce() {
|
||||
if (window.__aksrtPayVipBound) return;
|
||||
window.__aksrtPayVipBound = true;
|
||||
init();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", bindOnce, { once: true });
|
||||
} else {
|
||||
bindOnce();
|
||||
}
|
||||
|
||||
window.aksrtVipPay = vipPay;
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user