refactor(frontend): replace toast system with zibll notyf #7

Merged
kate522 merged 1 commits from worktree/replace-frontend-toast into main 2026-08-17 18:39:14 +00:00
33 changed files with 603 additions and 802 deletions
+1
View File
@@ -17,6 +17,7 @@
/tests/widget-system/*
!/tests/widget-system/post-visual-contract.test.cjs
!/tests/widget-system/lazyload-contract.test.cjs
!/tests/widget-system/toast-contract.test.cjs
!/tests/visual/docker-compose.zibll.yml
!/tests/visual/zibll-Caddyfile
!/tests/visual/README.md
+5 -116
View File
@@ -3556,105 +3556,17 @@ html.dark .aksrt-card-submenu__title {
/**
* AKSRT Enhancements — 全局 UI 增强
*
* 包含:Toast 通知、骨架屏、悬停动效、暗色过渡、滚动感知
* 包含:骨架屏、悬停动效、暗色过渡、滚动感知
* 所有选择器使用 aksrt- 前缀避免冲突
*/
/* ============================================================
* 1. Toast 通知系统
* ============================================================ */
.aksrt-toast-container {
position: fixed;
bottom: 80px;
right: 16px;
max-width: 360px;
z-index: 10000;
display: flex;
flex-direction: column-reverse;
gap: 8px;
pointer-events: none;
}
.aksrt-toast {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-radius: var(--main-radius, 8px);
color: #fff;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
backdrop-filter: saturate(5) blur(20px);
-webkit-backdrop-filter: saturate(5) blur(20px);
transform: translateX(120%);
opacity: 0;
animation: aksrt-toast-in 0.35s cubic-bezier(0.35, 0.71, 0.46, 1.08) forwards;
cursor: pointer;
transition: transform 0.3s, opacity 0.3s;
}
.aksrt-toast.is-dismissing {
transform: translateX(120%);
opacity: 0;
transition: transform 0.3s ease-in, opacity 0.3s ease-in;
}
.aksrt-toast__icon {
font-size: 18px;
flex-shrink: 0;
}
.aksrt-toast__text {
flex: 1;
min-width: 0;
line-height: 1.4;
}
/* Toast 类型 — 对齐 zibll 纯色 */
.aksrt-toast--success {
background: rgba(16, 185, 129, 0.9);
}
.aksrt-toast--info {
background: rgba(59, 130, 246, 0.9);
}
.aksrt-toast--warning {
background: rgba(245, 158, 11, 0.9);
}
.aksrt-toast--danger,
.aksrt-toast--error {
background: rgba(239, 68, 68, 0.9);
}
.aksrt-toast--loading {
background: rgba(107, 114, 128, 0.9);
}
.aksrt-toast--loading .aksrt-toast__icon {
animation: aksrt-spin 1s linear infinite;
}
@keyframes aksrt-toast-in {
from {
transform: translateX(120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes aksrt-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ============================================================
* 2. 骨架屏加载动画
* 1. 骨架屏加载动画
* ============================================================ */
.aksrt-skeleton {
background: linear-gradient(
@@ -3726,7 +3638,7 @@ html.dark .aksrt-card-submenu__title {
}
/* ============================================================
* 3. 悬停交互动效
* 2. 悬停交互动效
* ============================================================ */
/* 卡片放大 */
@@ -3805,7 +3717,7 @@ html.dark .aksrt-card-submenu__title {
}
/* ============================================================
* 4. 暗色模式过渡 (View Transitions API)
* 3. 暗色模式过渡 (View Transitions API)
* ============================================================ */
::view-transition-old(root),
::view-transition-new(root) {
@@ -3839,14 +3751,10 @@ html.dark .aksrt-card-submenu__title {
--aksrt-blur-bg: var(--surface-1);
}
.aksrt-toast {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
/* ============================================================
* 5. 滚动方向感知
* 4. 滚动方向感知
* ============================================================ */
/* 向下滚动时隐藏导航栏 */
@@ -5231,12 +5139,6 @@ html.dark .aksrt-lang-switch__dropdown {
* 响应式
* ============================================================ */
@media (max-width: 640px) {
.aksrt-toast-container {
right: 8px;
left: 8px;
max-width: none;
}
.aksrt-lightbox__nav {
width: 36px;
height: 36px;
@@ -5287,10 +5189,6 @@ html.dark .aksrt-lang-switch__dropdown {
animation: none !important;
}
.aksrt-toast {
animation: none !important;
}
[data-aksrt-animate] {
opacity: 1 !important;
transform: none !important;
@@ -5449,15 +5347,6 @@ input:focus, textarea:focus, select:focus,
transform-origin: top;
}
/* --- Toast 通知动效 --- */
.aksrt-toast {
animation: aksrtToastIn 0.35s var(--ease-out) forwards;
}
.aksrt-toast--out {
animation: aksrtToastOut 0.3s var(--ease) forwards;
}
/* --- 骨架屏动效 --- */
.aksrt-skeleton {
animation: aksrtSkeletonShimmer 1.5s infinite linear;
+6 -98
View File
@@ -1,87 +1,10 @@
/**
* AKSRT Enhancements — 全局 UI 增强
*
* 包含:Toast 通知、骨架屏、悬停动效、暗色过渡、滚动感知
* 包含:骨架屏、悬停动效、暗色过渡、滚动感知
* 所有选择器使用 aksrt- 前缀避免冲突
*/
/* ============================================================
* 1. Toast 通知系统
* ============================================================ */
.aksrt-toast-container {
position: fixed;
bottom: 80px;
right: 16px;
max-width: 360px;
z-index: 10000;
display: flex;
flex-direction: column-reverse;
gap: 8px;
pointer-events: none;
}
.aksrt-toast {
pointer-events: auto;
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-radius: var(--main-radius, 8px);
color: #fff;
font-size: 14px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
backdrop-filter: saturate(5) blur(20px);
-webkit-backdrop-filter: saturate(5) blur(20px);
transform: translateX(120%);
opacity: 0;
animation: aksrt-toast-in 0.35s cubic-bezier(0.35, 0.71, 0.46, 1.08) forwards;
cursor: pointer;
transition: transform 0.3s, opacity 0.3s;
}
.aksrt-toast.is-dismissing {
transform: translateX(120%);
opacity: 0;
transition: transform 0.3s ease-in, opacity 0.3s ease-in;
}
.aksrt-toast__icon {
font-size: 18px;
flex-shrink: 0;
}
.aksrt-toast__text {
flex: 1;
min-width: 0;
line-height: 1.4;
}
/* Toast 类型 — 对齐 zibll 纯色 */
.aksrt-toast--success {
background: rgba(16, 185, 129, 0.9);
}
.aksrt-toast--info {
background: rgba(59, 130, 246, 0.9);
}
.aksrt-toast--warning {
background: rgba(245, 158, 11, 0.9);
}
.aksrt-toast--danger,
.aksrt-toast--error {
background: rgba(239, 68, 68, 0.9);
}
.aksrt-toast--loading {
background: rgba(107, 114, 128, 0.9);
}
.aksrt-toast--loading .aksrt-toast__icon {
animation: aksrt-spin 1s linear infinite;
}
.aksrt-modal-auth__error.is-success {
background: rgba(16, 185, 129, 0.08);
border-color: rgba(16, 185, 129, 0.22);
@@ -103,24 +26,13 @@
line-height: 1.6;
}
@keyframes aksrt-toast-in {
from {
transform: translateX(120%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes aksrt-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ============================================================
* 2. 骨架屏加载动画
* 1. 骨架屏加载动画
* ============================================================ */
.aksrt-skeleton {
background: linear-gradient(
@@ -192,7 +104,7 @@
}
/* ============================================================
* 3. 悬停交互动效
* 2. 悬停交互动效
* ============================================================ */
/* 卡片放大 */
@@ -271,7 +183,7 @@
}
/* ============================================================
* 4. 暗色模式过渡 (View Transitions API)
* 3. 暗色模式过渡 (View Transitions API)
* ============================================================ */
::view-transition-old(root),
::view-transition-new(root) {
@@ -307,14 +219,10 @@ body.dark-theme::view-transition-new(root) {
--aksrt-blur-bg: var(--surface-1);
}
.aksrt-toast {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
}
/* ============================================================
* 5. 滚动方向感知
* 4. 滚动方向感知
* ============================================================ */
/* 向下滚动时隐藏导航栏 */
@@ -348,7 +256,7 @@ body.aksrt-body-scroll .aksrt-show-on-scroll {
}
/* ============================================================
* 6. 图片灯箱
* 5. 图片灯箱
* ============================================================ */
.aksrt-lightbox {
position: fixed;
@@ -3,12 +3,6 @@
* 响应式
* ============================================================ */
@media (max-width: 640px) {
.aksrt-toast-container {
right: 8px;
left: 8px;
max-width: none;
}
.aksrt-lightbox__nav {
width: 36px;
height: 36px;
@@ -59,10 +53,6 @@
animation: none !important;
}
.aksrt-toast {
animation: none !important;
}
[data-aksrt-animate] {
opacity: 1 !important;
transform: none !important;
@@ -230,15 +220,6 @@ input:focus, textarea:focus, select:focus,
transform-origin: top;
}
/* --- Toast 通知动效 --- */
.aksrt-toast {
animation: aksrtToastIn 0.35s var(--ease-out) forwards;
}
.aksrt-toast--out {
animation: aksrtToastOut 0.3s var(--ease) forwards;
}
/* --- 骨架屏动效 --- */
.aksrt-skeleton {
animation: aksrtSkeletonShimmer 1.5s infinite linear;
+142
View File
@@ -0,0 +1,142 @@
/**
* Zibll-compatible frontend notifications.
*
* @package AKSRT
*/
.notyn {
position: fixed;
bottom: 6em;
right: 0;
max-width: 320px;
z-index: 10000000;
}
.notyf {
margin-bottom: 0.6em;
color: #fff;
float: right;
min-width: 200px;
padding: 1em 3em 1em 3.4em;
box-shadow: -3px 3px 6px rgba(0, 0, 0, 0.1);
transform: translateX(1.5em);
transition: 0.3s;
-webkit-backdrop-filter: saturate(5) blur(20px);
backdrop-filter: saturate(5) blur(20px);
-webkit-animation: notyf 0.4s cubic-bezier(0.35, 0.71, 0.46, 1.08);
animation: notyf 0.4s cubic-bezier(0.35, 0.71, 0.46, 1.08);
}
.notyn-out .notyf {
transform: translateX(110%);
opacity: 0.5;
transition: 0.5s;
}
@-webkit-keyframes notyf {
0% {
transform: translateX(110%);
opacity: 0;
}
100% {
transform: translateX(1.5em);
opacity: 1;
}
}
@keyframes notyf {
0% {
transform: translateX(110%);
opacity: 0;
}
100% {
transform: translateX(1.5em);
opacity: 1;
}
}
.notyf::before {
font: normal normal normal 16px/1 FontAwesome;
left: 0.8em;
font-size: 1.5em;
position: absolute;
top: 0;
bottom: 0;
margin: auto;
height: 20px;
}
.notyf.success {
background: linear-gradient(90deg, rgba(15, 147, 249, 0.7), rgba(61, 189, 249, 0.8));
}
.notyf.success::before {
content: '\f058';
}
.notyf.info {
background: linear-gradient(90deg, rgba(58, 162, 54, 0.8), rgba(89, 247, 131, 0.8));
}
.notyf.info::before {
content: '\f0a1';
}
.notyf.warning,
.notyf.load {
background: linear-gradient(90deg, rgba(253, 170, 71, 0.7), rgba(247, 154, 13, 0.8));
}
.notyf.warning::before {
content: '\f05a';
}
.notyf.danger {
background: linear-gradient(90deg, rgba(253, 69, 28, 0.7), rgba(251, 110, 75, 0.8));
}
.notyf.danger::before {
content: '\f057';
}
.notyf .fa-close {
float: right;
padding: 0.6em;
margin: -0.4em;
}
.notyf.load::before {
content: '';
width: 0.7em;
height: 0.7em;
display: inline-block;
border: 0.08em solid transparent;
border-width: calc(1px + 0.03em);
border-radius: 50%;
border-top-color: var(--this-color);
border-bottom-color: var(--this-color);
-webkit-animation: huan-rotate 1s cubic-bezier(0.7, 0.1, 0.31, 0.9) infinite;
animation: huan-rotate 1s cubic-bezier(0.7, 0.1, 0.31, 0.9) infinite;
}
@-webkit-keyframes huan-rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
@keyframes huan-rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
-1
View File
@@ -13,7 +13,6 @@
.footer-tabbar,
.float-right,
.float-btn,
.aksrt-toast-container,
.resource-mobile-fab,
.resource-mobile-download-bar,
.sidebar,
-41
View File
@@ -5,7 +5,6 @@
* 通过 class-aksrt-scripts.php 在 is_page_template('templates/pages/page-profile.php') 时加载。
*
* 覆盖:
* - Toast 通知容器(仅 profile 页)
* - 公开个人主页头
* - 仪表盘标签 + 数据表
* - 个人资料编辑表单 + 联系方式行
@@ -14,46 +13,6 @@
* - 密码强度条
*/
/* ==================== Toast(profile 页专用) ==================== */
#toast-container {
position: fixed;
bottom: calc(24px + env(safe-area-inset-bottom, 0px));
right: calc(24px + env(safe-area-inset-right, 0px));
z-index: 99999;
display: flex;
flex-direction: column-reverse;
gap: 10px;
pointer-events: none;
}
.toast-item {
pointer-events: auto;
display: flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
background: rgba(0, 0, 0, 0.78);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
color: #fff;
font-size: 14px;
font-weight: 500;
border-radius: 100px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
animation: toastIn 0.35s cubic-bezier(0.22, 1, 0.36, 1) forwards;
white-space: nowrap;
}
.toast-item.fade-out {
animation: toastOut 0.4s ease forwards;
}
@keyframes toastIn {
0% { opacity: 0; transform: translateY(16px) scale(0.92); }
100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes toastOut {
0% { opacity: 1; transform: translateY(0) scale(1); }
100% { opacity: 0; transform: translateY(8px) scale(0.95); }
}
/* 用户主页 / 用户中心统一使用站点内容宽度,避免旧 page/dashboard 容器收窄。
* main 自身同时带 content-shell + aksrt-profile,不能写成 .aksrt-profile .content-shell。 */
.aksrt-profile.content-shell,
+8 -126
View File
@@ -3,7 +3,6 @@
*
* Unified core primitives for the entire frontend:
* aksrt.tbquire(deps, cb) — lazy script/CSS loader
* aksrt.notyf(msg, opts) — unified toast (same-ID update)
* aksrt.confirm(msg, opts) — i18n confirm dialog
* aksrt.scrollTopTo(target, off) — smart scroll with header offset
* aksrt.loading(el, show) — element-level loading overlay
@@ -93,123 +92,6 @@
return p;
};
// ══════════════════════════════════════════════════════════════
// §10.10 — notyf: unified toast with same-ID update
// ══════════════════════════════════════════════════════════════
var _notyfContainer = null;
var _notyfMap = {};
var NOTYF_DURATION = 5000;
function getNotyfContainer() {
if (_notyfContainer && _notyfContainer.parentNode) return _notyfContainer;
_notyfContainer = document.createElement('div');
_notyfContainer.className = 'aksrt-notyf';
_notyfContainer.setAttribute('role', 'alert');
_notyfContainer.setAttribute('aria-live', 'polite');
document.body.appendChild(_notyfContainer);
return _notyfContainer;
}
function createNotyfItem(msg, opts) {
var el = document.createElement('div');
el.className = 'aksrt-notyf__item aksrt-notyf--' + (opts.type || 'info');
if (opts.type === 'load') el.classList.add('aksrt-notyf--loading');
var iconMap = {
success: '<i class="fas fa-check-circle"></i>',
danger: '<i class="fas fa-exclamation-circle"></i>',
error: '<i class="fas fa-exclamation-circle"></i>',
warning: '<i class="fas fa-exclamation-triangle"></i>',
load: '<i class="fas fa-spinner fa-spin"></i>',
info: '<i class="fas fa-info-circle"></i>'
};
el.innerHTML =
'<span class="aksrt-notyf__icon">' + (iconMap[opts.type] || iconMap.info) + '</span>' +
'<span class="aksrt-notyf__text">' + escapeHtml(msg) + '</span>' +
'<button type="button" class="aksrt-notyf__close" aria-label="Close">&times;</button>';
el.querySelector('.aksrt-notyf__close').addEventListener('click', function () {
removeNotyf(el, opts.id);
});
return el;
}
function removeNotyf(el, id) {
el.classList.add('aksrt-notyf--out');
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 300);
if (id && _notyfMap[id]) {
clearTimeout(_notyfMap[id].timer);
delete _notyfMap[id];
}
}
aksrt.notyf = function (msg, opts) {
if (typeof opts === 'string') opts = { type: opts };
opts = opts || {};
var type = opts.type || 'info';
var duration = type === 'load' ? 0 : (opts.duration || NOTYF_DURATION);
var id = opts.id || null;
var container = getNotyfContainer();
if (id && _notyfMap[id]) {
var existing = _notyfMap[id];
clearTimeout(existing.timer);
existing.el.className = 'aksrt-notyf__item aksrt-notyf--' + type;
existing.el.querySelector('.aksrt-notyf__text').textContent = msg;
var iconSpan = existing.el.querySelector('.aksrt-notyf__icon');
var iconMap2 = {
success: '<i class="fas fa-check-circle"></i>',
danger: '<i class="fas fa-exclamation-circle"></i>',
error: '<i class="fas fa-exclamation-circle"></i>',
warning: '<i class="fas fa-exclamation-triangle"></i>',
load: '<i class="fas fa-spinner fa-spin"></i>',
info: '<i class="fas fa-info-circle"></i>'
};
iconSpan.innerHTML = iconMap2[type] || iconMap2.info;
if (duration > 0) {
existing.timer = setTimeout(function () { removeNotyf(existing.el, id); }, duration);
}
return existing.el;
}
var el = createNotyfItem(msg, { type: type, id: id });
container.appendChild(el);
requestAnimationFrame(function () {
el.classList.add('aksrt-notyf--in');
});
var timer = null;
if (duration > 0) {
timer = setTimeout(function () { removeNotyf(el, id); }, duration);
}
if (id) {
_notyfMap[id] = { el: el, timer: timer };
}
return el;
};
aksrt.notyf.close = function (id) {
if (_notyfMap[id]) removeNotyf(_notyfMap[id].el, id);
};
// Backward compat: wire window.aksrtToast to notyf
window.aksrtToast = function (msg, type) {
aksrt.notyf(msg, { type: type || 'info', duration: 2200 });
};
// Alias used by several modules that call aksrt.toast(...)
aksrt.toast = function (msg, type) {
return aksrt.notyf(msg, typeof type === 'string' ? { type: type, duration: 2200 } : (type || { duration: 2200 }));
};
// ══════════════════════════════════════════════════════════════
// §10.10 — confirm: i18n confirm dialog + [data-confirm]
// ══════════════════════════════════════════════════════════════
@@ -551,7 +433,7 @@
if (action === 'like' || action === 'favorite') {
try {
if (localStorage.getItem(storageKey) && !btn.classList.contains('is-active')) {
aksrt.notyf(__('already_done', '已操作'), 'warning');
notyf(__('already_done', '已操作'), 'warning');
return;
}
} catch (ex) { /* storage unavailable */ }
@@ -562,7 +444,7 @@
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
if (!(window.aksrtData && aksrtData.nonce)) {
btn.classList.remove('is-loading');
aksrt.notyf(__('action_failed', '操作失败'), 'danger');
notyf(__('action_failed', '操作失败'), 'danger');
return;
}
var params = new URLSearchParams();
@@ -602,19 +484,19 @@
} catch (ex) { /* */ }
if (json.data && json.data.message) {
aksrt.notyf(json.data.message, 'success');
notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
}
} else {
aksrt.notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
aksrt.notyf(err.message || __('network_error', '网络错误'), 'danger');
notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
@@ -651,7 +533,7 @@
kind: 'generic',
});
} else if (json.data && json.data.message) {
aksrt.notyf(json.data.message, 'success');
notyf(json.data.message, 'success');
}
if (json.data && json.data.goto) {
window.location.href = json.data.goto;
@@ -660,12 +542,12 @@
window.location.reload();
}
} else {
aksrt.notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
}
})
.catch(function (err) {
btn.classList.remove('is-loading');
aksrt.notyf(err.message || __('network_error', '网络错误'), 'danger');
notyf(err.message || __('network_error', '网络错误'), 'danger');
});
}
+6 -23
View File
@@ -12,23 +12,6 @@
(function () {
'use strict';
function notify(message, type) {
if (!message) return;
if (typeof window.AksrtToast === 'function') {
window.AksrtToast(message, type || 'info');
return;
}
if (typeof window.showToast === 'function') {
window.showToast(message, type || 'info');
return;
}
if (typeof window.aksrtToast === 'function') {
window.aksrtToast(message, type || 'info');
return;
}
window.alert(message);
}
function updateTabUrl(root, name, defaultTab) {
if (!window.history || typeof window.history.replaceState !== 'function') return;
var url = new URL(window.location.href);
@@ -149,7 +132,7 @@
if (!file) return;
if (upload.maxSize && file.size > upload.maxSize) {
notify(upload.tooLargeText || '图片大小不能超过 2MB', 'warning');
notyf(upload.tooLargeText || '图片大小不能超过 2MB', 'warning');
input.value = '';
return;
}
@@ -180,7 +163,7 @@
.then(function (payload) {
if (payload && payload.success && payload.url) {
applyImage(payload.url);
notify(upload.successText || '背景图已更新', 'success');
notyf(upload.successText || '背景图已更新', 'success');
return;
}
throw payload || { message: upload.errorText || '上传失败' };
@@ -190,7 +173,7 @@
? error.data.previous_url
: '';
resetImage(previousUrl);
notify((error && error.message) || upload.networkErrorText || '网络错误,请重试', 'error');
notyf((error && error.message) || upload.networkErrorText || '网络错误,请重试', 'danger');
})
.finally(function () {
setBusy(false);
@@ -222,7 +205,7 @@
var file = input.files && input.files[0];
if (!file) return;
if (upload.maxSize && file.size > upload.maxSize) {
notify(upload.tooLargeText || '图片大小不能超过 2MB', 'warning');
notyf(upload.tooLargeText || '图片大小不能超过 2MB', 'warning');
input.value = '';
return;
}
@@ -246,10 +229,10 @@
var url = payload.url || (payload.data && payload.data.url) || '';
if (!url) throw payload;
if (image) image.src = url;
notify(payload.message || upload.successText || '头像已更新', 'success');
notyf(payload.message || upload.successText || '头像已更新', 'success');
})
.catch(function (error) {
notify((error && error.message) || upload.errorText || '上传失败', 'error');
notyf((error && error.message) || upload.errorText || '上传失败', 'danger');
})
.finally(function () {
setBusy(false);
+3 -10
View File
@@ -9,13 +9,6 @@
if (window._win && window._win.ajax_url) return window._win.ajax_url;
return '/wp-admin/admin-ajax.php';
}
function toast(msg) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg);
return;
}
window.alert(msg);
}
function init() {
var form = document.querySelector('.aksrt-ban-appeal-form');
if (!form) return;
@@ -29,13 +22,13 @@
.then(function (r) { return r.json(); })
.then(function (data) {
if (!data || data.error) {
toast((data && data.msg) || '提交失败');
notyf((data && data.msg) || '提交失败', 'danger');
return;
}
toast(data.msg || '提交成功');
notyf(data.msg || '提交成功');
if (data.reload) window.setTimeout(function () { location.reload(); }, 400);
})
.catch(function () { toast('网络错误'); })
.catch(function () { notyf('网络错误', 'danger'); })
.finally(function () { if (btn) btn.disabled = false; });
});
}
+10 -17
View File
@@ -34,13 +34,6 @@
}).then(function (r) { return r.json(); });
}
function notify(message, type) {
if (typeof window.AksrtToast === 'function') { window.AksrtToast(message, type || 'info'); return; }
if (typeof window.aksrtToast === 'function') { window.aksrtToast(message, type || 'info'); return; }
if (typeof window.showToast === 'function') { window.showToast(message, type || 'info'); return; }
if (type === 'error') { window.alert(message); }
}
function getCommentId(el) {
return parseInt(el.getAttribute('data-comment-id') || (el.id || '').replace('comment-', ''), 10) || 0;
}
@@ -77,7 +70,7 @@
restPost('/comment/like', { comment_id: commentId, active: nextActive })
.then(function (result) {
if (!result.success) { notify((result.data && result.data.message) || result.message || '操作失败', 'error'); return; }
if (!result.success) { notyf((result.data && result.data.message) || result.message || '操作失败', 'danger'); return; }
var active = !!(result.data && result.data.active);
var count = (result.data && result.data.count) || 0;
btn.setAttribute('data-active', active ? '1' : '0');
@@ -91,7 +84,7 @@
if (count > 0) { countEl.removeAttribute('hidden'); } else { countEl.setAttribute('hidden', ''); }
}
})
.catch(function () { notify('网络错误', 'error'); })
.catch(function () { notyf('网络错误', 'danger'); })
.finally(function () { btn.dataset.busy = ''; });
}
@@ -120,10 +113,10 @@
}
}, 300);
} else {
notify((result.data && result.data.message) || result.message || '删除失败', 'error');
notyf((result.data && result.data.message) || result.message || '删除失败', 'danger');
}
})
.catch(function () { notify('网络错误', 'error'); });
.catch(function () { notyf('网络错误', 'danger'); });
};
if (window.aksrt && aksrt.confirm) {
@@ -138,15 +131,15 @@
restPost('/comment/pin', { comment_id: commentId, pinned: nextPinned })
.then(function (result) {
if (!result.success) { notify((result.data && result.data.message) || result.message || '操作失败', 'error'); return; }
if (!result.success) { notyf((result.data && result.data.message) || result.message || '操作失败', 'danger'); return; }
var pinned = !!(result.data && result.data.pinned);
btn.setAttribute('data-pinned', pinned ? '1' : '0');
btn.innerHTML = '<i class="fas fa-thumbtack" aria-hidden="true"></i> ' + (pinned ? '取消置顶' : '置顶');
commentEl.classList.toggle('aksrt-comment--pinned', pinned);
syncPinnedBadge(commentEl, pinned);
notify(result.message || (pinned ? '评论已置顶。' : '已取消置顶。'), 'success');
notyf(result.message || (pinned ? '评论已置顶。' : '已取消置顶。'), 'success');
})
.catch(function () { notify('网络错误', 'error'); });
.catch(function () { notyf('网络错误', 'danger'); });
}
function syncPinnedBadge(commentEl, pinned) {
@@ -175,11 +168,11 @@
var note = commentEl.querySelector('.awaiting-moderation');
if (note) note.remove();
}
notify(result.message || '操作成功。', 'success');
notyf(result.message || '操作成功。', 'success');
} else {
notify((result.data && result.data.message) || result.message || '操作失败', 'error');
notyf((result.data && result.data.message) || result.message || '操作失败', 'danger');
}
})
.catch(function () { notify('网络错误', 'error'); });
.catch(function () { notyf('网络错误', 'danger'); });
}
})();
+4 -10
View File
@@ -4,12 +4,6 @@
(function () {
'use strict';
function notify(msg) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, 'info');
}
}
function findTextarea(from) {
var form = from && from.closest ? from.closest('form') : null;
if (form) {
@@ -67,7 +61,7 @@
var ta = box ? box.querySelector('textarea') : null;
var code = ta ? ta.value : '';
if (!code || code.length < 2) {
notify('请输入代码');
notyf('请输入代码', 'warning');
return;
}
insertAtCursor(findTextarea(codeBtn), '[code]\n' + code + '\n[/code]\n');
@@ -84,7 +78,7 @@
var input = ibox ? ibox.querySelector('.input-image-url') : null;
var url = input ? (input.value || '').trim() : '';
if (!/^https?:\/\//i.test(url)) {
notify('请输入有效图片地址');
notyf('请输入有效图片地址', 'warning');
return;
}
insertAtCursor(findTextarea(imgBtn), '[img=' + url + ']');
@@ -107,12 +101,12 @@
.then(function (json) {
var u = (json && (json.url || json.source_url || (json.data && (json.data.url || json.data.source_url)))) || '';
if (!u) {
notify((json && json.message) || '上传失败');
notyf((json && json.message) || '上传失败', 'danger');
return;
}
insertAtCursor(findTextarea(upBtn), '[img=' + u + ']');
})
.catch(function () { notify('上传失败'); });
.catch(function () { notyf('上传失败', 'danger'); });
});
fileInput.click();
return;
+1 -3
View File
@@ -347,9 +347,7 @@
container.querySelectorAll(".ajax-item.is-fading").forEach(function (n) {
n.classList.remove("is-fading");
});
if (typeof window.aksrtToast === "function") {
window.aksrtToast((err && err.message) || "加载失败");
}
notyf((err && err.message) || "加载失败", "danger");
dispatch(container, "post_ajax.error", { link: link, error: err });
throw err;
});
+5 -15
View File
@@ -58,14 +58,6 @@
}
}
function showToast(msg, type) {
if (window.aksrt && aksrt.notyf) {
aksrt.notyf(msg, { type: type || "info", duration: 2200 });
} else {
console.log("[AKSRT Toast]", type, msg);
}
}
function initArticleTools() {
var buttons = document.querySelectorAll("[data-article-tool]");
if (!buttons.length) return;
@@ -80,9 +72,7 @@
if (tool === "pdf") {
window.print();
if (typeof showToast === "function") {
showToast("请在打印对话框中选择“另存为 PDF”", "info");
}
notyf("请在打印对话框中选择“另存为 PDF”", "info");
}
});
});
@@ -232,9 +222,9 @@
setButtonBusy(likeBtn, true);
requestInteraction("aksrt_toggle_like", postId, nextState).then(function (data) {
applyLikeState(!!data.active, parseInt(data.count, 10) || 0);
showToast(data.active ? "感谢你的点赞" : "已取消点赞", "success");
notyf(data.active ? "感谢你的点赞" : "已取消点赞", "success");
}).catch(function (error) {
showToast(error && error.message ? error.message : "点赞失败", "error");
notyf(error && error.message ? error.message : "点赞失败", "danger");
}).finally(function () {
setButtonBusy(likeBtn, false);
});
@@ -257,9 +247,9 @@
setButtonBusy(favBtn, true);
requestInteraction("aksrt_toggle_favorite", postId, nextState).then(function (data) {
applyFavoriteState(!!data.active, parseInt(data.count, 10) || 0);
showToast(data.active ? "收藏成功" : "已取消收藏", "success");
notyf(data.active ? "收藏成功" : "已取消收藏", "success");
}).catch(function (error) {
showToast(error && error.message ? error.message : "收藏失败", "error");
notyf(error && error.message ? error.message : "收藏失败", "danger");
}).finally(function () {
setButtonBusy(favBtn, false);
});
+1 -11
View File
@@ -22,16 +22,6 @@
return d.restNonce || d.nonce || '';
}
function notify(msg, type) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, type || 'info');
return;
}
if (window.notyf) {
try { window.notyf[type === 'error' ? 'error' : 'success'](msg); } catch (e) {}
}
}
function setActionText(text) {
var el = document.querySelector('#' + respondId + ' .action-text');
if (!el) return;
@@ -147,7 +137,7 @@
if (submit) submit.disabled = false;
})
.catch(function () {
notify('加载评论失败', 'error');
notyf('加载评论失败', 'danger');
if (textarea) {
textarea.disabled = false;
textarea.value = '';
+15 -23
View File
@@ -4,14 +4,6 @@
(function () {
"use strict";
function showToast(msg, type) {
if (window.aksrt && aksrt.notyf) {
aksrt.notyf(msg, { type: type || "info", duration: 2200 });
} else {
console.log("[AKSRT Toast]", type, msg);
}
}
function initCommentSystem() {
var form = document.querySelector("#commentform");
if (!form) return;
@@ -125,9 +117,9 @@
if (responseText.indexOf('审核') !== -1 ||
responseText.indexOf('moderation') !== -1 ||
responseText.includes('awaiting-moderation')) {
showToast('评论已提交,等待管理员审核', 'info');
notyf('评论已提交,等待管理员审核', 'info');
} else {
showToast('评论发布成功', 'success');
notyf('评论发布成功', 'success');
}
// 清空文本框
@@ -153,7 +145,7 @@
var errorMsg = extractErrorMessage(responseText);
var waitSeconds = extractWaitTime(errorMsg);
showToast(errorMsg, 'warning');
notyf(errorMsg, 'warning');
if (waitSeconds > 0) {
startCountdown(waitSeconds);
@@ -167,7 +159,7 @@
// 情况3: HTTP 403 权限不足(地区/IP/关键词等)
if (statusCode === 403) {
var forbiddenMsg = extractErrorMessage(responseText);
showToast(forbiddenMsg, 'error');
notyf(forbiddenMsg, 'danger');
setLoading(false);
return false;
}
@@ -175,12 +167,12 @@
// 情况4: 包含WordPress错误信息
var errorMatch = responseText.match(/<p[^>]*>([^<]*(?:错误|失败|error|Error)[^<]*)<\/p>/i);
if (errorMatch) {
showToast(errorMatch[1].trim(), 'error');
notyf(errorMatch[1].trim(), 'danger');
setLoading(false);
return false;
}
// 默认情况:可能是成功或其他
showToast('评论已提交', 'success');
notyf('评论已提交', 'success');
var textarea2 = form.querySelector('#comment');
if (textarea2) textarea2.value = '';
@@ -194,7 +186,7 @@
} catch (e) {
console.error('[AKSRT Comment] 解析响应失败:', e);
showToast('响应解析异常,请重试', 'error');
notyf('响应解析异常,请重试', 'danger');
setLoading(false);
return false;
}
@@ -239,7 +231,7 @@
if (editId) editId.remove();
restoreRespondPosition();
setLoading(false);
showToast(json.message || '评论已更新', 'success');
notyf(json.message || '评论已更新', 'success');
var respondEl = document.getElementById('respond');
if (respondEl && window.AksrtFixedInput) window.AksrtFixedInput(respondEl, 'hide');
return;
@@ -289,7 +281,7 @@
}
if (json && json.code) {
showToast(json.message || '评论提交失败', 'error');
notyf(json.message || '评论提交失败', 'danger');
try {
var failScene = window.AKSRTCaptchaState &&
window.AKSRTCaptchaState.scenes &&
@@ -303,7 +295,7 @@
return;
}
showToast('评论已提交', 'success');
notyf('评论已提交', 'success');
setLoading(false);
setTimeout(function () { reloadCommentList(); }, 500);
})
@@ -352,9 +344,9 @@
});
if (needsModeration) {
showToast('评论已提交,等待管理员审核', 'info');
notyf('评论已提交,等待管理员审核', 'info');
} else {
showToast('评论发布成功', 'success');
notyf('评论发布成功', 'success');
}
if (window.AksrtFrontend) AksrtFrontend.rehydrate(newComment, 'ajax');
@@ -397,9 +389,9 @@
console.error('[AKSRT Comment] 提交失败:', error);
if (error.message === 'Failed to fetch' || error.name === 'TypeError') {
showToast('网络连接失败,请检查网络后重试', 'error');
notyf('网络连接失败,请检查网络后重试', 'danger');
} else {
showToast(error.message || '评论提交失败,请重试', 'error');
notyf(error.message || '评论提交失败,请重试', 'danger');
}
setLoading(false);
@@ -439,7 +431,7 @@
performSubmit();
} else {
// 验证失败
showToast('人机验证未通过,请重试', 'warning');
notyf('人机验证未通过,请重试', 'warning');
setLoading(false);
}
});
+4 -87
View File
@@ -1,7 +1,7 @@
/**
* AKSRT Enhancements — 全局 UI 增强
*
* 包含:Toast 通知、图片灯箱、滚动方向感知
* 包含:图片灯箱、滚动方向感知
*
* @package AKSRT
*/
@@ -10,90 +10,7 @@
'use strict';
/* ============================================================
* 1. Toast 通知系统
* ============================================================ */
var toastContainer = null;
function ensureToastContainer() {
if (!toastContainer || !document.body.contains(toastContainer)) {
toastContainer = document.createElement('div');
toastContainer.className = 'aksrt-toast-container';
document.body.appendChild(toastContainer);
}
return toastContainer;
}
/**
* 显示 Toast 通知
*
* @param {string} message 消息文本
* @param {string} type 类型: success, info, warning, danger/error, loading
* @param {number} duration 显示时长(ms),0 表示不自动关闭
* @returns {Object} { dismiss: Function, update: Function }
*/
function showToast(message, type, duration) {
type = type || 'success';
if (type === 'error') type = 'danger';
duration = duration !== undefined ? duration : 3500;
if (duration > 0 && duration < 100) duration = duration * 1000;
var container = ensureToastContainer();
var icons = {
success: 'fas fa-check-circle',
info: 'fas fa-bell',
warning: 'fas fa-exclamation-circle',
danger: 'fas fa-times-circle',
loading: 'fas fa-spinner',
};
var el = document.createElement('div');
el.className = 'aksrt-toast aksrt-toast--' + type;
el.innerHTML =
'<span class="aksrt-toast__icon"><i class="' + (icons[type] || icons.info) + '"></i></span>' +
'<span class="aksrt-toast__text">' + message + '</span>';
container.appendChild(el);
var timer = null;
function dismiss() {
if (timer) clearTimeout(timer);
el.classList.add('is-dismissing');
setTimeout(function () {
if (el.parentNode) el.remove();
}, 300);
}
function update(newMessage, newType) {
if (newType) {
el.className = 'aksrt-toast aksrt-toast--' + newType;
var iconEl = el.querySelector('.aksrt-toast__icon i');
if (iconEl) iconEl.className = icons[newType] || icons.info;
}
var textEl = el.querySelector('.aksrt-toast__text');
if (textEl) textEl.textContent = newMessage;
if (newType !== 'loading' && duration > 0) {
if (timer) clearTimeout(timer);
timer = setTimeout(dismiss, duration);
}
}
el.addEventListener('click', dismiss);
if (type !== 'loading' && duration > 0) {
timer = setTimeout(dismiss, duration);
}
return { dismiss: dismiss, update: update };
}
// 暴露到全局
window.AksrtToast = showToast;
/* ============================================================
* 2. 图片灯箱 — zoom / thumbnails / slideshow / fullscreen /
* 1. 图片灯箱 — zoom / thumbnails / slideshow / fullscreen /
* mousewheel / origin animation / smart download
* ============================================================ */
var lightboxEl = null;
@@ -493,7 +410,7 @@
};
/* ============================================================
* 3. 滚动方向感知
* 2. 滚动方向感知
* ============================================================ */
// Scroll direction is now handled by aksrt-runtime.js body scroll bus
// (body.scroll-down / body.scroll-up / body.body-scroll).
@@ -521,7 +438,7 @@
}
/* ============================================================
* 4. 导航溢出折叠
* 3. 导航溢出折叠
* 由 assets/js/core/nav.js(zibll 1:1 navFolding)负责。
* 此处不再重复实现,避免两套逻辑争抢 .aksrt-nav-more 节点
* 触发 NotFoundError: insertBefore (moreLi 已被对方 remove)。
+2 -14
View File
@@ -40,11 +40,11 @@
var isFollowing = result.data.is_following;
updateFollowButton(btn, isFollowing, result.data.follower_count);
} else {
showToast(result.message || '操作失败', 'error');
notyf(result.message || '操作失败', 'danger');
}
})
.catch(function () {
showToast('网络错误,请重试', 'error');
notyf('网络错误,请重试', 'danger');
})
.finally(function () {
btn.disabled = false;
@@ -108,18 +108,6 @@
}
}
function showToast(msg, type) {
var toast = document.createElement('div');
toast.textContent = msg;
toast.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:12px 24px;border-radius:6px;color:#fff;font-size:14px;z-index:10000;transition:opacity 0.3s;';
toast.style.background = type === 'error' ? '#e74c3c' : '#27ae60';
document.body.appendChild(toast);
setTimeout(function () {
toast.style.opacity = '0';
setTimeout(function () { toast.remove(); }, 300);
}, 3000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initFollowButtons);
} else {
+3 -24
View File
@@ -29,27 +29,6 @@
return (window.aksrtData && window.aksrtData.restNonce) || '';
}
function toast(msg, type) {
type = type || 'info';
// Site-wide notification widget (aksrt-runtime notyf).
if (window.aksrt && typeof window.aksrt.notyf === 'function') {
window.aksrt.notyf(msg, { type: type, duration: 2200 });
return;
}
if (typeof window.aksrtToast === 'function') {
window.aksrtToast(msg, type);
return;
}
if (typeof window.AksrtToast === 'function') {
window.AksrtToast(msg, type);
return;
}
// Last resort: non-blocking console (never native alert for UX).
if (window.console && typeof window.console.log === 'function') {
window.console.log('[aksrt]', type, msg);
}
}
function openModal(id) {
var modal = document.getElementById(id);
if (!modal) return;
@@ -110,11 +89,11 @@
.then(function (res) {
if (!res.ok) {
var msg = (res.data && (res.data.message || res.data.msg)) || '签到失败';
toast(msg, 'danger');
notyf(msg, 'danger');
btn.disabled = false;
return;
}
toast((res.data && res.data.message) || '签到成功', 'success');
notyf((res.data && res.data.message) || '签到成功', 'success');
btn.classList.add('is-done');
btn.disabled = true;
btn.setAttribute('disabled', 'disabled');
@@ -127,7 +106,7 @@
'<span>' + signedLabel + '</span>';
})
.catch(function () {
toast('网络错误', 'danger');
notyf('网络错误', 'danger');
btn.disabled = false;
});
});
+2 -6
View File
@@ -382,11 +382,7 @@
updateNavUser(data.user);
}
if (window.aksrt && aksrt.notyf) {
aksrt.notyf(data.message || '登录成功', 'success');
} else if (typeof window.aksrtToast === 'function') {
window.aksrtToast(data.message || '登录成功', 'success');
}
notyf(data.message || '登录成功', 'success');
var redirect = modal.dataset.redirect || data.redirect;
if (redirect && redirect !== window.location.href) {
@@ -589,7 +585,7 @@
e.preventDefault();
e.stopImmediatePropagation();
agree.closest('label, .aksrt-modal-auth__agreement').classList.add('ani', 'shake');
if (window.aksrt && aksrt.notyf) aksrt.notyf('请先同意用户协议', 'warning');
notyf('请先同意用户协议', 'warning');
setTimeout(function () {
agree.closest('label, .aksrt-modal-auth__agreement').classList.remove('ani', 'shake');
}, 400);
+60
View File
@@ -0,0 +1,60 @@
/**
* Zibll-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);
+5 -9
View File
@@ -8,10 +8,6 @@
var instances = new WeakMap();
function toast(message, type) {
window.AksrtToast(message, type || "info");
}
function request(path, body, method) {
var options = { method: method || "POST" };
if (body !== undefined) options.body = JSON.stringify(body || {});
@@ -243,7 +239,7 @@
renderView(state, result.data);
return result.data;
}).catch(function (error) {
toast(errorMessage(error, "订单状态已变更,请刷新后查看"));
notyf(errorMessage(error, "订单状态已变更,请刷新后查看"), "danger");
return null;
});
}
@@ -260,12 +256,12 @@
window.location.assign(data.payment_url);
return;
}
toast(data.message || "支付成功", "success");
notyf(data.message || "支付成功", "success");
refreshView(state);
if (activeDetail(state, number)) openDetail(state, number);
}).catch(function (error) {
button.disabled = false;
toast(errorMessage(error, "网络错误,请稍后重试"), "error");
notyf(errorMessage(error, "网络错误,请稍后重试"), "danger");
});
}
@@ -281,14 +277,14 @@
request(orderPath(number, "/cancel"), {}, "POST").then(function (result) {
var data = result.data || {};
invalidateOrder(number, data.order);
toast("订单已关闭", "success");
notyf("订单已关闭", "success");
refreshView(state);
if (activeDetail(state, number)) openDetail(state, number);
}).catch(function (error) {
button.disabled = false;
button.removeAttribute("data-aksrt-confirm");
button.textContent = "关闭订单";
toast(errorMessage(error, "网络错误,请稍后重试"), "error");
notyf(errorMessage(error, "网络错误,请稍后重试"), "danger");
});
}
+7 -24
View File
@@ -10,23 +10,6 @@
(function () {
'use strict';
function notify(message, type) {
if (!message) return;
if (typeof window.AksrtToast === 'function') {
window.AksrtToast(message, type || 'info');
return;
}
if (typeof window.showToast === 'function') {
window.showToast(message, type || 'info');
return;
}
if (typeof window.aksrtToast === 'function') {
window.aksrtToast(message, type || 'info');
return;
}
window.alert(message);
}
function getMetaContent(selectors) {
for (var i = 0; i < selectors.length; i += 1) {
var node = document.querySelector(selectors[i]);
@@ -208,7 +191,7 @@
var share = window.AksrtShare;
if (!share || typeof share.openWeixinShare !== 'function') {
notify('微信分享模块未加载', 'error');
notyf('微信分享模块未加载', 'danger');
return Promise.resolve({ mode: 'error' });
}
@@ -230,7 +213,7 @@
qrImage: options.qrImage || null
}).then(function (result) {
if (result && result.message) {
notify(result.message, result.mode === 'error' ? 'error' : (result.mode === 'bridge' ? 'success' : 'info'));
notyf(result.message, result.mode === 'error' ? 'danger' : (result.mode === 'bridge' ? 'success' : 'info'));
}
return result;
});
@@ -245,13 +228,13 @@
if (platform === 'copy') {
return copyLink(payload.url).then(function () {
notify('链接已复制', 'success');
notyf('链接已复制', 'success');
if (options && typeof options.closeDropdown === 'function') {
options.closeDropdown();
}
return { mode: 'copy' };
}).catch(function () {
notify('复制失败,请手动复制链接', 'error');
notyf('复制失败,请手动复制链接', 'danger');
return { mode: 'error' };
});
}
@@ -271,7 +254,7 @@
if (options && typeof options.closeDropdown === 'function') options.closeDropdown();
return Promise.resolve({ mode: 'poster' });
}
notify('海报模块未加载', 'error');
notyf('海报模块未加载', 'danger');
return Promise.resolve({ mode: 'error' });
}
@@ -304,9 +287,9 @@
event.preventDefault();
var url = node.getAttribute('data-share-url') || window.location.href;
copyLink(url).then(function () {
notify('链接已复制', 'success');
notyf('链接已复制', 'success');
}).catch(function () {
notify('复制失败,请手动复制链接', 'error');
notyf('复制失败,请手动复制链接', 'danger');
});
});
}
+4 -13
View File
@@ -21,15 +21,6 @@
return '/wp-admin/admin-ajax.php';
}
function toast(msg, type) {
if (window.aksrt && typeof window.aksrt.toast === 'function') {
window.aksrt.toast(msg, type || 'info');
return;
}
// eslint-disable-next-line no-alert
window.alert(msg);
}
function initModal() {
var modal = qs('#post-navs-sz');
if (!modal) return;
@@ -144,7 +135,7 @@
if (id) ids.push(id);
});
if (!ids.length) {
toast('请选择需要显示的分类', 'danger');
notyf('请选择需要显示的分类', 'danger');
return;
}
@@ -167,16 +158,16 @@
.then(function (r) { return r.json(); })
.then(function (data) {
if (!data || data.error) {
toast((data && data.msg) || '保存失败', 'danger');
notyf((data && data.msg) || '保存失败', 'danger');
return;
}
toast(data.msg || '保存成功', 'success');
notyf(data.msg || '保存成功', 'success');
if (data.reload) {
window.setTimeout(function () { window.location.reload(); }, 400);
}
})
.catch(function () {
toast('网络错误', 'danger');
notyf('网络错误', 'danger');
})
.finally(function () {
if (btn) btn.disabled = false;
+3 -15
View File
@@ -117,15 +117,15 @@
.then(function (result) {
overlay.remove();
if (result.success) {
showToast(result.message || '举报已提交');
notyf(result.message || '举报已提交');
} else {
showToast(result.message || '提交失败', 'error');
notyf(result.message || '提交失败', 'danger');
}
})
.catch(function () {
submitBtn.disabled = false;
submitBtn.textContent = '提交举报';
showToast('网络错误,请重试', 'error');
notyf('网络错误,请重试', 'danger');
});
};
btnRow.appendChild(submitBtn);
@@ -140,18 +140,6 @@
reasonInput.focus();
}
function showToast(msg, type) {
var toast = document.createElement('div');
toast.textContent = msg;
toast.style.cssText = 'position:fixed;top:20px;left:50%;transform:translateX(-50%);padding:12px 24px;border-radius:6px;color:#fff;font-size:14px;z-index:10000;transition:opacity 0.3s;';
toast.style.background = type === 'error' ? '#e74c3c' : '#27ae60';
document.body.appendChild(toast);
setTimeout(function () {
toast.style.opacity = '0';
setTimeout(function () { toast.remove(); }, 300);
}, 3000);
}
function initReportButtons() {
document.addEventListener('click', function (e) {
var btn = e.target.closest('[data-aksrt-report]');
+42 -42
View File
@@ -7,7 +7,7 @@
* @package AKSRT
*/
/* global aksrtUserCenter, showToast */
/* global aksrtUserCenter, notyf */
(function() {
'use strict';
@@ -157,7 +157,7 @@
if (name === 'revenue' && typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit();
if (name === 'revenue' && typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit();
}).catch(function() {
if (typeof showToast === 'function') showToast(i18n.networkError || '加载失败,请重试');
notyf(i18n.networkError || '加载失败,请重试', 'danger');
});
}
@@ -220,7 +220,7 @@
if (typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit();
if (typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit();
}).catch(function() {
if (typeof showToast === 'function') showToast(i18n.networkError || '加载失败,请重试');
notyf(i18n.networkError || '加载失败,请重试', 'danger');
});
return;
}
@@ -246,7 +246,7 @@
if (window.history.pushState) window.history.pushState({}, '', pageUrl.toString());
if (typeof window.AKSRTRevenueCenterInit === 'function') window.AKSRTRevenueCenterInit(creatorPanel);
}).catch(function() {
if (typeof showToast === 'function') showToast(i18n.networkError || '加载失败,请重试');
notyf(i18n.networkError || '加载失败,请重试', 'danger');
});
});
}
@@ -283,7 +283,7 @@
btn.disabled = false;
btn.textContent = i18n.saveBtn || '保存修改';
if (res.success) {
showToast(i18n.saved || '已保存');
notyf(i18n.saved || '已保存');
} else {
msg.className = 'profile-form__submit-msg is-error';
msg.textContent = res.data.message || (i18n.saveFailed || '保存失败');
@@ -350,17 +350,17 @@
}
}, 220);
}
showToast(i18n.unfavorited || '已取消收藏');
notyf(i18n.unfavorited || '已取消收藏');
} else {
btn.disabled = false;
btn.classList.remove('is-loading');
showToast((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'));
notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger');
}
})
.catch(function() {
btn.disabled = false;
btn.classList.remove('is-loading');
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
}
@@ -383,11 +383,11 @@
if (!response || !response.success) throw new Error('delete-failed');
var item = btn.closest('.profile-list-item');
if (item) item.remove();
if (typeof showToast === 'function') showToast('文章已删除');
notyf('文章已删除');
})
.catch(function() {
btn.disabled = false;
if (typeof showToast === 'function') showToast(i18n.operationFailed || '操作失败');
notyf(i18n.operationFailed || '操作失败', 'danger');
});
});
}
@@ -429,14 +429,14 @@
var row = btn.closest('[data-notification-id]');
if (row) row.classList.add('is-read');
btn.remove();
showToast(i18n.markedRead || '已标记为已读');
notyf(i18n.markedRead || '已标记为已读');
} else {
btn.disabled = false;
showToast((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'));
notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger');
}
}).catch(function() {
btn.disabled = false;
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
return;
}
@@ -460,14 +460,14 @@
if (res && res.success) {
var delRow = delBtn.closest('[data-notification-id]');
if (delRow) delRow.remove();
showToast(i18n.notificationDeleted || '已删除通知');
notyf(i18n.notificationDeleted || '已删除通知');
} else {
delBtn.disabled = false;
showToast((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'));
notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger');
}
}).catch(function() {
delBtn.disabled = false;
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
@@ -482,14 +482,14 @@
row.classList.add('is-read');
});
list.querySelectorAll('.aksrt-notification-read-btn').forEach(function(b) { b.remove(); });
showToast(i18n.allMarkedRead || '已全部标记为已读');
notyf(i18n.allMarkedRead || '已全部标记为已读');
} else {
markAllBtn.disabled = false;
showToast((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'));
notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger');
}
}).catch(function() {
markAllBtn.disabled = false;
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
}
@@ -501,7 +501,7 @@
return input.value;
});
if (!checked.length) {
showToast(i18n.selectNotifications || '请先选择要标记的通知');
notyf(i18n.selectNotifications || '请先选择要标记的通知', 'warning');
return;
}
markSelectedBtn.disabled = true;
@@ -516,11 +516,11 @@
var b = row ? row.querySelector('.aksrt-notification-read-btn') : null;
if (b) b.remove();
});
showToast(i18n.selectedMarkedRead || '已标记所选通知为已读');
notyf(i18n.selectedMarkedRead || '已标记所选通知为已读');
markSelectedBtn.disabled = false;
}).catch(function() {
markSelectedBtn.disabled = false;
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
}
@@ -532,7 +532,7 @@
return input.value;
});
if (!checked.length) {
showToast(i18n.selectNotifications || '请先选择要删除的通知');
notyf(i18n.selectNotifications || '请先选择要删除的通知', 'warning');
return;
}
if (!confirm(i18n.confirmBatchDelete || '确定批量删除所选通知?')) return;
@@ -551,15 +551,15 @@
var row = list.querySelector('[data-notification-id="' + id + '"]');
if (row) row.remove();
});
showToast(i18n.batchDeleted || '已删除所选通知');
notyf(i18n.batchDeleted || '已删除所选通知');
} else {
deleteSelectedBtn.disabled = false;
showToast((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'));
notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger');
}
})
.catch(function() {
deleteSelectedBtn.disabled = false;
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
}
@@ -687,7 +687,7 @@
fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() })
.then(function(r) { return r.json(); }).then(function(res) {
if (res.success) {
showToast(res.data.message || (i18n.codeSent || '验证码已发送'));
notyf(res.data.message || (i18n.codeSent || '验证码已发送'));
var sec = 60;
self.classList.add('is-counting');
self.textContent = sec + 's 后重发';
@@ -703,13 +703,13 @@
}
}, 1000);
} else {
showToast(res.data.message || (i18n.sendFailed || '发送失败'));
notyf(res.data.message || (i18n.sendFailed || '发送失败'), 'danger');
self.disabled = false;
self.textContent = getSendBtnIdleText(btnId);
}
})
.catch(function() {
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
self.disabled = false;
self.textContent = getSendBtnIdleText(btnId);
});
@@ -730,14 +730,14 @@
var orig = btn ? btn.textContent : '';
var reqs = fm.querySelectorAll('[required]');
for (var i = 0; i < reqs.length; i++) {
if (!reqs[i].value.trim()) { reqs[i].focus(); showToast(i18n.fillRequired || '请填写必填项'); return; }
if (!reqs[i].value.trim()) { reqs[i].focus(); notyf(i18n.fillRequired || '请填写必填项', 'warning'); return; }
}
if (fid === 'pwd-form') {
var pwdNew = document.getElementById('pwd-new');
var pwdConfirm = document.getElementById('pwd-new-confirm');
if (pwdNew && pwdConfirm && pwdNew.value !== pwdConfirm.value) {
pwdConfirm.focus();
showToast(i18n.passwordMismatch || '两次输入的新密码不一致');
notyf(i18n.passwordMismatch || '两次输入的新密码不一致', 'warning');
return;
}
}
@@ -750,16 +750,16 @@
.then(function(r) {
if (btn) { btn.disabled = false; btn.textContent = orig; }
if (r.success) {
showToast(r.data.message || '');
notyf(r.data.message || '');
closeModal(fm.closest('.aksrt-modal'));
if (r.data.redirect_to) setTimeout(function() { window.location.href = r.data.redirect_to; }, 2500);
} else {
showToast(r.data.message || (i18n.operationFailed || '操作失败'));
notyf(r.data.message || (i18n.operationFailed || '操作失败'), 'danger');
}
})
.catch(function() {
if (btn) { btn.disabled = false; btn.textContent = orig; }
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
});
});
}
@@ -790,7 +790,7 @@
.then(function(r) { return r.json(); })
.then(function(d) {
if (d.success) {
showToast(i18n.unbindSuccess || '解除成功');
notyf(i18n.unbindSuccess || '解除成功');
setTimeout(function() {
var u = new URL(window.location);
u.searchParams.set('tab', 'settings');
@@ -798,13 +798,13 @@
window.location.href = u.toString();
}, 1000);
} else {
showToast(i18n.unbindFailed || '解除失败');
notyf(i18n.unbindFailed || '解除失败', 'danger');
self.disabled = false;
self.textContent = i18n.unbind || '解除';
}
})
.catch(function() {
showToast(i18n.networkError || '网络错误');
notyf(i18n.networkError || '网络错误', 'danger');
self.disabled = false;
self.textContent = i18n.unbind || '解除';
});
@@ -844,11 +844,11 @@
var phone = phoneEl ? String(phoneEl.value || '').trim() : '';
var code = codeEl ? String(codeEl.value || '').trim() : '';
if (!/^1[3-9]\d{9}$/.test(phone)) {
showToast('请输入正确的手机号');
notyf('请输入正确的手机号', 'warning');
return;
}
if (!code) {
showToast('请输入短信验证码');
notyf('请输入短信验证码', 'warning');
return;
}
var submitBtn = form.querySelector('[type="submit"]');
@@ -892,12 +892,12 @@
.then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); })
.then(function (res) {
if (res.ok && res.json && res.json.success) {
showToast((res.json.message) || '绑定成功');
notyf((res.json.message) || '绑定成功');
setTimeout(function () { window.location.reload(); }, 600);
return;
}
resetCaptcha();
showToast((res.json && (res.json.message || res.json.error)) || '绑定失败');
notyf((res.json && (res.json.message || res.json.error)) || '绑定失败', 'danger');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = '确认绑定';
@@ -905,7 +905,7 @@
})
.catch(function () {
resetCaptcha();
showToast('网络错误');
notyf('网络错误', 'danger');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = '确认绑定';
+18 -35
View File
@@ -56,7 +56,7 @@
const phone = phoneInput ? phoneInput.value.trim() : ''
if (!/^1[3-9]\d{9}$/.test(phone)) {
showToast('请输入正确的手机号', 'warning')
notyf('请输入正确的手机号', 'warning')
if (phoneInput) phoneInput.focus()
return
}
@@ -91,13 +91,13 @@
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
if (ok && data.success) {
showToast(data.message || '验证码已发送', 'success')
notyf(data.message || '验证码已发送', 'success')
const seconds = (data.data && data.data.remaining_time) || cooldown
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + seconds * 1000))
startCountdown(btn, seconds)
} else {
const msg = (data.data && data.data.message) || data.message || '发送失败'
showToast(msg, 'error')
notyf(msg, 'danger')
try {
const captchaState = window.AKSRTCaptchaState
if (captchaState && captchaState.scenes && captchaState.scenes[scene]) {
@@ -115,7 +115,7 @@
}
})
.catch(() => {
showToast('网络错误,请重试', 'error')
notyf('网络错误,请重试', 'danger')
btn.disabled = false
btn.textContent = original
})
@@ -187,15 +187,15 @@
const ref = form.querySelector('[name="ref"]')?.value?.trim()
if (!phone || !code || !password) {
showToast('请填写完整信息', 'warning')
notyf('请填写完整信息', 'warning')
return
}
if (password !== confirm) {
showToast('两次密码不一致', 'warning')
notyf('两次密码不一致', 'warning')
return
}
if (password.length < 6) {
showToast('密码至少 6 位', 'warning')
notyf('密码至少 6 位', 'warning')
return
}
@@ -216,13 +216,13 @@
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
if (ok && data.success) {
showToast(data.message || '注册成功', 'success')
if (data.notice) showToast(data.notice, 'warning')
notyf(data.message || '注册成功', 'success')
if (data.notice) notyf(data.notice, 'warning')
setTimeout(() => {
window.location.href = data.redirect || '/'
}, 500)
} else {
showToast(data.message || '注册失败', 'error')
notyf(data.message || '注册失败', 'danger')
try {
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
captchaState.scenes.register.reset()
@@ -235,7 +235,7 @@
}
})
.catch(() => {
showToast('网络错误', 'error')
notyf('网络错误', 'danger')
try {
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
captchaState.scenes.register.reset()
@@ -261,7 +261,7 @@
const loginMode = form.querySelector('[name="login_mode"]')?.value || 'password'
if (!phone) {
showToast('请输入手机号', 'warning')
notyf('请输入手机号', 'warning')
return
}
@@ -275,7 +275,7 @@
if (loginMode === 'code') {
const code = form.querySelector('[name="code"]')?.value?.trim()
if (!code) {
showToast('请输入验证码', 'warning')
notyf('请输入验证码', 'warning')
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' }
return
}
@@ -283,7 +283,7 @@
body = { phone, code }
} else {
if (!password) {
showToast('请输入密码', 'warning')
notyf('请输入密码', 'warning')
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' }
return
}
@@ -302,12 +302,12 @@
.then(r => r.json().then(data => ({ ok: r.ok, data })))
.then(({ ok, data }) => {
if (ok && data.success) {
showToast('登录成功', 'success')
notyf('登录成功', 'success')
setTimeout(() => {
window.location.href = data.data?.redirect || '/'
}, 500)
} else {
showToast(data.message || '登录失败', 'error')
notyf(data.message || '登录失败', 'danger')
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
captchaState.scenes.login.reset()
}
@@ -318,7 +318,7 @@
}
})
.catch(() => {
showToast('网络错误', 'error')
notyf('网络错误', 'danger')
if (submitBtn) {
submitBtn.disabled = false
submitBtn.textContent = '登录'
@@ -388,7 +388,7 @@
const confirm = form.querySelector('[name="password_confirm"]')
if (password && confirm && password.value !== confirm.value) {
e.preventDefault()
showToast('两次密码不一致', 'warning')
notyf('两次密码不一致', 'warning')
confirm.focus()
return
}
@@ -396,22 +396,6 @@
})
}
function showToast(message, type) {
if (typeof window.aksrtShowToast === 'function') {
window.aksrtShowToast(message, type)
return
}
const toast = document.createElement('div')
toast.className = 'aksrt-auth-toast aksrt-auth-toast--' + (type || 'info')
toast.textContent = message
document.body.appendChild(toast)
setTimeout(() => toast.classList.add('aksrt-auth-toast--show'), 10)
setTimeout(() => {
toast.classList.remove('aksrt-auth-toast--show')
setTimeout(() => toast.remove(), 300)
}, 3000)
}
// 初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initAuthPages)
@@ -424,6 +408,5 @@
init: initAuthPages,
sendSmsCode,
switchAuthTab,
showToast,
}
})()
+3 -7
View File
@@ -20,10 +20,6 @@
.replace(/'/g, '&#39;');
}
function toast(message, type) {
window.AksrtToast(message, type || 'info');
}
function api(path, options) {
return window.AksrtRequest.request(path, options || {}).then(function (response) {
return {
@@ -177,19 +173,19 @@
}).then(function (result) {
if (state.session && state.session.closed) return;
if (!result.ok) {
toast((result.json && (result.json.message || (result.json.data && result.json.data.message))) || '佩戴失败', 'error');
notyf((result.json && (result.json.message || (result.json.data && result.json.data.message))) || '佩戴失败', 'danger');
if (button) button.disabled = false;
return;
}
if (result.json && result.json.data) state.data = result.json.data;
state.detailSlug = slug;
window.AksrtModalRuntime.invalidate(['badges', 'identity', 'badges:user:' + state.userId]);
toast((result.json && result.json.message) || '已佩戴徽章', 'success');
notyf((result.json && result.json.message) || '已佩戴徽章', 'success');
renderInto(state);
syncHeaderIcon(state);
}).catch(function (error) {
if (error && error.name === 'AbortError') return;
toast('网络错误', 'error');
notyf('网络错误', 'danger');
if (button) button.disabled = false;
}).finally(function () {
state.wearing = false;
-2
View File
@@ -113,7 +113,5 @@ $show_sidebar = function_exists('aksrt_is_show_sidebar') ? aksrt_is_show_sidebar
</div>
<?php endif; ?>
<div class="toast-container" id="toast-container"></div>
<?php
get_footer();
+5 -5
View File
@@ -165,7 +165,7 @@ trait ScriptsPart1b {
wp_enqueue_script(
'aksrt-header-userbox',
$base_uri . '/assets/js/header-userbox.js',
[],
['aksrt-notyf'],
(string) filemtime($hub_js),
true
);
@@ -410,7 +410,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-report',
$base_uri . '/assets/js/report.js',
[],
['aksrt-notyf'],
filemtime($report_js_path) ?: $version,
true
);
@@ -422,7 +422,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-follow',
$base_uri . '/assets/js/follow.js',
[],
['aksrt-notyf'],
filemtime($follow_js_path) ?: $version,
true
);
@@ -609,7 +609,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-posts-navs-page',
$base_uri . '/assets/js/posts-navs-page.js',
['jquery'],
['jquery', 'aksrt-notyf'],
(string) filemtime($pn_js),
true
);
@@ -645,7 +645,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
wp_enqueue_script(
'aksrt-ban-appeal-page',
$base_uri . '/assets/js/ban-appeal-page.js',
[],
['aksrt-notyf'],
(string) filemtime($ba_js),
true
);
+20 -3
View File
@@ -62,6 +62,14 @@ trait ScriptsPart1c {
'5.15.4'
);
$notyf_css = get_template_directory() . '/assets/css/frontend/notyf.css';
wp_enqueue_style(
'aksrt-notyf',
$base_uri . '/assets/css/frontend/notyf.css',
[$core_dep, 'aksrt-font-awesome-5-v4-shims'],
file_exists($notyf_css) ? (string) filemtime($notyf_css) : $version
);
// ===== 7. JS =====
// Swiper.js(首页/归档/CMS 模块/单篇文章页加载)
@@ -111,13 +119,22 @@ trait ScriptsPart1c {
true
);
// Runtime infrastructure: tbquire / notyf / confirm / scrollTopTo / [data-*] declarative
$notyf_js = get_template_directory() . '/assets/js/notyf.js';
wp_enqueue_script(
'aksrt-notyf',
$base_uri . '/assets/js/notyf.js',
['jquery'],
file_exists($notyf_js) ? (string) filemtime($notyf_js) : $version,
true
);
// Runtime infrastructure: tbquire / confirm / scrollTopTo / [data-*] declarative
$runtime_js = get_template_directory() . '/assets/js/aksrt-runtime.js';
if (file_exists($runtime_js)) {
wp_enqueue_script(
'aksrt-runtime',
$base_uri . '/assets/js/aksrt-runtime.js',
['aksrt-rehydrate'],
['aksrt-rehydrate', 'aksrt-notyf'],
(string) filemtime($runtime_js),
true
);
@@ -309,7 +326,7 @@ trait ScriptsPart1c {
);
}
// ===== 全局 UI 增强(Toast、骨架屏、悬停动效、灯箱、滚动感知)=====
// ===== 全局 UI 增强(骨架屏、悬停动效、灯箱、滚动感知)=====
$enhancements_css = get_template_directory() . '/assets/css/frontend/enhancements.css';
if (file_exists($enhancements_css)) {
wp_enqueue_style(
-3
View File
@@ -39,9 +39,6 @@ $render_context = aksrt_get_user_center_render_context($current_user);
get_header();
?>
<?php \AKSRT\WidgetSystem\WidgetAreas::render('user_top_fluid', 'fluid-widget-wrap'); ?>
<div id="toast-container"></div>
<script>function showToast(text,d){d=d||2500;var c=document.getElementById('toast-container');if(!c){c=document.createElement('div');c.id='toast-container';document.body.appendChild(c)}var t=document.createElement('div');t.className='toast-item';t.textContent=text;c.appendChild(t);setTimeout(function(){t.classList.add('fade-out');setTimeout(function(){if(t.parentNode)t.parentNode.removeChild(t)},300)},d)}</script>
<main id="main-content" class="site-main container content-shell content-shell--stream aksrt-profile user-center" tabindex="-1">
<div class="content-wrap">
<?php get_template_part('templates/parts/user-center/header', null, ['context' => $render_context]); ?>
+218
View File
@@ -0,0 +1,218 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const vm = require('node:vm');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
class FakeElement {
constructor(classes = [], id = '') {
this.classes = new Set(classes);
this.id = id;
this.children = [];
this.parent = null;
this.innerHTML = '';
this.delegated = null;
}
append(child) {
child.parent = this;
this.children.push(child);
}
}
function descendants(element) {
return element.children.flatMap((child) => [child, ...descendants(child)]);
}
function createRuntime() {
const body = new FakeElement(['body']);
const document = { body };
const timers = [];
const window = {
setTimeout(callback, delay) {
timers.push({ callback, delay });
return timers.length;
},
};
class FakeQuery {
constructor(elements) {
this.elements = elements;
this.length = elements.length;
}
append(value) {
let elements = value instanceof FakeQuery ? value.elements : [];
if (typeof value === 'string' && value === '<div class="notyn"></div>') {
elements = [new FakeElement(['notyn'])];
}
this.elements.forEach((parent) => elements.forEach((element) => parent.append(element)));
return this;
}
addClass(classes) {
this.elements.forEach((element) => classes.split(/\s+/).forEach((name) => element.classes.add(name)));
return this;
}
removeClass() {
this.elements.forEach((element) => element.classes.clear());
return this;
}
find(selector) {
return new FakeQuery(this.elements.flatMap(descendants).filter((element) => matches(element, selector)));
}
html(value) {
this.elements.forEach((element) => { element.innerHTML = value; });
return this;
}
remove() {
this.elements.forEach((element) => {
if (!element.parent) return;
element.parent.children = element.parent.children.filter((child) => child !== element);
element.parent = null;
});
return this;
}
on(event, selector, handler) {
this.elements.forEach((element) => { element.delegated = { event, selector, handler }; });
return this;
}
}
function matches(element, selector) {
if (selector.startsWith('.')) return element.classes.has(selector.slice(1));
if (selector.startsWith('#')) return element.id === selector.slice(1);
return false;
}
function parseNotification(html) {
const id = (html.match(/ id="([^"]+)"/) || [])[1] || '';
const content = html.match(/<div class="notyf ([^"]+)">([\s\S]*)<\/div><\/div>$/);
assert.ok(content, 'notification markup should follow the Zibll contract');
const wrapper = new FakeElement(['noty1'], id);
const message = new FakeElement(['notyf', content[1]]);
message.innerHTML = content[2];
wrapper.append(message);
return wrapper;
}
function jQuery(value) {
if (value === document.body || value instanceof FakeElement) return new FakeQuery([value]);
if (typeof value === 'string' && value.startsWith('<div class="noty1"')) {
return new FakeQuery([parseNotification(value)]);
}
if (typeof value === 'string') {
return new FakeQuery([body, ...descendants(body)].filter((element) => matches(element, value)));
}
return new FakeQuery([]);
}
vm.runInNewContext(read('assets/js/notyf.js'), { jQuery, window, document });
return { body, timers, window };
}
test('notyf follows Zibll defaults, type normalization, and timing', () => {
const runtime = createRuntime();
runtime.window.notyf('完成');
const container = runtime.body.children[0];
const first = container.children[0];
assert.ok(container.classes.has('notyn'));
assert.ok(first.classes.has('noty1'));
assert.ok(first.children[0].classes.has('success'));
assert.equal(first.children[0].innerHTML, '完成');
assert.equal(runtime.timers[0].delay, 5000);
runtime.window.notyf('失败', 'error', 2);
const second = container.children[1];
assert.ok(second.children[0].classes.has('danger'));
assert.equal(runtime.timers[1].delay, 2000);
});
test('fixed-id notifications stay open until an in-place update', () => {
const runtime = createRuntime();
runtime.window.notyf('处理中', 'load', '', 'request-state');
const container = runtime.body.children[0];
const notification = container.children[0];
assert.equal(runtime.timers.length, 0);
assert.equal(notification.id, 'request-state');
assert.ok(notification.children[0].classes.has('load'));
runtime.window.notyf('<strong>完成</strong>', 'success', 3, 'request-state');
assert.equal(container.children.length, 1);
assert.equal(notification.children[0].innerHTML, '<strong>完成</strong>');
assert.ok(notification.children[0].classes.has('success'));
assert.equal(runtime.timers[0].delay, 3000);
});
test('clicking a notification uses the Zibll exit class and removal delay', () => {
const runtime = createRuntime();
runtime.window.notyf('可关闭');
const notification = runtime.body.children[0].children[0];
const delegated = runtime.body.delegated;
assert.deepEqual({ event: delegated.event, selector: delegated.selector }, { event: 'click', selector: '.noty1' });
delegated.handler.call(notification);
assert.ok(notification.classes.has('notyn-out'));
assert.equal(runtime.timers.at(-1).delay, 1000);
runtime.timers.at(-1).callback();
assert.equal(runtime.body.children[0].children.length, 0);
});
test('notyf CSS and WordPress enqueue match the reference contract', () => {
const css = read('assets/css/frontend/notyf.css');
const scripts = read('src/Core/ScriptsPart1c.php');
assert.match(css, /\.notyn\s*\{[\s\S]*?bottom:\s*6em;[\s\S]*?max-width:\s*320px;[\s\S]*?z-index:\s*10000000;/);
assert.match(css, /\.notyf\s*\{[\s\S]*?min-width:\s*200px;[\s\S]*?padding:\s*1em 3em 1em 3\.4em;/);
assert.match(css, /rgba\(15, 147, 249, 0\.7\)[\s\S]*?rgba\(61, 189, 249, 0\.8\)/);
assert.match(css, /rgba\(58, 162, 54, 0\.8\)[\s\S]*?rgba\(89, 247, 131, 0\.8\)/);
assert.match(css, /rgba\(253, 170, 71, 0\.7\)[\s\S]*?rgba\(247, 154, 13, 0\.8\)/);
assert.match(css, /rgba\(253, 69, 28, 0\.7\)[\s\S]*?rgba\(251, 110, 75, 0\.8\)/);
assert.match(css, /content:\s*'\\f058'/);
assert.match(css, /content:\s*'\\f0a1'/);
assert.match(css, /content:\s*'\\f05a'/);
assert.match(css, /content:\s*'\\f057'/);
assert.match(css, /\.notyf\.load::before[\s\S]*?huan-rotate 1s/);
assert.match(scripts, /'aksrt-notyf',[\s\S]*?'\/assets\/js\/notyf\.js',[\s\S]*?\['jquery'\]/);
assert.match(scripts, /'aksrt-runtime',[\s\S]*?\['aksrt-rehydrate', 'aksrt-notyf'\]/);
assert.match(scripts, /'\/assets\/css\/frontend\/notyf\.css',[\s\S]*?'aksrt-font-awesome-5-v4-shims'/);
});
test('legacy frontend toast APIs and selectors are fully removed', () => {
const files = [
'assets/js',
'assets/css/frontend',
'assets/css/routes',
'assets/css/build/frontend-base.min.css.disabled',
'page.php',
'templates/pages/page-profile.php',
];
const legacy = /AksrtToast|aksrtToast|aksrtShowToast|aksrt\.(?:toast|notyf)|showToast|toast-container|toast-item|aksrt-toast/;
function sourceFiles(target) {
const absolute = path.join(root, target);
if (!fs.statSync(absolute).isDirectory()) return [absolute];
return fs.readdirSync(absolute, { withFileTypes: true }).flatMap((entry) => {
if (entry.name === 'admin' || entry.name.endsWith('.min.js')) return [];
const child = path.join(absolute, entry.name);
return entry.isDirectory() ? sourceFiles(path.relative(root, child)) : [child];
});
}
const scanned = files.flatMap(sourceFiles);
const offenders = scanned.filter((file) => legacy.test(fs.readFileSync(file, 'utf8')));
assert.deepEqual(offenders.map((file) => path.relative(root, file)), []);
});