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:
@@ -63,6 +63,7 @@ require_once AKSRT_DIR . '/inc/helpers/header/aksrt-header-menu-search.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-runtime-win.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-identity.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-graphic-card.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-widgets-markup.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-icon-cards.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-user-card.php';
|
||||
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
|
||||
|
||||
@@ -168,7 +168,7 @@ if (!function_exists('aksrt_graphic_card')) :
|
||||
$class = trim('graphic hover-zoom-img ' . (string) $args['class'] . ' ' . $type);
|
||||
$height_scale = max(0, (int) $args['height_scale']);
|
||||
$scale_attr = $height_scale > 0
|
||||
? ' style="padding-bottom:' . esc_attr((string) $height_scale) . '%!important;"'
|
||||
? ' style="--cover-height-scale:' . esc_attr((string) $height_scale) . '%;"'
|
||||
: '';
|
||||
|
||||
$html = '';
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
<?php
|
||||
/**
|
||||
* Widget-system markup helpers (reference parity).
|
||||
*
|
||||
* Ports of the reference theme widget renderers that stay procedural so the
|
||||
* schema renderers stay small: hot posts ranking, category aggregation card,
|
||||
* recent comments list and the ajax empty state.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (!function_exists('aksrt_widget_str_cut')) :
|
||||
/**
|
||||
* Character-based truncation (reference zib_str_cut parity).
|
||||
*/
|
||||
function aksrt_widget_str_cut(string $text, int $length): string {
|
||||
$text = trim($text);
|
||||
if ($length <= 0 || function_exists('mb_strlen') === false) {
|
||||
return $text;
|
||||
}
|
||||
return mb_strlen($text) > $length ? mb_substr($text, 0, $length) . '...' : $text;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_ajax_null')) :
|
||||
/**
|
||||
* Empty state for ajax-paged lists (hidden pager stub included).
|
||||
*/
|
||||
function aksrt_widget_ajax_null(string $text = ''): string {
|
||||
if ($text === '') {
|
||||
$text = __('暂无内容', 'aksrt');
|
||||
}
|
||||
$html = '<div class="ajax-item">' . aksrt_get_null($text, 60, 'null.svg', '', 280) . '</div>';
|
||||
$html .= '<div class="ajax-pag hide"><div class="next-page ajax-next"><a href="#"></a></div></div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_cover_url')) :
|
||||
/**
|
||||
* Category cover image URL with the theme meta fallback chain.
|
||||
*/
|
||||
function aksrt_widget_term_cover_url(int $term_id): string {
|
||||
foreach (['aksrt_cover_url', 'cover_url'] as $meta_key) {
|
||||
$url = esc_url_raw((string) get_term_meta($term_id, $meta_key, true));
|
||||
if ($url !== '') {
|
||||
return $url;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_views_sum')) :
|
||||
/**
|
||||
* Sum of published post views inside a term (badge on the aggregation card).
|
||||
*/
|
||||
function aksrt_widget_term_views_sum(int $term_id): int {
|
||||
static $cache = [];
|
||||
if (isset($cache[$term_id])) {
|
||||
return $cache[$term_id];
|
||||
}
|
||||
$sum = 0;
|
||||
$term = get_term($term_id);
|
||||
if ($term instanceof WP_Term && class_exists('wpdb')) {
|
||||
global $wpdb;
|
||||
$view_key = class_exists('AKSRT\\Core\\DomainModel') ? \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY : 'aksrt_post_views';
|
||||
$tt_id = (int) $term->term_taxonomy_id;
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- prepare() below.
|
||||
$sum = (int) $wpdb->get_var($wpdb->prepare(
|
||||
"SELECT SUM(CAST(pm.meta_value AS UNSIGNED)) FROM {$wpdb->postmeta} AS pm
|
||||
INNER JOIN {$wpdb->posts} AS p ON p.ID = pm.post_id
|
||||
INNER JOIN {$wpdb->term_relationships} AS tr ON tr.object_id = p.ID
|
||||
WHERE pm.meta_key = %s AND tr.term_taxonomy_id = %d
|
||||
AND p.post_status = 'publish' AND p.post_type = 'post'",
|
||||
$view_key,
|
||||
$tt_id
|
||||
));
|
||||
}
|
||||
$cache[$term_id] = max(0, $sum);
|
||||
return $cache[$term_id];
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_hot_posts')) :
|
||||
/**
|
||||
* Hot posts ranking box (reference parity, sidebar sized).
|
||||
*
|
||||
* @param array<string,mixed> $args {orderby, limit_day, count, target_blank}.
|
||||
*/
|
||||
function aksrt_widget_hot_posts(array $args = []): string {
|
||||
$instance = wp_parse_args($args, [
|
||||
'orderby' => 'views',
|
||||
'limit_day' => 0,
|
||||
'count' => 6,
|
||||
'target_blank' => false,
|
||||
]);
|
||||
$orderby = (string) $instance['orderby'];
|
||||
$target = !empty($instance['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
|
||||
$instance['paginate'] = '';
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build($instance, 1, []);
|
||||
if (!$query->have_posts()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$posts_html = '';
|
||||
$i = 0;
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$i++;
|
||||
$data = aksrt_renderer_collect_post_data(['time_source' => 'date']);
|
||||
if (!$data) {
|
||||
continue;
|
||||
}
|
||||
$title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '<span class="focus-color">' . esc_html((string) $data['subtitle']) . '</span>' : '');
|
||||
|
||||
$badge_classes = ['', 'jb-red', 'jb-yellow'];
|
||||
$badge_class = ($i === 1 ? 'em12 ' : '') . ($badge_classes[$i - 1] ?? 'b-gray');
|
||||
$top_badge = '<badge class="img-badge left hot ' . esc_attr($badge_class) . '"><i>TOP' . $i . '</i></badge>';
|
||||
|
||||
$meta_value = '';
|
||||
if ($orderby === 'favorite') {
|
||||
$meta_value = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id'])) . __('人收藏', 'aksrt');
|
||||
} elseif ($orderby === 'like') {
|
||||
$meta_value = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id'])) . __('人点赞', 'aksrt');
|
||||
} elseif ($orderby === 'comment_count') {
|
||||
$meta_value = aksrt_format_number((int) $data['comments']) . __('条讨论', 'aksrt');
|
||||
}
|
||||
if ($meta_value === '') {
|
||||
$meta_value = aksrt_format_number((int) $data['views']) . __('人已阅读', 'aksrt');
|
||||
}
|
||||
|
||||
$thumb = '';
|
||||
if ((string) $data['primary'] !== '') {
|
||||
$thumb = '<img' . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['primary'], 'fit-cover radius8') . ' alt="' . esc_attr((string) $data['title']) . '">';
|
||||
}
|
||||
$time_ago = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
|
||||
$permalink = esc_url((string) $data['permalink']);
|
||||
|
||||
if ($i === 1) {
|
||||
$posts_html .= '<div class="relative">';
|
||||
$posts_html .= '<a' . $target . ' href="' . $permalink . '">';
|
||||
$posts_html .= '<div class="graphic hover-zoom-img" style="padding-bottom: 60%!important;">' . $thumb;
|
||||
$posts_html .= '<div class="absolute linear-mask"></div>';
|
||||
$posts_html .= '<div class="abs-center left-bottom box-body">';
|
||||
$posts_html .= '<div class="mb6"><span class="badg b-theme badg-sm">' . esc_html($meta_value) . '</span></div>';
|
||||
$posts_html .= $title;
|
||||
$posts_html .= '</div></div></a>';
|
||||
$posts_html .= $top_badge;
|
||||
$posts_html .= '</div>';
|
||||
} else {
|
||||
$posts_html .= '<div class="flex mt15 relative hover-zoom-img">';
|
||||
$posts_html .= '<a' . $target . ' href="' . $permalink . '"><div class="graphic">' . $thumb . '</div></a>';
|
||||
$posts_html .= '<div class="term-title ml10 flex xx flex1 jsb">';
|
||||
$posts_html .= '<div class="text-ellipsis-2"><a' . $target . ' href="' . $permalink . '">' . $title . '</a></div>';
|
||||
$posts_html .= '<div class="px12 muted-3-color text-ellipsis flex jsb"><span>' . $time_ago . '</span><span>' . esc_html($meta_value) . '</span></div>';
|
||||
$posts_html .= '</div>';
|
||||
$posts_html .= $top_badge;
|
||||
$posts_html .= '</div>';
|
||||
}
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
return '<div class="zib-widget hot-posts">' . $posts_html . '</div>';
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_term_aggregation')) :
|
||||
/**
|
||||
* Category aggregation card: cover + description + latest posts list.
|
||||
*
|
||||
* @param array<string,mixed> $args {term_id, target_blank, orderby, order, count}.
|
||||
*/
|
||||
function aksrt_widget_term_aggregation(array $args = []): string {
|
||||
$args = wp_parse_args($args, [
|
||||
'term_id' => 0,
|
||||
'target_blank' => false,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'count' => 4,
|
||||
]);
|
||||
$term_id = (int) $args['term_id'];
|
||||
$term = $term_id > 0 ? get_term($term_id, 'category') : null;
|
||||
if (!$term instanceof WP_Term || is_wp_error($term)) {
|
||||
return '';
|
||||
}
|
||||
$target = !empty($args['target_blank']) ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||
$href = get_term_link($term);
|
||||
if (is_wp_error($href)) {
|
||||
return '';
|
||||
}
|
||||
$href = esc_url((string) $href);
|
||||
|
||||
$icon = '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>';
|
||||
$img = aksrt_widget_term_cover_url((int) $term->term_id);
|
||||
$img_html = aksrt_graphic_card([
|
||||
'class' => '',
|
||||
'img' => $img,
|
||||
'alt' => __('分类', 'aksrt') . '-' . $term->name,
|
||||
'link' => ['url' => $href, 'target' => !empty($args['target_blank']) ? '_blank' : ''],
|
||||
'height_scale' => 0,
|
||||
'mask_opacity' => 0,
|
||||
'more' => '<badge class="img-badge px12">' . aksrt_renderer_svg_icon('hot') . ' ' . esc_html(aksrt_format_number(aksrt_widget_term_views_sum((int) $term->term_id))) . '</badge>',
|
||||
]);
|
||||
$img_html = '<div class="term-img flex0 em09-sm">' . $img_html . '</div>';
|
||||
|
||||
$name = '<a class="em14 key-color text-ellipsis"' . $target . ' href="' . $href . '">' . esc_html($term->name) . '</a>';
|
||||
$description = '<div class="text-ellipsis-2 muted-color">' . esc_html($term->description) . '</div>';
|
||||
|
||||
$query_args = [
|
||||
'cat' => (string) $term->term_id,
|
||||
'orderby' => (string) $args['orderby'],
|
||||
'order' => strtoupper((string) $args['order']) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'count' => max(1, (int) $args['count']),
|
||||
'paginate' => '',
|
||||
];
|
||||
$query = \AKSRT\WidgetSystem\PostQuery::build($query_args, 1, []);
|
||||
|
||||
$meta = '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>' . sprintf(esc_html__('%s篇文章', 'aksrt'), esc_html(aksrt_format_number(max(0, (int) $query->found_posts)))) . '</span>';
|
||||
$meta .= '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $target . ' href="' . $href . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
|
||||
$meta = '<div class="term-meta flex jsb ac">' . $meta . '</div>';
|
||||
|
||||
$posts_html = '';
|
||||
while ($query->have_posts()) {
|
||||
$query->the_post();
|
||||
$data = aksrt_renderer_collect_post_data(['time_source' => 'date']);
|
||||
if (!$data) {
|
||||
continue;
|
||||
}
|
||||
$title = esc_html((string) $data['title']) . ($data['subtitle'] !== '' ? '<span class="focus-color">' . esc_html((string) $data['subtitle']) . '</span>' : '');
|
||||
$title = '<div class="text-ellipsis"><a class="icon-circle mln3"' . $target . ' href="' . esc_url((string) $data['permalink']) . '">' . $title . '</a></div>';
|
||||
|
||||
$post_meta = '';
|
||||
switch ((string) $args['orderby']) {
|
||||
case 'views':
|
||||
$post_meta = aksrt_format_number((int) $data['views']);
|
||||
break;
|
||||
case 'favorite':
|
||||
$post_meta = aksrt_format_number((int) aksrt_get_post_favorites((int) $data['post_id']));
|
||||
break;
|
||||
case 'like':
|
||||
$post_meta = aksrt_format_number((int) aksrt_get_post_likes((int) $data['post_id']));
|
||||
break;
|
||||
case 'comment_count':
|
||||
$post_meta = aksrt_format_number((int) $data['comments']);
|
||||
break;
|
||||
}
|
||||
if ($post_meta === '') {
|
||||
$post_meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html((string) $data['time_ago']);
|
||||
}
|
||||
$posts_html .= '<div class="mt10 flex jsb ac">' . $title . '<div class="em09 muted-3-color flex0 ml10">' . $post_meta . '</div></div>';
|
||||
}
|
||||
wp_reset_postdata();
|
||||
|
||||
$html = '<div class="zib-widget term-aggregation mb10">';
|
||||
$html .= '<div class="mb20 hover-zoom-img flex px12-sm px12-m-s">';
|
||||
$html .= $img_html;
|
||||
$html .= '<div class="term-title ml10 flex xx flex1 jsb">' . $name . $description . $meta . '</div>';
|
||||
$html .= '</div>';
|
||||
$html .= $posts_html;
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_comments_list')) :
|
||||
/**
|
||||
* Recent comments widget list (reference parity).
|
||||
*
|
||||
* @param array<string,mixed> $args {limit, outer, outpost, style, show_source}.
|
||||
*/
|
||||
function aksrt_widget_comments_list(array $args = []): string {
|
||||
$args = wp_parse_args($args, [
|
||||
'limit' => 8,
|
||||
'outer' => '',
|
||||
'outpost' => '',
|
||||
'style' => '',
|
||||
'show_source' => false,
|
||||
]);
|
||||
$comments = get_comments([
|
||||
'orderby' => 'comment_date',
|
||||
'number' => max(1, (int) $args['limit']),
|
||||
'status' => 'approve',
|
||||
'post_type' => ['post'],
|
||||
'author__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outer']),
|
||||
'post__not_in' => \AKSRT\WidgetSystem\RendererHelpers::ids((string) $args['outpost']),
|
||||
]);
|
||||
if (!$comments) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = '';
|
||||
foreach ($comments as $comment) {
|
||||
$content = aksrt_widget_comment_excerpt((string) $comment->comment_content);
|
||||
$link = esc_url((string) get_comment_link($comment));
|
||||
$time = aksrt_time_ago((string) $comment->comment_date);
|
||||
$user_name = function_exists('aksrt_get_comments_user_name_html')
|
||||
? aksrt_get_comments_user_name_html($comment)
|
||||
: esc_html(get_comment_author($comment));
|
||||
$like = aksrt_get_comment_like_html($comment);
|
||||
$avatar = aksrt_get_comment_avatar_box($comment, 'avatar-img');
|
||||
$class = 'comment-widget-item' . ((string) $args['style'] === 'style_2' ? ' style-2' : '');
|
||||
|
||||
$source = '';
|
||||
if (!empty($args['show_source'])) {
|
||||
$post_title = esc_html(get_the_title((int) $comment->comment_post_ID));
|
||||
$source = '<div class="muted-color mt6 em09 text-ellipsis"><a href="' . esc_url((string) get_permalink((int) $comment->comment_post_ID)) . '" class="muted-2-color"><span class="muted-3-color">' . esc_html__('评论于:', 'aksrt') . '</span>' . $post_title . '</a></div>';
|
||||
}
|
||||
|
||||
$output .= '<div class="' . esc_attr($class) . '">';
|
||||
$output .= '<div class="flex gap6">' . $avatar;
|
||||
$output .= '<div class="posts-mini-con em09 ml10 flex xx jsb">';
|
||||
$output .= '<p class="flex jsb"><span class="flex ellipsis-box">' . $user_name . '<span class="flex0 icon-spot muted-3-color" title="' . esc_attr((string) $comment->comment_date) . '">' . esc_html($time) . '</span></span><span class="ml10 flex0">' . $like . '</span></p>';
|
||||
$output .= (string) $args['style'] === 'style_2' ? '' : '<a class="con-link muted-color text-ellipsis-5" href="' . $link . '">' . $content . '</a>';
|
||||
$output .= '</div></div>';
|
||||
$output .= (string) $args['style'] === 'style_2' ? '<a class="em09 con-link relative" href="' . $link . '"><div class="text-ellipsis-5 muted-box padding-10">' . $content . '</div></a>' : '';
|
||||
$output .= $source;
|
||||
$output .= '</div>';
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_widget_comment_excerpt')) :
|
||||
/**
|
||||
* Comment text reduced to a widget-safe plain snippet (images/code dropped).
|
||||
*/
|
||||
function aksrt_widget_comment_excerpt(string $content): string {
|
||||
$content = convert_smilies($content);
|
||||
$content = preg_replace('/\[img=(.*?)\]/', '[图片]', $content) ?? $content;
|
||||
$content = preg_replace('/\[link=(.*?) target=_blank\](.*?)\[\/link\]/', '$2', $content) ?? $content;
|
||||
$content = preg_replace('/\[link=(.*?)\](.*?)\[\/link\]/', '$2', $content) ?? $content;
|
||||
$content = preg_replace('/\[code]([\s\S]*)\[\/code]/', '[代码]', $content) ?? $content;
|
||||
$content = preg_replace('/\[g=(.*?)\]/', '[$1]', $content) ?? $content;
|
||||
// Strip any remaining anchors but keep their text.
|
||||
$content = preg_replace('/<a[^>]*>(.*?)<\/a>/', '$1', $content) ?? $content;
|
||||
return wp_kses_post($content);
|
||||
}
|
||||
endif;
|
||||
Reference in New Issue
Block a user