From 9c5ac01119c99ac037bc924292820cab4a5a2dad Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 21 Jul 2026 10:39:16 +0800 Subject: [PATCH] feat(widgets): shared forum topic/plate renderers for list depth Extract aksrt_render_forum_topic_mini/detail and plate row/card helpers, then wire TopicLists, PlateLists, and BoardList for zibll-aligned DOM, lazy thumbs, and identity-aware topic rows. --- inc/bootstrap.php | 1 + inc/helpers/aksrt-forum-widgets.php | 363 ++++++++++++++++++++ src/AKSRT/Widgets/WidgetForumBoardList.php | 47 ++- src/AKSRT/Widgets/WidgetForumPlateLists.php | 49 ++- src/AKSRT/Widgets/WidgetForumTopicLists.php | 25 +- 5 files changed, 443 insertions(+), 42 deletions(-) create mode 100644 inc/helpers/aksrt-forum-widgets.php diff --git a/inc/bootstrap.php b/inc/bootstrap.php index e8ec4665..2828ee90 100644 --- a/inc/bootstrap.php +++ b/inc/bootstrap.php @@ -32,6 +32,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-archive.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-archive-filters.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-comments.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-forum.php'; +require_once AKSRT_DIR . '/inc/helpers/aksrt-forum-widgets.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-slider.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-vip.php'; require_once AKSRT_DIR . '/inc/helpers/aksrt-auth-ui.php'; diff --git a/inc/helpers/aksrt-forum-widgets.php b/inc/helpers/aksrt-forum-widgets.php new file mode 100644 index 00000000..49ff9e22 --- /dev/null +++ b/inc/helpers/aksrt-forum-widgets.php @@ -0,0 +1,363 @@ + (string) get_post_meta($post_id, '_aksrt_topping', true) === '1' + || (string) get_post_meta($post_id, '_aksrt_is_top', true) === '1', + 'essence' => (string) get_post_meta($post_id, '_aksrt_essence', true) === '1', + 'views' => (int) get_post_meta($post_id, '_aksrt_views', true), + 'replies' => (int) get_comments_number($post_id), + 'plate_id' => (int) get_post_meta($post_id, '_aksrt_plate_id', true), + ]; + } +endif; + +if (!function_exists('aksrt_forum_topic_badges_html')) : + /** + * Topping / essence badges for forum topic rows. + */ + function aksrt_forum_topic_badges_html(int $post_id, bool $show_topping = true): string { + $flags = aksrt_forum_topic_meta_flags($post_id); + $html = ''; + if ($show_topping && !empty($flags['topping'])) { + $html .= '' . esc_html__('置顶', 'aksrt') . ''; + } + if (!empty($flags['essence'])) { + $html .= '' . esc_html__('精华', 'aksrt') . ''; + } + return $html; + } +endif; + +if (!function_exists('aksrt_render_forum_topic_mini')) : + /** + * Mini forum topic row (zibll zib_bbs_get_posts_mini_list parity). + * + * @param int|null $post_id Null = current post in loop. + * @param string $class Extra classes on outer element. + * @param bool $show_topping Whether to show topping badge. + */ + function aksrt_render_forum_topic_mini(?int $post_id = null, string $class = 'ajax-item', bool $show_topping = true): string { + $post_id = $post_id ?: (int) get_the_ID(); + if ($post_id <= 0) { + return ''; + } + $post = get_post($post_id); + if (!$post || $post->post_status !== 'publish') { + return ''; + } + + $author_id = (int) $post->post_author; + $flags = aksrt_forum_topic_meta_flags($post_id); + $permalink = get_permalink($post_id); + $title = get_the_title($post_id); + + $avatar_url = function_exists('aksrt_get_avatar_url') + ? aksrt_get_avatar_url($author_id, ['size' => 48]) + : get_avatar_url($author_id, ['size' => 48]); + $home = function_exists('aksrt_get_user_home_url') + ? aksrt_get_user_home_url($author_id) + : get_author_posts_url($author_id); + + $name = function_exists('aksrt_get_user_name') + ? aksrt_get_user_name([ + 'id' => $author_id, + 'class' => 'flex ac flex1', + 'follow' => false, + 'medal' => false, + 'level' => false, + ]) + : '' . esc_html(get_the_author_meta('display_name', $author_id)) . ''; + + // Prefer last reply time when available. + $time_raw = (string) get_post_meta($post_id, '_aksrt_last_reply_time', true); + if ($time_raw === '' || !strtotime($time_raw)) { + $time_raw = (string) $post->post_date; + } + $time_txt = function_exists('aksrt_time_ago') + ? aksrt_time_ago($time_raw) + : human_time_diff(strtotime($time_raw), current_time('timestamp')) . __('前', 'aksrt'); + + $views_txt = function_exists('aksrt_format_number') + ? aksrt_format_number($flags['views']) + : (string) $flags['views']; + + $cls = trim('forum-posts mini aksrt-ftopic-mini relative ' . $class); + $html = ''; + $html .= aksrt_forum_topic_badges_html($post_id, $show_topping); + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '' . $name . ''; + $html .= '' . esc_html($time_txt) . ''; + $html .= ' ' . esc_html((string) $views_txt) . ''; + if ($flags['replies'] > 0) { + $html .= ' ' . esc_html((string) $flags['replies']) . ''; + } + $html .= '
'; + + return $html; + } +endif; + +if (!function_exists('aksrt_render_forum_topic_detail')) : + /** + * Detail forum topic row (richer entry meta). + * + * @param int|null $post_id + * @param string $class + * @param bool $show_topping + */ + function aksrt_render_forum_topic_detail(?int $post_id = null, string $class = 'ajax-item', bool $show_topping = true): string { + $post_id = $post_id ?: (int) get_the_ID(); + if ($post_id <= 0) { + return ''; + } + $post = get_post($post_id); + if (!$post || $post->post_status !== 'publish') { + return ''; + } + + $author_id = (int) $post->post_author; + $flags = aksrt_forum_topic_meta_flags($post_id); + $permalink = get_permalink($post_id); + $title = get_the_title($post_id); + $excerpt = wp_trim_words(wp_strip_all_tags((string) $post->post_content), 36, '…'); + + $avatar_url = function_exists('aksrt_get_avatar_url') + ? aksrt_get_avatar_url($author_id, ['size' => 64]) + : get_avatar_url($author_id, ['size' => 64]); + $home = function_exists('aksrt_get_user_home_url') + ? aksrt_get_user_home_url($author_id) + : get_author_posts_url($author_id); + + $name = function_exists('aksrt_get_user_name') + ? aksrt_get_user_name([ + 'id' => $author_id, + 'class' => 'flex ac', + 'follow' => false, + 'medal' => true, + 'level' => true, + ]) + : '' . esc_html(get_the_author_meta('display_name', $author_id)) . ''; + + $time_raw = (string) get_post_meta($post_id, '_aksrt_last_reply_time', true); + if ($time_raw === '' || !strtotime($time_raw)) { + $time_raw = (string) $post->post_date; + } + $time_txt = function_exists('aksrt_time_ago') + ? aksrt_time_ago($time_raw) + : human_time_diff(strtotime($time_raw), current_time('timestamp')) . __('前', 'aksrt'); + + $views_txt = function_exists('aksrt_format_number') + ? aksrt_format_number($flags['views']) + : (string) $flags['views']; + + // Optional plate name chip. + $plate_chip = ''; + if ($flags['plate_id'] > 0) { + $plate = get_post($flags['plate_id']); + if ($plate instanceof WP_Post) { + $plate_chip = '' . esc_html(get_the_title($plate)) . ''; + } + } + + $cls = trim('forum-posts detail aksrt-ftopic-detail relative ' . $class); + $html = ''; + $html .= aksrt_forum_topic_badges_html($post_id, $show_topping); + $html .= '
'; + $html .= ''; + $html .= '
' . $name . '
' . esc_html($time_txt) . '
'; + $html .= '
'; + $html .= '

' . esc_html($title) . '

'; + if ($excerpt !== '') { + $html .= '
' . esc_html($excerpt) . '
'; + } + $html .= '
'; + $html .= $plate_chip; + $html .= ' ' . esc_html((string) $views_txt) . ''; + $html .= ' ' . esc_html((string) $flags['replies']) . ''; + $html .= '
'; + + return $html; + } +endif; + +if (!function_exists('aksrt_render_plate_thumb_html')) : + /** + * Shared plate thumb with lazy pattern. + */ + function aksrt_render_plate_thumb_html(int $plate_id, string $size = 'thumbnail'): string { + $thumb = get_the_post_thumbnail_url($plate_id, $size); + $icon = (string) get_post_meta($plate_id, '_aksrt_icon', true); + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; + if ($thumb) { + return ''; + } + if ($icon !== '') { + return ''; + } + return ''; + } +endif; + +if (!function_exists('aksrt_render_plate_row')) : + /** + * Plate list row (zibll plate-item flex). + * + * @param WP_Post|int $plate + */ + function aksrt_render_plate_row($plate): string { + $post = $plate instanceof WP_Post ? $plate : get_post((int) $plate); + if (!$post instanceof WP_Post) { + return ''; + } + $id = (int) $post->ID; + $url = get_permalink($id); + $count = (int) get_post_meta($id, '_aksrt_posts_count', true); + $follow = (int) get_post_meta($id, '_aksrt_follow_count', true); + $desc = $post->post_excerpt !== '' ? $post->post_excerpt : wp_trim_words(wp_strip_all_tags((string) $post->post_content), 18); + + $html = ''; + $html .= aksrt_render_plate_thumb_html($id, 'thumbnail'); + $html .= '
'; + $html .= '

' . esc_html(get_the_title($post)) . '

'; + if ($desc !== '') { + $html .= '
' . esc_html($desc) . '
'; + } + $html .= '
'; + $html .= '' . esc_html(sprintf(/* translators: %d topic count */ __('%d 帖', 'aksrt'), $count)) . ''; + if ($follow > 0) { + $html .= '' . esc_html(sprintf(/* translators: %d follow count */ __('%d 关注', 'aksrt'), $follow)) . ''; + } + $html .= '
'; + return $html; + } +endif; + +if (!function_exists('aksrt_render_plate_card')) : + /** + * Plate card slide item. + * + * @param WP_Post|int $plate + */ + function aksrt_render_plate_card($plate): string { + $post = $plate instanceof WP_Post ? $plate : get_post((int) $plate); + if (!$post instanceof WP_Post) { + return ''; + } + $id = (int) $post->ID; + $url = get_permalink($id); + $thumb = get_the_post_thumbnail_url($id, 'medium'); + $count = (int) get_post_meta($id, '_aksrt_posts_count', true); + $follow = (int) get_post_meta($id, '_aksrt_follow_count', true); + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; + + $html = ''; + $html .= '
'; + if ($thumb) { + $html .= ''; + } else { + $html .= ''; + } + $html .= '
'; + $html .= '
' . esc_html(get_the_title($post)) . '
'; + $html .= '
'; + $html .= '' . esc_html(sprintf(__('%d 帖', 'aksrt'), $count)) . ''; + $html .= '' . esc_html(sprintf(__('%d 关注', 'aksrt'), $follow)) . ''; + $html .= '
'; + return $html; + } +endif; + +if (!function_exists('aksrt_render_moderator_row')) : + /** + * Moderator identity row with optional follow. + * + * @param int $user_id + * @param string $role author|super|moderator + * @param bool $show_avatar + * @param string $style list|grid + */ + function aksrt_render_moderator_row(int $user_id, string $role = 'moderator', bool $show_avatar = true, string $style = 'list'): string { + if ($user_id <= 0) { + return ''; + } + $user = get_userdata($user_id); + if (!$user) { + return ''; + } + $avatar = function_exists('aksrt_get_avatar_url') + ? aksrt_get_avatar_url($user_id, ['size' => 64]) + : get_avatar_url($user_id, ['size' => 64]); + $home = function_exists('aksrt_get_user_home_url') + ? aksrt_get_user_home_url($user_id) + : get_author_posts_url($user_id); + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; + + if ($role === 'author' || $role === 'super') { + $role_label = __('超级版主', 'aksrt'); + } else { + $role_label = __('版主', 'aksrt'); + } + + $name = function_exists('aksrt_get_user_name') + ? aksrt_get_user_name([ + 'id' => $user_id, + 'class' => 'flex ac flex1', + 'follow' => false, + 'medal' => true, + 'level' => true, + ]) + : '' . esc_html($user->display_name) . ''; + + // Follow control (not for self). + $follow_html = ''; + $cuid = get_current_user_id(); + if ($cuid > 0 && $cuid !== $user_id && class_exists('\AKSRT\User\Follow')) { + $following = \AKSRT\User\Follow::get_instance()->is_following($cuid, $user_id); + $label = $following ? __('已关注', 'aksrt') : __('关注', 'aksrt'); + $follow_html = ''; + } + + if ($style === 'grid') { + $html = '
'; + if ($show_avatar) { + $html .= ''; + } + $html .= '
' . $name . '
'; + $html .= '
' . esc_html($role_label) . '
'; + $html .= $follow_html; + $html .= '
'; + return $html; + } + + $html = '
'; + if ($show_avatar) { + $html .= ''; + } + $html .= '
'; + $html .= '
' . $name . '
'; + $html .= '
' . esc_html($role_label) . '
'; + $html .= '
'; + $html .= $follow_html; + $html .= '
'; + return $html; + } +endif; diff --git a/src/AKSRT/Widgets/WidgetForumBoardList.php b/src/AKSRT/Widgets/WidgetForumBoardList.php index 6127f973..c66869fa 100644 --- a/src/AKSRT/Widgets/WidgetForumBoardList.php +++ b/src/AKSRT/Widgets/WidgetForumBoardList.php @@ -57,27 +57,44 @@ class WidgetForumBoardList extends \WP_Widget { $plates = get_posts($query_args); if ($plates) { ob_start(); - echo ''; return (string) ob_get_clean(); } } + // Taxonomy fallback. $term_args = [ 'taxonomy' => 'aksrt_forum_board', diff --git a/src/AKSRT/Widgets/WidgetForumPlateLists.php b/src/AKSRT/Widgets/WidgetForumPlateLists.php index 89d0e256..93bb6184 100644 --- a/src/AKSRT/Widgets/WidgetForumPlateLists.php +++ b/src/AKSRT/Widgets/WidgetForumPlateLists.php @@ -88,20 +88,27 @@ class WidgetForumPlateLists extends \WP_Widget { return ''; } ob_start(); - $self = new self(); if ($style === 'card') { - echo '
'; + echo '
'; foreach ($plates as $post) { if ($post instanceof \WP_Post) { - $self->render_card($post); + if (function_exists('aksrt_render_plate_card')) { + echo aksrt_render_plate_card($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + (new self())->render_card($post); + } } } echo '
'; } else { - echo '
'; + echo '
'; foreach ($plates as $post) { if ($post instanceof \WP_Post) { - $self->render_row($post); + if (function_exists('aksrt_render_plate_row')) { + echo aksrt_render_plate_row($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + } else { + (new self())->render_row($post); + } } } echo '
'; @@ -154,42 +161,56 @@ class WidgetForumPlateLists extends \WP_Widget { } public function render_row(\WP_Post $post): void { + if (function_exists('aksrt_render_plate_row')) { + echo aksrt_render_plate_row($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return; + } $id = (int) $post->ID; $url = get_permalink($id); $thumb = get_the_post_thumbnail_url($id, 'thumbnail'); $icon = (string) get_post_meta($id, '_aksrt_icon', true); $count = (int) get_post_meta($id, '_aksrt_posts_count', true); $desc = $post->post_excerpt !== '' ? $post->post_excerpt : wp_trim_words(wp_strip_all_tags($post->post_content), 18); + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; - echo ''; - echo '
'; + echo ''; + echo '
'; if ($thumb) { - echo ''; + echo ''; } elseif ($icon !== '') { echo ''; } else { echo ''; } echo '
'; - echo '
'; - echo '
' . esc_html(get_the_title($post)) . '
'; + echo '
'; + echo '
' . esc_html(get_the_title($post)) . '
'; if ($desc !== '') { - echo '
' . esc_html($desc) . '
'; + echo '
' . esc_html($desc) . '
'; } echo '
' . esc_html(sprintf(/* translators: %d topic count */ __('%d 帖', 'aksrt'), $count)) . '
'; echo '
'; } public function render_card(\WP_Post $post): void { + if (function_exists('aksrt_render_plate_card')) { + echo aksrt_render_plate_card($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + return; + } $id = (int) $post->ID; $url = get_permalink($id); $thumb = get_the_post_thumbnail_url($id, 'medium'); $count = (int) get_post_meta($id, '_aksrt_posts_count', true); $follow = (int) get_post_meta($id, '_aksrt_follow_count', true); + $ph = get_template_directory_uri() . '/assets/img/placeholder.svg'; - echo ''; - echo '
'; - echo '
' . esc_html(get_the_title($post)) . '
'; + echo '
'; + echo '
'; + if ($thumb) { + echo ''; + } + echo '
'; + echo '
' . esc_html(get_the_title($post)) . '
'; echo '
'; echo '' . esc_html(sprintf(__('%d 帖', 'aksrt'), $count)) . ''; echo '' . esc_html(sprintf(__('%d 关注', 'aksrt'), $follow)) . ''; diff --git a/src/AKSRT/Widgets/WidgetForumTopicLists.php b/src/AKSRT/Widgets/WidgetForumTopicLists.php index b12ae482..d8fe5691 100644 --- a/src/AKSRT/Widgets/WidgetForumTopicLists.php +++ b/src/AKSRT/Widgets/WidgetForumTopicLists.php @@ -79,26 +79,25 @@ class WidgetForumTopicLists extends \WP_Widget { if (!$q->have_posts()) { return ''; } - $list_class = $style === 'detail' ? 'aksrt-ftopic-list is-detail' : 'aksrt-ftopic-list is-mini'; ob_start(); - echo ''; + echo '
'; wp_reset_postdata(); return (string) ob_get_clean(); }