feat(forum): rebuild plate, post and compose pages
Match the reference layouts for the remaining forum routes: plate pages get the blurred header card, moderator row and the affix tab bar with per-tab filtering, topping posts and moderator-only pending/trash tabs; post pages get the article shell with breadcrumbs, cover, meta row, vote box, tag row and footer actions; compose pages get the reference editor plus settings sidebar (submit/draft, status, access, plate, topic, tag, type, vote) with a cover picker. Saving now honours draft state and the featured image through the REST contract. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
eceddb5f7d
commit
5d3358d844
13 files changed
+891
-199
No files matched your search
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace Jio\Forum;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/** Forum listing pages: plate header card, plate tab bar and post lists. */
|
||||
final class ListingView {
|
||||
/** Plate page: fluid widgets, blur header card, tab bar, then the list. */
|
||||
public static function plate(): void {
|
||||
$plate = get_queried_object();
|
||||
if (!$plate instanceof \WP_Post || $plate->post_type !== Core::PLATE) {
|
||||
self::term();
|
||||
return;
|
||||
}
|
||||
self::open($plate->post_title, 'plate', $plate);
|
||||
\Jio\WidgetSystem\WidgetAreas::render('forum_plate_top_fluid');
|
||||
echo '<div class="fixed-wrap"><div class="fixed-wrap-content">';
|
||||
echo Cards::plate_header($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo self::tab_bar($plate); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div></div>';
|
||||
self::close('plate');
|
||||
}
|
||||
|
||||
/** Taxonomy archives (plate category / topic / tag). */
|
||||
public static function term(): void {
|
||||
$term = get_queried_object();
|
||||
if (!$term instanceof \WP_Term) {
|
||||
self::open('论坛', 'term', null);
|
||||
self::close('term');
|
||||
return;
|
||||
}
|
||||
self::open($term->name, 'term', null, $term);
|
||||
\Jio\WidgetSystem\WidgetAreas::render('forum_term_top_fluid');
|
||||
echo '<div class="zib-widget">';
|
||||
echo self::tab_bar(null, $term); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
echo '</div>';
|
||||
self::close('term');
|
||||
}
|
||||
|
||||
private static function open(string $title, string $context, ?\WP_Post $plate, ?\WP_Term $term = null): void {
|
||||
echo '<main id="forum" class="jio-forum forum-listing forum-listing--' . esc_attr($context) . '">';
|
||||
echo '<div class="container"><div class="content-wrap"><div class="content-layout">';
|
||||
echo '<div class="jio-forum__list-main">';
|
||||
if ($plate) {
|
||||
$plate_name = (string) jio_get_setting('jio_forum_plate_name', '版块') ?: '版块';
|
||||
echo '<nav class="jio-forum__breadcrumbs" aria-label="路径"><a href="' . esc_url(home_url('/')) . '">首页</a><span>/</span><a href="' . esc_url(Core::home_url()) . '">论坛</a><span>/</span><span>' . esc_html($title !== '' ? $title : $plate_name) . '</span></nav>';
|
||||
} elseif ($term instanceof \WP_Term) {
|
||||
echo '<div class="zib-widget"><div class="box-body notop"><div class="title-theme">' . esc_html($term->name) . '</div></div>';
|
||||
if ($term->description !== '') echo '<p class="muted-2-color em09 padding-w10">' . esc_html($term->description) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
private static function close(string $context): void {
|
||||
echo '</div>';
|
||||
self::sidebar($context);
|
||||
echo '</div></div>';
|
||||
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_bottom_fluid');
|
||||
echo '</main>';
|
||||
}
|
||||
|
||||
private static function sidebar(string $context): void {
|
||||
if (!jio_is_show_sidebar()) return;
|
||||
echo '<div class="sidebar">';
|
||||
\Jio\WidgetSystem\WidgetAreas::render('forum_' . $context . '_sidebar');
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
/** Plate tab bar + panes; the first tab renders server-side, the rest load by `?plate_tab=N`. */
|
||||
public static function tab_bar(?\WP_Post $plate = null, ?\WP_Term $term = null): string {
|
||||
$tabs = self::tabs($plate);
|
||||
if (!$tabs) return '';
|
||||
$active = self::active_index(count($tabs));
|
||||
$base = remove_query_arg(['plate_tab', 'fpage']);
|
||||
if ($term instanceof \WP_Term) $base = get_term_link($term);
|
||||
$nav = $panes = '';
|
||||
foreach ($tabs as $index => $tab) {
|
||||
$id = 'plate-tab-' . $index;
|
||||
$href = add_query_arg('plate_tab', $index, is_string($base) ? $base : Core::home_url());
|
||||
$nav .= '<li' . ($index === $active ? ' class="active"' : '') . '><a href="' . esc_url($href) . '" data-forum-tab="#' . $id . '" data-plate-tab="' . $index . '">' . $tab['title'] . '</a></li>';
|
||||
$panes .= '<div class="forum-tab-pane tab-pane fade' . ($index === $active ? ' in active' : '') . '" id="' . $id . '" data-href="' . esc_url($href) . '">';
|
||||
$panes .= $index === $active
|
||||
? self::pane($tab, $plate, $term, $index)
|
||||
: '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>';
|
||||
$panes .= '</div>';
|
||||
}
|
||||
return '<div class="plate-tab zib-widget"><div class="affix-header-sm" offset-top="7"><ul class="list-inline scroll-x mini-scrollbar tab-nav-theme" role="tablist">' . $nav . '</ul></div><div class="tab-content">' . $panes . '</div></div>';
|
||||
}
|
||||
|
||||
/** @return array<int, array<string, mixed>> */
|
||||
public static function tabs(?\WP_Post $plate = null): array {
|
||||
$tabs = [];
|
||||
$configured = jio_get_setting('jio_forum_plate_tab_items', null);
|
||||
if (is_array($configured) && $configured) {
|
||||
foreach ($configured as $row) {
|
||||
if (!is_array($row)) continue;
|
||||
$tabs[] = [
|
||||
'title' => wp_kses_post((string) ($row['title'] ?? '') ?: '栏目'),
|
||||
'orderby' => sanitize_key((string) ($row['orderby'] ?? 'date')),
|
||||
'filter' => sanitize_key((string) ($row['filter'] ?? '')),
|
||||
'style' => sanitize_key((string) ($row['style'] ?? 'detail')),
|
||||
];
|
||||
}
|
||||
} else {
|
||||
foreach ([
|
||||
['最新发布', 'date', '', 'detail'],
|
||||
['最近回复', 'last_reply', '', 'detail'],
|
||||
['热门', 'views', '', 'detail'],
|
||||
['精华', 'modified', 'essence', 'detail'],
|
||||
] as [$title, $orderby, $filter, $style]) {
|
||||
$tabs[] = ['title' => $title, 'orderby' => $orderby, 'filter' => $filter, 'style' => $style];
|
||||
}
|
||||
}
|
||||
$user_id = get_current_user_id();
|
||||
if ($plate instanceof \WP_Post && Access::is_moderator($user_id, $plate->ID)) {
|
||||
$tabs[] = ['title' => '待审核', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'pending'];
|
||||
$tabs[] = ['title' => '<i class="fa fas fa-trash-alt mr3"></i>回收站', 'orderby' => 'date', 'filter' => '', 'style' => 'mini', 'status' => 'trash'];
|
||||
}
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
public static function active_index(int $count): int {
|
||||
$raw = $_GET['plate_tab'] ?? 0;
|
||||
$index = (is_scalar($raw) ? absint($raw) : 0) ?: (int) jio_get_setting('jio_forum_plate_tab_active', 1);
|
||||
return $index >= 1 && $index <= $count ? $index : 1;
|
||||
}
|
||||
|
||||
private static function pane(array $tab, ?\WP_Post $plate, ?\WP_Term $term, int $index): string {
|
||||
$page = max(1, is_scalar($_GET['fpage'] ?? 1) ? absint($_GET['fpage'] ?? 1) : 1);
|
||||
$args = [
|
||||
'post_type' => Core::POST,
|
||||
'post_status' => $tab['status'] ?? 'publish',
|
||||
'posts_per_page' => max(1, min(50, (int) jio_get_setting('jio_forum_page_size', 20))),
|
||||
'paged' => $page,
|
||||
'jio_forum_list' => $tab['orderby'],
|
||||
];
|
||||
if ($plate instanceof \WP_Post) {
|
||||
$args['meta_query'][] = ['key' => '_jio_forum_plate_id', 'value' => $plate->ID];
|
||||
$topping = self::topping($plate->ID, $tab);
|
||||
if ($topping) {
|
||||
$args['post__not_in'] = $topping;
|
||||
}
|
||||
} elseif ($term instanceof \WP_Term) {
|
||||
$args['tax_query'][] = ['taxonomy' => $term->taxonomy, 'terms' => [$term->term_id]];
|
||||
}
|
||||
if (($tab['filter'] ?? '') === 'essence') {
|
||||
$args['meta_query'][] = ['key' => '_jio_forum_essence', 'value' => 1];
|
||||
}
|
||||
$query = new \WP_Query($args);
|
||||
$html = '';
|
||||
if ($plate instanceof \WP_Post && ($tab['status'] ?? '') === '') {
|
||||
Cards::prime($topping_posts = self::topping_posts($plate->ID, $tab));
|
||||
foreach ($topping_posts as $post) {
|
||||
$html .= Cards::post_mini($post, 'ajax-item', true);
|
||||
}
|
||||
}
|
||||
if ($query->have_posts()) {
|
||||
Cards::prime($query->posts);
|
||||
foreach ($query->posts as $post) {
|
||||
$html .= ($tab['style'] ?? 'detail') === 'detail'
|
||||
? Cards::post_detail($post, 'ajax-item')
|
||||
: Cards::post_mini($post, 'ajax-item');
|
||||
}
|
||||
}
|
||||
$base = $plate instanceof \WP_Post ? get_permalink($plate) : ($term instanceof \WP_Term ? get_term_link($term) : Core::home_url());
|
||||
$next = add_query_arg(['plate_tab' => $index, 'fpage' => $page + 1], is_string($base) ? $base : Core::home_url());
|
||||
if ($page < (int) $query->max_num_pages) {
|
||||
$html .= '<div class="text-center theme-pagination forum-more"><a class="but hollow c-blue" href="' . esc_url($next) . '" data-forum-more>加载更多</a></div>';
|
||||
}
|
||||
$html .= '<div class="post_ajax_loader">' . HomeTabs::placeholder('posts', 3) . '</div>';
|
||||
if ($html !== '' && trim(wp_strip_all_tags($html)) !== '') return $html;
|
||||
$empty = jio_get_ajax_null('内容空空如也');
|
||||
if ($index === 1 && $page === 1 && $plate instanceof \WP_Post) {
|
||||
$empty .= '<div class="text-center mb20"><a class="ml10 mr10 mt10 but hollow c-green padding-lg" href="' . esc_url(Core::edit_url()) . '"><i class="fa fas fa-plus mr3"></i>发布' . esc_html((string) jio_get_setting('jio_forum_post_name', '帖子')) . '</a></div>';
|
||||
}
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
private static function topping(int $plate_id, array $tab): array {
|
||||
if (($tab['filter'] ?? '') === 'essence' || ($tab['status'] ?? '') !== '') return [];
|
||||
return wp_list_pluck(self::topping_posts($plate_id, $tab), 'ID');
|
||||
}
|
||||
|
||||
/** @return \WP_Post[] */
|
||||
private static function topping_posts(int $plate_id, array $tab): array {
|
||||
return get_posts([
|
||||
'post_type' => Core::POST,
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => 5,
|
||||
'meta_query' => [
|
||||
['key' => '_jio_forum_plate_id', 'value' => $plate_id],
|
||||
['key' => '_jio_forum_pinned', 'value' => 1],
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user