Files
wordpress-theme-aksrt/assets/js/author.js
T

354 lines
13 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, AksrtFrontend, 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 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() {
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() {
initPublicDashboards();
initCoverMenus();
initProfileDetailsModal();
initCoverUpload();
initAvatarUpload();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();