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)
This commit is contained in:
@@ -5,7 +5,7 @@ namespace AKSRT\WidgetSystem;
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Shared list/card/mini markup and pagination payloads.
|
||||
* 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 {
|
||||
@@ -26,35 +26,32 @@ final class PostRenderer {
|
||||
|
||||
public static function items(\WP_Query $query, array $instance, string $slug, int $page = 1): string {
|
||||
if (!$query->have_posts()) {
|
||||
return RendererHelpers::empty_state(__('暂无内容', 'aksrt'));
|
||||
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' => $slug === 'hot_posts' || in_array('show_number', (array) ($instance['mini_opt'] ?? []), true),
|
||||
'show_thumb' => $slug === 'tab_posts'
|
||||
? in_array('show_thumb', (array) ($instance['mini_opt'] ?? []), true)
|
||||
: ($slug !== 'hot_posts' || !array_key_exists('show_thumbnail', $instance) || !empty($instance['show_thumbnail'])),
|
||||
'show_meta' => $slug !== 'tab_posts' || in_array('show_meta', (array) ($instance['mini_opt'] ?? []), true),
|
||||
'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)),
|
||||
'wrapper_class' => 'aksrt-widget-posts aksrt-widget-posts--' . $slug,
|
||||
'widget_style' => $mode,
|
||||
];
|
||||
if ($slug === 'oneline_posts') {
|
||||
return self::oneline($query, $config, !empty($instance['autoplay']));
|
||||
return self::oneline($query, $config, $mode);
|
||||
}
|
||||
if ($mode === 'mini') {
|
||||
return aksrt_render_mini_list($query, $config, false);
|
||||
return aksrt_render_mini_list($query, array_merge($config, ['class' => 'ajax-item']), false);
|
||||
}
|
||||
return aksrt_render_post_list($config, $query, 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, bool $autoplay): string {
|
||||
private static function oneline(\WP_Query $query, array $config, string $style): string {
|
||||
$items = '';
|
||||
$style = in_array(($config['widget_style'] ?? 'card'), ['list', 'card', 'mini'], true)
|
||||
? (string) $config['widget_style']
|
||||
: 'card';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
if ($style === 'mini') {
|
||||
@@ -74,30 +71,37 @@ final class PostRenderer {
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
$autoplay_attr = $autoplay ? ' data-autoplay="true"' : '';
|
||||
return '<div class="widget-main-post mb20 style-' . esc_attr($style) . '"><div class="swiper-container swiper-scroll" data-slideClass="posts-item"' . $autoplay_attr . '><div class="swiper-wrapper">' . $items . '</div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
|
||||
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 === '' || $mode === 'none') {
|
||||
return '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
}
|
||||
if ($mode === 'number' || $mode === 'numbered') {
|
||||
$links = '';
|
||||
$start = max(1, min($page - 2, $max_pages - 4));
|
||||
$end = min($max_pages, max(5, $page + 2));
|
||||
$html = '<nav class="aksrt-widget-pagination" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">';
|
||||
$end = min($max_pages, max($start + 4, $page + 2));
|
||||
for ($number = $start; $number <= $end; $number++) {
|
||||
$html .= '<button type="button" data-widget-page="' . $number . '"' . ($number === $page ? ' aria-current="page"' : '') . '>' . $number . '</button>';
|
||||
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 $html . '</nav>';
|
||||
return '<div class="pagenav ajax-pag" role="navigation" aria-label="' . esc_attr__('文章分页', 'aksrt') . '">' . $links . '</div>';
|
||||
}
|
||||
$label = __('加载更多', 'aksrt');
|
||||
$class = 'aksrt-widget-load-more';
|
||||
$action = '';
|
||||
return '<button class="' . $class . '"' . $action . ' type="button" data-next-page="' . (int) ($page + 1) . '" data-mode="' . esc_attr($mode) . '">' . esc_html($label) . '</button>';
|
||||
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>';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user