Merge pull request 'Worktree ux depth batch13' (#231) from worktree-ux-depth-batch13 into main

Reviewed-on: CYL/wordpress-theme-aksrt#231
This commit is contained in:
2026-07-21 09:13:11 +08:00
7 changed files with 1011 additions and 135 deletions
+270
View File
@@ -1432,3 +1432,273 @@
.aksrt-product-widget--oneline .aksrt-product-widget__grid {
/* legacy flex fallback if swiper unavailable */
}
/* ===== Batch13: oneline posts swiper-scroll + user minicards + term graphic ===== */
.widget-main-post.aksrt-oneline-posts {
margin-bottom: 16px;
}
.aksrt-oneline-swiper {
position: relative;
overflow: hidden;
padding: 0 28px 4px;
}
.aksrt-oneline-swiper .swiper-wrapper {
display: flex;
align-items: stretch;
}
.aksrt-oneline-swiper .swiper-slide {
width: auto;
max-width: 46%;
min-width: 160px;
height: auto;
}
.widget-main-post.style-mini .aksrt-oneline-swiper .swiper-slide {
max-width: 280px;
min-width: 220px;
}
.widget-main-post.style-list .aksrt-oneline-swiper .swiper-slide {
max-width: 320px;
min-width: 240px;
}
.aksrt-oneline-swiper .posts-item,
.aksrt-oneline-swiper .posts-mini {
height: 100%;
margin: 0 8px 0 0;
}
.aksrt-oneline-swiper .swiper-button-prev,
.aksrt-oneline-swiper .swiper-button-next {
width: 28px;
height: 28px;
margin-top: 0;
top: 42%;
border-radius: 50%;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
color: var(--color-ink, #111);
box-shadow: 0 2px 8px rgba(0,0,0,.06);
}
.aksrt-oneline-swiper .swiper-button-prev:after,
.aksrt-oneline-swiper .swiper-button-next:after {
font-size: 12px;
font-weight: 700;
}
.aksrt-oneline-swiper .swiper-button-prev { left: 0; }
.aksrt-oneline-swiper .swiper-button-next { right: 0; }
/* User lists → author-minicard */
.aksrt-user-lists .user_lists {
display: flex;
flex-direction: column;
gap: 10px;
text-align: left;
}
.aksrt-user-lists .author-minicard {
display: block;
padding: 12px 14px;
border-radius: 8px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
overflow: hidden;
}
.aksrt-user-lists.is-bare .author-minicard {
background: transparent;
}
.aksrt-user-lists .author-minicard > ul.list-inline {
display: flex;
align-items: center;
gap: 12px;
margin: 0;
padding: 0;
list-style: none;
}
.aksrt-user-lists .author-minicard > ul.list-inline > li {
margin: 0;
list-style: none;
}
.aksrt-user-lists .author-minicard .avatar-img img.avatar {
width: 42px;
height: 42px;
border-radius: 50%;
object-fit: cover;
display: block;
}
.aksrt-user-lists .author-minicard dl {
margin: 0;
min-width: 0;
}
.aksrt-user-lists .author-minicard dt.flex {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
}
.aksrt-user-lists .author-minicard dd {
margin: 0;
max-width: 100%;
}
.aksrt-user-lists .author-minicard .abs-blur-bg {
position: absolute;
inset: 0;
overflow: hidden;
z-index: 0;
opacity: .18;
pointer-events: none;
}
.aksrt-user-lists .author-minicard .abs-blur-bg img {
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(18px);
transform: scale(1.2);
}
.aksrt-user-lists .author-minicard .relative {
position: relative;
z-index: 1;
}
/* Graphic card polish */
.graphic {
background: var(--surface-2, #f3f4f6);
}
.graphic .graphic-mask {
position: absolute;
inset: 0;
background: #000;
pointer-events: none;
z-index: 1;
}
.graphic .graphic-text,
.graphic .abs-center {
z-index: 2;
padding: 10px 12px;
}
.graphic .title-h-center {
font-size: 16px;
font-weight: 700;
}
.graphic badge.b-black,
.graphic .img-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 2px 8px;
border-radius: 4px;
background: rgba(0,0,0,.55);
color: #fff;
font-size: 12px;
line-height: 1.4;
}
.graphic video.fit-cover {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
.hover-zoom-img .graphic img.fit-cover,
.hover-zoom-img.graphic img.fit-cover {
transition: transform .45s ease;
}
.hover-zoom-img:hover .graphic img.fit-cover,
.hover-zoom-img.graphic:hover img.fit-cover {
transform: scale(1.06);
}
.aksrt-term-cards .row.gutters-5 {
display: flex;
flex-wrap: wrap;
margin: 0 -4px;
}
.aksrt-term-cards .row.gutters-5 > [class*="col-"] {
padding: 0 4px;
box-sizing: border-box;
}
.aksrt-term-cards .col-sm-12 { width: 100%; }
.aksrt-term-cards .col-sm-6 { width: 50%; }
.aksrt-term-cards .col-sm-4 { width: 33.333%; }
.aksrt-term-cards .col-sm-3 { width: 25%; }
.aksrt-term-cards .col-sm-2 { width: 16.666%; }
@media (max-width: 767px) {
.aksrt-term-cards .col-xs-12 { width: 100%; }
.aksrt-term-cards .col-xs-6 { width: 50%; }
.aksrt-term-cards .col-xs-4 { width: 33.333%; }
}
/* Term aggregation (zibll term-aggregation) */
.term-aggregation.aksrt-term-agg__card,
.zib-widget.term-aggregation {
padding: 14px;
border-radius: 10px;
background: var(--surface-1, #fff);
border: 1px solid var(--color-line, #e5e7eb);
margin-bottom: 12px;
}
.aksrt-term-agg {
display: grid;
gap: 12px;
grid-template-columns: repeat(var(--aksrt-ta-cols, 1), minmax(0, 1fr));
}
.term-aggregation .hover-zoom-img.flex {
display: flex;
align-items: stretch;
gap: 0;
}
.term-aggregation .term-img {
width: 38%;
max-width: 180px;
min-width: 110px;
}
.term-aggregation .term-img .graphic {
margin: 0;
border-radius: 8px;
}
.term-aggregation .term-title {
min-width: 0;
padding-left: 4px;
}
.term-aggregation .term-title > a {
font-weight: 700;
text-decoration: none;
color: inherit;
}
.term-aggregation .term-meta {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-top: 6px;
}
.term-aggregation .icon-circle {
position: relative;
padding-left: 14px;
color: inherit;
text-decoration: none;
}
.term-aggregation .icon-circle:before {
content: "";
position: absolute;
left: 0;
top: 50%;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--theme-color, var(--focus-color, #f04494));
transform: translateY(-50%);
}
.term-aggregation .but.c-blue {
display: inline-flex;
align-items: center;
gap: 2px;
border-radius: 999px;
padding: 2px 10px;
font-size: 12px;
background: rgba(59,130,246,.1);
color: #2563eb;
text-decoration: none;
}
@media (max-width: 640px) {
.aksrt-term-agg { grid-template-columns: 1fr; }
.term-aggregation .term-img { max-width: 120px; min-width: 88px; width: 34%; }
.term-aggregation .hide-sm { display: none !important; }
.aksrt-oneline-swiper .swiper-slide { max-width: 72%; min-width: 140px; }
}
+1
View File
@@ -48,6 +48,7 @@ require_once AKSRT_DIR . '/inc/helpers/aksrt-header-userbox.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-header-menu-buttons.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-user-card.php';
require_once AKSRT_DIR . '/inc/helpers/aksrt-rewards.php';
require_once AKSRT_DIR . '/inc/helpers/menu-decoration.php';
+276
View File
@@ -0,0 +1,276 @@
<?php
/**
* Shared graphic / cover card (zibll zib_graphic_card parity).
*
* Used by term cards, cover cards, term aggregation thumbs, etc.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_graphic_card')) :
/**
* Render a graphic cover card.
*
* @param array<string,mixed> $args {
* @type string $type style-1|style-2|style-3|style-4|''.
* @type string $class Extra classes on .graphic.
* @type string $img Image URL.
* @type string $video Optional video URL (desktop).
* @type string $alt Image alt.
* @type array{url?:string,target?:string}|string $link Link.
* @type string $text1 Primary text.
* @type string $text2 Secondary text.
* @type string $text3 Tertiary / badge text (HTML allowed for icon+count).
* @type string $more Extra absolute HTML (badge etc).
* @type bool $lazy Use aksrt-lazy data-src.
* @type int|float $height_scale Padding-bottom % (0 = default CSS).
* @type int|float $mask_opacity 0–100 overlay opacity.
* @type string $hide pc|m|'' hide on device.
* }
* @param bool $echo Echo instead of return.
*/
function aksrt_graphic_card(array $args = [], bool $echo = false): string {
$defaults = [
'type' => '',
'hide' => '',
'class' => 'mb10',
'img' => '',
'video' => '',
'alt' => __('图片', 'aksrt'),
'link' => [
'url' => '',
'target' => '',
],
'text1' => '',
'text2' => '',
'text3' => '',
'more' => '',
'lazy' => true,
'height_scale' => 0,
'mask_opacity' => 0,
];
$args = wp_parse_args($args, $defaults);
$img_url = trim((string) $args['img']);
$video_url = trim((string) $args['video']);
if ($img_url === '' && $video_url === '') {
return '';
}
// Device visibility.
$hide = sanitize_key((string) $args['hide']);
if ($hide === 'pc' || $hide === 'm') {
$is_mobile = function_exists('wp_is_mobile') ? wp_is_mobile() : false;
if ((!$is_mobile && $hide === 'pc') || ($is_mobile && $hide === 'm')) {
return '';
}
}
$type = sanitize_key((string) $args['type']);
if ($type !== '' && !in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
// Accept bare "1"–"4".
if (preg_match('/^[1-4]$/', $type)) {
$type = 'style-' . $type;
} else {
$type = '';
}
}
$link = $args['link'];
if (is_string($link)) {
$link = ['url' => $link, 'target' => ''];
}
if (!is_array($link)) {
$link = ['url' => '', 'target' => ''];
}
$link_url = esc_url(trim((string) ($link['url'] ?? '')));
$link_target = sanitize_text_field((string) ($link['target'] ?? ''));
$lazy = !empty($args['lazy']);
$placeholder = get_template_directory_uri() . '/assets/img/placeholder.svg';
$media = '';
if ($video_url !== '' && !(function_exists('wp_is_mobile') && wp_is_mobile())) {
$poster = $img_url !== '' ? ' poster="' . esc_url($img_url) . '"' : '';
if ($lazy) {
$media = '<video autoplay loop muted playsinline class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($video_url) . '" src=""' . $poster . '></video>';
} else {
$media = '<video autoplay loop muted playsinline class="fit-cover" src="' . esc_url($video_url) . '"' . $poster . '></video>';
}
} elseif ($img_url !== '') {
$alt = (string) ($args['alt'] !== '' ? $args['alt'] : __('图片', 'aksrt'));
if ($lazy) {
$media = '<img class="fit-cover aksrt-lazy lazyload" data-src="' . esc_url($img_url) . '" src="' . esc_url($placeholder) . '" alt="' . esc_attr($alt) . '" loading="lazy" />';
} else {
$media = '<img class="fit-cover" src="' . esc_url($img_url) . '" alt="' . esc_attr($alt) . '" loading="lazy" />';
}
}
if ($media === '') {
return '';
}
$mask_opacity = max(0, min(100, (int) $args['mask_opacity']));
$mask = $mask_opacity > 0
? '<div class="absolute graphic-mask" style="opacity:' . esc_attr((string) ($mask_opacity / 100)) . '"></div>'
: '';
// text1/2/3 may contain small trusted HTML (icons); sanitize lightly.
$text1 = (string) $args['text1'];
$text2 = (string) $args['text2'];
$text3 = (string) $args['text3'];
$allow = [
'i' => ['class' => true, 'aria-hidden' => true],
'span' => ['class' => true, 'title' => true],
'item' => ['class' => true, 'data-toggle' => true, 'title' => true],
'b' => [],
'strong'=> [],
'em' => [],
'badge' => ['class' => true],
];
$text1_safe = $text1 !== '' ? wp_kses($text1, $allow) : '';
$text2_safe = $text2 !== '' ? wp_kses($text2, $allow) : '';
$text3_safe = $text3 !== '' ? wp_kses($text3, $allow) : '';
$more = (string) $args['more'];
$more_safe = $more !== '' ? wp_kses($more, array_merge($allow, [
'div' => ['class' => true, 'style' => true],
'badge' => ['class' => true],
])) : '';
$text = '';
if ($type === 'style-2') {
$text = '<div class="abs-center conter-conter graphic-text">';
$text .= $text1_safe !== '' ? '<div class="title-h-center"><b>' . $text1_safe . '</b></div>' : '';
$text .= $text2_safe !== '' ? '<div class="em09 opacity8">' . $text2_safe . '</div>' : '';
$text .= '</div>';
if ($text3_safe !== '') {
$text .= '<div class="abs-center right-top"><badge class="b-black opacity8">' . $text3_safe . '</badge></div>';
}
} elseif ($type === 'style-3') {
$text = $text1_safe !== '' ? '<div class="abs-center left-bottom graphic-text text-ellipsis">' . $text1_safe . '</div>' : '';
$text .= '<div class="abs-center left-bottom graphic-text">';
$text .= $text2_safe !== '' ? '<div class="em09 opacity8">' . $text2_safe . '</div>' : '';
$text .= $text3_safe !== '' ? '<div class="px12 opacity8 mt6">' . $text3_safe . '</div>' : '';
$text .= '</div>';
} elseif ($type === 'style-4') {
if ($text3_safe !== '') {
$text = '<div class="abs-center right-top"><badge class="b-black opacity8 mr6 mt6">' . $text3_safe . '</badge></div>';
}
} else {
// style-1 / default.
$inner = $text1_safe !== '' ? '<div class="title-h-left"><b>' . $text1_safe . '</b></div>' : '';
$inner .= $text3_safe !== '' ? '<div class="em09 opacity8">' . $text3_safe . '</div>' : '';
$inner .= $text2_safe !== '' ? '<div class="em09">' . $text2_safe . '</div>' : '';
$text = $inner !== '' ? '<div class="abs-center left-bottom graphic-text">' . $inner . '</div>' : '';
}
$text .= $more_safe;
$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;"'
: '';
$html = '';
if ($link_url !== '') {
$tgt = $link_target !== '' ? ' target="' . esc_attr($link_target) . '" rel="noopener noreferrer"' : '';
$html .= '<a href="' . esc_url($link_url) . '"' . $tgt . '>';
}
if ($type === 'style-4') {
$html .= '<div class="main-shadow radius8 main-bg mb10">';
}
$html .= '<div class="' . esc_attr($class) . '"' . $scale_attr . '>';
$html .= $media;
$html .= $mask;
$html .= $text;
if ($type === 'style-4') {
$html .= '</div>'; // close .graphic early for style-4 body.
$html .= '<div class="padding-10">';
$html .= $text1_safe !== '' ? '<div class="text-ellipsis">' . $text1_safe . '</div>' : '';
$html .= $text2_safe !== '' ? '<div class="muted-2-color em09 text-ellipsis mt6">' . $text2_safe . '</div>' : '';
$html .= '</div></div>'; // padding + main-shadow
} else {
$html .= '</div>';
}
if ($link_url !== '') {
$html .= '</a>';
}
if ($echo) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return '';
}
return $html;
}
endif;
if (!function_exists('aksrt_author_minicard')) :
/**
* Author mini card for user lists (zibll zib_author_card).
*
* @param int $user_id
* @param string $class
* @param bool $blur_bg
*/
function aksrt_author_minicard(int $user_id, string $class = '', bool $blur_bg = false): string {
if ($user_id <= 0) {
return '';
}
$user = get_userdata($user_id);
if (!$user) {
return '';
}
$avatar_url = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($user_id, ['size' => 96])
: get_avatar_url($user_id, ['size' => 96]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($user_id)
: get_author_posts_url($user_id);
$avatar_box = '<div class="avatar-img"><a href="' . esc_url($home) . '"><img class="avatar" src="' . esc_url($avatar_url) . '" alt="" loading="lazy" width="42" height="42" /></a></div>';
$name = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $user_id,
'class' => 'flex ac flex1',
'follow' => false,
'medal' => true,
'auth' => true,
'level' => true,
])
: '<a href="' . esc_url($home) . '">' . esc_html($user->display_name) . '</a>';
// Follow control (separate from name, zibll places both in dt).
$follow = '';
$current = get_current_user_id();
if ($current !== $user_id) {
if (is_user_logged_in()) {
$follow = '<button type="button" class="focus-color px12 ml10 follow flex0 but" data-aksrt-follow="' . (int) $user_id . '"><i class="fa fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</button>';
} else {
$login = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
$follow = '<a class="focus-color px12 ml10 follow flex0 but" href="' . esc_url($login) . '"><i class="fa fa-heart em09" aria-hidden="true"></i> ' . esc_html__('关注', 'aksrt') . '</a>';
}
}
$desc = trim((string) get_user_meta($user_id, 'description', true));
if ($desc === '') {
$desc = __('这家伙很懒,什么都没有写…', 'aksrt');
}
$blur = '';
if ($blur_bg) {
$blur = '<div class="abs-blur-bg blur"><img src="' . esc_url($avatar_url) . '" alt="" loading="lazy" /></div>';
}
return '<div class="author-minicard radius8 relative-h ' . esc_attr($class) . '">'
. $blur
. '<ul class="list-inline relative">'
. '<li>' . $avatar_box . '</li>'
. '<li><dl>'
. '<dt class="flex ac">' . $name . $follow . '</dt>'
. '<dd class="mt6 em09 muted-color text-ellipsis">' . esc_html($desc) . '</dd>'
. '</dl></li>'
. '</ul></div>';
}
endif;
+90 -26
View File
@@ -1,6 +1,8 @@
<?php
/**
* Horizontal one-line posts scroller (zibll oneline + remote HTML).
* Horizontal one-line posts scroller (zibll zib_widget_ui_oneline_posts).
*
* Uses shared post renderer + slider.js Type A (.swiper-scroll).
*
* @package AKSRT
*/
@@ -16,18 +18,22 @@ class WidgetOnelinePosts extends \WP_Widget {
'aksrt_oneline_posts',
esc_html__('AKSRT 横向文章', 'aksrt'),
[
'description' => esc_html__('横向滚动的文章卡片/迷你列表。', 'aksrt'),
'description' => esc_html__('横向滚动的文章卡片/迷你列表(共享 swiper-scroll)。', 'aksrt'),
'classname' => 'widget_aksrt_oneline_posts',
]
);
}
/**
* @param array<string,mixed> $instance
* @return array<string,mixed>
*/
public static function build_query_args(array $instance): array {
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$count = max(1, min(20, (int) ($instance['count'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$cat = (int) ($instance['cat'] ?? 0);
$limit_day = max(0, (int) ($instance['limit_day'] ?? 0));
$query = [
$query = [
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => $count,
@@ -43,28 +49,32 @@ class WidgetOnelinePosts extends \WP_Widget {
switch ($orderby) {
case 'views':
$query['meta_key'] = 'aksrt_post_views';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
$query['orderby'] = 'meta_value_num';
$query['order'] = 'DESC';
break;
case 'comments':
$query['orderby'] = 'comment_count';
$query['order'] = 'DESC';
$query['order'] = 'DESC';
break;
case 'rand':
$query['orderby'] = 'rand';
break;
default:
$query['orderby'] = 'date';
$query['order'] = 'DESC';
$query['order'] = 'DESC';
}
return $query;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$style = sanitize_key((string) ($instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
$style = 'card';
}
$q = new \WP_Query(self::build_query_args($instance));
if (!$q->have_posts()) {
wp_reset_postdata();
@@ -72,29 +82,75 @@ class WidgetOnelinePosts extends \WP_Widget {
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
ob_start();
echo '<div class="aksrt-oneline style-' . esc_attr($style) . ' ajax-item"><div class="aksrt-oneline__track">';
while ($q->have_posts()) {
$q->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
echo '<a class="aksrt-oneline__item" href="' . esc_url(get_permalink()) . '">';
if ($thumb) {
echo '<div class="aksrt-oneline__thumb"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></div>';
} else {
echo '<div class="aksrt-oneline__thumb is-empty"></div>';
$mode = $style === 'mini' ? 'mini' : ($style === 'list' ? 'list' : 'card');
$cfg = [
'mode' => $mode,
'context' => 'widget',
'show_thumb' => true,
'show_meta' => $style !== 'mini',
'show_excerpt' => false,
'show_author' => false,
'show_comments' => false,
'show_likes' => false,
'show_tags' => false,
'show_badges' => $style === 'card',
'skip_wrapper' => true,
'extra_class' => 'posts-item',
];
$slides = '';
if (function_exists('aksrt_render_post')) {
while ($q->have_posts()) {
$q->the_post();
$item = aksrt_render_post($cfg);
if ($item === '') {
continue;
}
// Ensure freeMode width calc has a posts-item root when renderer wraps differently.
if (strpos($item, 'posts-item') === false && strpos($item, 'posts-mini') === false) {
$item = '<div class="posts-item">' . $item . '</div>';
}
$slides .= '<div class="swiper-slide ajax-item">' . $item . '</div>';
}
echo '<div class="aksrt-oneline__body">';
echo '<div class="aksrt-oneline__title">' . esc_html(get_the_title()) . '</div>';
if ($style !== 'mini') {
echo '<div class="aksrt-oneline__meta">' . esc_html(get_the_date()) . '</div>';
wp_reset_postdata();
} else {
// Fallback: bare thumb+title (should rarely hit).
while ($q->have_posts()) {
$q->the_post();
$thumb = get_the_post_thumbnail_url(get_the_ID(), $style === 'card' ? 'medium' : 'thumbnail');
$slides .= '<div class="swiper-slide ajax-item"><a class="posts-item aksrt-oneline-fallback" href="' . esc_url(get_permalink()) . '">';
if ($thumb) {
$slides .= '<div class="item-thumbnail"><img src="' . esc_url($thumb) . '" alt="" loading="lazy" /></div>';
}
$slides .= '<div class="item-body"><div class="item-heading">' . esc_html(get_the_title()) . '</div></div></a></div>';
}
echo '</div></a>';
wp_reset_postdata();
}
if ($slides === '') {
return function_exists('aksrt_get_ajax_null')
? aksrt_get_ajax_null(__('暂无文章', 'aksrt'))
: '<p class="widget-empty ajax-item">' . esc_html__('暂无文章', 'aksrt') . '</p>';
}
// zibll: .widget-main-post.style-{card|mini|list} + .swiper-container.swiper-scroll data-slideClass="posts-item".
ob_start();
echo '<div class="widget-main-post style-' . esc_attr($style) . ' aksrt-oneline-posts ajax-item">';
echo '<div class="swiper-container swiper-scroll aksrt-oneline-swiper" data-slideClass="posts-item">';
echo '<div class="swiper-wrapper">';
echo $slides; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
echo '<div class="swiper-button-prev" aria-label="' . esc_attr__('上一页', 'aksrt') . '"></div>';
echo '<div class="swiper-button-next" aria-label="' . esc_attr__('下一页', 'aksrt') . '"></div>';
echo '</div></div>';
wp_reset_postdata();
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
@@ -112,6 +168,9 @@ class WidgetOnelinePosts extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
@@ -156,6 +215,11 @@ class WidgetOnelinePosts extends \WP_Widget {
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$style = sanitize_key((string) ($new_instance['style'] ?? 'card'));
if (!in_array($style, ['card', 'mini', 'list'], true)) {
+166 -42
View File
@@ -1,6 +1,6 @@
<?php
/**
* Term aggregation cards (zibll zib_widget_ui_term_lists_card lite).
* Term aggregation cards (zibll zib_widget_ui_term_lists_card → zib_term_aggregation).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetTermAggregation extends \WP_Widget {
'aksrt_term_aggregation',
esc_html__('AKSRT 分类聚合', 'aksrt'),
[
'description' => esc_html__('分类封面 + 最新文章列表聚合卡。', 'aksrt'),
'description' => esc_html__('分类封面 + 描述 + 更多文章 CTA + 文章列表聚合卡。', 'aksrt'),
'classname' => 'widget_aksrt_term_aggregation',
]
);
@@ -37,15 +37,68 @@ class WidgetTermAggregation extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* @param \WP_Term $term
*/
private static function term_cover_url(\WP_Term $term): string {
if (class_exists(WidgetTermCards::class) && method_exists(WidgetTermCards::class, 'term_cover_url')) {
return WidgetTermCards::term_cover_url($term);
}
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$url = wp_get_attachment_image_url($img_id, 'medium');
if ($url) {
return (string) $url;
}
}
$url = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
return $url !== '' ? $url : get_template_directory_uri() . '/assets/img/placeholder.svg';
}
/**
* Sum of post views under a term (best-effort).
*/
private static function term_views_sum(int $term_id, string $taxonomy): int {
$q = new \WP_Query([
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => 50,
'fields' => 'ids',
'no_found_rows' => true,
'update_post_meta_cache' => true,
'update_post_term_cache' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
]],
]);
$sum = 0;
foreach ($q->posts as $pid) {
$sum += function_exists('aksrt_get_post_views')
? (int) aksrt_get_post_views((int) $pid)
: (int) get_post_meta((int) $pid, 'aksrt_post_views', true);
}
return $sum;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$term_ids = self::parse_ids_static((string) ($instance['term_ids'] ?? ''));
$count = max(1, min(12, (int) ($instance['count'] ?? 4)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
$count = max(1, min(12, (int) ($instance['count'] ?? 4)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'date'));
$columns = max(1, min(3, (int) ($instance['columns'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$tgt = $target_blank ? ' target="_blank" rel="noopener noreferrer"' : '';
if (!$term_ids) {
$terms = get_terms([
@@ -74,16 +127,38 @@ class WidgetTermAggregation extends \WP_Widget {
if (is_wp_error($link)) {
continue;
}
$cover = '';
$img_id = (int) get_term_meta($term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term_id, '_aksrt_cover', true);
$cover = self::term_cover_url($term);
$views = self::term_views_sum((int) $term->term_id, $taxonomy);
$more_badge = '<badge class="img-badge px12"><i class="fa fa-fire" aria-hidden="true"></i> ' . esc_html((string) $views) . '</badge>';
$img_html = '';
if (function_exists('aksrt_graphic_card')) {
$img_html = aksrt_graphic_card([
'type' => '',
'class' => '',
'img' => $cover,
'alt' => $term->name,
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'lazy' => true,
'height_scale' => 70,
'mask_opacity' => 0,
'more' => $more_badge,
]);
}
if ($img_id) {
$cover = wp_get_attachment_image_url($img_id, 'medium') ?: '';
}
if ($cover === '') {
$cover = (string) get_term_meta($term_id, '_aksrt_cover_url', true);
$description = trim((string) $term->description);
if ($description === '' && current_user_can('manage_categories')) {
$edit = get_edit_term_link((int) $term->term_id, $taxonomy);
$description = esc_html__('请在后台为该分类添加描述。', 'aksrt');
if ($edit) {
$description .= ' <a href="' . esc_url($edit) . '">' . esc_html__('立即编辑', 'aksrt') . '</a>';
}
} else {
$description = esc_html($description);
}
$posts_q = [
@@ -91,39 +166,75 @@ class WidgetTermAggregation extends \WP_Widget {
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'no_found_rows' => false,
'tax_query' => [[
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $term_id,
'terms' => (int) $term->term_id,
]],
];
if ($orderby === 'views') {
$posts_q['meta_key'] = 'aksrt_post_views';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'like') {
$posts_q['meta_key'] = 'aksrt_post_likes';
$posts_q['orderby'] = 'meta_value_num';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'comment_count') {
$posts_q['orderby'] = 'comment_count';
$posts_q['order'] = 'DESC';
} elseif ($orderby === 'modified') {
$posts_q['orderby'] = 'modified';
$posts_q['order'] = 'DESC';
$posts_q['order'] = 'DESC';
} else {
$posts_q['orderby'] = 'date';
$posts_q['order'] = 'DESC';
$posts_q['order'] = 'DESC';
}
$q = new \WP_Query($posts_q);
$found = (int) $q->found_posts;
echo '<div class="zib-widget term-aggregation mb10 aksrt-term-agg__card">';
echo '<div class="mb20 hover-zoom-img flex px12-sm">';
echo '<div class="term-img flex0 em09-sm">' . $img_html . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-title ml10 flex xx flex1 jsb">';
echo '<a class="em14 key-color text-ellipsis"' . $tgt . ' href="' . esc_url($link) . '">' . esc_html($term->name) . '</a>';
echo '<div class="text-ellipsis-2 muted-color">' . $description . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<div class="term-meta flex jsb ac">';
echo '<span class="px12 muted-3-color text-ellipsis"><i class="fa fa-file-text-o fa-fw" aria-hidden="true"></i>'
. esc_html(sprintf(
/* translators: %s: article count */
__('%s篇文章', 'aksrt'),
(string) $found
))
. '</span>';
echo '<a class="but p2-10 px12 c-blue hide-sm shrink0"' . $tgt . ' href="' . esc_url($link) . '"><i class="fa fa-angle-right" aria-hidden="true"></i>' . esc_html__('更多文章', 'aksrt') . '</a>';
echo '</div></div></div>';
echo '<div class="aksrt-term-agg__card">';
echo '<a class="aksrt-term-agg__head" href="' . esc_url($link) . '">';
echo '<div class="aksrt-term-agg__cover' . ($cover ? '' : ' is-empty') . '"' . ($cover ? ' style="background-image:url(' . esc_url($cover) . ')"' : '') . '></div>';
echo '<div class="aksrt-term-agg__name">' . esc_html($term->name) . '</div>';
echo '<div class="aksrt-term-agg__count">' . esc_html(sprintf(_n('%d 篇', '%d 篇', (int) $term->count, 'aksrt'), (int) $term->count)) . '</div>';
echo '</a>';
if ($q->have_posts()) {
echo '<ul class="aksrt-term-agg__posts">';
while ($q->have_posts()) {
$q->the_post();
echo '<li><a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a></li>';
$pid = get_the_ID();
$title = get_the_title($pid);
$meta = '';
if ($orderby === 'views') {
$v = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($pid) : (int) get_post_meta($pid, 'aksrt_post_views', true);
$meta = '<i class="fa fa-eye mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'like') {
$v = function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($pid) : (int) get_post_meta($pid, 'aksrt_post_likes', true);
$meta = '<i class="fa fa-heart mr3" aria-hidden="true"></i>' . esc_html((string) $v);
} elseif ($orderby === 'comment_count') {
$meta = '<i class="fa fa-comment-o mr3" aria-hidden="true"></i>' . esc_html((string) get_comments_number($pid));
} else {
$ts = $orderby === 'modified' ? get_the_modified_time('U', $pid) : get_the_time('U', $pid);
$ago = human_time_diff((int) $ts, current_time('timestamp'));
$meta = '<i class="fa fa-clock-o mr3" aria-hidden="true"></i>' . esc_html($ago);
}
echo '<div class="mt10 flex jsb ac">';
echo '<div class="text-ellipsis"><a class="icon-circle mln3"' . $tgt . ' href="' . esc_url(get_permalink($pid)) . '">' . esc_html($title) . '</a></div>';
echo '<div class="em09 muted-3-color flex0 ml10">' . $meta . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '</div>';
}
echo '</ul>';
wp_reset_postdata();
}
echo '</div>';
@@ -132,6 +243,10 @@ class WidgetTermAggregation extends \WP_Widget {
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
@@ -146,11 +261,9 @@ class WidgetTermAggregation extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/** @return array<int,int> */
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
@@ -187,25 +300,36 @@ class WidgetTermAggregation extends \WP_Widget {
<option value="date" <?php selected(($instance['orderby'] ?? 'date'), 'date'); ?>><?php esc_html_e('时间', 'aksrt'); ?></option>
<option value="modified" <?php selected(($instance['orderby'] ?? ''), 'modified'); ?>><?php esc_html_e('更新', 'aksrt'); ?></option>
<option value="views" <?php selected(($instance['orderby'] ?? ''), 'views'); ?>><?php esc_html_e('浏览', 'aksrt'); ?></option>
<option value="like" <?php selected(($instance['orderby'] ?? ''), 'like'); ?>><?php esc_html_e('点赞', 'aksrt'); ?></option>
<option value="comment_count" <?php selected(($instance['orderby'] ?? ''), 'comment_count'); ?>><?php esc_html_e('评论', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'date'));
if (!in_array($orderby, ['date', 'modified', 'views'], true)) {
if (!in_array($orderby, ['date', 'modified', 'views', 'like', 'comment_count'], true)) {
$orderby = 'date';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(12, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'term_ids' => sanitize_text_field((string) ($new_instance['term_ids'] ?? '')),
'count' => max(1, min(12, (int) ($new_instance['count'] ?? 4))),
'columns' => max(1, min(3, (int) ($new_instance['columns'] ?? 1))),
'orderby' => $orderby,
'auto_terms' => max(1, min(4, (int) ($new_instance['auto_terms'] ?? 2))),
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
];
}
}
+162 -41
View File
@@ -1,6 +1,6 @@
<?php
/**
* Taxonomy term cards (zibll zib_widget_ui_term_card lite).
* Taxonomy term graphic cards (zibll zib_widget_ui_term_card → zib_graphic_card).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetTermCards extends \WP_Widget {
'aksrt_term_cards',
esc_html__('AKSRT 分类/标签卡片', 'aksrt'),
[
'description' => esc_html__('以卡片展示分类或标签。', 'aksrt'),
'description' => esc_html__('以图文封面卡片展示分类或标签(style-1..4)。', 'aksrt'),
'classname' => 'widget_aksrt_term_cards',
]
);
@@ -37,15 +37,49 @@ class WidgetTermCards extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* Resolve term cover URL.
*/
public static function term_cover_url(\WP_Term $term): string {
$thumb = '';
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = (string) (wp_get_attachment_image_url($img_id, 'medium_large') ?: wp_get_attachment_image_url($img_id, 'medium') ?: '');
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
}
if ($thumb === '') {
// Lightweight default gradient placeholder as data URI is heavy — use theme placeholder.
$thumb = get_template_directory_uri() . '/assets/img/placeholder.svg';
}
return $thumb;
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$taxonomy = sanitize_key((string) ($instance['taxonomy'] ?? 'category'));
if (!taxonomy_exists($taxonomy)) {
$taxonomy = 'category';
}
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$number = max(1, min(30, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'count'));
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$hide_empty = !isset($instance['hide_empty']) || !empty($instance['hide_empty']);
$type = sanitize_key((string) ($instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
$height_scale = max(30, min(120, (int) ($instance['height_scale'] ?? 60)));
$mask_opacity = max(0, min(80, (int) ($instance['mask_opacity'] ?? 10)));
$pc_row = max(1, min(6, (int) ($instance['pc_row'] ?? $instance['columns'] ?? 2)));
$m_row = max(1, min(3, (int) ($instance['m_row'] ?? 1)));
$target_blank = !empty($instance['target_blank']);
$term_args = [
'taxonomy' => $taxonomy,
@@ -67,42 +101,94 @@ class WidgetTermCards extends \WP_Widget {
: '<p class="widget-empty ajax-item">' . esc_html__('暂无分类', 'aksrt') . '</p>';
}
$cols = max(1, min(4, (int) ($instance['columns'] ?? 2)));
$icon = $taxonomy === 'category'
? '<i class="fa fa-folder-open-o mr6" aria-hidden="true"></i>'
: '<i class="fa fa-cube mr6" aria-hidden="true"></i>';
$is_row = count($terms) > 1;
// Bootstrap-like col classes already used elsewhere; keep numeric CSS vars as fallback.
$pc_span = max(1, (int) floor(12 / $pc_row));
$m_span = max(1, (int) floor(12 / $m_row));
$row_class = 'col-sm-' . $pc_span . ($m_row > 1 ? ' col-xs-' . $m_span : '');
ob_start();
echo '<div class="aksrt-term-cards ajax-item" style="--aksrt-tc-cols:' . esc_attr((string) $cols) . '">';
echo '<div class="aksrt-term-cards ajax-item' . ($is_row ? ' mb10' : ' mb20') . '">';
if ($is_row) {
echo '<div class="row gutters-5">';
}
foreach ($terms as $term) {
if (!($term instanceof \WP_Term)) {
continue;
}
$link = get_term_link($term);
if (is_wp_error($link)) {
continue;
}
$thumb = '';
if (function_exists('get_term_meta')) {
$img_id = (int) get_term_meta($term->term_id, 'thumbnail_id', true);
if (!$img_id) {
$img_id = (int) get_term_meta($term->term_id, '_aksrt_cover', true);
}
if ($img_id) {
$thumb = wp_get_attachment_image_url($img_id, 'medium') ?: '';
}
if ($thumb === '') {
$thumb = (string) get_term_meta($term->term_id, '_aksrt_cover_url', true);
$name = mb_strlen($term->name) > 16 ? mb_substr($term->name, 0, 16) . '…' : $term->name;
$desc = trim((string) $term->description);
if (mb_strlen($desc) > 24) {
$desc = mb_substr($desc, 0, 24) . '…';
}
$count = (int) $term->count;
$count_label = sprintf(
/* translators: %d: post count */
_n('%d篇文章', '%d篇文章', $count, 'aksrt'),
$count
);
$text1 = $name;
$text2 = $desc;
$text3 = $icon . esc_html($count_label);
if ($type === 'style-2' || $type === 'style-4') {
$text3 = '<item data-toggle="tooltip" title="' . esc_attr(sprintf(__('共%d篇文章', 'aksrt'), $count)) . '">' . $icon . $count . '</item>';
}
if ($type === 'style-3' || $type === 'style-4') {
$text1 = $icon . esc_html($name);
if ($type === 'style-3') {
$text3 = '<i class="fa mr6 fa-file-text-o" aria-hidden="true"></i>' . esc_html($count_label);
}
}
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '">';
if ($thumb !== '') {
echo '<div class="aksrt-term-card__cover" style="background-image:url(' . esc_url($thumb) . ')"></div>';
$card_args = [
'type' => $type,
'class' => 'mb10',
'img' => self::term_cover_url($term),
'alt' => $name . ($desc !== '' ? '-' . $desc : ''),
'link' => [
'url' => $link,
'target' => $target_blank ? '_blank' : '',
],
'text1' => $text1,
'text2' => $text2,
'text3' => $text3,
'lazy' => true,
'height_scale' => $height_scale,
'mask_opacity' => $mask_opacity,
];
if ($is_row) {
echo '<div class="' . esc_attr($row_class) . '">';
}
if (function_exists('aksrt_graphic_card')) {
echo aksrt_graphic_card($card_args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
echo '<div class="aksrt-term-card__cover is-empty"></div>';
echo '<a class="aksrt-term-card" href="' . esc_url($link) . '"><div class="aksrt-term-card__name">' . esc_html($name) . '</div></a>';
}
echo '<div class="aksrt-term-card__body">';
echo '<div class="aksrt-term-card__name">' . esc_html($term->name) . '</div>';
echo '<div class="aksrt-term-card__count">' . esc_html(sprintf(_n('%d 篇', '%d 篇', (int) $term->count, 'aksrt'), (int) $term->count)) . '</div>';
echo '</div></a>';
if ($is_row) {
echo '</div>';
}
}
if ($is_row) {
echo '</div>';
}
echo '</div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
if ($html === '') {
@@ -117,11 +203,9 @@ class WidgetTermCards extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/** @return array<int,int> */
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
$taxs = get_taxonomies(['public' => true], 'objects');
?>
@@ -143,8 +227,29 @@ class WidgetTermCards extends \WP_Widget {
<label><?php esc_html_e('数量', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="30" name="<?php echo esc_attr($this->get_field_name('number')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['number'] ?? 8))); ?>">
</label>
<label><?php esc_html_e('列数', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="4" name="<?php echo esc_attr($this->get_field_name('columns')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['columns'] ?? 2))); ?>">
<label><?php esc_html_e('PC 列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="6" name="<?php echo esc_attr($this->get_field_name('pc_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['pc_row'] ?? $instance['columns'] ?? 2))); ?>">
</label>
<label><?php esc_html_e('移动列', 'aksrt'); ?>
<input class="tiny-text" type="number" min="1" max="3" name="<?php echo esc_attr($this->get_field_name('m_row')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['m_row'] ?? 1))); ?>">
</label>
</p>
<p>
<label><?php esc_html_e('卡片样式', 'aksrt'); ?>
<select name="<?php echo esc_attr($this->get_field_name('type')); ?>">
<option value="style-1" <?php selected(($instance['type'] ?? 'style-1'), 'style-1'); ?>><?php esc_html_e('样式 1(左下标题)', 'aksrt'); ?></option>
<option value="style-2" <?php selected(($instance['type'] ?? ''), 'style-2'); ?>><?php esc_html_e('样式 2(居中)', 'aksrt'); ?></option>
<option value="style-3" <?php selected(($instance['type'] ?? ''), 'style-3'); ?>><?php esc_html_e('样式 3(左下多层)', 'aksrt'); ?></option>
<option value="style-4" <?php selected(($instance['type'] ?? ''), 'style-4'); ?>><?php esc_html_e('样式 4(图+下方文案)', 'aksrt'); ?></option>
</select>
</label>
</p>
<p>
<label><?php esc_html_e('高度占比 %', 'aksrt'); ?>
<input class="tiny-text" type="number" min="30" max="120" name="<?php echo esc_attr($this->get_field_name('height_scale')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['height_scale'] ?? 60))); ?>">
</label>
<label><?php esc_html_e('遮罩 0-80', 'aksrt'); ?>
<input class="tiny-text" type="number" min="0" max="80" name="<?php echo esc_attr($this->get_field_name('mask_opacity')); ?>" value="<?php echo esc_attr((string) ((int) ($instance['mask_opacity'] ?? 10))); ?>">
</label>
</p>
<p>
@@ -162,20 +267,36 @@ class WidgetTermCards extends \WP_Widget {
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_empty')); ?>" value="1" <?php checked(!isset($instance['hide_empty']) || !empty($instance['hide_empty'])); ?>> <?php esc_html_e('隐藏空分类', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('target_blank')); ?>" value="1" <?php checked(!empty($instance['target_blank'])); ?>> <?php esc_html_e('新窗口打开', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$type = sanitize_key((string) ($new_instance['type'] ?? 'style-1'));
if (!in_array($type, ['style-1', 'style-2', 'style-3', 'style-4'], true)) {
$type = 'style-1';
}
return [
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'columns' => max(1, min(4, (int) ($new_instance['columns'] ?? 2))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'title' => sanitize_text_field((string) ($new_instance['title'] ?? '')),
'taxonomy' => sanitize_key((string) ($new_instance['taxonomy'] ?? 'category')),
'number' => max(1, min(30, (int) ($new_instance['number'] ?? 8))),
'pc_row' => max(1, min(6, (int) ($new_instance['pc_row'] ?? 2))),
'm_row' => max(1, min(3, (int) ($new_instance['m_row'] ?? 1))),
'columns' => max(1, min(6, (int) ($new_instance['pc_row'] ?? $new_instance['columns'] ?? 2))),
'type' => $type,
'height_scale' => max(30, min(120, (int) ($new_instance['height_scale'] ?? 60))),
'mask_opacity' => max(0, min(80, (int) ($new_instance['mask_opacity'] ?? 10))),
'orderby' => in_array(($new_instance['orderby'] ?? ''), ['count', 'name', 'id'], true) ? $new_instance['orderby'] : 'count',
'include' => sanitize_text_field((string) ($new_instance['include'] ?? '')),
'hide_empty' => !empty($new_instance['hide_empty']) ? 1 : 0,
'target_blank' => !empty($new_instance['target_blank']) ? 1 : 0,
];
}
}
+46 -26
View File
@@ -1,6 +1,6 @@
<?php
/**
* Registered user list (zibll widget_ui_user_lists + remote HTML).
* Registered user list (zibll widget_ui_user_lists → zib_author_card).
*
* @package AKSRT
*/
@@ -16,7 +16,7 @@ class WidgetUserLists extends \WP_Widget {
'aksrt_user_lists',
esc_html__('AKSRT 用户列表', 'aksrt'),
[
'description' => esc_html__('展示站点用户头像网格,支持排序与筛选。', 'aksrt'),
'description' => esc_html__('展示站点用户迷你名片(头像/身份/关注/简介)。', 'aksrt'),
'classname' => 'widget_aksrt_user_lists',
]
);
@@ -37,13 +37,17 @@ class WidgetUserLists extends \WP_Widget {
return array_values(array_unique($ids));
}
/**
* @param array<string,mixed> $instance
*/
public static function render_html(array $instance): string {
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$number = max(1, min(50, (int) ($instance['number'] ?? 8)));
$orderby = sanitize_key((string) ($instance['orderby'] ?? 'user_registered'));
$order = strtoupper((string) ($instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$include = self::parse_ids_static((string) ($instance['include'] ?? ''));
$exclude = self::parse_ids_static((string) ($instance['exclude'] ?? ''));
$hide_box = !empty($instance['hide_box']);
$blur_bg = !empty($instance['blur_bg']);
$query = [
'number' => $number,
@@ -102,26 +106,36 @@ class WidgetUserLists extends \WP_Widget {
}
ob_start();
$wrap = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists ajax-item';
echo '<div class="' . esc_attr($wrap) . '">';
// zibll: optional zib-widget + text-center user_lists of author-minicard.
$outer = $hide_box ? 'aksrt-user-lists is-bare ajax-item' : 'aksrt-user-lists zib-widget ajax-item';
echo '<div class="' . esc_attr($outer) . '">';
echo '<div class="text-center user_lists">';
foreach ($users as $user) {
$uid = (int) $user->ID;
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($uid, ['size' => 96])
: get_avatar_url($uid, ['size' => 96]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
$name = $user->display_name ?: $user->user_nicename;
echo '<a class="aksrt-user-lists__item" href="' . esc_url($home) . '" title="' . esc_attr($name) . '">';
echo '<img class="aksrt-user-lists__avatar" src="' . esc_url($avatar) . '" alt="' . esc_attr($name) . '" loading="lazy" width="48" height="48" />';
echo '<span class="aksrt-user-lists__name">' . esc_html($name) . '</span>';
echo '</a>';
if (function_exists('aksrt_author_minicard')) {
echo aksrt_author_minicard($uid, 'mb10', $blur_bg); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
$avatar = function_exists('aksrt_get_avatar_url')
? aksrt_get_avatar_url($uid, ['size' => 96])
: get_avatar_url($uid, ['size' => 96]);
$home = function_exists('aksrt_get_user_home_url')
? aksrt_get_user_home_url($uid)
: get_author_posts_url($uid);
$name = $user->display_name ?: $user->user_nicename;
echo '<a class="author-minicard radius8" href="' . esc_url($home) . '">';
echo '<img class="avatar" src="' . esc_url($avatar) . '" alt="" loading="lazy" width="42" height="42" /> ';
echo esc_html($name);
echo '</a>';
}
}
echo '</div>';
echo '</div></div>';
return (string) ob_get_clean();
}
/**
* @param array<string,mixed> $args
* @param array<string,mixed> $instance
*/
public function widget($args, $instance): void {
$html = self::render_html($instance);
echo $args['before_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -136,10 +150,9 @@ class WidgetUserLists extends \WP_Widget {
echo $args['after_widget']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
private function parse_ids(string $raw): array {
return self::parse_ids_static($raw);
}
/**
* @param array<string,mixed> $instance
*/
public function form($instance): void {
?>
<p>
@@ -182,11 +195,17 @@ class WidgetUserLists extends \WP_Widget {
</label>
</p>
<p>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('hide_box')); ?>" value="1" <?php checked(!empty($instance['hide_box'])); ?>> <?php esc_html_e('不显示背景盒子', 'aksrt'); ?></label><br>
<label><input type="checkbox" name="<?php echo esc_attr($this->get_field_name('blur_bg')); ?>" value="1" <?php checked(!empty($instance['blur_bg'])); ?>> <?php esc_html_e('头像模糊背景', 'aksrt'); ?></label>
</p>
<?php
}
/**
* @param array<string,mixed> $new_instance
* @param array<string,mixed> $old_instance
* @return array<string,mixed>
*/
public function update($new_instance, $old_instance): array {
$orderby = sanitize_key((string) ($new_instance['orderby'] ?? 'user_registered'));
$allowed = ['user_registered', 'display_name', 'post_count', 'last_login', 'followed-user-count'];
@@ -201,6 +220,7 @@ class WidgetUserLists extends \WP_Widget {
'orderby' => $orderby,
'order' => strtoupper((string) ($new_instance['order'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC',
'hide_box' => !empty($new_instance['hide_box']) ? 1 : 0,
'blur_bg' => !empty($new_instance['blur_bg']) ? 1 : 0,
];
}
}