Merge pull request 'feat(frontend): align single related/prevnext/maxheight with Zibll' (#63) from worktree-frontend-single-1to1 into main

Reviewed-on: CYL/wordpress-theme-aksrt#63
This commit is contained in:
2026-07-11 21:58:47 +08:00
8 changed files with 234 additions and 40 deletions
@@ -104,6 +104,8 @@ export const pageSchema = {
{ label: '随机', value: 'rand' },
{ label: '相关度', value: 'relevance' },
{ label: '时间', value: 'date' },
{ label: '浏览量', value: 'views' },
{ label: '更新时间', value: 'modified' },
], dependsOn: { key: 'post_related_enabled', value: true } },
{ key: 'post_related_title', label: '推荐标题', type: 'input', default: '相关推荐', dependsOn: { key: 'post_related_enabled', value: true } },
],
@@ -434,3 +434,41 @@ html.dark .article-end-separator span {
}
}
/* ===== Related list (Zibll-like) ===== */
.relates .title-theme,
.related-box .title-theme {
font-size: 14px;
font-weight: 700;
margin: 0 0 8px;
}
.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);
}
.post-nav-box.is-empty,
.post-nav-box__item.is-empty {
visibility: hidden;
}
+9 -5
View File
@@ -190,15 +190,19 @@
}
function initContentExpand() {
var defaultMaxHeight = 800;
document.querySelectorAll('.entry-content[data-nav]').forEach(function (content) {
if (content.dataset.aksrtExpandBound === '1') return;
content.dataset.aksrtExpandBound = '1';
var customMax = parseInt(content.dataset.maxHeight || '0', 10);
var limit = customMax > 0 ? customMax : defaultMaxHeight;
if (content.scrollHeight <= limit) return;
// Only expand when server enabled maxheight (data-maxheight > 0).
var customMax = parseInt(content.getAttribute('data-maxheight') || content.dataset.maxheight || '0', 10);
if (!(customMax > 0)) return;
var limit = customMax;
if (content.scrollHeight <= limit) {
content.classList.remove('limit-height');
content.style.maxHeight = '';
return;
}
content.classList.add('limit-height');
content.style.maxHeight = limit + 'px';
@@ -103,6 +103,8 @@ class AKSRT_Admin_Schema_Post_List {
['label' => '随机', 'value' => 'rand'],
['label' => '相关度', 'value' => 'relevance'],
['label' => '时间', 'value' => 'date'],
['label' => '浏览量', 'value' => 'views'],
['label' => '更新时间', 'value' => 'modified'],
]],
['key' => 'post_related_title', 'label' => '推荐标题', 'type' => 'input', 'default' => '相关推荐'],
]],
+1 -1
View File
@@ -903,7 +903,7 @@ class AKSRT_Settings {
$related_type = 'category';
}
$related_orderby = sanitize_key((string) ($settings['post_related_orderby'] ?? 'rand'));
if (!in_array($related_orderby, ['rand', 'relevance', 'date'], true)) {
if (!in_array($related_orderby, ['rand', 'relevance', 'date', 'views', 'modified'], true)) {
$related_orderby = 'rand';
}
@@ -221,7 +221,7 @@ class AKSRT_Settings_Post_Display {
'related_enabled' => !isset($settings['post_related_enabled']) || !empty($settings['post_related_enabled']),
'related_count' => max(1, min(20, (int) ($settings['post_related_count'] ?? 6))),
'related_type' => in_array(sanitize_key((string) ($settings['post_related_type'] ?? 'category')), ['category', 'tag', 'random'], true) ? sanitize_key((string) ($settings['post_related_type'] ?? 'category')) : 'category',
'related_orderby' => in_array(sanitize_key((string) ($settings['post_related_orderby'] ?? 'rand')), ['rand', 'relevance', 'date'], true) ? sanitize_key((string) ($settings['post_related_orderby'] ?? 'rand')) : 'rand',
'related_orderby' => in_array(sanitize_key((string) ($settings['post_related_orderby'] ?? 'rand')), ['rand', 'relevance', 'date', 'views', 'modified'], true) ? sanitize_key((string) ($settings['post_related_orderby'] ?? 'rand')) : 'rand',
'related_title' => sanitize_text_field((string) ($settings['post_related_title'] ?? '相关推荐')),
'like_enabled' => !isset($settings['post_like_enabled']) || !empty($settings['post_like_enabled']),
'favorite_enabled' => !isset($settings['post_favorite_enabled']) || !empty($settings['post_favorite_enabled']),
+7
View File
@@ -231,6 +231,13 @@ if (!function_exists('aksrt_resolve_single_post_display_settings')) :
$settings['related_count'] = max(1, min(20, (int) $meta['related_count']));
}
// Per-post maxheight override (Zibll article_maxheight_xz).
$meta_maxheight = (int) ($meta['maxheight'] ?? 0);
if ($meta_maxheight > 0) {
$settings['maxheight_enabled'] = true;
$settings['maxheight'] = $meta_maxheight;
}
$cover_mode = (string) ($meta['header_image_mode'] ?? '');
$cover_url = '';
$cover_type = 'none';
+170 -29
View File
@@ -288,7 +288,12 @@ if (!function_exists('aksrt_single_render_box_header')) :
<h1 class="article-title"><?php the_title(); ?></h1>
<?php endif; ?>
<?php if (!empty($settings['authordesc'])) : ?>
<?php
$show_author_bar = array_key_exists('authordesc', $settings)
? !empty($settings['authordesc'])
: !empty($settings['authordesc_s']);
if ($show_author_bar) :
?>
<?php
aksrt_single_render_author_bar([
'show_actions' => true,
@@ -480,79 +485,203 @@ endif;
if (!function_exists('aksrt_single_render_prevnext')) :
/**
* Previous / next navigation.
*
* @param array<string,mixed> $ctx Single context.
*/
function aksrt_single_render_prevnext(): void {
function aksrt_single_render_prevnext(array $ctx = []): void {
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
// Accept both canonical `prevnext` (Settings DTO) and legacy `prevnext_s`.
$enabled = array_key_exists('prevnext', $settings)
? !empty($settings['prevnext'])
: (!array_key_exists('prevnext_s', $settings) || !empty($settings['prevnext_s']));
if (!$enabled) {
return;
}
$prev = get_previous_post();
$next = get_next_post();
if (!$prev && !$next) {
return;
}
?>
<nav class="post-nav-box">
<nav class="post-nav-box theme-box box-body radius8 main-shadow">
<?php if ($prev) : ?>
<a class="post-nav-box__item post-nav-box__item--prev" href="<?php echo esc_url(get_permalink($prev)); ?>">
<span class="post-nav-box__label"><?php esc_html_e('上一篇', 'aksrt'); ?></span>
<span class="post-nav-box__title"><?php echo esc_html(get_the_title($prev)); ?></span>
</a>
<?php else : ?>
<span class="post-nav-box__item post-nav-box__item--prev is-empty" aria-hidden="true"></span>
<?php endif; ?>
<?php if ($next) : ?>
<a class="post-nav-box__item post-nav-box__item--next" href="<?php echo esc_url(get_permalink($next)); ?>">
<span class="post-nav-box__label"><?php esc_html_e('下一篇', 'aksrt'); ?></span>
<span class="post-nav-box__title"><?php echo esc_html(get_the_title($next)); ?></span>
</a>
<?php else : ?>
<span class="post-nav-box__item post-nav-box__item--next is-empty" aria-hidden="true"></span>
<?php endif; ?>
</nav>
<?php
}
endif;
if (!function_exists('aksrt_single_related_query_args')) :
/**
* Build related-posts WP_Query args (Zibll-like multi-tax OR + orderby).
*
* @param int $post_id Post ID.
* @param array<string,mixed> $settings Single display settings.
* @return array<string,mixed>
*/
function aksrt_single_related_query_args(int $post_id, array $settings): array {
$count = max(1, min(12, (int) ($settings['related_count'] ?? 6)));
$type = sanitize_key((string) ($settings['related_type'] ?? 'category'));
if (!in_array($type, ['category', 'tag', 'random'], true)) {
$type = 'category';
}
$orderby = sanitize_key((string) ($settings['related_orderby'] ?? 'views'));
if (!in_array($orderby, ['rand', 'relevance', 'date', 'views', 'modified'], true)) {
$orderby = 'views';
}
$args = [
'post_type' => get_post_type($post_id) ?: 'post',
'post_status' => 'publish',
'post__not_in' => [$post_id],
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'order' => 'DESC',
];
if ($type === 'random') {
$args['orderby'] = 'rand';
return $args;
}
$tax_query = ['relation' => 'OR'];
if ($type === 'tag') {
$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),
];
}
} else {
// category (default): OR across category + tags + topics like Zibll.
$cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']);
if (!empty($cat_ids) && !is_wp_error($cat_ids)) {
$tax_query[] = [
'taxonomy' => 'category',
'field' => 'term_id',
'terms' => array_map('intval', $cat_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 (taxonomy_exists('topics')) {
$topic_ids = wp_get_post_terms($post_id, 'topics', ['fields' => 'ids']);
if (!empty($topic_ids) && !is_wp_error($topic_ids)) {
$tax_query[] = [
'taxonomy' => 'topics',
'field' => 'term_id',
'terms' => array_map('intval', $topic_ids),
];
}
}
}
if (count($tax_query) > 1) {
$args['tax_query'] = $tax_query;
}
switch ($orderby) {
case 'rand':
$args['orderby'] = 'rand';
break;
case 'date':
$args['orderby'] = 'date';
break;
case 'modified':
$args['orderby'] = 'modified';
break;
case 'views':
$args['meta_key'] = 'aksrt_post_views';
$args['orderby'] = 'meta_value_num';
break;
case 'relevance':
default:
// Prefer date when no search relevance context.
$args['orderby'] = 'date';
break;
}
return $args;
}
endif;
if (!function_exists('aksrt_single_render_related')) :
/**
* Related posts via shared renderer.
* Related posts — Zibll-like list + optional shared mini renderer.
*
* @param array<string,mixed> $ctx Single context.
*/
function aksrt_single_render_related(array $ctx): void {
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
if (empty($settings['related_enabled']) || !function_exists('aksrt_render_post_list')) {
if (empty($settings['related_enabled'])) {
return;
}
$post_id = (int) $ctx['post_id'];
$count = max(1, min(12, (int) ($settings['related_count'] ?? 6)));
$title = (string) ($settings['related_title'] ?? '');
$args = [
if ($title === '') {
$title = __('相关文章', 'aksrt');
}
$args = function_exists('aksrt_single_related_query_args')
? aksrt_single_related_query_args($post_id, $settings)
: [
'post_type' => get_post_type($post_id),
'post_status' => 'publish',
'post__not_in' => [$post_id],
'posts_per_page' => $count,
'posts_per_page' => max(1, min(12, (int) ($settings['related_count'] ?? 6))),
'ignore_sticky_posts' => true,
];
if (($settings['related_type'] ?? 'category') === 'tag') {
$tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']);
if (!empty($tag_ids) && !is_wp_error($tag_ids)) {
$args['tag__in'] = $tag_ids;
}
} else {
$cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']);
if (!empty($cat_ids) && !is_wp_error($cat_ids)) {
$args['category__in'] = $cat_ids;
}
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
return;
}
?>
<div class="related-box theme-box box-body radius8 main-shadow">
<div class="related-box__header">
<h3 class="related-box__title"><?php echo esc_html($title !== '' ? $title : __('相关文章', 'aksrt')); ?></h3>
<div class="theme-box relates related-box">
<div class="box-body notop">
<div class="title-theme related-box__title"><?php echo esc_html($title); ?></div>
</div>
<div class="related-box__list">
<?php aksrt_render_post_list($query, ['context' => 'related', 'mode' => 'card', 'card_style' => 'standard']); ?>
<div class="zib-widget related-box__list">
<?php if (function_exists('aksrt_render_post_list')) : ?>
<?php aksrt_render_post_list($query, ['context' => 'related', 'mode' => 'mini', 'show_thumb' => true, 'show_meta' => true]); ?>
<?php else : ?>
<ul class="no-thumb related-list">
<?php
while ($query->have_posts()) :
$query->the_post();
?>
<li>
<a class="icon-circle" href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
</div>
</div>
<?php
@@ -590,10 +719,12 @@ if (!function_exists('aksrt_single_render_article')) :
<?php endif; ?>
<div class="theme-box wp-posts-content entry-content<?php echo $maxheight > 0 ? ' limit-height' : ''; ?>"
<?php echo $maxheight > 0 ? ' style="max-height:' . esc_attr((string) $maxheight) . 'px;"' : ''; ?>
data-nav="posts"
data-maxheight="<?php echo esc_attr((string) $maxheight); ?>"
data-aksrt-toc="<?php echo !empty($ctx['show_toc']) ? '1' : '0'; ?>"
data-aksrt-toc-depth="<?php echo esc_attr((string) ((int) ($settings['toc_depth'] ?? 3))); ?>">
data-aksrt-toc-depth="<?php echo esc_attr((string) ((int) ($settings['toc_depth'] ?? 3))); ?>"
<?php echo !empty($settings['lightbox']) ? ' data-aksrt-lightbox="1"' : ''; ?>>
<?php the_content(); ?>
<?php
wp_link_pages([
@@ -622,18 +753,28 @@ endif;
if (!function_exists('aksrt_single_render_after_box')) :
/**
* After-article stack: author card / prevnext / related.
* After-article stack: yiyan box / author card / prevnext / related.
*
* @param array<string,mixed> $ctx Single context.
*/
function aksrt_single_render_after_box(array $ctx): void {
$settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : [];
if (!empty($settings['authordesc'])) {
if (!empty($settings['yiyan_box'])) {
?>
<div class="yiyan-box main-bg theme-box text-center box-body radius8 main-shadow aksrt-yiyan" data-aksrt-yiyan data-type="hitokoto"></div>
<?php
}
// Accept both `authordesc` (Settings DTO) and legacy `authordesc_s`.
$show_author = array_key_exists('authordesc', $settings)
? !empty($settings['authordesc'])
: !empty($settings['authordesc_s']);
if ($show_author) {
aksrt_single_render_author_card($ctx);
}
aksrt_single_render_prevnext();
aksrt_single_render_prevnext($ctx);
aksrt_single_render_related($ctx);
}
endif;