Add zibll-parity AjaxList/RemoteModal engines and replace order-center prompt() review flow with multi-dim star modal, multi-image upload, auto-praise countdown, and REST review-modal endpoint.
233 lines
7.5 KiB
JavaScript
233 lines
7.5 KiB
JavaScript
/**
|
|
* AKSRT Remote Modal — zibll RefreshModal parity.
|
|
*
|
|
* Open a center/bottom modal with static HTML or remote fetch.
|
|
* Contract:
|
|
* - data-toggle="AksrtRemoteModal" | data-aksrt-remote-modal
|
|
* - data-remote / href → fetch HTML
|
|
* - data-class → dialog class (modal-mini full-sm …)
|
|
* - data-height → min-height hint
|
|
* - mobile-bottom="true" → bottom sheet on narrow viewports
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
(function (window, document) {
|
|
"use strict";
|
|
|
|
var ROOT_ID = "aksrt-remote-modal-root";
|
|
var active = null;
|
|
|
|
function isMobile() {
|
|
return window.matchMedia && window.matchMedia("(max-width: 768px)").matches;
|
|
}
|
|
|
|
function ensureRoot() {
|
|
var root = document.getElementById(ROOT_ID);
|
|
if (root) return root;
|
|
root = document.createElement("div");
|
|
root.id = ROOT_ID;
|
|
root.className = "aksrt-remote-modal-host";
|
|
document.body.appendChild(root);
|
|
return root;
|
|
}
|
|
|
|
function restHeaders() {
|
|
var h = { Accept: "text/html, application/json" };
|
|
if (window.aksrtData && aksrtData.restNonce) {
|
|
h["X-WP-Nonce"] = aksrtData.restNonce;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
function lockScroll(lock) {
|
|
document.documentElement.classList.toggle("aksrt-remote-modal-open", !!lock);
|
|
document.body.classList.toggle("aksrt-remote-modal-open", !!lock);
|
|
}
|
|
|
|
function close() {
|
|
if (!active) return;
|
|
active.classList.remove("is-open");
|
|
lockScroll(false);
|
|
var node = active;
|
|
active = null;
|
|
window.setTimeout(function () {
|
|
if (node && node.parentNode) node.parentNode.removeChild(node);
|
|
}, 220);
|
|
document.dispatchEvent(new CustomEvent("aksrt:remote-modal:close"));
|
|
}
|
|
|
|
function bindChrome(shell) {
|
|
shell.addEventListener("click", function (e) {
|
|
if (e.target.closest("[data-aksrt-remote-modal-close], [data-dismiss='modal'], .aksrt-remote-modal__close")) {
|
|
e.preventDefault();
|
|
close();
|
|
return;
|
|
}
|
|
if (e.target === shell || e.target.classList.contains("aksrt-remote-modal__overlay")) {
|
|
close();
|
|
}
|
|
});
|
|
shell.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape") close();
|
|
});
|
|
}
|
|
|
|
function open(options) {
|
|
options = options || {};
|
|
close();
|
|
|
|
var root = ensureRoot();
|
|
var shell = document.createElement("div");
|
|
var bottom = options.bottom === true || (options.mobileBottom !== false && isMobile());
|
|
shell.className =
|
|
"aksrt-remote-modal" +
|
|
(bottom ? " aksrt-remote-modal--bottom" : "") +
|
|
(options.className ? " " + options.className : "");
|
|
shell.setAttribute("role", "dialog");
|
|
shell.setAttribute("aria-modal", "true");
|
|
|
|
var heightStyle = options.height ? "min-height:" + parseInt(options.height, 10) + "px;" : "";
|
|
shell.innerHTML =
|
|
'<div class="aksrt-remote-modal__overlay" data-aksrt-remote-modal-close></div>' +
|
|
'<div class="aksrt-remote-modal__dialog" style="' + heightStyle + '">' +
|
|
'<div class="aksrt-remote-modal__content">' +
|
|
(options.html || "") +
|
|
"</div></div>";
|
|
|
|
root.appendChild(shell);
|
|
bindChrome(shell);
|
|
active = shell;
|
|
lockScroll(true);
|
|
|
|
// Force reflow then open for CSS transition.
|
|
void shell.offsetWidth;
|
|
shell.classList.add("is-open");
|
|
|
|
var focusable = shell.querySelector(
|
|
"button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])"
|
|
);
|
|
if (focusable) {
|
|
try {
|
|
focusable.focus();
|
|
} catch (err) {}
|
|
}
|
|
|
|
document.dispatchEvent(
|
|
new CustomEvent("aksrt:remote-modal:open", { detail: { shell: shell, options: options } })
|
|
);
|
|
return shell;
|
|
}
|
|
|
|
function openRemote(options) {
|
|
options = options || {};
|
|
var url = options.url || "";
|
|
if (!url) return Promise.reject(new Error("missing url"));
|
|
|
|
var loading = open({
|
|
html:
|
|
'<div class="aksrt-remote-modal__loading"><i class="fas fa-spinner fa-spin"></i> ' +
|
|
((window.aksrtData && aksrtData.ajaxLoading) || "加载中…") +
|
|
"</div>",
|
|
className: options.className || "modal-mini full-sm",
|
|
bottom: options.bottom,
|
|
mobileBottom: options.mobileBottom,
|
|
height: options.height,
|
|
});
|
|
|
|
return fetch(url, {
|
|
method: options.method || "GET",
|
|
credentials: "same-origin",
|
|
headers: Object.assign(restHeaders(), options.headers || {}),
|
|
body: options.body,
|
|
})
|
|
.then(function (res) {
|
|
var type = (res.headers.get("content-type") || "").toLowerCase();
|
|
if (type.indexOf("application/json") !== -1) {
|
|
return res.json().then(function (json) {
|
|
if (!res.ok || (json && json.success === false)) {
|
|
var msg =
|
|
(json && (json.message || (json.data && json.data.message))) ||
|
|
"加载失败";
|
|
throw new Error(msg);
|
|
}
|
|
var html =
|
|
(json && (json.html || (json.data && (json.data.html || json.data)))) || "";
|
|
if (typeof html !== "string") html = "";
|
|
return html;
|
|
});
|
|
}
|
|
return res.text().then(function (text) {
|
|
if (!res.ok) throw new Error("加载失败");
|
|
return text;
|
|
});
|
|
})
|
|
.then(function (html) {
|
|
if (loading && loading.parentNode) loading.parentNode.removeChild(loading);
|
|
if (active === loading) active = null;
|
|
return open({
|
|
html: html,
|
|
className: options.className || "modal-mini full-sm",
|
|
bottom: options.bottom,
|
|
mobileBottom: options.mobileBottom,
|
|
height: options.height,
|
|
});
|
|
})
|
|
.catch(function (err) {
|
|
if (loading && loading.parentNode) loading.parentNode.removeChild(loading);
|
|
if (active === loading) active = null;
|
|
open({
|
|
html:
|
|
'<div class="aksrt-remote-modal__error touch border-title"><b>提示</b></div>' +
|
|
'<div class="aksrt-remote-modal__error-body">' +
|
|
(err && err.message ? String(err.message) : "加载失败") +
|
|
'</div><div class="but-average modal-buts"><button type="button" class="but" data-aksrt-remote-modal-close>关闭</button></div>',
|
|
className: "modal-mini",
|
|
mobileBottom: true,
|
|
});
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
function fromTrigger(el) {
|
|
if (!el) return null;
|
|
var url =
|
|
el.getAttribute("data-remote") ||
|
|
el.getAttribute("data-aksrt-remote") ||
|
|
el.getAttribute("href") ||
|
|
"";
|
|
if (!url || url === "#") return null;
|
|
var height = el.getAttribute("data-height") || el.getAttribute("data-aksrt-height");
|
|
return openRemote({
|
|
url: url,
|
|
className: el.getAttribute("data-class") || el.getAttribute("data-aksrt-class") || "modal-mini full-sm",
|
|
height: height ? parseInt(height, 10) : 0,
|
|
mobileBottom: el.getAttribute("mobile-bottom") !== "false",
|
|
bottom: el.getAttribute("data-bottom") === "true",
|
|
});
|
|
}
|
|
|
|
document.addEventListener("click", function (e) {
|
|
var trigger = e.target.closest(
|
|
'[data-toggle="AksrtRemoteModal"], [data-toggle="RefreshModal"], [data-aksrt-remote-modal], a.aksrt-remote-modal-link'
|
|
);
|
|
if (!trigger) return;
|
|
if (trigger.getAttribute("data-aksrt-remote-modal") === "off") return;
|
|
e.preventDefault();
|
|
fromTrigger(trigger);
|
|
});
|
|
|
|
document.addEventListener("keydown", function (e) {
|
|
if (e.key === "Escape" && active) close();
|
|
});
|
|
|
|
window.AksrtRemoteModal = {
|
|
open: open,
|
|
openRemote: openRemote,
|
|
close: close,
|
|
fromTrigger: fromTrigger,
|
|
getActive: function () {
|
|
return active;
|
|
},
|
|
};
|
|
})(window, document);
|