5445 lines
191 KiB
JavaScript
5445 lines
191 KiB
JavaScript
/* 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, '<')
|
||
.replace(/>/g, '>');
|
||
}
|
||
|
||
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, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ── 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, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/\"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
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="关闭">×</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();
|
||
});
|
||
|
||
})();
|
||
;
|