Files
wordpress-theme-aksrt/src/WidgetSystem/PostRenderer.php
T
kate522 b639d57c5b feat(widgets): replicate 12 reference widgets for full Zibll parity
- rewrite schemas for posts, tab_posts, oneline_posts, hot_posts,
  term_cards, term_aggregation, media_cards, combo_info, partners,
  recent_comments, posts_toc and slider_media_combo to mirror the
  modern reference modules field by field (options, defaults,
  dependencies, min/max/step, subheadings)
- port reference renderers: hot ranking with TOP badges, category
  aggregation card, graphic cover cards, partners CSS marquee,
  combo info cards with auto band palette, recent comment list
- rename 分类与标签聚合卡片 to 分类聚合卡片 and restrict it to
  categories (no tags/topics branch in this project)
- drop topics filter fields and the topics query branch; align
  orderby value domain with supported post meta
- add searchable sortable terms admin field type and spinner unit
  suffix rendering
- unify widget pagination and lazy loading on the REST client with
  the runtime-owned ias reveal trigger; remove the legacy
  post_ajax_trigger dual path
- align section-navs TOC with the reference contract (wznav ids,
  3-heading minimum, zib-widget box)
2026-09-05 11:28:00 +08:00

108 lines
4.6 KiB
PHP

<?php
namespace AKSRT\WidgetSystem;
defined('ABSPATH') || exit;
/**
* Shared list/card/mini markup and pagination payloads (reference parity).
*/
final class PostRenderer {
public static function payload(array $instance, string $slug, int $page = 1, string $segment = ''): array {
$segment_config = $slug === 'tab_posts' ? PostQuery::segment($instance, $segment) : [];
$query = PostQuery::build($instance, $page, $segment_config);
$html = self::items($query, $instance, $slug, $page);
$max_pages = max(1, (int) $query->max_num_pages);
$total = (int) $query->found_posts;
return [
'html' => $html,
'paginate_html' => self::pagination($instance, $page, $max_pages),
'has_more' => $page < $max_pages,
'page' => $page,
'max_pages' => $max_pages,
'total' => $total,
];
}
public static function items(\WP_Query $query, array $instance, string $slug, int $page = 1): string {
if (!$query->have_posts()) {
return function_exists('aksrt_widget_ajax_null') ? aksrt_widget_ajax_null() : RendererHelpers::empty_state(__('暂无内容', 'aksrt'));
}
$style = (string) ($instance['style'] ?? 'card');
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'card';
$mini_opt = (array) ($instance['mini_opt'] ?? []);
$config = [
'type' => $mode === 'card' ? 'card' : 'list',
'show_number' => in_array('show_number', $mini_opt, true),
'show_thumb' => in_array('show_thumb', $mini_opt, true),
'show_meta' => in_array('show_meta', $mini_opt, true),
'number_offset' => ($page - 1) * max(1, (int) ($instance['count'] ?? 8)),
'widget_style' => $mode,
];
if ($slug === 'oneline_posts') {
return self::oneline($query, $config, $mode);
}
if ($mode === 'mini') {
return aksrt_render_mini_list($query, array_merge($config, ['class' => 'ajax-item']), false);
}
// The .widget-ajaxpager container rendered by the caller is the ajax
// target, so item markup goes in without the page-level list wrapper.
return aksrt_render_post_list(array_merge($config, ['skip_wrapper' => true]), $query, false);
}
private static function oneline(\WP_Query $query, array $config, string $style): string {
$items = '';
while ($query->have_posts()) {
$query->the_post();
if ($style === 'mini') {
$data = aksrt_renderer_collect_post_data($config);
$items .= $data ? aksrt_renderer_mini($data, array_merge($config, [
'class' => 'posts-item',
'show_thumb' => true,
'show_meta' => true,
'show_number' => false,
])) : '';
} else {
$items .= aksrt_render_post(array_merge($config, [
'type' => $style === 'card' ? 'card' : 'list',
'is_mult_thumb' => 'disable',
'is_no_thumb' => 'disable',
]));
}
}
wp_reset_postdata();
return '<div class="widget-main-post mb20 style-' . esc_attr($style) . '"><div class="swiper-container swiper-scroll" data-slideClass="posts-item"><div class="swiper-wrapper">' . $items . '</div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
}
/**
* Pagination contract: '' keeps the hidden stub, ajax renders the load
* more trigger, number renders numbered page links; all targets carry the
* `.widget-ajaxpager` reference attribute. Interactive controls stay clear
* of the runtime's legacy `.ajax-next` HTML pipeline — the widget system
* owns the REST client in frontend.js.
*/
private static function pagination(array $instance, int $page, int $max_pages): string {
$mode = (string) ($instance['paginate'] ?? '');
if ($mode === '' || $mode === 'none') {
return '<div class="ajax-pag hide"><div class="next-page"><a href="#"></a></div></div>';
}
if ($max_pages <= 1) {
return '';
}
if ($mode === 'number' || $mode === 'numbered') {
$links = '';
$start = max(1, min($page - 2, $max_pages - 4));
$end = min($max_pages, max($start + 4, $page + 2));
for ($number = $start; $number <= $end; $number++) {
if ($number === $page) {
$links .= '<span class="page-numbers current">' . $number . '</span>';
continue;
}
$links .= '<a class="next-page page-numbers" ajaxpager-target=".widget-ajaxpager" ajax-replace="true" data-widget-page="' . $number . '" role="button" tabindex="0" href="#page-' . $number . '">' . $number . '</a>';
}
return '<div class="pagenav ajax-pag" role="navigation" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">' . $links . '</div>';
}
return '<div class="text-center theme-pagination ajax-pag"><div no-scroll="true" ajaxpager-target=".widget-ajaxpager" class="next-page"><a role="button" tabindex="0" href="#next" data-next-page="' . (int) ($page + 1) . '">' . esc_html__('加载更多', 'aksrt') . '</a></div></div>';
}
}