feat(users): govern profiles and access
Normalize fixed profile fields, profile actions, and the five-column capability matrix for comments, uploads, and downloads.
This commit is contained in:
1 parent
0b06b2c7df
commit
8241570ca2
67 files changed
+780
-1127
No files matched your search
@@ -258,8 +258,50 @@
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
+84
-35
@@ -390,27 +390,41 @@
|
||||
}
|
||||
},
|
||||
|
||||
openMedia: function (opts, cb) {
|
||||
opts = opts || {};
|
||||
if (!window.wp || !wp.media) {
|
||||
alert('Media library unavailable');
|
||||
return;
|
||||
uploadFile: function (kind, file) {
|
||||
var upload = (cfg.uploads || {})[kind] || null;
|
||||
if (!upload || !upload.url || !file) {
|
||||
return Promise.reject(new Error(i18n.error || 'Upload unavailable'));
|
||||
}
|
||||
var frame = wp.media({
|
||||
title: opts.title || i18n.pickImage || 'Select',
|
||||
button: { text: opts.button || 'OK' },
|
||||
multiple: !!opts.multiple,
|
||||
library: opts.library || { type: 'image' },
|
||||
});
|
||||
frame.on('select', function () {
|
||||
var selection = frame.state().get('selection');
|
||||
var files = selection.map(function (att) {
|
||||
var a = att.toJSON();
|
||||
return { id: a.id, url: a.url, title: a.title || '' };
|
||||
if (upload.maxSize && file.size > upload.maxSize) {
|
||||
return Promise.reject(new Error('File is too large'));
|
||||
}
|
||||
var body = new FormData();
|
||||
body.append('file', file);
|
||||
return fetch(upload.url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-WP-Nonce': cfg.restNonce || '' },
|
||||
body: body,
|
||||
}).then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (payload) {
|
||||
if (!response.ok || !payload || !payload.success) {
|
||||
throw new Error((payload && payload.message) || i18n.error || 'Upload failed');
|
||||
}
|
||||
return payload;
|
||||
});
|
||||
cb(opts.multiple ? files : files[0]);
|
||||
});
|
||||
frame.open();
|
||||
},
|
||||
|
||||
pickUpload: function (kind, accept) {
|
||||
return new Promise(function (resolve) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = accept;
|
||||
input.addEventListener('change', function () {
|
||||
resolve(input.files && input.files[0] ? input.files[0] : null);
|
||||
}, { once: true });
|
||||
input.click();
|
||||
});
|
||||
},
|
||||
|
||||
bindFeatured: function () {
|
||||
@@ -431,24 +445,21 @@
|
||||
return;
|
||||
}
|
||||
if (act === 'image') {
|
||||
self.openMedia({ title: i18n.pickImage, multiple: false }, function (file) {
|
||||
if (file) {
|
||||
self.setFeatured('image', { url: file.url, id: file.id });
|
||||
}
|
||||
});
|
||||
self.pickUpload('image', 'image/*').then(function (file) {
|
||||
if (!file) return null;
|
||||
return self.uploadFile('image', file);
|
||||
}).then(function (file) {
|
||||
if (file) self.setFeatured('image', { url: file.url, id: file.attachment_id });
|
||||
}).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); });
|
||||
return;
|
||||
}
|
||||
if (act === 'slide') {
|
||||
self.openMedia({ title: i18n.pickImage, multiple: true }, function (files) {
|
||||
if (files && files.length) {
|
||||
self.setFeatured(
|
||||
'slide',
|
||||
files.map(function (f) {
|
||||
return { url: f.url, id: f.id };
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
self.pickUpload('image', 'image/*').then(function (file) {
|
||||
if (!file) return null;
|
||||
return self.uploadFile('image', file);
|
||||
}).then(function (file) {
|
||||
if (file) self.setFeatured('slide', [{ url: file.url, id: file.attachment_id }]);
|
||||
}).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); });
|
||||
return;
|
||||
}
|
||||
if (act === 'video') {
|
||||
@@ -456,9 +467,12 @@
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
self.openMedia({ title: i18n.pickVideo || 'Poster', multiple: false }, function (file) {
|
||||
self.pickUpload('image', 'image/*').then(function (file) {
|
||||
if (!file) return null;
|
||||
return self.uploadFile('image', file);
|
||||
}).then(function (file) {
|
||||
self.setFeatured('video', { url: url, pic: file ? file.url : '' });
|
||||
});
|
||||
}).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); });
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -520,6 +534,32 @@
|
||||
list.appendChild(node);
|
||||
});
|
||||
}
|
||||
var uploadBtn = qs('[data-pay-download-upload]', box);
|
||||
if (uploadBtn && list && tpl) {
|
||||
uploadBtn.addEventListener('click', function () {
|
||||
App.pickUpload('resource', '*/*').then(function (file) {
|
||||
if (!file) return null;
|
||||
uploadBtn.disabled = true;
|
||||
return App.uploadFile('resource', file);
|
||||
}).then(function (file) {
|
||||
if (!file) return;
|
||||
var node = tpl.content.cloneNode(true);
|
||||
var idx = list.querySelectorAll('[data-pay-download-item]').length;
|
||||
qsa('[data-name]', node).forEach(function (input) {
|
||||
input.name = 'aksrt_pay[downloads][' + idx + '][' + input.getAttribute('data-name') + ']';
|
||||
});
|
||||
var url = qs('[data-pay-download-url]', node);
|
||||
var label = qs('[data-pay-download-label]', node);
|
||||
if (url) url.value = file.url || '';
|
||||
if (label) label.value = file.name || '';
|
||||
list.appendChild(node);
|
||||
}).catch(function (error) {
|
||||
alert(error.message || i18n.error || 'Upload failed');
|
||||
}).finally(function () {
|
||||
uploadBtn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
if (list) {
|
||||
list.addEventListener('click', function (e) {
|
||||
var rm = e.target.closest('[data-pay-download-remove]');
|
||||
@@ -541,6 +581,15 @@
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
if (btn.hasAttribute('data-insert-upload-image')) {
|
||||
App.pickUpload('image', 'image/*').then(function (file) {
|
||||
if (!file) return null;
|
||||
return App.uploadFile('image', file);
|
||||
}).then(function (file) {
|
||||
if (file && file.url) insertEditorContent('<img src="' + file.url.replace(/"/g, '"') + '" alt="">');
|
||||
}).catch(function (error) { alert(error.message || i18n.error || 'Upload failed'); });
|
||||
return;
|
||||
}
|
||||
var type = btn.getAttribute('data-insert-shortcode');
|
||||
if (type === 'hide') {
|
||||
insertEditorContent('[hidecontent type="login"]' + (i18n.hidePlaceholder || '隐藏内容') + '[/hidecontent]');
|
||||
|
||||
@@ -194,40 +194,6 @@
|
||||
var form = document.getElementById('profile-form');
|
||||
if (!form) return;
|
||||
|
||||
var iconOptions = '<option value="">—</option>'
|
||||
+ '<option value="github">GitHub</option>'
|
||||
+ '<option value="google">Google</option>'
|
||||
+ '<option value="x">X</option>'
|
||||
+ '<option value="email">邮箱</option>'
|
||||
+ '<option value="wechat">微信</option>'
|
||||
+ '<option value="qq">QQ</option>'
|
||||
+ '<option value="weibo">微博</option>'
|
||||
+ '<option value="telegram">Telegram</option>'
|
||||
+ '<option value="discord">Discord</option>'
|
||||
+ '<option value="bilibili">Bilibili</option>'
|
||||
+ '<option value="linkedin">LinkedIn</option>'
|
||||
+ '<option value="facebook">Facebook</option>'
|
||||
+ '<option value="youtube">YouTube</option>'
|
||||
+ '<option value="instagram">Instagram</option>'
|
||||
+ '<option value="web">网站</option>';
|
||||
|
||||
var addBtn = document.getElementById('add-contact-btn');
|
||||
if (addBtn) {
|
||||
addBtn.addEventListener('click', function() {
|
||||
var list = document.getElementById('contacts-list');
|
||||
var idx = list.children.length;
|
||||
var div = document.createElement('div');
|
||||
div.className = 'contact-row';
|
||||
div.dataset.index = idx;
|
||||
div.innerHTML =
|
||||
'<select name="contacts[' + idx + '][icon]" class="profile-input contact-icon">' + iconOptions + '</select>' +
|
||||
'<input type="hidden" name="contacts[' + idx + '][name]" value="">' +
|
||||
'<input type="url" name="contacts[' + idx + '][url]" class="profile-input contact-url" placeholder="' + (i18n.contactUrlPlaceholder || '链接 URL') + '">' +
|
||||
'<button type="button" class="contact-remove" onclick="this.parentElement.remove()" title="' + (i18n.deleteLabel || '删除') + '">×</button>';
|
||||
list.appendChild(div);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
var btn = document.getElementById('profile-save-btn');
|
||||
|
||||
Reference in new issue
Block a user