diff --git a/assets/css/frontend/remote-modal.css b/assets/css/frontend/modal-runtime.css
similarity index 79%
rename from assets/css/frontend/remote-modal.css
rename to assets/css/frontend/modal-runtime.css
index 92a2fdd2..6c481ebc 100644
--- a/assets/css/frontend/remote-modal.css
+++ b/assets/css/frontend/modal-runtime.css
@@ -1,17 +1,17 @@
/**
- * Remote modal + ajax list primitives (shared).
+ * Modal runtime + ajax list primitives (shared).
*
* @package AKSRT
*/
/* ---- scroll lock ---- */
-html.aksrt-remote-modal-open,
-body.aksrt-remote-modal-open {
+html.aksrt-modal-runtime-open,
+body.aksrt-modal-runtime-open {
overflow: hidden;
}
-/* ---- remote modal shell ---- */
-.aksrt-remote-modal {
+/* ---- modal runtime shell ---- */
+.aksrt-modal-runtime {
position: fixed;
inset: 0;
z-index: 100050;
@@ -24,20 +24,20 @@ body.aksrt-remote-modal-open {
pointer-events: none;
}
-.aksrt-remote-modal.is-open {
+.aksrt-modal-runtime.is-open {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
-.aksrt-remote-modal__overlay {
+.aksrt-modal-runtime__overlay {
position: absolute;
inset: 0;
background: rgba(15, 18, 28, 0.48);
backdrop-filter: blur(2px);
}
-.aksrt-remote-modal__dialog {
+.aksrt-modal-runtime__dialog {
position: relative;
z-index: 1;
width: min(440px, calc(100vw - 32px));
@@ -51,15 +51,15 @@ body.aksrt-remote-modal-open {
color: var(--color-ink, #1f2937);
}
-.aksrt-remote-modal.is-open .aksrt-remote-modal__dialog {
+.aksrt-modal-runtime.is-open .aksrt-modal-runtime__dialog {
transform: translateY(0) scale(1);
}
-.aksrt-remote-modal--bottom {
+.aksrt-modal-runtime--bottom {
align-items: flex-end;
}
-.aksrt-remote-modal--bottom .aksrt-remote-modal__dialog {
+.aksrt-modal-runtime--bottom .aksrt-modal-runtime__dialog {
width: 100%;
max-width: 100%;
border-radius: 16px 16px 0 0;
@@ -67,11 +67,11 @@ body.aksrt-remote-modal-open {
transform: translateY(24px);
}
-.aksrt-remote-modal--bottom.is-open .aksrt-remote-modal__dialog {
+.aksrt-modal-runtime--bottom.is-open .aksrt-modal-runtime__dialog {
transform: translateY(0);
}
-.aksrt-remote-modal__header {
+.aksrt-modal-runtime__header {
display: flex;
align-items: center;
justify-content: space-between;
@@ -79,32 +79,32 @@ body.aksrt-remote-modal-open {
padding: 16px 18px 0;
}
-.aksrt-remote-modal__title {
+.aksrt-modal-runtime__title {
margin: 0;
font-size: 17px;
line-height: 1.35;
}
-.aksrt-remote-modal__content {
+.aksrt-modal-runtime__content {
padding: 18px 18px 16px;
}
-.aksrt-remote-modal__loading,
-.aksrt-remote-modal__error-body {
+.aksrt-modal-runtime__loading,
+.aksrt-modal-runtime__error-body {
padding: 28px 8px;
text-align: center;
color: var(--color-ink-2, #6b7280);
font-size: 14px;
}
-.aksrt-remote-modal__skeleton {
+.aksrt-modal-runtime__skeleton {
display: grid;
gap: 12px;
padding: 8px 2px;
}
-.aksrt-remote-modal__skeleton-line,
-.aksrt-remote-modal__skeleton-block {
+.aksrt-modal-runtime__skeleton-line,
+.aksrt-modal-runtime__skeleton-block {
display: block;
border-radius: 8px;
background: linear-gradient(90deg, rgba(148, 163, 184, 0.16), rgba(148, 163, 184, 0.3), rgba(148, 163, 184, 0.16));
@@ -112,26 +112,26 @@ body.aksrt-remote-modal-open {
animation: aksrt-modal-skeleton 1.2s ease-in-out infinite;
}
-.aksrt-remote-modal__skeleton-line {
+.aksrt-modal-runtime__skeleton-line {
width: 72%;
height: 12px;
}
-.aksrt-remote-modal__skeleton-line.is-wide {
+.aksrt-modal-runtime__skeleton-line.is-wide {
width: 92%;
height: 18px;
}
-.aksrt-remote-modal__skeleton-line.is-short {
+.aksrt-modal-runtime__skeleton-line.is-short {
width: 44%;
}
-.aksrt-remote-modal__skeleton-block {
+.aksrt-modal-runtime__skeleton-block {
width: 100%;
min-height: 84px;
}
-.aksrt-remote-modal__retry {
+.aksrt-modal-runtime__retry {
cursor: pointer;
}
@@ -140,8 +140,8 @@ body.aksrt-remote-modal-open {
100% { background-position: -100% 0; }
}
-.aksrt-remote-modal .touch.border-title,
-.aksrt-remote-modal .border-title {
+.aksrt-modal-runtime .touch.border-title,
+.aksrt-modal-runtime .border-title {
font-size: 16px;
font-weight: 700;
margin: 0 0 14px;
@@ -151,8 +151,7 @@ body.aksrt-remote-modal-open {
gap: 6px;
}
-.aksrt-remote-modal .abs-close,
-.aksrt-remote-modal .aksrt-remote-modal__close {
+.aksrt-modal-runtime .aksrt-modal-runtime__close {
position: absolute;
top: 10px;
right: 10px;
@@ -167,17 +166,17 @@ body.aksrt-remote-modal-open {
line-height: 1;
}
-.aksrt-remote-modal .but-average.modal-buts,
-.aksrt-remote-modal .modal-buts {
+.aksrt-modal-runtime .but-average.modal-buts,
+.aksrt-modal-runtime .modal-buts {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-top: 16px;
}
-.aksrt-remote-modal .but-average.modal-buts .but,
-.aksrt-remote-modal .modal-buts .but,
-.aksrt-remote-modal .modal-buts button {
+.aksrt-modal-runtime .but-average.modal-buts .but,
+.aksrt-modal-runtime .modal-buts .but,
+.aksrt-modal-runtime .modal-buts button {
min-height: 40px;
border-radius: 10px;
border: 1px solid var(--color-line, #e5e7eb);
@@ -187,9 +186,9 @@ body.aksrt-remote-modal-open {
cursor: pointer;
}
-.aksrt-remote-modal .modal-buts .but.c-blue,
-.aksrt-remote-modal .modal-buts button.c-blue,
-.aksrt-remote-modal .modal-buts .btn-primary {
+.aksrt-modal-runtime .modal-buts .but.c-blue,
+.aksrt-modal-runtime .modal-buts button.c-blue,
+.aksrt-modal-runtime .modal-buts .btn-primary {
background: var(--color-primary, #f04494);
border-color: color-mix(in srgb, var(--color-primary, #f04494) 70%, #000 0%);
color: var(--color-on-primary, #fff);
@@ -351,7 +350,7 @@ body.aksrt-remote-modal-open {
}
@media (max-width: 768px) {
- .aksrt-remote-modal:not(.aksrt-remote-modal--bottom) .aksrt-remote-modal__dialog {
+ .aksrt-modal-runtime:not(.aksrt-modal-runtime--bottom) .aksrt-modal-runtime__dialog {
width: calc(100vw - 20px);
}
}
diff --git a/assets/css/frontend/vip-modal.css b/assets/css/frontend/vip-modal.css
index bd68c1ce..ef6e0b58 100644
--- a/assets/css/frontend/vip-modal.css
+++ b/assets/css/frontend/vip-modal.css
@@ -2,26 +2,7 @@
* VIP pay modal + hover cards (zibll payvip-modal / vip-card).
*/
-.aksrt-vip-modal-open {
- overflow: hidden;
-}
-
-.aksrt-vip-modal-overlay {
- position: fixed;
- inset: 0;
- z-index: 1200;
- display: none;
- align-items: center;
- justify-content: center;
- padding: 16px;
- background: rgba(0, 0, 0, 0.5);
-}
-
-.aksrt-vip-modal-overlay.is-open {
- display: flex;
-}
-
-.aksrt-vip-modal-dialog {
+.aksrt-vip-modal-panel {
width: min(560px, 100%);
max-height: min(90vh, 760px);
overflow: auto;
@@ -31,11 +12,11 @@
}
/* VIP now uses the shared modal runtime host. */
-.aksrt-remote-modal.aksrt-vip-modal-shell {
+.aksrt-modal-runtime.aksrt-vip-modal-shell {
z-index: 1200;
}
-.aksrt-remote-modal.aksrt-vip-modal-shell .aksrt-remote-modal__dialog {
+.aksrt-modal-runtime.aksrt-vip-modal-shell .aksrt-modal-runtime__dialog {
width: min(560px, 100%);
max-height: min(90vh, 760px);
background: var(--main-bg-color, #fff);
@@ -43,7 +24,7 @@
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.22);
}
-.aksrt-remote-modal.aksrt-vip-modal-shell .aksrt-remote-modal__content {
+.aksrt-modal-runtime.aksrt-vip-modal-shell .aksrt-modal-runtime__content {
padding: 0;
}
@@ -52,8 +33,7 @@
padding: 18px 16px 16px;
}
-.payvip-modal .close,
-.aksrt-vip-modal-close {
+.aksrt-modal-close {
position: absolute;
top: 8px;
right: 12px;
@@ -121,7 +101,7 @@
/* Modal-only equity (admin HTML; no forced bullets) */
.payvip-modal .v-equity,
-.aksrt-vip-modal-dialog .v-equity {
+.aksrt-vip-modal-panel .v-equity {
display: block;
list-style: none;
margin: 8px 0 0;
@@ -130,7 +110,7 @@
line-height: 1.6;
}
.payvip-modal .v-equity > li,
-.aksrt-vip-modal-dialog .v-equity > li {
+.aksrt-vip-modal-panel .v-equity > li {
display: block;
margin: 0 0 6px;
padding: 0;
@@ -138,8 +118,8 @@
}
.payvip-modal .v-equity > li::before,
.payvip-modal .v-equity > li::after,
-.aksrt-vip-modal-dialog .v-equity > li::before,
-.aksrt-vip-modal-dialog .v-equity > li::after {
+.aksrt-vip-modal-panel .v-equity > li::before,
+.aksrt-vip-modal-panel .v-equity > li::after {
content: none !important;
display: none !important;
}
diff --git a/assets/css/frontend/widget-batch3f.css b/assets/css/frontend/widget-batch3f.css
index 9f1b0d1d..9609e788 100644
--- a/assets/css/frontend/widget-batch3f.css
+++ b/assets/css/frontend/widget-batch3f.css
@@ -166,7 +166,7 @@
font-size: 16px;
margin-bottom: 14px;
}
-.aksrt-checkin-modal__head .aksrt-remote-modal__close {
+.aksrt-checkin-modal__head .aksrt-modal-runtime__close {
margin-left: auto;
border: 0;
background: transparent;
diff --git a/assets/js/aksrt-runtime.js b/assets/js/aksrt-runtime.js
index 349575ba..85fa4183 100644
--- a/assets/js/aksrt-runtime.js
+++ b/assets/js/aksrt-runtime.js
@@ -5,7 +5,6 @@
* aksrt.tbquire(deps, cb) — lazy script/CSS loader
* aksrt.notyf(msg, opts) — unified toast (same-ID update)
* aksrt.confirm(msg, opts) — i18n confirm dialog
- * aksrt.refreshModal(opts) — remote content modal
* aksrt.scrollTopTo(target, off) — smart scroll with header offset
* aksrt.loading(el, show) — element-level loading overlay
* aksrt.Drawer(opts) — mobile fullscreen drawer + route
@@ -313,68 +312,6 @@
el.appendChild(mask);
};
- // ══════════════════════════════════════════════════════════════
- // §10.10 — refreshModal: remote content modal
- // ══════════════════════════════════════════════════════════════
-
- function closeRefreshModal() {
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === 'function') {
- window.AksrtModalRuntime.close('refresh-modal');
- }
- }
-
- aksrt.refreshModal = function (opts) {
- opts = opts || {};
- var runtime = window.AksrtModalRuntime;
- if (!runtime || typeof runtime.open !== 'function' || typeof runtime.openRemote !== 'function') {
- return Promise.reject(new Error('modal runtime unavailable'));
- }
-
- var base = {
- title: opts.title || '',
- className: opts.class || '',
- bottom: opts.mobile_from_bottom === true,
- mobileBottom: opts.mobile_from_bottom === true,
- cache: opts.cache !== false,
- cacheTtl: opts.cacheTtl || 15000,
- cacheTags: opts.cacheTags || opts.tags || [],
- kind: opts.kind || 'generic',
- };
-
- if (opts.content !== undefined) {
- return Promise.resolve(runtime.open(Object.assign({}, base, { html: opts.content })))
- .catch(function () { return null; });
- }
-
- if (opts.remote) {
- return runtime.openRemote(Object.assign({}, base, {
- url: opts.remote,
- method: opts.method || 'GET',
- }));
- }
-
- if (opts.action) {
- var ajaxUrl = (window.aksrtData && window.aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
- var params = new URLSearchParams();
- params.set('action', opts.action);
- if (window.aksrtData && window.aksrtData.nonce) params.set('nonce', window.aksrtData.nonce);
- if (opts.data) {
- Object.keys(opts.data).forEach(function (key) { params.set(key, opts.data[key]); });
- }
- return runtime.openRemote(Object.assign({}, base, {
- url: ajaxUrl,
- method: 'POST',
- cache: false,
- headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
- body: params.toString(),
- }));
- }
-
- return Promise.resolve(runtime.open(base));
- };
-
- aksrt.refreshModal.close = closeRefreshModal;
-
// ══════════════════════════════════════════════════════════════
// §10.11 — Drawer: mobile fullscreen slide-in + route
// ══════════════════════════════════════════════════════════════
@@ -707,7 +644,12 @@
btn.classList.remove('is-loading');
if (json && json.success) {
if (json.data && json.data.show_modal) {
- aksrt.refreshModal({ content: json.data.show_modal, mobile_from_bottom: true });
+ window.AksrtModalRuntime.open({
+ html: json.data.show_modal,
+ bottom: true,
+ mobileBottom: true,
+ kind: 'generic',
+ });
} else if (json.data && json.data.message) {
aksrt.notyf(json.data.message, 'success');
}
diff --git a/assets/js/core/modal-runtime.js b/assets/js/core/modal-runtime.js
index c5150ccd..7440dbbe 100644
--- a/assets/js/core/modal-runtime.js
+++ b/assets/js/core/modal-runtime.js
@@ -1,7 +1,7 @@
/**
* Canonical frontend modal runtime.
*
- * Keeps the existing remote-modal shell while adding cancellable loaders,
+ * Owns the modal shell while adding cancellable loaders,
* short in-memory caching, request deduplication, retryable errors, focus
* restoration, and modal-scoped rehydration.
*
@@ -10,7 +10,7 @@
(function (window, document) {
"use strict";
- var ROOT_ID = "aksrt-remote-modal-root";
+ var ROOT_ID = "aksrt-modal-runtime-root";
var active = null;
var sequence = 0;
var loaders = Object.create(null);
@@ -24,7 +24,7 @@
if (root) return root;
root = document.createElement("div");
root.id = ROOT_ID;
- root.className = "aksrt-remote-modal-host";
+ root.className = "aksrt-modal-runtime-host";
document.body.appendChild(root);
return root;
}
@@ -39,31 +39,28 @@
}
function lockScroll(lock) {
- document.documentElement.classList.toggle("aksrt-remote-modal-open", !!lock);
- document.body.classList.toggle("aksrt-remote-modal-open", !!lock);
+ document.documentElement.classList.toggle("aksrt-modal-runtime-open", !!lock);
+ document.body.classList.toggle("aksrt-modal-runtime-open", !!lock);
}
function skeleton(options) {
return (options && options.skeleton) ||
- '
' +
- '
' +
- '
' +
- '
' +
- '
' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
'
';
}
function telemetryStart(kind) {
if (kind === false) return "";
- var telemetry = window.AksrtModalTelemetry;
- if (!telemetry || typeof telemetry.start !== "function") return "";
- return telemetry.start(kind || "unknown");
+ return window.AksrtModalTelemetry.start(kind || "generic");
}
function telemetryMark(session, event) {
- var telemetry = window.AksrtModalTelemetry;
- if (!session || !session.telemetryId || !telemetry || typeof telemetry.mark !== "function") return;
- telemetry.mark(session.telemetryId, event);
+ if (!session || !session.telemetryId) return;
+ window.AksrtModalTelemetry.mark(session.telemetryId, event);
}
function setState(session, status, error) {
@@ -81,7 +78,7 @@
}
function closeSelector() {
- return "[data-aksrt-remote-modal-close], [data-aksrt-modal-close], [data-dismiss='modal'], .aksrt-remote-modal__close, .aksrt-vip-modal-close, .payvip-modal .close";
+ return "[data-aksrt-modal-close], .aksrt-modal-runtime__close";
}
function bindChrome(session) {
@@ -93,11 +90,11 @@
close("control");
return;
}
- if (event.target === shell || event.target.classList.contains("aksrt-remote-modal__overlay")) {
+ if (event.target === shell || event.target.classList.contains("aksrt-modal-runtime__overlay")) {
close("overlay");
return;
}
- var retry = event.target.closest("[data-aksrt-remote-modal-retry], [data-aksrt-modal-retry]");
+ var retry = event.target.closest("[data-aksrt-modal-retry]");
if (retry) {
event.preventDefault();
refresh();
@@ -138,31 +135,29 @@
var initialHtml = options.html !== undefined ? options.html : skeleton(options);
var titleId = "aksrt-modal-title-" + String(sequence + 1);
var titleHtml = options.title
- ? ''
+ ? ''
: "";
shell.className =
- "aksrt-remote-modal" +
- (bottom ? " aksrt-remote-modal--bottom" : "") +
+ "aksrt-modal-runtime" +
+ (bottom ? " aksrt-modal-runtime--bottom" : "") +
(options.className ? " " + options.className : "");
shell.setAttribute("role", "dialog");
shell.setAttribute("aria-modal", "true");
if (options.title) shell.setAttribute("aria-labelledby", titleId);
shell.setAttribute("tabindex", "-1");
shell.innerHTML =
- '
' +
- '
' +
+ '
' +
+ '
' +
titleHtml +
- '
' + initialHtml + "
";
+ '
' + initialHtml + "
";
var session = {
id: String(++sequence),
shell: shell,
- content: shell.querySelector(".aksrt-remote-modal__content"),
+ content: shell.querySelector(".aksrt-modal-runtime__content"),
options: options,
- state: window.AksrtModalState && typeof window.AksrtModalState.create === "function"
- ? window.AksrtModalState.create(options.kind || "generic")
- : { status: "idle", error: null },
+ state: window.AksrtModalState.create(options.kind || "generic"),
focusReturn: document.activeElement,
controller: null,
flight: null,
@@ -184,9 +179,6 @@
(nodes.length ? nodes[0] : shell).focus();
} catch (error) {}
- document.dispatchEvent(new CustomEvent("aksrt:remote-modal:open", {
- detail: { shell: shell, options: options, session: session },
- }));
document.dispatchEvent(new CustomEvent("aksrt:modal:open", {
detail: { shell: shell, options: options, session: session },
}));
@@ -203,9 +195,7 @@
function rehydrate(session) {
if (!session || !session.content) return;
- if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === "function") {
- window.AksrtFrontend.rehydrate(session.content, "modal");
- }
+ window.AksrtFrontend.rehydrate(session.content, "modal");
}
function abortSessionRequest(session) {
@@ -226,9 +216,6 @@
if (!preserveRequest) abortSessionRequest(session);
session.shell.classList.remove("is-open");
if (!preserveRequest) lockScroll(false);
- document.dispatchEvent(new CustomEvent("aksrt:remote-modal:close", {
- detail: { shell: session.shell, reason: reason || "close", session: session },
- }));
document.dispatchEvent(new CustomEvent("aksrt:modal:close", {
detail: { shell: session.shell, reason: reason || "close", session: session },
}));
@@ -249,8 +236,6 @@
if (json) {
if (typeof json.html === "string") return json.html;
if (json.data && typeof json.data.html === "string") return json.data.html;
- if (json.data && typeof json.data.show_modal === "string") return json.data.show_modal;
- if (typeof json.data === "string") return json.data;
}
return typeof result.text === "string" ? result.text : "";
}
@@ -266,11 +251,11 @@
setState(session, unauthorized ? "unauthorized" : "error", error);
telemetryMark(session, unauthorized ? "unauthorized" : "error");
session.content.innerHTML =
- '
提示
' +
- '
' + escapeHtml(errorMessage(error)) +
+ '
提示
' +
+ '
' + escapeHtml(errorMessage(error)) +
'
' +
- '' +
- '
';
+ '
' +
+ '
';
rehydrate(session);
if (typeof session.options.onError === "function") session.options.onError(error, session);
}
@@ -282,7 +267,7 @@
setState(session, "empty");
telemetryMark(session, "first-content");
telemetryMark(session, "ready");
- session.content.innerHTML = '
暂无内容
';
+ session.content.innerHTML = '
暂无内容
';
return;
}
session.content.innerHTML = html;
@@ -311,21 +296,14 @@
if (requestOptions.body === undefined) delete requestOptions.body;
if (!requestOptions.signal) delete requestOptions.signal;
- var request = window.AksrtRequest && typeof window.AksrtRequest.request === "function"
- ? function () { return window.AksrtRequest.request(session.options.url, requestOptions); }
- : function () {
- return window.fetch(session.options.url, requestOptions).then(function (response) {
- return response.text().then(function (text) { return { ok: response.ok, status: response.status, text: text, json: null }; });
- });
- };
+ var request = function () {
+ return window.AksrtRequest.request(session.options.url, requestOptions);
+ };
var method = String(session.options.method || "GET").toUpperCase();
var cacheable = method === "GET" && session.options.cache !== false;
var cacheKey = session.options.cacheKey ||
- method + ":" +
- (window.AksrtRequest && typeof window.AksrtRequest.url === "function"
- ? window.AksrtRequest.url(session.options.url, session.options.query)
- : session.options.url);
+ method + ":" + window.AksrtRequest.url(session.options.url, session.options.query);
var cache = window.AksrtModalCache;
var acquireOptions = {
ttl: Number(session.options.cacheTtl || 15000),
@@ -339,7 +317,7 @@
},
};
- session.flight = cacheable && cache && typeof cache.acquire === "function"
+ session.flight = cacheable
? cache.acquire(cacheKey, request, acquireOptions)
: { promise: request(), release: function () {} };
@@ -367,9 +345,7 @@
function sessionKey(options) {
if (!options || !options.url) return "";
var method = String(options.method || "GET").toUpperCase();
- var requestUrl = window.AksrtRequest && typeof window.AksrtRequest.url === "function"
- ? window.AksrtRequest.url(options.url, options.query)
- : String(options.url);
+ var requestUrl = window.AksrtRequest.url(options.url, options.query);
return method + ":" + requestUrl;
}
@@ -400,10 +376,7 @@
}
function invalidate(tags) {
- if (window.AksrtModalCache && typeof window.AksrtModalCache.invalidate === "function") {
- return window.AksrtModalCache.invalidate(tags);
- }
- return 0;
+ return window.AksrtModalCache.invalidate(tags);
}
window.AksrtModalRuntime = {
diff --git a/assets/js/core/modal-triggers.js b/assets/js/core/modal-triggers.js
new file mode 100644
index 00000000..375a2582
--- /dev/null
+++ b/assets/js/core/modal-triggers.js
@@ -0,0 +1,42 @@
+/**
+ * Declarative modal triggers.
+ *
+ * The only modal entry point is AksrtModalRuntime. Triggers carry an
+ * explicit URL and never create a second modal implementation.
+ *
+ * @package AKSRT
+ */
+(function (window, document) {
+ "use strict";
+
+ function openFromTrigger(element) {
+ var runtime = window.AksrtModalRuntime;
+ var url = element.getAttribute("data-aksrt-modal-url") || "";
+ if (!url || url === "#") return;
+
+ var ttl = parseInt(element.getAttribute("data-aksrt-modal-cache-ttl") || "15000", 10);
+ var tags = (element.getAttribute("data-aksrt-modal-cache-tags") || "").split(",").map(function (tag) {
+ return tag.trim();
+ }).filter(Boolean);
+
+ runtime.openRemote({
+ url: url,
+ method: element.getAttribute("data-aksrt-modal-method") || "GET",
+ className: element.getAttribute("data-aksrt-modal-class") || "modal-mini full-sm",
+ height: parseInt(element.getAttribute("data-aksrt-modal-height") || "0", 10) || 0,
+ mobileBottom: element.getAttribute("data-aksrt-modal-mobile-bottom") === "true",
+ bottom: element.getAttribute("data-aksrt-modal-bottom") === "true",
+ kind: element.getAttribute("data-aksrt-modal-kind") || "generic",
+ cacheKey: element.getAttribute("data-aksrt-modal-cache-key") || "",
+ cacheTtl: ttl > 0 ? ttl : 15000,
+ cacheTags: tags,
+ });
+ }
+
+ document.addEventListener("click", function (event) {
+ var trigger = event.target.closest('[data-aksrt-modal="remote"]');
+ if (!trigger) return;
+ event.preventDefault();
+ openFromTrigger(trigger);
+ });
+})(window, document);
diff --git a/assets/js/core/remote-modal.js b/assets/js/core/remote-modal.js
deleted file mode 100644
index e115c9b5..00000000
--- a/assets/js/core/remote-modal.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
- * AKSRT Remote Modal compatibility bridge.
- *
- * The lifecycle implementation lives in AksrtModalRuntime. This file keeps
- * the legacy trigger contract and the AksrtRemoteModal public alias stable.
- *
- * @package AKSRT
- */
-(function (window, document) {
- "use strict";
-
- function runtime() {
- return window.AksrtModalRuntime || null;
- }
-
- function fromTrigger(element) {
- var modalRuntime = runtime();
- if (!modalRuntime || !element) return Promise.reject(new Error("modal runtime unavailable"));
-
- var url = element.getAttribute("data-remote") ||
- element.getAttribute("data-aksrt-remote") ||
- element.getAttribute("href") || "";
- if (!url || url === "#") return Promise.resolve(null);
-
- var ttl = parseInt(element.getAttribute("data-modal-cache-ttl") || "15000", 10);
- var tags = (element.getAttribute("data-modal-cache-tags") || "").split(",").map(function (tag) {
- return tag.trim();
- }).filter(Boolean);
-
- return modalRuntime.openRemote({
- url: url,
- method: element.getAttribute("data-method") || "GET",
- className: element.getAttribute("data-class") || element.getAttribute("data-aksrt-class") || "modal-mini full-sm",
- height: parseInt(element.getAttribute("data-height") || element.getAttribute("data-aksrt-height") || "0", 10) || 0,
- mobileBottom: element.getAttribute("mobile-bottom") !== "false",
- bottom: element.getAttribute("data-bottom") === "true",
- kind: element.getAttribute("data-modal-kind") || "generic",
- cacheKey: element.getAttribute("data-modal-cache-key") || "",
- cacheTtl: ttl > 0 ? ttl : 15000,
- cacheTags: tags,
- });
- }
-
- function bind() {
- if (window.__aksrtRemoteModalBound) return;
- if (!runtime()) return;
- window.__aksrtRemoteModalBound = true;
-
- document.addEventListener("click", function (event) {
- var trigger = event.target.closest(
- '[data-toggle="AksrtRemoteModal"], [data-toggle="RefreshModal"], [data-aksrt-remote-modal], a.aksrt-remote-modal-link'
- );
- if (!trigger || trigger.getAttribute("data-aksrt-remote-modal") === "off") return;
- event.preventDefault();
- fromTrigger(trigger);
- });
- }
-
- function expose() {
- var modalRuntime = runtime();
- if (!modalRuntime) return;
- window.AksrtRemoteModal = {
- open: modalRuntime.open,
- openRemote: modalRuntime.openRemote,
- close: modalRuntime.close,
- refresh: modalRuntime.refresh,
- fromTrigger: fromTrigger,
- getActive: modalRuntime.getActive,
- };
- bind();
- }
-
- expose();
- if (!window.AksrtModalRuntime) {
- document.addEventListener("aksrt:modal-runtime:ready", expose, { once: true });
- }
-})(window, document);
diff --git a/assets/js/enhancements2.js b/assets/js/enhancements2.js
index 110ef7c3..273edb5d 100644
--- a/assets/js/enhancements2.js
+++ b/assets/js/enhancements2.js
@@ -302,7 +302,7 @@
initParallax();
initFloatLabels();
initSigninCalendar();
- document.addEventListener('aksrt:remote-modal:open', function () {
+ document.addEventListener('aksrt:modal:open', function () {
// Remote modal may inject calendar markup after open.
window.setTimeout(initSigninCalendar, 30);
});
diff --git a/assets/js/order-center.js b/assets/js/order-center.js
index 941b14c9..9927f199 100644
--- a/assets/js/order-center.js
+++ b/assets/js/order-center.js
@@ -16,21 +16,10 @@
var detailOrderNumber = "";
function toast(message, type) {
- if (typeof window.AksrtToast === "function") {
- window.AksrtToast(message, type || "info");
- return;
- }
- if (typeof window.aksrtNotify === "function") {
- window.aksrtNotify(message, type || "info");
- return;
- }
- window.alert(message);
+ window.AksrtToast(message, type || "info");
}
function request(path, body, method) {
- if (!window.AksrtRequest || typeof window.AksrtRequest.request !== "function") {
- return Promise.resolve({ success: false, message: "请求适配器未加载" });
- }
var options = { method: method || "POST" };
if (body !== undefined) options.body = JSON.stringify(body || {});
return window.AksrtRequest.request(path, options).then(function (response) {
@@ -219,9 +208,7 @@
button.disabled = true;
request(orderPath(number, "/pay"), method ? { payment_method: method } : {}).then(function (result) {
if (!result.success) throw new Error(result.message || "支付失败");
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
- window.AksrtModalRuntime.invalidate(["orders", "vip"]);
- }
+ window.AksrtModalRuntime.invalidate(["orders", "vip"]);
var data = result.data || {};
if (data.redirect && data.payment_url) {
window.location.assign(data.payment_url);
@@ -246,9 +233,7 @@
button.disabled = true;
request(orderPath(number, "/cancel")).then(function (result) {
if (!result.success) throw new Error(result.message || "关闭订单失败");
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === "function") {
- window.AksrtModalRuntime.invalidate(["orders", "vip"]);
- }
+ window.AksrtModalRuntime.invalidate(["orders", "vip"]);
toast("订单已关闭");
refreshView();
}).catch(function (error) {
diff --git a/assets/js/pay-vip.js b/assets/js/pay-vip.js
index fbbf5283..ad0ef7b2 100644
--- a/assets/js/pay-vip.js
+++ b/assets/js/pay-vip.js
@@ -6,16 +6,7 @@
"use strict";
function api(path, options) {
- var opts = Object.assign({}, options || {});
- 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 window.AksrtRequest.request(path, Object.assign({}, options || {})).then(function (response) {
return {
ok: response.ok,
status: response.status,
@@ -34,11 +25,10 @@
var activeVipShell = null;
function openModal() {
- if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.open !== "function") return null;
activeVipShell = window.AksrtModalRuntime.open({
className: "aksrt-vip-modal-shell",
mobileBottom: false,
- skeleton: '
',
+ skeleton: '
',
telemetry: false,
kind: "vip",
});
@@ -46,9 +36,7 @@
}
function closeModal() {
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === "function") {
- window.AksrtModalRuntime.close("vip");
- }
+ window.AksrtModalRuntime.close("vip");
activeVipShell = null;
}
@@ -151,17 +139,13 @@
}
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"]);
- }
+ 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"]);
- }
+ window.AksrtModalRuntime.invalidate(["vip", "orders"]);
setTip(pane, (data && data.message) || "开通成功,正在刷新…");
setTimeout(function () {
window.location.reload();
@@ -189,11 +173,6 @@
if (telemetry && telemetryId && typeof telemetry.mark === "function") telemetry.mark(telemetryId, event);
}
var shell = openModal();
- if (!shell) {
- loading = false;
- telemetryMark("error");
- return;
- }
var body = shell.querySelector("[data-aksrt-vip-modal-body]");
telemetryMark("shell");
@@ -220,9 +199,7 @@
if (body) {
body.innerHTML = r.json.html;
telemetryMark("first-content");
- if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === "function") {
- window.AksrtFrontend.rehydrate(body, "modal");
- }
+ window.AksrtFrontend.rehydrate(body, "modal");
bindModalUi(body);
telemetryMark("ready");
}
diff --git a/assets/js/user-badges.js b/assets/js/user-badges.js
index 9e3fd19e..04ed5e8d 100644
--- a/assets/js/user-badges.js
+++ b/assets/js/user-badges.js
@@ -3,15 +3,12 @@
*
* User-center modal: category-grouped medal card grid + in-modal detail
* view (medal-background card, obtain time, wear action). Also provides the
- * global wear handler used inside remote (AJAX HTML) badge modals.
+ * global wear handler used inside runtime-loaded badge modals.
*/
(function () {
'use strict';
function api(path, options) {
- if (!window.AksrtRequest || typeof window.AksrtRequest.request !== 'function') {
- return Promise.resolve({ ok: false, status: 0, json: { message: '请求适配器未加载' } });
- }
return window.AksrtRequest.request(path, options || {}).then(function (response) {
return { ok: response.ok, status: response.status, json: response.json || {} };
}).catch(function (error) {
@@ -25,15 +22,7 @@
}
function toast(msg, type) {
- if (typeof window.AksrtToast === 'function') {
- window.AksrtToast(msg, type || 'info');
- return;
- }
- if (typeof window.aksrtNotify === 'function') {
- window.aksrtNotify(msg, type || 'info');
- return;
- }
- try { console.log('[AKSRT badges]', msg); } catch (_) {}
+ window.AksrtToast(msg, type || 'info');
}
function escapeHtml(s) {
@@ -260,9 +249,7 @@
return;
}
toast((r.json && r.json.message) || '已佩戴徽章', 'success');
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
- window.AksrtModalRuntime.invalidate(['badges', 'identity']);
- }
+ window.AksrtModalRuntime.invalidate(['badges', 'identity']);
state.data = (r.json && r.json.data) || state.data;
renderHead();
renderGrid();
@@ -352,9 +339,7 @@
return;
}
toast((r.json && r.json.message) || '已佩戴徽章', 'success');
- if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.invalidate === 'function') {
- window.AksrtModalRuntime.invalidate(['badges', 'identity']);
- }
+ window.AksrtModalRuntime.invalidate(['badges', 'identity']);
// Re-open the refreshed single-badge modal so state stays in sync.
var refreshed = null;
try {
@@ -362,19 +347,21 @@
var data = r.json && r.json.data;
var isSelf = data && data.is_self;
if (isSelf && data.wear) {
- refreshed = (window.aksrtData && aksrtData.ajaxUrl)
- ? aksrtData.ajaxUrl + '?action=aksrt_single_badge_info_modal&name=' + encodeURIComponent(data.wear) + '&user_id=' + (uid || 0)
+ refreshed = uid > 0 && data.wear
+ ? window.AksrtRequest.url('/users/' + uid + '/badges/' + encodeURIComponent(data.wear) + '/modal')
: null;
}
} catch (_) {}
- try {
- if (refreshed && window.AksrtRemoteModal && typeof window.AksrtRemoteModal.openRemote === 'function') {
- window.AksrtRemoteModal.openRemote({ url: refreshed, className: 'modal-mini', mobileBottom: true });
- } else {
- document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close'));
- }
- } catch (_) {
- try { document.dispatchEvent(new CustomEvent('aksrt:remote-modal:close')); } catch (__) {}
+ if (refreshed) {
+ window.AksrtModalRuntime.openRemote({
+ url: refreshed,
+ className: 'modal-mini',
+ mobileBottom: true,
+ kind: 'badges',
+ cache: false,
+ });
+ } else {
+ window.AksrtModalRuntime.close('badge-wear');
}
}).catch(function () {
toast('网络错误', 'error');
diff --git a/inc/helpers/aksrt-ajax-list.php b/inc/helpers/aksrt-ajax-list.php
index 9013a057..1021888b 100644
--- a/inc/helpers/aksrt-ajax-list.php
+++ b/inc/helpers/aksrt-ajax-list.php
@@ -1,6 +1,6 @@
* } $args
*/
- function aksrt_get_remote_modal_link(array $args = []): string {
+ function aksrt_get_modal_link(array $args = []): string {
$tag = sanitize_key((string) ($args['tag'] ?? 'a'));
if (!in_array($tag, ['a', 'button', 'span'], true)) {
$tag = 'a';
@@ -90,15 +90,15 @@ if (!function_exists('aksrt_get_remote_modal_link')) {
$height = isset($args['height']) ? (int) $args['height'] : 0;
$mobile = !isset($args['mobile_bottom']) || !empty($args['mobile_bottom']);
- $attrs = ' class="' . esc_attr(trim($class . ' aksrt-remote-modal-link')) . '"';
- $attrs .= ' data-toggle="AksrtRemoteModal"';
- $attrs .= ' data-remote="' . esc_url($url) . '"';
- $attrs .= ' data-class="' . esc_attr($data_class) . '"';
+ $attrs = ' class="' . esc_attr(trim($class . ' aksrt-modal-link')) . '"';
+ $attrs .= ' data-aksrt-modal="remote"';
+ $attrs .= ' data-aksrt-modal-url="' . esc_url($url) . '"';
+ $attrs .= ' data-aksrt-modal-class="' . esc_attr($data_class) . '"';
if ($height > 0) {
- $attrs .= ' data-height="' . esc_attr((string) $height) . '"';
+ $attrs .= ' data-aksrt-modal-height="' . esc_attr((string) $height) . '"';
}
if ($mobile) {
- $attrs .= ' mobile-bottom="true"';
+ $attrs .= ' data-aksrt-modal-mobile-bottom="true"';
}
if ($tag === 'a') {
$attrs .= ' href="' . esc_url($url !== '' ? $url : '#') . '"';
diff --git a/src/Content/VipServiceModal.php b/src/Content/VipServiceModal.php
index c26b0d3e..b1433d68 100644
--- a/src/Content/VipServiceModal.php
+++ b/src/Content/VipServiceModal.php
@@ -152,7 +152,7 @@ trait VipServiceModal {
}
$html = '
';
- $html .= '
';
+ $html .= '
';
$html .= '
- ' . $avatar . '
';
$html .= '- ' . esc_html($name) . '';
if ($desc !== '') {
@@ -232,7 +232,7 @@ trait VipServiceModal {
}
$html = '
';
- $html .= '
';
+ $html .= '
';
$html .= '
- ' . $avatar . '
';
$html .= '- ' . esc_html($name) . '';
$html .= '
'
diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php
index 1b9bd2ff..bbf76059 100644
--- a/src/Core/ScriptsPart1b.php
+++ b/src/Core/ScriptsPart1b.php
@@ -130,7 +130,7 @@ trait ScriptsPart1b {
}
// 4a-b. 全局补充样式(骨架屏、空状态、分页、加载指示器)
- foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner', 'remote-modal'] as $slug) {
+ foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner', 'modal-runtime'] as $slug) {
$css_path = get_template_directory() . '/assets/css/frontend/' . $slug . '.css';
if (file_exists($css_path)) {
wp_enqueue_style(
@@ -894,7 +894,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-user-badges',
$base_uri . '/assets/js/user-badges.js',
- ['aksrt-script', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-core-remote-modal'],
+ ['aksrt-script', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-core-modal-triggers'],
(string) filemtime($badges_js_path),
true
);
@@ -917,7 +917,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-user-badges',
$base_uri . '/assets/js/user-badges.js',
- ['aksrt-script', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-core-remote-modal'],
+ ['aksrt-script', 'aksrt-core-request', 'aksrt-core-modal-runtime', 'aksrt-core-modal-triggers'],
(string) filemtime($badges_js_path_global),
true
);
diff --git a/src/Core/ScriptsPart1c.php b/src/Core/ScriptsPart1c.php
index a715b0b7..bd5dbff9 100644
--- a/src/Core/ScriptsPart1c.php
+++ b/src/Core/ScriptsPart1c.php
@@ -101,7 +101,7 @@ trait ScriptsPart1c {
true
);
- // Runtime infrastructure: tbquire / notyf / confirm / refreshModal / scrollTopTo / [data-*] declarative
+ // Runtime infrastructure: tbquire / notyf / confirm / scrollTopTo / [data-*] declarative
$runtime_js = get_template_directory() . '/assets/js/aksrt-runtime.js';
if (file_exists($runtime_js)) {
wp_enqueue_script(
@@ -132,7 +132,7 @@ trait ScriptsPart1c {
'aksrt-core-modal-cache' => 'modal-cache.js',
'aksrt-core-modal-state' => 'modal-state.js',
'aksrt-core-modal-runtime' => 'modal-runtime.js',
- 'aksrt-core-remote-modal' => 'remote-modal.js',
+ 'aksrt-core-modal-triggers' => 'modal-triggers.js',
'aksrt-core-pagination' => 'pagination.js',
'aksrt-core-archive' => 'archive.js',
'aksrt-core-footer-tabbar' => 'footer-tabbar.js',
@@ -153,7 +153,7 @@ trait ScriptsPart1c {
'aksrt-core-modal-cache',
'aksrt-core-modal-state',
]);
- } elseif ($handle === 'aksrt-core-remote-modal') {
+ } elseif ($handle === 'aksrt-core-modal-triggers') {
$deps[] = 'aksrt-core-modal-runtime';
}
wp_enqueue_script(
diff --git a/src/Credits/Signin.php b/src/Credits/Signin.php
index cfb6bff3..3b072b2d 100644
--- a/src/Credits/Signin.php
+++ b/src/Credits/Signin.php
@@ -276,7 +276,7 @@ class Signin {
$html .= '';
$html .= '';
$html .= '' . esc_html__('签到领奖励', 'aksrt') . '';
- $html .= '';
+ $html .= '';
$html .= '
';
$html .= '';
diff --git a/src/User/Badges.php b/src/User/Badges.php
index f3650169..ff69551e 100644
--- a/src/User/Badges.php
+++ b/src/User/Badges.php
@@ -30,11 +30,6 @@ class Badges {
public function init(): void {
add_action('rest_api_init', [$this, 'register_routes']);
add_action('aksrt_badge_granted', [$this, 'on_badge_granted_notify'], 10, 3);
- // HTML modal for remote-modal (zibll user_medal_info_modal).
- add_action('wp_ajax_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
- add_action('wp_ajax_nopriv_aksrt_user_badge_info_modal', [$this, 'ajax_info_modal']);
- add_action('wp_ajax_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
- add_action('wp_ajax_nopriv_aksrt_single_badge_info_modal', [$this, 'ajax_single_info_modal']);
}
/**
@@ -71,39 +66,32 @@ class Badges {
}
/**
- * AJAX: full badge list modal HTML.
+ * REST response for the badge list modal.
*/
- public function ajax_info_modal(): void {
- $user_id = max(0, (int) ($_REQUEST['id'] ?? $_REQUEST['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- if ($user_id <= 0) {
- $user_id = get_current_user_id();
- }
+ public function rest_modal_list(\WP_REST_Request $request) {
+ $user_id = absint($request->get_param('id'));
if ($user_id <= 0 || !get_userdata($user_id)) {
- wp_send_json_error(['message' => __('用户不存在', 'aksrt')], 404);
+ return new \WP_Error('aksrt_badge_user_missing', __('用户不存在', 'aksrt'), ['status' => 404]);
}
- $html = self::render_info_modal_html($user_id);
- wp_send_json_success(['html' => $html]);
+ return new \WP_REST_Response(['html' => self::render_info_modal_html($user_id)], 200);
}
/**
- * AJAX: single badge detail modal.
+ * REST response for a single badge modal.
*/
- public function ajax_single_info_modal(): void {
- $slug = sanitize_key((string) ($_REQUEST['name'] ?? $_REQUEST['slug'] ?? '')); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $user_id = max(0, (int) ($_REQUEST['user_id'] ?? $_REQUEST['id'] ?? 0)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ public function rest_modal_single(\WP_REST_Request $request) {
+ $slug = sanitize_key((string) $request->get_param('slug'));
+ $user_id = absint($request->get_param('id'));
if ($slug === '') {
- wp_send_json_error(['message' => __('徽章不存在', 'aksrt')], 404);
+ return new \WP_Error('aksrt_badge_missing', __('徽章不存在', 'aksrt'), ['status' => 404]);
}
- $html = self::render_single_modal_html($slug, $user_id);
- wp_send_json_success(['html' => $html]);
+ return new \WP_REST_Response(['html' => self::render_single_modal_html($slug, $user_id)], 200);
}
/**
- * Badge list modal HTML (zibll zib_get_user_medal_info_modal parity).
+ * Badge list modal HTML.
*
- * Head (avatar + owner + worn medal) + category-grouped medal card grid.
- * Each card deep-links into the single badge modal via the remote-modal
- * contract (data-toggle="AksrtRemoteModal" + data-remote).
+ * Head (avatar + owner + worn medal) and category-grouped medal card grid.
*/
public static function render_info_modal_html(int $user_id): string {
$user = get_userdata($user_id);
@@ -134,7 +122,7 @@ class Badges {
}
$html = '
';
- $html .= '
';
+ $html .= '
';
$html .= '
';
$html .= '
 $avatar) . ')
';
$html .= '
' . esc_html(sprintf(__('%s的徽章', 'aksrt'), $display)) . '' . $wear_slot . '
';
@@ -162,9 +150,9 @@ class Badges {
// Footer: quick jump to my own badges while browsing someone else (zibll).
if (!$is_self && $current > 0) {
- $my_url = add_query_arg(['action' => 'aksrt_user_badge_info_modal', 'id' => $current], admin_url('admin-ajax.php'));
+ $my_url = rest_url('aksrt/v1/users/' . $current . '/badges/modal');
$html .= '
';
}
@@ -199,12 +187,9 @@ class Badges {
}
$card .= '
';
- $url = add_query_arg(
- ['action' => 'aksrt_single_badge_info_modal', 'name' => $slug, 'user_id' => $user_id],
- admin_url('admin-ajax.php')
- );
+ $url = rest_url('aksrt/v1/users/' . $user_id . '/badges/' . rawurlencode($slug) . '/modal');
- return '
' . $card . '';
+ return '
' . $card . '';
}
/**
@@ -244,7 +229,7 @@ class Badges {
}
$html = '
';
- $html .= '
';
+ $html .= '
';
$html .= '
';
$html .= '
';
$html .= '
' . $icon_html;
@@ -262,8 +247,8 @@ class Badges {
// Back to the full badge list modal.
if ($user_id > 0) {
- $back_url = add_query_arg(['action' => 'aksrt_user_badge_info_modal', 'id' => $user_id], admin_url('admin-ajax.php'));
- $html .= '
' . esc_html__('返回徽章列表', 'aksrt') . '';
+ $back_url = rest_url('aksrt/v1/users/' . $user_id . '/badges/modal');
+ $html .= '
' . esc_html__('返回徽章列表', 'aksrt') . '';
}
$html .= '
';
@@ -271,7 +256,7 @@ class Badges {
}
/**
- * Remote-modal trigger for a user's badge list (zibll zib_get_user_medal_info_link).
+ * Declarative modal runtime trigger for a user's badge list.
*/
public static function get_info_link(int $user_id, string $class = '', ?string $text = null): string {
if ($user_id <= 0 || !self::enabled()) {
@@ -280,17 +265,11 @@ class Badges {
if ($text === null) {
$text = __('徽章明细', 'aksrt');
}
- if (!function_exists('aksrt_get_remote_modal_link')) {
+ if (!function_exists('aksrt_get_modal_link')) {
return '';
}
- $url = add_query_arg(
- [
- 'action' => 'aksrt_user_badge_info_modal',
- 'id' => $user_id,
- ],
- admin_url('admin-ajax.php')
- );
- return aksrt_get_remote_modal_link([
+ $url = rest_url('aksrt/v1/users/' . $user_id . '/badges/modal');
+ return aksrt_get_modal_link([
'tag' => 'a',
'class' => trim('user-medal-info ' . $class),
'text' => $text,
@@ -315,6 +294,37 @@ class Badges {
],
]);
+ register_rest_route('aksrt/v1', '/users/(?P
\d+)/badges/modal', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'rest_modal_list'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'id' => [
+ 'type' => 'integer',
+ 'required' => true,
+ 'sanitize_callback' => 'absint',
+ ],
+ ],
+ ]);
+
+ register_rest_route('aksrt/v1', '/users/(?P\d+)/badges/(?P[a-z0-9_-]+)/modal', [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'rest_modal_single'],
+ 'permission_callback' => '__return_true',
+ 'args' => [
+ 'id' => [
+ 'type' => 'integer',
+ 'required' => true,
+ 'sanitize_callback' => 'absint',
+ ],
+ 'slug' => [
+ 'type' => 'string',
+ 'required' => true,
+ 'sanitize_callback' => 'sanitize_key',
+ ],
+ ],
+ ]);
+
register_rest_route('aksrt/v1', '/users/me/badges/wear', [
'methods' => 'POST',
'callback' => [$this, 'rest_wear'],
diff --git a/templates/parts/user/author-hero.php b/templates/parts/user/author-hero.php
index 9ac275a2..fcd8f509 100644
--- a/templates/parts/user/author-hero.php
+++ b/templates/parts/user/author-hero.php
@@ -109,7 +109,7 @@ $pm_url = ($pm_enabled && $messages_url !== '')