1371 lines
62 KiB
JavaScript
1371 lines
62 KiB
JavaScript
/**
|
|
* 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 = '<div class="profile-empty__icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="30" height="30"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></div><p class="profile-empty__title">暂无收藏</p><p class="profile-empty__desc">你还没有收藏任何文章。</p>';
|
|
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();
|
|
});
|
|
|
|
|
|
|
|
})();
|