/** * AKSRT Public User Home runtime * * Handles public user-home dashboard tabs, panel fragment loading and cover * uploads. The URL stays clean (/author/{name}/) — tabs, follow sub-tabs and * pagination are loaded via the aksrt/v1 user-home panel REST fragment. * * @package AKSRT */ /* global aksrtAuthorPage, aksrtData, notyf */ (function () { 'use strict'; function notifyLoadError() { if (typeof notyf === 'function') { notyf('加载失败,请重试', 'danger'); } } function initPublicDashboards() { document.querySelectorAll('.dashboard--public').forEach(function (root) { if (root.dataset.aksrtAuthorBound === '1') return; root.dataset.aksrtAuthorBound = '1'; var tabs = Array.prototype.slice.call(root.querySelectorAll('.dashboard-tabs [data-tab]')); if (!tabs.length) return; var defaultTab = root.dataset.defaultTab || tabs[0].getAttribute('data-tab') || 'posts'; var userId = parseInt(root.dataset.userId || '0', 10); var panels = {}; tabs.forEach(function (tab) { var name = tab.getAttribute('data-tab'); var panel = root.querySelector('#panel-' + name); if (name && panel) { panels[name] = { tab: tab, panel: panel }; } }); 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); if (active) { panels[key].panel.removeAttribute('hidden'); } else { panels[key].panel.setAttribute('hidden', ''); } }); } function appendCommentPage(panel, html) { var fragment = document.createElement('div'); fragment.innerHTML = html; var currentList = panel.querySelector('.profile-comment-list'); var incomingList = fragment.querySelector('.profile-comment-list'); if (currentList && incomingList) { Array.prototype.slice.call(incomingList.children).forEach(function (item) { currentList.appendChild(item); }); } var currentMore = panel.querySelector('.profile-comment-more'); var incomingMore = fragment.querySelector('.profile-comment-more'); if (currentMore) currentMore.remove(); if (incomingMore && currentList) currentList.insertAdjacentElement('afterend', incomingMore); } function loadPanelBody(panel, name, page, sub, appendComments, trigger) { var cfg = window.aksrtData || {}; var restBase = cfg.apiUrl || cfg.restUrl || ''; if (!userId || !restBase) return Promise.reject(new Error('rest-unavailable')); if (panel._aksrtUhRequest) return panel._aksrtUhRequest; var followSub = sub || 'followers'; // uhPage / uhSub 是**粉丝面板**的私有状态(其它面板没有子页签)。 // 写满所有面板会互相污染:文章面板上的 uhSub 残留会让粉丝翻页读到 // 别人的分组,uhPage 也会让旧页码假活跃。非粉丝面板一次都不写。 if (name === 'follow') { panel.dataset.uhPage = String(page); panel.dataset.uhSub = followSub; } if (appendComments && trigger) { trigger.disabled = true; trigger.setAttribute('aria-busy', 'true'); trigger.textContent = '加载中…'; } panel.classList.remove('is-load-error'); panel.classList.add('is-loading'); var url = restBase.replace(/\/$/, '') + '/user-home/panel/' + userId + '/' + encodeURIComponent(name) + '?page=' + encodeURIComponent(page) + '&sub=' + encodeURIComponent(followSub); panel._aksrtUhRequest = fetch(url, { credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || cfg.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 (appendComments && name === 'comments') { appendCommentPage(panel, payload.data.html); } else { panel.innerHTML = payload.data.html; } panel.dataset.panelLoaded = 'true'; if (window.jQuery) jQuery(document).trigger('post_ajax.ed', [panel]); if (typeof window.auto_fun === 'function') window.auto_fun(); }); }).catch(function (error) { if (panel.dataset.panelLoaded === 'false') { panel.classList.add('is-load-error'); } if (appendComments && trigger && trigger.isConnected) { trigger.disabled = false; trigger.removeAttribute('aria-busy'); trigger.textContent = '加载更多'; } throw error; }).finally(function () { panel._aksrtUhRequest = null; panel.classList.remove('is-loading'); }); return panel._aksrtUhRequest; } function panelNameOf(panel) { var name = (panel.id || '').replace(/^panel-/, ''); return panels[name] ? name : ''; } tabs.forEach(function (tab) { tab.addEventListener('click', function () { var name = tab.getAttribute('data-tab'); switchTo(name); var panel = panels[name] && panels[name].panel; if (panel && panel.dataset.panelLoaded === 'false') { loadPanelBody(panel, name, 1, 'followers').catch(notifyLoadError); } }); }); // Delegate sub-tab / pagination clicks so buttons injected by REST // fragments keep working without rebinding. root.addEventListener('click', function (event) { var target = event.target && event.target.closest ? event.target : null; if (!target) return; var subBtn = target.closest('[data-uh-sub]'); if (subBtn) { event.preventDefault(); var followPanel = subBtn.closest('.dashboard-panel'); if (!followPanel) return; var sub = subBtn.getAttribute('data-uh-sub') === 'following' ? 'following' : 'followers'; loadPanelBody(followPanel, 'follow', 1, sub).catch(notifyLoadError); return; } var retryBtn = target.closest('[data-uh-retry]'); if (retryBtn) { event.preventDefault(); var retryPanel = retryBtn.closest('.dashboard-panel'); if (!retryPanel) return; var retryName = panelNameOf(retryPanel); if (!retryName) return; loadPanelBody(retryPanel, retryName, 1, 'followers').catch(notifyLoadError); return; } var commentsNext = target.closest('[data-uh-comments-next]'); if (commentsNext) { event.preventDefault(); var commentsPanel = commentsNext.closest('.dashboard-panel'); if (!commentsPanel) return; var commentsPage = Math.max(2, parseInt(commentsNext.getAttribute('data-uh-comments-next') || '2', 10) || 2); loadPanelBody(commentsPanel, 'comments', commentsPage, 'followers', true, commentsNext).catch(notifyLoadError); return; } var pageBtn = target.closest('[data-uh-page]'); if (!pageBtn) return; event.preventDefault(); var panel = pageBtn.closest('.dashboard-panel'); if (!panel) return; var name = panelNameOf(panel); if (!name) return; var page = Math.max(1, parseInt(pageBtn.getAttribute('data-uh-page') || '1', 10) || 1); // 只认**本面板内**的子页签状态:uhSub 是粉丝面板私有的(panel-follow.php // 的 data-uh-sub),一旦落到别的面板上就会互相串味 —— 先点过粉丝子页签, // panel.dataset.uhSub 会残留在文章面板上,之后粉丝面板的翻页读到的是 // 文章面板留下的值。只看当前面板,读不到就回落默认分组。 var pageSub = panel.querySelector('[data-uh-sub].is-active'); var sub = pageSub && pageSub.getAttribute('data-uh-sub') === 'following' ? 'following' : 'followers'; loadPanelBody(panel, name, page, sub).catch(notifyLoadError); }); switchTo(defaultTab); }); } function initCoverUpload() { var cfg = window.aksrtAuthorPage || {}; var upload = cfg.coverUpload || null; if (!upload || !upload.enabled || !upload.restUrl) return; var btn = document.getElementById(upload.buttonId || 'aksrt-upload-bg-btn'); var input = document.getElementById(upload.inputId || 'aksrt-bg-file-input'); if (!btn || !input || btn.dataset.aksrtCoverBound === '1') return; btn.dataset.aksrtCoverBound = '1'; var label = btn.querySelector('span'); var idleText = upload.idleText || (label ? label.textContent : ''); var loadingText = upload.loadingText || idleText; function setBusy(busy) { btn.disabled = !!busy; if (label) { label.textContent = busy ? loadingText : idleText; } } function resetImage(previousUrl) { var image = document.getElementById(upload.imageId || 'aksrt-cover-img'); if (!image) return; if (previousUrl) { image.src = previousUrl; return; } if (upload.fallbackImage) { image.src = upload.fallbackImage; } } function applyImage(nextUrl) { var image = document.getElementById(upload.imageId || 'aksrt-cover-img'); if (image && nextUrl) { image.src = nextUrl; } } btn.addEventListener('click', function () { input.click(); }); input.addEventListener('change', function () { var file = input.files && input.files[0]; if (!file) return; if (upload.maxSize && file.size > upload.maxSize) { notyf(upload.tooLargeText || '图片大小不能超过 2MB', 'warning'); input.value = ''; return; } setBusy(true); var formData = new FormData(); formData.append(upload.fieldName || 'background', file); fetch(upload.restUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': upload.restNonce || '' }, body: formData }) .then(function (response) { return response.json().catch(function () { return null; }).then(function (payload) { if (!response.ok) { throw payload || { message: upload.errorText || '上传失败' }; } return payload; }); }) .then(function (payload) { if (payload && payload.success && payload.url) { applyImage(payload.url); notyf(upload.successText || '背景图已更新', 'success'); return; } throw payload || { message: upload.errorText || '上传失败' }; }) .catch(function (error) { var previousUrl = error && error.data && error.data.previous_url !== undefined ? error.data.previous_url : ''; resetImage(previousUrl); notyf((error && error.message) || upload.networkErrorText || '网络错误,请重试', 'danger'); }) .finally(function () { setBusy(false); input.value = ''; }); }); } function initAvatarUpload() { var upload = (window.aksrtUserCenter || {}).avatarUpload; if (!upload || !upload.enabled || !upload.restUrl) return; var button = document.getElementById(upload.buttonId || 'aksrt-upload-avatar-btn'); var input = document.getElementById(upload.inputId || 'aksrt-avatar-file-input'); var image = document.getElementById(upload.imageId || 'aksrt-user-avatar-image'); if (!button || !input || button.dataset.aksrtAvatarBound === '1') return; button.dataset.aksrtAvatarBound = '1'; var label = button.querySelector('span'); var idleText = upload.idleText || (label ? label.textContent : '更换头像'); function setBusy(busy) { button.disabled = !!busy; if (label) label.textContent = busy ? (upload.loadingText || '上传中…') : idleText; } button.addEventListener('click', function () { input.click(); }); input.addEventListener('change', function () { var file = input.files && input.files[0]; if (!file) return; if (upload.maxSize && file.size > upload.maxSize) { notyf(upload.tooLargeText || '图片大小不能超过 2MB', 'warning'); input.value = ''; return; } var body = new FormData(); body.append(upload.fieldName || 'avatar', file); setBusy(true); fetch(upload.restUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': upload.restNonce || '' }, body: body }) .then(function (response) { return response.json().then(function (payload) { if (!response.ok || !payload || !payload.success) throw payload || {}; return payload; }); }) .then(function (payload) { var url = payload.url || (payload.data && payload.data.url) || ''; if (!url) throw payload; if (image) image.src = url; notyf(payload.message || upload.successText || '头像已更新', 'success'); }) .catch(function (error) { notyf((error && error.message) || upload.errorText || '上传失败', 'danger'); }) .finally(function () { setBusy(false); input.value = ''; }); }); } function initCoverMenus() { document.querySelectorAll('[data-aksrt-menu]').forEach(function (menu) { if (menu.dataset.aksrtMenuBound === '1') return; menu.dataset.aksrtMenuBound = '1'; var trigger = menu.querySelector('.aksrt-cover-menu__trigger'); var panel = menu.querySelector('.aksrt-cover-menu__panel'); if (!trigger || !panel) return; function close() { panel.hidden = true; trigger.setAttribute('aria-expanded', 'false'); } function open() { document.querySelectorAll('[data-aksrt-menu]').forEach(function (otherMenu) { if (otherMenu === menu) return; var otherPanel = otherMenu.querySelector('.aksrt-cover-menu__panel'); var otherTrigger = otherMenu.querySelector('.aksrt-cover-menu__trigger'); if (otherPanel) otherPanel.hidden = true; if (otherTrigger) otherTrigger.setAttribute('aria-expanded', 'false'); }); panel.hidden = false; trigger.setAttribute('aria-expanded', 'true'); var first = panel.querySelector('[role="menuitem"]'); if (first) first.focus(); } trigger.addEventListener('click', function () { panel.hidden ? open() : close(); }); menu.addEventListener('keydown', function (event) { if (event.key === 'Escape') { close(); trigger.focus(); } }); document.addEventListener('pointerdown', function (event) { if (!menu.contains(event.target)) close(); }); }); } function initProfileDetailsModal() { var modal = document.getElementById('aksrt-profile-details-modal'); if (!modal) return; var dialog = modal.querySelector('.aksrt-profile-details-modal__dialog'); var returnFocus = null; function close() { modal.hidden = true; if (returnFocus) returnFocus.focus(); } document.querySelectorAll('[data-aksrt-profile-details]').forEach(function (button) { button.addEventListener('click', function () { returnFocus = button; modal.hidden = false; if (dialog) dialog.focus(); }); }); modal.querySelectorAll('[data-aksrt-modal-close]').forEach(function (button) { button.addEventListener('click', close); }); document.addEventListener('keydown', function (event) { if (event.key === 'Escape' && !modal.hidden) close(); }); } function init() { if (document.documentElement.dataset.aksrtAuthorInitialized === '1') return; document.documentElement.dataset.aksrtAuthorInitialized = '1'; initPublicDashboards(); initCoverMenus(); initProfileDetailsModal(); initCoverUpload(); initAvatarUpload(); } jQuery(document).off('auto_fun.aksrt-author').on('auto_fun.aksrt-author', function () { init(document); }); })();