Files
wordpress-theme-aksrt/assets/js/user-ban.js
T
kate522 7597972eea feat(user-ban): frontend ban modal replaces the backend restrictions tab
1:1 replicate the reference theme user-ban flow on the frontend:

- Ban/unban modal rendered by inc/helpers/user-ban.php, opened via
  AksrtModalRuntime.openRemote (600px desktop dialog, 360px initial
  height, mobile bottom drawer, c-yellow colorful unban header).
- Three entry points wired: user homepage cover three-dot menu,
  comment section menu, article author card menu (ban item takes
  priority over report, matching the reference hook order).
- REST endpoints /user-ban/modal and /user-ban/set under aksrt/v1,
  guarded by check_permission, persisted through User\Restriction.
- Submit semantics match zib_ajax_set_user_ban: ban reloads the page,
  unban only closes the modal.

Cleanup (no dual-track): the backend ban-restrictions tab, its form
handlers and flash keys are removed; ticket processing links to the
frontend user homepage as the reference theme does; admin/report and
appeal notification URLs move to type/status query args.

Verification: 337/337 widget-system contract tests, 41/41 admin and
front-submit tests, php -l clean on all changed PHP files.
2026-09-20 18:42:53 +08:00

263 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AKSRT User Ban — 前台“禁封/解封用户”弹窗
*
* 1:1 复刻参考主题 Zibll 前台禁封能力:
* - zib_get_edit_user_ban_link → [data-aksrt-ban] 委托入口(三点菜单内)
* - zib_get_set_user_ban_modal → REST /user-ban/modal 拉取弹窗内容
* - zib_ajax_set_user_ban → REST /user-ban/set 提交
*
* 弹窗经 AksrtModalRuntime.openRemote 打开(与参考主题 refresh_modal 每次刷新
* 同语义):桌面 600px 宽(参考主题未传 data-class → Bootstrap .modal-dialog
* 默认宽 600px)/ min-height 360px(data-height=360 → .modal-content 初始高度),
* 移动端底部抽屉(mobileBottom)。提交成功语义与参考主题 zib_ajax_set_user_ban
* 返回值一致:禁封 → reload:true(整页刷新),解封 → hide_modal:true(仅关闭弹窗)。
*
* @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) || '';
var MODAL_HEIGHT = 360;
function openBanModal(userId) {
if (!window.AksrtModalRuntime || typeof window.AksrtModalRuntime.openRemote !== 'function') {
notyf('禁封组件加载失败,请刷新页面后重试', 'danger');
return;
}
window.AksrtModalRuntime.openRemote({
url: API_ROOT + '/user-ban/modal',
method: 'GET',
query: { user_id: userId },
cache: false,
height: MODAL_HEIGHT,
mobileBottom: true,
kind: 'user-ban',
className: 'aksrt-user-ban-modal',
onContent: function (html, session) {
if (session && session.shell) bindBanModal(session.shell);
},
});
}
function collectPayload(shell, userId, unban) {
var payload = { user_id: userId, ban: unban ? 0 : 1 };
if (unban) {
var descInput = shell.querySelector('[data-aksrt-ban-form] [name="desc"]');
payload.desc = (descInput && descInput.value || '').trim();
return payload;
}
var form = shell.querySelector('[data-aksrt-ban-form]');
if (!form) return payload;
var banInput = form.querySelector('input[name="ban"]:checked');
payload.ban = banInput ? parseInt(banInput.value, 10) : 1;
var timeInput = form.querySelector('input[name="time_day"]:checked');
payload.time_day = timeInput ? parseInt(timeInput.value, 10) : 7;
var reasonInput = form.querySelector('input[name="reason"]:checked');
payload.reason = reasonInput ? reasonInput.value : '';
var otherInput = form.querySelector('[name="reason_other"]');
payload.reason_other = (otherInput && otherInput.value || '').trim();
var desc = form.querySelector('[name="desc"]');
payload.desc = (desc && desc.value || '').trim();
payload.delete_content = !!(form.querySelector('[name="delete_content"]') || {}).checked;
payload.no_appeal = !!(form.querySelector('[name="no_appeal"]') || {}).checked;
var noAppealDesc = form.querySelector('[name="no_appeal_desc"]');
payload.no_appeal_desc = (noAppealDesc && noAppealDesc.value || '').trim();
return payload;
}
function submitBan(shell, userId, unban) {
var payload = collectPayload(shell, userId, unban);
if (!unban) {
var reason = payload.reason === 'other' || !payload.reason ? payload.reason_other : payload.reason;
if (!reason) {
notyf('请选择或输入禁封原因', 'danger');
return;
}
if (payload.no_appeal && !payload.no_appeal_desc) {
notyf('请输入禁止用户申诉的说明或原因', 'danger');
return;
}
}
var submitBtn = shell.querySelector(unban ? '[data-aksrt-unban-submit]' : '[data-aksrt-ban-submit]');
if (submitBtn) submitBtn.disabled = true;
notyf(unban ? '正在解封…' : '正在提交禁封…', 'load', 3000, 'aksrt-ban-submit');
window
.fetch(API_ROOT + '/user-ban/set', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': NONCE },
body: JSON.stringify(payload),
})
.then(function (res) { return res.json(); })
.then(function (result) {
if (result && result.success) {
notyf(result.message || '操作成功', 'success');
// 参考主题 zib_ajax_set_user_ban 返回值语义:
// 禁封 → reload:true(整页刷新,入口按钮状态随页面重渲染);
// 解封 → hide_modal:true(仅关闭弹窗)。
if (window.AksrtModalRuntime && typeof window.AksrtModalRuntime.close === 'function') {
window.AksrtModalRuntime.close('submit');
}
if (!unban) window.location.reload();
} else {
if (submitBtn) submitBtn.disabled = false;
notyf((result && result.message) || '操作失败', 'danger');
}
})
.catch(function () {
if (submitBtn) submitBtn.disabled = false;
notyf('网络错误,请重试', 'danger');
})
.then(function () {
var toast = document.getElementById('aksrt-ban-submit');
if (toast) toast.remove();
});
}
function bindAutoHeight(textarea) {
// 1:1 复刻参考主题 textarea[autoHeight] 行为(input/focus 触发):
// 高度 = scrollHeight + 上下边框;超过 maxHeight 属性则锁定 maxHeight 并
// 打开自身滚动条(参考主题 main.js autoHeight 同语义)。
if (!textarea || textarea.dataset.aksrtAutoHeight === '1') return;
textarea.dataset.aksrtAutoHeight = '1';
var grow = function () {
var style = getComputedStyle(textarea);
var border = (parseInt(style.getPropertyValue('border-top-width'), 10) || 0)
+ (parseInt(style.getPropertyValue('border-bottom-width'), 10) || 0);
var max = parseInt(textarea.getAttribute('maxHeight'), 10) || 0;
textarea.style.height = 'auto';
if (max > textarea.scrollHeight) {
textarea.style.height = textarea.scrollHeight + border + 'px';
textarea.style.overflow = 'hidden';
} else {
textarea.style.height = max + 'px';
textarea.style.overflow = 'auto';
}
};
textarea.addEventListener('input', grow);
textarea.addEventListener('focus', grow);
if (textarea.offsetHeight > 0) grow();
}
function syncBanForm(shell) {
var form = shell.querySelector('[data-aksrt-ban-form]');
if (!form || form.getAttribute('data-aksrt-ban-mode') !== 'ban') return;
var banInput = form.querySelector('input[name="ban"]:checked');
var ban = banInput ? banInput.value : '1';
Array.prototype.forEach.call(form.querySelectorAll('[data-aksrt-ban-desc]'), function (el) {
el.style.display = el.getAttribute('data-aksrt-ban-desc') === ban ? '' : 'none';
});
var reasonInput = form.querySelector('input[name="reason"]:checked');
var otherBox = form.querySelector('[data-aksrt-ban-reason-other]');
if (otherBox) otherBox.style.display = reasonInput && reasonInput.value === 'other' ? '' : 'none';
var noAppealBox = form.querySelector('[data-aksrt-ban-noappeal-desc]');
var noAppealInput = form.querySelector('[name="no_appeal"]');
if (noAppealBox) noAppealBox.style.display = noAppealInput && noAppealInput.checked ? '' : 'none';
}
function bindBanModal(shell) {
Array.prototype.forEach.call(shell.querySelectorAll('textarea.form-control'), bindAutoHeight);
syncBanForm(shell);
}
/**
* 收起封面三点菜单面板(参考主题 author_header_drop_lists 菜单项点击后
* 不自动收起,需手动关闭;评论区与文章作者下拉由各自脚本点击项后自动收起)。
*/
function closeCoverMenu(trigger) {
var menu = trigger && trigger.closest ? trigger.closest('[data-aksrt-menu]') : null;
if (!menu) return;
var panel = menu.querySelector('.aksrt-cover-menu__panel');
if (panel) panel.hidden = true;
var menuTrigger = menu.querySelector('.aksrt-cover-menu__trigger');
if (menuTrigger) menuTrigger.setAttribute('aria-expanded', 'false');
}
function initBanBindings() {
if (document.documentElement.dataset.aksrtBanInitialized === '1') return;
document.documentElement.dataset.aksrtBanInitialized = '1';
document.addEventListener('click', function (event) {
if (!event.target || !event.target.closest) return;
// 禁封 / 解封入口(封面菜单按钮 / 评论区菜单项 / 文章作者菜单项)。
var banBtn = event.target.closest('[data-aksrt-ban]');
if (banBtn) {
event.preventDefault();
var userId = parseInt(banBtn.getAttribute('data-aksrt-ban'), 10);
if (userId > 0) {
closeCoverMenu(banBtn);
openBanModal(userId);
}
return;
}
// 弹窗内交互(事件委托到 document,弹窗壳由 runtime portal 到 body)。
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
? window.AksrtModalRuntime.getActive()
: null;
if (!active || !active.contains(event.target)) return;
var shell = active;
var form = shell.querySelector('[data-aksrt-ban-form]');
var modalUser = form ? parseInt(form.getAttribute('data-aksrt-ban-user'), 10) : 0;
var submitBtn = event.target.closest('[data-aksrt-ban-submit]');
if (submitBtn) {
event.preventDefault();
submitBtn.disabled = true;
submitBan(shell, modalUser, false);
return;
}
var unbanBtn = event.target.closest('[data-aksrt-unban-submit]');
if (unbanBtn) {
event.preventDefault();
unbanBtn.disabled = true;
submitBan(shell, modalUser, true);
return;
}
});
document.addEventListener('change', function (event) {
var active = window.AksrtModalRuntime && window.AksrtModalRuntime.getActive
? window.AksrtModalRuntime.getActive()
: null;
if (!active || !event.target || !active.contains(event.target)) return;
syncBanForm(active);
});
// 弹窗内容注入后由 onContent 绑定;bs.modal 展示契约下对 user-ban 弹窗做同步。
jQuery(document).on('loaded.bs.modal show.bs.modal', function (event) {
var shell = event.target && event.target.classList && event.target.classList.contains('aksrt-user-ban-modal')
? event.target
: null;
if (shell) syncBanForm(shell);
});
}
jQuery(document)
.off('auto_fun.aksrt-ban')
.on('auto_fun.aksrt-ban', initBanBindings);
})();