Files
wordpress-theme-aksrt/assets/js/build/public.js
T
2026-08-26 12:10:06 +08:00

5445 lines
191 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* source: assets/js/lazy-image-attr.js */
/**
* Build lazy-image attributes for dynamically rendered AKSRT markup.
* legacy owns the lazySizes runtime; this file only provides the shared bridge.
*/
(function (window) {
'use strict';
function escapeAttribute(value) {
return String(value == null ? '' : value)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
window.aksrtGetLazyImageAttr = function (key, src, className, size) {
var config = window.aksrtData || {};
var scopes = config.lazyScopes || {};
var classes = String(className || '').trim();
var realSrc = escapeAttribute(src);
if (!src || scopes[key] === false) {
return ' class="' + escapeAttribute(classes) + '" src="' + realSrc + '"';
}
var thumbs = config.lazyThumbs || {};
var placeholder = key === 'lazy_avatar'
? (config.lazyAvatar || thumbs.sm || '')
: (thumbs[size || 'default'] || thumbs.default || '');
return ' class="lazyload ' + escapeAttribute(classes) + '" src="' +
escapeAttribute(placeholder) + '" data-src="' + realSrc + '"';
};
})(window);
;
/* source: assets/js/rehydrate.js */
/**
* AKSRT Rehydrate System
*
* Centralized component initialization and re-initialization system.
* Components register their init functions, and rehydrate() is called
* to initialize or re-initialize them on a given root element.
*
* Usage:
* AksrtFrontend.register('name', initFn); // Register a component
* AksrtFrontend.rehydrate(root, 'init'); // Initialize all components
*
* @package AKSRT
* @since 5.0.0
*/
(function () {
'use strict';
var _registry = [];
var _initialized = [];
window.AksrtFrontend = {
/**
* Register a component's init function.
*
* @param {string} name Unique component name
* @param {Function} initFn Function(root, reason) - root defaults to document
*/
register: function (name, initFn) {
if (typeof name !== 'string' || typeof initFn !== 'function') return;
// Prevent duplicate registrations
for (var i = 0; i < _registry.length; i++) {
if (_registry[i].name === name) {
_registry[i].initFn = initFn;
return;
}
}
_registry.push({ name: name, initFn: initFn });
},
/**
* Initialize or re-initialize all registered components on a root element.
*
* @param {Element|Document} root The root DOM element to search within
* @param {string} reason Why rehydrate is called:
* 'init' = initial page load
* 'ajax' = after AJAX content injection
* 'modal' = after modal content loads
* Custom string for other triggers
*/
rehydrate: function (root, reason) {
root = root || document;
reason = reason || 'init';
for (var i = 0; i < _registry.length; i++) {
var entry = _registry[i];
try {
entry.initFn(root, reason);
} catch (err) {
console.error('[AKSRT Rehydrate] Error initializing "' + entry.name + '":', err);
}
}
// Dispatch event so external scripts can hook in
try {
root.dispatchEvent(new CustomEvent('aksrt:rehydrated', {
bubbles: true,
detail: { reason: reason }
}));
} catch (e) { /* IE fallback */ }
},
/**
* Get the list of registered component names (for debugging).
*/
getRegistered: function () {
return _registry.map(function (r) { return r.name; });
}
};
})();
;
/* source: assets/js/notyf.js */
/**
* legacy-compatible frontend notifications.
*
* @package AKSRT
*/
(function ($, window, document) {
'use strict';
function closeNotyf($element) {
$element.addClass('notyn-out');
window.setTimeout(function () {
$element.remove();
}, 1000);
}
function notyf(message, type, time, id) {
var $body = $(document.body);
if (!$('.notyn').length) {
$body.append('<div class="notyn"></div>');
}
type = type || 'success';
if (type === 'error') {
type = 'danger';
}
time = time || 5000;
time = time < 100 ? time * 1000 : time;
var idAttribute = id ? ' id="' + id + '"' : '';
var $notification = $('<div class="noty1"' + idAttribute + '><div class="notyf ' + type + '">' + message + '</div></div>');
var shouldClose = !id;
if (id && $('#' + id).length) {
$('#' + id)
.find('.notyf')
.removeClass()
.addClass('notyf ' + type)
.html(message);
$notification = $('#' + id);
shouldClose = true;
} else {
$('.notyn').append($notification);
}
if (shouldClose) {
window.setTimeout(function () {
closeNotyf($notification);
}, time);
}
}
$(document.body).on('click', '.noty1', function () {
closeNotyf($(this));
});
window.notyf = notyf;
})(jQuery, window, document);
;
/* source: assets/js/aksrt-runtime.js */
/**
* AKSRT Runtime Infrastructure
*
* Unified core primitives for the entire frontend:
* aksrt.tbquire(deps, cb) — lazy script/CSS loader
* aksrt.confirm(msg, opts) — i18n confirm dialog
* aksrt.scrollTopTo(target, off) — smart scroll with header offset
* aksrt.loading(el, show) — element-level loading overlay
* aksrt.route(url, title) — push/replace URL + title
*
* Declarative interactions (event delegation on body):
* [data-close] — close target element
* [data-onclick] — trigger click on another element
* [data-confirm] — confirm before action
* [data-action] — universal AJAX action button (like/follow/checkin)
* [ajax-action] — general AJAX button with modal response
*
* @package AKSRT
* @since 5.0.0
*/
(function () {
'use strict';
var aksrt = window.aksrt || {};
window.aksrt = aksrt;
// ══════════════════════════════════════════════════════════════
// §10.11 — tbquire: lazy script/CSS loader
// ══════════════════════════════════════════════════════════════
var _loadedScripts = {};
var _loadingPromises = {};
function loadSingleScript(src) {
if (_loadedScripts[src]) return Promise.resolve();
if (_loadingPromises[src]) return _loadingPromises[src];
_loadingPromises[src] = new Promise(function (resolve, reject) {
var isCSS = /\.css(\?|$)/i.test(src);
var el;
if (isCSS) {
el = document.createElement('link');
el.rel = 'stylesheet';
el.href = src;
} else {
el = document.createElement('script');
el.src = src;
el.async = true;
}
el.onload = function () {
_loadedScripts[src] = true;
delete _loadingPromises[src];
resolve();
};
el.onerror = function () {
delete _loadingPromises[src];
reject(new Error('Failed to load: ' + src));
};
(document.head || document.documentElement).appendChild(el);
});
return _loadingPromises[src];
}
aksrt.tbquire = function (deps, callback) {
if (typeof deps === 'string') deps = [deps];
if (!Array.isArray(deps) || !deps.length) {
if (typeof callback === 'function') callback();
return Promise.resolve();
}
var resolvedDeps = deps.map(function (dep) {
if (/^https?:\/\/|^\//.test(dep)) return dep;
var map = (window.aksrtData && aksrtData.tbquireMap) || {};
return map[dep] || dep;
});
var p = Promise.all(resolvedDeps.map(loadSingleScript));
if (typeof callback === 'function') {
p.then(callback).catch(function (err) {
console.error('[AKSRT tbquire]', err);
});
}
return p;
};
// ══════════════════════════════════════════════════════════════
// §10.10 — confirm: i18n confirm dialog + [data-confirm]
// ══════════════════════════════════════════════════════════════
var _i18n = (window.aksrtData && aksrtData.i18n) || {};
function __(key, fallback) {
return _i18n[key] || fallback || key;
}
aksrt.__ = __;
aksrt.confirm = function (msg, opts) {
opts = opts || {};
return new Promise(function (resolve) {
var overlay = document.createElement('div');
overlay.className = 'aksrt-confirm-overlay';
var dialog = document.createElement('div');
dialog.className = 'aksrt-confirm-dialog';
dialog.setAttribute('role', 'alertdialog');
dialog.setAttribute('aria-modal', 'true');
dialog.innerHTML =
'<div class="aksrt-confirm__body">' + escapeHtml(msg) + '</div>' +
'<div class="aksrt-confirm__actions">' +
'<button type="button" class="aksrt-confirm__cancel btn-ghost">' +
escapeHtml(opts.cancelText || __('cancel', '取消')) +
'</button>' +
'<button type="button" class="aksrt-confirm__ok btn-primary">' +
escapeHtml(opts.okText || __('confirm', '确定')) +
'</button>' +
'</div>';
overlay.appendChild(dialog);
document.body.appendChild(overlay);
requestAnimationFrame(function () { overlay.classList.add('is-open'); });
function close(result) {
overlay.classList.remove('is-open');
setTimeout(function () { overlay.remove(); }, 200);
resolve(result);
}
dialog.querySelector('.aksrt-confirm__cancel').addEventListener('click', function () { close(false); });
dialog.querySelector('.aksrt-confirm__ok').addEventListener('click', function () { close(true); });
overlay.addEventListener('click', function (e) { if (e.target === overlay) close(false); });
dialog.querySelector('.aksrt-confirm__ok').focus();
});
};
// ══════════════════════════════════════════════════════════════
// §10.10 — scrollTopTo: smart scroll with header offset
// ══════════════════════════════════════════════════════════════
aksrt.scrollTopTo = function (target, extraOffset) {
var top = 0;
if (typeof target === 'number') {
top = target;
} else if (target && target.getBoundingClientRect) {
top = target.getBoundingClientRect().top + window.pageYOffset;
}
var navFixed = document.querySelector('body.nav-fixed .header, .navbar.navbar-top');
var headerOffset = navFixed ? (navFixed.offsetHeight + 20) : 0;
top = Math.max(0, top - headerOffset - (extraOffset || 0));
var current = window.pageYOffset;
if (Math.abs(top - current) < 80) return;
window.scrollTo({ top: top, behavior: 'smooth' });
};
// ══════════════════════════════════════════════════════════════
// §10.10 — loading: element-level loading overlay
// ══════════════════════════════════════════════════════════════
aksrt.loading = function (el, show) {
if (!el) return;
if (show === false) {
var mask = el.querySelector('.aksrt-loading-mask');
if (mask) {
mask.classList.add('aksrt-loading-mask--out');
setTimeout(function () { mask.remove(); }, 300);
}
return;
}
if (el.querySelector('.aksrt-loading-mask')) return;
var pos = getComputedStyle(el).position;
if (pos === 'static') el.style.position = 'relative';
var mask = document.createElement('div');
mask.className = 'aksrt-loading-mask';
mask.innerHTML = '<svg class="aksrt-loading-mask__spinner" width="36" height="36" viewBox="0 0 36 36">' +
'<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" stroke-width="3" stroke-dasharray="80 40" stroke-linecap="round">' +
'<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="0.8s" repeatCount="indefinite"/>' +
'</circle></svg>';
el.appendChild(mask);
};
// ══════════════════════════════════════════════════════════════
// §10.11 — route: tab route + popstate
// ══════════════════════════════════════════════════════════════
aksrt.route = function (url, title) {
if (!window.history || !history.pushState) return;
history.pushState({ aksrt: true }, title || '', url);
if (title) document.title = title;
};
aksrt.routeReplace = function (url, title) {
if (!window.history || !history.replaceState) return;
history.replaceState({ aksrt: true }, title || '', url);
if (title) document.title = title;
};
// Tab route: elements with [data-route] push URL on activation
window.addEventListener('popstate', function (e) {
var routeTabs = document.querySelectorAll('[data-route]');
if (!routeTabs.length) return;
var path = window.location.pathname + window.location.search;
routeTabs.forEach(function (tab) {
var tabRoute = tab.getAttribute('data-route');
var isMatch = path === tabRoute || path.indexOf(tabRoute) === 0;
tab.classList.toggle('is-active', isMatch);
var target = tab.getAttribute('data-route-target');
if (target) {
var panel = document.querySelector(target);
if (panel) panel.classList.toggle('is-active', isMatch);
}
});
});
// ══════════════════════════════════════════════════════════════
// §10.11 — Declarative interactions (event delegation)
// ══════════════════════════════════════════════════════════════
document.addEventListener('click', function (e) {
var target = e.target.closest('[data-close]');
if (target) {
e.preventDefault();
var sel2 = target.getAttribute('data-close');
var el2 = sel2 ? document.querySelector(sel2) : target.parentElement;
if (el2) {
el2.classList.remove('is-open', 'is-active', 'is-visible');
el2.hidden = true;
}
return;
}
target = e.target.closest('[data-onclick]');
if (target) {
e.preventDefault();
var sel3 = target.getAttribute('data-onclick');
var el3 = document.querySelector(sel3);
if (el3) el3.click();
return;
}
});
// [data-confirm] — intercept submission
document.addEventListener('click', function (e) {
var target = e.target.closest('[data-confirm]');
if (!target) return;
var msg = target.getAttribute('data-confirm');
if (!msg) return;
e.preventDefault();
e.stopPropagation();
aksrt.confirm(msg).then(function (ok) {
if (!ok) return;
target.removeAttribute('data-confirm');
target.click();
target.setAttribute('data-confirm', msg);
});
}, true);
// ══════════════════════════════════════════════════════════════
// §10.10a — 拦截 vendor 评论脚本 & 解绑 vendor ajax 委托
// ══════════════════════════════════════════════════════════════
// vendor 链路:loader.js(tbquire,改造版 RequireJS)→ main.min.js 检测到
// #commentform/#postcomments 即 tbquire(["comment"]) 懒加载 comment.min.js。
// AKSRT 自有评论脚本(comments/comment-tools/comment-fixed-input/
// comment-move-form/comment-ajax)已承担全部行为,vendor comment.min.js 的
// 7 条 body 委托会双路触发(提交 legacy action=submit_comment 无处理器,
// 即控制台「ajax请求错误」400 "0" 的来源;回复/排序/分页同理)。
//
// 双层防御:
// 1) tbfine 预注册空 'comment' 模块 → tbquire 不再下载 comment.min.js;
// 2) 若模块抢在注册前加载,退化为对 7 条委托做幂等 .off。
function stubVendorCommentModule() {
if (!(window.tbfine && window.tbquire && window.tbquire.defined)) return false;
try {
if (window.tbquire.defined('comment')) return true;
window.tbfine('comment', [], function () { return {}; });
return true;
} catch (e) {
return false;
}
}
// main.min.js 的 body 级 legacy 委托同理摘除(vendor 只保留 UI 行为)。
function unbindVendorAjaxDelegates() {
stubVendorCommentModule();
if (!window.jQuery) return;
try {
var $body = jQuery('body');
$body.off('click', '[data-action]');
$body.off('click', '[ajax-action]');
$body.off('click', '.wp-ajax-submit,[zibajax="submit"]');
$body.off('click', '.initiate-checkin');
$body.off('click', '.oauth-untying');
// comment.min.js 的 7 条委托(legacy/js/comment.js 原始选择器)
$body.off('click', '[fixed-input]');
$body.off('click', '.commentlist .pagenav a,.comment-orderby');
$body.off('click', '.comment-edit-link,.comment-reply-link');
$body.off('keydown', '#comment');
$body.off('click', '.comment-trash-link');
$body.off('click', '.comment-approve-link');
$body.off('click', '#commentform #submit');
} catch (e) { /* vendor 运行时未就绪时忽略 */ }
}
var unbindVendorAjaxLoop = function () {
unbindVendorAjaxDelegates();
if (++unbindVendorAjaxLoop.tries >= 50) return; // ~10s @200ms 覆盖异步加载窗口
setTimeout(unbindVendorAjaxLoop, 200);
};
unbindVendorAjaxLoop.tries = 0;
unbindVendorAjaxLoop();
// ══════════════════════════════════════════════════════════════
// §10.11 — [data-action] / [ajax-action] universal buttons
// ══════════════════════════════════════════════════════════════
// Only these data-action values are network AJAX. UI-only actions
// (toggle-theme, editor toolbar, download, etc.) must not hit admin-ajax.
var DATA_ACTION_AJAX = {
like: true,
favorite: true,
follow: true,
checkin: true
};
document.addEventListener('click', function (e) {
// [data-action] — like/follow/checkin with localStorage de-dup
var btn = e.target.closest('[data-action]');
if (btn) {
var dataAction = btn.getAttribute('data-action') || '';
if (!DATA_ACTION_AJAX[dataAction]) {
// Leave non-AJAX data-action handlers (theme toggle, toolbar, download) alone.
return;
}
e.preventDefault();
handleDataAction(btn);
return;
}
// [ajax-action] — general AJAX button
btn = e.target.closest('[ajax-action]');
if (btn) {
e.preventDefault();
handleAjaxAction(btn);
return;
}
});
function handleDataAction(btn) {
if (btn.classList.contains('is-loading')) return;
var action = btn.getAttribute('data-action');
if (!action || !DATA_ACTION_AJAX[action]) return;
var id = btn.getAttribute('data-id') || '';
var storageKey = 'aksrt_action_' + action + '_' + id;
if (action === 'like' || action === 'favorite') {
try {
if (localStorage.getItem(storageKey) && !btn.classList.contains('is-active')) {
notyf(__('already_done', '已操作'), 'warning');
return;
}
} catch (ex) { /* storage unavailable */ }
}
btn.classList.add('is-loading');
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
if (!(window.aksrtData && aksrtData.nonce)) {
btn.classList.remove('is-loading');
notyf(__('action_failed', '操作失败'), 'danger');
return;
}
var params = new URLSearchParams();
params.set('action', 'aksrt_' + action);
params.set('nonce', aksrtData.nonce);
params.set('id', id);
var extraData = btn.getAttribute('data-action-data');
if (extraData) {
try {
var ed = JSON.parse(extraData);
Object.keys(ed).forEach(function (k) { params.set(k, ed[k]); });
} catch (ex) { /* ignore */ }
}
fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
credentials: 'same-origin',
body: params.toString()
})
.then(function (r) { return r.json(); })
.then(function (json) {
btn.classList.remove('is-loading');
if (json && json.success) {
var isActive = !!json.data && !!json.data.active;
btn.classList.toggle('is-active', isActive);
var countEl = btn.querySelector('[data-count]') || btn.querySelector('.action-count');
if (countEl && json.data && json.data.count !== undefined) {
countEl.textContent = String(json.data.count);
}
try {
if (isActive) localStorage.setItem(storageKey, '1');
else localStorage.removeItem(storageKey);
} catch (ex) { /* */ }
if (json.data && json.data.message) {
notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
}
} else {
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
function handleAjaxAction(btn) {
if (btn.classList.contains('is-loading')) return;
btn.classList.add('is-loading');
var action = btn.getAttribute('ajax-action');
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
var params = new URLSearchParams();
params.set('action', action);
if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce);
var form = btn.closest('form');
if (form) {
new FormData(form).forEach(function (v, k) { params.set(k, v); });
}
fetch(ajaxUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
credentials: 'same-origin',
body: params.toString()
})
.then(function (r) { return r.json(); })
.then(function (json) {
btn.classList.remove('is-loading');
if (json && json.success) {
if (json.data && json.data.show_modal) {
window.AksrtModalRuntime.open({
html: json.data.show_modal,
bottom: true,
mobileBottom: true,
kind: 'generic',
});
} else if (json.data && json.data.message) {
notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
}
if (json.data && json.data.reload) {
window.location.reload();
}
} else {
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
// ══════════════════════════════════════════════════════════════
// §10.11 — Ripple (broad coverage)
// ══════════════════════════════════════════════════════════════
var RIPPLE_SELECTORS = '.toggle-radius,.but-ripple,.but,.btn,.btn-primary,.btn-ghost,.btn-outline,.item-thumbnail,.menu-item>a,.aksrt-ripple,.yiyan-box,.aksrt-card';
document.addEventListener('click', function (e) {
var target = e.target.closest(RIPPLE_SELECTORS);
if (!target) return;
// Markup opt-out (header publish uses class="nowave") + nav CTA chips
if (target.classList.contains('nowave') || target.closest('.nowave')) return;
if (target.classList.contains('navbar-cta') || target.closest('.navbar-cta')) return;
if (target.closest('.navbar-top .navbar-but, .navbar-top .navbar-vip, .navbar-but-cluster')) return;
if (target.querySelector('.aksrt-wave')) return;
var rect = target.getBoundingClientRect();
var size = Math.max(rect.width, rect.height) * 2;
var x = e.clientX - rect.left - size / 2;
var y = e.clientY - rect.top - size / 2;
var wave = document.createElement('span');
wave.className = 'aksrt-wave';
// Must be absolute — without it the huge span expands the button box ("变长").
wave.style.cssText = 'position:absolute;pointer-events:none;border-radius:50%;background:rgba(255,255,255,0.35);width:'
+ size + 'px;height:' + size + 'px;left:' + x + 'px;top:' + y + 'px;transform:scale(0);animation:aksrtWaveRipple .6s ease-out forwards;';
var cs = getComputedStyle(target);
if (cs.position === 'static') target.style.position = 'relative';
if (cs.overflow === 'visible') target.style.overflow = 'hidden';
target.appendChild(wave);
setTimeout(function () {
if (wave.parentNode) wave.parentNode.removeChild(wave);
}, 600);
});
// ══════════════════════════════════════════════════════════════
// §10.11 — auto_fun: extended rehydration coverage
// ══════════════════════════════════════════════════════════════
aksrt.autoFun = function (root) {
root = root || document;
if (window.AksrtFrontend) {
AksrtFrontend.rehydrate(root, 'ajax');
}
// Tooltips
root.querySelectorAll('[data-tooltip]').forEach(function (el) {
if (el.dataset.tooltipBound) return;
el.dataset.tooltipBound = '1';
el.addEventListener('mouseenter', function () {
var tip = document.createElement('div');
tip.className = 'aksrt-tooltip';
tip.textContent = el.getAttribute('data-tooltip');
document.body.appendChild(tip);
var rect = el.getBoundingClientRect();
tip.style.left = (rect.left + rect.width / 2 - tip.offsetWidth / 2) + 'px';
tip.style.top = (rect.top - tip.offsetHeight - 8 + window.pageYOffset) + 'px';
el._aksrtTooltip = tip;
});
el.addEventListener('mouseleave', function () {
if (el._aksrtTooltip) { el._aksrtTooltip.remove(); el._aksrtTooltip = null; }
});
});
// Countdowns
root.querySelectorAll('[data-countdown]').forEach(function (el) {
if (el.dataset.countdownBound) return;
el.dataset.countdownBound = '1';
var end = parseInt(el.getAttribute('data-countdown'), 10);
if (!end) return;
var iv = setInterval(function () {
var left = Math.max(0, end - Math.floor(Date.now() / 1000));
if (left <= 0) { clearInterval(iv); el.textContent = '00:00'; return; }
var m = Math.floor(left / 60);
var s = left % 60;
el.textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
}, 1000);
});
};
// ══════════════════════════════════════════════════════════════
// Helpers
// ══════════════════════════════════════════════════════════════
function escapeHtml(str) {
if (!str) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
// ── DOMContentLoaded ──
function initRuntime() {
aksrt.autoFun(document);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initRuntime);
} else {
initRuntime();
}
})();
;
/* source: assets/js/grade.min.js */
(function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Grade=e()}})(function(){return function e(t,n,r){function i(o,s){if(!n[o]){if(!t[o]){var u="function"==typeof require&&require;if(!s&&u)return u(o,!0);if(a)return a(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var g=n[o]={exports:{}};t[o][0].call(g.exports,function(e){var n=t[o][1][e];return i(n||e)},g,g.exports,e,t,n,r)}return n[o].exports}for(var a="function"==typeof require&&require,o=0;o<r.length;o++)i(r[o]);return i}({1:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=["webkit"],o=function(){function e(t,n,i){r(this,e),this.callback=i||null,this.container=t;var a=this.container.querySelector(n||"img");if(this.gradientData=[],a&&this.container){this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.imageData=[];var o=this;o.image=new Image,o.image.crossOrigin="Anonymous",o.image.src=a.getAttribute("data-src")||a.getAttribute("src"),o.image.onload=function(){o.readImage()}}}return i(e,[{key:"readImage",value:function(){this.imageDimensions={width:100,height:100},this.render()}},{key:"getImageData",value:function(){var e=this.ctx.getImageData(0,0,this.imageDimensions.width,this.imageDimensions.height).data;this.imageData=Array.from(e)}},{key:"getChunkedImageData",value:function(){var e=4,t=this.imageData.reduce(function(t,n,r){var i=Math.floor(r/e);return t[i]||(t[i]=[]),t[i].push(n),t},[]),n=t.filter(function(e){var t=e[0],n=e[1],r=e[2],i=Math.max(Math.abs(t-n),Math.abs(t-r),Math.abs(n-r));return i>20});return(!n||n.length<2)&&(n=t.filter(function(e){return e.slice(0,2).every(function(e){return e<245})&&e.slice(0,2).every(function(e){return e>10})})),n}},{key:"getRGBAGradientValues",value:function(e){return e.map(function(e,t){return"rgb("+e.rgba.slice(0,3).join(",")+") "+(0==t?"0%":"75%")}).join(",")}},{key:"getCSSGradientProperty",value:function(e){var t=this.getRGBAGradientValues(e);return a.map(function(e){return"background-image: -"+e+"-linear-gradient(\n 135deg,\n "+t+"\n )"}).concat(["background-image: linear-gradient(\n 135deg,\n "+t+"\n )"]).join(";")}},{key:"getMiddleRGB",value:function(e,t){var n=0,r=(n+1)/2,i=1-r,a=[parseInt(e[0]*r+t[0]*i),parseInt(e[1]*r+t[1]*i),parseInt(e[2]*r+t[2]*i)];return a}},{key:"getSortedValues",value:function(e){var t=Object.keys(e).map(function(t){var n=t,r=t.split("|"),i=(299*r[0]+587*r[1]+114*r[2])/1e3;return{rgba:n.split("|"),occurs:e[t],brightness:i}}).sort(function(e,t){return e.occurs-t.occurs}).reverse().slice(0,10);return t.sort(function(e,t){return e.brightness-t.brightness}).reverse()}},{key:"getTextProperty",value:function(e){var t=this.getMiddleRGB(e[0].rgba.slice(0,3),e[1].rgba.slice(0,3)),n=Math.round((299*parseInt(t[0])+587*parseInt(t[1])+114*parseInt(t[2]))/1e3);return n>125?"color: #000":"color: #fff"}},{key:"getTopValues",value:function(e){var t=this.getSortedValues(e);return[t[0],t[t.length-1]]}},{key:"getUniqValues",value:function(e){return e.reduce(function(e,t){var n=t.join("|");return e[n]?(e[n]=++e[n],e):(e[n]=1,e)},{})}},{key:"renderGradient",value:function(){var e=window.localStorage,t="grade-"+this.image.getAttribute("src"),n=null;if(e&&e.getItem(t)&&(n=JSON.parse(e.getItem(t))),!n||!n[0]){var r=this.getChunkedImageData();n=this.getTopValues(this.getUniqValues(r)),e&&e.setItem(t,JSON.stringify(n))}if(this.callback)return this.gradientData=n,void this.callback([{element:this.container,gradientData:n}]);var i=this.getCSSGradientProperty(n),a=this.getTextProperty(n),o=(this.container.getAttribute("style")||"")+"; "+i+"; "+a;this.container.setAttribute("style",o)}},{key:"render",value:function(){this.canvas.width=this.imageDimensions.width,this.canvas.height=this.imageDimensions.height,this.ctx.drawImage(this.image,0,0,this.imageDimensions.width,this.imageDimensions.height),this.getImageData(),this.renderGradient()}}]),e}();t.exports=function(e,t,n){var r=function(e,t,n){var r=new o(e,t,n),i=r.gradientData;return i.length?{element:e,gradientData:i}:null},i=(NodeList.prototype.isPrototypeOf(e)?Array.from(e).map(function(e){return r(e,t,n)}):[r(e,t,n)]).filter(Boolean);if(i.length)return n(i)}},{}]},{},[1])(1)});
;
/* source: assets/js/core/modal-telemetry.js */
/**
* Frontend modal performance marks.
*
* Records only bounded domain/event names and timing data. Never include
* payment credentials, order contents, identity evidence, or user payloads.
*
* @package AKSRT
*/
(function (window) {
'use strict';
var allowedKinds = {
identity: true,
badges: true,
orders: true,
vip: true,
};
var allowedEvents = {
click: true,
shell: true,
'first-content': true,
ready: true,
'action-start': true,
'action-end': true,
unauthorized: true,
error: true,
};
var sessions = Object.create(null);
var sequence = 0;
var maxSessions = 100;
function normalize(value, fallback) {
var normalized = String(value || fallback || '').toLowerCase().replace(/[^a-z0-9-]/g, '');
return normalized || fallback;
}
function now() {
return window.performance && typeof window.performance.now === 'function'
? window.performance.now()
: 0;
}
function markName(session, event) {
return 'aksrt-modal-' + session.kind + '-' + session.id + '-' + event;
}
function emit(session, event) {
if (!window.performance || typeof window.performance.mark !== 'function') return;
var name = markName(session, event);
window.performance.mark(name);
session.marks[event] = name;
if (typeof window.performance.measure === 'function' && session.marks.click && event !== 'click') {
try {
window.performance.measure(
'aksrt-modal-' + session.kind + '-' + session.id + '-click-to-' + event,
session.marks.click,
name
);
} catch (error) {
// A missing mark must never affect the modal interaction.
}
}
}
function start(kind) {
var safeKind = normalize(kind, 'unknown');
if (!allowedKinds[safeKind]) safeKind = 'unknown';
sequence += 1;
var id = String(sequence);
var session = {
id: id,
kind: safeKind,
startedAt: now(),
marks: Object.create(null),
};
sessions[id] = session;
emit(session, 'click');
var ids = Object.keys(sessions);
while (ids.length > maxSessions) {
delete sessions[ids.shift()];
}
return id;
}
function mark(id, event) {
var session = sessions[String(id || '')];
var safeEvent = normalize(event, 'error');
if (!session || !allowedEvents[safeEvent]) return;
emit(session, safeEvent);
}
function finish(id) {
mark(id, 'ready');
}
function fail(id) {
mark(id, 'error');
}
window.AksrtModalTelemetry = {
start: start,
mark: mark,
finish: finish,
error: fail,
};
})(window);
;
/* source: assets/js/core/request.js */
/**
* AKSRT frontend request adapter.
*
* Owns the injected REST root, nonce headers, response envelopes, and
* abort-aware error normalization used by modal loaders.
*
* @package AKSRT
*/
(function (window) {
"use strict";
function data() {
return window.aksrtData || window.aksrt || {};
}
function createIdempotencyKey() {
if (window.crypto && typeof window.crypto.randomUUID === "function") {
return window.crypto.randomUUID();
}
if (!window.crypto || typeof window.crypto.getRandomValues !== "function") {
throw new Error("当前浏览器无法生成安全的操作标识");
}
var bytes = new Uint8Array(16);
window.crypto.getRandomValues(bytes);
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
var hex = Array.prototype.map.call(bytes, function (value) {
return value.toString(16).padStart(2, "0");
}).join("");
return hex.slice(0, 8) + "-" + hex.slice(8, 12) + "-" + hex.slice(12, 16) + "-" + hex.slice(16, 20) + "-" + hex.slice(20);
}
function restBase() {
var config = data();
var raw = config.restUrl || config.apiUrl || config.rest_url || "";
var siteUrl = config.siteUrl || config.homeUrl || (window.location.origin + "/");
if (window.location.protocol === "https:" && siteUrl.substring(0, 5) === "http:") {
siteUrl = "https:" + siteUrl.substring(5);
}
if (raw) return String(raw).replace(/\/$/, "");
return String(siteUrl).replace(/\/$/, "") + "/index.php?rest_route=/aksrt/v1";
}
function appendQuery(url, query) {
if (!query) return url;
var parts = [];
if (typeof query === "string") {
parts = query.replace(/^\?/, "").split("&").filter(Boolean);
} else if (typeof query === "object") {
Object.keys(query).forEach(function (key) {
if (query[key] === undefined || query[key] === null) return;
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(String(query[key])));
});
}
if (!parts.length) return url;
return url + (url.indexOf("?") >= 0 ? "&" : "?") + parts.join("&");
}
function url(path, query) {
path = String(path || "");
var inlineQuery = "";
var queryIndex = path.indexOf("?");
if (queryIndex >= 0) {
inlineQuery = path.slice(queryIndex + 1);
path = path.slice(0, queryIndex);
}
if (/^https?:\/\//i.test(path)) {
return appendQuery(path, query || inlineQuery);
}
if (path.charAt(0) !== "/") path = "/" + path;
var base = restBase();
var result = base.indexOf("rest_route=") >= 0 ? base + path : base + path;
return appendQuery(result, query || inlineQuery);
}
function headers(options) {
var config = data();
var source = (options && options.headers) || {};
var result = {};
var nonce = config.restNonce || config.nonce || "";
Object.keys(source).forEach(function (key) {
result[key] = source[key];
});
if (!result.Accept) result.Accept = "application/json, text/html;q=0.9, */*;q=0.8";
if (nonce && !result["X-WP-Nonce"]) result["X-WP-Nonce"] = nonce;
if (options && options.body !== undefined && options.body !== null &&
!(window.FormData && options.body instanceof window.FormData) &&
!result["Content-Type"]) {
result["Content-Type"] = "application/json";
}
return result;
}
function responseError(result) {
var json = result.json;
var message = json && (json.message || (json.data && json.data.message));
var error = new Error(message || (result.status ? "请求失败(HTTP " + result.status + ")" : "请求失败"));
error.status = result.status;
error.code = result.status === 401 ? "unauthorized" : "http_error";
error.response = result;
error.json = json;
return error;
}
function parseResponse(response) {
return response.text().then(function (text) {
var type = (response.headers.get("content-type") || "").toLowerCase();
var json = null;
var trimmed = String(text || "").trim();
if (type.indexOf("application/json") >= 0 || trimmed.charAt(0) === "{" || trimmed.charAt(0) === "[") {
try {
json = trimmed ? JSON.parse(trimmed) : {};
} catch (error) {
json = null;
}
}
return {
ok: response.ok,
status: response.status,
headers: response.headers,
text: text,
json: json,
data: json && json.data !== undefined ? json.data : json,
};
});
}
function request(path, options) {
options = Object.assign({}, options || {});
var requestUrl = url(path, options.query);
delete options.query;
var fetchOptions = Object.assign({}, options, {
credentials: options.credentials || "same-origin",
headers: headers(options),
});
return window.fetch(requestUrl, fetchOptions).then(function (response) {
return parseResponse(response);
}).then(function (result) {
if (!result.ok || (result.json && result.json.success === false)) {
throw responseError(result);
}
return result;
});
}
window.AksrtRequest = {
url: url,
request: request,
createIdempotencyKey: createIdempotencyKey,
get: function (path, options) {
return request(path, Object.assign({}, options || {}, { method: "GET" }));
},
post: function (path, body, options) {
return request(path, Object.assign({}, options || {}, {
method: "POST",
body: body,
}));
},
};
})(window);
;
/* source: assets/js/core/modal-cache.js */
/**
* User-scoped in-memory modal cache with short TTLs and request deduplication.
* Private modal payloads are never persisted to browser storage.
*
* @package AKSRT
*/
(function (window, document) {
"use strict";
var DEFAULT_TTL = 15000;
var entries = Object.create(null);
var flights = Object.create(null);
var lastScope = "";
function scope() {
var config = window.aksrtData || window.aksrt || {};
var userId = parseInt(config.userId || 0, 10) || 0;
var current = (config.isLoggedIn && userId > 0 ? "user:" + userId : "guest") + ":v1";
if (lastScope && lastScope !== current) clear();
lastScope = current;
return current;
}
function token(key) {
return scope() + "::" + String(key || "");
}
function read(key) {
var id = token(key);
var entry = entries[id];
if (!entry) return { hit: false, value: undefined };
if (entry.expiresAt <= Date.now()) {
delete entries[id];
return { hit: false, value: undefined };
}
return { hit: true, value: entry.value };
}
function tagsOf(value) {
return Array.isArray(value) ? value.slice() : (value ? [value] : []);
}
function set(key, value, options) {
options = options || {};
var ttl = Number(options.ttl || DEFAULT_TTL);
entries[token(key)] = {
value: value,
expiresAt: Date.now() + Math.max(0, ttl),
tags: tagsOf(options.tags),
};
return value;
}
function matchesTags(entry, wanted) {
if (!wanted.length) return true;
return wanted.some(function (tag) { return entry.tags.indexOf(tag) >= 0; });
}
function invalidate(tags) {
var wanted = Array.isArray(tags) ? tags : (tags ? [tags] : []);
var removed = 0;
Object.keys(entries).forEach(function (id) {
if (!wanted.length || matchesTags(entries[id], wanted)) {
delete entries[id];
removed += 1;
}
});
Object.keys(flights).forEach(function (id) {
var flight = flights[id];
if (!wanted.length || matchesTags(flight, wanted)) {
flight.cancelled = true;
delete flights[id];
if (typeof flight.cancel === "function") flight.cancel();
}
});
return removed;
}
function clear() {
entries = Object.create(null);
Object.keys(flights).forEach(function (id) {
var flight = flights[id];
flight.cancelled = true;
if (typeof flight.cancel === "function") flight.cancel();
});
flights = Object.create(null);
}
function acquire(key, loader, options) {
options = options || {};
var id = token(key);
var cached = options.skipCache ? { hit: false } : read(key);
if (cached.hit) {
return {
cached: true,
promise: Promise.resolve(cached.value),
release: function () {},
};
}
var flight = flights[id];
if (!flight) {
flight = {
refs: 0,
settled: false,
cancelled: false,
tags: tagsOf(options.tags),
cancel: options.cancel,
};
flight.promise = Promise.resolve().then(loader).then(function (value) {
flight.settled = true;
if (!flight.cancelled && options.store !== false) set(key, value, options);
if (flights[id] === flight) delete flights[id];
return value;
}, function (error) {
flight.settled = true;
if (flights[id] === flight) delete flights[id];
throw error;
});
flights[id] = flight;
}
flight.refs += 1;
var released = false;
return {
cached: false,
promise: flight.promise,
release: function () {
if (released) return;
released = true;
flight.refs = Math.max(0, flight.refs - 1);
if (flight.refs === 0 && !flight.settled) {
flight.cancelled = true;
if (flights[id] === flight) delete flights[id];
if (typeof flight.cancel === "function") flight.cancel();
}
},
};
}
["aksrt:auth:logout", "aksrt:account-switch", "aksrt:logout", "aksrt:user:logout", "aksrt:auth:changed", "wp_logout"].forEach(function (eventName) {
document.addEventListener(eventName, clear);
});
window.AksrtModalCache = {
get: function (key) { return read(key); },
set: set,
acquire: acquire,
invalidate: invalidate,
clear: clear,
scope: scope,
};
})(window, document);
;
/* source: assets/js/core/modal-state.js */
/**
* Shared modal state vocabulary.
*
* @package AKSRT
*/
(function (window) {
"use strict";
var statuses = {
idle: "idle",
loading: "loading",
ready: "ready",
empty: "empty",
submitting: "submitting",
success: "success",
error: "error",
unauthorized: "unauthorized",
stale: "stale",
};
function create(kind) {
return {
kind: String(kind || "generic"),
status: statuses.idle,
updatedAt: 0,
error: null,
};
}
window.AksrtModalState = {
statuses: statuses,
create: create,
};
})(window);
;
/* source: assets/js/core/modal-runtime.js */
/**
* Canonical frontend modal runtime.
*
* Owns the modal shell while adding cancellable loaders,
* short in-memory caching, request deduplication, retryable errors, focus
* restoration, and modal-scoped rehydration.
*
* @package AKSRT
*/
(function (window, document) {
"use strict";
var ROOT_ID = "aksrt-modal-runtime-root";
var active = null;
var sequence = 0;
var loaders = Object.create(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-modal-runtime-host";
document.body.appendChild(root);
return root;
}
function escapeHtml(value) {
return String(value == null ? "" : value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/\"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function lockScroll(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) ||
'<div class="aksrt-modal-runtime__skeleton" aria-live="polite" aria-busy="true">' +
'<span class="aksrt-modal-runtime__skeleton-line is-wide"></span>' +
'<span class="aksrt-modal-runtime__skeleton-line"></span>' +
'<span class="aksrt-modal-runtime__skeleton-line is-short"></span>' +
'<span class="aksrt-modal-runtime__skeleton-block"></span>' +
'</div>';
}
function telemetryStart(kind) {
if (kind === false) return "";
return window.AksrtModalTelemetry.start(kind || "generic");
}
function telemetryMark(session, event) {
if (!session || !session.telemetryId) return;
window.AksrtModalTelemetry.mark(session.telemetryId, event);
}
function setState(session, status, error) {
session.state.status = status;
session.state.error = error || null;
session.state.updatedAt = Date.now();
if (session.shell) session.shell.setAttribute("data-aksrt-modal-state", status);
if (typeof session.options.onState === "function") {
session.options.onState(status, session);
}
}
function focusable(shell) {
return shell.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex='-1'])");
}
function closeSelector() {
return "[data-aksrt-modal-close], .aksrt-modal-runtime__close";
}
function bindChrome(session) {
var shell = session.shell;
shell.addEventListener("click", function (event) {
var closeButton = event.target.closest(closeSelector());
if (closeButton) {
event.preventDefault();
close("control");
return;
}
if (event.target === shell || event.target.classList.contains("aksrt-modal-runtime__overlay")) {
close("overlay");
return;
}
var retry = event.target.closest("[data-aksrt-modal-retry]");
if (retry) {
event.preventDefault();
refresh();
}
});
shell.addEventListener("keydown", function (event) {
if (event.key === "Escape") {
event.preventDefault();
close("escape");
return;
}
if (event.key !== "Tab") return;
var nodes = focusable(shell);
if (!nodes.length) {
event.preventDefault();
shell.focus();
return;
}
var first = nodes[0];
var last = nodes[nodes.length - 1];
if (event.shiftKey && document.activeElement === first) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
}
function createSession(options) {
options = options || {};
var root = ensureRoot();
var shell = document.createElement("div");
var bottom = options.bottom === true || (options.mobileBottom !== false && isMobile());
var heightStyle = options.height ? "min-height:" + parseInt(options.height, 10) + "px;" : "";
var initialHtml = options.html !== undefined ? options.html : skeleton(options);
var titleId = "aksrt-modal-title-" + String(sequence + 1);
var titleHtml = options.title
? '<header class="aksrt-modal-runtime__header"><h3 class="aksrt-modal-runtime__title" id="' + titleId + '">' + escapeHtml(options.title) + '</h3><button type="button" class="aksrt-modal-runtime__close" data-aksrt-modal-close aria-label="关闭">&times;</button></header>'
: "";
shell.className =
"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 =
'<div class="aksrt-modal-runtime__overlay" data-aksrt-modal-close></div>' +
'<div class="aksrt-modal-runtime__dialog" style="' + heightStyle + '">' +
titleHtml +
'<div class="aksrt-modal-runtime__content">' + initialHtml + "</div></div>";
var session = {
id: String(++sequence),
shell: shell,
content: shell.querySelector(".aksrt-modal-runtime__content"),
options: options,
state: window.AksrtModalState.create(options.kind || "generic"),
focusReturn: document.activeElement,
controller: null,
flight: null,
closed: false,
telemetryId: telemetryStart(options.telemetry === false ? false : options.kind),
};
root.appendChild(shell);
bindChrome(session);
active = session;
lockScroll(true);
void shell.offsetWidth;
shell.classList.add("is-open");
setState(session, options.html !== undefined ? "ready" : "loading");
telemetryMark(session, "shell");
var nodes = focusable(shell);
try {
(nodes.length ? nodes[0] : shell).focus();
} catch (error) {}
document.dispatchEvent(new CustomEvent("aksrt:modal:open", {
detail: { shell: shell, options: options, session: session },
}));
if (options.html !== undefined) {
rehydrate(session);
telemetryMark(session, "first-content");
telemetryMark(session, "ready");
if (typeof options.onContent === "function") options.onContent(options.html, session);
}
return session;
}
function rehydrate(session) {
if (!session || !session.content) return;
window.AksrtFrontend.rehydrate(session.content, "modal");
}
function abortSessionRequest(session) {
if (!session) return;
if (session.flight && typeof session.flight.release === "function") session.flight.release();
session.flight = null;
if (session.controller) {
try { session.controller.abort(); } catch (error) {}
}
session.controller = null;
}
function close(reason, preserveRequest) {
if (!active) return;
var session = active;
active = null;
session.closed = true;
if (!preserveRequest) abortSessionRequest(session);
session.shell.classList.remove("is-open");
if (!preserveRequest) lockScroll(false);
document.dispatchEvent(new CustomEvent("aksrt:modal:close", {
detail: { shell: session.shell, reason: reason || "close", session: session },
}));
var node = session.shell;
window.setTimeout(function () {
if (node && node.parentNode) node.parentNode.removeChild(node);
var target = session.focusReturn;
if (!preserveRequest && !active && target && target !== node && document.contains(target) && typeof target.focus === "function") {
try { target.focus(); } catch (error) {}
}
}, 220);
}
function responseData(result) {
if (!result) return null;
if (result.data !== undefined && result.data !== null) return result.data;
if (result.json && result.json.data !== undefined) return result.json.data;
return result.json;
}
function responseHtml(result) {
var data = responseData(result);
if (data && typeof data.html === "string") return data.html;
if (result && result.json && typeof result.json.html === "string") return result.json.html;
return result && typeof result.text === "string" ? result.text : "";
}
function errorMessage(error) {
if (error && error.code === "unauthorized") return "请先登录";
return error && error.message ? String(error.message) : "加载失败,请重试";
}
function renderError(session, error) {
if (!session || session.closed || active !== session) return;
var unauthorized = error && error.code === "unauthorized";
setState(session, unauthorized ? "unauthorized" : "error", error);
telemetryMark(session, unauthorized ? "unauthorized" : "error");
session.content.innerHTML =
'<div class="aksrt-modal-runtime__error touch border-title"><b>提示</b></div>' +
'<div class="aksrt-modal-runtime__error-body">' + escapeHtml(errorMessage(error)) +
'</div><div class="but-average modal-buts">' +
'<button type="button" class="but" data-aksrt-modal-retry>重试</button>' +
'<button type="button" class="but" data-aksrt-modal-close>关闭</button></div>';
rehydrate(session);
if (typeof session.options.onError === "function") session.options.onError(error, session);
}
function renderResult(session, result) {
if (!session || session.closed || active !== session) return;
if (session.options.responseType === "json" || typeof session.options.renderData === "function") {
var data = responseData(result);
if (data === null || data === undefined) {
setState(session, "empty");
telemetryMark(session, "first-content");
telemetryMark(session, "ready");
session.content.innerHTML = '<div class="aksrt-modal-runtime__error-body">暂无内容</div>';
return;
}
if (typeof session.options.renderData === "function") {
session.options.renderData(data, session, result);
}
setState(session, "ready");
telemetryMark(session, "first-content");
rehydrate(session);
telemetryMark(session, "ready");
if (typeof session.options.onData === "function") session.options.onData(data, session, result);
return;
}
var html = responseHtml(result);
if (!html) {
setState(session, "empty");
telemetryMark(session, "first-content");
telemetryMark(session, "ready");
session.content.innerHTML = '<div class="aksrt-modal-runtime__error-body">暂无内容</div>';
return;
}
session.content.innerHTML = html;
setState(session, "ready");
telemetryMark(session, "first-content");
rehydrate(session);
telemetryMark(session, "ready");
if (typeof session.options.onContent === "function") session.options.onContent(html, session);
}
function load(session, loadOptions) {
loadOptions = loadOptions || {};
if (!session || session.closed || !session.options.url) return Promise.resolve(session && session.shell);
abortSessionRequest(session);
setState(session, "loading");
session.content.innerHTML = skeleton(session.options);
session.controller = window.AbortController ? new window.AbortController() : null;
var requestOptions = {
method: session.options.method || "GET",
headers: session.options.headers || {},
body: session.options.body,
query: session.options.query,
signal: session.controller ? session.controller.signal : undefined,
};
if (requestOptions.body === undefined) delete requestOptions.body;
if (!requestOptions.signal) delete requestOptions.signal;
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.url(session.options.url, session.options.query);
var cache = window.AksrtModalCache;
var acquireOptions = {
ttl: Number(session.options.cacheTtl || 15000),
tags: session.options.cacheTags || session.options.tags || [],
skipCache: !!loadOptions.force || !cacheable,
store: cacheable,
cancel: function () {
if (session.controller) {
try { session.controller.abort(); } catch (error) {}
}
},
};
var flight = cacheable
? cache.acquire(cacheKey, request, acquireOptions)
: { promise: request(), release: function () {} };
session.flight = flight;
return flight.promise.then(function (result) {
if (!session.closed && active === session) renderResult(session, result);
return session.shell;
}).catch(function (error) {
var aborted = error && (error.name === "AbortError" || error.code === "ABORT_ERR");
if (!aborted && !session.closed && active === session) renderError(session, error);
return session.shell;
}).then(function (shell) {
// A refresh/replacement may have installed a newer flight on this session.
// Only the owner of this promise may release and clear the slot.
if (session.flight === flight) {
if (flight && typeof flight.release === "function") flight.release();
session.flight = null;
}
return shell;
});
}
function open(options) {
options = options || {};
close("replace");
var session = createSession(options);
return session.shell;
}
function sessionKey(options) {
if (!options || !options.url) return "";
var method = String(options.method || "GET").toUpperCase();
var requestUrl = window.AksrtRequest.url(options.url, options.query);
return method + ":" + requestUrl;
}
function openRemote(options) {
options = Object.assign({}, options || {});
if (!options.url) return Promise.reject(new Error("missing url"));
var sameRequest = active && active.flight && sessionKey(active.options) === sessionKey(options);
close("replace", !!sameRequest);
var session = createSession(options);
return load(session).then(function () { return session.shell; });
}
function refresh(options) {
if (!active || !active.options.url) return Promise.resolve(null);
if (options && typeof options === "object") active.options = Object.assign({}, active.options, options);
return load(active, { force: true });
}
function registerLoader(kind, loader) {
if (!kind || typeof loader !== "function") return;
loaders[String(kind)] = loader;
}
function openLoader(kind, context) {
var loader = loaders[String(kind)];
if (!loader) return Promise.reject(new Error("missing modal loader: " + kind));
return openRemote(loader(context || {}));
}
function invalidate(tags) {
return window.AksrtModalCache.invalidate(tags);
}
window.AksrtModalRuntime = {
open: open,
openRemote: openRemote,
close: close,
refresh: refresh,
registerLoader: registerLoader,
openLoader: openLoader,
invalidate: invalidate,
getActive: function () { return active ? active.shell : null; },
getState: function () { return active ? active.state : null; },
};
document.dispatchEvent(new CustomEvent("aksrt:modal-runtime:ready"));
})(window, document);
;
/* source: assets/js/core/modal-triggers.js */
/**
* 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 cache = element.getAttribute("data-aksrt-modal-cache") !== "false";
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") || "",
cache: cache,
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);
;
/* source: assets/js/core/article.js */
/**
* AKSRT Article Tools — password toggles, search clear, article actions, interaction bar
*/
(function () {
"use strict";
function initPasswordToggles(root) {
(root || document).querySelectorAll(".toggle-pwd").forEach(function (btn) {
if (btn.dataset.pwdToggleBound) return;
btn.dataset.pwdToggleBound = "1";
btn.addEventListener("click", function () {
var id = btn.getAttribute("data-target");
var input = id ? document.getElementById(id) : null;
if (!input) return;
var show = input.getAttribute("type") === "password";
input.setAttribute("type", show ? "text" : "password");
btn.setAttribute("aria-label", show ? "隐藏密码" : "显示密码");
});
});
}
function initSearchClear() {
var btn = document.getElementById("search-clear-btn");
var input = document.getElementById("search-page-input");
if (btn && input) {
var syncClearButton = function () {
var hasValue = input.value.trim().length > 0;
btn.classList.toggle("is-visible", hasValue);
btn.hidden = !hasValue;
};
btn.addEventListener("click", function () {
input.value = "";
input.focus();
syncClearButton();
});
input.addEventListener("input", syncClearButton);
syncClearButton();
}
var categorySelect = document.getElementById("search-category-filter");
if (categorySelect) {
categorySelect.addEventListener("change", function () {
var url = new URL(window.location.href);
if (this.value) {
url.searchParams.set("aksrt_category", this.value);
} else {
url.searchParams.delete("aksrt_category");
}
if (url.searchParams.get("aksrt_search_type") && url.searchParams.get("aksrt_search_type") !== "post") {
url.searchParams.delete("aksrt_category");
}
url.searchParams.delete("paged");
window.location.href = url.toString();
});
}
}
function initArticleTools() {
var buttons = document.querySelectorAll("[data-article-tool]");
if (!buttons.length) return;
buttons.forEach(function (button) {
button.addEventListener("click", function () {
var tool = button.getAttribute("data-article-tool");
if (tool === "print") {
window.print();
return;
}
if (tool === "pdf") {
window.print();
notyf("请在打印对话框中选择“另存为 PDF”", "info");
}
});
});
}
function getBaseStatCount(statName) {
var statEl = document.querySelector('[data-stat-count="' + statName + '"][data-base-count]');
return statEl ? (parseInt(statEl.dataset.baseCount, 10) || 0) : 0;
}
function updateStatCounts(statName, count) {
var safeCount = Math.max(0, count);
document.querySelectorAll('[data-stat-count="' + statName + '"]').forEach(function (statEl) {
statEl.textContent = String(safeCount);
});
}
function setBaseStatCount(statName, count) {
var safeCount = Math.max(0, count);
document.querySelectorAll('[data-stat-count="' + statName + '"][data-base-count]').forEach(function (statEl) {
statEl.dataset.baseCount = String(safeCount);
});
updateStatCounts(statName, safeCount);
}
function requestInteraction(actionName, postId, active) {
if (!window.aksrtData || !aksrtData.ajaxUrl || !aksrtData.nonce) {
return Promise.reject(new Error("互动服务未配置"));
}
var body = new URLSearchParams();
body.set("action", actionName);
body.set("nonce", aksrtData.nonce);
body.set("postId", String(postId));
body.set("active", active ? "1" : "0");
return fetch(aksrtData.ajaxUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
credentials: "same-origin",
body: body.toString()
}).then(function (response) {
return response.json().catch(function () {
throw new Error("服务器返回无效响应");
});
}).then(function (payload) {
if (!payload || !payload.success) {
var message = payload && payload.data && payload.data.message ? payload.data.message : "请求失败";
throw new Error(message);
}
return payload.data || {};
});
}
function setButtonBusy(button, busy) {
button.disabled = !!busy;
button.classList.toggle("is-loading", !!busy);
}
function findInScope(scope, id) {
scope = scope || document;
if (!scope) return null;
if (scope.nodeType === 9) {
return scope.getElementById(id);
}
if (scope.nodeType === 1) {
if (scope.id === id) return scope;
return typeof scope.querySelector === "function"
? scope.querySelector("#" + id)
: null;
}
return null;
}
function collectActionButtons(scope, selector) {
scope = scope || document;
var list = [];
if (!scope) return list;
if (scope.nodeType === 1 && scope.matches && scope.matches(selector)) {
list.push(scope);
}
if (typeof scope.querySelectorAll === "function") {
var found = scope.querySelectorAll(selector);
for (var i = 0; i < found.length; i++) list.push(found[i]);
} else if (scope.nodeType === 9) {
var all = document.querySelectorAll(selector);
for (var j = 0; j < all.length; j++) list.push(all[j]);
}
// also keep legacy id hooks
var legacyId = selector.indexOf("action-like") >= 0 ? "action-like" : (selector.indexOf("action-favorite") >= 0 ? "action-favorite" : "");
if (legacyId) {
var legacy = findInScope(scope, legacyId);
if (legacy && list.indexOf(legacy) === -1) list.push(legacy);
}
return list;
}
function initInteractionBar(root) {
var scope = root || document;
// Bind all like/favorite controls (in-content + footer tabbar extend).
var likeBtns = collectActionButtons(scope, ".action-like[data-post-id]");
var favBtns = collectActionButtons(scope, ".action-favorite[data-post-id]");
function paintLike(btn, isActive) {
btn.dataset.initialActive = isActive ? "1" : "0";
btn.classList.toggle("is-active", isActive);
var icon = btn.querySelector("i");
if (icon) icon.className = isActive ? "fa fas fa-heart" : "fa fas fa-heart";
}
function paintFavorite(btn, isActive) {
btn.dataset.initialActive = isActive ? "1" : "0";
btn.classList.toggle("is-active", isActive);
var icon = btn.querySelector("i");
if (icon) icon.className = isActive ? "fa fas fa-star" : "fa fas fa-star";
}
function applyLikeState(isActive, count) {
for (var i = 0; i < likeBtns.length; i++) paintLike(likeBtns[i], isActive);
if ("number" === typeof count && !isNaN(count)) {
setBaseStatCount("likes", count);
} else {
updateStatCounts("likes", getBaseStatCount("likes"));
}
}
function applyFavoriteState(isActive, count) {
for (var i = 0; i < favBtns.length; i++) paintFavorite(favBtns[i], isActive);
if ("number" === typeof count && !isNaN(count)) {
setBaseStatCount("favorites", count);
} else {
updateStatCounts("favorites", getBaseStatCount("favorites"));
}
}
if (likeBtns.length) {
var seedLike = likeBtns[0];
if (!seedLike.dataset.aksrtInteractionBound) {
applyLikeState(seedLike.dataset.initialActive === "1", getBaseStatCount("likes"));
likeBtns.forEach(function (likeBtn) {
if (likeBtn.dataset.aksrtInteractionBound) return;
likeBtn.dataset.aksrtInteractionBound = "1";
likeBtn.addEventListener("click", function () {
var postId = likeBtn.dataset.postId ? String(likeBtn.dataset.postId) : "";
if (!postId || likeBtn.disabled) return;
var nextState = likeBtn.dataset.initialActive !== "1";
setButtonBusy(likeBtn, true);
requestInteraction("aksrt_toggle_like", postId, nextState).then(function (data) {
applyLikeState(!!data.active, parseInt(data.count, 10) || 0);
notyf(data.active ? "感谢你的点赞" : "已取消点赞", "success");
}).catch(function (error) {
notyf(error && error.message ? error.message : "点赞失败", "danger");
}).finally(function () {
setButtonBusy(likeBtn, false);
});
});
});
}
}
if (favBtns.length) {
var seedFav = favBtns[0];
if (!seedFav.dataset.aksrtInteractionBound) {
applyFavoriteState(seedFav.dataset.initialActive === "1", getBaseStatCount("favorites"));
favBtns.forEach(function (favBtn) {
if (favBtn.dataset.aksrtInteractionBound) return;
favBtn.dataset.aksrtInteractionBound = "1";
favBtn.addEventListener("click", function () {
var postId = favBtn.dataset.postId ? String(favBtn.dataset.postId) : "";
if (!postId || favBtn.disabled) return;
var nextState = favBtn.dataset.initialActive !== "1";
setButtonBusy(favBtn, true);
requestInteraction("aksrt_toggle_favorite", postId, nextState).then(function (data) {
applyFavoriteState(!!data.active, parseInt(data.count, 10) || 0);
notyf(data.active ? "收藏成功" : "已取消收藏", "success");
}).catch(function (error) {
notyf(error && error.message ? error.message : "收藏失败", "danger");
}).finally(function () {
setButtonBusy(favBtn, false);
});
});
});
}
}
}
window.AksrtFrontend.register("aksrt-search-clear", function () { initSearchClear(); });
window.AksrtFrontend.register("aksrt-article-tools", function () { initArticleTools(); });
window.AksrtFrontend.register("aksrt-password-toggles", function (root) { initPasswordToggles(root); });
window.AksrtFrontend.register("aksrt-interaction-bar", function (root) { initInteractionBar(root); });
})();
;
/* source: assets/js/core/comments.js */
/**
* AKSRT Comment System — AJAX submit, captcha, ordering, author filter
*/
(function () {
"use strict";
function initCommentSystem() {
var form = document.querySelector("#commentform");
if (!form) return;
// 修复:同时匹配 button 和 input 元素
var submitBtn = form.querySelector('[type="submit"]');
if (!submitBtn) return;
var isInputSubmit = submitBtn.tagName === 'INPUT';
// 保存原始按钮内容(用于恢复)
var originalBtnHTML = isInputSubmit ? submitBtn.value : submitBtn.innerHTML;
function setSubmitBtnContent(html, textFallback) {
if (isInputSubmit) {
submitBtn.value = textFallback;
} else {
submitBtn.innerHTML = html;
}
}
// 状态管理
var isSubmitting = false;
var countdownTimer = null;
// 显示/隐藏加载状态
function setLoading(loading) {
if (loading) {
isSubmitting = true;
submitBtn.classList.add("is-loading");
submitBtn.disabled = true;
var loadingText = (window.aksrtData && aksrtData.commentSubmittingText) || '发送中...';
setSubmitBtnContent('<i class="fa fas fa-spinner fa-spin"></i> ' + loadingText, loadingText);
} else {
isSubmitting = false;
submitBtn.classList.remove("is-loading");
submitBtn.classList.remove('countdown-active');
submitBtn.disabled = false;
setSubmitBtnContent(originalBtnHTML, originalBtnHTML);
}
}
// 收集表单数据(包含验证码字段)
function collectFormData() {
var formData = new FormData(form);
// 确保包含必要的隐藏字段
var postId = document.querySelector('input[name="comment_post_ID"]');
if (postId && !formData.get('comment_post_ID')) {
formData.append('comment_post_ID', postId.value);
}
return formData;
}
// 从 wp_die() HTML 中提取错误信息
function extractErrorMessage(html) {
// WordPress wp_die() 输出格式: <p>错误信息</p>
var match = html.match(/<p[^>]*>([^<]+)<\/p>/i);
return match ? match[1].trim() : '未知错误';
}
// 从错误消息中提取等待时间(秒)
function extractWaitTime(msg) {
var match = msg.match(/(\d+)\s*秒/);
return match ? parseInt(match[1]) : 0;
}
// 启动倒计时(用于频率限制)
function startCountdown(seconds) {
if (countdownTimer) clearInterval(countdownTimer);
submitBtn.classList.add('countdown-active');
submitBtn.disabled = true;
var remaining = seconds;
countdownTimer = setInterval(function () {
setSubmitBtnContent(
'<i class="fa fas fa-clock"></i> 请等待 ' + remaining + 's',
'请等待 ' + remaining + 's'
);
if (--remaining <= 0) {
clearInterval(countdownTimer);
countdownTimer = null;
submitBtn.classList.remove('countdown-active', 'is-loading');
submitBtn.disabled = false;
setSubmitBtnContent(originalBtnHTML, originalBtnHTML);
isSubmitting = false;
}
}, 1000);
}
function updateCommentCount(delta) {
var countEl = document.querySelector('#comments .title-theme small');
if (!countEl) return;
var text = countEl.textContent || '';
var match = text.match(/(\d+)/);
if (!match) return;
var nextCount = Math.max(0, parseInt(match[1], 10) + delta);
countEl.textContent = text.replace(match[1], String(nextCount));
}
function handleResponse(responseText, statusCode) {
try {
// 情况1: 成功提交 - 包含评论ID或成功标记
if (statusCode === 200 || statusCode === 201) {
if (responseText.includes('comment-submitted') ||
responseText.indexOf('id="comment-') !== -1 ||
responseText.indexOf('awaiting-moderation') !== -1) {
// 区分是否需要审核
if (responseText.indexOf('审核') !== -1 ||
responseText.indexOf('moderation') !== -1 ||
responseText.includes('awaiting-moderation')) {
notyf('评论已提交,等待管理员审核', 'info');
} else {
notyf('评论发布成功', 'success');
}
// 清空文本框
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
// 重置回复状态
var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none';
// 刷新评论列表(延迟执行,确保后端处理完成)
setTimeout(function () {
reloadCommentList();
}, 500);
setLoading(false);
return true;
}
}
// 情况2: HTTP 429 频率限制
if (statusCode === 429) {
var errorMsg = extractErrorMessage(responseText);
var waitSeconds = extractWaitTime(errorMsg);
notyf(errorMsg, 'warning');
if (waitSeconds > 0) {
startCountdown(waitSeconds);
} else {
setLoading(false); // 无具体等待时间时恢复按钮
}
return false;
}
// 情况3: HTTP 403 权限不足(地区/IP/关键词等)
if (statusCode === 403) {
var forbiddenMsg = extractErrorMessage(responseText);
notyf(forbiddenMsg, 'danger');
setLoading(false);
return false;
}
// 情况4: 包含WordPress错误信息
var errorMatch = responseText.match(/<p[^>]*>([^<]*(?:错误|失败|error|Error)[^<]*)<\/p>/i);
if (errorMatch) {
notyf(errorMatch[1].trim(), 'danger');
setLoading(false);
return false;
}
// 默认情况:可能是成功或其他
notyf('评论已提交', 'success');
var textarea2 = form.querySelector('#comment');
if (textarea2) textarea2.value = '';
// 局部刷新评论列表(不再整页 reload)
setTimeout(function () {
reloadCommentList();
}, 500);
setLoading(false);
return true;
} catch (e) {
console.error('[AKSRT Comment] 解析响应失败:', e);
notyf('响应解析异常,请重试', 'danger');
setLoading(false);
return false;
}
}
// 执行实际的 AJAX 请求 — local insert instead of full-page re-fetch
function performSubmit() {
setLoading(true);
var formData = collectFormData();
var restRoot = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, '') : '';
var editId = form.querySelector('[name="edit_comment_ID"]');
var isEdit = editId && parseInt(editId.value, 10) > 0;
var restBase = (restRoot || '') + '/wp-json/aksrt/v1/comment/' + (isEdit ? 'edit' : 'submit');
// Try REST endpoint for local-insert HTML response
var restBody = {};
formData.forEach(function (v, k) { restBody[k] = v; });
if (isEdit) {
restBody.comment_id = parseInt(editId.value, 10);
restBody.content = restBody.comment || restBody.content || '';
}
fetch(restBase, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': (window.aksrtData && aksrtData.restNonce) || ''
},
credentials: 'same-origin',
body: JSON.stringify(restBody)
})
.then(function (r) { return r.json(); })
.then(function (json) {
if (isEdit && json && json.success) {
var cid = restBody.comment_id;
var contentEl = document.querySelector('#comment-content-' + cid + ', [data-comment-id="' + cid + '"] .comment-content');
var rendered = (json.data && json.data.content) || json.content || '';
if (contentEl && rendered) contentEl.innerHTML = rendered;
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
if (editId) editId.remove();
restoreRespondPosition();
setLoading(false);
notyf(json.message || '评论已更新', 'success');
var respondEl = document.getElementById('respond');
if (respondEl && window.AksrtFixedInput) window.AksrtFixedInput(respondEl, 'hide');
return;
}
if (json && json.success && json.html) {
// Local insert: prepend new comment HTML
localInsertComment(json.html, json.is_reply, json.parent_id, json.needs_moderation);
var textarea = form.querySelector('#comment');
if (textarea) textarea.value = '';
var replyInfo = document.getElementById('comment-reply-info');
if (replyInfo) replyInfo.style.display = 'none';
// Reset captcha after successful submit
try {
var captchaScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (captchaScene && typeof captchaScene.reset === 'function') {
captchaScene.reset();
}
} catch (e) { /* ignore */ }
// Wait time countdown
if (json.wait_time && json.wait_time > 0) {
startCountdown(json.wait_time);
} else {
setLoading(false);
}
// Move respond back to original position
restoreRespondPosition();
// Hide mobile FixedInput after successful submit.
var respondEl = document.getElementById('respond');
if (respondEl && window.AksrtFixedInput) {
window.AksrtFixedInput(respondEl, 'hide');
} else if (respondEl) {
respondEl.classList.remove('show');
}
// If editing via moveForm, clear edit field.
var editField = form.querySelector('[name="edit_comment_ID"]');
if (editField) editField.remove();
return;
}
if (json && json.code) {
notyf(json.message || '评论提交失败', 'danger');
try {
var failScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (failScene && typeof failScene.reset === 'function') {
failScene.reset();
}
} catch (e) { /* ignore */ }
if (json.wait_time) startCountdown(json.wait_time);
else setLoading(false);
return;
}
notyf('评论已提交', 'success');
setLoading(false);
setTimeout(function () { reloadCommentList(); }, 500);
})
.catch(function () {
// Fallback: submit to wp-comments-post.php directly
performLegacySubmit();
});
}
function localInsertComment(html, isReply, parentId, needsModeration) {
var temp = document.createElement('div');
temp.innerHTML = html;
var newComment = temp.firstElementChild;
if (!newComment) { reloadCommentList(); return; }
newComment.style.overflow = 'hidden';
newComment.style.maxHeight = '0';
newComment.style.opacity = '0';
newComment.style.transition = 'max-height 0.4s ease, opacity 0.3s ease';
if (isReply && parentId) {
var parentComment = document.querySelector('#comment-' + parentId);
if (parentComment) {
var children = parentComment.querySelector(':scope > .children');
if (!children) {
children = document.createElement('ol');
children.className = 'children';
parentComment.appendChild(children);
}
children.insertBefore(newComment, children.firstChild);
} else {
prependToCommentList(newComment);
}
} else {
prependToCommentList(newComment);
}
// slideDown animation
requestAnimationFrame(function () {
newComment.style.maxHeight = newComment.scrollHeight + 100 + 'px';
newComment.style.opacity = '1';
setTimeout(function () {
newComment.style.maxHeight = '';
newComment.style.overflow = '';
newComment.style.transition = '';
}, 500);
});
if (needsModeration) {
notyf('评论已提交,等待管理员审核', 'info');
} else {
notyf('评论发布成功', 'success');
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(newComment, 'ajax');
updateCommentCount(1);
}
function prependToCommentList(el) {
var list = document.querySelector('#comments .commentlist');
if (!list) return;
// 插到筛选栏之后、最新评论之前
var filter = list.querySelector('.comment-filter');
if (filter && filter.nextSibling) {
list.insertBefore(el, filter.nextSibling);
} else {
list.insertBefore(el, list.firstChild);
}
}
function restoreRespondPosition() {
var respond = document.getElementById('respond');
var placeholder = document.getElementById('wp-temp-form-div');
if (respond && placeholder && placeholder.parentNode) {
placeholder.parentNode.insertBefore(respond, placeholder);
placeholder.remove();
}
}
// Legacy fallback: POST to wp-comments-post.php
function performLegacySubmit() {
var formData = collectFormData();
var actionUrl = form.getAttribute('action') || '/wp-comments-post.php';
fetch(actionUrl, {
method: 'POST',
body: formData,
credentials: 'same-origin',
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function (response) {
return response.text().then(function (text) {
return { status: response.status, text: text, ok: response.ok };
});
})
.then(function (result) {
handleResponse(result.text, result.status);
})
.catch(function (error) {
console.error('[AKSRT Comment] 提交失败:', error);
if (error.message === 'Failed to fetch' || error.name === 'TypeError') {
notyf('网络连接失败,请检查网络后重试', 'danger');
} else {
notyf(error.message || '评论提交失败,请重试', 'danger');
}
setLoading(false);
})
.finally(function () {
// 兜底:避免异常分支漏掉状态恢复导致按钮卡在"发送中"
if (!countdownTimer && isSubmitting) {
setLoading(false);
}
});
}
// 核心表单提交事件处理器
function handleSubmit(e) {
// 如果已经在提交中或倒计时中,防止重复提交
if (isSubmitting || countdownTimer) {
e.preventDefault();
e.stopImmediatePropagation();
return false;
}
// 阻止默认表单提交
e.preventDefault();
e.stopImmediatePropagation();
// 检查验证码状态(如果有验证码组件)
var captchaScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
window.AKSRTCaptchaState.scenes.comment;
if (captchaScene && typeof captchaScene.verify === 'function') {
// 有验证码:先触发验证流程
setLoading(true);
captchaScene.verify(function (verified) {
if (verified !== false) {
// 验证通过,执行 AJAX 提交
performSubmit();
} else {
// 验证失败
notyf('人机验证未通过,请重试', 'warning');
setLoading(false);
}
});
} else {
// 无验证码或已经验证过:直接提交
performSubmit();
}
return false;
}
// 绑定表单提交事件(使用捕获阶段,高优先级)
form.addEventListener('submit', handleSubmit, true);
}
function getCommentsRestBase() {
var root = (window.aksrtData && aksrtData.siteUrl) ? String(aksrtData.siteUrl).replace(/\/$/, "") : "";
return root + "/wp-json/aksrt/v1";
}
function getCurrentCommentPostId() {
var input = document.querySelector('input[name="comment_post_ID"]');
if (input && input.value) return parseInt(input.value, 10) || 0;
var comments = document.getElementById("comments");
if (comments && comments.dataset.postId) return parseInt(comments.dataset.postId, 10) || 0;
return 0;
}
function getCurrentCommentOrder() {
var active = document.querySelector("#comments .comment-order-box li.active .comment-orderby");
if (active && active.dataset && active.dataset.order) return active.dataset.order;
var url = new URL(window.location.href);
return url.searchParams.get("aksrt_comment_order") || "newest";
}
function getCurrentAuthorOnly() {
var url = new URL(window.location.href);
if (url.searchParams.get("aksrt_author_only") === "1") return true;
var btn = document.querySelector("#comments .btn-only-author");
return !!(btn && btn.classList.contains("active") && btn.dataset.authorOnly === "0");
}
function applyFragmentPayload(commentsArea, data, urlState) {
if (!commentsArea || !data || !data.html) return;
// data.html 是完整的 <div id="postcomments">(含筛选、列表、分页)
var tempList = document.createElement("div");
tempList.innerHTML = data.html;
var newListWrap = tempList.querySelector("#postcomments");
if (!newListWrap) return;
var currentListWrap = commentsArea.querySelector("#postcomments");
if (currentListWrap) {
currentListWrap.replaceWith(newListWrap);
} else {
commentsArea.appendChild(newListWrap);
}
var currentCount = commentsArea.querySelector(".title-theme small");
if (currentCount && data.count_text) currentCount.textContent = data.count_text;
if (urlState && window.history && typeof window.history.replaceState === "function") {
window.history.replaceState(null, "", urlState);
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(commentsArea, "ajax");
}
function fetchCommentFragment(opts) {
opts = opts || {};
var postId = opts.postId || getCurrentCommentPostId();
if (!postId) return Promise.reject(new Error("missing post id"));
var order = opts.order || getCurrentCommentOrder() || "newest";
var authorOnly = !!opts.authorOnly;
var page = opts.page || 1;
var qs = new URLSearchParams();
qs.set("post_id", String(postId));
qs.set("order", order);
qs.set("page", String(page));
if (authorOnly) qs.set("author_only", "1");
var url = getCommentsRestBase() + "/comment/list?" + qs.toString();
return fetch(url, {
method: "GET",
credentials: "same-origin",
headers: {
"X-WP-Nonce": (window.aksrtData && aksrtData.restNonce) || ""
}
}).then(function (r) { return r.json(); });
}
/* ====== 评论列表动态刷新 ====== */
function showListLoading(commentsArea) {
var list = commentsArea.querySelector(".commentlist");
if (!list) return;
// 保留筛选栏,清空条目并显示 legacy 骨架
var filter = list.querySelector(".comment-filter");
var loader = list.querySelector(".post_ajax_loader");
var skeleton = loader ? loader.cloneNode(true) : null;
Array.prototype.slice.call(list.children).forEach(function (child) {
if (child !== filter && child !== loader) child.remove();
});
if (skeleton) {
skeleton.style.display = "block";
skeleton.removeAttribute("hidden");
list.appendChild(skeleton);
}
}
function reloadCommentList() {
var commentsArea = document.getElementById('comments');
if (!commentsArea) {
window.location.reload();
return;
}
showListLoading(commentsArea);
fetchCommentFragment({
order: getCurrentCommentOrder(),
authorOnly: getCurrentAuthorOnly(),
page: 1
}).then(function (json) {
if (json && json.success && json.data) {
applyFragmentPayload(commentsArea, json.data, null);
return;
}
throw new Error('fragment failed');
}).catch(function (error) {
console.error('[AKSRT Comment] 刷新评论列表失败:', error);
window.location.reload();
});
}
/* ====== 评论排序(最新 / 最早) ====== */
/**
* Locate #id within a rehydrate root.
* Document has getElementById; Element roots (AJAX panels) only have querySelector.
*/
function findInScope(scope, id) {
scope = scope || document;
if (!scope) return null;
if (scope.nodeType === 9) {
return scope.getElementById(id);
}
if (scope.nodeType === 1) {
if (scope.id === id) return scope;
return typeof scope.querySelector === "function"
? scope.querySelector("#" + id)
: null;
}
return null;
}
function initCommentOrder(root) {
var scope = root || document;
var commentsArea = findInScope(scope, "comments");
if (!commentsArea) return;
// Event delegation survives filter HTML replacement after AJAX rehydrate.
if (commentsArea.dataset.orderInit === "1") return;
commentsArea.dataset.orderInit = "1";
function renderLoadingState() {
showListLoading(commentsArea);
}
function applyCommentOrder(order) {
if (order !== "newest" && order !== "oldest" && order !== "hottest") {
order = "newest";
}
renderLoadingState();
var authorOnly = getCurrentAuthorOnly();
var url = new URL(window.location.href);
url.searchParams.set("aksrt_comment_order", order);
url.searchParams.delete("cpage");
if (authorOnly) url.searchParams.set("aksrt_author_only", "1");
else url.searchParams.delete("aksrt_author_only");
fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 })
.then(function (json) {
if (json && json.success && json.data) {
applyFragmentPayload(commentsArea, json.data, url.toString());
return;
}
throw new Error("order fragment failed");
})
.catch(function (error) {
console.error("[AKSRT Comment] 排序刷新失败:", error);
window.location.href = url.toString();
});
}
function applyAuthorFilter(authorOnlyFlag) {
renderLoadingState();
var authorOnly = authorOnlyFlag === "1" || authorOnlyFlag === 1 || authorOnlyFlag === true;
var order = getCurrentCommentOrder();
var url = new URL(window.location.href);
if (authorOnly) url.searchParams.set("aksrt_author_only", "1");
else url.searchParams.delete("aksrt_author_only");
url.searchParams.delete("cpage");
if (order) url.searchParams.set("aksrt_comment_order", order);
fetchCommentFragment({ order: order, authorOnly: authorOnly, page: 1 })
.then(function (json) {
if (json && json.success && json.data) {
applyFragmentPayload(commentsArea, json.data, url.toString());
return;
}
throw new Error("author fragment failed");
})
.catch(function (error) {
console.error("[AKSRT Comment] Author filter failed:", error);
window.location.href = url.toString();
});
}
commentsArea.addEventListener("click", function (e) {
var target = e.target;
if (!target || typeof target.closest !== "function") return;
// 「只看作者」同时带 comment-orderby,需优先于排序按钮判断
var authorBtn = target.closest(".btn-only-author");
if (authorBtn && commentsArea.contains(authorBtn)) {
e.preventDefault();
applyAuthorFilter(authorBtn.getAttribute("data-author-only") || "0");
return;
}
var orderBtn = target.closest(".comment-orderby[data-order]");
if (orderBtn && commentsArea.contains(orderBtn)) {
e.preventDefault();
applyCommentOrder(orderBtn.getAttribute("data-order") || "newest");
}
});
}
/* ====== 数字分页跳页(legacy pag-jump) ====== */
function jumpToCommentPage(page) {
page = parseInt(page, 10) || 1;
var url = new URL(window.location.href);
url.searchParams.set("cpage", String(page));
url.hash = "comments";
window.location.href = url.toString();
}
function initCommentPageJump() {
document.addEventListener("click", function (e) {
var jump = e.target.closest ? e.target.closest("#comments .pag-jump") : null;
if (!jump) return;
e.preventDefault();
var input = jump.querySelector(".jump-input");
if (input) jumpToCommentPage(input.value);
});
document.addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;
var input = e.target;
if (!input || !input.classList || !input.classList.contains("jump-input")) return;
if (!input.closest("#comments .pagenav")) return;
e.preventDefault();
jumpToCommentPage(input.value);
});
}
window.AksrtFrontend.register("aksrt-comment-system", function () { initCommentSystem(); initCommentPageJump(); });
window.AksrtFrontend.register("aksrt-comment-order", function (root) { initCommentOrder(root); });
})();
;
/* source: assets/js/core/comment-fixed-input.js */
/**
* Mobile FixedInput + autoheight for comment respond (legacy parity).
*/
(function () {
'use strict';
function $(sel, root) {
return (root || document).querySelector(sel);
}
function FixedInput(el, action) {
if (!el) return;
if (!el.dataset.fixedBound) {
el.addEventListener('click', function (e) {
if (e.target && e.target.closest && e.target.closest('.fixed-body')) {
FixedInput(el, 'hide');
}
});
el.dataset.fixedBound = '1';
}
if (action === 'show') {
el.classList.add('show');
window.setTimeout(function () {
var field = el.querySelector('textarea, input');
if (field) field.focus();
}, 100);
return;
}
if (action === 'hide') {
var cancel = el.querySelector('#cancel-comment-reply-link');
if (cancel) cancel.click();
el.querySelectorAll('input,textarea').forEach(function (n) {
try { n.blur(); } catch (e) {}
});
el.querySelectorAll('.dropup.open, .open').forEach(function (n) {
n.classList.remove('open');
});
el.classList.remove('show');
}
}
document.addEventListener('click', function (e) {
var trigger = e.target && e.target.closest ? e.target.closest('[fixed-input]') : null;
if (!trigger) return;
e.preventDefault();
var targetSel = trigger.getAttribute('fixed-input') || '#respond';
var panel = document.querySelector(targetSel);
var comments = document.getElementById('comments');
if (comments && comments.scrollIntoView) {
comments.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
FixedInput(panel, 'show');
});
// Autoheight textareas.
function bindAutoheight(ta) {
if (!ta || ta.dataset.autoheightBound) return;
ta.dataset.autoheightBound = '1';
var max = parseInt(ta.getAttribute('maxheight') || '188', 10) || 188;
var resize = function () {
ta.style.height = 'auto';
var h = Math.min(ta.scrollHeight, max);
ta.style.height = h + 'px';
ta.style.overflowY = ta.scrollHeight > max ? 'auto' : 'hidden';
};
ta.addEventListener('input', resize);
resize();
}
function scan() {
document.querySelectorAll('textarea[autoheight], textarea.grin').forEach(bindAutoheight);
}
document.addEventListener('DOMContentLoaded', scan);
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
window.AksrtFrontend.register('aksrt-comment-fixed-input', function (root) {
scan();
// Expose for submit hide.
root = root || document;
var respond = root.querySelector ? root.querySelector('#respond') : document.getElementById('respond');
if (respond) {
respond.aksrtFixedInput = function (action) {
FixedInput(respond, action);
};
}
});
}
window.AksrtFixedInput = FixedInput;
})();
;
/* source: assets/js/core/comment-move-form.js */
/**
* Comment moveForm for reply/edit (legacy parity) — REST-backed edit fetch.
*/
(function () {
'use strict';
var respondId = 'respond';
var tempId = 'aksrt-wp-temp-form-div';
var editingId = 0;
function getRespond() {
return document.getElementById(respondId);
}
function restRoot() {
var d = window.aksrtData || {};
return (d.restUrl || d.rest_url || '/wp-json/aksrt/v1').replace(/\/$/, '');
}
function nonce() {
var d = window.aksrtData || {};
return d.restNonce || d.nonce || '';
}
function setActionText(text) {
var el = document.querySelector('#' + respondId + ' .action-text');
if (!el) return;
if (text) {
el.style.display = '';
el.textContent = text;
} else {
el.style.display = 'none';
el.textContent = '';
}
}
function ensureParentField(respond) {
var form = respond.querySelector('#commentform') || respond;
var parent = form.querySelector('[name="comment_parent"]');
if (!parent) {
parent = document.createElement('input');
parent.type = 'hidden';
parent.name = 'comment_parent';
parent.id = 'comment_parent';
parent.value = '0';
form.appendChild(parent);
}
return parent;
}
function ensureEditField(respond, commentId) {
var form = respond.querySelector('#commentform') || respond;
var existing = form.querySelector('[name="edit_comment_ID"]');
if (commentId) {
if (!existing) {
existing = document.createElement('input');
existing.type = 'hidden';
existing.name = 'edit_comment_ID';
existing.id = 'edit_comment_ID';
form.appendChild(existing);
}
existing.value = String(commentId);
// Hide parent while editing.
var parent = form.querySelector('[name="comment_parent"]');
if (parent) parent.name = 'comment_parent_bak';
} else if (existing) {
existing.remove();
var bak = form.querySelector('[name="comment_parent_bak"]');
if (bak) bak.name = 'comment_parent';
}
}
function moveForm(commId, parentId, isEdit) {
var respond = getRespond();
if (!respond) return false;
var comm = document.getElementById(commId);
var parent = ensureParentField(respond);
var cancel = document.getElementById('cancel-comment-reply-link');
var submit = respond.querySelector('#submit');
var textarea = respond.querySelector('#comment');
if (!document.getElementById(tempId)) {
var temp = document.createElement('div');
temp.id = tempId;
temp.style.display = 'none';
respond.parentNode.insertBefore(temp, respond);
}
if (comm && comm.parentNode) {
comm.parentNode.insertBefore(respond, comm.nextSibling);
}
parent.value = isEdit ? '0' : String(parentId || '0');
editingId = isEdit ? parseInt(parentId, 10) || 0 : 0;
if (cancel) {
cancel.style.display = '';
cancel.onclick = function () {
var t = document.getElementById(tempId);
var r = getRespond();
if (t && r && t.parentNode) {
t.parentNode.insertBefore(r, t);
t.parentNode.removeChild(t);
}
ensureEditField(r, 0);
if (parent) parent.value = '0';
setActionText('');
if (textarea) textarea.value = '';
editingId = 0;
cancel.style.display = 'none';
if (window.AksrtFixedInput) window.AksrtFixedInput(r, 'hide');
else if (r) r.classList.remove('show');
return false;
};
}
if (isEdit && editingId) {
setActionText('编辑评论');
ensureEditField(respond, editingId);
if (textarea) {
textarea.disabled = true;
textarea.value = '加载中…';
}
if (submit) submit.disabled = true;
fetch(restRoot() + '/comment/get?comment_id=' + encodeURIComponent(String(editingId)), {
headers: { 'X-WP-Nonce': nonce() },
credentials: 'same-origin',
})
.then(function (r) { return r.json(); })
.then(function (json) {
var content = (json && (json.content || (json.data && json.data.content))) || '';
if (textarea) {
textarea.disabled = false;
textarea.value = content;
textarea.dispatchEvent(new Event('input'));
}
if (submit) submit.disabled = false;
})
.catch(function () {
notyf('加载评论失败', 'danger');
if (textarea) {
textarea.disabled = false;
textarea.value = '';
}
if (submit) submit.disabled = false;
});
} else {
var nameEl = comm ? comm.querySelector('.comment-header name') : null;
var author = nameEl ? (nameEl.textContent || '').trim() : '';
setActionText(author ? ('回复 @' + author) : '回复评论');
ensureEditField(respond, 0);
if (textarea) {
textarea.focus();
}
}
if (window.AksrtFixedInput) {
window.AksrtFixedInput(respond, 'show');
} else {
respond.classList.add('show');
}
if (comm && comm.scrollIntoView) {
comm.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
return false;
}
document.addEventListener('click', function (e) {
var reply = e.target && e.target.closest ? e.target.closest('.comment-reply-link') : null;
var edit = e.target && e.target.closest ? e.target.closest('.comment-edit-link, [data-comment-action="edit"]') : null;
if (reply) {
e.preventDefault();
var cid = reply.getAttribute('data-commentid') || reply.getAttribute('data-comment-id') || '';
var postid = reply.getAttribute('data-postid') || '';
if (!cid) {
var li = reply.closest('[data-comment-id], #comment-' + (cid || ''));
if (li && li.getAttribute('data-comment-id')) cid = li.getAttribute('data-comment-id');
}
if (!cid) {
var m = (reply.closest('li[id^="comment-"]') || {}).id || '';
var mm = /comment-(\d+)/.exec(m);
if (mm) cid = mm[1];
}
moveForm('div-comment-' + cid, cid, false);
return;
}
if (edit) {
e.preventDefault();
var eid = edit.getAttribute('data-commentid') || edit.getAttribute('data-comment-id') || '';
if (!eid) {
var row = edit.closest('[data-comment-id]');
if (row) eid = row.getAttribute('data-comment-id');
}
if (!eid) return;
moveForm('div-comment-' + eid, eid, true);
}
});
// Dropup / dropdown toggles (guest info, expand toolbar, action menu).
document.addEventListener('click', function (e) {
var t = e.target && e.target.closest ? e.target.closest('[data-toggle-class="open"], span.dropup > a.but, [data-toggle="dropdown"]') : null;
if (!t) return;
var wrap = t.closest('span.dropup, span.dropdown');
if (!wrap) return;
// 点击已在打开的下拉内部(表情/快捷项等)不切换
if (wrap.classList.contains('open') && t.parentElement !== wrap) return;
e.preventDefault();
wrap.classList.toggle('open');
});
// Close dropups on outside click.
document.addEventListener('click', function (e) {
if (e.target.closest && e.target.closest('span.dropup.open, span.dropdown.open')) return;
document.querySelectorAll('span.dropup.open, span.dropdown.open').forEach(function (n) {
n.classList.remove('open');
});
});
// Scroll helper for @parent.
document.addEventListener('click', function (e) {
var a = e.target && e.target.closest ? e.target.closest('[data-scroll-to]') : null;
if (!a) return;
e.preventDefault();
var sel = a.getAttribute('data-scroll-to');
var el = sel ? document.querySelector(sel) : null;
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
});
window.aksrtCommentMoveForm = moveForm;
window.aksrtGetEditingCommentId = function () { return editingId; };
})();
;
/* source: assets/js/core/ajax-list.js */
/**
* AKSRT Ajax List Engine — legacy .ajax-next / ajax-replace / route parity.
*
* DOM contract:
* .ajaxpager list container
* .ajax-item replace/append unit
* .ajax-pag .next-page.ajax-next paginate control
* ajax-replace="true" replace mode
* route / route-title history + document.title
* [win-ajax-replace] partial header sync from response HTML
*
* Transport:
* - Prefer data-aksrt-ajax-url JSON { html, header_html, paginate_html, title }
* - Else fetch href as HTML fragment and parse .ajaxpager / .ajax-item
*
* Events: post_ajax.before / post_ajax.ed on container (bubbles).
*
* @package AKSRT
*/
(function (window, document) {
"use strict";
var loadingMap = new WeakMap();
function restHeaders() {
var h = { Accept: "application/json, text/html", "X-AKSRT-Ajax": "list" };
if (window.aksrtData && aksrtData.restNonce) {
h["X-WP-Nonce"] = aksrtData.restNonce;
}
return h;
}
function closestPager(el) {
if (!el) return null;
return (
el.closest(".ajaxpager") ||
el.closest("[data-aksrt-ajaxpager]") ||
el.closest(".aksrt-ajaxpager")
);
}
function dispatch(container, name, detail) {
if (!container) return;
try {
container.dispatchEvent(
new CustomEvent(name, { bubbles: true, detail: detail || {} })
);
} catch (err) {}
}
function showLoader(container) {
if (!container) return;
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
if (loader) {
loader.classList.add("is-loading");
loader.hidden = false;
loader.style.display = "";
return;
}
container.classList.add("is-ajax-loading");
}
function hideLoader(container) {
if (!container) return;
var loader = container.querySelector(".post_ajax_loader, .post-ajax-loader, [data-aksrt-ajax-loader]");
if (loader) {
loader.classList.remove("is-loading");
if (loader.hasAttribute("data-aksrt-ajax-loader-hide")) {
loader.hidden = true;
loader.style.display = "none";
}
}
container.classList.remove("is-ajax-loading");
}
function parseHtml(html) {
var wrap = document.createElement("div");
wrap.innerHTML = html || "";
return wrap;
}
function extractFromHtml(html, container) {
var doc = parseHtml(html);
var pager =
doc.querySelector(".ajaxpager") ||
doc.querySelector("[data-aksrt-ajaxpager]") ||
doc.querySelector(".aksrt-ajaxpager");
var items = [];
var scope = pager || doc;
scope.querySelectorAll(".ajax-item").forEach(function (n) {
items.push(n.outerHTML);
});
if (!items.length && pager) {
items.push(pager.innerHTML);
}
var pag = scope.querySelector(".ajax-pag");
var headerBits = {};
doc.querySelectorAll("[win-ajax-replace]").forEach(function (n) {
var key = n.getAttribute("win-ajax-replace");
if (key) headerBits[key] = n.outerHTML;
});
var titleEl = doc.querySelector("title");
return {
itemsHtml: items.join(""),
paginateHtml: pag ? pag.outerHTML : "",
headerBits: headerBits,
title: titleEl ? titleEl.textContent : "",
fullHtml: pager ? pager.innerHTML : html,
};
}
function syncWinReplace(bits) {
if (!bits) return;
Object.keys(bits).forEach(function (key) {
var live = document.querySelector('[win-ajax-replace="' + key + '"]');
if (!live) return;
var tmp = document.createElement("div");
tmp.innerHTML = bits[key];
var next = tmp.firstElementChild;
if (next) live.replaceWith(next);
});
}
function applyRoute(link, title) {
if (!link) return;
var route = link.getAttribute("route") || link.getAttribute("data-route");
if (!(route === "1" || route === "true" || link.hasAttribute("route"))) return;
var href = link.getAttribute("href") || link.getAttribute("ajax-href") || "";
if (!href) return;
try {
window.history.replaceState({}, "", href);
} catch (err) {}
var rt =
title ||
link.getAttribute("route-title") ||
link.getAttribute("data-route-title") ||
"";
if (rt) {
try {
document.title = rt;
} catch (e2) {}
}
}
function replacePaginate(container, paginateHtml) {
if (!container || !paginateHtml) return;
var old = container.querySelector(".ajax-pag");
if (!old) {
container.insertAdjacentHTML("beforeend", paginateHtml);
return;
}
var tmp = document.createElement("div");
tmp.innerHTML = paginateHtml;
var next = tmp.firstElementChild;
if (next) old.replaceWith(next);
}
// Some widgets keep the rendered post grid inside an outer ajaxpager that
// also owns pagination/loading controls. Preserve that grid when replacing
// or appending AJAX items instead of flattening cards into the outer panel.
function findItemHost(container) {
if (!container || !container.children) return container;
for (var i = 0; i < container.children.length; i++) {
var child = container.children[i];
if (
child.matches &&
child.matches(".posts-row.post-grid, .aksrt-home-widget__post-grid")
) {
return child;
}
}
return container;
}
function inject(container, payload, options) {
options = options || {};
var replace = options.replace !== false;
if (!container) return;
var itemsHtml = payload.itemsHtml || payload.html || "";
var itemHost = findItemHost(container);
if (replace) {
itemHost.querySelectorAll(".ajax-item").forEach(function (n) {
n.remove();
});
// If container itself holds only items, clear non-loader children.
if (container.getAttribute("data-aksrt-ajax-clear") === "1") {
Array.prototype.slice.call(container.children).forEach(function (child) {
if (
child.classList.contains("post_ajax_loader") ||
child.classList.contains("post-ajax-loader") ||
child.hasAttribute("data-aksrt-ajax-loader") ||
child.classList.contains("ajax-pag")
) {
return;
}
child.remove();
});
}
if (itemsHtml) {
var pag = itemHost.querySelector(".ajax-pag");
if (pag) {
pag.insertAdjacentHTML("beforebegin", itemsHtml);
} else {
itemHost.insertAdjacentHTML("beforeend", itemsHtml);
}
}
} else if (itemsHtml) {
var pag2 = itemHost.querySelector(".ajax-pag");
if (pag2) {
pag2.insertAdjacentHTML("beforebegin", itemsHtml);
} else {
itemHost.insertAdjacentHTML("beforeend", itemsHtml);
}
}
if (payload.paginateHtml) {
replacePaginate(container, payload.paginateHtml);
}
if (payload.headerBits) {
syncWinReplace(payload.headerBits);
}
if (payload.headerHtml) {
var headerHost = document.querySelector(
options.headerSelector || "[data-aksrt-ajax-header]"
);
if (headerHost) headerHost.innerHTML = payload.headerHtml;
}
}
function normalizeJson(json) {
var data = json && json.data ? json.data : json;
if (!data) return null;
var html = data.html || data.list_html || "";
var itemsHtml = data.items_html || "";
if (!itemsHtml && html) {
// If html is only item nodes, use as-is; else extract.
if (html.indexOf("ajax-item") !== -1) {
var parsed = extractFromHtml("<div class='ajaxpager'>" + html + "</div>");
itemsHtml = parsed.itemsHtml || html;
} else {
itemsHtml = html;
}
}
return {
itemsHtml: itemsHtml,
html: html,
paginateHtml: data.paginate_html || data.paginateHtml || "",
headerHtml: data.header_html || data.headerHtml || "",
headerBits: data.header_bits || null,
title: data.title || "",
emptyHtml: data.empty_html || "",
hasMore: typeof data.has_more !== "undefined" ? !!data.has_more : null,
};
}
function loadFromLink(link, override) {
override = override || {};
if (!link) return Promise.resolve();
var container =
override.container ||
(link.getAttribute("ajaxpager-target")
? document.querySelector(link.getAttribute("ajaxpager-target"))
: null) ||
closestPager(link);
if (!container) {
// Fall back: parent stream list.
container = link.closest("[data-aksrt-ajax-list]");
}
if (!container) return Promise.resolve();
if (loadingMap.get(container)) return Promise.resolve();
loadingMap.set(container, true);
var replaceAttr = link.getAttribute("ajax-replace");
var replace =
typeof override.replace === "boolean"
? override.replace
: replaceAttr === "true" || replaceAttr === "1" || replaceAttr === "replace";
var href =
override.url ||
link.getAttribute("data-aksrt-ajax-url") ||
link.getAttribute("ajax-href") ||
link.getAttribute("href") ||
"";
dispatch(container, "post_ajax.before", { link: link, replace: replace, url: href });
showLoader(container);
if (replace) {
// Soft clear items while loading (keep skeleton if present).
container.querySelectorAll(".ajax-item").forEach(function (n) {
n.classList.add("is-fading");
});
}
return fetch(href, {
method: override.method || "GET",
credentials: "same-origin",
headers: restHeaders(),
})
.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)) {
throw new Error(
(json && (json.message || (json.data && json.data.message))) || "加载失败"
);
}
return normalizeJson(json);
});
}
return res.text().then(function (text) {
if (!res.ok) throw new Error("加载失败");
return extractFromHtml(text, container);
});
})
.then(function (payload) {
if (!payload) throw new Error("空响应");
if (replace && !payload.itemsHtml && payload.emptyHtml) {
inject(container, { itemsHtml: payload.emptyHtml, paginateHtml: payload.paginateHtml }, { replace: true });
} else {
inject(container, payload, { replace: replace });
}
applyRoute(link, payload.title);
hideLoader(container);
loadingMap.set(container, false);
dispatch(container, "post_ajax.ed", {
link: link,
replace: replace,
url: href,
payload: payload,
});
document.dispatchEvent(
new CustomEvent("aksrt:posts:rendered", {
bubbles: true,
detail: { container: container, replace: replace },
})
);
return payload;
})
.catch(function (err) {
hideLoader(container);
loadingMap.set(container, false);
container.querySelectorAll(".ajax-item.is-fading").forEach(function (n) {
n.classList.remove("is-fading");
});
notyf((err && err.message) || "加载失败", "danger");
dispatch(container, "post_ajax.error", { link: link, error: err });
throw err;
});
}
document.addEventListener("click", function (e) {
var link = e.target.closest("a.ajax-next, .ajax-next a, button.ajax-next, [data-aksrt-ajax-next]");
if (!link) return;
// Ignore if inside a component that opts out.
if (link.closest("[data-aksrt-ajax-list='off']")) return;
// The canonical pagination engine exclusively owns these controls.
if (link.closest("[data-aksrt-pagination], .aksrt-comment-pagination")) return;
var href = link.getAttribute("href") || link.getAttribute("ajax-href") || link.getAttribute("data-aksrt-ajax-url");
if (!href && !link.getAttribute("data-aksrt-ajax-url")) return;
e.preventDefault();
loadFromLink(link.tagName === "A" || link.classList.contains("ajax-next") ? link : link.closest(".ajax-next") || link);
});
window.AksrtAjaxList = {
load: loadFromLink,
inject: inject,
extractFromHtml: extractFromHtml,
closestPager: closestPager,
};
})(window, document);
;
/* source: assets/js/core/pagination.js */
/**
* AKSRT AJAX Pagination & Lazy Load
*/
(function () {
"use strict";
function hasAjaxConfig() {
return !!(window.aksrtData && aksrtData.ajaxUrl && aksrtData.nonce);
}
function initAjaxPagination(root) {
var containers = (root || document).querySelectorAll("[data-aksrt-pagination], .aksrt-comment-pagination");
if (!containers.length) return;
var triggerText = (window.aksrtData && aksrtData.ajaxTrigger) || "加载更多";
var nomoreText = (window.aksrtData && aksrtData.ajaxNomore) || "没有更多了";
var loadingText = (window.aksrtData && aksrtData.ajaxLoading) || "正在加载...";
var errorText = (window.aksrtData && aksrtData.ajaxLoadError) || "加载失败,请重试";
containers.forEach(function (container) {
if (container.dataset.aksrtPaginationBound === "1") return;
container.dataset.aksrtPaginationBound = "1";
var mode = container.dataset.mode || "load_more";
var nextPage = parseInt(container.dataset.nextPage, 10) || 2;
var maxPages = parseInt(container.dataset.maxPages, 10) || 1;
var isComment = container.classList.contains("aksrt-comment-pagination");
var postId = container.dataset.postId || "";
var commentOrder = container.dataset.commentOrder || "newest";
var loading = false;
var failed = false;
var clickTarget = container.querySelector(".next-page.ajax-next, .ias-btn") || container;
var loadMoreBtn = container.querySelector("[data-aksrt-load-more]") || clickTarget;
var indicator = container.querySelector(".post_ajax_loader");
var successfulAutoLoads = 0;
var iasMax = isComment
? ((window.aksrtData && typeof aksrtData.commentIasMax !== "undefined") ? parseInt(aksrtData.commentIasMax, 10) : 0)
: ((window.aksrtData && typeof aksrtData.iasMax !== "undefined") ? parseInt(aksrtData.iasMax, 10) : 0);
var homepagePanel = container.closest("[data-home-tab-panel]");
var target = homepagePanel || container.previousElementSibling;
if (!homepagePanel && target && target.classList && target.classList.contains("content-stream__footer")) {
target = target.previousElementSibling;
}
if (isComment) {
var commentList = container.closest("#comment-list, .commentlist, ol.commentlist") ||
document.getElementById("comment-list");
if (commentList) target = commentList;
}
if (!target && container.parentElement) {
target = container.parentElement.querySelector(".post-grid, .comment-list, .commentlist, .content-stream__list");
}
function showSkeleton() {
if (!target) return;
var loader = target.querySelector(".post-ajax-loader");
if (loader) {
loader.classList.add("is-loading");
}
}
function hideSkeleton() {
if (!target) return;
var loader = target.querySelector(".post-ajax-loader");
if (loader) {
loader.classList.remove("is-loading");
}
}
function stopInfinite(markNomore) {
container.classList.remove("ias-pending");
if (markNomore && loadMoreBtn) {
loadMoreBtn.innerHTML = nomoreText;
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.classList.add("ajax-nomore");
var nextWrap = loadMoreBtn.closest(".next-page");
if (nextWrap) nextWrap.classList.add("no-more");
loadMoreBtn.style.pointerEvents = "none";
} else if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore")) {
loadMoreBtn.classList.remove("is-loading");
if (failed) {
loadMoreBtn.textContent = errorText;
} else {
loadMoreBtn.innerHTML = triggerText;
}
loadMoreBtn.style.pointerEvents = "";
}
}
function markFailed(message) {
failed = true;
stopInfinite(false);
if (loadMoreBtn) {
loadMoreBtn.textContent = message || errorText;
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.classList.remove("ajax-nomore");
loadMoreBtn.style.pointerEvents = "";
}
}
function loadNextPage(fromAuto) {
if (loading || failed || nextPage > maxPages || !target) return Promise.resolve(false);
if (!hasAjaxConfig()) {
markFailed(errorText);
return Promise.resolve(false);
}
loading = true;
if (indicator) indicator.style.display = "flex";
if (loadMoreBtn) {
loadMoreBtn.classList.add("is-loading");
loadMoreBtn.textContent = loadingText;
}
showSkeleton();
var action = isComment ? "aksrt_load_more_comments" : "aksrt_load_more_posts";
var queryContext = container.dataset.queryContext || "";
var queryVars = container.dataset.queryVars || "";
var renderConfig = container.dataset.renderConfig || "";
var body = new URLSearchParams();
body.set("action", action);
body.set("nonce", aksrtData.nonce);
body.set("page", String(nextPage));
if (queryContext) {
body.set("query_context", queryContext);
}
if (!isComment && queryVars) {
body.set("query_vars", queryVars);
}
if (!isComment && renderConfig) {
body.set("render_config", renderConfig);
}
if (isComment && postId) {
body.set("post_id", postId);
body.set("comment_order", commentOrder);
var authorOnly = container.dataset.authorOnly || "0";
if (authorOnly === "1") body.set("author_only", "1");
}
return fetch(aksrtData.ajaxUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
credentials: "same-origin",
body: body.toString(),
})
.then(function (r) {
if (!r.ok) {
throw new Error("HTTP " + r.status);
}
return r.text().then(function (text) {
try {
return JSON.parse(text);
} catch (err) {
throw new Error("Invalid JSON");
}
});
})
.then(function (res) {
if (!(res && res.success && res.data && typeof res.data.html === "string")) {
var msg = res && res.data && res.data.message ? res.data.message : errorText;
markFailed(msg);
return false;
}
var temp = document.createElement("div");
temp.innerHTML = res.data.html;
var newItems = temp.children;
while (newItems.length > 0) {
if (homepagePanel && container.parentNode === target) {
target.insertBefore(newItems[0], container);
} else if (container.parentNode === target) {
target.insertBefore(newItems[0], container);
} else {
target.appendChild(newItems[0]);
}
}
if (window.AksrtFrontend) {
AksrtFrontend.rehydrate(target, "ajax");
}
nextPage++;
container.dataset.nextPage = String(nextPage);
if (fromAuto) {
successfulAutoLoads++;
}
if (nextPage > maxPages || !res.data.has_more) {
stopInfinite(true);
if (!loadMoreBtn) {
container.innerHTML = '<span class="ajax-nomore">' + nomoreText + '</span>';
}
}
return true;
})
.catch(function () {
markFailed(errorText);
return false;
})
.finally(function () {
loading = false;
hideSkeleton();
if (indicator) indicator.style.display = "none";
if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) {
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.innerHTML = triggerText;
if (mode === "infinite") {
var lazyTrigger = clickTarget.closest(".next-page") || clickTarget;
if (iasMax > 0 && successfulAutoLoads >= iasMax) {
container.classList.remove("ias-pending");
lazyTrigger.classList.remove("lazyload", "lazyloaded", "lazyloading");
lazyTrigger.removeAttribute("lazyload-action");
} else {
lazyTrigger.classList.remove("lazyloaded", "lazyloading");
lazyTrigger.classList.add("lazyload");
if (window.lazySizes && lazySizes.loader) {
lazySizes.loader.checkElems();
}
}
}
}
});
}
if (loadMoreBtn) {
clickTarget.addEventListener("click", function(e) {
e.preventDefault();
if (loadMoreBtn.dataset.jumpComments === "1") {
var jumpInput = container.querySelector(".jump-input");
if (!jumpInput) return;
var targetPage = parseInt(jumpInput.value, 10) || 1;
var max = parseInt(jumpInput.dataset.maxPages || String(maxPages), 10) || maxPages;
targetPage = Math.max(1, Math.min(max, targetPage));
var url = new URL(window.location.href);
url.searchParams.set("cpage", String(targetPage));
window.location.href = url.toString();
return;
}
if (loadMoreBtn.dataset.jumpArchive === "1") {
var archiveJumpInput = container.querySelector(".jump-input");
if (!archiveJumpInput) return;
var archiveTargetPage = parseInt(archiveJumpInput.value, 10) || 1;
var archiveMax = parseInt(archiveJumpInput.dataset.maxPages || String(maxPages), 10) || maxPages;
archiveTargetPage = Math.max(1, Math.min(archiveMax, archiveTargetPage));
var archiveUrl = new URL(window.location.href);
archiveUrl.searchParams.set(loadMoreBtn.dataset.pageVar || "paged", String(archiveTargetPage));
window.location.href = archiveUrl.toString();
return;
}
var fromAuto = clickTarget.dataset.lazyAuto === "1";
delete clickTarget.dataset.lazyAuto;
if (fromAuto && iasMax > 0 && successfulAutoLoads >= iasMax) {
container.classList.remove("ias-pending");
return;
}
// Manual click can recover from a previous auto-load failure.
failed = false;
loadNextPage(fromAuto);
});
}
// The runtime lazySizes dispatches lazybeforeunveil natively but follows up
// with a jQuery-simulated click, which native listeners never receive.
// Route the unveil trigger through the same click pipeline.
if (container.classList.contains("lazyload") || clickTarget.classList.contains("lazyload")) {
container.addEventListener("lazybeforeunveil", function (e) {
if (e.target !== container && e.target !== clickTarget) return;
clickTarget.dataset.lazyAuto = "1";
clickTarget.click();
});
}
if (mode === "infinite") {
if ((isComment && window.aksrtData && aksrtData.commentIasAuto === false) || !hasAjaxConfig()) {
var autoTrigger = clickTarget.closest(".next-page") || clickTarget;
autoTrigger.classList.remove("lazyload");
autoTrigger.removeAttribute("lazyload-action");
return;
}
container.classList.add("ias-pending");
}
});
}
window.AksrtFrontend.register("aksrt-ajax-pagination", function (root) { initAjaxPagination(root); });
})();
;
/* source: assets/js/core/archive.js */
/**
* AKSRT Archive AJAX Filter
*/
(function () {
"use strict";
function initArchiveFilter(root) {
var scope = root || document;
var bar = scope.querySelector(".archive-filter-bar--ajax[data-ajax-archive]");
if (!bar) return;
var archiveType = bar.dataset.ajaxArchive || "category";
var termId = parseInt(bar.dataset.termId, 10) || 0;
var wrap = scope.querySelector("[data-ajax-wrap]");
if (!wrap) return;
var loading = wrap.querySelector(".archive__ajax-loading");
var REST_BASE = "/wp-json/aksrt/v1/archive/posts";
var emptyText = archiveType === "tag" ? "该标签下暂无文章" : "该分类下暂无文章";
var state = {
filter_cat: "",
filter_tag: "",
sort: "date",
page: 1
};
var activeCat = bar.querySelector("[data-filter-cat].is-active");
if (activeCat) state.filter_cat = activeCat.dataset.filterCat || "";
var activeTag = bar.querySelector("[data-filter-tag].is-active");
if (activeTag) state.filter_tag = activeTag.dataset.filterTag || "";
var activeSort = bar.querySelector("[data-filter-sort].is-active");
if (activeSort) state.sort = activeSort.dataset.filterSort || "date";
function buildUrl() {
var params = new URLSearchParams();
params.set("archive_type", archiveType);
params.set("term_id", String(termId));
if (state.filter_cat) params.set("filter_cat", state.filter_cat);
if (state.filter_tag) params.set("filter_tag", state.filter_tag);
params.set("sort", state.sort || "date");
params.set("page", String(state.page));
return REST_BASE + "?" + params.toString();
}
function updateActiveChips() {
bar.querySelectorAll("[data-filter-cat]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterCat || "") === state.filter_cat);
});
bar.querySelectorAll("[data-filter-tag]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterTag || "") === state.filter_tag);
});
bar.querySelectorAll("[data-filter-sort]").forEach(function (el) {
el.classList.toggle("is-active", (el.dataset.filterSort || "date") === state.sort);
});
}
function showLoading() {
if (loading) loading.classList.add("is-loading");
wrap.classList.add("is-loading");
}
function hideLoading() {
if (loading) loading.classList.remove("is-loading");
wrap.classList.remove("is-loading");
}
function rebuildEmptyState() {
var noResults = document.createElement("div");
noResults.className = "no-results content-stream__empty";
noResults.innerHTML = "<p>" + emptyText + "</p>";
wrap.appendChild(noResults);
}
function fetchPosts(pushState) {
showLoading();
fetch(buildUrl(), {
method: "GET",
headers: { Accept: "application/json" },
credentials: "same-origin"
})
.then(function (r) {
if (!r.ok) throw new Error("HTTP " + r.status);
return r.json();
})
.then(function (data) {
hideLoading();
if (data.html !== undefined) {
var children = Array.from(wrap.children);
children.forEach(function (child) {
if (child !== loading) wrap.removeChild(child);
});
if (data.html) {
var temp = document.createElement("div");
temp.innerHTML = data.html;
while (temp.firstChild) wrap.appendChild(temp.firstChild);
} else {
rebuildEmptyState();
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(wrap, "ajax");
}
if (pushState !== false) {
var url = new URL(window.location.href);
url.searchParams.delete("orderby");
url.searchParams.delete("order");
url.searchParams.delete("paged");
url.searchParams.delete("aksrt_filter_cat");
url.searchParams.delete("aksrt_filter_tag");
if (state.filter_cat) url.searchParams.set("aksrt_filter_cat", state.filter_cat);
if (state.filter_tag) url.searchParams.set("aksrt_filter_tag", state.filter_tag);
if (state.sort) url.searchParams.set("orderby", state.sort);
if (state.page > 1) url.searchParams.set("paged", String(state.page));
history.pushState({ aksrtArchive: true, state: Object.assign({}, state) }, "", url.toString());
}
})
.catch(function () {
hideLoading();
});
}
bar.addEventListener("click", function (e) {
var chip = e.target.closest("[data-filter-cat], [data-filter-tag], [data-filter-sort]");
if (!chip) return;
e.preventDefault();
var changed = false;
if (chip.dataset.filterCat !== undefined) {
var newCat = chip.dataset.filterCat;
if (newCat !== state.filter_cat) {
state.filter_cat = newCat;
changed = true;
}
}
if (chip.dataset.filterTag !== undefined) {
var newTag = chip.dataset.filterTag;
if (newTag !== state.filter_tag) {
state.filter_tag = newTag;
changed = true;
}
}
if (chip.dataset.filterSort !== undefined) {
var newSort = chip.dataset.filterSort || "date";
if (newSort !== state.sort) {
state.sort = newSort;
changed = true;
}
}
if (changed) {
state.page = 1;
updateActiveChips();
fetchPosts(true);
}
});
wrap.addEventListener("click", function (e) {
var pageLink = e.target.closest(".pagination a, .nav-links a, a.page-numbers");
if (!pageLink || !wrap.contains(pageLink)) return;
var href = pageLink.getAttribute("href");
if (!href) return;
var match = href.match(/[?&]paged=(\d+)/);
if (match) {
e.preventDefault();
state.page = parseInt(match[1], 10) || 1;
fetchPosts(true);
}
});
window.addEventListener("popstate", function (e) {
if (e.state && e.state.aksrtArchive) {
state = Object.assign(state, e.state.state);
updateActiveChips();
fetchPosts(false);
}
});
}
window.AksrtFrontend.register("aksrt-archive-filter", function (root) {
initArchiveFilter(root);
});
})();
;
/* source: assets/js/core/footer-tabbar.js */
/**
* AKSRT Footer Tabbar
*/
(function () {
"use strict";
function initFooterTabbar() {
var tabbar = document.getElementById("footer-tabbar");
if (!tabbar) return;
var addBackdrop = document.getElementById("tabbar-add-backdrop");
var ticking = false;
var scrollThreshold = 100;
var isHidden = false;
// Use body scroll bus classes from aksrt-runtime.js
function handleScrollDownHide() {
var body = document.body;
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
if (scrollTop > scrollThreshold) {
if (body.classList.contains("scroll-down") && !isHidden) {
// 向下滚动 - 隐藏
tabbar.classList.add("is-hidden");
isHidden = true;
} else if (body.classList.contains("scroll-up") && isHidden) {
tabbar.classList.remove("is-hidden");
isHidden = false;
}
} else {
tabbar.classList.remove("is-hidden");
isHidden = false;
}
}
// 返回顶部按钮切换
function handleOntopWrap() {
var ontopWraps = tabbar.querySelectorAll(".tabbar-ontop-wrap");
var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
ontopWraps.forEach(function (wrap) {
if (scrollTop > scrollThreshold) {
wrap.classList.add("is-scrolled");
} else {
wrap.classList.remove("is-scrolled");
}
});
}
// 滚动事件处理
function onScroll() {
if (!ticking) {
requestAnimationFrame(function () {
if (tabbar.classList.contains("scroll-down-hide")) {
handleScrollDownHide();
}
handleOntopWrap();
ticking = false;
});
ticking = true;
}
}
window.addEventListener("scroll", onScroll, { passive: true });
function closeAddPanels() {
document.querySelectorAll(".tabbar-add-panel").forEach(function (panel) {
panel.setAttribute("hidden", "");
});
if (addBackdrop) addBackdrop.setAttribute("hidden", "");
document.body.classList.remove("tabbar-add-open");
}
function openAddPanel(panelId) {
closeAddPanels();
var panel = document.getElementById(panelId);
if (!panel) return;
panel.removeAttribute("hidden");
if (addBackdrop) addBackdrop.removeAttribute("hidden");
document.body.classList.add("tabbar-add-open");
}
if (addBackdrop) {
addBackdrop.addEventListener("click", closeAddPanels);
}
document.addEventListener("keydown", function (e) {
if (e.key === "Escape") closeAddPanels();
});
// 返回顶部点击事件 + 双击首页刷新
var homeClickTimer = null;
tabbar.addEventListener("click", function (e) {
var addBtn = e.target.closest('.tabbar-item[data-type="add"]');
if (addBtn) {
e.preventDefault();
openAddPanel(addBtn.getAttribute("data-panel-id"));
return;
}
var searchBtn = e.target.closest('.tabbar-item[data-type="search"]');
if (searchBtn) {
e.preventDefault();
if (typeof window.__aksrtOpenSearchPanel === "function") {
window.__aksrtOpenSearchPanel();
}
return;
}
var ontopBtn = e.target.closest(".tabbar-ontop-btn");
if (ontopBtn) {
e.preventDefault();
window.scrollTo({ top: 0, behavior: "smooth" });
return;
}
// 双击首页Tab刷新页面
var homeBtn = e.target.closest('.tabbar-item[data-type="home"]');
if (homeBtn && tabbar.classList.contains("has-animation")) {
if (homeClickTimer) {
clearTimeout(homeClickTimer);
homeClickTimer = null;
window.location.reload();
return;
}
homeClickTimer = setTimeout(function () {
homeClickTimer = null;
}, 350);
}
});
document.addEventListener("click", function (e) {
if (e.target.closest("[data-close-tabbar-add-panel]")) {
closeAddPanels();
}
});
}
window.AksrtFrontend.register("aksrt-footer-tabbar", function () { initFooterTabbar(); });
})();
;
/* source: assets/js/core/misc.js */
/**
* AKSRT Misc — related swiper, slide covers, hidden content
*/
(function () {
"use strict";
function renderGradientBackground(container) {
if (!container || container.dataset.aksrtGradientState || typeof window.Grade !== "function") return;
var source = container.querySelector("img");
if (!source || (!source.classList.contains("lazyloaded") && !(source.complete && source.naturalWidth > 0))) return;
container.dataset.aksrtGradientState = "loading";
window.Grade(container, null, function (result) {
var gradientData = result && result[0];
var colors = gradientData && gradientData.gradientData;
if (!gradientData || !colors || !colors[0] || !colors[1]) {
delete container.dataset.aksrtGradientState;
return;
}
var opacity = container.getAttribute("data-opacity");
var colorName = opacity ? "rgba" : "rgb";
var alpha = opacity ? "," + opacity : "";
var color1 = colorName + "(" + colors[0].rgba.slice(0, 3).join(",") + alpha + ") 10%";
var color2 = colorName + "(" + colors[1].rgba.slice(0, 3).join(",") + alpha + ") 80%";
gradientData.element.classList.add("gradient-bg-loaded");
gradientData.element.style.backgroundImage = "linear-gradient(135deg, " + color1 + ", " + color2 + ")";
container.dataset.aksrtGradientState = "loaded";
});
}
function initGradientBackgrounds(root) {
var scope = root || document;
if (scope.nodeType === 1 && scope.matches(".gradient-bg")) {
renderGradientBackground(scope);
}
scope.querySelectorAll(".gradient-bg").forEach(renderGradientBackground);
}
document.addEventListener("lazyloaded", function (event) {
if (!(event.target instanceof Element) || !event.target.matches(".gradient-bg img")) return;
renderGradientBackground(event.target.closest(".gradient-bg"));
}, true);
function initPostThumbMedia(root) {
var scope = root || document;
scope.querySelectorAll("[data-post-thumb-video]").forEach(function (box) {
if (box.dataset.aksrtThumbVideoBound === "1") return;
box.dataset.aksrtThumbVideoBound = "1";
var video = box.querySelector("video");
if (!video) return;
function loadVideo() {
if (!video.getAttribute("src") && video.dataset.src) {
video.setAttribute("src", video.dataset.src);
video.load();
}
}
function playVideo() {
loadVideo();
var result = video.play();
var postItem = box.closest(".posts-item");
if (postItem) postItem.classList.add("thumb-dplayer-playing");
if (result && typeof result.catch === "function") result.catch(function () {});
}
function pauseVideo() {
video.pause();
var postItem = box.closest(".posts-item");
if (postItem) postItem.classList.remove("thumb-dplayer-playing");
}
var item = box.closest(".item-thumbnail") || box;
item.addEventListener("mouseenter", playVideo);
item.addEventListener("mouseleave", pauseVideo);
item.addEventListener("focusin", playVideo);
item.addEventListener("focusout", pauseVideo);
});
}
window.AksrtFrontend.register("aksrt-post-thumb-media", function (root) {
initPostThumbMedia(root);
initGradientBackgrounds(root);
});
})();
(function () {
"use strict";
function initSlideCovers() {
var covers = document.querySelectorAll(".single-cover--slide");
covers.forEach(function (cover) {
var slides = cover.querySelectorAll(".single-cover__slide");
var dots = cover.querySelectorAll(".single-cover__dot");
if (slides.length < 2) return;
var current = 0;
var interval = 5000;
function goTo(idx) {
slides[current].classList.remove("is-active");
if (dots[current]) dots[current].classList.remove("is-active");
current = idx;
slides[current].classList.add("is-active");
if (dots[current]) dots[current].classList.add("is-active");
}
var timer = setInterval(function () {
goTo((current + 1) % slides.length);
}, interval);
dots.forEach(function (dot, i) {
dot.addEventListener("click", function () {
clearInterval(timer);
goTo(i);
timer = setInterval(function () {
goTo((current + 1) % slides.length);
}, interval);
});
});
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initSlideCovers);
} else {
initSlideCovers();
}
})();
(function () {
"use strict";
function initHiddenContent() {
document.addEventListener("click", function (e) {
var btn = e.target.closest(".aksrt-hidden-content__submit-btn");
if (!btn) return;
e.preventDefault();
var wrapper = btn.closest(".aksrt-hidden-content--locked");
if (!wrapper) return;
var mode = wrapper.getAttribute("data-aksrt-visibility-mode");
var pid = wrapper.getAttribute("data-aksrt-visibility-pid");
var input = wrapper.querySelector(".aksrt-hidden-content__password-input");
var password = input ? input.value.trim() : "";
if (mode === "password" && !password) {
if (input) input.focus();
return;
}
var formData = new FormData();
formData.append("action", "aksrt_check_visibility");
formData.append("_nonce", (window.aksrtData && aksrtData.visibilityNonce) || "");
formData.append("mode", mode);
formData.append("pid", pid);
if (password) formData.append("password", password);
btn.disabled = true;
btn.textContent = "...";
fetch((window.aksrtData && aksrtData.ajaxUrl) || "/wp-admin/admin-ajax.php", {
method: "POST",
body: formData,
credentials: "same-origin",
})
.then(function (res) { return res.json(); })
.then(function (data) {
if (data.success) {
window.location.reload();
} else {
var msg = (data.data && data.data.message) ? data.data.message : "验证失败";
alert(msg);
btn.disabled = false;
btn.textContent = "验证";
}
})
.catch(function () {
alert("网络错误,请重试");
btn.disabled = false;
btn.textContent = "验证";
});
});
// Handle Enter key on password input
document.addEventListener("keydown", function (e) {
if (e.key === "Enter" && e.target.classList.contains("aksrt-hidden-content__password-input")) {
e.preventDefault();
var wrapper = e.target.closest(".aksrt-hidden-content--locked");
if (wrapper) {
var btn = wrapper.querySelector(".aksrt-hidden-content__submit-btn");
if (btn) btn.click();
}
}
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initHiddenContent);
} else {
initHiddenContent();
}
})();
;
/* source: assets/js/follow.js */
/**
* AKSRT Follow — 前台关注交互
*
* 监听 [data-aksrt-follow] 按钮点击,切换关注状态。
*
* @package AKSRT
*/
(function () {
'use strict';
var API_ROOT = (function () {
var d = window.aksrtData || {};
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
return String(raw).replace(/\/$/, '');
})();
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
// 初始关注状态由服务端渲染(is-following class + 文案),无需逐按钮查询。
function initFollowButtons() {
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-aksrt-follow]');
if (!btn) return;
e.preventDefault();
var targetUserId = parseInt(btn.getAttribute('data-aksrt-follow'));
if (!targetUserId) return;
btn.disabled = true;
fetch(API_ROOT + '/follow/toggle', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': NONCE,
},
body: JSON.stringify({ target_user_id: targetUserId }),
})
.then(function (res) { return res.json(); })
.then(function (result) {
if (result.success && result.data) {
var isFollowing = result.data.is_following;
updateFollowButton(btn, isFollowing, result.data.follower_count);
notyf(isFollowing ? '关注成功' : '已取消关注', 'success');
} else {
notyf(result.message || '操作失败', 'danger');
}
})
.catch(function () {
notyf('网络错误,请重试', 'danger');
})
.finally(function () {
btn.disabled = false;
});
});
}
function updateFollowButton(btn, isFollowing, count) {
var icon = btn.querySelector('i');
var text = btn.querySelector('span');
function setLabel(label) {
if (text) {
text.textContent = label;
return;
}
// Fallback when markup has bare text nodes next to the icon.
var nodes = Array.prototype.slice.call(btn.childNodes);
var textNode = nodes.filter(function (n) { return n.nodeType === 3 && n.textContent.trim() !== ''; })[0];
if (textNode) {
textNode.textContent = ' ' + label + ' ';
}
}
if (isFollowing) {
btn.classList.add('is-following');
if (icon) icon.className = 'fa fas fa-heart';
setLabel('已关注');
btn.title = '点击取消关注';
} else {
btn.classList.remove('is-following');
if (icon) icon.className = 'fa fas fa-heart';
setLabel('关注');
btn.title = '关注该用户';
}
if (!isFollowing && btn.closest('.uc-follow-management__item')) {
btn.closest('.uc-follow-management__item').remove();
}
// Update follower count display if exists
var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]');
if (countEl && count !== undefined) {
countEl.textContent = count;
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initFollowButtons);
} else {
initFollowButtons();
}
})();
;
/* source: assets/js/enhancements.js */
/**
* AKSRT Enhancements — 全局 UI 增强
*
* 包含:图片灯箱、滚动方向感知
*
* @package AKSRT
*/
(function () {
'use strict';
/* ============================================================
* 1. 图片灯箱 — zoom / thumbnails / slideshow / fullscreen /
* mousewheel / origin animation / smart download
* ============================================================ */
var lightboxEl = null;
var lightboxImg = null;
var lightboxCaption = null;
var lightboxCounter = null;
var lightboxThumbsWrap = null;
var currentGallery = [];
var currentIndex = 0;
var currentZoom = 1;
var autoplayTimer = null;
var isAutoplay = false;
var EXCLUDE_SEL = '.no-imgbox,.no-lightbox,.avatar,.img-icon,.avatar-badge,.smilie-icon,.aksrt-avatar';
var TRIGGER_SEL = '.entry-content img, .wp-posts-content img, .aksrt-lightbox-trigger img, [data-aksrt-lightbox] img, .imgbox-container img, .comment-content .box-img img, a[data-imgbox] img';
var CONTAINER_SEL = '.entry-content, .wp-posts-content, .aksrt-lightbox-trigger, [data-aksrt-lightbox], .imgbox-container, .comment-content';
var MAX_ZOOM_DESKTOP = 2;
var MAX_ZOOM_MOBILE = 3;
function lightboxFlag(key, fallback) {
var w = window._win || (window.aksrtData && window.aksrtData.imgbox) || {};
if (w[key] === undefined || w[key] === null || w[key] === '') return fallback;
return w[key] === true || w[key] === 1 || w[key] === '1';
}
function lightboxEnabled() {
return lightboxFlag('imgbox', true);
}
function resolveFullUrl(img) {
var full = img.getAttribute('data-full-url') || img.getAttribute('data-src') || '';
if (full) return full;
var src = img.src || '';
// thumbnail → original: strip -NNNxNNN suffix
return src.replace(/-\d+x\d+(\.[a-z]+)$/i, '$1');
}
function isNoLightbox(img) {
if (img.closest(EXCLUDE_SEL)) return true;
// parent anchor that does NOT point to an image
var parentA = img.closest('a');
if (parentA) {
var href = parentA.getAttribute('href') || '';
if (href && !/\.(jpe?g|png|gif|webp|avif|svg|bmp)(\?|$)/i.test(href) && !parentA.hasAttribute('data-imgbox')) {
return true;
}
}
return false;
}
function createLightbox() {
if (!lightboxEnabled()) return;
if (lightboxEl) return;
lightboxEl = document.createElement('div');
lightboxEl.className = 'aksrt-lightbox';
lightboxEl.innerHTML =
'<div class="aksrt-lightbox__toolbar">' +
'<span class="aksrt-lightbox__counter"></span>' +
'<button type="button" class="aksrt-lightbox__tool toggle-zoom" title="缩放"><i class="fa fas fa-search-plus"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-thumbs" title="缩略图"><i class="fa fas fa-th"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-play" title="幻灯片"><i class="fa fas fa-play"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-full" title="全屏"><i class="fa fas fa-expand"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool aksrt-lightbox__download" title="下载"><i class="fa fas fa-download"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool aksrt-lightbox__close" title="关闭"><i class="fa fas fa-times"></i></button>' +
'</div>' +
'<button type="button" class="aksrt-lightbox__nav aksrt-lightbox__nav--prev" aria-label="上一张"><i class="fa fas fa-chevron-left"></i></button>' +
'<div class="aksrt-lightbox__stage"><img class="aksrt-lightbox__img" src="" alt=""></div>' +
'<button type="button" class="aksrt-lightbox__nav aksrt-lightbox__nav--next" aria-label="下一张"><i class="fa fas fa-chevron-right"></i></button>' +
'<div class="aksrt-lightbox__caption"></div>' +
'<div class="aksrt-lightbox__thumbs" hidden></div>';
document.body.appendChild(lightboxEl);
lightboxImg = lightboxEl.querySelector('.aksrt-lightbox__img');
lightboxCaption = lightboxEl.querySelector('.aksrt-lightbox__caption');
lightboxCounter = lightboxEl.querySelector('.aksrt-lightbox__counter');
lightboxThumbsWrap = lightboxEl.querySelector('.aksrt-lightbox__thumbs');
// Apply window._win imgbox feature flags
var zoomBtn = lightboxEl.querySelector('.toggle-zoom');
var thumbsBtn = lightboxEl.querySelector('.toggle-thumbs');
var playBtn = lightboxEl.querySelector('.toggle-play');
var fullBtn = lightboxEl.querySelector('.toggle-full');
var downBtn = lightboxEl.querySelector('.aksrt-lightbox__download');
if (zoomBtn) zoomBtn.hidden = !lightboxFlag('imgbox_zoom', true);
if (thumbsBtn) thumbsBtn.hidden = !lightboxFlag('imgbox_thumbs', true);
if (playBtn) playBtn.hidden = !lightboxFlag('imgbox_play', false);
if (fullBtn) fullBtn.hidden = !lightboxFlag('imgbox_full', true);
if (downBtn) downBtn.hidden = !lightboxFlag('imgbox_down', false);
// Close
lightboxEl.querySelector('.aksrt-lightbox__close').addEventListener('click', function (e) { e.stopPropagation(); closeLightbox(); });
lightboxEl.addEventListener('click', function (e) { if (e.target === lightboxEl || e.target.classList.contains('aksrt-lightbox__stage')) closeLightbox(); });
// Nav
lightboxEl.querySelector('.aksrt-lightbox__nav--prev').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(-1); });
lightboxEl.querySelector('.aksrt-lightbox__nav--next').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(1); });
// Keyboard
document.addEventListener('keydown', function (e) {
if (!lightboxEl.classList.contains('is-open')) return;
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowLeft') navigateLightbox(-1);
if (e.key === 'ArrowRight') navigateLightbox(1);
});
// Mousewheel nav
lightboxEl.addEventListener('wheel', function (e) {
if (!lightboxEl.classList.contains('is-open')) return;
if (currentZoom > 1) return; // don't nav when zoomed
e.preventDefault();
if (e.deltaY > 0) navigateLightbox(1);
else navigateLightbox(-1);
}, { passive: false });
// Image click → toggle zoom
lightboxImg.addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); });
// Double-click → zoom in
lightboxImg.addEventListener('dblclick', function (e) {
e.stopPropagation();
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
if (currentZoom < maxZoom) setZoom(maxZoom);
else setZoom(1);
});
// Pinch zoom (mobile)
var pinchStart = 0;
lightboxEl.addEventListener('touchstart', function (e) {
if (e.touches.length === 2) {
pinchStart = Math.hypot(
e.touches[1].clientX - e.touches[0].clientX,
e.touches[1].clientY - e.touches[0].clientY
);
}
}, { passive: true });
lightboxEl.addEventListener('touchmove', function (e) {
if (e.touches.length === 2 && pinchStart > 0) {
var dist = Math.hypot(
e.touches[1].clientX - e.touches[0].clientX,
e.touches[1].clientY - e.touches[0].clientY
);
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
var newZoom = Math.min(maxZoom, Math.max(1, currentZoom * (dist / pinchStart)));
setZoom(newZoom);
pinchStart = dist;
}
}, { passive: true });
lightboxEl.addEventListener('touchend', function () { pinchStart = 0; }, { passive: true });
// Touch swipe/pull-dismiss (skip when zoomed)
var touchStartY = 0, touchStartX = 0, isSwiping = false;
lightboxEl.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1 || currentZoom > 1.05) return;
touchStartY = e.touches[0].clientY;
touchStartX = e.touches[0].clientX;
isSwiping = false;
}, { passive: true });
lightboxEl.addEventListener('touchmove', function (e) {
if (e.touches.length !== 1 || currentZoom > 1.05) return;
var dy = e.touches[0].clientY - touchStartY;
var dx = e.touches[0].clientX - touchStartX;
if (Math.abs(dy) > Math.abs(dx) && dy > 30) {
isSwiping = true;
var opacity = Math.max(0.2, (150 - dy) / 80);
lightboxImg.style.transform = 'translate3d(0, ' + dy + 'px, 0) scale(' + currentZoom + ')';
lightboxImg.style.opacity = String(Math.min(1, opacity));
}
}, { passive: true });
lightboxEl.addEventListener('touchend', function () {
if (isSwiping) {
closeLightbox();
lightboxImg.style.transform = '';
lightboxImg.style.opacity = '';
}
isSwiping = false;
}, { passive: true });
// Download
lightboxEl.querySelector('.aksrt-lightbox__download').addEventListener('click', function (e) {
e.stopPropagation();
var item = currentGallery[currentIndex];
if (!item || !item.src) return;
var a = document.createElement('a');
var fullSrc = item.fullSrc || item.src;
a.href = fullSrc;
a.download = fullSrc.substring(fullSrc.lastIndexOf('/') + 1) || 'image';
document.body.appendChild(a);
a.click();
a.remove();
});
// Zoom button
lightboxEl.querySelector('.toggle-zoom').addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); });
// Thumbnails toggle
lightboxEl.querySelector('.toggle-thumbs').addEventListener('click', function (e) {
e.stopPropagation();
lightboxThumbsWrap.hidden = !lightboxThumbsWrap.hidden;
});
// Slideshow toggle
lightboxEl.querySelector('.toggle-play').addEventListener('click', function (e) {
e.stopPropagation();
toggleAutoplay();
});
// Fullscreen
lightboxEl.querySelector('.toggle-full').addEventListener('click', function (e) {
e.stopPropagation();
toggleFullscreen();
});
document.addEventListener('fullscreenchange', function () {
var icon = lightboxEl.querySelector('.toggle-full i');
if (document.fullscreenElement) {
lightboxEl.classList.add('full-screen');
icon.className = 'fa fas fa-compress';
} else {
lightboxEl.classList.remove('full-screen');
icon.className = 'fa fas fa-expand';
}
});
}
function setZoom(z) {
currentZoom = z;
lightboxImg.style.transform = 'scale(' + z + ')';
lightboxEl.classList.toggle('is-zoomed', z > 1);
}
function toggleZoom() {
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
setZoom(currentZoom > 1 ? 1 : maxZoom);
}
function toggleAutoplay() {
var icon = lightboxEl.querySelector('.toggle-play i');
if (isAutoplay) {
clearInterval(autoplayTimer);
isAutoplay = false;
icon.className = 'fa fas fa-play';
} else {
if (currentGallery.length < 2) return;
isAutoplay = true;
icon.className = 'fa fas fa-pause';
autoplayTimer = setInterval(function () { navigateLightbox(1); }, 3000);
}
}
function toggleFullscreen() {
var rfs = lightboxEl.requestFullscreen || lightboxEl.webkitRequestFullscreen || lightboxEl.msRequestFullscreen;
if (document.fullscreenElement || document.webkitFullscreenElement) {
(document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document);
} else if (rfs) {
rfs.call(lightboxEl);
}
}
function buildThumbnails() {
if (!lightboxThumbsWrap) return;
lightboxThumbsWrap.innerHTML = '';
currentGallery.forEach(function (item, i) {
var thumb = document.createElement('img');
thumb.src = item.src;
thumb.className = 'aksrt-lightbox__thumb' + (i === currentIndex ? ' is-active' : '');
thumb.addEventListener('click', function (e) {
e.stopPropagation();
showLightboxImage(i);
});
lightboxThumbsWrap.appendChild(thumb);
});
}
function syncThumbActive() {
if (!lightboxThumbsWrap) return;
var thumbs = lightboxThumbsWrap.querySelectorAll('.aksrt-lightbox__thumb');
thumbs.forEach(function (t, i) { t.classList.toggle('is-active', i === currentIndex); });
var active = thumbs[currentIndex];
if (active) active.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' });
}
function openLightbox(src, caption, gallery, index, originEl) {
createLightbox();
currentGallery = gallery || [{ src: src, caption: caption || '' }];
currentIndex = index || 0;
currentZoom = 1;
// Origin animation
if (originEl) {
var rect = originEl.getBoundingClientRect();
lightboxEl.style.setProperty('--imgbox-origin-top', (rect.top + rect.height / 2) + 'px');
lightboxEl.style.setProperty('--imgbox-origin-left', (rect.left + rect.width / 2) + 'px');
lightboxEl.classList.add('from-origin');
} else {
lightboxEl.classList.remove('from-origin');
}
showLightboxImage(currentIndex);
buildThumbnails();
lightboxEl.classList.add('is-open');
document.body.style.overflow = 'hidden';
lightboxEl.dispatchEvent(new CustomEvent('aksrt:lightbox-open', { bubbles: true }));
}
function showLightboxImage(index) {
if (index < 0) index = currentGallery.length - 1;
if (index >= currentGallery.length) index = 0;
currentIndex = index;
setZoom(1);
var item = currentGallery[currentIndex];
lightboxImg.src = item.fullSrc || item.src;
lightboxImg.alt = item.caption || '';
lightboxCaption.textContent = item.caption || '';
lightboxCaption.style.display = item.caption ? '' : 'none';
if (currentGallery.length > 1) {
lightboxCounter.textContent = (currentIndex + 1) + ' / ' + currentGallery.length;
lightboxCounter.style.display = '';
} else {
lightboxCounter.style.display = 'none';
}
syncThumbActive();
}
function navigateLightbox(dir) {
showLightboxImage(currentIndex + dir);
}
function closeLightbox() {
if (!lightboxEl) return;
if (isAutoplay) toggleAutoplay();
lightboxEl.classList.remove('is-open', 'is-zoomed', 'from-origin');
document.body.style.overflow = '';
lightboxThumbsWrap.hidden = true;
if (document.fullscreenElement) {
(document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document);
}
}
// Auto-bind: expanded trigger scope + exclusion rules
function initLightbox() {
document.addEventListener('click', function (e) {
// Also support a[data-imgbox]
var anchor = e.target.closest('a[data-imgbox]');
if (anchor) {
e.preventDefault();
var href = anchor.getAttribute('href') || anchor.getAttribute('data-imgbox');
openLightbox(href, '', [{ src: href, fullSrc: href, caption: '' }], 0, anchor);
return;
}
var img = e.target.closest(TRIGGER_SEL);
if (!img) return;
if (img.width < 100 || img.height < 100) return;
if (!lightboxEnabled() || isNoLightbox(img)) return;
e.preventDefault();
var container = img.closest(CONTAINER_SEL);
if (!container) return;
var allImages = container.querySelectorAll('img');
var gallery = [];
var foundIndex = 0;
allImages.forEach(function (el) {
if (el.width < 100 || el.height < 100) return;
if (isNoLightbox(el)) return;
var full = resolveFullUrl(el);
var caption = el.alt || '';
var figcaption = el.closest('figure') ? el.closest('figure').querySelector('figcaption') : null;
if (figcaption) caption = figcaption.textContent;
gallery.push({ src: el.dataset.src || el.src, fullSrc: full, caption: caption });
if (el === img) foundIndex = gallery.length - 1;
});
if (gallery.length > 0) {
openLightbox(img.dataset.src || img.src, img.alt, gallery, foundIndex, img);
}
});
}
// 暴露到全局
window.AksrtLightbox = {
open: openLightbox,
close: closeLightbox,
};
/* ============================================================
* 2. 滚动方向感知
* ============================================================ */
// Scroll direction is now handled by aksrt-runtime.js body scroll bus
// (body.scroll-down / body.scroll-up / body.body-scroll).
// This stub syncs the legacy aksrt-* prefixed classes for backward compat.
function initScrollDirection() {
var body = document.body;
function sync() {
body.classList.toggle('aksrt-body-scroll', body.classList.contains('body-scroll'));
body.classList.toggle('aksrt-scroll-down', body.classList.contains('scroll-down'));
body.classList.toggle('aksrt-scroll-up', body.classList.contains('scroll-up'));
body.classList.add('aksrt-scrolling');
clearTimeout(body._aksrtScrollTimer);
body._aksrtScrollTimer = setTimeout(function () {
body.classList.remove('aksrt-scrolling');
}, 300);
}
function onScroll() {
requestAnimationFrame(sync);
}
window.addEventListener('scroll', onScroll, { passive: true });
}
/* ============================================================
* 3. 导航溢出折叠
* 由 legacy main.js 的 navFolding 负责。
* 此处不再重复实现,避免两套逻辑争抢 .aksrt-nav-more 节点
* 触发 NotFoundError: insertBefore (moreLi 已被对方 remove)。
* ============================================================ */
/* ============================================================
* 初始化入口
* ============================================================ */
document.addEventListener('DOMContentLoaded', function () {
initLightbox();
initScrollDirection();
});
})();
;
/* source: assets/js/enhancements2.js */
/**
* AKSRT Enhancements Part 2 — 高级交互组件
*
* 包含:3D 悬停倾斜、滚动触发动画、封面轮播、打赏按钮
*
* @package AKSRT
*/
(function () {
'use strict';
/* ============================================================
* 1. 3D 悬停倾斜效果 (vanilla-tilt 轻量版)
* ============================================================ */
function initTilt() {
document.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) {
if (el.dataset.tiltInit) return;
el.dataset.tiltInit = '1';
var maxTilt = parseInt(el.dataset.aksrtTiltMax || '15', 10);
var perspective = parseInt(el.dataset.aksrtTiltPerspective || '1000', 10);
var scale = parseFloat(el.dataset.aksrtTiltScale || '1.03');
var speed = parseInt(el.dataset.aksrtTiltSpeed || '400', 10);
var glare = el.dataset.aksrtTiltGlare === '1';
el.style.transformStyle = 'preserve-3d';
el.style.transition = 'transform ' + speed + 'ms ease';
var glareEl = null;
if (glare) {
glareEl = document.createElement('div');
glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;opacity:0;transition:opacity ' + speed + 'ms;';
el.style.position = el.style.position || 'relative';
el.style.overflow = 'hidden';
el.appendChild(glareEl);
}
el.addEventListener('mousemove', function (e) {
var rect = el.getBoundingClientRect();
var x = (e.clientX - rect.left) / rect.width;
var y = (e.clientY - rect.top) / rect.height;
var tiltX = (0.5 - y) * maxTilt;
var tiltY = (x - 0.5) * maxTilt;
el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + tiltX + 'deg) rotateY(' + tiltY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')';
if (glareEl) {
glareEl.style.opacity = '1';
glareEl.style.background = 'linear-gradient(135deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.2) 50%, rgba(255,255,255,0) 100%)';
glareEl.style.transform = 'translate(' + ((x - 0.5) * 100) + '%, ' + ((y - 0.5) * 100) + '%)';
}
});
el.addEventListener('mouseleave', function () {
el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)';
if (glareEl) glareEl.style.opacity = '0';
});
});
}
/* ============================================================
* 2. 滚动触发动画 (IntersectionObserver)
* ============================================================ */
function initScrollAnimations() {
if (!('IntersectionObserver' in window)) {
// 不支持时直接显示所有元素
document.querySelectorAll('[data-aksrt-animate]').forEach(function (el) {
el.style.opacity = '1';
el.style.transform = 'none';
});
return;
}
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
var animation = el.dataset.aksrtAnimate || 'fadeInUp';
var delay = parseInt(el.dataset.aksrtAnimateDelay || '0', 10);
var duration = parseInt(el.dataset.aksrtAnimateDuration || '600', 10);
setTimeout(function () {
el.style.transition = 'opacity ' + duration + 'ms ease, transform ' + duration + 'ms ease';
el.style.opacity = '1';
el.style.transform = 'translateY(0) translateX(0) scale(1)';
el.classList.add('aksrt-animated');
}, delay);
observer.unobserve(el);
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px',
});
document.querySelectorAll('[data-aksrt-animate]').forEach(function (el) {
var animation = el.dataset.aksrtAnimate || 'fadeInUp';
// 设置初始状态
el.style.opacity = '0';
switch (animation) {
case 'fadeIn':
el.style.transform = 'none';
break;
case 'fadeInUp':
el.style.transform = 'translateY(30px)';
break;
case 'fadeInDown':
el.style.transform = 'translateY(-30px)';
break;
case 'fadeInLeft':
el.style.transform = 'translateX(-30px)';
break;
case 'fadeInRight':
el.style.transform = 'translateX(30px)';
break;
case 'zoomIn':
el.style.transform = 'scale(0.9)';
break;
default:
el.style.transform = 'translateY(30px)';
}
observer.observe(el);
});
}
/* ============================================================
* 5. 粘性元素 (Sticky)
* ============================================================ */
function initSticky() {
document.querySelectorAll('[data-aksrt-sticky]').forEach(function (el) {
var top = parseInt(el.dataset.aksrtStickyTop || '80', 10);
var offset = parseInt(el.dataset.aksrtStickyOffset || '0', 10);
function update() {
var rect = el.getBoundingClientRect();
var parentRect = el.parentElement.getBoundingClientRect();
if (parentRect.top < top && parentRect.bottom > el.offsetHeight + top) {
el.style.position = 'sticky';
el.style.top = top + 'px';
}
}
window.addEventListener('scroll', update, { passive: true });
});
}
/* ============================================================
* 6. 视差滚动
* ============================================================ */
function initParallax() {
var elements = document.querySelectorAll('[data-aksrt-parallax]');
if (elements.length === 0) return;
var ticking = false;
function update() {
var scrollTop = window.pageYOffset;
elements.forEach(function (el) {
var speed = parseFloat(el.dataset.aksrtParallax || '0.3');
var rect = el.getBoundingClientRect();
var center = rect.top + rect.height / 2;
var windowCenter = window.innerHeight / 2;
var offset = (center - windowCenter) * speed;
el.style.transform = 'translateY(' + offset + 'px)';
});
ticking = false;
}
window.addEventListener('scroll', function () {
if (!ticking) {
requestAnimationFrame(update);
ticking = true;
}
}, { passive: true });
}
/* ============================================================
* 7. 涟漪点击效果
* ============================================================ */
function initRipple() {
document.addEventListener('click', function (e) {
var el = e.target.closest('.aksrt-ripple');
if (!el) return;
var rect = el.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
var size = Math.max(rect.width, rect.height) * 2;
var wave = document.createElement('span');
wave.className = 'aksrt-ripple__wave';
wave.style.width = wave.style.height = size + 'px';
wave.style.left = (x - size / 2) + 'px';
wave.style.top = (y - size / 2) + 'px';
el.appendChild(wave);
wave.addEventListener('animationend', function () {
wave.remove();
});
});
}
/* ============================================================
* 8. 表单浮动标签自动初始化
* ============================================================ */
function initFloatLabels() {
document.querySelectorAll('.aksrt-float-label__input').forEach(function (input) {
// 检查初始值
if (input.value) {
input.setAttribute('data-has-value', '1');
}
input.addEventListener('input', function () {
if (this.value) {
this.setAttribute('data-has-value', '1');
} else {
this.removeAttribute('data-has-value');
}
});
});
}
/* ============================================================
* 8. 签到日历可视化
* ============================================================ */
function initSigninCalendar() {
var calendar = document.querySelector('[data-aksrt-signin-calendar]');
if (!calendar) return;
var data = [];
try {
data = JSON.parse(calendar.dataset.aksrtSigninCalendar || '[]');
} catch (e) {}
// 生成最近 30 天的格子
var today = new Date();
var html = '<div class="aksrt-signin-calendar__grid">';
for (var i = 29; i >= 0; i--) {
var d = new Date(today);
d.setDate(d.getDate() - i);
var dateStr = d.toISOString().split('T')[0];
var isSigned = data.indexOf(dateStr) !== -1;
var isToday = i === 0;
html += '<div class="aksrt-signin-calendar__day' +
(isSigned ? ' is-signed' : '') +
(isToday ? ' is-today' : '') +
'" title="' + dateStr + (isSigned ? ' ✓' : '') + '">' +
'<span>' + d.getDate() + '</span>' +
'</div>';
}
html += '</div>';
calendar.innerHTML = html;
}
/* ============================================================
* 初始化入口
* ============================================================ */
document.addEventListener('DOMContentLoaded', function () {
initTilt();
initScrollAnimations();
initRipple();
initSticky();
initParallax();
initFloatLabels();
initSigninCalendar();
document.addEventListener('aksrt:modal:open', function () {
// Remote modal may inject calendar markup after open.
window.setTimeout(initSigninCalendar, 30);
});
});
// 暴露到全局供动态内容使用
window.AksrtEnhancements = {
initTilt: initTilt,
initScrollAnimations: initScrollAnimations,
initRipple: initRipple,
};
})();
;
/* source: assets/js/color-extract.js */
/**
* AKSRT Color Extract
*
* Extracts dominant colors from images and applies them as dynamic accents.
* Uses canvas-based color sampling — no external dependencies.
*
* Usage:
* <img data-aksrt-color-extract data-aksrt-color-target=".card">
* <!-- Will set --aksrt-dynamic-color on .card based on image colors -->
*
* @package AKSRT
*/
(function () {
'use strict';
var canvas = null;
var ctx = null;
function getCanvas() {
if (!canvas) {
canvas = document.createElement('canvas');
canvas.width = 40;
canvas.height = 40;
ctx = canvas.getContext('2d', { willReadFrequently: true });
}
return { canvas: canvas, ctx: ctx };
}
function extractColor(img) {
try {
var c = getCanvas();
c.ctx.drawImage(img, 0, 0, 40, 40);
var data = c.ctx.getImageData(0, 0, 40, 40).data;
var r = 0, g = 0, b = 0, count = 0;
var step = 4; // sample every 4th pixel for speed
for (var i = 0; i < data.length; i += 4 * step) {
var pr = data[i], pg = data[i + 1], pb = data[i + 2];
// Skip very dark or very light pixels
var brightness = (pr + pg + pb) / 3;
if (brightness < 20 || brightness > 240) continue;
// Skip near-transparent
if (data[i + 3] < 128) continue;
r += pr;
g += pg;
b += pb;
count++;
}
if (count === 0) return null;
r = Math.round(r / count);
g = Math.round(g / count);
b = Math.round(b / count);
return { r: r, g: g, b: b, hex: rgbToHex(r, g, b) };
} catch (e) {
// CORS or tainted canvas — silently fail
return null;
}
}
function rgbToHex(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function lighten(r, g, b, amount) {
return rgbToHex(
Math.min(255, r + amount),
Math.min(255, g + amount),
Math.min(255, b + amount)
);
}
function darken(r, g, b, amount) {
return rgbToHex(
Math.max(0, r - amount),
Math.max(0, g - amount),
Math.max(0, b - amount)
);
}
function applyColor(target, color) {
if (!target || !color) return;
var r = color.r, g = color.g, b = color.b;
target.style.setProperty('--aksrt-dynamic-color', color.hex);
target.style.setProperty('--aksrt-dynamic-color-rgb', r + ',' + g + ',' + b);
target.style.setProperty('--aksrt-dynamic-color-light', lighten(r, g, b, 60));
target.style.setProperty('--aksrt-dynamic-color-dark', darken(r, g, b, 40));
// Add a subtle tinted background if target doesn't already have one
if (!target.style.background && !target.style.backgroundColor) {
target.style.setProperty('--aksrt-dynamic-bg', 'rgba(' + r + ',' + g + ',' + b + ',0.05)');
}
}
function processImage(img) {
if (img.dataset.aksrtColorDone) return;
// Need crossOrigin for canvas access
if (!img.crossOrigin && img.src.indexOf(window.location.origin) !== 0) {
img.crossOrigin = 'anonymous';
}
function extract() {
var color = extractColor(img);
if (!color) return;
img.dataset.aksrtColorDone = '1';
var targetSelector = img.dataset.aksrtColorTarget;
var target = targetSelector
? img.closest(targetSelector) || document.querySelector(targetSelector)
: img.parentElement;
applyColor(target, color);
}
if (img.complete && img.naturalWidth > 0) {
extract();
} else {
img.addEventListener('load', extract, { once: true });
}
}
function init() {
document.querySelectorAll('[data-aksrt-color-extract]').forEach(processImage);
}
// Public API
window.AksrtColorExtract = {
extract: extractColor,
apply: applyColor,
process: processImage,
init: init
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
;
/* source: assets/js/effects.js */
/**
* AKSRT Effects — count-up / countdown / marquee / 3D tilt / FPS detection
*
* 全部渐进增强,尊重 prefers-reduced-motion。
*
* @package AKSRT
*/
(function () {
'use strict';
var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
/* ============================================================
* 1. 数字递增 (count-up) — IntersectionObserver 触发
* 用法:<span data-aksrt-countup data-target="1234" data-duration="2000">0</span>
* ============================================================ */
function initCountUp() {
if (reducedMotion) return;
if (!('IntersectionObserver' in window)) return;
var els = document.querySelectorAll('[data-aksrt-countup]');
if (!els.length) return;
var observer = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
var el = entry.target;
observer.unobserve(el);
var target = parseFloat(el.dataset.target || '0');
var duration = parseInt(el.dataset.duration || '2000', 10);
var decimals = (el.dataset.decimals || '0|').split('|');
var prefix = decimals[1] || '';
var dec = parseInt(decimals[0], 10) || 0;
var start = 0;
var startTime = null;
function step(timestamp) {
if (!startTime) startTime = timestamp;
var progress = Math.min((timestamp - startTime) / duration, 1);
// ease-out cubic
var eased = 1 - Math.pow(1 - progress, 3);
var current = start + (target - start) * eased;
el.textContent = prefix + current.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
if (progress < 1) {
requestAnimationFrame(step);
} else {
el.textContent = prefix + target.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
requestAnimationFrame(step);
});
}, { threshold: 0.3 });
els.forEach(function (el) { observer.observe(el); });
}
/* ============================================================
* 2. 倒计时组件
* 用法:<span data-aksrt-countdown data-end="2026-12-31T23:59:59">--:--:--</span>
* ============================================================ */
function initCountdown() {
var els = document.querySelectorAll('[data-aksrt-countdown]');
if (!els.length) return;
function pad(n) { return n < 10 ? '0' + n : '' + n; }
function update() {
var now = Date.now();
els.forEach(function (el) {
var end = new Date(el.dataset.end).getTime();
var diff = end - now;
if (diff <= 0) {
el.textContent = el.dataset.expired || '已结束';
return;
}
var d = Math.floor(diff / 86400000);
var h = Math.floor((diff % 86400000) / 3600000);
var m = Math.floor((diff % 3600000) / 60000);
var s = Math.floor((diff % 60000) / 1000);
var format = el.dataset.format || 'dhms';
if (format === 'dhms') {
el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + ':' + pad(m) + ':' + pad(s);
} else if (format === 'hms') {
el.textContent = pad(h + d * 24) + ':' + pad(m) + ':' + pad(s);
} else {
el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + '时' + pad(m) + '分' + pad(s) + '秒';
}
});
}
update();
setInterval(update, 1000);
}
/* ============================================================
* 4. 3D 悬停倾斜 (vanilla tilt)
* 用法:<div data-aksrt-tilt data-tilt-max="15">...</div>
* ============================================================ */
function initTilt() {
if (reducedMotion) return;
document.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) {
if (el.dataset.tiltBound) return;
el.dataset.tiltBound = '1';
var maxDeg = parseFloat(el.dataset.tiltMax || '10');
var perspective = parseFloat(el.dataset.tiltPerspective || '800');
var scale = parseFloat(el.dataset.tiltScale || '1.03');
var glare = el.dataset.tiltGlare === '1';
var glareEl = null;
if (glare) {
glareEl = document.createElement('div');
glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,0) 0%,rgba(255,255,255,0.25) 100%);opacity:0;transition:opacity 0.2s;';
el.style.position = el.style.position || 'relative';
el.style.overflow = 'hidden';
el.appendChild(glareEl);
}
el.addEventListener('mousemove', function (e) {
var rect = el.getBoundingClientRect();
var x = (e.clientX - rect.left) / rect.width;
var y = (e.clientY - rect.top) / rect.height;
var rotateX = (0.5 - y) * maxDeg;
var rotateY = (x - 0.5) * maxDeg;
el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + rotateX + 'deg) rotateY(' + rotateY + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')';
if (glareEl) glareEl.style.opacity = '1';
});
el.addEventListener('mouseleave', function () {
el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)';
if (glareEl) glareEl.style.opacity = '0';
});
});
}
/* ============================================================
* 5. FPS 检测 + 低端设备降级
* ============================================================ */
function initFpsDetection() {
if (!('requestAnimationFrame' in window)) return;
var frames = 0;
var startTime = performance.now();
function countFrames() {
frames++;
var elapsed = performance.now() - startTime;
if (elapsed >= 1000) {
var fps = Math.round(frames * 1000 / elapsed);
if (fps < 24) {
document.body.classList.add('fps-accelerat');
}
return; // 只检测一次
}
requestAnimationFrame(countFrames);
}
requestAnimationFrame(countFrames);
}
/* ============================================================
* 初始化入口
* ============================================================ */
document.addEventListener('DOMContentLoaded', function () {
initCountUp();
initCountdown();
if (typeof initMarquee === 'function') {
initMarquee();
}
initTilt();
initFpsDetection();
});
})();
;