/** * AKSRT User Center — 本人中心专属脚本 * * 从 page-profile.php 内联 JS 迁移而来。 * 依赖全局 aksrtUserCenter 配置对象(由模板注入)。 * * @package AKSRT */ /* global aksrtUserCenter, notyf */ (function() { 'use strict'; var cfg = window.aksrtUserCenter || {}; var ajaxUrl = cfg.ajaxUrl || ''; var publicNonce = cfg.publicNonce || ''; var settingsNonce = cfg.settingsNonce || ''; var submitNonce = cfg.submitNonce || ''; var userId = cfg.userId || 0; var i18n = cfg.i18n || {}; var tabAliases = cfg.tabAliases || {}; var tabUrls = cfg.tabUrls || {}; function captchaAvailable(scene, scope) { var captcha = window.AKSRTCaptcha; if (!captcha) return false; var element = scope && scope.nodeType ? scope : null; if (element && element.matches && element.matches('[data-aksrt-captcha-config][data-scene="' + scene + '"]')) return true; if (element && element.querySelector && element.querySelector('[data-aksrt-captcha-config][data-scene="' + scene + '"]')) return true; var mount = element && element.closest ? element.closest('[data-aksrt-captcha-config][data-scene="' + scene + '"]') : null; return !!mount; } function runCaptcha(scene, scope) { return window.AKSRTCaptcha ? window.AKSRTCaptcha.run(scene, scope) : Promise.resolve({}); } function resetCaptcha(scene, scope) { if (window.AKSRTCaptcha) window.AKSRTCaptcha.reset(scene, scope); } function setSettingsSubmitLabel(button, text) { var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); if (label) label.textContent = text; } /* ==================== Dashboard 导航 ==================== */ function initDashboardNavigation() { if (document.documentElement.dataset.aksrtUserCenterNavigationInitialized === 'true') return; var tabContainer = document.querySelector('.uc-mobile-tabs'); if (!tabContainer) return; document.documentElement.dataset.aksrtUserCenterNavigationInitialized = 'true'; var tabs = tabContainer.querySelectorAll('[data-tab]'); var sidebarBtns = document.querySelectorAll('.uc-sidebar [data-tab]'); var panels = {}; var defaultTab = cfg.defaultTab || 'profile'; var drawerTrigger = document.getElementById('uc-drawer-trigger'); var drawerOverlay = document.getElementById('uc-drawer-overlay'); var sidebar = document.querySelector('.uc-sidebar'); tabs.forEach(function(tab) { var name = tab.getAttribute('data-tab'); var panel = document.getElementById('panel-' + name); if (panel) { panels[name] = { tab: tab, panel: panel }; } }); function openDrawer() { if (sidebar) sidebar.classList.add('is-open'); if (drawerOverlay) drawerOverlay.classList.add('is-visible'); document.body.style.overflow = 'hidden'; } function closeDrawer() { if (sidebar) sidebar.classList.remove('is-open'); if (drawerOverlay) drawerOverlay.classList.remove('is-visible'); document.body.style.overflow = ''; } function switchTo(name) { if (!panels[name]) name = defaultTab; Object.keys(panels).forEach(function(key) { var active = key === name; panels[key].tab.classList.toggle('is-active', active); panels[key].tab.setAttribute('aria-selected', active ? 'true' : 'false'); panels[key].panel.classList.toggle('is-active', active); panels[key].panel.hidden = !active; }); sidebarBtns.forEach(function(btn) { btn.classList.toggle('is-active', btn.getAttribute('data-tab') === name); }); closeDrawer(); } function loadPanel(name, forceLoad, queryParams) { if (!panels[name]) return Promise.reject(new Error('panel-not-found')); var target = panels[name].panel; if (!forceLoad && target.dataset.panelLoaded === 'true') return Promise.resolve(); if (target._aksrtPanelRequest) return target._aksrtPanelRequest; var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; if (!restUrl) return Promise.reject(new Error('rest-unavailable')); if (name === 'orders' && (!window.AKSRTOrderCenter || typeof window.AKSRTOrderCenter.init !== 'function')) { return Promise.reject(new Error('orders-initializer-unavailable')); } target.classList.add('is-loading'); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/' + encodeURIComponent(name); if (name === 'creator' || name === 'revenue' || name === 'posts' || name === 'follow') { var params = queryParams || new URLSearchParams(window.location.search); var allowedParams = []; if (name === 'creator') allowedParams = ['income_page']; if (name === 'revenue') allowedParams = ['reward_tab', 'commission_page', 'user_page']; if (name === 'posts') allowedParams = ['post_status', 'posts_page']; if (name === 'follow') allowedParams = ['follow_list', 'follow_page']; allowedParams.forEach(function(key) { if (params.get(key)) url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(params.get(key)); }); } target._aksrtPanelRequest = fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') throw new Error('panel-load-failed'); if (name === 'orders' && window.AKSRTOrderCenter && typeof window.AKSRTOrderCenter.destroy === 'function') { window.AKSRTOrderCenter.destroy(target); } target.innerHTML = payload.data.html; if (window.jQuery) jQuery(document).trigger('post_ajax.ed', [target]); if (typeof window.auto_fun === 'function') window.auto_fun(); target.dataset.panelLoaded = 'true'; }); }).finally(function() { target._aksrtPanelRequest = null; target.classList.remove('is-loading'); }); return target._aksrtPanelRequest; } /* Level tabs are nested inside the active panel and must not enter the * main user-center navigation handler. */ function activateLevelTab(link) { var page = link.closest('.aksrt-level-page'); var paneId = link.getAttribute('data-aksrt-level-tab') || ''; if (!page || !paneId) return false; var pane = Array.prototype.find.call(page.querySelectorAll('.aksrt-level-experience .tab-pane'), function(item) { return item.id === paneId; }); if (!pane) return false; page.querySelectorAll('[data-aksrt-level-tab]').forEach(function(tab) { var active = tab.getAttribute('data-aksrt-level-tab') === paneId; tab.classList.toggle('is-active', active); if (tab.getAttribute('role') === 'tab') tab.setAttribute('aria-selected', active ? 'true' : 'false'); }); page.querySelectorAll('.aksrt-level-experience .tab-pane').forEach(function(item) { var active = item === pane; item.classList.toggle('active', active); item.classList.toggle('in', active); item.hidden = !active; }); return true; } /* 推广收益三个 Tab 的内容在首屏已由 PHP 全部直出,切换只需本地切换 * 显隐,无需任何网络往返,消除切换阻塞。分页仍走 loadRevenueTab。 */ function toggleRevenueTab(tabName) { var target = document.getElementById('panel-revenue'); if (!target) return false; var pane = target.querySelector('[data-reward-pane="' + tabName + '"]'); if (!pane) return false; target.querySelectorAll('[data-reward-pane]').forEach(function(node) { var active = node === pane; node.hidden = !active; }); target.querySelectorAll('.uc-reward-tabs a[data-reward-tab]').forEach(function(link) { var active = link.getAttribute('data-reward-tab') === tabName; link.classList.toggle('is-active', active); if (active) link.setAttribute('aria-current', 'page'); else link.removeAttribute('aria-current'); }); return true; } function loadRevenueTab(queryParams) { var target = document.getElementById('panel-revenue'); var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; if (!target || !restUrl) return Promise.reject(new Error('revenue-panel-unavailable')); var params = queryParams || new URLSearchParams(); var url = restUrl.replace(/\/$/, '') + '/user-center/panel/revenue'; params.forEach(function(value, key) { if (key === 'reward_tab' || key === 'commission_page' || key === 'user_page') { url += (url.indexOf('?') === -1 ? '?' : '&') + key + '=' + encodeURIComponent(value); } }); target.classList.add('is-loading'); return fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || '' } }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success || !payload.data || typeof payload.data.html !== 'string') { throw new Error('revenue-tab-load-failed'); } target.innerHTML = payload.data.html; target.dataset.panelLoaded = 'true'; if (window.jQuery) jQuery(document).trigger('post_ajax.ed', [target]); if (typeof window.auto_fun === 'function') window.auto_fun(); }); }).finally(function() { target.classList.remove('is-loading'); }); } function activate(name, updateHistory, forceLoad, queryParams) { if (!panels[name]) name = defaultTab; return loadPanel(name, forceLoad, queryParams).then(function() { switchTo(name); if (updateHistory) updateUrl(name); if (name === 'revenue' && typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit(); if (name === 'revenue' && typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit(); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); } function tabFromUrl() { var pathParts = window.location.pathname.replace(/\/+$/, '').split('/'); var pathTab = pathParts.length > 2 && pathParts[pathParts.length - 2] === 'user' ? pathParts[pathParts.length - 1] : ''; var name = pathTab || (cfg.activeTab || defaultTab); if (name === 'balance') name = 'credits'; return tabAliases[name] || name; } function updateUrl(name, sourceUrl) { if (!window.history.pushState) return; var pathTab = name === 'credits' ? 'balance' : name; var u = new URL('/user/' + pathTab + '/', window.location.origin); if (sourceUrl && name !== 'revenue' && name !== 'orders' && name !== 'credits') { var source = new URL(sourceUrl, window.location.href); source.searchParams.forEach(function(value, key) { u.searchParams.set(key, value); }); } window.history.pushState({}, '', u); } // Delegate every internal user-center link. This also covers markup injected // after a REST panel refresh, so panel navigation never falls back to a full load. document.addEventListener('click', function(e) { var levelTab = e.target && e.target.closest ? e.target.closest('[data-aksrt-level-tab]') : null; if (levelTab && activateLevelTab(levelTab)) { e.preventDefault(); e.stopPropagation(); return; } var withdrawOpen = e.target && e.target.closest ? e.target.closest('[data-aksrt-withdraw-open]') : null; if (withdrawOpen) { var withdrawModal = document.querySelector('[data-aksrt-withdraw-modal]'); if (withdrawModal) { e.preventDefault(); e.stopPropagation(); withdrawModal.hidden = false; withdrawModal.classList.add('is-open'); document.body.classList.add('uc-modal-open'); } return; } var withdrawClose = e.target && e.target.closest ? e.target.closest('[data-aksrt-withdraw-close]') : null; if (withdrawClose) { var openWithdrawModal = withdrawClose.closest('[data-aksrt-withdraw-modal]'); if (openWithdrawModal) { e.preventDefault(); e.stopPropagation(); openWithdrawModal.hidden = true; openWithdrawModal.classList.remove('is-open'); if (!document.querySelector('[data-aksrt-withdraw-modal].is-open')) document.body.classList.remove('uc-modal-open'); } return; } var link = e.target && e.target.closest ? e.target.closest('a[href]') : null; if (!link || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || link.target === '_blank') return; var url; try { url = new URL(link.href, window.location.href); } catch (_) { return; } if (url.origin !== window.location.origin) return; if (link.closest('.uc-reward-tabs, .uc-reward-pagination')) { e.preventDefault(); e.stopPropagation(); var rewardTab = link.getAttribute('data-reward-tab'); /* Tab 切换:内容已全部直出,纯本地切换显隐,零网络往返。 */ if (rewardTab && toggleRevenueTab(rewardTab)) { if (window.history.replaceState) window.history.replaceState({}, '', '/user/revenue/'); return; } /* 分页:需要新一页数据,仍走 REST 片段加载。 */ loadRevenueTab(url.searchParams).then(function() { if (window.history.replaceState) window.history.replaceState({}, '', '/user/revenue/'); if (typeof window.AKSRTAffiliateRevenueInit === 'function') window.AKSRTAffiliateRevenueInit(); if (typeof window.AKSRTWithdrawInit === 'function') window.AKSRTWithdrawInit(); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); return; } var parts = url.pathname.replace(/\/+$/, '').split('/'); var pathTab = parts.length > 2 && parts[parts.length - 2] === 'user' ? parts[parts.length - 1] : ''; var name = link.getAttribute('data-tab') || pathTab; if (name === 'balance') name = 'credits'; name = tabAliases[name] || name; if (!name || !panels[name]) return; e.preventDefault(); var hasQuery = url.search !== '' && name !== 'credits'; activate(name, false, hasQuery, name === 'credits' ? null : url.searchParams).then(function() { updateUrl(name, name === 'credits' ? '' : url.href); }); }, true); var creatorPanel = panels.creator ? panels.creator.panel : null; if (creatorPanel) { creatorPanel.addEventListener('click', function(e) { var link = e.target && e.target.closest ? e.target.closest('.creator-income__pagination a') : null; if (!link || e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; e.preventDefault(); var pageUrl = new URL(link.href, window.location.href); loadPanel('creator', true, pageUrl.searchParams).then(function() { switchTo('creator'); if (window.history.pushState) window.history.pushState({}, '', pageUrl.toString()); }).catch(function() { notyf(i18n.networkError || '加载失败,请重试', 'danger'); }); }); } window.addEventListener('popstate', function() { var current = new URL(window.location.href); ['wallet_page', 'credits_page', 'asset', 'page'].forEach(function (key) { current.searchParams.delete(key); }); if (current.href !== window.location.href && window.history.replaceState) { window.history.replaceState({}, '', current.pathname + current.search + current.hash); } activate(tabFromUrl(), false, false, null); }); if (drawerTrigger) drawerTrigger.addEventListener('click', openDrawer); if (drawerOverlay) drawerOverlay.addEventListener('click', closeDrawer); var initialTab = tabFromUrl(); if (initialTab === 'credits' && window.history && window.history.replaceState) { var cleanUrl = new URL(window.location.href); ['wallet_page', 'credits_page', 'asset', 'page'].forEach(function (key) { cleanUrl.searchParams.delete(key); }); window.history.replaceState({}, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash); } if (initialTab === 'orders' && window.location.search && window.history.replaceState) { window.history.replaceState({}, '', window.location.pathname + window.location.hash); } activate(initialTab, false); } /* ==================== 个人信息保存 ==================== */ function initProfileForm(root) { root = root || document; var form = root.id === 'profile-form' ? root : root.querySelector('#profile-form'); if (!form) return; if (form.dataset.aksrtProfileInitialized === 'true') return; form.dataset.aksrtProfileInitialized = 'true'; form.addEventListener('submit', function(e) { e.preventDefault(); var btn = form.querySelector('#profile-save-btn'); if (!btn || btn.disabled) return; btn.disabled = true; btn.textContent = i18n.saving || '保存中…'; notyf(i18n.saving || '正在保存资料…', 'load', '', 'aksrt-profile-save-toast'); var data = new FormData(form); data.set('user_id', String(userId)); fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data }) .then(function(r) { return r.json(); }) .then(function(res) { btn.disabled = false; btn.textContent = i18n.saveBtn || '保存修改'; if (res.success) { notyf((res.data && res.data.message) || i18n.saved || '保存成功!', 'success', 5, 'aksrt-profile-save-toast'); } else { notyf((res.data && res.data.message) || i18n.saveFailed || '保存失败', 'danger', 5, 'aksrt-profile-save-toast'); } }) .catch(function() { btn.disabled = false; btn.textContent = i18n.saveBtn || '保存修改'; notyf(i18n.networkError || '网络错误,请重试', 'danger', 5, 'aksrt-profile-save-toast'); }); }); } function initAuthForm() { var root = document.querySelector('[data-identity-root]'); if (root && window.AKSRTUserCenterAuth && typeof window.AKSRTUserCenterAuth.init === 'function') { window.AKSRTUserCenterAuth.init(root); } } /* ==================== 列表内取消收藏 ==================== */ function initUnfavorite() { var list = document.querySelector('.profile-list[data-list="favorites"]'); if (!list) return; list.addEventListener('click', function(e) { var btn = e.target.closest('[data-action="unfavorite"]'); if (!btn) return; e.preventDefault(); var postId = btn.getAttribute('data-post-id'); if (!postId || btn.disabled) return; if (!confirm(i18n.confirmUnfavorite || '确定取消收藏这篇文章?')) return; btn.disabled = true; btn.classList.add('is-loading'); var body = new URLSearchParams(); body.set('action', 'aksrt_toggle_favorite'); body.set('postId', postId); body.set('nonce', publicNonce); body.set('active', '0'); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }) .then(function(r) { return r.json(); }) .then(function(res) { if (res && res.success) { var item = btn.closest('.profile-list-item'); if (item) { item.classList.add('is-removing'); setTimeout(function() { if (item.parentNode) item.parentNode.removeChild(item); if (!list.querySelector('.profile-list-item')) { // Show empty state instead of reloading var empty = document.createElement('div'); empty.className = 'profile-empty'; empty.innerHTML = '

暂无收藏

你还没有收藏任何文章。

'; list.parentNode.replaceChild(empty, list); } }, 220); } notyf(i18n.unfavorited || '已取消收藏'); } else { btn.disabled = false; btn.classList.remove('is-loading'); notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }) .catch(function() { btn.disabled = false; btn.classList.remove('is-loading'); notyf(i18n.networkError || '网络错误', 'danger'); }); }); } function initPostDeletion() { if (document.documentElement.dataset.aksrtPostDeletionBound === 'true') return; document.documentElement.dataset.aksrtPostDeletionBound = 'true'; document.addEventListener('click', function(e) { var btn = e.target && e.target.closest ? e.target.closest('[data-action="delete-post"]') : null; if (!btn || btn.disabled || !confirm('确定删除这篇文章?')) return; e.preventDefault(); btn.disabled = true; var body = new URLSearchParams(); body.set('action', 'aksrt_delete_post'); body.set('post_id', btn.getAttribute('data-post-id') || ''); body.set('nonce', btn.getAttribute('data-nonce') || submitNonce); fetch(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(); }) .then(function(response) { if (!response || !response.success) throw new Error('delete-failed'); var item = btn.closest('.profile-list-item'); if (item) item.remove(); notyf('文章已删除'); }) .catch(function() { btn.disabled = false; notyf(i18n.operationFailed || '操作失败', 'danger'); }); }); } /* ==================== 通知中心 ==================== */ function initNotifications() { var list = document.getElementById('aksrt-notification-list'); var markAllBtn = document.getElementById('aksrt-mark-all-notifications-read'); var markSelectedBtn = document.getElementById('aksrt-mark-selected-notifications-read'); var deleteSelectedBtn = document.getElementById('aksrt-delete-selected-notifications'); if (!list) return; function markRead(notificationId, markAll) { var body = new URLSearchParams(); body.set('action', 'aksrt_mark_notification_read'); body.set('nonce', publicNonce); if (markAll) { body.set('mark_all', '1'); } else { body.set('notification_id', notificationId); } return fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }).then(function(r) { return r.json(); }); } // 单条标记已读 / 删除 list.addEventListener('click', function(e) { var btn = e.target.closest('.aksrt-notification-read-btn'); if (btn) { var id = btn.getAttribute('data-notification-id'); if (!id || btn.disabled) return; btn.disabled = true; markRead(id, false).then(function(res) { if (res && res.success) { var row = btn.closest('[data-notification-id]'); if (row) row.classList.add('is-read'); btn.remove(); notyf(i18n.markedRead || '已标记为已读'); } else { btn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { btn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); return; } var delBtn = e.target.closest('.aksrt-notification-delete-btn'); if (!delBtn) return; var delId = delBtn.getAttribute('data-notification-id'); if (!delId || delBtn.disabled) return; if (!confirm(i18n.confirmDeleteNotification || '确定删除这条通知?')) return; delBtn.disabled = true; var body = new URLSearchParams(); body.set('action', 'aksrt_delete_notification'); body.set('nonce', publicNonce); body.set('notification_id', delId); fetch(ajaxUrl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, credentials: 'same-origin', body: body.toString() }).then(function(r) { return r.json(); }).then(function(res) { if (res && res.success) { var delRow = delBtn.closest('[data-notification-id]'); if (delRow) delRow.remove(); notyf(i18n.notificationDeleted || '已删除通知'); } else { delBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { delBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); // 全部已读 if (markAllBtn) { markAllBtn.addEventListener('click', function() { if (markAllBtn.disabled) return; markAllBtn.disabled = true; markRead('', true).then(function(res) { if (res && res.success) { list.querySelectorAll('[data-notification-id]').forEach(function(row) { row.classList.add('is-read'); }); list.querySelectorAll('.aksrt-notification-read-btn').forEach(function(b) { b.remove(); }); notyf(i18n.allMarkedRead || '已全部标记为已读'); } else { markAllBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }).catch(function() { markAllBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } // 批量已读 if (markSelectedBtn) { markSelectedBtn.addEventListener('click', function() { var checked = Array.prototype.slice.call(list.querySelectorAll('.aksrt-notification-check:checked')).map(function(input) { return input.value; }); if (!checked.length) { notyf(i18n.selectNotifications || '请先选择要标记的通知', 'warning'); return; } markSelectedBtn.disabled = true; var chain = Promise.resolve(); checked.forEach(function(id) { chain = chain.then(function() { return markRead(id, false); }); }); chain.then(function() { checked.forEach(function(id) { var row = list.querySelector('[data-notification-id="' + id + '"]'); if (row) row.classList.add('is-read'); var b = row ? row.querySelector('.aksrt-notification-read-btn') : null; if (b) b.remove(); }); notyf(i18n.selectedMarkedRead || '已标记所选通知为已读'); markSelectedBtn.disabled = false; }).catch(function() { markSelectedBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } // 批量删除 if (deleteSelectedBtn) { deleteSelectedBtn.addEventListener('click', function() { var checked = Array.prototype.slice.call(list.querySelectorAll('.aksrt-notification-check:checked')).map(function(input) { return input.value; }); if (!checked.length) { notyf(i18n.selectNotifications || '请先选择要删除的通知', 'warning'); return; } if (!confirm(i18n.confirmBatchDelete || '确定批量删除所选通知?')) return; deleteSelectedBtn.disabled = true; var body = new FormData(); body.append('action', 'aksrt_delete_notifications'); body.append('nonce', publicNonce); checked.forEach(function(id) { body.append('notification_ids[]', id); }); fetch(ajaxUrl, { method: 'POST', body: body, credentials: 'same-origin' }) .then(function(r) { return r.json(); }) .then(function(res) { if (res && res.success) { checked.forEach(function(id) { var row = list.querySelector('[data-notification-id="' + id + '"]'); if (row) row.remove(); }); notyf(i18n.batchDeleted || '已删除所选通知'); } else { deleteSelectedBtn.disabled = false; notyf((res && res.data && res.data.message) || (i18n.operationFailed || '操作失败'), 'danger'); } }) .catch(function() { deleteSelectedBtn.disabled = false; notyf(i18n.networkError || '网络错误', 'danger'); }); }); } } /* ==================== 消息通知偏好 ==================== */ function initNotificationPreferences(root) { root = root || document; var form = root.matches && root.matches('[data-aksrt-notification-preferences]') ? root : root.querySelector('[data-aksrt-notification-preferences]'); if (!form || form.dataset.aksrtInitialized === 'true') return; form.dataset.aksrtInitialized = 'true'; form.addEventListener('submit', function(e) { e.preventDefault(); var button = form.querySelector('[type="submit"]'); if (!button || button.disabled) return; var toastId = 'aksrt-notification-preferences-toast'; var restUrl = (window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || ''; var nonce = (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || cfg.restNonce || ''; var originalText = button.textContent; var preferences = {}; ['posts', 'like', 'system'].forEach(function(key) { var input = form.querySelector('[name="' + key + '"]'); preferences[key] = Boolean(input && input.checked); }); button.disabled = true; button.textContent = i18n.saving || '保存中…'; notyf(i18n.saving || '正在保存设置…', 'load', '', toastId); fetch(restUrl.replace(/\/$/, '') + '/user/notification-preferences', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify({ preferences: preferences }) }).then(function(response) { return response.json().then(function(payload) { if (!response.ok || !payload || !payload.success) { throw new Error(payload && payload.message ? payload.message : (i18n.saveFailed || '保存失败')); } return payload; }); }).then(function(payload) { notyf(payload.message || i18n.saved || '设置已保存', 'success', 5, toastId); }).catch(function(error) { notyf(error.message || i18n.networkError || '网络错误,请重试', 'danger', 5, toastId); }).finally(function() { button.disabled = false; button.textContent = originalText; }); }); } /* ==================== 账号设置:弹窗 / 密码 / 邮箱 / 绑定 ==================== */ function initSettings(root) { if (!ajaxUrl || !settingsNonce) return; root = root || document; function openSettingsModal(kind) { var runtime = window.AksrtModalRuntime; var template = document.querySelector('template[data-aksrt-settings-modal-template="' + kind + '"]'); if (!runtime || typeof runtime.open !== 'function' || !template) return; runtime.open({ html: template.innerHTML, kind: 'account-settings', className: 'aksrt-user-settings-modal modal-mini full-sm', mobileBottom: true, onContent: function (_, session) { var shell = session && session.shell; if (!shell) return; var dialog = shell.querySelector('[data-account-dialog]'); var title = dialog && dialog.querySelector('[data-account-password-title], .modal-colorful-header .text-center > div:last-child'); if (title && title.textContent.trim()) shell.setAttribute('aria-label', title.textContent.trim()); bindDialog(shell); }, }); } function setStep(dialog, key) { dialog.querySelectorAll('[data-account-step]').forEach(function (panel) { panel.classList.toggle('is-active', panel.getAttribute('data-account-step') === key); }); var panels = Array.prototype.slice.call(dialog.querySelectorAll('[data-account-step]')); var index = Math.max(0, panels.findIndex(function (panel) { return panel.getAttribute('data-account-step') === key; })); dialog.querySelectorAll('.account-step-list li').forEach(function (item, itemIndex) { item.classList.toggle('active', itemIndex === index); item.classList.toggle('done', itemIndex < index); }); } function resetCountdown(button) { if (!button) return; if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer); button.__aksrtCountdownTimer = null; button.disabled = false; button.classList.remove('is-counting'); button.textContent = button.getAttribute('data-idle-label') || '发送验证码'; } function startCountdown(button, seconds) { var idleText = button.textContent; var left = Number(seconds || 60); if (button.__aksrtCountdownTimer) window.clearInterval(button.__aksrtCountdownTimer); button.disabled = true; button.classList.add('is-counting'); button.setAttribute('data-idle-label', idleText); button.textContent = left + 's'; button.__aksrtCountdownTimer = window.setInterval(function () { left -= 1; if (left <= 0) { resetCountdown(button); return; } button.textContent = left + 's'; }, 1000); } function settingsRestRoot() { return String((window.aksrtData && (window.aksrtData.restUrl || window.aksrtData.apiUrl)) || cfg.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, ''); } function settingsRestNonce() { return (window.aksrtData && (window.aksrtData.restNonce || window.aksrtData.nonce)) || cfg.restNonce || ''; } function formHasRequiredValues(form) { var required = form.querySelectorAll('[required]'); for (var i = 0; i < required.length; i++) { if (required[i].type === 'checkbox' && !required[i].checked) { required[i].focus(); notyf(i18n.agreeTerms || '请先同意相关协议', 'warning'); return false; } if (required[i].type !== 'checkbox' && !String(required[i].value || '').trim()) { required[i].focus(); notyf(i18n.fillRequired || '请填写必填项', 'warning'); return false; } } return true; } function sendEmailCode(button, form, scene) { if (!form || button.disabled) return; if (scene === 'change_email' && !String((form.querySelector('[name="new_email"]') || {}).value || '').trim()) { notyf('请输入邮箱', 'warning'); return; } runCaptcha('change_email', form).then(function (captchaPayload) { if (captchaPayload === null) return null; var data = new URLSearchParams(); data.set('action', 'aksrt_send_account_email_code'); data.set('_settings_nonce', settingsNonce); data.set('scene', scene); new FormData(form).forEach(function (value, key) { if (value !== '' && !data.has(key)) data.set(key, value); }); Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); }); button.disabled = true; return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() }); }).then(function (response) { return response ? response.json() : null; }).then(function (payload) { if (!payload) return; if (!payload.success) throw new Error((payload.data && payload.data.message) || '发送失败'); notyf((payload.data && payload.data.message) || '验证码已发送', 'success'); startCountdown(button, payload.data && payload.data.countdown); }).catch(function (error) { resetCountdown(button); notyf(error.message || '发送失败', 'danger'); }).finally(function () { resetCaptcha('change_email', form); }); } function sendPhoneCode(button, form, stage) { if (!form || button.disabled) return; var payload = { stage: stage }; if (stage === 'verify_old_phone') { var dialog = form.closest('[data-account-dialog="phone"]'); var boxes = dialog ? phoneDigitBoxes(dialog) : []; if (boxes.length) { var hiddenLength = parseInt(dialog.querySelector('[data-aksrt-phone-digits]').getAttribute('data-hidden-length'), 10) || boxes.length; var middle = phoneDigitsValue(dialog); if (middle.length !== hiddenLength) { notyf('请先补全手机号缺失的数字', 'warning'); return; } payload.middle = middle; } } else { var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim(); if (!phone) { notyf('请输入手机号', 'warning'); return; } payload.phone = phone; } runCaptcha('bind_phone', form).then(function (captchaPayload) { if (captchaPayload === null) return null; var restRoot = settingsRestRoot(); var nonce = settingsRestNonce(); Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); var request = fetch(restRoot + '/user/phone-code', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(payload) }); button.disabled = true; return request; }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { if (!result) return; if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败'); notyf(result.payload.message || '验证码已发送', 'success'); startCountdown(button, result.payload.data && result.payload.data.countdown); }).catch(function (error) { resetCountdown(button); notyf(error.message || '发送失败', 'danger'); }).finally(function () { resetCaptcha('bind_phone', form); }); } function postAjaxForm(form, action, scene, success) { if (!formHasRequiredValues(form)) return; var button = form.querySelector('[type="submit"]'); var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); var original = label ? label.textContent : ''; if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } runCaptcha(scene, form).then(function (captchaPayload) { if (captchaPayload === null) { if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } return null; } var data = new FormData(form); data.set('action', action); data.set('_settings_nonce', settingsNonce); Object.keys(captchaPayload || {}).forEach(function (key) { data.set(key, captchaPayload[key]); }); return fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', body: data }); }).then(function (response) { return response ? response.json() : null; }).then(function (payload) { if (!payload) return; if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } if (!payload.success) throw new Error((payload.data && payload.data.message) || '操作失败'); notyf((payload.data && payload.data.message) || '操作成功', 'success'); if (typeof success === 'function') success(payload); }).catch(function (error) { if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } notyf(error.message || '操作失败', 'danger'); }).finally(function () { resetCaptcha(scene, form); }); } function postPhoneForm(form, endpoint, success) { if (!formHasRequiredValues(form)) return; var button = form.querySelector('[type="submit"]'); var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); var original = label ? label.textContent : ''; runCaptcha('bind_phone', form).then(function (captchaPayload) { if (captchaPayload === null) return null; var restRoot = settingsRestRoot(); var nonce = settingsRestNonce(); if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } var payload = {}; new FormData(form).forEach(function (value, key) { payload[key] = value; }); Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); return fetch(restRoot + endpoint, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(payload) }); }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { if (!result) return; if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '操作失败'); if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } notyf(result.payload.message || '操作成功', 'success'); if (typeof success === 'function') success(result.payload); }).catch(function (error) { if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } notyf(error.message || '操作失败', 'danger'); }).finally(function () { resetCaptcha('bind_phone', form); }); } /* ---- 修改手机:脱敏中间位数字框(格数随号码长度变化) ---- */ function phoneDigitBoxes(dialog) { return Array.prototype.slice.call(dialog.querySelectorAll('[data-aksrt-phone-digits] .aksrt-phone-digit')); } function phoneDigitsValue(dialog) { return phoneDigitBoxes(dialog).map(function (input) { return String(input.value || '').replace(/\D/g, ''); }).join(''); } function setPhoneDigitsError(dialog, message) { var group = dialog.querySelector('[data-aksrt-phone-digits]'); var error = dialog.querySelector('[data-aksrt-phone-digits-error]'); if (error) { error.textContent = message || ''; error.hidden = !message; } if (group) group.classList.toggle('has-error', !!message); } function hidePhoneCodeArea(dialog, options) { var area = dialog.querySelector('[data-aksrt-phone-code-area]'); if (!area) return; var wasVisible = !area.hidden; area.hidden = true; area.querySelectorAll('.account-code-send,[data-aksrt-sms-trigger]').forEach(resetCountdown); if (wasVisible && options && options.destroyCode) { // 中间位被修改:销毁服务端已发放的老号验证码状态。 fetch(settingsRestRoot() + '/user/phone-code', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, body: JSON.stringify({ stage: 'reset_old_phone' }) }).catch(function () {}); } } function verifyPhoneMiddle(dialog) { var group = dialog.querySelector('[data-aksrt-phone-digits]'); var boxes = phoneDigitBoxes(dialog); if (!group || !boxes.length) return; var hiddenLength = parseInt(group.getAttribute('data-hidden-length'), 10) || boxes.length; var middle = phoneDigitsValue(dialog); if (middle.length !== hiddenLength) return; dialog.__aksrtPhoneMatchTicket = (dialog.__aksrtPhoneMatchTicket || 0) + 1; var ticket = dialog.__aksrtPhoneMatchTicket; fetch(settingsRestRoot() + '/user/phone-match', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, body: JSON.stringify({ middle: middle }) }).then(function (response) { return response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }); }).then(function (result) { if (ticket !== dialog.__aksrtPhoneMatchTicket) return; if (result.ok && result.payload.success) { var area = dialog.querySelector('[data-aksrt-phone-code-area]'); if (area) area.hidden = false; setPhoneDigitsError(dialog, ''); } else { hidePhoneCodeArea(dialog, { destroyCode: true }); setPhoneDigitsError(dialog, (result.payload && result.payload.message) || '填写的号码与绑定手机号不一致'); } }).catch(function () { if (ticket === dialog.__aksrtPhoneMatchTicket) { hidePhoneCodeArea(dialog, { destroyCode: false }); setPhoneDigitsError(dialog, '网络错误,请重试'); } }); } function initPhoneDigits(dialog) { var boxes = phoneDigitBoxes(dialog); if (!boxes.length || dialog.dataset.aksrtDigitsBound === 'true') return; dialog.dataset.aksrtDigitsBound = 'true'; boxes.forEach(function (input, index) { input.addEventListener('input', function () { var value = String(input.value || '').replace(/\D/g, ''); input.value = value === '' ? '' : value.charAt(value.length - 1); hidePhoneCodeArea(dialog, { destroyCode: true }); setPhoneDigitsError(dialog, ''); if (input.value !== '' && index < boxes.length - 1) boxes[index + 1].focus(); verifyPhoneMiddle(dialog); }); input.addEventListener('keydown', function (event) { if (event.key === 'Backspace' && !input.value && index > 0) { event.preventDefault(); boxes[index - 1].value = ''; boxes[index - 1].focus(); hidePhoneCodeArea(dialog, { destroyCode: true }); setPhoneDigitsError(dialog, ''); } else if (event.key === 'ArrowLeft' && index > 0) { boxes[index - 1].focus(); } else if (event.key === 'ArrowRight' && index < boxes.length - 1) { boxes[index + 1].focus(); } }); input.addEventListener('paste', function (event) { var text = String((event.clipboardData || window.clipboardData).getData('text') || ''); var digits = text.replace(/\D/g, ''); if (!digits) return; event.preventDefault(); for (var offset = 0; offset < boxes.length - index && offset < digits.length; offset++) { boxes[index + offset].value = digits.charAt(offset); } boxes[Math.min(index + digits.length, boxes.length - 1)].focus(); hidePhoneCodeArea(dialog, { destroyCode: true }); setPhoneDigitsError(dialog, ''); verifyPhoneMiddle(dialog); }); }); } function sendRecoveryCode(button, form) { if (!form || button.disabled) return; var target = String((form.querySelector('[name="target"]') || {}).value || '').trim(); if (!target) { notyf('请输入邮箱或手机号', 'warning'); return; } var dialog = form.closest('[data-account-dialog="password"]'); button.disabled = true; runCaptcha('lostpassword', form).then(function (captchaPayload) { if (captchaPayload === null) { notyf('请先完成人机验证', 'warning'); resetCountdown(button); return null; } var payload = { scene: 'resetpassword', target: target, channel: dialog ? (dialog.getAttribute('data-recovery-channel') || '') : '' }; Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); return fetch(settingsRestRoot() + '/auth/code', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, body: JSON.stringify(payload) }); }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { if (!result) return; if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '发送失败'); notyf((result.payload.data && result.payload.data.message) || result.payload.message || '验证码已发送', 'success'); startCountdown(button, (result.payload.data && result.payload.data.countdown) || result.payload.countdown); }).catch(function (error) { resetCountdown(button); notyf(error.message || '发送失败', 'danger'); }).finally(function () { resetCaptcha('lostpassword', form); }); } function postRecoveryForm(form) { if (!formHasRequiredValues(form)) return; var password = form.querySelector('[name="password"]'); var confirm = form.querySelector('[name="password_confirm"]'); if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; } var button = form.querySelector('[type="submit"]'); var label = button && button.querySelector('[data-aksrt-settings-submit-label]'); var original = label ? label.textContent : ''; var dialog = form.closest('[data-account-dialog="password"]'); if (button) { button.disabled = true; setSettingsSubmitLabel(button, i18n.submitting || '提交中...'); } runCaptcha('lostpassword', form).then(function (captchaPayload) { if (captchaPayload === null) { if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } return null; } var payload = {}; new FormData(form).forEach(function (value, key) { payload[key] = value; }); payload.channel = dialog ? (dialog.getAttribute('data-recovery-channel') || '') : ''; Object.keys(captchaPayload || {}).forEach(function (key) { payload[key] = captchaPayload[key]; }); return fetch(settingsRestRoot() + '/auth/reset-password', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': settingsRestNonce() }, body: JSON.stringify(payload) }); }).then(function (response) { return response ? response.json().then(function (payload) { return { ok: response.ok, payload: payload }; }) : null; }).then(function (result) { if (!result) return; if (!result.ok || !result.payload.success) throw new Error(result.payload.message || '重设密码失败'); if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } notyf(result.payload.message || (result.payload.data && result.payload.data.message) || '密码已重置', 'success'); window.setTimeout(function () { var redirect = result.payload.redirect || (result.payload.data && result.payload.data.redirect); if (redirect) window.location.assign(redirect); else window.location.reload(); }, 700); }).catch(function (error) { if (button) { button.disabled = false; setSettingsSubmitLabel(button, original); } notyf(error.message || '重设密码失败', 'danger'); }).finally(function () { resetCaptcha('lostpassword', form); }); } function bindDialog(shell) { var dialog = shell.querySelector('[data-account-dialog]'); if (!dialog || dialog.dataset.aksrtBound === 'true') return; dialog.dataset.aksrtBound = 'true'; dialog.querySelectorAll('.toggle-pwd').forEach(function (button) { button.addEventListener('click', function () { var input = dialog.querySelector('#' + button.getAttribute('data-target')); if (!input) return; input.type = input.type === 'password' ? 'text' : 'password'; var icon = button.querySelector('i'); if (icon) { icon.classList.toggle('fa-eye', input.type === 'password'); icon.classList.toggle('fa-eye-slash', input.type === 'text'); } }); }); var type = dialog.getAttribute('data-account-dialog'); if (type === 'password') { var recoveryTarget = dialog.querySelector('#pwd-recovery-target'); if (recoveryTarget && dialog.dataset.recoveryTarget) recoveryTarget.value = dialog.dataset.recoveryTarget; dialog.querySelectorAll('[data-account-recovery-open],[data-account-recovery-back]').forEach(function (button) { button.addEventListener('click', function () { var showRecovery = button.hasAttribute('data-account-recovery-open'); dialog.querySelectorAll('[data-account-password-pane]').forEach(function (pane) { pane.hidden = pane.getAttribute('data-account-password-pane') === 'recovery' ? !showRecovery : showRecovery; }); var title = dialog.querySelector('[data-account-password-title]'); if (title) { title.textContent = title.getAttribute(showRecovery ? 'data-recovery-label' : 'data-change-label') || title.textContent; var shell = dialog.closest('.aksrt-user-settings-modal'); if (shell) shell.setAttribute('aria-label', title.textContent); } var input = dialog.querySelector(showRecovery ? '#pwd-recovery-target' : '#pwd-old'); if (input) input.focus(); }); }); var passwordForm = dialog.querySelector('#pwd-form'); if (passwordForm) passwordForm.addEventListener('submit', function (event) { event.preventDefault(); var password = passwordForm.querySelector('[name="password"]'); var confirm = passwordForm.querySelector('[name="password2"]'); if (password && confirm && password.value !== confirm.value) { notyf('两次密码输入不一致', 'warning'); confirm.focus(); return; } postAjaxForm(passwordForm, 'aksrt_change_password', 'change_password', function () { window.setTimeout(function () { window.location.reload(); }, 700); }); }); var recoveryForm = dialog.querySelector('#pwd-recovery-form'); var recoveryCodeButton = dialog.querySelector('[data-account-recovery-send]'); if (recoveryCodeButton && recoveryForm) recoveryCodeButton.addEventListener('click', function () { sendRecoveryCode(recoveryCodeButton, recoveryForm); }); if (recoveryForm) recoveryForm.addEventListener('submit', function (event) { event.preventDefault(); postRecoveryForm(recoveryForm); }); } else if (type === 'email') { dialog.querySelectorAll('.account-code-send').forEach(function (button) { button.addEventListener('click', function () { sendEmailCode(button, button.closest('form'), button.getAttribute('data-account-code-scene')); }); }); var oldForm = dialog.querySelector('#email-old-verify-form'); var newForm = dialog.querySelector('#email-new-form'); if (oldForm) oldForm.addEventListener('submit', function (event) { event.preventDefault(); postAjaxForm(oldForm, 'aksrt_verify_account_email', 'change_email', function () { setStep(dialog, 'email-new'); }); }); if (newForm) newForm.addEventListener('submit', function (event) { event.preventDefault(); postAjaxForm(newForm, 'aksrt_change_email', 'change_email', function () { if (dialog.querySelector('[data-account-step="email-success"]')) setStep(dialog, 'email-success'); window.setTimeout(function () { window.location.reload(); }, 700); }); }); } else if (type === 'phone') { initPhoneDigits(dialog); dialog.querySelectorAll('[data-aksrt-sms-trigger]').forEach(function (button) { button.addEventListener('click', function () { sendPhoneCode(button, button.closest('form'), button.getAttribute('data-aksrt-sms-scene')); }); }); var phoneOldForm = dialog.querySelector('#phone-old-verify-form'); var phoneNewForm = dialog.querySelector('#aksrt-bind-phone-form'); if (phoneOldForm) phoneOldForm.addEventListener('submit', function (event) { event.preventDefault(); postPhoneForm(phoneOldForm, '/user/phone-verify', function () { setStep(dialog, 'phone-new'); }); }); if (phoneNewForm) phoneNewForm.addEventListener('submit', function (event) { event.preventDefault(); postPhoneForm(phoneNewForm, '/user/phone', function () { if (dialog.querySelector('[data-account-step="phone-success"]')) setStep(dialog, 'phone-success'); window.setTimeout(function () { window.location.reload(); }, 700); }); }); } } function resetSettingsModal(shell) { if (!shell || !shell.classList.contains('aksrt-user-settings-modal')) return; shell.querySelectorAll('.account-code-send,[data-aksrt-sms-trigger],[data-account-recovery-send]').forEach(resetCountdown); shell.querySelectorAll('form').forEach(function (form) { form.reset(); }); shell.querySelectorAll('[data-account-dialog="phone"]').forEach(function (dialog) { dialog.querySelectorAll('.aksrt-phone-digit').forEach(function (input) { input.value = ''; }); var group = dialog.querySelector('[data-aksrt-phone-digits]'); if (group) group.classList.remove('has-error'); var error = dialog.querySelector('[data-aksrt-phone-digits-error]'); if (error) { error.hidden = true; error.textContent = ''; } var area = dialog.querySelector('[data-aksrt-phone-code-area]'); if (area) area.hidden = !!group; }); } if (document.documentElement.dataset.aksrtSettingsModalCleanupInitialized !== 'true') { document.documentElement.dataset.aksrtSettingsModalCleanupInitialized = 'true'; document.addEventListener('aksrt:modal:close', function (event) { resetSettingsModal(event.detail && event.detail.shell); }); } root.querySelectorAll('[data-aksrt-settings-modal]').forEach(function (button) { if (button.dataset.aksrtSettingsInitialized === 'true') return; button.dataset.aksrtSettingsInitialized = 'true'; button.addEventListener('click', function () { openSettingsModal(button.getAttribute('data-aksrt-settings-modal')); }); }); root.querySelectorAll('.binding-item--linkable').forEach(function(link) { if (link.dataset.aksrtBindCaptchaInitialized === 'true') return; link.dataset.aksrtBindCaptchaInitialized = 'true'; link.addEventListener('click', function(event) { if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; var startUrl = link.getAttribute('data-bind-start-url') || link.getAttribute('href') || ''; var provider = link.getAttribute('data-provider') || link.getAttribute('data-bind-provider') || ''; var scope = link.closest('[data-aksrt-captcha-scope]') || root; if (!startUrl || !captchaAvailable('bind', scope)) return; event.preventDefault(); var restUrl = String(cfg.restUrl || (window.aksrtData && (aksrtData.restUrl || aksrtData.apiUrl)) || '').replace(/\/$/, ''); var nonce = cfg.restNonce || (window.aksrtData && (aksrtData.restNonce || aksrtData.nonce)) || ''; var redirectTo = (cfg.tabUrls && cfg.tabUrls.settings) || window.location.href; if (!restUrl) { window.location.assign(startUrl); return; } link.classList.add('is-loading'); runCaptcha('bind', scope).then(function(captchaPayload) { if (captchaPayload === null) return null; return fetch(restUrl + '/oauth/bind-pass', { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce }, body: JSON.stringify(Object.assign({ provider: provider, redirect_to: redirectTo }, captchaPayload || {})) }); }).then(function(response) { if (!response) return null; return response.json().catch(function() { return {}; }).then(function(payload) { if (!response.ok || !payload || payload.success === false) throw new Error(payload.message || '绑定验证失败'); return payload.data || payload; }); }).then(function(payload) { if (payload && payload.url) window.location.assign(payload.url); }).catch(function(error) { notyf(error.message || '绑定验证失败,请重试', 'danger'); }).finally(function() { link.classList.remove('is-loading'); resetCaptcha('bind', scope); }); }); }); // 解绑 root.querySelectorAll('.binding-unbind-btn').forEach(function(b) { if (b.dataset.aksrtSettingsInitialized === 'true') return; b.dataset.aksrtSettingsInitialized = 'true'; b.addEventListener('click', function() { var p = this.getAttribute('data-provider'); if (!p) return; if (!confirm(i18n.confirmUnbind || '确定解除绑定?')) return; var self = b; self.disabled = true; self.textContent = i18n.unbinding || '解除中...'; var toastId = 'aksrt-settings-oauth-unbind-toast'; notyf(i18n.unbinding || '正在解除绑定…', 'load', '', toastId); var unbindNonce = document.getElementById('aksrt-oauth-unbind-nonce'); var data = new URLSearchParams(); data.set('action', 'aksrt_oauth_unbind'); data.set('nonce', unbindNonce ? unbindNonce.value : ''); data.set('provider', p); data.set('userId', String(userId)); fetch(ajaxUrl, { method: 'POST', credentials: 'same-origin', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: data.toString() }) .then(function(response) { return response.json().catch(function() { return {}; }).then(function(payload) { return { ok: response.ok, status: response.status, payload: payload || {} }; }); }) .then(function(result) { var d = result.payload; if (result.ok && d.success) { notyf((d.data && d.data.message) || d.message || i18n.unbindSuccess || '解除成功', 'success', 5, toastId); setTimeout(function() { window.location.href = tabUrls.settings || new URL('/user/settings/', window.location.origin).toString(); }, 1000); } else { var message = result.status === 409 ? (d.message || (d.data && d.data.message) || i18n.unbindFailed || '解除失败') : ((d.data && d.data.message) || d.message || i18n.unbindFailed || '解除失败'); notyf(message, 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; } }) .catch(function() { notyf(i18n.networkError || '网络错误', 'danger', 5, toastId); self.disabled = false; self.textContent = i18n.unbind || '解除'; }); }); }); } function initSettingsCallbackToast() { if (document.documentElement.dataset.aksrtSettingsCallbackInitialized === 'true') return; var currentUrl = new URL(window.location.href); var action = currentUrl.searchParams.get('oauth_action'); if (['login', 'bind', 'error'].indexOf(action) === -1) return; if (action !== 'bind' && document.documentElement.dataset.aksrtOauthResultHandled === 'true') return; document.documentElement.dataset.aksrtSettingsCallbackInitialized = 'true'; var provider = currentUrl.searchParams.get('oauth_provider') || ''; var providerLabels = { github: 'GitHub', gitee: 'Gitee', qq: 'QQ', google: 'Google', weibo: '微博', weixin: '微信', weixingzh: '微信公众号' }; var errorMessages = { access_denied: '已取消第三方授权', cancelled: '已取消第三方授权', canceled: '已取消第三方授权', invalid_state: '授权状态已失效,请重新尝试', state_mismatch: '授权状态已失效,请重新尝试', provider_unavailable: '该登录方式暂时不可用', provider_error: '第三方授权失败,请稍后重试', callback_failed: '第三方授权失败,请稍后重试', login_failed: '第三方登录失败,请重试', bind_failed: '第三方账号绑定失败,请重试', bind_verification_required: '请先完成人机验证后再绑定', account_conflict: '该第三方账号无法绑定到当前账号', already_bound: '该第三方账号已绑定其他账号', timeout: '第三方授权已超时,请重试' }; var providerLabel = providerLabels[provider] || ''; var message = ''; var type = 'success'; if (action === 'login') message = providerLabel ? providerLabel + '登录成功' : '登录成功'; if (action === 'bind') message = providerLabel ? providerLabel + '绑定成功' : (i18n.bindSuccess || '绑定成功'); if (action === 'error') { type = 'danger'; message = errorMessages[currentUrl.searchParams.get('oauth_error') || ''] || '第三方授权失败,请重试'; } notyf(message, type); if (window.history.replaceState) { ['oauth_action', 'oauth_provider', 'oauth_error', 'oauth_bound'].forEach(function(key) { currentUrl.searchParams.delete(key); }); window.history.replaceState({}, '', currentUrl.pathname + currentUrl.search + currentUrl.hash); } } jQuery(document).off('auto_fun.aksrt-user-center').on('auto_fun.aksrt-user-center', function () { initDashboardNavigation(); initProfileForm(); initAuthForm(); initUnfavorite(); initPostDeletion(); initNotifications(); initNotificationPreferences(); initSettings(); initSettingsCallbackToast(); }); })();