Merge pull request 'fix(single): restore author follow controls' (#14) from worktree/fix-single-author-follow into main
Reviewed-on: #14
This commit was merged in pull request #14.
This commit is contained in:
+11
-11
@@ -984,17 +984,6 @@ strong, b {
|
|||||||
|
|
||||||
.but a { color: inherit; }
|
.but a { color: inherit; }
|
||||||
|
|
||||||
/* 关注控件(.follow 且无 .but):纯文字色样式,无背景色;
|
|
||||||
清除原生 button 的 UA 背景/边框,并保持 .but 同款可点反馈 */
|
|
||||||
.follow:not(.but) {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.15s;
|
|
||||||
}
|
|
||||||
.follow:not(.but):hover { opacity: 0.8; }
|
|
||||||
|
|
||||||
.but .icon,
|
.but .icon,
|
||||||
.but > .fa:not(.fa-angle-right),
|
.but > .fa:not(.fa-angle-right),
|
||||||
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
|
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
|
||||||
@@ -2697,6 +2686,17 @@ main.container,
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.user-card.author .user-name > name {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.user-card.author .user-name > name > .display-name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.user-card.author .user-name > name > .follow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.aksrt-author-hero__name,
|
.aksrt-author-hero__name,
|
||||||
.uc-header__name {
|
.uc-header__name {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -738,58 +738,6 @@ article.article.box-body,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* source: assets/css/frontend/single-post/author-actions.css */
|
/* source: assets/css/frontend/single-post/author-actions.css */
|
||||||
/* ===== Author Bar (inside article card) ===== */
|
|
||||||
.article-author-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__name:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-ink-4, var(--color-ink-4));
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__meta span {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__line {
|
|
||||||
height: 1px;
|
|
||||||
background: var(--color-line);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Content Footer ===== */
|
/* ===== Content Footer ===== */
|
||||||
.article-footer {
|
.article-footer {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
@@ -1210,88 +1158,6 @@ html.dark .post-drop-meta__item:hover {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-box__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__avatar {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: 3px solid var(--color-primary-soft, rgba(240, 68, 148, 0.2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__name {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-ink);
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__name:hover { color: var(--color-primary); }
|
|
||||||
|
|
||||||
.author-box__bio {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-ink-3, var(--color-ink-4));
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: 0 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__stats {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-ink-4, var(--color-ink-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__stats strong {
|
|
||||||
color: var(--color-ink);
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts {
|
|
||||||
margin-top: 14px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid var(--color-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts-title {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-ink-3, var(--color-ink-4));
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts li { padding: 3px 0; }
|
|
||||||
|
|
||||||
.author-box__posts a {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-ink);
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts a:hover { color: var(--color-primary); }
|
|
||||||
.author-box__posts a::before { content: '\2022'; color: var(--color-primary); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===== legacy single-metabox / author bar ===== */
|
/* ===== legacy single-metabox / author bar ===== */
|
||||||
.article-avatar .user-info {
|
.article-avatar .user-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -178,17 +178,6 @@
|
|||||||
|
|
||||||
.but a { color: inherit; }
|
.but a { color: inherit; }
|
||||||
|
|
||||||
/* 关注控件(.follow 且无 .but):纯文字色样式,无背景色;
|
|
||||||
清除原生 button 的 UA 背景/边框,并保持 .but 同款可点反馈 */
|
|
||||||
.follow:not(.but) {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: 0.15s;
|
|
||||||
}
|
|
||||||
.follow:not(.but):hover { opacity: 0.8; }
|
|
||||||
|
|
||||||
.but .icon,
|
.but .icon,
|
||||||
.but > .fa:not(.fa-angle-right),
|
.but > .fa:not(.fa-angle-right),
|
||||||
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
|
.but > i[class*="fa-"]:not(.fa-angle-right) { margin-right: 0.25em; }
|
||||||
|
|||||||
@@ -259,6 +259,17 @@ main.container,
|
|||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
.user-card.author .user-name > name {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.user-card.author .user-name > name > .display-name {
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.user-card.author .user-name > name > .follow {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
.aksrt-author-hero__name,
|
.aksrt-author-hero__name,
|
||||||
.uc-header__name {
|
.uc-header__name {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -1,55 +1,3 @@
|
|||||||
/* ===== Author Bar (inside article card) ===== */
|
|
||||||
.article-author-bar {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__name {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
color: var(--color-ink);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__name:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-ink-4, var(--color-ink-4));
|
|
||||||
margin-top: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__meta span {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.article-author-bar__line {
|
|
||||||
height: 1px;
|
|
||||||
background: var(--color-line);
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Content Footer ===== */
|
/* ===== Content Footer ===== */
|
||||||
.article-footer {
|
.article-footer {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
@@ -470,88 +418,6 @@ html.dark .post-drop-meta__item:hover {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author-box__header {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__avatar {
|
|
||||||
width: 56px;
|
|
||||||
height: 56px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
border: 3px solid var(--color-primary-soft, rgba(240, 68, 148, 0.2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__info {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__name {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--color-ink);
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__name:hover { color: var(--color-primary); }
|
|
||||||
|
|
||||||
.author-box__bio {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-ink-3, var(--color-ink-4));
|
|
||||||
line-height: 1.5;
|
|
||||||
margin: 0 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__stats {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--color-ink-4, var(--color-ink-4));
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__stats strong {
|
|
||||||
color: var(--color-ink);
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts {
|
|
||||||
margin-top: 14px;
|
|
||||||
padding-top: 12px;
|
|
||||||
border-top: 1px solid var(--color-line);
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts-title {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-ink-3, var(--color-ink-4));
|
|
||||||
margin: 0 0 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts li { padding: 3px 0; }
|
|
||||||
|
|
||||||
.author-box__posts a {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--color-ink);
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 1;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.author-box__posts a:hover { color: var(--color-primary); }
|
|
||||||
.author-box__posts a::before { content: '\2022'; color: var(--color-primary); }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ===== legacy single-metabox / author bar ===== */
|
/* ===== legacy single-metabox / author bar ===== */
|
||||||
.article-avatar .user-info {
|
.article-avatar .user-info {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
+58
-59
@@ -4508,31 +4508,60 @@ tbquire.config({
|
|||||||
/**
|
/**
|
||||||
* AKSRT Follow — 前台关注交互
|
* AKSRT Follow — 前台关注交互
|
||||||
*
|
*
|
||||||
* 监听 [data-aksrt-follow] 按钮点击,切换关注状态。
|
|
||||||
*
|
|
||||||
* @package AKSRT
|
* @package AKSRT
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var API_ROOT = (function () {
|
var API_ROOT = (function () {
|
||||||
var d = window.aksrtData || {};
|
var data = window.aksrtData || {};
|
||||||
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
|
var raw = data.apiUrl || data.restUrl || data.rest_url || '/index.php?rest_route=/aksrt/v1';
|
||||||
return String(raw).replace(/\/$/, '');
|
return String(raw).replace(/\/$/, '');
|
||||||
})();
|
})();
|
||||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||||
|
|
||||||
|
function buttonsFor(userId) {
|
||||||
|
return Array.prototype.slice.call(document.querySelectorAll('[data-aksrt-follow="' + userId + '"]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoading(buttons, loading) {
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
button.disabled = loading;
|
||||||
|
button.classList.toggle('is-loading', loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButton(button, isFollowing) {
|
||||||
|
var icon = button.querySelector('i');
|
||||||
|
var label = button.querySelector('[data-aksrt-follow-label]');
|
||||||
|
if (!label) return;
|
||||||
|
|
||||||
|
button.classList.toggle('is-following', isFollowing);
|
||||||
|
if (icon) {
|
||||||
|
icon.className = isFollowing ? 'fa fas fa-heart em09' : 'fa far fa-heart em09';
|
||||||
|
}
|
||||||
|
label.textContent = isFollowing ? '已关注' : '关注';
|
||||||
|
button.title = isFollowing ? '点击取消关注' : '关注该用户';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFollowerCount(count) {
|
||||||
|
var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]');
|
||||||
|
if (countEl && count !== undefined) {
|
||||||
|
countEl.textContent = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始关注状态由服务端渲染(is-following class + 文案),无需逐按钮查询。
|
|
||||||
function initFollowButtons() {
|
function initFollowButtons() {
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (event) {
|
||||||
var btn = e.target.closest('[data-aksrt-follow]');
|
var button = event.target.closest('[data-aksrt-follow]');
|
||||||
if (!btn) return;
|
if (!button) return;
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var targetUserId = parseInt(btn.getAttribute('data-aksrt-follow'));
|
var targetUserId = parseInt(button.getAttribute('data-aksrt-follow'), 10);
|
||||||
if (!targetUserId) return;
|
if (!targetUserId || button.disabled) return;
|
||||||
|
|
||||||
btn.disabled = true;
|
var matchingButtons = buttonsFor(targetUserId);
|
||||||
|
setLoading(matchingButtons, true);
|
||||||
|
|
||||||
fetch(API_ROOT + '/follow/toggle', {
|
fetch(API_ROOT + '/follow/toggle', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -4542,64 +4571,34 @@ tbquire.config({
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ target_user_id: targetUserId }),
|
body: JSON.stringify({ target_user_id: targetUserId }),
|
||||||
})
|
})
|
||||||
.then(function (res) { return res.json(); })
|
.then(function (response) { return response.json(); })
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
if (result.success && result.data) {
|
if (!result.success || !result.data) {
|
||||||
var isFollowing = result.data.is_following;
|
|
||||||
updateFollowButton(btn, isFollowing, result.data.follower_count);
|
|
||||||
notyf(isFollowing ? '关注成功' : '已取消关注', 'success');
|
|
||||||
} else {
|
|
||||||
notyf(result.message || '操作失败', 'danger');
|
notyf(result.message || '操作失败', 'danger');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFollowing = Boolean(result.data.is_following);
|
||||||
|
matchingButtons.forEach(function (matchingButton) {
|
||||||
|
updateButton(matchingButton, isFollowing);
|
||||||
|
});
|
||||||
|
updateFollowerCount(result.data.follower_count);
|
||||||
|
|
||||||
|
if (!isFollowing) {
|
||||||
|
var managementItem = button.closest('.uc-follow-management__item');
|
||||||
|
if (managementItem) managementItem.remove();
|
||||||
|
}
|
||||||
|
notyf(isFollowing ? '关注成功' : '已取消关注', 'success');
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
notyf('网络错误,请重试', 'danger');
|
notyf('网络错误,请重试', 'danger');
|
||||||
})
|
})
|
||||||
.finally(function () {
|
.finally(function () {
|
||||||
btn.disabled = false;
|
setLoading(matchingButtons, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFollowButton(btn, isFollowing, count) {
|
|
||||||
var icon = btn.querySelector('i');
|
|
||||||
var text = btn.querySelector('span');
|
|
||||||
function setLabel(label) {
|
|
||||||
if (text) {
|
|
||||||
text.textContent = label;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fallback when markup has bare text nodes next to the icon.
|
|
||||||
var nodes = Array.prototype.slice.call(btn.childNodes);
|
|
||||||
var textNode = nodes.filter(function (n) { return n.nodeType === 3 && n.textContent.trim() !== ''; })[0];
|
|
||||||
if (textNode) {
|
|
||||||
textNode.textContent = ' ' + label + ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFollowing) {
|
|
||||||
btn.classList.add('is-following');
|
|
||||||
if (icon) icon.className = 'fa fas fa-heart';
|
|
||||||
setLabel('已关注');
|
|
||||||
btn.title = '点击取消关注';
|
|
||||||
} else {
|
|
||||||
btn.classList.remove('is-following');
|
|
||||||
if (icon) icon.className = 'fa fas fa-heart';
|
|
||||||
setLabel('关注');
|
|
||||||
btn.title = '关注该用户';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFollowing && btn.closest('.uc-follow-management__item')) {
|
|
||||||
btn.closest('.uc-follow-management__item').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update follower count display if exists
|
|
||||||
var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]');
|
|
||||||
if (countEl && count !== undefined) {
|
|
||||||
countEl.textContent = count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', initFollowButtons);
|
document.addEventListener('DOMContentLoaded', initFollowButtons);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+58
-59
@@ -1,31 +1,60 @@
|
|||||||
/**
|
/**
|
||||||
* AKSRT Follow — 前台关注交互
|
* AKSRT Follow — 前台关注交互
|
||||||
*
|
*
|
||||||
* 监听 [data-aksrt-follow] 按钮点击,切换关注状态。
|
|
||||||
*
|
|
||||||
* @package AKSRT
|
* @package AKSRT
|
||||||
*/
|
*/
|
||||||
(function () {
|
(function () {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var API_ROOT = (function () {
|
var API_ROOT = (function () {
|
||||||
var d = window.aksrtData || {};
|
var data = window.aksrtData || {};
|
||||||
var raw = d.apiUrl || d.restUrl || d.rest_url || '/index.php?rest_route=/aksrt/v1';
|
var raw = data.apiUrl || data.restUrl || data.rest_url || '/index.php?rest_route=/aksrt/v1';
|
||||||
return String(raw).replace(/\/$/, '');
|
return String(raw).replace(/\/$/, '');
|
||||||
})();
|
})();
|
||||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||||
|
|
||||||
|
function buttonsFor(userId) {
|
||||||
|
return Array.prototype.slice.call(document.querySelectorAll('[data-aksrt-follow="' + userId + '"]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLoading(buttons, loading) {
|
||||||
|
buttons.forEach(function (button) {
|
||||||
|
button.disabled = loading;
|
||||||
|
button.classList.toggle('is-loading', loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateButton(button, isFollowing) {
|
||||||
|
var icon = button.querySelector('i');
|
||||||
|
var label = button.querySelector('[data-aksrt-follow-label]');
|
||||||
|
if (!label) return;
|
||||||
|
|
||||||
|
button.classList.toggle('is-following', isFollowing);
|
||||||
|
if (icon) {
|
||||||
|
icon.className = isFollowing ? 'fa fas fa-heart em09' : 'fa far fa-heart em09';
|
||||||
|
}
|
||||||
|
label.textContent = isFollowing ? '已关注' : '关注';
|
||||||
|
button.title = isFollowing ? '点击取消关注' : '关注该用户';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFollowerCount(count) {
|
||||||
|
var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]');
|
||||||
|
if (countEl && count !== undefined) {
|
||||||
|
countEl.textContent = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 初始关注状态由服务端渲染(is-following class + 文案),无需逐按钮查询。
|
|
||||||
function initFollowButtons() {
|
function initFollowButtons() {
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (event) {
|
||||||
var btn = e.target.closest('[data-aksrt-follow]');
|
var button = event.target.closest('[data-aksrt-follow]');
|
||||||
if (!btn) return;
|
if (!button) return;
|
||||||
e.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
var targetUserId = parseInt(btn.getAttribute('data-aksrt-follow'));
|
var targetUserId = parseInt(button.getAttribute('data-aksrt-follow'), 10);
|
||||||
if (!targetUserId) return;
|
if (!targetUserId || button.disabled) return;
|
||||||
|
|
||||||
btn.disabled = true;
|
var matchingButtons = buttonsFor(targetUserId);
|
||||||
|
setLoading(matchingButtons, true);
|
||||||
|
|
||||||
fetch(API_ROOT + '/follow/toggle', {
|
fetch(API_ROOT + '/follow/toggle', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -35,64 +64,34 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ target_user_id: targetUserId }),
|
body: JSON.stringify({ target_user_id: targetUserId }),
|
||||||
})
|
})
|
||||||
.then(function (res) { return res.json(); })
|
.then(function (response) { return response.json(); })
|
||||||
.then(function (result) {
|
.then(function (result) {
|
||||||
if (result.success && result.data) {
|
if (!result.success || !result.data) {
|
||||||
var isFollowing = result.data.is_following;
|
|
||||||
updateFollowButton(btn, isFollowing, result.data.follower_count);
|
|
||||||
notyf(isFollowing ? '关注成功' : '已取消关注', 'success');
|
|
||||||
} else {
|
|
||||||
notyf(result.message || '操作失败', 'danger');
|
notyf(result.message || '操作失败', 'danger');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isFollowing = Boolean(result.data.is_following);
|
||||||
|
matchingButtons.forEach(function (matchingButton) {
|
||||||
|
updateButton(matchingButton, isFollowing);
|
||||||
|
});
|
||||||
|
updateFollowerCount(result.data.follower_count);
|
||||||
|
|
||||||
|
if (!isFollowing) {
|
||||||
|
var managementItem = button.closest('.uc-follow-management__item');
|
||||||
|
if (managementItem) managementItem.remove();
|
||||||
|
}
|
||||||
|
notyf(isFollowing ? '关注成功' : '已取消关注', 'success');
|
||||||
})
|
})
|
||||||
.catch(function () {
|
.catch(function () {
|
||||||
notyf('网络错误,请重试', 'danger');
|
notyf('网络错误,请重试', 'danger');
|
||||||
})
|
})
|
||||||
.finally(function () {
|
.finally(function () {
|
||||||
btn.disabled = false;
|
setLoading(matchingButtons, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFollowButton(btn, isFollowing, count) {
|
|
||||||
var icon = btn.querySelector('i');
|
|
||||||
var text = btn.querySelector('span');
|
|
||||||
function setLabel(label) {
|
|
||||||
if (text) {
|
|
||||||
text.textContent = label;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Fallback when markup has bare text nodes next to the icon.
|
|
||||||
var nodes = Array.prototype.slice.call(btn.childNodes);
|
|
||||||
var textNode = nodes.filter(function (n) { return n.nodeType === 3 && n.textContent.trim() !== ''; })[0];
|
|
||||||
if (textNode) {
|
|
||||||
textNode.textContent = ' ' + label + ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFollowing) {
|
|
||||||
btn.classList.add('is-following');
|
|
||||||
if (icon) icon.className = 'fa fas fa-heart';
|
|
||||||
setLabel('已关注');
|
|
||||||
btn.title = '点击取消关注';
|
|
||||||
} else {
|
|
||||||
btn.classList.remove('is-following');
|
|
||||||
if (icon) icon.className = 'fa fas fa-heart';
|
|
||||||
setLabel('关注');
|
|
||||||
btn.title = '关注该用户';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFollowing && btn.closest('.uc-follow-management__item')) {
|
|
||||||
btn.closest('.uc-follow-management__item').remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update follower count display if exists
|
|
||||||
var countEl = document.querySelector('.aksrt-user-home__stat-value[data-followers]');
|
|
||||||
if (countEl && count !== undefined) {
|
|
||||||
countEl.textContent = count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
document.addEventListener('DOMContentLoaded', initFollowButtons);
|
document.addEventListener('DOMContentLoaded', initFollowButtons);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -68,13 +68,10 @@ if (!function_exists('aksrt_resolve_single_post_display_settings')) :
|
|||||||
if (!array_key_exists('breadcrumbs_home', $settings) && array_key_exists('breadcrumbs_home_text', $settings)) {
|
if (!array_key_exists('breadcrumbs_home', $settings) && array_key_exists('breadcrumbs_home_text', $settings)) {
|
||||||
$settings['breadcrumbs_home'] = (string) $settings['breadcrumbs_home_text'];
|
$settings['breadcrumbs_home'] = (string) $settings['breadcrumbs_home_text'];
|
||||||
}
|
}
|
||||||
if (!array_key_exists('prevnext', $settings) && array_key_exists('prevnext_s', $settings)) {
|
if (!array_key_exists('prevnext', $settings) && array_key_exists('prevnext_s', $settings)) {
|
||||||
$settings['prevnext'] = !empty($settings['prevnext_s']);
|
$settings['prevnext'] = !empty($settings['prevnext_s']);
|
||||||
}
|
}
|
||||||
if (!array_key_exists('authordesc', $settings) && array_key_exists('authordesc_s', $settings)) {
|
$meta = class_exists('\AKSRT\Core\PostMetaBox') ? \AKSRT\Core\PostMetaBox::get_meta($post->ID) : [];
|
||||||
$settings['authordesc'] = !empty($settings['authordesc_s']);
|
|
||||||
}
|
|
||||||
$meta = class_exists('\AKSRT\Core\PostMetaBox') ? \AKSRT\Core\PostMetaBox::get_meta($post->ID) : [];
|
|
||||||
if (!empty($meta['article_maxheight_xz'])) {
|
if (!empty($meta['article_maxheight_xz'])) {
|
||||||
$settings['maxheight_enabled'] = true;
|
$settings['maxheight_enabled'] = true;
|
||||||
} elseif (empty($settings['maxheight_enabled'])) {
|
} elseif (empty($settings['maxheight_enabled'])) {
|
||||||
|
|||||||
@@ -241,27 +241,12 @@ if (!function_exists('aksrt_author_minicard')) :
|
|||||||
])
|
])
|
||||||
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
|
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
|
||||||
|
|
||||||
// Follow control (separate from name, legacy places both in dt).
|
// Follow control (separate from name, legacy places both in dt).
|
||||||
// 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。
|
$follow = function_exists('aksrt_get_user_follow')
|
||||||
$follow = '';
|
? aksrt_get_user_follow('focus-color px12 ml10 follow flex0 but', $user_id)
|
||||||
$current = get_current_user_id();
|
: '';
|
||||||
if ($current !== $user_id) {
|
|
||||||
$is_following = false;
|
|
||||||
if (is_user_logged_in() && class_exists('\AKSRT\User\Follow')) {
|
|
||||||
$is_following = \AKSRT\User\Follow::get_instance()->is_following($current, $user_id);
|
|
||||||
}
|
|
||||||
$follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
|
|
||||||
$follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
|
|
||||||
$follow_state = $is_following ? ' is-following' : '';
|
|
||||||
if (is_user_logged_in()) {
|
|
||||||
$follow = '<button type="button" class="focus-color px12 ml10 follow flex0 but' . $follow_state . '" data-aksrt-follow="' . (int) $user_id . '" title="' . esc_attr($follow_title) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html($follow_label) . '</button>';
|
|
||||||
} else {
|
|
||||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
|
||||||
$follow = '<a class="focus-color px12 ml10 follow flex0 but" href="' . esc_url($login) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$desc = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($user_id) : trim((string) get_user_meta($user_id, 'description', true));
|
$desc = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($user_id) : trim((string) get_user_meta($user_id, 'description', true));
|
||||||
|
|
||||||
$blur = '';
|
$blur = '';
|
||||||
if ($blur_bg) {
|
if ($blur_bg) {
|
||||||
|
|||||||
@@ -119,6 +119,37 @@ if (!function_exists('aksrt_render_user_identity_chips')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('aksrt_get_user_follow')) {
|
||||||
|
/**
|
||||||
|
* Render the canonical user follow control.
|
||||||
|
*/
|
||||||
|
function aksrt_get_user_follow(string $class, int $follow_id, ?string $text = null, ?string $ok_text = null, ?string $guest_text = null): string {
|
||||||
|
$current_user_id = get_current_user_id();
|
||||||
|
if ($follow_id <= 0 || $current_user_id === $follow_id) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = $text ?? __('关注', 'aksrt');
|
||||||
|
$ok_text = $ok_text ?? __('已关注', 'aksrt');
|
||||||
|
$guest_text = $guest_text ?? $text;
|
||||||
|
$class = trim($class);
|
||||||
|
|
||||||
|
if (!is_user_logged_in()) {
|
||||||
|
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
||||||
|
return '<a class="' . esc_attr($class) . '" href="' . esc_url($login_url) . '"><i class="fa far fa-heart em09" aria-hidden="true"></i> <span>' . esc_html($guest_text) . '</span></a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$is_following = class_exists('\AKSRT\User\Follow')
|
||||||
|
&& \AKSRT\User\Follow::get_instance()->is_following($current_user_id, $follow_id);
|
||||||
|
$state_class = $is_following ? ' is-following' : '';
|
||||||
|
$title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
|
||||||
|
$label = $is_following ? $ok_text : $text;
|
||||||
|
$icon_class = $is_following ? 'fa fas fa-heart em09' : 'fa far fa-heart em09';
|
||||||
|
|
||||||
|
return '<a href="javascript:;" class="' . esc_attr($class . $state_class) . '" data-aksrt-follow="' . $follow_id . '" title="' . esc_attr($title) . '"><i class="' . esc_attr($icon_class) . '" aria-hidden="true"></i> <span data-aksrt-follow-label>' . esc_html($label) . '</span></a>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('aksrt_get_user_name')) {
|
if (!function_exists('aksrt_get_user_name')) {
|
||||||
/**
|
/**
|
||||||
* legacy parity: name link + auth + wear medal + level + optional follow.
|
* legacy parity: name link + auth + wear medal + level + optional follow.
|
||||||
@@ -155,7 +186,9 @@ if (!function_exists('aksrt_get_user_name')) {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$home = function_exists('get_author_posts_url') ? get_author_posts_url($user_id) : '';
|
$home = function_exists('aksrt_get_user_home_url')
|
||||||
|
? aksrt_get_user_home_url($user_id)
|
||||||
|
: get_author_posts_url($user_id);
|
||||||
$display = $user->display_name;
|
$display = $user->display_name;
|
||||||
$vip_html = '';
|
$vip_html = '';
|
||||||
if (!empty($args['vip']) && function_exists('aksrt_render_vip_badge')) {
|
if (!empty($args['vip']) && function_exists('aksrt_render_vip_badge')) {
|
||||||
@@ -220,22 +253,11 @@ if (!function_exists('aksrt_get_user_name')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$follow_html = '';
|
$follow_html = !empty($args['follow'])
|
||||||
if (!empty($args['follow']) && get_current_user_id() !== $user_id) {
|
? aksrt_get_user_follow('focus-color ml10 follow flex0', $user_id)
|
||||||
if (is_user_logged_in()) {
|
: '';
|
||||||
// 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。
|
|
||||||
$is_following = class_exists('\AKSRT\User\Follow') && \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), $user_id);
|
|
||||||
$follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
|
|
||||||
$follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
|
|
||||||
$follow_state = $is_following ? ' is-following' : '';
|
|
||||||
$follow_html = '<button type="button" class="focus-color ml10 follow flex0' . $follow_state . '" data-aksrt-follow="' . (int) $user_id . '" title="' . esc_attr($follow_title) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html($follow_label) . '</button>';
|
|
||||||
} else {
|
|
||||||
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
|
||||||
$follow_html = '<a class="focus-color ml10 follow flex0" href="' . esc_url($login) . '"><i class="fa fas fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order: name → auth → wear medal → level → follow (legacy).
|
// Order: name → auth → wear medal → level → follow (legacy).
|
||||||
$html = '<name class="' . esc_attr((string) $args['class']) . '">' . $name_link . $vip_html . $auth_html . $medal_html . $level_html . $follow_html . '</name>';
|
$html = '<name class="' . esc_attr((string) $args['class']) . '">' . $name_link . $vip_html . $auth_html . $medal_html . $level_html . $follow_html . '</name>';
|
||||||
/**
|
/**
|
||||||
* Filter rendered user name chrome.
|
* Filter rendered user name chrome.
|
||||||
|
|||||||
@@ -156,13 +156,10 @@ if (!function_exists('aksrt_single_render_box_header')) :
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
// Author bar always shows in header (legacy).
|
get_template_part('templates/parts/single-author-bar', null, [
|
||||||
// `authordesc` only controls the after-box author card.
|
'show_time' => empty($settings['hide_time']),
|
||||||
aksrt_single_render_author_bar([
|
'meta_html' => (string) ($ctx['meta_box_html'] ?? ''),
|
||||||
'show_actions' => true,
|
]);
|
||||||
'show_time' => empty($settings['hide_time']),
|
|
||||||
'meta_html' => (string) ($ctx['meta_box_html'] ?? ''),
|
|
||||||
]);
|
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
@@ -681,13 +678,9 @@ if (!function_exists('aksrt_single_render_after_box')) :
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept both `authordesc` (Settings DTO) and legacy `authordesc_s`.
|
if (!empty($settings['authordesc'])) {
|
||||||
$show_author = array_key_exists('authordesc', $settings)
|
aksrt_single_render_author_card($ctx);
|
||||||
? !empty($settings['authordesc'])
|
}
|
||||||
: !empty($settings['authordesc_s']);
|
|
||||||
if ($show_author) {
|
|
||||||
aksrt_single_render_author_card($ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
aksrt_single_render_prevnext($ctx);
|
aksrt_single_render_prevnext($ctx);
|
||||||
aksrt_single_render_related($ctx);
|
aksrt_single_render_related($ctx);
|
||||||
|
|||||||
@@ -137,23 +137,6 @@ if (!function_exists('aksrt_single_is_docs_page')) :
|
|||||||
}
|
}
|
||||||
endif;
|
endif;
|
||||||
|
|
||||||
if (!function_exists('aksrt_single_render_author_bar')) :
|
|
||||||
/**
|
|
||||||
* Render author bar from a real args array (no ambient query vars).
|
|
||||||
*
|
|
||||||
* @param array<string,mixed> $args Partial args.
|
|
||||||
*/
|
|
||||||
function aksrt_single_render_author_bar(array $args = []): void {
|
|
||||||
$args = wp_parse_args($args, [
|
|
||||||
'extra_class' => '',
|
|
||||||
'show_actions' => false,
|
|
||||||
'show_bio' => false,
|
|
||||||
'meta_items' => [],
|
|
||||||
]);
|
|
||||||
get_template_part('templates/parts/single-author-bar', null, $args);
|
|
||||||
}
|
|
||||||
endif;
|
|
||||||
|
|
||||||
if (!function_exists('aksrt_single')) :
|
if (!function_exists('aksrt_single')) :
|
||||||
/**
|
/**
|
||||||
* Compose header + article + after-box (legacy equivalent).
|
* Compose header + article + after-box (legacy equivalent).
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ $result = $follow
|
|||||||
<div class="uc-follow-management__identity"><a href="<?php echo esc_url($home_url); ?>"><?php echo esc_html((string) ($item['display_name'] ?? '')); ?></a><span><?php echo esc_html(sprintf(__('关注于 %s', 'aksrt'), get_date_from_gmt((string) ($item['created_at'] ?? ''), get_option('date_format')))); ?></span></div>
|
<div class="uc-follow-management__identity"><a href="<?php echo esc_url($home_url); ?>"><?php echo esc_html((string) ($item['display_name'] ?? '')); ?></a><span><?php echo esc_html(sprintf(__('关注于 %s', 'aksrt'), get_date_from_gmt((string) ($item['created_at'] ?? ''), get_option('date_format')))); ?></span></div>
|
||||||
<div class="uc-follow-management__actions">
|
<div class="uc-follow-management__actions">
|
||||||
<a class="btn-ghost btn-sm" href="<?php echo esc_url($home_url); ?>"><?php esc_html_e('主页', 'aksrt'); ?></a>
|
<a class="btn-ghost btn-sm" href="<?php echo esc_url($home_url); ?>"><?php esc_html_e('主页', 'aksrt'); ?></a>
|
||||||
<?php if ($active_list === 'following') : ?><button type="button" class="btn-outline btn-sm is-following" data-aksrt-follow="<?php echo esc_attr((string) $member_id); ?>"><i class="fa fas fa-heart" aria-hidden="true"></i><span><?php esc_html_e('已关注', 'aksrt'); ?></span></button><?php endif; ?>
|
<?php if ($active_list === 'following') : ?><button type="button" class="btn-outline btn-sm is-following" data-aksrt-follow="<?php echo esc_attr((string) $member_id); ?>"><i class="fa fas fa-heart" aria-hidden="true"></i><span data-aksrt-follow-label><?php esc_html_e('已关注', 'aksrt'); ?></span></button><?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
|
|||||||
@@ -218,12 +218,11 @@ class PostDisplay {
|
|||||||
'breadcrumbs_single_s' => $breadcrumbs,
|
'breadcrumbs_single_s' => $breadcrumbs,
|
||||||
'breadcrumbs_home_text' => $breadcrumbs_home,
|
'breadcrumbs_home_text' => $breadcrumbs_home,
|
||||||
'breadcrumbs_single_text' => $breadcrumbs_single_text,
|
'breadcrumbs_single_text' => $breadcrumbs_single_text,
|
||||||
// Canonical + legacy prev/next / author / time.
|
// Canonical + legacy prev/next / time; canonical author card flag.
|
||||||
'prevnext' => $prevnext,
|
'prevnext' => $prevnext,
|
||||||
'prevnext_s' => $prevnext,
|
'prevnext_s' => $prevnext,
|
||||||
'authordesc' => $authordesc,
|
'authordesc' => $authordesc,
|
||||||
'authordesc_s' => $authordesc,
|
'hide_time' => $hide_time,
|
||||||
'hide_time' => $hide_time,
|
|
||||||
'single_hide_time_s' => $hide_time,
|
'single_hide_time_s' => $hide_time,
|
||||||
'article_image_cover' => !isset($settings['post_article_image_cover']) || !empty($settings['post_article_image_cover']),
|
'article_image_cover' => !isset($settings['post_article_image_cover']) || !empty($settings['post_article_image_cover']),
|
||||||
'article_image_cover_scale'=> max(10, min(100, (int) ($settings['post_article_image_cover_scale'] ?? 35))),
|
'article_image_cover_scale'=> max(10, min(100, (int) ($settings['post_article_image_cover_scale'] ?? 35))),
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Shared author info bar for single post content (legacy article-avatar / user-info).
|
* Article header author bar.
|
||||||
*
|
*
|
||||||
* Contract: get_template_part(..., null, $args)
|
* Contract: get_template_part(..., null, $args)
|
||||||
* show_actions bool Follow / PM buttons + meta row
|
* show_time bool Relative publish/update time (default true)
|
||||||
* show_bio bool Append bio after time
|
* meta_html string Prebuilt single-metabox HTML
|
||||||
* show_time bool Relative publish/update time (default true)
|
|
||||||
* meta_html string Prebuilt legacy single-metabox HTML
|
|
||||||
* meta_items array Legacy text meta items (fallback)
|
|
||||||
* extra_class string
|
|
||||||
*
|
*
|
||||||
* @package AKSRT
|
* @package AKSRT
|
||||||
*/
|
*/
|
||||||
@@ -16,78 +12,61 @@
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
$args = is_array($args ?? null) ? $args : [];
|
$args = is_array($args ?? null) ? $args : [];
|
||||||
$extra_class = (string) ($args['extra_class'] ?? '');
|
|
||||||
$show_actions = !empty($args['show_actions']);
|
|
||||||
$show_bio = !empty($args['show_bio']);
|
|
||||||
$show_time = !array_key_exists('show_time', $args) || !empty($args['show_time']);
|
$show_time = !array_key_exists('show_time', $args) || !empty($args['show_time']);
|
||||||
$meta_html = (string) ($args['meta_html'] ?? '');
|
$meta_html = (string) ($args['meta_html'] ?? '');
|
||||||
$meta_items = is_array($args['meta_items'] ?? null) ? array_values(array_filter($args['meta_items'], static function ($meta_item) {
|
|
||||||
if (is_array($meta_item)) {
|
|
||||||
return !empty($meta_item['text']);
|
|
||||||
}
|
|
||||||
return is_string($meta_item) && $meta_item !== '';
|
|
||||||
})) : [];
|
|
||||||
|
|
||||||
$author_id = (int) get_the_author_meta('ID');
|
$author_id = (int) get_the_author_meta('ID');
|
||||||
$author_name = get_the_author();
|
$author_name = get_the_author();
|
||||||
$author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id);
|
$author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id);
|
||||||
$modified_ts = (int) get_the_modified_time('U');
|
$modified_ts = (int) get_the_modified_time('U');
|
||||||
$published_ts = (int) get_the_time('U');
|
$published_ts = (int) get_the_time('U');
|
||||||
$now = (int) current_time('timestamp');
|
|
||||||
$use_modified = $modified_ts > $published_ts;
|
$use_modified = $modified_ts > $published_ts;
|
||||||
$rel_base = $use_modified ? $modified_ts : $published_ts;
|
$rel_base = $use_modified ? $modified_ts : $published_ts;
|
||||||
$rel_label = $use_modified ? __('更新', 'aksrt') : __('发布', 'aksrt');
|
$rel_label = $use_modified ? __('更新', 'aksrt') : __('发布', 'aksrt');
|
||||||
$abs_fmt = $use_modified
|
$abs_fmt = $use_modified
|
||||||
? sprintf(__('%s更新', 'aksrt'), get_the_modified_time('Y年m月d日 H:i'))
|
? sprintf(__('%s更新', 'aksrt'), get_the_modified_time('Y年m月d日 H:i'))
|
||||||
: sprintf(__('%s发布', 'aksrt'), get_the_time('Y年m月d日 H:i'));
|
: sprintf(__('%s发布', 'aksrt'), get_the_time('Y年m月d日 H:i'));
|
||||||
$time_text = human_time_diff($rel_base, $now) . $rel_label;
|
$time_text = human_time_diff($rel_base, (int) current_time('timestamp')) . $rel_label;
|
||||||
|
$description = function_exists('aksrt_get_user_description')
|
||||||
$classes = trim('article-avatar ' . $extra_class);
|
? aksrt_get_user_description($author_id)
|
||||||
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink());
|
: trim((string) get_the_author_meta('description'));
|
||||||
$is_self = is_user_logged_in() && get_current_user_id() === $author_id;
|
$name_html = aksrt_get_user_name([
|
||||||
|
'id' => $author_id,
|
||||||
// Identity: aksrt_get_user_name is the canonical renderer (legacy order).
|
'class' => 'name flex ac flex1',
|
||||||
$name_html = '';
|
'name_class' => '',
|
||||||
if (function_exists('aksrt_get_user_name')) {
|
'vip' => true,
|
||||||
$name_html = aksrt_get_user_name([
|
'medal' => true,
|
||||||
'id' => $author_id,
|
'auth' => true,
|
||||||
'class' => 'name flex ac flex1',
|
'level' => true,
|
||||||
'name_class' => '',
|
'follow' => false,
|
||||||
'vip' => true,
|
]);
|
||||||
'medal' => true,
|
$follow_html = aksrt_get_user_follow('px12-sm ml10 follow but c-red', $author_id);
|
||||||
'auth' => true,
|
$reward_button = get_current_user_id() !== $author_id && function_exists('aksrt_get_rewards_button')
|
||||||
'level' => true,
|
? aksrt_get_rewards_button($author_id, 'ml6 but c-yellow px12-sm', get_the_ID())
|
||||||
'follow' => false,
|
: '';
|
||||||
]);
|
|
||||||
}
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="<?php echo esc_attr($classes); ?>">
|
<div class="article-avatar">
|
||||||
<div class="user-info flex ac article-avatar">
|
<div class="user-info flex ac article-avatar">
|
||||||
<a href="<?php echo esc_url($author_url); ?>">
|
<a href="<?php echo esc_url($author_url); ?>">
|
||||||
<span class="avatar-img">
|
<span class="avatar-img">
|
||||||
<?php if (function_exists('aksrt_get_avatar_html')) : ?>
|
<?php if (function_exists('aksrt_get_avatar_html')) : ?>
|
||||||
<?php echo aksrt_get_avatar_html($author_id, 42, ['class' => 'avatar avatar-id-' . $author_id]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
<?php echo aksrt_get_avatar_html($author_id, 42, ['class' => 'avatar avatar-id-' . $author_id]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php echo get_avatar($author_id, 42, '', esc_attr($author_name), ['class' => 'avatar avatar-id-' . $author_id]); ?>
|
<?php echo get_avatar($author_id, 42, '', esc_attr($author_name), ['class' => 'avatar avatar-id-' . $author_id]); ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="user-right flex flex1 ac jsb ml10">
|
<div class="user-right flex flex1 ac jsb ml10">
|
||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
<?php if ($name_html !== '') : ?>
|
<?php echo $name_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php echo $name_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
||||||
<?php else : ?>
|
|
||||||
<span class="name flex ac flex1">
|
|
||||||
<a class="display-name text-ellipsis" href="<?php echo esc_url($author_url); ?>"><?php echo esc_html($author_name); ?></a>
|
|
||||||
</span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<div class="px12-sm muted-2-color text-ellipsis">
|
<div class="px12-sm muted-2-color text-ellipsis">
|
||||||
<?php if ($show_time) : ?>
|
<?php if ($show_time) : ?>
|
||||||
<span class="icon-circle" title="<?php echo esc_attr($abs_fmt); ?>" data-toggle="tooltip"><?php echo esc_html($time_text); ?></span>
|
<span class="icon-circle" title="<?php echo esc_attr($abs_fmt); ?>" data-toggle="tooltip"><?php echo esc_html($time_text); ?></span>
|
||||||
|
<?php elseif ($description !== '') : ?>
|
||||||
|
<span class="icon-circle"><?php echo esc_html($description); ?></span>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php
|
<?php
|
||||||
// Article detail IP 归属地 (extension display: post).
|
|
||||||
$post_city = function_exists('aksrt_get_post_author_city_badge_html')
|
$post_city = function_exists('aksrt_get_post_author_city_badge_html')
|
||||||
? aksrt_get_post_author_city_badge_html($author_id, 'badg badg-sm')
|
? aksrt_get_post_author_city_badge_html($author_id, 'badg badg-sm')
|
||||||
: '';
|
: '';
|
||||||
@@ -95,68 +74,20 @@ if (function_exists('aksrt_get_user_name')) {
|
|||||||
echo $post_city; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
echo $post_city; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
<?php if ($show_bio) : ?>
|
|
||||||
<?php $bio = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($author_id) : trim((string) get_the_author_meta('description')); ?>
|
|
||||||
<?php if ($bio !== '') : ?>
|
|
||||||
<span class="icon-circle"><?php echo esc_html($bio); ?></span>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php if ($show_actions) : ?>
|
<div class="flex0 user-action">
|
||||||
<div class="flex0 user-action">
|
<?php echo $follow_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php if (!$is_self) : ?>
|
<?php if ($reward_button !== '') : ?>
|
||||||
<?php if (is_user_logged_in()) : ?>
|
<?php echo $reward_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php
|
<?php aksrt_render_rewards_modal_for_user($author_id, get_the_ID()); ?>
|
||||||
// 服务端直接输出真实关注状态(legacy parity: zib_get_user_follow 恒渲染当前态)。
|
<?php endif; ?>
|
||||||
$is_following = class_exists('\AKSRT\User\Follow') && \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), $author_id);
|
</div>
|
||||||
$follow_label = $is_following ? __('已关注', 'aksrt') : __('关注', 'aksrt');
|
|
||||||
$follow_title = $is_following ? __('点击取消关注', 'aksrt') : __('关注该用户', 'aksrt');
|
|
||||||
?>
|
|
||||||
<button type="button" class="px12-sm ml10 follow c-red<?php echo $is_following ? ' is-following' : ''; ?>" data-aksrt-follow="<?php echo (int) $author_id; ?>" title="<?php echo esc_attr($follow_title); ?>">
|
|
||||||
<i class="fa fas fa-heart em09" aria-hidden="true"></i> <span><?php echo esc_html($follow_label); ?></span>
|
|
||||||
</button>
|
|
||||||
<?php
|
|
||||||
$reward_button = function_exists('aksrt_get_rewards_button')
|
|
||||||
? aksrt_get_rewards_button($author_id, 'ml6 but c-yellow px12-sm', get_the_ID())
|
|
||||||
: '';
|
|
||||||
if ($reward_button !== '') {
|
|
||||||
echo $reward_button; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
|
||||||
aksrt_render_rewards_modal_for_user($author_id, get_the_ID());
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<?php else : ?>
|
|
||||||
<a href="<?php echo esc_url($login_url); ?>" class="px12-sm ml10 follow c-red">
|
|
||||||
<i class="fa fas fa-heart em09" aria-hidden="true"></i> <?php esc_html_e('关注', 'aksrt'); ?>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($show_actions) : ?>
|
<div class="single-meta-row">
|
||||||
<div class="single-meta-row">
|
<i class="line-form-line" aria-hidden="true"></i>
|
||||||
<i class="line-form-line" aria-hidden="true"></i>
|
<?php echo $meta_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php if ($meta_html !== '') : ?>
|
</div>
|
||||||
<?php echo $meta_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
|
||||||
<?php elseif (!empty($meta_items)) : ?>
|
|
||||||
<div class="flex ac single-metabox">
|
|
||||||
<div class="post-metas">
|
|
||||||
<?php foreach ($meta_items as $meta_item) : ?>
|
|
||||||
<?php
|
|
||||||
$meta_text = is_array($meta_item) ? (string) ($meta_item['text'] ?? '') : (string) $meta_item;
|
|
||||||
$meta_icon = is_array($meta_item) ? (string) ($meta_item['icon'] ?? '') : '';
|
|
||||||
if ($meta_text === '') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<span class="item meta-view"><?php if ($meta_icon !== '') : ?><i class="<?php echo esc_attr($meta_icon); ?>" aria-hidden="true"></i> <?php endif; ?><?php echo esc_html($meta_text); ?></span>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -156,23 +156,8 @@ $unread_count = $is_own ? aksrt_get_user_unread_notification_count($user_id) : 0
|
|||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php else : ?>
|
<?php else : ?>
|
||||||
<?php if ($show_follow) : ?>
|
<?php if ($show_follow) : ?>
|
||||||
<?php if (is_user_logged_in()) : ?>
|
<?php echo aksrt_get_user_follow('btn-outline btn-sm aksrt-author-hero__follow-btn', (int) $user_id, null, null, __('登录后关注', 'aksrt')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||||
<?php
|
|
||||||
$hero_following = class_exists('\AKSRT\User\Follow')
|
|
||||||
? \AKSRT\User\Follow::get_instance()->is_following(get_current_user_id(), (int) $user_id)
|
|
||||||
: false;
|
|
||||||
?>
|
|
||||||
<button type="button" class="btn-outline btn-sm aksrt-author-hero__follow-btn<?php echo $hero_following ? ' is-following' : ''; ?>" data-aksrt-follow="<?php echo (int) $user_id; ?>" title="<?php echo $hero_following ? esc_attr__('点击取消关注', 'aksrt') : esc_attr__('关注该用户', 'aksrt'); ?>">
|
|
||||||
<i class="fa fas fa-heart" aria-hidden="true"></i>
|
|
||||||
<span><?php echo esc_html($hero_following ? __('已关注', 'aksrt') : __('关注', 'aksrt')); ?></span>
|
|
||||||
</button>
|
|
||||||
<?php else : ?>
|
|
||||||
<a href="<?php echo esc_url(aksrt_get_login_url($home_url)); ?>" class="btn-outline btn-sm aksrt-author-hero__follow-btn">
|
|
||||||
<i class="fa fas fa-heart" aria-hidden="true"></i>
|
|
||||||
<span><?php esc_html_e('登录后关注', 'aksrt'); ?></span>
|
|
||||||
</a>
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|||||||
@@ -259,6 +259,12 @@ test('after-box author card mirrors legacy user-card structure', () => {
|
|||||||
const userCard = read('inc/helpers/aksrt-user-card.php');
|
const userCard = read('inc/helpers/aksrt-user-card.php');
|
||||||
const css = read('assets/css/frontend/single-post/related-toc.css');
|
const css = read('assets/css/frontend/single-post/related-toc.css');
|
||||||
const authorActions = read('assets/css/frontend/single-post/author-actions.css');
|
const authorActions = read('assets/css/frontend/single-post/author-actions.css');
|
||||||
|
const base = read('assets/css/frontend/base.css');
|
||||||
|
const follow = read('assets/js/follow.js');
|
||||||
|
const authorBar = read('templates/parts/single-author-bar.php');
|
||||||
|
const single = read('inc/helpers/aksrt-single.php');
|
||||||
|
const settings = read('src/Settings/PostDisplay.php');
|
||||||
|
const frontendTags = read('inc/helpers/aksrt-frontend-tags.php');
|
||||||
|
|
||||||
assert.match(render, /avatar-img avatar-lg/);
|
assert.match(render, /avatar-img avatar-lg/);
|
||||||
assert.match(render, /'vip'\s+=>\s+false/);
|
assert.match(render, /'vip'\s+=>\s+false/);
|
||||||
@@ -280,6 +286,21 @@ test('after-box author card mirrors legacy user-card structure', () => {
|
|||||||
assert.match(css, /^\.zib-widget \{/m);
|
assert.match(css, /^\.zib-widget \{/m);
|
||||||
assert.doesNotMatch(css, /relates-thumb \.graphic\.style-3 \.graphic-img/);
|
assert.doesNotMatch(css, /relates-thumb \.graphic\.style-3 \.graphic-img/);
|
||||||
assert.doesNotMatch(authorActions, /\.user-card-author|\.user-card \.author|user-card-posts/);
|
assert.doesNotMatch(authorActions, /\.user-card-author|\.user-card \.author|user-card-posts/);
|
||||||
|
assert.match(identity, /function aksrt_get_user_follow\(/);
|
||||||
|
assert.match(identity, /data-aksrt-follow-label/);
|
||||||
|
assert.match(identity, /aksrt_get_user_follow\('focus-color ml10 follow flex0', \$user_id\)/);
|
||||||
|
assert.match(authorBar, /aksrt_get_user_follow\('px12-sm ml10 follow but c-red', \$author_id\)/);
|
||||||
|
assert.doesNotMatch(authorBar, /Follow::get_instance|is_following|show_actions|show_bio|extra_class|meta_items/);
|
||||||
|
assert.doesNotMatch(single, /aksrt_single_render_author_bar/);
|
||||||
|
assert.match(base, /\.user-card\.author \.user-name > name\s*\{[^}]*overflow:\s*visible;/s);
|
||||||
|
assert.match(base, /\.user-card\.author \.user-name > name > \.follow\s*\{[^}]*flex-shrink:\s*0;/s);
|
||||||
|
assert.match(follow, /querySelectorAll\('\[data-aksrt-follow="' \+ userId/);
|
||||||
|
assert.match(follow, /matchingButtons\.forEach/);
|
||||||
|
assert.match(follow, /\[data-aksrt-follow-label\]/);
|
||||||
|
assert.doesNotMatch(follow, /childNodes|textNode|Fallback when markup/);
|
||||||
|
assert.doesNotMatch(settings, /'authordesc_s'\s*=>/);
|
||||||
|
assert.doesNotMatch(frontendTags, /array_key_exists\('authordesc_s'/);
|
||||||
|
assert.doesNotMatch(authorActions, /article-author-bar__|author-box__/);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('AJAX pagination can recover, caps auto loading and rehydrates appended media', () => {
|
test('AJAX pagination can recover, caps auto loading and rehydrates appended media', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user