用户公开主页全局采用DOM无刷新加载
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# 公开用户主页去参数化 + 全链路无刷新加载
|
||||
|
||||
## 目标
|
||||
|
||||
`/author/demo/` 的 URL 永远干净(无 `?tab=`、`follow_sub`、`follow_page`、`posts_page` 等),Tab 切换、粉丝/关注子切换、各面板分页全部走 REST AJAX 无刷新加载。单路径实现,不写双路/兼容/迁移代码。
|
||||
|
||||
现状:主 Tab 已是 DOM 切换,但 `author.js` 会把 `?tab=` 写进 URL;follow 子切换和 comments/follow 分页是带参整页链接;posts/favorites 分页走 `aksrt_render_pagination()`(numbered 模式产出 `?posts_page=N` 链接;load_more/infinite 走 admin-ajax)。
|
||||
|
||||
## 服务端
|
||||
|
||||
### 1. 上下文构建 — `inc/helpers/aksrt-user-home.php`
|
||||
- `aksrt_get_user_home_page_context()` 删除全部 `$_GET` 读取(第 104、134-136 行):`active_tab` 恒等于 `default_tab`,三个 page 恒为 1(保留键,供 REST 覆写);新增 `follow_page => 1`、`follow_sub => 'followers'` 键。
|
||||
- 新增 `aksrt_render_user_home_pagination(int $total, int $current)`:输出 `<nav class="profile-pagination uh-pagination">` + `<button type="button" class="page-numbers" data-uh-page="N">`(当前页带 `current` 类,含 `‹ ›` 前后页),复用现有 `.profile-pagination` 样式(lists.css:331+)。
|
||||
- 新增 `aksrt_capture_user_home_panel(string $tab, array $ctx): string`:`locate_template('templates/parts/user-home/panel-{$tab}.php')` + ob_start 捕获(仿 `aksrt_capture_user_center_panel`,aksrt-user-center.php:199)。
|
||||
|
||||
### 2. 面板模板重构 — `templates/parts/user-home/`
|
||||
- 删除 `panels-core.php`,拆为按面板 body 模板:`panel-posts.php`、`panel-comments.php`、`panel-favorites.php`、`panel-credits.php`;`panel-follow.php` 改为纯 body(去掉外层 wrapper,第 21-23 行 `$_GET` 读取改为从 `$args` 取 `$follow_page`/`$follow_sub`)。
|
||||
- `page-user-home.php`:dashboard 根节点加 `data-user-id="<?php echo (int) $user_id; ?>"`;循环 `$enabled_tabs`,对存在 body 模板的 tab 输出 `<div id="panel-{tab}" class="dashboard-panel…">` wrapper + `get_template_part('templates/parts/user-home/panel', $tab, $ctx)`。
|
||||
- 各 body 分页统一改用 `aksrt_render_user_home_pagination()`:
|
||||
- posts/favorites 不再用 `aksrt_render_pagination()`(本页退出全站三模式分页体系);
|
||||
- comments 不再用 `aksrt_render_profile_pagination()`(该函数保留,用户中心仍在用)。
|
||||
- `panel-follow.php` 子切换 `<a href="?tab=follow&…">`(第 40、47 行)→ `<button type="button" data-uh-sub="followers|following">`;`paginate_links`(第 63-69 行)→ 新分页渲染器。
|
||||
|
||||
### 3. REST 端点 — `src/User/Home.php`(仿 `src/User/Center.php:34-110`)
|
||||
- `init()` 挂 `rest_api_init`;路由 `GET aksrt/v1/user-home/panel/(?P<user_id>\d+)/(?P<tab>[a-z0-9_-]+)`,query 参数 `page`(int ≥1)、`sub`(followers|following),`permission_callback => '__return_true'`(公开数据)。
|
||||
- `rest_panel()`:校验用户存在 → `aksrt_get_user_home_page_context()` 重建上下文 → 用请求参数覆写 `active_tab`、对应 page、`follow_sub` → tab 不在 `enabled_tabs`(如 credits 非本人)返回 404 → 捕获 body → 返回 `{success:true, data:{tab, html}}`。
|
||||
|
||||
## 前端 — `assets/js/author.js`
|
||||
|
||||
- 删除 `updateTabUrl()`(第 15-24 行)与 `resolveInitialTab()`(第 70-73 行):不再读写任何 URL 参数,初始态以服务端渲染的 `is-active`/`data-default-tab` 为准。
|
||||
- 新增 `loadPanelBody(tab, page, sub)`:`fetch(aksrtData.apiUrl + '/user-home/panel/{data-user-id}/{tab}?page=N(&sub=X)', {credentials:'same-origin', headers:{'X-WP-Nonce': aksrtData.restNonce}})` → `panel.innerHTML = data.html` → `AksrtFrontend.rehydrate(panel, 'ajax')`;per-panel 请求去重防竞态(仿 user-center.js `_aksrtPanelRequest`);`is-loading` 态 + 失败 notyf 提示。
|
||||
- dashboard 根上事件委托(覆盖 AJAX 注入的新按钮):
|
||||
- `[data-uh-sub]` → 加载 follow 面板 `sub=X, page=1`;
|
||||
- `[data-uh-page]` → `btn.closest('.dashboard-panel')` 得 tab,`panel.dataset.uhSub`(默认 followers)得子项,加载对应页。
|
||||
- URL 永不变更(无 pushState/replaceState)。`follow.js` 是 document 级委托,注入的 minicard 关注按钮自动可用,无需改动。
|
||||
|
||||
## 附带清理与 CSS
|
||||
|
||||
- `templates/parts/submit/editor-main.php:106-108`:「查看草稿」现指向公开主页 + `?tab=post&post_status=draft`(公开主页从未支持该参数,本就错位)。改为 `add_query_arg('post_status', 'draft', aksrt_get_user_center_tab_url('posts'))`(用户中心支持该过滤,posts-list.php:14-16)。
|
||||
- `assets/css/routes/user-home.css`:`button.page-numbers` 复位 + hover 态(现 hover 选择器只覆盖 `a.page-numbers`);`.uh-follow__subtab` 改 button 后的复位;`.dashboard-panel.is-loading` 半透明过渡。
|
||||
|
||||
## 测试与验证
|
||||
|
||||
- 更新 `tests/widget-system/post-visual-contract.test.cjs`:移除 `panels-core.php`/`page_var` 断言(第 39、205、222-224、328-330 行),改为断言:各 `panel-*.php` body 模板存在且调用 `aksrt_render_user_home_pagination`;user-home 模板无 `$_GET`/`add_query_arg`;author.js 无 `searchParams`(防回归契约)。
|
||||
- `php -l` 全部改动 PHP 文件;`node --test tests/widget-system/post-visual-contract.test.cjs`。
|
||||
|
||||
## 明确的行为变化(单路径的直接结果)
|
||||
|
||||
- 公开主页 posts/favorites 分页不再跟随后台「文章分页模式」(numbered/加载更多/无限滚动)设置,统一为本页 AJAX 数字分页。
|
||||
- 刷新/分享/回退永远回到默认 Tab 第 1 页(URL 无状态,需求本身决定)。
|
||||
- 旧外链 `?tab=follow` 不解析不重定向,按默认 Tab 加载(无兼容代码)。
|
||||
@@ -504,7 +504,12 @@
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
background: none;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: color .2s;
|
||||
}
|
||||
|
||||
@@ -567,3 +572,21 @@
|
||||
.aksrt-profile-details-modal__dialog dt { color: var(--color-muted, #6b7280); }
|
||||
.aksrt-profile-details-modal__dialog dd { margin: 0; overflow-wrap: anywhere; }
|
||||
.aksrt-profile-details-modal__close { position: absolute; top: 12px; right: 12px; width: 32px; height: 32px; border: 0; border-radius: 50%; background: transparent; font-size: 24px; cursor: pointer; }
|
||||
|
||||
/* Button-driven panel pagination + fragments (no URL state) */
|
||||
.aksrt-user-home .profile-pagination button.page-numbers {
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.aksrt-user-home .profile-pagination button.page-numbers:hover {
|
||||
color: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
}
|
||||
|
||||
.aksrt-user-home .dashboard-panel.is-loading {
|
||||
opacity: .6;
|
||||
transition: opacity var(--dur-fast, .15s) var(--ease, ease);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
+77
-21
@@ -1,26 +1,22 @@
|
||||
/**
|
||||
* AKSRT Public User Home runtime
|
||||
*
|
||||
* Handles public user-home dashboard tabs and cover uploads.
|
||||
* Loaded on /user/{nicename}/ (author archives 301-redirect here).
|
||||
* 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 */
|
||||
/* global aksrtAuthorPage, aksrtData, AksrtFrontend, notyf */
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function updateTabUrl(root, name, defaultTab) {
|
||||
if (!window.history || typeof window.history.replaceState !== 'function') return;
|
||||
var url = new URL(window.location.href);
|
||||
if (!name || name === defaultTab) {
|
||||
url.searchParams.delete('tab');
|
||||
} else {
|
||||
url.searchParams.set('tab', name);
|
||||
function notifyLoadError() {
|
||||
if (typeof notyf === 'function') {
|
||||
notyf('加载失败,请重试', 'danger');
|
||||
}
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
}
|
||||
|
||||
function initPublicDashboards() {
|
||||
@@ -32,6 +28,7 @@
|
||||
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) {
|
||||
@@ -45,7 +42,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
function switchTo(name, syncUrl) {
|
||||
function switchTo(name) {
|
||||
if (!panels[name]) {
|
||||
name = defaultTab;
|
||||
}
|
||||
@@ -61,24 +58,83 @@
|
||||
panels[key].panel.setAttribute('hidden', '');
|
||||
}
|
||||
});
|
||||
|
||||
if (syncUrl) {
|
||||
updateTabUrl(root, name, defaultTab);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveInitialTab() {
|
||||
var url = new URL(window.location.href);
|
||||
return url.searchParams.get('tab') || defaultTab;
|
||||
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'), true);
|
||||
switchTo(tab.getAttribute('data-tab'));
|
||||
});
|
||||
});
|
||||
|
||||
switchTo(resolveInitialTab(), false);
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -101,10 +101,7 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
|
||||
}
|
||||
}
|
||||
|
||||
$active_tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : $default_tab;
|
||||
if (!in_array($active_tab, $enabled_tabs, true)) {
|
||||
$active_tab = $default_tab;
|
||||
}
|
||||
|
||||
$home_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user) : home_url('/');
|
||||
|
||||
@@ -131,9 +128,11 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
|
||||
'tab_counts' => $tab_counts,
|
||||
'tab_labels' => $tab_labels,
|
||||
'home_url' => $home_url,
|
||||
'posts_page' => max(1, isset($_GET['posts_page']) ? (int) $_GET['posts_page'] : 1),
|
||||
'comments_page' => max(1, isset($_GET['comments_page']) ? (int) $_GET['comments_page'] : 1),
|
||||
'favorites_page' => max(1, isset($_GET['favorites_page']) ? (int) $_GET['favorites_page'] : 1),
|
||||
'posts_page' => 1,
|
||||
'comments_page' => 1,
|
||||
'favorites_page' => 1,
|
||||
'follow_sub' => 'followers',
|
||||
'follow_page' => 1,
|
||||
'svg_empty_post' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M9 13h6M9 17h6M9 9h1"/></svg>',
|
||||
'svg_empty_comment' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
|
||||
'svg_empty_favorite' => '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>',
|
||||
@@ -141,3 +140,70 @@ if (!function_exists('aksrt_get_user_home_page_context')) {
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_render_user_home_pagination')) {
|
||||
/**
|
||||
* 渲染公开用户主页面板的无刷新数字分页(按钮驱动,无 URL 参数)。
|
||||
*
|
||||
* 复用 .profile-pagination / .page-numbers 既有样式契约。
|
||||
*
|
||||
* @param int $total_pages 总页数。
|
||||
* @param int $current 当前页。
|
||||
*/
|
||||
function aksrt_render_user_home_pagination(int $total_pages, int $current): void {
|
||||
$total_pages = max(1, $total_pages);
|
||||
$current = min(max(1, $current), $total_pages);
|
||||
if ($total_pages <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
for ($i = 1; $i <= $total_pages; $i++) {
|
||||
if ($i === 1 || $i === $total_pages || abs($i - $current) <= 1) {
|
||||
$items[] = (string) $i;
|
||||
} elseif ($items && end($items) !== '…') {
|
||||
$items[] = '…';
|
||||
}
|
||||
}
|
||||
|
||||
echo '<nav class="profile-pagination uh-pagination" aria-label="' . esc_attr__('分页', 'aksrt') . '">';
|
||||
if ($current > 1) {
|
||||
echo '<button type="button" class="page-numbers" data-uh-page="' . esc_attr((string) ($current - 1)) . '"><span aria-hidden="true">‹</span><span class="screen-reader-text">' . esc_html__('上一页', 'aksrt') . '</span></button>';
|
||||
}
|
||||
foreach ($items as $item) {
|
||||
if ($item === '…') {
|
||||
echo '<span class="page-numbers dots">…</span>';
|
||||
continue;
|
||||
}
|
||||
if ((int) $item === $current) {
|
||||
echo '<span class="page-numbers current" aria-current="page">' . esc_html($item) . '</span>';
|
||||
continue;
|
||||
}
|
||||
echo '<button type="button" class="page-numbers" data-uh-page="' . esc_attr($item) . '">' . esc_html($item) . '</button>';
|
||||
}
|
||||
if ($current < $total_pages) {
|
||||
echo '<button type="button" class="page-numbers" data-uh-page="' . esc_attr((string) ($current + 1)) . '"><span aria-hidden="true">›</span><span class="screen-reader-text">' . esc_html__('下一页', 'aksrt') . '</span></button>';
|
||||
}
|
||||
echo '</nav>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('aksrt_capture_user_home_panel')) {
|
||||
/**
|
||||
* 捕获公开用户主页面板 body(templates/parts/user-home/panel-{tab}.php)。
|
||||
*
|
||||
* @param string $tab 面板键名。
|
||||
* @param array<string, mixed> $ctx aksrt_get_user_home_page_context() 上下文。
|
||||
* @return string 捕获的 HTML,模板不存在时为空串。
|
||||
*/
|
||||
function aksrt_capture_user_home_panel(string $tab, array $ctx): string {
|
||||
$template = locate_template(['templates/parts/user-home/panel-' . sanitize_key($tab) . '.php'], false, false);
|
||||
if (!$template) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ob_start();
|
||||
load_template($template, false, $ctx);
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,95 @@ class Home {
|
||||
|
||||
public function init(): void {
|
||||
$this->register_default_tabs();
|
||||
add_action('rest_api_init', [$this, 'register_rest_routes']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register public user-home panel fragment routes.
|
||||
*/
|
||||
public function register_rest_routes(): void {
|
||||
register_rest_route('aksrt/v1', '/user-home/panel/(?P<user_id>\d+)/(?P<tab>[a-z0-9_-]+)', [
|
||||
'methods' => \WP_REST_Server::READABLE,
|
||||
'callback' => [$this, 'rest_panel'],
|
||||
'permission_callback' => '__return_true',
|
||||
'args' => [
|
||||
'user_id' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
'validate_callback' => static function ($value): bool {
|
||||
return (int) $value > 0;
|
||||
},
|
||||
],
|
||||
'tab' => [
|
||||
'required' => true,
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => static function ($value): bool {
|
||||
return preg_match('/^[a-z0-9_-]+$/', (string) $value) === 1;
|
||||
},
|
||||
],
|
||||
'page' => [
|
||||
'required' => false,
|
||||
'default' => 1,
|
||||
'sanitize_callback' => 'absint',
|
||||
],
|
||||
'sub' => [
|
||||
'required' => false,
|
||||
'default' => 'followers',
|
||||
'sanitize_callback' => 'sanitize_key',
|
||||
'validate_callback' => static function ($value): bool {
|
||||
return in_array((string) $value, ['followers', 'following'], true);
|
||||
},
|
||||
],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a public user-home panel body fragment.
|
||||
*/
|
||||
public function rest_panel(\WP_REST_Request $request) {
|
||||
$user = get_userdata((int) $request->get_param('user_id'));
|
||||
if (!$user) {
|
||||
return new \WP_Error('aksrt_user_home_user', __('用户不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$tab = sanitize_key((string) $request->get_param('tab'));
|
||||
$page = max(1, (int) $request->get_param('page'));
|
||||
$sub = (string) $request->get_param('sub') === 'following' ? 'following' : 'followers';
|
||||
|
||||
if (!function_exists('aksrt_get_user_home_page_context')) {
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-home.php';
|
||||
}
|
||||
$context = aksrt_get_user_home_page_context($user);
|
||||
|
||||
if (!in_array($tab, (array) ($context['enabled_tabs'] ?? []), true)) {
|
||||
return new \WP_Error('aksrt_user_home_panel', __('用户主页面板不存在', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
|
||||
$context['active_tab'] = $tab;
|
||||
$context['follow_sub'] = $sub;
|
||||
$page_keys = [
|
||||
'posts' => 'posts_page',
|
||||
'comments' => 'comments_page',
|
||||
'favorites' => 'favorites_page',
|
||||
'follow' => 'follow_page',
|
||||
];
|
||||
if (isset($page_keys[$tab])) {
|
||||
$context[$page_keys[$tab]] = $page;
|
||||
}
|
||||
|
||||
$html = aksrt_capture_user_home_panel($tab, $context);
|
||||
if ($html === '') {
|
||||
return new \WP_Error('aksrt_user_home_panel_empty', __('用户主页面板暂不可用', 'aksrt'), ['status' => 404]);
|
||||
}
|
||||
|
||||
return rest_ensure_response([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'tab' => $tab,
|
||||
'html' => $html,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,7 +48,7 @@ require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php';
|
||||
]);
|
||||
?>
|
||||
|
||||
<div class="dashboard dashboard--public" data-default-tab="<?php echo esc_attr($default_tab); ?>">
|
||||
<div class="dashboard dashboard--public" data-default-tab="<?php echo esc_attr($default_tab); ?>" data-user-id="<?php echo (int) $user_id; ?>">
|
||||
<div class="dashboard-tabs" role="tablist">
|
||||
<?php foreach ($enabled_tabs as $tab_key) :
|
||||
$tab_class = $tab_key === $active_tab ? 'is-active' : '';
|
||||
@@ -68,9 +68,15 @@ require_once AKSRT_DIR . '/templates/parts/user-shared-helpers.php';
|
||||
</div>
|
||||
|
||||
<?php
|
||||
get_template_part('templates/parts/user-home/panels', 'core', $ctx);
|
||||
get_template_part('templates/parts/user-home/panel', 'follow', $ctx);
|
||||
foreach ($enabled_tabs as $panel_tab) :
|
||||
if (!locate_template(['templates/parts/user-home/panel-' . $panel_tab . '.php'], false, false)) {
|
||||
continue;
|
||||
}
|
||||
?>
|
||||
<div id="panel-<?php echo esc_attr($panel_tab); ?>" class="dashboard-panel<?php echo $panel_tab === $active_tab ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $panel_tab !== $active_tab ? ' hidden' : ''; ?>>
|
||||
<?php get_template_part('templates/parts/user-home/panel', $panel_tab, $ctx); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -103,8 +103,8 @@ $is_can_new = !empty($ctx['is_can_new']);
|
||||
<?php endif; ?>
|
||||
<?php elseif (!empty($ctx['draft_count']) && $ctx['user_id'] > 0) : ?>
|
||||
<?php
|
||||
$draft_url = function_exists('aksrt_get_user_home_url')
|
||||
? add_query_arg(['tab' => 'post', 'post_status' => 'draft'], aksrt_get_user_home_url($ctx['user_id']))
|
||||
$draft_url = function_exists('aksrt_get_user_center_url')
|
||||
? add_query_arg('post_status', 'draft', aksrt_get_user_center_url('posts'))
|
||||
: '';
|
||||
?>
|
||||
<?php if ($draft_url !== '') : ?>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — comments tab panel body.
|
||||
*
|
||||
* Rendered inside the #panel-comments wrapper on the public user home and via
|
||||
* the aksrt/v1 user-home panel REST fragment. Pagination is button-driven
|
||||
* (aksrt_render_user_home_pagination) and never touches the URL.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
$user_comments = get_comments([
|
||||
'user_id' => $user_id,
|
||||
'status' => 'approve',
|
||||
'number' => $per_page,
|
||||
'offset' => ($comments_page - 1) * $per_page,
|
||||
'type' => 'comment',
|
||||
'update_comment_meta_cache' => false,
|
||||
]);
|
||||
|
||||
if ($user_comments) :
|
||||
?>
|
||||
<div class="profile-comment-list">
|
||||
<?php foreach ($user_comments as $c) : ?>
|
||||
<?php
|
||||
get_template_part('templates/parts/content', 'profile-comment-item', ['comment' => $c]);
|
||||
?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
aksrt_render_user_home_pagination((int) ceil($comment_count / max(1, $per_page)), (int) $comments_page);
|
||||
else :
|
||||
aksrt_render_profile_empty($svg_empty_comment, __('TA 还没有发表评论', 'aksrt'));
|
||||
endif;
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — credits tab panel body (own profile only).
|
||||
*
|
||||
* Rendered inside the #panel-credits wrapper on the public user home. Static
|
||||
* balance card; no pagination, no REST swapping needed.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
if (empty($credits_enabled) || empty($is_own_profile)) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<div class="user-home-balance-card">
|
||||
<p class="user-home-balance-card__label"><?php echo esc_html(sprintf(__('当前%s余额', 'aksrt'), $currency_name)); ?></p>
|
||||
<div class="user-home-balance-card__value"><?php echo esc_html(aksrt_format_credits($credit_balance)); ?></div>
|
||||
<p class="user-home-balance-card__hint"><?php esc_html_e('完整账单和赚取/消费记录请在用户中心查看。', 'aksrt'); ?></p>
|
||||
<a href="<?php echo esc_url(aksrt_get_user_center_url('credits')); ?>" class="btn-outline btn-sm"><?php esc_html_e('进入用户中心', 'aksrt'); ?></a>
|
||||
</div>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — favorites tab panel body.
|
||||
*
|
||||
* Rendered inside the #panel-favorites wrapper on the public user home and via
|
||||
* the aksrt/v1 user-home panel REST fragment. Pagination is button-driven
|
||||
* (aksrt_render_user_home_pagination) and never touches the URL.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
if (!empty($favorite_post_ids)) :
|
||||
$user_home_render_config = aksrt_resolve_post_list_render_config([
|
||||
'context' => 'author',
|
||||
'overrides' => [
|
||||
'type' => 'list',
|
||||
'no_author' => true,
|
||||
'no_margin' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$favorites_query = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'post__in' => $favorite_post_ids,
|
||||
'orderby' => 'post__in',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $favorites_page,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => false,
|
||||
'update_post_meta_cache' => false,
|
||||
]);
|
||||
aksrt_render_post_list(array_merge($user_home_render_config, ['wrapper_class' => 'aksrt-user-home__post-list']), $favorites_query);
|
||||
aksrt_render_user_home_pagination((int) $favorites_query->max_num_pages, (int) $favorites_page);
|
||||
wp_reset_postdata();
|
||||
else :
|
||||
aksrt_render_profile_empty($svg_empty_favorite, __('TA 还没有收藏文章', 'aksrt'));
|
||||
endif;
|
||||
@@ -1,11 +1,15 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — follow tab panel (social following/followers).
|
||||
* Public user home — follow tab panel body (social following/followers).
|
||||
*
|
||||
* Legacy parity: 粉丝/关注用户列表复刻 zib_get_follow_user_list + zib_author_card
|
||||
* (inc/functions/zib-author.php:395-454, 685-716):子切换「粉丝 | 关注」居中文字链接,
|
||||
* 列表项走 aksrt_author_minicard(.author-minicard 三列 inline-block 卡片流)。
|
||||
*
|
||||
* Rendered inside the #panel-follow wrapper on the public user home and via
|
||||
* the aksrt/v1 user-home panel REST fragment. Sub-tabs and pagination are
|
||||
* button-driven ([data-uh-sub] / [data-uh-page]) and never touch the URL.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
@@ -14,13 +18,8 @@ defined('ABSPATH') || exit;
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
?>
|
||||
<?php if (in_array('follow', $enabled_tabs, true)) : ?>
|
||||
<div id="panel-follow" class="dashboard-panel<?php echo $active_tab === 'follow' ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $active_tab !== 'follow' ? ' hidden' : ''; ?>>
|
||||
<?php
|
||||
$follow_page = max(1, isset($_GET['follow_page']) ? (int) $_GET['follow_page'] : 1);
|
||||
$follow_sub = isset($_GET['follow_sub']) ? sanitize_key(wp_unslash($_GET['follow_sub'])) : 'followers';
|
||||
$follow_sub = in_array($follow_sub, ['following', 'followers'], true) ? $follow_sub : 'followers';
|
||||
$follow_page = max(1, (int) ($follow_page ?? 1));
|
||||
$follow_sub = in_array($follow_sub ?? 'followers', ['following', 'followers'], true) ? $follow_sub : 'followers';
|
||||
|
||||
$follow_instance = class_exists('\AKSRT\User\Follow') ? \AKSRT\User\Follow::get_instance() : null;
|
||||
$following_count = $follow_instance ? (int) $follow_instance->get_following_count((int) $user_id) : 0;
|
||||
@@ -36,18 +35,16 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
|
||||
?>
|
||||
<ul class="uh-follow__subtabs list-inline splitters">
|
||||
<li>
|
||||
<a class="uh-follow__subtab<?php echo $follow_sub === 'followers' ? ' is-active focus-color' : ' muted-color'; ?>"
|
||||
href="<?php echo esc_url(add_query_arg(['tab' => 'follow', 'follow_sub' => 'followers', 'follow_page' => 1], $home_url)); ?>">
|
||||
<button type="button" class="uh-follow__subtab<?php echo $follow_sub === 'followers' ? ' is-active focus-color' : ' muted-color'; ?>" data-uh-sub="followers">
|
||||
<?php esc_html_e('粉丝', 'aksrt'); ?>
|
||||
<span class="uh-follow__subtab-count"><?php echo esc_html((string) $follower_count); ?></span>
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<a class="uh-follow__subtab<?php echo $follow_sub === 'following' ? ' is-active focus-color' : ' muted-color'; ?>"
|
||||
href="<?php echo esc_url(add_query_arg(['tab' => 'follow', 'follow_sub' => 'following', 'follow_page' => 1], $home_url)); ?>">
|
||||
<button type="button" class="uh-follow__subtab<?php echo $follow_sub === 'following' ? ' is-active focus-color' : ' muted-color'; ?>" data-uh-sub="following">
|
||||
<?php esc_html_e('关注', 'aksrt'); ?>
|
||||
<span class="uh-follow__subtab-count"><?php echo esc_html((string) $following_count); ?></span>
|
||||
</a>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -57,19 +54,7 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
|
||||
echo aksrt_author_minicard((int) ($row['user_id'] ?? 0)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
endforeach; ?>
|
||||
</div>
|
||||
<?php if ($total_pages > 1) : ?>
|
||||
<div class="uh-follow__pagination">
|
||||
<?php
|
||||
echo paginate_links([
|
||||
'base' => add_query_arg(['tab' => 'follow', 'follow_sub' => $follow_sub, 'follow_page' => '%#%'], $home_url),
|
||||
'format' => '',
|
||||
'current' => $follow_page,
|
||||
'total' => $total_pages,
|
||||
'add_args' => ['tab' => 'follow', 'follow_sub' => $follow_sub],
|
||||
]);
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php aksrt_render_user_home_pagination($total_pages, $follow_page); ?>
|
||||
<?php else : ?>
|
||||
<?php
|
||||
$empty_msg = $follow_sub === 'followers'
|
||||
@@ -78,5 +63,3 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
|
||||
aksrt_render_profile_empty($svg_empty_follow ?? $svg_empty_post ?? '', $empty_msg);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — posts tab panel body.
|
||||
*
|
||||
* Rendered inside the #panel-posts wrapper on the public user home and via
|
||||
* the aksrt/v1 user-home panel REST fragment. Pagination is button-driven
|
||||
* (aksrt_render_user_home_pagination) and never touches the URL.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
$user_home_render_config = aksrt_resolve_post_list_render_config([
|
||||
'context' => 'author',
|
||||
'overrides' => [
|
||||
'type' => 'list',
|
||||
'no_author' => true,
|
||||
'no_margin' => true,
|
||||
],
|
||||
]);
|
||||
|
||||
$author_posts = new WP_Query([
|
||||
'author' => $user_id,
|
||||
'posts_per_page' => $per_page,
|
||||
'post_status' => 'publish',
|
||||
'paged' => $posts_page,
|
||||
'no_found_rows' => false,
|
||||
]);
|
||||
aksrt_render_post_list(array_merge($user_home_render_config, ['wrapper_class' => 'aksrt-user-home__post-list']), $author_posts);
|
||||
aksrt_render_user_home_pagination((int) $author_posts->max_num_pages, (int) $posts_page);
|
||||
wp_reset_postdata();
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Public user home — core content panels (posts/comments/favorites/credits/placeholders).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$args = is_array($args ?? null) ? $args : [];
|
||||
extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
?>
|
||||
<?php if (in_array('posts', $enabled_tabs, true) || in_array('favorites', $enabled_tabs, true)) : ?>
|
||||
<?php
|
||||
$user_home_render_config = aksrt_resolve_post_list_render_config([
|
||||
'context' => 'author',
|
||||
'overrides' => [
|
||||
'type' => 'list',
|
||||
'no_author' => true,
|
||||
'no_margin' => true,
|
||||
],
|
||||
]);
|
||||
?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('posts', $enabled_tabs, true)) : ?>
|
||||
<div id="panel-posts" class="dashboard-panel<?php echo $active_tab === 'posts' ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $active_tab !== 'posts' ? ' hidden' : ''; ?>>
|
||||
<?php
|
||||
$author_posts = new WP_Query([
|
||||
'author' => $user_id,
|
||||
'posts_per_page' => $per_page,
|
||||
'post_status' => 'publish',
|
||||
'paged' => $posts_page,
|
||||
'no_found_rows' => false,
|
||||
]);
|
||||
aksrt_render_post_list(array_merge($user_home_render_config, ['wrapper_class' => 'aksrt-user-home__post-list']), $author_posts);
|
||||
aksrt_render_pagination($author_posts, [
|
||||
'query_context' => 'author',
|
||||
'render_config' => $user_home_render_config,
|
||||
'page_var' => 'posts_page',
|
||||
'current_page' => $posts_page,
|
||||
]);
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('comments', $enabled_tabs, true)) : ?>
|
||||
<div id="panel-comments" class="dashboard-panel<?php echo $active_tab === 'comments' ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $active_tab !== 'comments' ? ' hidden' : ''; ?>>
|
||||
<?php
|
||||
$user_comments = get_comments([
|
||||
'user_id' => $user_id,
|
||||
'status' => 'approve',
|
||||
'number' => $per_page,
|
||||
'offset' => ($comments_page - 1) * $per_page,
|
||||
'type' => 'comment',
|
||||
'update_comment_meta_cache' => false,
|
||||
]);
|
||||
if ($user_comments) :
|
||||
?>
|
||||
<div class="profile-comment-list">
|
||||
<?php foreach ($user_comments as $c) : ?>
|
||||
<?php
|
||||
get_template_part('templates/parts/content', 'profile-comment-item', ['comment' => $c]);
|
||||
?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php
|
||||
$cm_total_pages = (int) ceil($comment_count / $per_page);
|
||||
aksrt_render_profile_pagination($cm_total_pages, $comments_page, 'comments_page');
|
||||
else :
|
||||
aksrt_render_profile_empty($svg_empty_comment, __('TA 还没有发表评论', 'aksrt'));
|
||||
endif;
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('favorites', $enabled_tabs, true)) : ?>
|
||||
<div id="panel-favorites" class="dashboard-panel<?php echo $active_tab === 'favorites' ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $active_tab !== 'favorites' ? ' hidden' : ''; ?>>
|
||||
<?php if (!empty($favorite_post_ids)) : ?>
|
||||
<?php
|
||||
$favorites_query = new WP_Query([
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'post__in' => $favorite_post_ids,
|
||||
'orderby' => 'post__in',
|
||||
'posts_per_page' => $per_page,
|
||||
'paged' => $favorites_page,
|
||||
'ignore_sticky_posts' => true,
|
||||
'no_found_rows' => false,
|
||||
'update_post_meta_cache' => false,
|
||||
]);
|
||||
aksrt_render_post_list(array_merge($user_home_render_config, ['wrapper_class' => 'aksrt-user-home__post-list']), $favorites_query);
|
||||
aksrt_render_pagination($favorites_query, [
|
||||
'query_context' => 'author',
|
||||
'render_config' => $user_home_render_config,
|
||||
'page_var' => 'favorites_page',
|
||||
'current_page' => $favorites_page,
|
||||
]);
|
||||
wp_reset_postdata();
|
||||
?>
|
||||
<?php else : ?>
|
||||
<?php aksrt_render_profile_empty($svg_empty_favorite, __('TA 还没有收藏文章', 'aksrt')); ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (in_array('credits', $enabled_tabs, true) && $credits_enabled && $is_own_profile) : ?>
|
||||
<div id="panel-credits" class="dashboard-panel<?php echo $active_tab === 'credits' ? ' is-active' : ''; ?>" role="tabpanel"<?php echo $active_tab !== 'credits' ? ' hidden' : ''; ?>>
|
||||
<div class="user-home-balance-card">
|
||||
<p class="user-home-balance-card__label"><?php echo esc_html(sprintf(__('当前%s余额', 'aksrt'), $currency_name)); ?></p>
|
||||
<div class="user-home-balance-card__value"><?php echo esc_html(aksrt_format_credits($credit_balance)); ?></div>
|
||||
<p class="user-home-balance-card__hint"><?php esc_html_e('完整账单和赚取/消费记录请在用户中心查看。', 'aksrt'); ?></p>
|
||||
<a href="<?php echo esc_url(aksrt_get_user_center_url('credits')); ?>" class="btn-outline btn-sm"><?php esc_html_e('进入用户中心', 'aksrt'); ?></a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -36,7 +36,8 @@ test('the article stream exposes only the new typed renderer contract', () => {
|
||||
'src/Core/Shortcodes.php',
|
||||
'src/WidgetSystem/PostRenderer.php',
|
||||
'inc/helpers/homepage/content.php',
|
||||
'templates/parts/user-home/panels-core.php',
|
||||
'templates/parts/user-home/panel-posts.php',
|
||||
'templates/parts/user-home/panel-favorites.php',
|
||||
]);
|
||||
|
||||
assert.match(markup, /function aksrt_render_post_list\(array \$args = \[\], \?WP_Query \$query = null, bool \$echo = true\): string/);
|
||||
@@ -202,8 +203,7 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
const related = read('inc/helpers/aksrt-single-render.php');
|
||||
const relatedCard = read('inc/helpers/aksrt-single-components.php');
|
||||
const resourceRelated = read('templates/parts/resource-related.php');
|
||||
const author = read('templates/parts/user-home/panels-core.php');
|
||||
const authorPosts = author.slice(author.indexOf("in_array('posts'"), author.indexOf("in_array('comments'"));
|
||||
const author = read('templates/parts/user-home/panel-posts.php');
|
||||
const widgets = read('src/WidgetSystem/PostRenderer.php');
|
||||
|
||||
assert.match(related, /\['img', 'list', 'text'\]/);
|
||||
@@ -215,14 +215,13 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
assert.match(resourceRelated, /swiper-container swiper-scroll/);
|
||||
assert.match(resourceRelated, /aksrt_render_related_graphic_card/);
|
||||
assert.doesNotMatch(resourceRelated, /aksrt_render_post_list/);
|
||||
assert.match(authorPosts, /aksrt_render_post_list/);
|
||||
assert.match(authorPosts, /'type'\s*=>\s*'list'/);
|
||||
assert.match(authorPosts, /'no_author'\s*=>\s*true/);
|
||||
assert.match(authorPosts, /'no_margin'\s*=>\s*true/);
|
||||
assert.match(authorPosts, /'wrapper_class'\s*=>\s*'aksrt-user-home__post-list'/);
|
||||
assert.match(authorPosts, /aksrt_render_pagination/);
|
||||
assert.match(authorPosts, /'page_var'\s*=>\s*'posts_page'/);
|
||||
assert.doesNotMatch(authorPosts, /aksrt_render_profile_pagination/);
|
||||
assert.match(author, /aksrt_render_post_list/);
|
||||
assert.match(author, /'type'\s*=>\s*'list'/);
|
||||
assert.match(author, /'no_author'\s*=>\s*true/);
|
||||
assert.match(author, /'no_margin'\s*=>\s*true/);
|
||||
assert.match(author, /'wrapper_class'\s*=>\s*'aksrt-user-home__post-list'/);
|
||||
assert.match(author, /aksrt_render_user_home_pagination\(\(int\) \$author_posts->max_num_pages, \(int\) \$posts_page\)/);
|
||||
assert.doesNotMatch(author, /aksrt_render_pagination|aksrt_render_profile_pagination|\$_GET|add_query_arg/);
|
||||
assert.match(widgets, /aksrt_render_mini_list/);
|
||||
assert.match(widgets, /swiper-container swiper-scroll/);
|
||||
});
|
||||
@@ -325,10 +324,10 @@ test('profile routes load versioned leaf styles and keep dialogs hidden initiall
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*repeat\(2,\s*minmax\(0,\s*1fr\)\);/);
|
||||
assert.match(userHomeCss, /@media \(min-width: 992px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list > \.posts-item\s*\{[^}]*width:\s*auto;[^}]*margin:\s*0 !important;/);
|
||||
assert.match(userHomeCss, /@media \(max-width: 767px\)\s*\{[\s\S]*?\.aksrt-user-home \.aksrt-user-home__post-list \.posts-item\s*\{[^}]*padding:\s*10px 0;/);
|
||||
const panelsCore = read('templates/parts/user-home/panels-core.php');
|
||||
assert.match(panelsCore, /'page_var'\s*=>\s*'favorites_page'/);
|
||||
assert.match(panelsCore, /array_merge\(\$user_home_render_config, \['wrapper_class' => 'aksrt-user-home__post-list'\]\), \$favorites_query\)/);
|
||||
assert.doesNotMatch(panelsCore, /profile-list-item/);
|
||||
const panelFavorites = read('templates/parts/user-home/panel-favorites.php');
|
||||
assert.match(panelFavorites, /aksrt_render_user_home_pagination\(\(int\) \$favorites_query->max_num_pages, \(int\) \$favorites_page\)/);
|
||||
assert.match(panelFavorites, /array_merge\(\$user_home_render_config, \['wrapper_class' => 'aksrt-user-home__post-list'\]\), \$favorites_query\)/);
|
||||
assert.doesNotMatch(panelFavorites, /profile-list-item|\$_GET|add_query_arg/);
|
||||
assert.match(profileLayoutCss, /\.aksrt-cover-menu__panel\[hidden\]\s*\{\s*display:\s*none\s*!important;\s*\}/);
|
||||
assert.match(profileLayoutCss, /#aksrt-bg-file-input\[hidden\],[\s\S]*?#aksrt-avatar-file-input\[hidden\][\s\S]*?display:\s*none\s*!important;/);
|
||||
assert.match(settingsSecurity, /data-modal="email-modal"[\s\S]*?<\/button>\s*<\/div>\s*<div class="security-action-item">[\s\S]*?data-modal="phone-modal"/);
|
||||
@@ -337,3 +336,44 @@ test('profile routes load versioned leaf styles and keep dialogs hidden initiall
|
||||
assert.doesNotMatch(accountModals, /id="phone-modal"[\s\S]*?aksrt-modal__(?:dialog|body|actions)/);
|
||||
assert.match(profileModalsCss, /\.modal-field input\[type="tel"\]/);
|
||||
});
|
||||
|
||||
test('public user home stays param-free and swaps panels via REST fragments', () => {
|
||||
const ctx = read('inc/helpers/aksrt-user-home.php');
|
||||
const shell = read('templates/pages/page-user-home.php');
|
||||
const follow = read('templates/parts/user-home/panel-follow.php');
|
||||
const comments = read('templates/parts/user-home/panel-comments.php');
|
||||
const favorites = read('templates/parts/user-home/panel-favorites.php');
|
||||
const rest = read('src/User/Home.php');
|
||||
const authorJs = read('assets/js/author.js');
|
||||
|
||||
// Context builder owns no request state; pages default to 1.
|
||||
assert.doesNotMatch(ctx, /\$_GET/);
|
||||
assert.match(ctx, /function aksrt_render_user_home_pagination\(int \$total_pages, int \$current\): void/);
|
||||
assert.match(ctx, /data-uh-page/);
|
||||
assert.match(ctx, /function aksrt_capture_user_home_panel\(string \$tab, array \$ctx\): string/);
|
||||
|
||||
// Shell mounts panel wrappers from the tab registry with a user id hook.
|
||||
assert.match(shell, /data-user-id="<\?php echo \(int\) \$user_id; \?>">/);
|
||||
assert.match(shell, /get_template_part\('templates\/parts\/user-home\/panel', \$panel_tab, \$ctx\)/);
|
||||
|
||||
// Follow panel: button-driven sub-tabs, pagination without URL building.
|
||||
assert.match(follow, /data-uh-sub="followers"/);
|
||||
assert.match(follow, /data-uh-sub="following"/);
|
||||
assert.match(follow, /aksrt_render_user_home_pagination\(\$total_pages, \$follow_page\)/);
|
||||
assert.doesNotMatch(follow, /add_query_arg|\$_GET|paginate_links/);
|
||||
|
||||
assert.match(comments, /aksrt_render_user_home_pagination\(/);
|
||||
assert.match(favorites, /aksrt_render_user_home_pagination\(/);
|
||||
assert.doesNotMatch(comments + favorites, /add_query_arg|\$_GET/);
|
||||
|
||||
// REST fragment endpoint serves public panel bodies.
|
||||
assert.ok(rest.includes("'/user-home/panel/(?P<user_id>\\d+)/(?P<tab>[a-z0-9_-]+)'"));
|
||||
assert.match(rest, /'permission_callback'\s*=>\s*'__return_true'/);
|
||||
assert.match(rest, /aksrt_capture_user_home_panel\(\$tab, \$context\)/);
|
||||
|
||||
// Runtime never reads or writes URL params; fragments replace panel bodies.
|
||||
assert.doesNotMatch(authorJs, /searchParams|pushState|replaceState/);
|
||||
assert.match(authorJs, /\/user-home\/panel\/' \+ userId \+ '/);
|
||||
assert.match(authorJs, /data-uh-sub/);
|
||||
assert.match(authorJs, /AksrtFrontend\.rehydrate\(panel, 'ajax'\)/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user