用户公开主页全局采用DOM无刷新加载

This commit is contained in:
2026-08-23 14:55:24 +08:00
parent c3f66e9a14
commit bb9a410ad6
14 changed files with 569 additions and 231 deletions
@@ -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'\)/);
});