diff --git a/assets/css/frontend/single-post/author-actions.css b/assets/css/frontend/single-post/author-actions.css index 3a3bd8d6..bc575c7b 100644 --- a/assets/css/frontend/single-post/author-actions.css +++ b/assets/css/frontend/single-post/author-actions.css @@ -646,17 +646,3 @@ html.dark .post-drop-meta__item:hover { .article-tags .but.c-green { color: #5cb85c; } .article-tags .but.c-purple { color: #9c27b0; } .article-tags .but.c-red { color: #f44336; } - - -/* ===== legacy user-card after-box ===== */ -.user-card.author { margin-bottom: 16px; overflow: hidden; } -.user-card.author .user-avatar { display: flex; justify-content: center; } -.user-card.author .user-avatar .avatar, -.user-card.author .user-avatar img { width: 80px; height: 80px; border-radius: 50%; object-fit: cover; } -.user-card.author .user-name { display: flex; flex-wrap: nowrap; align-items: center; justify-content: center; gap: 4px; } -.user-card.author .user-name .display-name { font-weight: 700; color: var(--key-color, inherit); } -.user-card.author .user-desc { text-align: center; } -.user-card.author .user-btns { text-align: center; } -.user-card.author .user-card-posts ul { list-style: none; margin: 0; padding: 0; } -.user-card.author .user-card-posts li { padding: 6px 0; border-bottom: 1px dashed var(--color-line, rgba(0,0,0,.06)); } -.user-card.author .user-card-posts li:last-child { border-bottom: 0; } diff --git a/assets/css/frontend/single-post/related-toc.css b/assets/css/frontend/single-post/related-toc.css index 98f82922..558b8708 100644 --- a/assets/css/frontend/single-post/related-toc.css +++ b/assets/css/frontend/single-post/related-toc.css @@ -385,61 +385,26 @@ html.dark .article-end-separator span { -/* ===== Related list (legacy-like) ===== */ -.relates .title-theme, -.related-box .title-theme { - font-size: 14px; - font-weight: 700; - margin: 0 0 8px; +/* ===== Zibll .zib-widget base (正文下方卡片堆栈依赖) ===== */ +.zib-widget { + clear: both; + background-color: var(--main-bg-color); + padding: 15px; + box-shadow: 0 0 10px var(--main-shadow); + border-radius: var(--main-radius); + margin-bottom: 20px; } -.related-list, -.relates ul.no-thumb { - list-style: none; - margin: 0; - padding: 4px 0 8px; -} -.related-list li, -.relates ul.no-thumb li { - padding: 8px 0; - border-bottom: 1px solid var(--color-line); -} -.related-list li:last-child, -.relates ul.no-thumb li:last-child { - border-bottom: none; -} -.related-list a, -.relates ul.no-thumb a { - color: var(--color-ink); - text-decoration: none; - font-size: 14px; -} -.related-list a:hover, -.relates ul.no-thumb a:hover { - color: var(--color-primary); +.zib-widget.nobottom.notop { + padding-top: 0; + padding-bottom: 0; } + +/* ===== Related (legacy .relates / .relates-thumb) ===== */ .post-nav-box.is-empty, .post-nav-box__item.is-empty { visibility: hidden; } - -/* ===== Related (legacy .relates / .relates-thumb) ===== */ -.relates .title-theme { margin-bottom: 0; } -.relates .aksrt-panel { padding: 12px 15px; } -.relates ul.no-thumb { list-style: none; margin: 0; padding: 0; } -.relates ul.no-thumb li { padding: 6px 0; border-bottom: 1px dashed var(--color-line, rgba(0,0,0,.06)); } -.relates ul.no-thumb li:last-child { border-bottom: 0; } -.relates ul.no-thumb a.icon-circle { color: inherit; text-decoration: none; } -.relates ul.no-thumb a.icon-circle:hover { color: var(--focus-color, #f04494); } -.relates-thumb .swiper-scroll { padding-bottom: 4px; } -.relates-thumb .swiper-slide { width: 200px; max-width: 70vw; } -.relates-thumb .graphic.style-3 { position: relative; overflow: hidden; border-radius: 8px; } -.relates-thumb .graphic.style-3 .graphic-img { position: relative; overflow: hidden; background: var(--surface-2, #f3f4f6); } -.relates-thumb .graphic.style-3 img.fit-cover { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; } -.relates-thumb .graphic.style-3 .graphic-text { padding: 10px 12px; width: 100%; background: linear-gradient(transparent, rgba(0,0,0,.55)); color: #fff; } -.relates-thumb .graphic.style-3 .text1 { font-size: 13px; font-weight: 600; color: #fff; } -.relates-thumb .graphic.style-3 .text3 { opacity: .9; margin-top: 4px; } - /* Colored tag pills on single */ .article-tags .but { display: inline-flex; align-items: center; gap: 4px; margin: 0 6px 6px 0; } .article-tags .but.ml6 { margin-left: 0; } diff --git a/inc/helpers/aksrt-avatar.php b/inc/helpers/aksrt-avatar.php index ce89327c..963f5c97 100644 --- a/inc/helpers/aksrt-avatar.php +++ b/inc/helpers/aksrt-avatar.php @@ -222,6 +222,43 @@ add_filter('get_avatar_data', static function (array $args, $subject): array { return $args; }, 20, 2); +if (!function_exists('aksrt_get_avatar_box')) { + /** + * legacy parity: 头像框(span.avatar-img + VIP 角标 + 可选主页链接)。 + * + * @param mixed $id_or_email 用户 ID / WP_User / 邮箱。 + * @param string $class 外层 span 类(avatar-img avatar-lg 等)。 + * @param bool $link 是否包一层用户主页链接。 + * @param bool $vip 是否叠加 VIP 角标图。 + */ + function aksrt_get_avatar_box($id_or_email, string $class = 'avatar-img', bool $link = true, bool $vip = true): string { + $user_id = aksrt_resolve_avatar_user_id($id_or_email); + $subject = $user_id > 0 ? $user_id : $id_or_email; + $img = aksrt_get_avatar_html($subject, 80, ['class' => 'avatar']); + + $badge = ''; + if ($vip && $user_id > 0 && function_exists('aksrt_get_user_vip_level') && aksrt_get_user_vip_level($user_id) > 0) { + $icon_url = ''; + if (class_exists('\AKSRT\Settings\Settings')) { + $vip_settings = \AKSRT\Settings\Settings::get_payment_vip_settings(); + $level = aksrt_get_user_vip_level($user_id); + $icon_url = esc_url((string) ($vip_settings['levels'][$level]['img_icon'] ?? '')); + } + if ($icon_url !== '') { + $badge_text = function_exists('aksrt_get_vip_badge_text') ? aksrt_get_vip_badge_text($user_id) : 'VIP'; + $badge = '' . esc_attr($badge_text) . ''; + } + } + + $html = '' . $img . $badge . ''; + if ($link && $user_id > 0) { + $home = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($user_id) : get_author_posts_url($user_id); + $html = '' . $html . ''; + } + return $html; + } +} + add_action('show_user_profile', 'aksrt_render_admin_avatar_field'); add_action('edit_user_profile', 'aksrt_render_admin_avatar_field'); add_action('user_edit_form_tag', static function (): void { diff --git a/inc/helpers/aksrt-frontend-tags.php b/inc/helpers/aksrt-frontend-tags.php index ea842309..f8cd0e50 100644 --- a/inc/helpers/aksrt-frontend-tags.php +++ b/inc/helpers/aksrt-frontend-tags.php @@ -297,6 +297,32 @@ if (! function_exists('aksrt_format_number')) : return (string) number_format_i18n($num); } endif; +if (! function_exists('aksrt_cut_count')) : + /** + * legacy parity: 万级数字缩写(_cut_count)。 + */ + function aksrt_cut_count($number): string { + $number = (float) $number; + if ($number > 999999) { + return round($number / 10000, 0) . 'W+'; + } + if ($number > 9999) { + return round($number / 10000, 1) . 'W+'; + } + return (string) $number; + } +endif; +if (! function_exists('aksrt_str_cut')) : + /** + * legacy parity: 按显示宽度截断标题(ASCII 记 1、CJK 记 2,zib_str_cut)。 + */ + function aksrt_str_cut(string $str, int $start = 0, int $width = 100, string $trimmarker = '...'): string { + if ($str === '' || $width <= 0) { + return $str; + } + return mb_strimwidth($str, max(0, $start), $width * 2, $trimmarker, 'UTF-8'); + } +endif; if (!function_exists('aksrt_render_mini_post_item')) : /** * Render a compact sidebar post item for homepage widgets. diff --git a/inc/helpers/aksrt-identity.php b/inc/helpers/aksrt-identity.php index 21d641ca..02623428 100644 --- a/inc/helpers/aksrt-identity.php +++ b/inc/helpers/aksrt-identity.php @@ -243,6 +243,80 @@ if (!function_exists('aksrt_get_user_name')) { } } +if (!function_exists('aksrt_get_user_posts_meta_sum')) { + /** + * 求和用户已发布文章的某个 meta(legacy get_user_posts_meta_sum parity)。 + * + * @param int $user_id 用户 ID。 + * @param string $meta_key 文章 meta 键。 + */ + function aksrt_get_user_posts_meta_sum(int $user_id, string $meta_key): int { + global $wpdb; + if ($user_id <= 0 || $meta_key === '') { + return 0; + } + $cache_group = 'aksrt_user_posts_' . $meta_key . '_sum'; + $cached = wp_cache_get($user_id, $cache_group); + if (false !== $cached) { + return (int) $cached; + } + $sum = $wpdb->get_var($wpdb->prepare( + "SELECT SUM(_m.meta_value + 0) FROM {$wpdb->posts} _p INNER JOIN {$wpdb->postmeta} _m ON _p.ID = _m.post_id" + . " WHERE _p.post_author = %d AND _m.meta_key = %s AND _p.post_status = 'publish'", + $user_id, + $meta_key + )); + $sum = $sum ? (int) $sum : 0; + wp_cache_set($user_id, $sum, $cache_group, 43200); + return $sum; + } +} + +if (!function_exists('aksrt_get_user_badges')) { + /** + * legacy parity: 作者资料卡统计徽章行(文章 / 评论 / 粉丝|点赞 / 人气值)。 + * + * @param int $user_id 用户 ID。 + */ + function aksrt_get_user_badges(int $user_id): string { + if ($user_id <= 0) { + return ''; + } + + $home = aksrt_get_user_home_url($user_id); + $post_n = aksrt_cut_count(count_user_posts($user_id, 'post', true)); + $com_n = aksrt_cut_count((int) get_comments([ + 'user_id' => $user_id, + 'status' => 'approve', + 'count' => true, + 'post_type' => ['post'], + ])); + $followed_n = class_exists('\AKSRT\User\Follow') + ? aksrt_cut_count((int) \AKSRT\User\Follow::get_instance()->get_follower_count($user_id)) + : 0; + $like_n = aksrt_cut_count(aksrt_get_user_posts_meta_sum($user_id, '_aksrt_like_count')); + $view_n = aksrt_cut_count(aksrt_get_user_posts_meta_sum($user_id, 'aksrt_post_views')); + + $html = ''; + $html .= '' . aksrt_renderer_svg_icon('post') . $post_n . ''; + $html .= '' . aksrt_renderer_svg_icon('comment') . $com_n . ''; + if ($followed_n) { + $html .= '' . $followed_n . ''; + } elseif ($like_n) { + $html .= '' . aksrt_renderer_svg_icon('like') . $like_n . ''; + } + $html .= '' . aksrt_renderer_svg_icon('hot') . $view_n . ''; + + /** + * Filter the author-card count badges row. + * + * @param string $html Badges HTML. + * @param int $user_id User ID. + */ + return (string) apply_filters('aksrt_user_count_badges', $html, $user_id); + } +} + if (!function_exists('aksrt_render_user_medal_entry')) { /** * Render the compact legacy-style badge overview entry. diff --git a/inc/helpers/aksrt-single-components.php b/inc/helpers/aksrt-single-components.php index 0e2598b2..ffa74da5 100644 --- a/inc/helpers/aksrt-single-components.php +++ b/inc/helpers/aksrt-single-components.php @@ -475,34 +475,49 @@ endif; // Register gate once helpers load. add_filter('the_content', 'aksrt_single_content_allow_view', 8); -if (!function_exists('aksrt_render_related_graphic_card')) : +if (!function_exists('aksrt_render_graphic_card_post')) : /** - * Minimal legacy style-3 related thumb card. + * legacy parity: style-3 图文卡(相关推荐 / 作者卡文章条共用)。 + * + * @param WP_Post $post 文章对象。 + * @param array $args { + * @type string $class .graphic 附加类(mb10 / em09)。 + * @type int $cut 悬停第二行标题截断宽度。 + * } */ - function aksrt_render_related_graphic_card(WP_Post $post): string { + function aksrt_render_graphic_card_post(WP_Post $post, array $args = []): string { + $args = wp_parse_args($args, [ + 'class' => 'mb10', + 'cut' => 45, + ]); $subtitle = class_exists('\AKSRT\Core\PostMetaBox') ? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post->ID, 'subtitle', '') : ''; $title = get_the_title($post) . $subtitle; - $url = get_permalink($post); $media = aksrt_renderer_resolve_images($post->ID, aksrt_renderer_config(['type' => 'card'])); - $thumb = (string) $media['primary']; - $time = human_time_diff((int) get_the_time('U', $post), (int) current_time('timestamp')); + $img = (string) ($media['primary'] ?? ''); + if ($img === '') { + $img = aksrt_get_lazy_thumb('lg'); + } + $time = aksrt_time_ago((string) get_the_time('Y-m-d H:i:s', $post)); $views = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post->ID) : 0; - $views_label = function_exists('aksrt_format_number') ? aksrt_format_number($views) : number_format_i18n($views); + $info = '' . esc_html($time) . '' + . '' . aksrt_renderer_svg_icon('view') . ' ' . esc_html(aksrt_cut_count($views)) . ''; - $img_attrs = aksrt_get_lazy_attr('lazy_cover', (string) $thumb, 'fit-cover', aksrt_get_lazy_thumb('lg')); - - $html = '
'; - $html .= '
'; - return $html; + return aksrt_graphic_card([ + 'type' => 'style-3', + 'class' => (string) $args['class'], + 'img' => $img, + 'alt' => $title, + 'link' => [ + 'url' => get_permalink($post), + 'target' => '', + ], + 'text1' => esc_html($title), + 'text2' => esc_html(aksrt_str_cut($title, 0, (int) $args['cut'], '...')), + 'text3' => $info, + 'lazy' => true, + 'height_scale' => 70, + ]); } endif; diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php index 95d41b3c..4171ef96 100644 --- a/inc/helpers/aksrt-single-render.php +++ b/inc/helpers/aksrt-single-render.php @@ -300,182 +300,55 @@ endif; if (!function_exists('aksrt_single_render_author_card')) : /** - * Compact author card used in the after-box stack. + * After-article author card (legacy zib_get_user_card_box parity: + * show_button=false / show_img_bg=false / class="author"). * * @param array $ctx Single context. */ function aksrt_single_render_author_card(array $ctx): void { - $author_id = (int) get_the_author_meta('ID'); - if ($author_id <= 0) { - return; - } - $author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id); - $author_name = get_the_author(); - $bio = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($author_id) : trim((string) get_the_author_meta('description')); - $name_html = function_exists('aksrt_get_user_name') - ? aksrt_get_user_name([ - 'id' => $author_id, - 'class' => 'user-name flex jc ac', - 'vip' => true, - 'medal' => true, - 'auth' => true, - 'level' => true, - 'follow' => false, - ]) - : ''; - $identity_chips = ($name_html === '' && function_exists('aksrt_render_user_identity_chips')) - ? aksrt_render_user_identity_chips($author_id, 'card') - : ''; - $avatar = function_exists('aksrt_get_avatar_html') - ? aksrt_get_avatar_html($author_id, 80, ['class' => 'avatar avatar-lg']) - : get_avatar($author_id, 80, '', esc_attr($author_name), ['class' => 'avatar avatar-lg']); - $page_s = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_post_page_settings') - ? \AKSRT\Settings\Settings::get_post_page_settings() - : []; - $card_limit = max(0, min(12, (int) ($page_s['author_card_limit'] ?? 6))); - $card_orderby = sanitize_key((string) ($page_s['author_card_orderby'] ?? 'views')); - $card_style = sanitize_key((string) ($page_s['author_card_style'] ?? 'card')); - $recent = []; - if ($card_limit > 0) { - $q = [ - 'author' => $author_id, - 'post_type' => 'post', - 'post_status' => 'publish', - 'posts_per_page' => $card_limit, - 'ignore_sticky_posts' => true, - 'no_found_rows' => true, - ]; - $cur = get_the_ID(); - if ($cur) { - $q['post__not_in'] = [(int) $cur]; - } - if ($card_orderby === 'views') { - $q['meta_query'] = [ - 'relation' => 'OR', - 'views_value' => [ - 'key' => 'aksrt_post_views', - 'compare' => 'EXISTS', - 'type' => 'NUMERIC', - ], - 'views_missing' => [ - 'key' => 'aksrt_post_views', - 'compare' => 'NOT EXISTS', - ], - ]; - $q['orderby'] = ['views_value' => 'DESC', 'date' => 'DESC']; - } elseif ($card_orderby === 'comment_count') { - $q['orderby'] = 'comment_count'; - $q['order'] = 'DESC'; - } elseif ($card_orderby === 'modified') { - $q['orderby'] = 'modified'; - $q['order'] = 'DESC'; - } else { - $q['orderby'] = 'date'; - $q['order'] = 'DESC'; - } - $recent = get_posts($q); - } - $is_self = is_user_logged_in() && get_current_user_id() === $author_id; - $login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url(get_permalink()); - $aksrt_post_settings = class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'get_post_page_settings') - ? \AKSRT\Settings\Settings::get_post_page_settings() - : []; - $rewards_on = !empty($aksrt_post_settings['rewards_enabled']) - || !empty($aksrt_post_settings['post_rewards_enabled']) - || !empty($aksrt_post_settings['post_rewards_s']); - ?> -
-
-
-
- -
- - - -
-
-
- $author_id, + 'class' => 'flex1 flex ac', + 'vip' => false, + 'medal' => true, + 'auth' => true, + 'level' => true, + 'follow' => true, + ]); + $badges = aksrt_get_user_badges($author_id); + $desc = aksrt_get_user_description($author_id); + $card_style = sanitize_key((string) ($settings['author_card_style'] ?? 'card')) === 'card' ? 'card' : 'mini'; + $posts_html = aksrt_render_user_card_posts([ + 'user_id' => $author_id, + 'limit' => max(0, min(12, (int) ($settings['author_card_limit'] ?? 6))), + 'orderby' => sanitize_key((string) ($settings['author_card_orderby'] ?? 'views')), + 'post_type' => 'post', + 'post_style' => $card_style, + 'exclude' => (int) get_the_ID(), + ]); + ?> +
+
+
+
+ +
+ +
+
+ $settings Single display settings. @@ -548,36 +422,25 @@ if (!function_exists('aksrt_single_related_query_args')) : } $args = [ - 'post_type' => get_post_type($post_id) ?: 'post', - 'post_status' => 'publish', - 'post__not_in' => [$post_id], - 'posts_per_page' => $count, + 'showposts' => $count, 'ignore_sticky_posts' => true, - 'no_found_rows' => true, + 'post_type' => 'post', + 'post_status' => 'publish', 'order' => 'DESC', + 'no_found_rows' => true, ]; + // legacy: all three term clauses are always present (even when empty). $tax_query = ['relation' => 'OR']; - $cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']); - if (!empty($cat_ids) && !is_wp_error($cat_ids)) { + foreach (['category', 'topics', 'post_tag'] as $taxonomy) { + $term_ids = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'ids']); $tax_query[] = [ - 'taxonomy' => 'category', + 'taxonomy' => $taxonomy, 'field' => 'term_id', - 'terms' => array_map('intval', $cat_ids), + 'terms' => array_map('intval', (array) $term_ids), ]; } - $tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']); - if (!empty($tag_ids) && !is_wp_error($tag_ids)) { - $tax_query[] = [ - 'taxonomy' => 'post_tag', - 'field' => 'term_id', - 'terms' => array_map('intval', $tag_ids), - ]; - } - - if (count($tax_query) > 1) { - $args['tax_query'] = $tax_query; - } + $args['tax_query'] = $tax_query; switch ($orderby) { case 'rand': @@ -591,32 +454,19 @@ if (!function_exists('aksrt_single_related_query_args')) : break; case 'like': $args['meta_key'] = '_aksrt_like_count'; - $args['orderby'] = ['meta_value_num' => 'DESC', 'date' => 'DESC']; + $args['orderby'] = 'meta_value_num'; break; case 'favorite': $args['meta_key'] = '_aksrt_favorite_count'; - $args['orderby'] = ['meta_value_num' => 'DESC', 'date' => 'DESC']; + $args['orderby'] = 'meta_value_num'; break; case 'comment_count': $args['orderby'] = 'comment_count'; break; case 'views': - $args['meta_query'] = [ - 'relation' => 'OR', - 'views_value' => [ - 'key' => 'aksrt_post_views', - 'compare' => 'EXISTS', - 'type' => 'NUMERIC', - ], - 'views_missing' => [ - 'key' => 'aksrt_post_views', - 'compare' => 'NOT EXISTS', - ], - ]; - $args['orderby'] = ['views_value' => 'DESC', 'date' => 'DESC']; - break; default: - $args['orderby'] = 'modified'; + $args['meta_key'] = 'aksrt_post_views'; + $args['orderby'] = 'meta_value_num'; break; } @@ -626,7 +476,7 @@ endif; if (!function_exists('aksrt_single_render_related')) : /** - * Related posts — legacy-like list + optional shared mini renderer. + * Related posts — legacy zib_posts_related parity (img/list/text). * * @param array $ctx Single context. */ @@ -646,15 +496,7 @@ if (!function_exists('aksrt_single_render_related')) : $style = 'img'; } - $args = function_exists('aksrt_single_related_query_args') - ? aksrt_single_related_query_args($post_id, $settings) - : [ - 'post_type' => get_post_type($post_id) ?: 'post', - 'post_status' => 'publish', - 'post__not_in' => [$post_id], - 'posts_per_page' => max(1, min(12, (int) ($settings['related_count'] ?? 6))), - 'ignore_sticky_posts' => true, - ]; + $args = aksrt_single_related_query_args($post_id, $settings); $query = new WP_Query($args); $wrap_class = 'theme-box relates' . ($style === 'img' ? ' relates-thumb' : ''); @@ -674,8 +516,8 @@ if (!function_exists('aksrt_single_render_related')) : if ($data) { $posts_html .= aksrt_renderer_mini($data, $mini_cfg); } - } elseif ($style === 'img' && function_exists('aksrt_render_related_graphic_card')) { - $posts_html .= aksrt_render_related_graphic_card($post); + } elseif ($style === 'img') { + $posts_html .= '
' . aksrt_render_graphic_card_post($post) . '
'; } else { $subtitle = class_exists('\AKSRT\Core\PostMetaBox') ? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post->ID, 'subtitle', '') @@ -692,7 +534,7 @@ if (!function_exists('aksrt_single_render_related')) :
-
+
diff --git a/inc/helpers/aksrt-user-card.php b/inc/helpers/aksrt-user-card.php index 52fd697a..2d36a2ae 100644 --- a/inc/helpers/aksrt-user-card.php +++ b/inc/helpers/aksrt-user-card.php @@ -100,41 +100,18 @@ if (!function_exists('aksrt_render_user_card_posts')) : $style = 'mini'; } $posts = aksrt_user_card_query_posts($args); - $show_empty = !empty($args['show_empty']); if (!$posts) { - if (!$show_empty) { - return ''; - } - return '
' . - esc_html__('暂无更多文章', 'aksrt') . - '
'; + return ''; } ob_start(); - echo '
'; if ($style === 'card') { echo ''; } else { @@ -146,7 +123,6 @@ if (!function_exists('aksrt_render_user_card_posts')) : } echo '
'; } - echo '
'; return (string) ob_get_clean(); } endif; diff --git a/inc/helpers/template-tags/search-meta.php b/inc/helpers/template-tags/search-meta.php index 87f40049..b3f3c996 100644 --- a/inc/helpers/template-tags/search-meta.php +++ b/inc/helpers/template-tags/search-meta.php @@ -470,22 +470,52 @@ endif; if (!function_exists('aksrt_time_ago')) : /** - * 将日期转为相对时间描述(中文) + * 将日期转为相对时间描述(中文,legacy zib_get_time_ago parity)。 * * @param string $date 日期字符串(MySQL datetime 格式) * @return string 例如 "19天前"、"3个月前" */ function aksrt_time_ago(string $date): string { - $diff = time() - strtotime($date); + $time = (int) strtotime($date); + $ctime = (int) strtotime(current_time('mysql')); + $t = $ctime - $time; - if ($diff < 0) return __('刚刚', 'aksrt'); - if ($diff < 60) return __('刚刚', 'aksrt'); - if ($diff < 3600) return sprintf(__('%d分钟前', 'aksrt'), (int) floor($diff / 60)); - if ($diff < 86400) return sprintf(__('%d小时前', 'aksrt'), (int) floor($diff / 3600)); - if ($diff < 2592000) return sprintf(__('%d天前', 'aksrt'), (int) floor($diff / 86400)); - if ($diff < 31536000) return sprintf(__('%d个月前', 'aksrt'), (int) floor($diff / 2592000)); - - return sprintf(__('%d年前', 'aksrt'), (int) floor($diff / 31536000)); + if ($t < 0) { + return date('Y-m-d H:i', $time); + } + $y = (int) date('Y', $ctime) - (int) date('Y', $time); + if (0 === $t) { + return __('刚刚', 'aksrt'); + } + if ($t < 60) { + return sprintf(__('%d秒前', 'aksrt'), $t); + } + if ($t < 3600) { + return sprintf(__('%d分钟前', 'aksrt'), (int) floor($t / 60)); + } + if ($t < 86400) { + return sprintf(__('%d小时前', 'aksrt'), (int) floor($t / 3600)); + } + if ($t < 2592000) { + if ($time > (int) strtotime(date('Ymd', (int) strtotime('-1 day')))) { + return __('昨天', 'aksrt'); + } + if ($time > (int) strtotime(date('Ymd', (int) strtotime('-2 days')))) { + return __('前天', 'aksrt'); + } + return sprintf(__('%d天前', 'aksrt'), (int) floor($t / 86400)); + } + if ($t < 31536000 && 0 === $y) { + $m = (int) date('m', $ctime) - (int) date('m', $time) - 1; + if (0 === $m) { + return sprintf(__('%d天前', 'aksrt'), (int) floor($t / 86400)); + } + return sprintf(__('%d个月前', 'aksrt'), $m); + } + if ($t < 31536000 && $y > 0) { + return sprintf(__('%d个月前', 'aksrt'), 12 - (int) date('m', $time) + (int) date('m', $ctime)); + } + return sprintf(__('%d年前', 'aksrt'), (int) date('Y', $ctime) - (int) date('Y', $time)); } endif; diff --git a/src/WidgetSystem/Renderers/UserRenderer.php b/src/WidgetSystem/Renderers/UserRenderer.php index 44d47b3b..5a438569 100644 --- a/src/WidgetSystem/Renderers/UserRenderer.php +++ b/src/WidgetSystem/Renderers/UserRenderer.php @@ -106,7 +106,7 @@ final class UserRenderer { } $html .= '
'; if (!empty($i['show_posts']) && function_exists('aksrt_render_user_card_posts')) { - $html .= '
' . aksrt_render_user_card_posts(['user_id' => $user_id, 'count' => 4, 'post_style' => 'mini', 'show_empty' => false]) . '
'; + $html .= '
' . aksrt_render_user_card_posts(['user_id' => $user_id, 'limit' => 4, 'post_style' => 'mini']) . '
'; } return $html . ''; } diff --git a/templates/parts/resource-related.php b/templates/parts/resource-related.php index d11d5d4c..64fbf54d 100644 --- a/templates/parts/resource-related.php +++ b/templates/parts/resource-related.php @@ -51,7 +51,9 @@ if (!$query->have_posts()) { $query->the_post(); $post = get_post(); if ($post instanceof WP_Post) { - echo aksrt_render_related_graphic_card($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + echo '
' . + aksrt_render_graphic_card_post($post) . // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + '
'; } } wp_reset_postdata(); diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs index be153935..036e1f53 100644 --- a/tests/widget-system/post-visual-contract.test.cjs +++ b/tests/widget-system/post-visual-contract.test.cjs @@ -82,7 +82,8 @@ test('settings use legacy list modes, grouped options, and destructive related k for (const key of legacyKeys) { assert.equal(source.includes(key), false, `legacy article key remains: ${key}`); } - assert.doesNotMatch(source, /list_card_topics|is_topics|\btopics\b/); + // bare `topics` is legal again: the related-posts query mirrors the legacy taxonomy OR. + assert.doesNotMatch(source, /list_card_topics|is_topics/); }); test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', () => { @@ -226,12 +227,18 @@ test('related posts, author page and widgets use their dedicated renderers', () assert.match(related, /\['img', 'list', 'text'\]/); assert.match(related, /aksrt_renderer_mini\(\$data, \$mini_cfg\)/); - assert.match(related, /aksrt_render_related_graphic_card\(\$post\)/); + assert.match(related, /\
' \. aksrt_render_graphic_card_post\(\$post\)/); assert.match(related, /
  • \s+array_map\('intval', \(array\) \$term_ids\)/); + assert.match(related, /class=\"zib-widget\"/); + assert.doesNotMatch(related, /post_related_source/); + assert.doesNotMatch(related, /post__not_in/); + assert.match(related, /\$args\['meta_key'\]\s*=\s*'aksrt_post_views'/); assert.match(relatedCard, /aksrt_renderer_resolve_images/); + assert.match(relatedCard, /'text2'\s+=>\s+esc_html\(aksrt_str_cut\(\$title, 0, \(int\) \$args\['cut'\], '\.\.\.'\)\)/); assert.match(resourceRelated, /swiper-container swiper-scroll/); - assert.match(resourceRelated, /aksrt_render_related_graphic_card/); + assert.match(resourceRelated, /aksrt_render_graphic_card_post/); assert.doesNotMatch(resourceRelated, /aksrt_render_post_list/); assert.match(author, /aksrt_render_post_list/); assert.doesNotMatch(author, /'type'\s*=>\s*'list'/); @@ -245,6 +252,36 @@ test('related posts, author page and widgets use their dedicated renderers', () assert.match(widgets, /swiper-container swiper-scroll/); }); +test('after-box author card mirrors legacy user-card structure', () => { + const render = read('inc/helpers/aksrt-single-render.php'); + const identity = read('inc/helpers/aksrt-identity.php'); + const avatar = read('inc/helpers/aksrt-avatar.php'); + const userCard = read('inc/helpers/aksrt-user-card.php'); + const css = read('assets/css/frontend/single-post/related-toc.css'); + const authorActions = read('assets/css/frontend/single-post/author-actions.css'); + + assert.match(render, /avatar-img avatar-lg/); + assert.match(render, /'vip'\s+=>\s+false/); + assert.match(render, /'follow'\s+=>\s+true/); + assert.match(render, /
    /); + assert.match(render, /
    /); + assert.doesNotMatch(render, /user-btns/); + assert.doesNotMatch(render, /发布文章.*用户中心/s); + assert.match(identity, /tag-posts/); + assert.match(identity, /tag-comment/); + assert.match(identity, /tag-follow/); + assert.match(identity, /tag-like/); + assert.match(identity, /tag-view/); + assert.match(identity, /get_follower_count/); + assert.match(avatar, /class="avatar-badge"/); + assert.match(userCard, /swiper-container more-posts swiper-scroll/); + assert.match(userCard, /'class'\s+=>\s+'em09', 'cut'\s+=>\s+32/); + assert.doesNotMatch(userCard, /aksrt-more-post-card/); + assert.match(css, /^\.zib-widget \{/m); + assert.doesNotMatch(css, /relates-thumb \.graphic\.style-3 \.graphic-img/); + assert.doesNotMatch(authorActions, /\.user-card-author|\.user-card \.author|user-card-posts/); +}); + test('AJAX pagination can recover, caps auto loading and rehydrates appended media', () => { const php = read('inc/helpers/template-tags/pagination-permissions.php'); const homepage = read('inc/helpers/homepage/content.php');