Files

427 lines
16 KiB
JavaScript

/**
* 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); });
})();