feat(links): move friend links to native WordPress links with review flow
- enable the native Links manager and drop the custom friend-link option/REST path - add per-page link settings for the friends template - render link categories from get_bookmarks() with search and page-content options - add a validated public link submission endpoint with logo upload, captcha, rate limiting and duplicate checks - create pending links for review and notify administrators by email and in-app message - add native Links admin pending count, columns and edit notes - cover the new submission and cleanup contracts Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ef7b5939ba
commit
4c562428b4
24 files changed
+792
-273
No files matched your search
@@ -12,11 +12,6 @@ export const globalApiMethods = {
|
||||
getFloatingButtons() { return this.request('/settings/floating-buttons') },
|
||||
saveFloatingButtons(data) { return this.request('/settings/floating-buttons', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
|
||||
getFriendLinks() { return this.request('/friend-links') },
|
||||
addFriendLink(data) { return this.request('/friend-links', { method: 'POST', body: JSON.stringify(data) }) },
|
||||
deleteFriendLink(id) { return this.request(`/friend-links/${id}`, { method: 'DELETE' }) },
|
||||
reorderFriendLinks(ids) { return this.request('/friend-links/reorder', { method: 'POST', body: JSON.stringify({ ids }) }) },
|
||||
|
||||
getCmsFeatures() { return this.request('/settings/cms-features') },
|
||||
saveCmsFeatures(cms_features) { return this.request('/settings/cms-features', { method: 'POST', body: JSON.stringify({ cms_features }) }) },
|
||||
}
|
||||
@@ -87,6 +87,7 @@ export const permissionsSchemas = {
|
||||
{ label: '打赏用户', value: 'tip_user' },
|
||||
{ label: '提交工单', value: 'ticket_create' },
|
||||
{ label: '回复工单', value: 'ticket_reply' },
|
||||
{ label: '友情链接申请', value: 'friend_link' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
/**
|
||||
* Jio Friends Page — 友情链接页样式
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
.friends-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: var(--space-4);
|
||||
margin-top: var(--space-6);
|
||||
}
|
||||
|
||||
.friend-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-4);
|
||||
padding: var(--card-padding);
|
||||
background: var(--surface-1);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
text-decoration: none;
|
||||
color: var(--color-ink-2);
|
||||
transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
|
||||
}
|
||||
|
||||
.friend-card:hover {
|
||||
box-shadow: var(--shadow);
|
||||
border-color: var(--color-primary-soft);
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.friend-card__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: var(--radius-avatar, 50%);
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
background: var(--surface-2);
|
||||
}
|
||||
|
||||
.friend-card__info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.friend-card__name {
|
||||
font-size: var(--text-base);
|
||||
font-weight: 600;
|
||||
color: var(--color-ink);
|
||||
margin: 0 0 4px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.friend-card__desc {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--color-ink-3);
|
||||
margin: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: var(--leading-snug);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 640px) {
|
||||
.friends-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@@ -148,6 +148,17 @@
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.jio-links-submit-status {
|
||||
margin: 10px 0;
|
||||
font-size: 0.92em;
|
||||
}
|
||||
|
||||
.jio-links-logo-preview {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* ---- search cover ---- */
|
||||
.link-page-search-cover.page-cover {
|
||||
position: relative;
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Native link submission form.
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var cfg = window.jioLinksSubmit || {};
|
||||
var i18n = cfg.i18n || {};
|
||||
var form = document.querySelector('.jio-links-submit-form');
|
||||
if (!form) return;
|
||||
|
||||
var button = form.querySelector('.jio-links-submit-btn');
|
||||
var status = form.querySelector('.jio-links-submit-status');
|
||||
var logoInput = form.querySelector('.jio-links-logo-input');
|
||||
var preview = form.querySelector('.jio-links-logo-preview');
|
||||
|
||||
function setStatus(message, type) {
|
||||
if (!status) return;
|
||||
status.textContent = message || '';
|
||||
status.className = 'jio-links-submit-status' + (type ? ' text-' + type : '');
|
||||
}
|
||||
|
||||
if (logoInput && preview) {
|
||||
logoInput.addEventListener('change', function () {
|
||||
var file = logoInput.files && logoInput.files[0];
|
||||
if (!file) return;
|
||||
preview.src = URL.createObjectURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
if (button && button.disabled) return;
|
||||
|
||||
var payload = new FormData(form);
|
||||
var original = button ? button.innerHTML : '';
|
||||
if (button) {
|
||||
button.disabled = true;
|
||||
button.innerHTML = '<i class="fa fas fa-spinner fa-spin"></i> ' + (i18n.submitting || '正在提交…');
|
||||
}
|
||||
setStatus(i18n.submitting || '正在提交…', 'muted');
|
||||
|
||||
var captcha = window.JioCaptcha;
|
||||
var captchaPromise = captcha ? captcha.run('friend_link', form) : Promise.resolve({});
|
||||
captchaPromise.then(function (tokens) {
|
||||
if (tokens === null) throw new Error(i18n.captcha || '请完成人机验证');
|
||||
Object.keys(tokens || {}).forEach(function (key) {
|
||||
payload.set(key, tokens[key]);
|
||||
});
|
||||
return fetch(cfg.ajaxUrl || '/wp-admin/admin-ajax.php', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: payload,
|
||||
});
|
||||
}).then(function (response) {
|
||||
return response.json().then(function (body) {
|
||||
return { ok: response.ok, body: body };
|
||||
});
|
||||
}).then(function (result) {
|
||||
var body = result.body || {};
|
||||
var data = body.data || {};
|
||||
if (!result.ok || !body.success) {
|
||||
throw new Error(data.message || i18n.error || '提交失败,请重试');
|
||||
}
|
||||
setStatus(data.message || i18n.success || '提交成功', 'success');
|
||||
form.reset();
|
||||
if (preview) preview.src = '';
|
||||
if (button) button.disabled = false;
|
||||
if (button) button.innerHTML = original;
|
||||
if (captcha) captcha.reset('friend_link', form);
|
||||
window.setTimeout(function () {
|
||||
var modal = document.getElementById('submit-links-modal');
|
||||
if (modal) modal.classList.remove('show', 'is-open');
|
||||
setStatus('', '');
|
||||
}, 1800);
|
||||
}).catch(function (error) {
|
||||
setStatus(error.message || i18n.error || '提交失败,请重试', 'danger');
|
||||
if (button) {
|
||||
button.disabled = false;
|
||||
button.innerHTML = original;
|
||||
}
|
||||
if (captcha) captcha.reset('friend_link', form);
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in new issue
Block a user