fix(forum): align forum pages with the reference layout
- cards: restore the shared card shell, add author follow/message actions, score group, last-reply meta and reference plate header actions - home: desktop left tab rail, widget-only sidebar, reference load-more markup - plate: 1-based tabs, reference defaults (全部/推荐/最新回复/热门/精华) - post: fixed action rail, score footer, author card, h1 article title - quick publish: plate selector moved into the submit row like the reference
This commit is contained in:
1 parent
7a59c65252
commit
ec5dfcd82f
15 files changed
+396
-166
No files matched your search
+85
-18
@@ -7,8 +7,10 @@ defined('ABSPATH') || exit;
|
||||
final class Cards {
|
||||
/** @var array<int, \WP_Comment> */
|
||||
private static array $hot = [];
|
||||
/** @var array<int, string> */
|
||||
private static array $last_reply = [];
|
||||
|
||||
/** Prime authors, plates and hot replies for a page of posts in batch queries. */
|
||||
/** Prime authors, plates, hot replies and last replies for a page of posts in batch queries. */
|
||||
public static function prime(array $posts): void {
|
||||
$ids = $authors = $plates = [];
|
||||
foreach ($posts as $post) {
|
||||
@@ -26,6 +28,20 @@ final class Cards {
|
||||
$post_id = (int) $comment->comment_post_ID;
|
||||
if (isset($wanted[$post_id]) && !isset(self::$hot[$post_id])) self::$hot[$post_id] = $comment;
|
||||
}
|
||||
self::prime_last_replies($ids);
|
||||
}
|
||||
|
||||
/** @param int[] $ids */
|
||||
private static function prime_last_replies(array $ids): void {
|
||||
$missing = array_values(array_diff(array_unique(array_map('absint', $ids)), array_keys(self::$last_reply)));
|
||||
if (!$missing) return;
|
||||
global $wpdb;
|
||||
$placeholders = implode(',', array_fill(0, count($missing), '%d'));
|
||||
$sql = "SELECT comment_post_ID AS post_id, MAX(comment_date) AS last_date FROM {$wpdb->comments} WHERE comment_approved = '1' AND comment_post_ID IN ($placeholders) GROUP BY comment_post_ID";
|
||||
$rows = $wpdb->get_results($wpdb->prepare($sql, $missing)); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
foreach ((array) $rows as $row) {
|
||||
self::$last_reply[(int) $row->post_id] = (string) $row->last_date;
|
||||
}
|
||||
}
|
||||
|
||||
public static function post_detail(\WP_Post $post, string $class = 'alone ajax-item', bool $show_plate = true, bool $show_topping = false): string {
|
||||
@@ -51,12 +67,21 @@ final class Cards {
|
||||
$html .= '<div class="mr20 forum-user">' . self::avatar($author, 'avatar-img forum-avatar') . '<span class="show-sm ml6 flex ac" style="width: 90%;">' . $name . '</span></div>';
|
||||
$html .= '<div class="entry-info">' . self::title($post, 'forum-title flex ac', 'text-ellipsis', $show_topping);
|
||||
$html .= '<div class="flex ac jsb item-meta"><div class="meta-left em09-sm flex"><span class="hide-sm">' . $name . '</span>';
|
||||
$html .= '<span class="icon-circle" title="' . esc_attr(sprintf('发布时间:%s', get_the_date('Y-m-d H:i:s', $post))) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span></div>';
|
||||
$html .= self::reply_time($post) . '</div>';
|
||||
$html .= '<div class="meta-right">' . self::icon_metas($post) . '</div></div></div>';
|
||||
return $html . '</article>';
|
||||
}
|
||||
|
||||
public static function title(\WP_Post $post, string $class, string $link_class = '', bool $show_topping = false): string {
|
||||
/** Reference mini-card meta: "最后回复" when replies exist, otherwise the publish time. */
|
||||
private static function reply_time(\WP_Post $post): string {
|
||||
$last = self::$last_reply[$post->ID] ?? '';
|
||||
if ($last !== '') {
|
||||
return '<span class="icon-circle" title="' . esc_attr(sprintf('最后回复:%s', $last)) . '">' . esc_html(self::ago($last) . '回复') . '</span>';
|
||||
}
|
||||
return '<span class="icon-circle" title="' . esc_attr(sprintf('发布时间:%s', get_the_date('Y-m-d H:i:s', $post))) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span>';
|
||||
}
|
||||
|
||||
public static function title(\WP_Post $post, string $class, string $link_class = '', bool $show_topping = false, string $tag = 'h2'): string {
|
||||
$id = $post->ID;
|
||||
$badges = '';
|
||||
if ($show_topping && get_post_meta($id, '_jio_forum_pinned', true)) $badges .= '<span class="badg badg-sm c-blue mr3">置顶</span>';
|
||||
@@ -70,7 +95,8 @@ final class Cards {
|
||||
}
|
||||
$url = $post->post_status === 'publish' ? get_permalink($post) : Core::edit_url($id);
|
||||
$link_class = $link_class !== '' ? ' class="' . esc_attr($link_class) . '"' : '';
|
||||
return '<h2 class="' . esc_attr($class) . '"><a' . $link_class . ' href="' . esc_url($url) . '" title="' . esc_attr($post->post_title) . '">' . $badges . esc_html($post->post_title) . '</a></h2>';
|
||||
$tag = in_array($tag, ['h1', 'h2'], true) ? $tag : 'h2';
|
||||
return '<' . $tag . ' class="' . esc_attr($class) . '"><a' . $link_class . ' href="' . esc_url($url) . '" title="' . esc_attr($post->post_title) . '">' . $badges . esc_html($post->post_title) . '</a></' . $tag . '>';
|
||||
}
|
||||
|
||||
/** Plate page header: blurred cover, title, stats and moderators (reference layout). */
|
||||
@@ -94,6 +120,7 @@ final class Cards {
|
||||
$excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 40);
|
||||
$html = '<div class="plate-header-wrap"><div class="forum-header blur-header relative-h mb20">';
|
||||
$html .= '<div class="abs-blur-bg">' . $thumb . '</div><div class="absolute forum-mask"></div>';
|
||||
$html .= self::plate_more_btns($plate);
|
||||
if (get_post_meta($id, '_jio_forum_hot', true)) {
|
||||
$html .= '<span class="img-badge top jb-red px12-sm">热门</span>';
|
||||
}
|
||||
@@ -107,18 +134,32 @@ final class Cards {
|
||||
return $html . '</div></div></div>';
|
||||
}
|
||||
|
||||
/** Header corner actions: follow, search and share (reference `.more-btns`). */
|
||||
private static function plate_more_btns(\WP_Post $plate): string {
|
||||
$html = '<div class="flex ac more-btns">' . self::follow_button($plate->ID, '');
|
||||
if (jio_get_setting('jio_forum_search_enabled', true)) {
|
||||
$html .= '<a class="main-search-btn item" href="' . esc_url(add_query_arg(['type' => 'plate', 's' => $plate->post_title], home_url('/'))) . '" title="搜索"><i class="fa fas fa-search"></i></a>';
|
||||
}
|
||||
if (function_exists('jio_get_post_share_btn')) {
|
||||
$html .= jio_get_post_share_btn($plate, 'item btn-share', true, true);
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
/** Plate moderator avatars row under the header. */
|
||||
private static function plate_moderator_btns(\WP_Post $plate): string {
|
||||
$ids = array_values(array_unique(array_filter(array_merge(
|
||||
[(int) $plate->post_author],
|
||||
array_map('absint', (array) get_post_meta($plate->ID, '_jio_forum_moderators', true))
|
||||
))));
|
||||
if (!$ids) return '';
|
||||
$btns = '';
|
||||
foreach ($ids as $user_id) {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user) continue;
|
||||
$btns .= '<a class="but c-white mr6 mt6" href="' . esc_url(get_author_posts_url($user_id)) . '" title="' . esc_attr($user->display_name) . '"><span class="avatar-mini avatar-img">' . get_avatar($user_id, 40) . '</span><span class="ml6">' . esc_html($user->display_name) . '</span></a>';
|
||||
$btns .= '<a class="but c-white mr6 mt6" href="' . esc_url(get_author_posts_url($user_id)) . '" title="' . esc_attr($user->display_name) . '"><span class="avatar-mini mr6">' . get_avatar($user_id, 40) . '</span>' . esc_html($user->display_name) . '</a>';
|
||||
}
|
||||
if (is_user_logged_in() && Access::can_publish(get_current_user_id(), $plate->ID)) {
|
||||
$btns .= '<a rel="nofollow" class="but c-white mr6 mt6" href="' . esc_url(add_query_arg('plate', $plate->ID, Core::edit_url())) . '"><i class="fa fas fa-plus mr6"></i>发布</a>';
|
||||
}
|
||||
$html = '<div class="mt10 moderator-btns em09">' . $btns;
|
||||
if (is_user_logged_in() && !Access::is_moderator(get_current_user_id(), $plate->ID) && jio_get_setting('jio_forum_moderator_apply', true)) {
|
||||
@@ -127,7 +168,8 @@ final class Cards {
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
public static function plate_item(\WP_Post $plate): string { $url = get_permalink($plate);
|
||||
public static function plate_item(\WP_Post $plate): string {
|
||||
$url = get_permalink($plate);
|
||||
$counts = PlateStats::get($plate->ID);
|
||||
$today = $counts['today'] > 0 ? '<span class="badg but-plate hide-sm flex0" title="' . esc_attr(sprintf('今日新帖 %d', $counts['today'])) . '"><i class="fa fas fa-comment-dots c-red mr3"></i>' . esc_html(sprintf('今日:%s', jio_cut_count($counts['today']))) . '</span>' : '';
|
||||
$excerpt = wp_trim_words(wp_strip_all_tags($plate->post_excerpt ?: $plate->post_content), 30);
|
||||
@@ -165,10 +207,10 @@ final class Cards {
|
||||
|
||||
public static function follow_button(int $plate_id, string $class): string {
|
||||
if (!is_user_logged_in()) {
|
||||
return '<a class="btn-follow ' . esc_attr($class) . '" href="' . esc_url(jio_get_login_url(get_permalink($plate_id))) . '"><text>关注</text></a>';
|
||||
return '<a class="btn-follow ' . esc_attr($class) . '" href="' . esc_url(jio_get_login_url(get_permalink($plate_id))) . '"><text><i class="fa far fa-heart mr6 em12"></i></text></a>';
|
||||
}
|
||||
$followed = Access::follows(get_current_user_id(), $plate_id);
|
||||
return '<button type="button" class="btn-follow ' . esc_attr($class . ($followed ? ' active' : '')) . '" data-forum-action="follow" data-id="' . (int) $plate_id . '"><text>' . ($followed ? '已关注' : '关注') . '</text></button>';
|
||||
return '<button type="button" class="btn-follow ' . esc_attr($class . ($followed ? ' active' : '')) . '" data-forum-action="follow" data-id="' . (int) $plate_id . '"><text><i class="fa ' . ($followed ? 'fas' : 'far') . ' fa-heart mr6 em12"></i></text></button>';
|
||||
}
|
||||
|
||||
public static function plate_button(int $plate_id): string {
|
||||
@@ -185,15 +227,29 @@ final class Cards {
|
||||
}
|
||||
|
||||
private static function author_link(int $user_id): string {
|
||||
return '<a class="display-name text-ellipsis" href="' . esc_url(get_author_posts_url($user_id)) . '">' . esc_html((string) get_the_author_meta('display_name', $user_id)) . '</a>';
|
||||
$url = function_exists('jio_get_n') ? jio_get_n($user_id) : get_author_posts_url($user_id);
|
||||
return '<a class="display-name text-ellipsis" href="' . esc_url($url) . '">' . esc_html((string) get_the_author_meta('display_name', $user_id)) . '</a>';
|
||||
}
|
||||
|
||||
/** Reference author row: avatar, name, publish time + views, follow and message actions. */
|
||||
private static function author_box(\WP_Post $post): string {
|
||||
$author = (int) $post->post_author;
|
||||
$name = function_exists('jio_get_user_name') ? jio_get_user_name(['id' => $author, 'class' => 'flex ac flex1']) : self::author_link($author);
|
||||
$views = jio_cut_count((int) get_post_meta($post->ID, '_jio_forum_views', true));
|
||||
$desc = '<span title="' . esc_attr(get_the_date('Y-m-d H:i:s', $post)) . '">' . esc_html(self::ago($post->post_date)) . '</span><span class="icon-spot">' . esc_html(sprintf('%s次阅读', $views)) . '</span>';
|
||||
return '<div class="user-info flex ac mb10">' . self::avatar($author, 'avatar-img') . '<div class="user-right flex flex1 ac jsb ml10"><div class="flex1 overflow-hidden"><div class="flex ac">' . $name . '</div><div class="px12-sm muted-2-color text-ellipsis mt3">' . $desc . '</div></div></div></div>';
|
||||
$desc = '<span title="' . esc_attr(get_the_date('Y-m-d H:i:s', $post)) . '">' . esc_html(self::ago($post->post_date) . '发布') . '</span><span class="icon-spot">' . esc_html(sprintf('%s次阅读', $views)) . '</span>';
|
||||
$actions = '';
|
||||
if (function_exists('jio_get_user_follow')) {
|
||||
$actions .= jio_get_user_follow('px12-sm ml10 follow but c-red', $author);
|
||||
}
|
||||
if (function_exists('jio_get_messages_page_url')) {
|
||||
$messages = jio_get_messages_page_url();
|
||||
if ($messages !== '' && get_current_user_id() !== $author) {
|
||||
$actions .= '<a class="ml6 but c-blue px12-sm" href="' . esc_url($messages) . '"><i class="fa far fa-envelope mr3"></i>私信</a>';
|
||||
}
|
||||
}
|
||||
$html = '<div class="user-info flex ac mb10">' . self::avatar($author, 'avatar-img') . '<div class="user-right flex flex1 ac jsb ml10"><div class="flex1 overflow-hidden"><div class="flex ac">' . $name . '</div><div class="px12-sm muted-2-color text-ellipsis">' . $desc . '</div></div>';
|
||||
if ($actions !== '') $html .= '<div class="flex0 user-action">' . $actions . '</div>';
|
||||
return $html . '</div></div>';
|
||||
}
|
||||
|
||||
private static function status_badge(\WP_Post $post): string {
|
||||
@@ -205,6 +261,7 @@ final class Cards {
|
||||
if (!has_post_thumbnail($post)) return '';
|
||||
return '<div class="forum-lists-cover mb10"><div class="graphic imgbox-container" style="padding-bottom:45%">' . get_the_post_thumbnail($post, 'large', ['class' => 'fit-cover']) . '</div></div>';
|
||||
}
|
||||
|
||||
private static function content(\WP_Post $post, bool $can_view): string {
|
||||
if (!$can_view) {
|
||||
return '<div class="hide-post mt6"><i class="fa fas fa-unlock-alt mr6"></i>此处内容已隐藏</div>';
|
||||
@@ -237,23 +294,33 @@ final class Cards {
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
/** Reference action row: score up/down group, reply count and share. */
|
||||
private static function action_meta(\WP_Post $post): string {
|
||||
$id = $post->ID;
|
||||
$score = (int) get_post_meta($id, '_jio_forum_score', true);
|
||||
$reply = (int) $post->comment_count;
|
||||
$html = '<div class="action-meta flex0 flex jsb">';
|
||||
if (jio_get_setting('jio_forum_score_enabled', true)) {
|
||||
$html .= is_user_logged_in()
|
||||
? '<button type="button" class="item btn-score" data-forum-action="score" data-direction="up" data-id="' . $id . '"><i class="fa far fa-thumbs-up"></i><text>' . esc_html($score ? jio_cut_count($score) : '加分') . '</text></button>'
|
||||
: '<a class="item btn-score" href="' . esc_url(jio_get_login_url(get_permalink($post))) . '"><i class="fa far fa-thumbs-up"></i><text>' . esc_html($score ? jio_cut_count($score) : '加分') . '</text></a>';
|
||||
$user_id = get_current_user_id();
|
||||
$user_score = $user_id > 0 ? (int) get_user_meta($user_id, '_jio_forum_score_' . $id, true) : 0;
|
||||
$score = (int) get_post_meta($id, '_jio_forum_score', true);
|
||||
$html .= '<div class="score-btns">' . self::score_button($post, 'up', 'extra', $user_score > 0) . '<text>' . esc_html($score ? jio_cut_count($score) : '评分') . '</text>' . self::score_button($post, 'down', 'deduct', $user_score < 0) . '</div>';
|
||||
}
|
||||
$reply = (int) $post->comment_count;
|
||||
$html .= '<a rel="nofollow" href="' . esc_url(get_permalink($post) . '#comments') . '" class="item"><i class="fa far fa-comment"></i><text>' . esc_html($reply ? jio_cut_count($reply) : '回复') . '</text></a>';
|
||||
if ($post->post_status === 'publish' && function_exists('jio_get_post_share_btn')) {
|
||||
$html .= jio_get_post_share_btn($post, 'item btn-share', true, false);
|
||||
$html .= jio_get_post_share_btn($post, 'item btn-share', true, true);
|
||||
}
|
||||
return $html . '</div>';
|
||||
}
|
||||
|
||||
private static function score_button(\WP_Post $post, string $direction, string $kind, bool $active): string {
|
||||
$icon = '<i class="fa fas fa-' . ($direction === 'up' ? 'plus' : 'minus') . '-circle"></i>';
|
||||
$class = 'btn-score ' . $kind . ($active ? ' active' : '');
|
||||
if (!is_user_logged_in()) {
|
||||
return '<a href="' . esc_url(jio_get_login_url(get_permalink($post))) . '" class="' . esc_attr($class . ' signin-loader') . '" data-id="' . (int) $post->ID . '">' . $icon . '</a>';
|
||||
}
|
||||
return '<button type="button" class="' . esc_attr($class) . '" data-forum-action="score" data-direction="' . esc_attr($direction) . '" data-id="' . (int) $post->ID . '">' . $icon . '</button>';
|
||||
}
|
||||
|
||||
private static function icon_metas(\WP_Post $post): string {
|
||||
$id = $post->ID;
|
||||
return '<item><i class="fa far fa-eye mr3"></i>' . esc_html(jio_cut_count((int) get_post_meta($id, '_jio_forum_views', true))) . '</item>'
|
||||
|
||||
Reference in new issue
Block a user