/** * 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, AksrtFrontend, notyf */ (function () { 'use strict'; var globalEventsBound = false; function elementsIn(root, selector) { var elements = []; root = root || document; if (root.nodeType === 1 && root.matches(selector)) elements.push(root); if (root.querySelectorAll) root.querySelectorAll(selector).forEach(function (element) { elements.push(element); }); return elements; } function elementById(root, id) { root = root || document; if (root.nodeType === 1 && root.id === id) return root; return root.querySelector ? root.querySelector('#' + id) : null; } function notifyLoadError() { if (typeof notyf === 'function') { notyf('加载失败,请重试', 'danger'); } } function initPublicDashboards(scope) { elementsIn(scope, '.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 loadPanelBody(panel, name, page, sub) { 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 || panel.dataset.uhSub || 'followers'; panel.dataset.uhPage = String(page); panel.dataset.uhSub = followSub; 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'); } panel.innerHTML = payload.data.html; if (window.AksrtFrontend && typeof window.AksrtFrontend.rehydrate === 'function') { window.AksrtFrontend.rehydrate(panel, 'ajax'); } }); }).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 () { switchTo(tab.getAttribute('data-tab')); }); }); // 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 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); loadPanelBody(panel, name, page, panel.dataset.uhSub || 'followers').catch(notifyLoadError); }); switchTo(defaultTab); }); } function initCoverUpload(root) { var cfg = window.aksrtAuthorPage || {}; var upload = cfg.coverUpload || null; if (!upload || !upload.enabled || !upload.restUrl) return; var btn = elementById(root, upload.buttonId || 'aksrt-upload-bg-btn'); var input = elementById(root, 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(root) { var upload = (window.aksrtUserCenter || {}).avatarUpload; if (!upload || !upload.enabled || !upload.restUrl) return; var button = elementById(root, upload.buttonId || 'aksrt-upload-avatar-btn'); var input = elementById(root, upload.inputId || 'aksrt-avatar-file-input'); var image = elementById(root, 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(root) { elementsIn(root, '[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(); } }); }); } function initProfileDetailsModal(root) { var modal = elementById(root, 'aksrt-profile-details-modal'); if (!modal || modal.dataset.aksrtProfileModalBound === '1') return; modal.dataset.aksrtProfileModalBound = '1'; var dialog = modal.querySelector('.aksrt-profile-details-modal__dialog'); var returnFocus = null; function close() { modal.hidden = true; if (returnFocus) returnFocus.focus(); } elementsIn(root, '[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); }); } function initGlobalEvents() { if (globalEventsBound) return; globalEventsBound = true; document.addEventListener('pointerdown', function (event) { document.querySelectorAll('[data-aksrt-menu]').forEach(function (menu) { if (menu.contains(event.target)) return; var panel = menu.querySelector('.aksrt-cover-menu__panel'); var trigger = menu.querySelector('.aksrt-cover-menu__trigger'); if (panel) panel.hidden = true; if (trigger) trigger.setAttribute('aria-expanded', 'false'); }); }); document.addEventListener('keydown', function (event) { if (event.key !== 'Escape') return; var modal = document.getElementById('aksrt-profile-details-modal'); if (modal && !modal.hidden) { var close = modal.querySelector('[data-aksrt-modal-close]'); if (close) close.click(); } }); } function init(root) { initGlobalEvents(); initPublicDashboards(root); initCoverMenus(root); initProfileDetailsModal(root); initCoverUpload(root); initAvatarUpload(root); } if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') { window.AksrtFrontend.register('aksrt-author', init); } })();