feat(frontend): replace article system with Zibll 9.0

This commit is contained in:
2026-08-18 16:06:47 +08:00
parent 2881c69e9c
commit 0f2ac6c34a
69 changed files with 2603 additions and 4163 deletions
+4 -10
View File
@@ -160,22 +160,16 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_
<?php if (have_posts()) : ?>
<?php
$render_config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config([
$render_config = aksrt_resolve_post_list_render_config([
'context' => 'archive',
'overrides' => ['mode' => 'card'],
])
: ['context' => 'archive', 'mode' => 'card'];
'overrides' => ['type' => 'card'],
]);
if (function_exists('aksrt_render_post_list')) {
aksrt_render_post_list(null, array_merge($render_config, ['wrapper_class' => 'content-stream__list']));
}
if (function_exists('aksrt_render_pagination')) {
aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']));
aksrt_render_pagination(null, [
'query_context' => 'archive',
'render_config' => $render_config,
]);
}
?>
<?php else : ?>
<div class="no-results">
+33 -1
View File
@@ -359,6 +359,17 @@
</div>
</div>
<!-- Fieldset (single nested object) -->
<div v-else-if="schema.type === 'fieldset'" class="aksrt-field__fieldset">
<AksField
v-for="field in (schema.fields || [])"
:key="field.key"
:schema="field"
:model-value="fieldsetValue(field)"
@update:model-value="updateFieldsetValue(field.key, $event)"
/>
</div>
<!-- Repeater (array of objects) — inline expand (default) -->
<div v-else-if="schema.type === 'repeater' && schema.ui !== 'list-modal'" class="aksrt-field__repeater">
<div
@@ -529,7 +540,7 @@
* AksField — renders a single form field based on its schema definition.
*
* Supported types: switch, input/text, textarea, number, select, radio,
* checkbox, color, icon, media, slider, code, alert, repeater (inline|list-modal), custom (slot).
* checkbox, color, icon, media, slider, code, alert, fieldset, repeater (inline|list-modal), custom (slot).
*/
import { computed, ref, onMounted, watch } from 'vue'
import FaIcon from './FaIcon.vue'
@@ -553,6 +564,20 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue'])
function fieldsetValue(field) {
const current = props.modelValue && typeof props.modelValue === 'object' && !Array.isArray(props.modelValue)
? props.modelValue
: {}
return Object.prototype.hasOwnProperty.call(current, field.key) ? current[field.key] : field.default
}
function updateFieldsetValue(key, value) {
const current = props.modelValue && typeof props.modelValue === 'object' && !Array.isArray(props.modelValue)
? props.modelValue
: {}
emit('update:modelValue', { ...current, [key]: value })
}
/** Normalize options: support both string[] and {label, value}[] */
const normalizedOptions = computed(() => {
const opts = props.schema.options || []
@@ -974,6 +999,13 @@ function confirmRepeaterModal() {
}
/* Repeater */
.aksrt-field__fieldset {
padding: 14px;
border: 1px solid #eef1f6;
border-radius: 8px;
background: #fafbfc;
}
.aksrt-field__repeater {
display: flex;
flex-direction: column;
@@ -140,7 +140,7 @@
<n-card class="aksrt-section-card" style="margin-bottom:16px;">
<template #header><strong>列表显示</strong></template>
<n-alert type="info" :show-icon="false">
首页文章列表/卡片模式、样式变体与缩略图比例已统一到「文章&amp;列表 → 文章列表」设置(对齐 Zibll 全局 list_type / list_card_home)。此处不再单独维护 homepage_card_style / 默认视图。
首页文章列表/卡片模式、样式变体与缩略图比例统一由「文章&amp;列表 → 文章列表」中的 Zibll 全局字段控制。
</n-alert>
</n-card>
@@ -224,11 +224,7 @@ const form = reactive({
home_exclude_posts: [],
home_exclude_cats: [],
home_lists: [],
homepage_card_style: 'default',
homepage_multi_thumb_enabled: true,
homepage_multi_thumb_min_images: 3,
homepage_sidebar_mini_enabled: true,
homepage_default_view: 'list',
homepage_sort_ajax_enabled: true,
homepage_sticky_tabs_enabled: true,
home_list_sort_dropdown: false,
@@ -294,11 +290,7 @@ async function loadSettings() {
form.home_exclude_posts = Array.isArray(d.home_exclude_posts) ? d.home_exclude_posts : []
form.home_exclude_cats = Array.isArray(d.home_exclude_cats) ? d.home_exclude_cats : []
form.home_lists = Array.isArray(d.home_lists) ? d.home_lists : []
form.homepage_card_style = d.homepage_card_style || 'default'
form.homepage_multi_thumb_enabled = d.homepage_multi_thumb_enabled !== false
form.homepage_multi_thumb_min_images = d.homepage_multi_thumb_min_images || 3
form.homepage_sidebar_mini_enabled = d.homepage_sidebar_mini_enabled !== false
form.homepage_default_view = d.homepage_default_view || 'list'
form.homepage_sort_ajax_enabled = d.homepage_sort_ajax_enabled !== false
form.homepage_sticky_tabs_enabled = d.homepage_sticky_tabs_enabled !== false
form.home_list_sort_dropdown = !!d.home_list_sort_dropdown
@@ -929,15 +929,11 @@ export const pageDisplaySchemas = {
key: 'page_display.homepage',
title: '首页配置',
icon: 'fas fa-home',
description: '配置首页主列表、分类 Tab、卡片样式与首页交互。实际编辑界面在 HomepageSettings.vue(customPage),此处补齐 page_display 的 schema 契约。',
description: '配置首页主列表、分类 Tab 与首页交互。文章列表视觉统一由文章列表设置控制。',
defaults: {
homepage_tabs: [],
homepage_card_style: 'default',
homepage_multi_thumb_enabled: false,
homepage_multi_thumb_min_images: 3,
homepage_sidebar_mini_enabled: true,
homepage_default_sort: 'date',
homepage_default_view: 'list',
homepage_sort_ajax_enabled: true,
homepage_sticky_tabs_enabled: true,
home_posts_list_s: true,
@@ -986,34 +982,9 @@ export const pageDisplaySchemas = {
},
{
key: 'cards-sidebar',
title: '卡片与侧栏',
title: '侧栏与交互',
icon: 'fas fa-columns',
fields: [
{
key: 'homepage_card_style',
label: '卡片风格(已弃用)',
type: 'radio',
default: 'default',
options: [
{ label: '默认', value: 'default' },
{ label: '风格 2', value: 'style2' },
{ label: '风格 3', value: 'style3' },
],
help: '已弃用:请改用「文章列表」中的列表/卡片样式设置。',
},
{ key: 'homepage_multi_thumb_enabled', label: '多图卡片(已弃用)', type: 'switch', default: true, help: '请改用文章列表缩略图/多图设置。' },
{ key: 'homepage_multi_thumb_min_images', label: '最少图片数(已弃用)', type: 'number', default: 3, min: 2, max: 4 },
{
key: 'homepage_default_view',
label: '默认视图(已弃用)',
type: 'radio',
default: 'list',
options: [
{ label: '列表', value: 'list' },
{ label: '卡片', value: 'card' },
],
help: 'SSR 以文章列表全局设置为准;此字段仅作兼容保留。',
},
{ key: 'homepage_sidebar_mini_enabled', label: '迷你侧栏列表', type: 'switch', default: true },
{ key: 'homepage_sort_ajax_enabled', label: '排序栏 AJAX', type: 'switch', default: true },
],
@@ -1,237 +1,104 @@
/**
* Post list — list section (Zibll-aligned admin contract).
* Keys match PHP AKSRT_Admin_Schema_Post_List_List / Zibll option IDs.
*/
/** Zibll 9.0 article-list settings. */
export const listSchema = {
key: 'post_list.list',
title: '文章列表',
icon: 'fas fa-list',
description: '对齐 Zibll 文章列表:默认排序、置顶范围、翻页模式(数字/AJAX/自动加载)与列表显示部件。',
description: '文章列表模式、UI、标签和翻页行为。',
defaults: {
list_orderby: 'modified',
post_time_source: 'modified',
sticky_s: ['home', 'cat'],
target_blank: false,
item_heading_bold: false,
post_list_author: true,
post_list_time: true,
list_meta_show: 'like',
list_badge_show: ['pay', 'tag', 'cat'],
list_badge_show: ['pay', 'cat', 'tag'],
list_show_type: 'separate',
list_type: 'thumb',
list_card_home: false,
list_card_tag: false,
list_card_author: false,
list_card_cat: [],
list_list_option: { style: 'null', img_position: 'left', scale: 70 },
list_card_option: { style: 'null', scale: 70 },
mult_thumb_cat: [],
paging_ajax_s: '1',
paging_ajax_ias_s: true,
ias_max: 3,
ajax_trigger: '加载更多',
ajax_nomore: '没有更多了',
post_card_style: 'thumb',
post_excerpt_length: 120,
post_list_per_page: 12,
list_show_type: 'separate',
list_list_style: '',
list_list_img_position: 'left',
list_list_scale: 70,
list_card_style: '',
list_card_scale: 70,
list_card_home: false,
list_card_cat: [],
list_card_tag: false,
list_card_author: false,
ajax_trigger: '<i class="fa fa-angle-right"></i>加载更多',
ajax_nomore: '没有更多内容了',
},
groups: [
{
key: 'order',
title: '默认排序与置顶',
icon: 'fas fa-sort',
fields: [
{
key: 'list_orderby',
label: '默认排序方式',
type: 'radio',
default: 'modified',
options: [
{ label: '发布时间', value: 'date' },
{ label: '更新时间', value: 'modified' },
{ label: 'WP默认', value: 'default' },
],
help: '文章列表全局默认排序(Zibll list_orderby)',
},
{
key: 'sticky_s',
label: '显示置顶文章',
type: 'checkbox',
default: ['home', 'cat'],
options: [
{ label: '首页', value: 'home' },
{ label: '分类页', value: 'cat' },
{ label: '标签页', value: 'tag' },
],
},
key: 'order', title: '默认排序与置顶', icon: 'fas fa-sort', fields: [
{ key: 'list_orderby', label: '默认排序方式', type: 'radio', default: 'modified', options: [
{ label: '发布时间', value: 'date' }, { label: '更新时间', value: 'modified' }, { label: 'WP默认', value: 'default' },
] },
{ key: 'post_time_source', label: '时间显示数据源', type: 'radio', default: 'modified', options: [
{ label: '发布时间', value: 'date' }, { label: '更新时间', value: 'modified' },
] },
{ key: 'sticky_s', label: '显示置顶文章', type: 'checkbox', default: ['home', 'cat'], options: [
{ label: '首页', value: 'home' }, { label: '分类页', value: 'cat' }, { label: '标签页', value: 'tag' },
] },
],
},
{
key: 'display',
title: '列表显示',
icon: 'fas fa-eye',
fields: [
key: 'display', title: '列表显示', icon: 'fas fa-eye', fields: [
{ key: 'target_blank', label: '新窗口打开文章', type: 'switch', default: false },
{ key: 'item_heading_bold', label: '列表标题粗体显示', type: 'switch', default: false },
{ key: 'post_list_author', label: '显示文章作者', type: 'switch', default: true },
{
key: 'post_list_time',
label: '显示文章时间',
type: 'switch',
default: true,
help: '建议作者与时间至少开启一项',
},
{
key: 'list_meta_show',
label: '列表小部件(移动端优先)',
type: 'radio',
default: 'like',
options: [
{ label: '阅读量', value: 'view' },
{ label: '点赞数', value: 'like' },
{ label: '评论', value: 'comm' },
{ key: 'post_list_time', label: '显示文章时间', type: 'switch', default: true },
{ key: 'list_meta_show', label: '列表小部件(移动端优先)', type: 'radio', default: 'like', options: [
{ label: '阅读量', value: 'view' }, { label: '点赞数', value: 'like' }, { label: '评论', value: 'comm' },
] },
{ key: 'list_badge_show', label: '显示列表标签', type: 'checkbox', default: ['pay', 'cat', 'tag'], options: [
{ label: '付费信息', value: 'pay' }, { label: '分类', value: 'cat' }, { label: '标签', value: 'tag' },
] },
],
},
{
key: 'list_badge_show',
label: '列表徽章',
type: 'checkbox',
default: ['pay', 'tag', 'cat'],
options: [
{ label: '付费', value: 'pay' },
{ label: '分类', value: 'cat' },
{ label: '标签', value: 'tag' },
],
},
],
},
{
key: 'paging',
title: '列表翻页',
icon: 'fas fa-ellipsis-h',
fields: [
{
key: 'paging_ajax_s',
label: '列表翻页模式',
type: 'radio',
default: '1',
options: [
{ label: 'AJAX 追加列表翻页', value: '1' },
{ label: '数字翻页按钮', value: '0' },
],
help: '对齐 Zibll paging_ajax_s',
},
{
key: 'paging_ajax_ias_s',
label: 'AJAX 自动加载',
type: 'switch',
default: true,
dependsOn: { key: 'paging_ajax_s', value: '1' },
help: '滚动到列表尽头时自动加载下一页',
},
{
key: 'ias_max',
label: '自动加载页数',
type: 'number',
default: 3,
min: 0,
max: 10,
dependsOn: { key: 'paging_ajax_s', value: '1' },
help: '0 表示不限制',
},
{
key: 'ajax_trigger',
label: '加载更多文案',
type: 'input',
default: '加载更多',
dependsOn: { key: 'paging_ajax_s', value: '1' },
},
{
key: 'ajax_nomore',
label: '没有更多文案',
type: 'input',
default: '没有更多了',
dependsOn: { key: 'paging_ajax_s', value: '1' },
},
],
},
{
key: 'card',
title: '卡片与布局',
icon: 'fas fa-th-large',
fields: [
{
key: 'post_card_style',
label: '默认列表模式',
type: 'select',
default: 'thumb',
options: [
{ label: '列表图文模式', value: 'thumb' },
key: 'layout', title: '列表模式与 UI', icon: 'fas fa-th-large', fields: [
{ key: 'list_show_type', label: '列表样式', type: 'radio', default: 'separate', options: [
{ label: '分离', value: 'separate' }, { label: '无间距', value: 'no_margin' },
] },
{ key: 'list_type', label: '默认列表模式', type: 'radio', default: 'thumb', options: [
{ label: '列表文字模式', value: 'text' },
{ label: '列表自动图文', value: 'thumb_if_has' },
{ label: '列表图文模式', value: 'thumb' },
{ label: '列表自动图文模式', value: 'thumb_if_has' },
{ label: '卡片模式', value: 'card' },
{ label: '紧凑列表', value: 'compact' },
{ label: '大图列表', value: 'large_thumb' },
{ label: '多图列表', value: 'multi_thumb' },
],
help: '对齐 Zibll list_type:thumb/text/thumb_if_has 为列表,card 为卡片网格。首页可用下方「首页卡片模式」单独强制卡片。',
},
{ key: 'post_excerpt_length', label: '摘要长度', type: 'number', default: 120, min: 20, max: 500 },
{ key: 'post_list_per_page', label: '每页文章数', type: 'number', default: 12, min: 4, max: 48 },
{
key: 'list_show_type',
label: '列表间距样式',
type: 'radio',
default: 'separate',
options: [
{ label: '分隔卡片', value: 'separate' },
{ label: '无间距', value: 'no_margin' },
], help: '文字、自动图文和多图模式仅在开启侧栏的页面有效。' },
{ key: 'list_card_home', label: '首页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_tag', label: '标签页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_author', label: '用户页列表卡片模式', type: 'switch', default: false, dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_card_cat', label: '分类页自定义卡片模式', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], dependsOn: { key: 'list_type', not: true, value: 'card' } },
{ key: 'list_list_option', label: '列表模式 UI 配置', type: 'fieldset', default: { style: 'null', img_position: 'left', scale: 70 }, fields: [
{ key: 'style', label: '列表样式', type: 'radio', default: 'null', options: [
{ label: '默认', value: 'null' }, { label: '样式 2', value: 'style2' },
] },
{ key: 'img_position', label: '缩略图位置', type: 'radio', default: 'left', options: [
{ label: '靠左', value: 'left' }, { label: '靠右', value: 'right' },
] },
{ key: 'scale', label: '缩略图长宽比例(%)', type: 'number', default: 70, min: 50, max: 200, step: 5 },
] },
{ key: 'list_card_option', label: '卡片模式 UI 配置', type: 'fieldset', default: { style: 'null', scale: 70 }, fields: [
{ key: 'style', label: '卡片样式', type: 'radio', default: 'null', options: [
{ label: '默认', value: 'null' }, { label: '样式 3', value: 'style3' },
] },
{ key: 'scale', label: '缩略图长宽比例(%)', type: 'number', default: 70, min: 20, max: 200, step: 5 },
] },
{ key: 'mult_thumb_cat', label: '列表多图显示分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '文章格式为图片或画廊时默认显示 4 张缩略图;卡片模式或无侧栏时无效。' },
],
},
{
key: 'list_list_style',
label: '列表样式变体',
type: 'radio',
default: '',
options: [
{ label: '默认', value: '' },
{ label: '样式2', value: 'style2' },
],
},
{
key: 'list_list_img_position',
label: '列表缩略图位置',
type: 'radio',
default: 'left',
options: [
{ label: '左侧', value: 'left' },
{ label: '右侧', value: 'right' },
],
},
{ key: 'list_list_scale', label: '列表缩略图比例 (%)', type: 'number', default: 70, min: 50, max: 200 },
{
key: 'list_card_style',
label: '卡片样式变体',
type: 'radio',
default: '',
options: [
{ label: '默认', value: '' },
{ label: '样式3', value: 'style3' },
],
},
{ key: 'list_card_scale', label: '卡片缩略图比例 (%)', type: 'number', default: 70, min: 20, max: 200 },
{ key: 'list_card_home', label: '首页卡片模式', type: 'switch', default: false, help: '当默认模式不为卡片时,单独将首页列表强制为卡片(Zibll list_card_home)', dependsOn: { key: 'post_card_style', operator: '!=', value: 'card' } },
{
key: 'list_card_cat',
label: '分类卡片模式',
type: 'taxonomy_select',
taxonomy: 'category',
multiple: true,
default: [],
},
{ key: 'list_card_tag', label: '标签页卡片模式', type: 'switch', default: false },
{ key: 'list_card_author', label: '用户页卡片模式', type: 'switch', default: false },
key: 'paging', title: '列表翻页', icon: 'fas fa-ellipsis-h', fields: [
{ key: 'paging_ajax_s', label: '列表翻页模式', type: 'radio', default: '1', options: [
{ label: 'AJAX 追加列表翻页', value: '1' }, { label: '数字翻页按钮', value: '0' },
] },
{ key: 'paging_ajax_ias_s', label: 'AJAX 自动加载', type: 'switch', default: true, dependsOn: { key: 'paging_ajax_s', value: '1' } },
{ key: 'ias_max', label: '自动加载页数', type: 'number', default: 3, min: 0, max: 10, dependsOn: { key: 'paging_ajax_s', value: '1' } },
{ key: 'ajax_trigger', label: '翻页按钮文字', type: 'textarea', default: '<i class="fa fa-angle-right"></i>加载更多' },
{ key: 'ajax_nomore', label: '列表全部加载完毕文案', type: 'textarea', default: '没有更多内容了' },
],
},
],
@@ -19,14 +19,11 @@ export const pageSchema = {
post_lightbox_full: true,
post_lightbox_down: false,
post_lightbox_play: false,
post_related_enabled: true,
post_related_count: 6,
// Display style (Zibll img/list/text). Legacy post_related_type may still hold source or style.
post_related_style: 'img',
post_related_source: 'category',
post_related_type: 'category',
post_related_s: true,
post_related_type: 'img',
related_title: '相关推荐',
post_related_n: 6,
post_related_orderby: 'views',
post_related_title: '相关推荐',
breadcrumbs_single_text: true,
post_copyright_enabled: true,
post_copyright_text: '',
@@ -116,26 +113,23 @@ export const pageSchema = {
title: '相关推荐',
icon: 'fas fa-random',
fields: [
{ key: 'post_related_enabled', label: '显示相关推荐', type: 'switch', default: true },
{ key: 'post_related_count', label: '推荐数量', type: 'number', default: 6, min: 1, max: 20, dependsOn: { key: 'post_related_enabled', value: true } },
{ key: 'post_related_style', label: '展示样式', type: 'radio', default: 'img', options: [
{ key: 'post_related_s', label: '显示相关推荐', type: 'switch', default: true },
{ key: 'post_related_type', label: '展示样式', type: 'radio', default: 'img', options: [
{ label: '图文卡片', value: 'img' },
{ label: '列表', value: 'list' },
{ label: '纯文字', value: 'text' },
], dependsOn: { key: 'post_related_enabled', value: true }, help: '对齐 Zibll 相关文章三种样式' },
{ key: 'post_related_source', label: '推荐来源', type: 'radio', default: 'category', options: [
{ label: '同分类/标签', value: 'category' },
{ label: '同标签', value: 'tag' },
{ label: '随机', value: 'random' },
], dependsOn: { key: 'post_related_enabled', value: true } },
], dependsOn: { key: 'post_related_s', value: true }, help: '对齐 Zibll 相关文章三种样式' },
{ key: 'related_title', label: '推荐标题', type: 'input', default: '相关推荐', dependsOn: { key: 'post_related_s', value: true } },
{ key: 'post_related_n', label: '推荐数量', type: 'number', default: 6, min: 4, max: 12, step: 2, dependsOn: { key: 'post_related_s', value: true } },
{ key: 'post_related_orderby', label: '推荐排序', type: 'select', default: 'views', options: [
{ label: '浏览量', value: 'views' },
{ label: '随机', value: 'rand' },
{ label: '相关度', value: 'relevance' },
{ label: '时间', value: 'date' },
{ 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 } },
{ label: '发布时间', value: 'date' },
{ label: '浏览量', value: 'views' },
{ label: '点赞量', value: 'like' },
{ label: '评论量', value: 'comment_count' },
{ label: '收藏数量', value: 'favorite' },
{ label: '随机', value: 'rand' },
], dependsOn: { key: 'post_related_s', value: true } },
],
},
{
@@ -1,22 +1,23 @@
/** Zibll 9.0 list-thumbnail settings. */
const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
: ''
const spareThumbnailDefault = [{ img: `${themeRoot}/assets/img/thumbnail.svg` }]
export const thumbSchema = {
key: 'post_list.thumb',
title: '列表缩略图',
icon: 'fas fa-images',
description: '配置缩略图来源优先级、尺寸、占位图和多图模式。',
description: '缩略图获取优先级:文章特色图像 > 外链特色图像 > 文章首图 > 分类封面图 > 备用缩略图 > 懒加载预载图。',
defaults: {
thumb_priority: 'featured,attachment,resource,custom',
thumb_fallback: 'placeholder',
thumb_spare_images: [],
thumb_aspect_ratio: '16:9',
list_thumb_fit: 'cover',
thumb_multi_enabled: false,
thumb_multi_count: 3,
list_thumb_slides_s: true,
list_thumb_video_s: true,
list_thumb_video_mute_s: true,
thumb_postfirstimg_s: true,
thumb_catimg_s: true,
mult_thumb_cat: [],
list_thumb_fit: '',
thumb_postfirstimg_size: 'medium',
spare_thumbnail: spareThumbnailDefault,
},
groups: [
{
@@ -24,44 +25,24 @@ export const thumbSchema = {
title: '图片来源',
icon: 'fas fa-image',
fields: [
{ key: 'thumb_priority', label: '图片优先级', type: 'input', default: 'featured,attachment,resource,custom', help: '逗号分隔: featured(特色图), attachment(附件), resource(资源封面), custom(自定义), post_first_img(文章首图), category_cover(分类封面)' },
{ key: 'thumb_fallback', label: '无图时处理', type: 'radio', default: 'placeholder', options: [
{ label: '显示占位图', value: 'placeholder' },
{ label: '隐藏缩略图', value: 'hide' },
{ label: '随机封面', value: 'random' },
{ key: 'list_thumb_slides_s', label: '幻灯片缩略图', type: 'switch', default: true, help: '允许设置幻灯片为列表缩略图' },
{ key: 'list_thumb_video_s', label: '视频缩略图', type: 'switch', default: true, help: '鼠标移动到列表上自动播放' },
{ key: 'list_thumb_video_mute_s', label: '视频缩略图静音播放', type: 'switch', default: true, dependsOn: { key: 'list_thumb_video_s', value: true }, help: '部分手机浏览器无法实现静音播放' },
{ key: 'thumb_postfirstimg_s', label: '自动获取文章首图', type: 'switch', default: true },
{ key: 'thumb_catimg_s', label: '自动获取分类封面', type: 'switch', default: true },
{ key: 'list_thumb_fit', label: '缩略图填充方式', type: 'radio', default: '', options: [
{ label: 'cover 铺满', value: '' },
{ label: 'contain 等比例缩放', value: 'contain' },
] },
{ key: 'thumb_spare_images', label: '备用缩略图', type: 'gallery', default: [], help: '当无图处理选择"随机封面"时从此图库随机选取', dependsOn: { key: 'thumb_fallback', value: 'random' } },
{ key: 'thumb_aspect_ratio', label: '图片比例', type: 'select', default: '16:9', options: [
{ label: '16:9 宽屏', value: '16:9' },
{ label: '4:3 标准', value: '4:3' },
{ label: '1:1 正方形', value: '1:1' },
{ label: '自适应', value: 'auto' },
{ key: 'thumb_postfirstimg_size', label: '缩略图大小', type: 'radio', default: 'medium', options: [
{ label: '小尺寸', value: 'thumbnail' },
{ label: '中尺寸', value: 'medium' },
{ label: '大尺寸', value: 'large' },
{ label: '原图', value: 'full' },
] },
{ key: 'list_thumb_fit', label: '缩略图填充方式', type: 'radio', default: 'cover', options: [
{ label: '填充裁剪', value: 'cover' },
{ label: '完整显示', value: 'contain' },
]},
{ key: 'list_thumb_slides_s', label: '幻灯片缩略图', type: 'switch', default: true, help: '允许幻灯片作为列表缩略图' },
{ key: 'list_thumb_video_s', label: '视频缩略图', type: 'switch', default: true, help: '鼠标悬停时自动播放视频缩略图' },
{ key: 'list_thumb_video_mute_s', label: '视频缩略图静音', type: 'switch', default: true, dependsOn: { key: 'list_thumb_video_s', value: true }, help: '视频缩略图播放时静音' },
{ key: 'thumb_postfirstimg_s', label: '自动获取文章首图', type: 'switch', default: true, help: '自动从文章内容中提取第一张图片作为缩略图' },
{ key: 'thumb_postfirstimg_size', label: '首图尺寸', type: 'select', default: 'medium', options: [
{ label: '缩略图', value: 'thumbnail' },
{ label: '中等', value: 'medium' },
{ label: '大', value: 'large' },
{ label: '原始', value: 'full' },
], dependsOn: { key: 'thumb_postfirstimg_s', value: true }, help: '自动获取文章首图时使用的 WP 图片尺寸' },
{ key: 'thumb_catimg_s', label: '自动获取分类封面', type: 'switch', default: true, help: '无特色图时使用分类封面图作为缩略图' },
],
},
{
key: 'multi',
title: '多图模式',
icon: 'fas fa-images',
fields: [
{ key: 'thumb_multi_enabled', label: '多图卡片', type: 'switch', default: false, help: '当文章有多张图片时,在列表中展示多图预览' },
{ key: 'thumb_multi_count', label: '最多显示几张', type: 'number', default: 3, min: 2, max: 6, dependsOn: { key: 'thumb_multi_enabled', value: true } },
{ key: 'mult_thumb_cat', label: '按分类多图显示', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], help: '选择的分类下所有文章自动显示多张缩略图' },
{ key: 'spare_thumbnail', label: '备用缩略图', type: 'repeater', default: spareThumbnailDefault, addText: '添加备用缩略图', fields: [
{ key: 'img', label: '图片', type: 'media', default: '' },
], help: '添加多张图片时自动随机获取' },
],
},
],
+3 -9
View File
@@ -78,8 +78,6 @@ export const MODULE_LIBRARY = [
options: [
{ label: '卡片', value: 'card' },
{ label: '列表', value: 'list' },
{ label: '瀑布流', value: 'masonry' },
{ label: '资源网格', value: 'grid-cms' },
],
},
],
@@ -89,7 +87,7 @@ export const MODULE_LIBRARY = [
label: '分类 Tabs',
icon: 'switcher',
desc: '多分类 Tab 切换',
defaultConfig: { cat_ids: [], count: 8, layout: 'grid-cms', title: '' },
defaultConfig: { cat_ids: [], count: 8, layout: 'card', title: '' },
fields: [
{ key: 'title', type: 'text', label: '标题' },
{ key: 'cat_ids', type: 'cats', label: '分类列表' },
@@ -99,8 +97,6 @@ export const MODULE_LIBRARY = [
options: [
{ label: '卡片', value: 'card' },
{ label: '列表', value: 'list' },
{ label: '瀑布流', value: 'masonry' },
{ label: '资源网格', value: 'grid-cms' },
],
},
],
@@ -138,8 +134,6 @@ export const MODULE_LIBRARY = [
options: [
{ label: '卡片', value: 'card' },
{ label: '列表', value: 'list' },
{ label: '瀑布流', value: 'masonry' },
{ label: '资源网格', value: 'grid-cms' },
],
},
],
@@ -460,8 +454,8 @@ export const PRESET_TEMPLATES = [
desc: 'Hero(固定骨架)+ Tabs + 最新文章 + 下载排行',
modules: [
{ type: 'hero', config: {} },
{ type: 'tabs', config: { count: 8, layout: 'grid-cms' } },
{ type: 'recent', config: { count: 8, layout: 'grid-cms' } },
{ type: 'tabs', config: { count: 8, layout: 'card' } },
{ type: 'recent', config: { count: 8, layout: 'card' } },
{ type: 'ranking', config: { metric: 'downloads', count: 6 } },
],
},
+1 -3
View File
@@ -418,11 +418,9 @@ html.dark .post-card,
html.dark .widget,
html.dark .aksrt-theme-box,
html.dark .theme-box,
html.dark .posts-item,
body.dark-theme .post-card,
body.dark-theme .widget,
body.dark-theme .aksrt-theme-box,
body.dark-theme .theme-box,
body.dark-theme .posts-item {
body.dark-theme .theme-box {
border-color: transparent;
}
@@ -41,26 +41,6 @@
padding-top: 16px;
}
.aksrt-homepage__posts > .posts-item.list {
padding: 15px;
gap: 14px;
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail {
width: 160px;
min-width: 160px;
padding-bottom: calc(160px * 0.7);
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span {
width: calc(33.333% - 6px);
padding-bottom: calc(var(--posts-list-scale, 70%) / 3);
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span:nth-child(4) {
display: none;
}
}
/* ================================================================
@@ -73,41 +53,6 @@
padding-right: 10px;
}
.aksrt-homepage__posts > .posts-item.list {
padding: 12px;
gap: 12px;
margin-bottom: 10px;
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail {
width: 120px;
min-width: 120px;
padding-bottom: calc(120px * 0.7);
}
.aksrt-homepage__posts .item-heading {
font-size: 15px;
line-height: 1.4;
}
.aksrt-homepage__posts .item-excerpt {
display: none;
}
.aksrt-homepage__posts .meta-right {
display: none;
}
/* Zibll keeps article cards in two columns throughout the mobile range. */
.aksrt-homepage__posts[data-view="card"] {
margin: -5px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item {
width: calc(50% - 10px);
margin: 5px;
}
/* Tab skeleton: smaller thumb */
.aksrt-homepage__tab-skeleton-thumb {
width: 120px;
@@ -158,44 +103,6 @@
font-size: 13px;
}
.aksrt-homepage__posts > .posts-item.list {
padding: 10px;
gap: 10px;
border-radius: var(--radius, 8px);
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail {
width: 96px;
min-width: 96px;
padding-bottom: calc(96px * 0.7);
border-radius: 6px;
}
.aksrt-homepage__posts .item-heading {
font-size: 15px;
-webkit-line-clamp: 2;
}
.aksrt-homepage__posts .item-tags .badge {
font-size: 11px;
padding: 2px 5px;
}
.aksrt-homepage__posts .item-meta {
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
}
.aksrt-homepage__posts .meta-avatar {
width: 20px;
height: 20px;
}
.aksrt-homepage__posts .meta-item {
font-size: 12px;
}
/* Tab skeleton: adapt */
.aksrt-homepage__tab-skeleton-thumb {
width: 96px;
+31 -454
View File
@@ -1,6 +1,5 @@
.aksrt-homepage__view-btn.is-active {
color: var(--color-primary, var(--color-primary));
color: var(--color-primary);
}
.aksrt-homepage__view-btn svg {
@@ -9,10 +8,6 @@
fill: currentColor;
}
/* ================================================================
7. TITLE THEME (sidebar section headers)
================================================================ */
.title-theme {
position: relative;
padding-left: 1.2em;
@@ -23,14 +18,14 @@
.title-theme::before {
position: absolute;
content: "";
width: 4px;
background: var(--color-primary, var(--color-primary));
top: 10%;
left: 2px;
bottom: 10%;
left: 2px;
width: 4px;
content: "";
background: var(--color-primary);
border-radius: 5px;
box-shadow: 1px 1px 3px -1px var(--color-primary, var(--color-primary));
box-shadow: 1px 1px 3px -1px var(--color-primary);
}
.aksrt-homepage__page-indicator {
@@ -40,29 +35,18 @@
opacity: 0.7;
}
/* ================================================================
8. PANELS
================================================================ */
.aksrt-homepage__panel[hidden] {
display: none !important;
}
.aksrt-homepage__panels {
display: block;
}
.home-tab-content {
display: block;
}
.aksrt-homepage__panel[hidden],
.index-tab-main .tab-pane[hidden],
.home-tab-content .tab-pane[hidden] {
display: none !important;
}
.aksrt-homepage__panels,
.home-tab-content {
display: block;
}
.aksrt-homepage__panel {
/* Inactive panes start transparent; active pane must be fully visible. */
opacity: 0;
transition: opacity 0.3s ease;
}
@@ -72,18 +56,15 @@
.aksrt-homepage__panel.fade.in,
.aksrt-homepage__panel.tab-pane.active,
#index-tab-main.aksrt-homepage__panel,
#index-tab-main.posts-row,
.home-tab-content .tab-pane.is-active,
.home-tab-content .tab-pane.show,
.home-tab-content .tab-pane.active,
.aksrt-homepage__panel.is-active,
.aksrt-homepage__panel.show {
opacity: 1 !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
}
/* Beat Bootstrap .fade:not(.show){opacity:0} if fade class remains */
.home-tab-content .tab-pane.fade.is-active,
.home-tab-content .tab-pane.fade.show,
.home-tab-content .tab-pane.fade.active,
@@ -95,430 +76,26 @@
display: none;
}
.aksrt-homepage__posts.post-grid--home {
gap: 0;
}
.aksrt-homepage__posts {
display: block;
margin: 0;
}
/* List cards: wrapped (.aksrt-homepage__posts) OR direct panel children (skip_wrapper). */
.aksrt-homepage__posts > .posts-item.list,
.posts-row.ajaxpager > .posts-item.list,
#index-tab-main > .posts-item.list {
width: 100%;
margin: 15px 0;
padding: 20px;
display: flex;
flex-direction: row;
gap: 16px;
min-width: 0;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--main-radius, var(--radius, 8px));
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: box-shadow 0.2s ease;
font-size: 1rem;
opacity: 1;
}
.aksrt-homepage__posts > .posts-item.list:last-child,
.posts-row.ajaxpager > .posts-item.list:last-child,
#index-tab-main > .posts-item.list:last-child {
margin-bottom: 0;
}
.aksrt-homepage__posts > .posts-item.list:hover,
.posts-row.ajaxpager > .posts-item.list:hover,
#index-tab-main > .posts-item.list:hover {
box-shadow: var(--shadow);
transform: none;
}
html.dark .aksrt-homepage__posts > .posts-item.list:hover,
html.dark .posts-row.ajaxpager > .posts-item.list:hover,
html.dark #index-tab-main > .posts-item.list:hover {
box-shadow: 0 4px 16px rgba(24, 24, 24, 0.22);
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail {
position: relative;
width: 190px;
min-width: 190px;
height: 0;
padding-top: 0;
padding-bottom: calc(190px * 0.7);
border-radius: var(--radius, 8px);
overflow: hidden;
flex-shrink: 0;
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail > a {
position: absolute;
inset: 0;
display: block;
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.aksrt-homepage__posts > .posts-item.list:hover .item-thumbnail img {
transform: scale(1.02);
}
.aksrt-homepage__posts > .posts-item.list .item-thumbnail .img-badge {
position: absolute;
top: 8px;
left: 8px;
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
font-weight: 500;
line-height: 1.4;
background: rgba(0, 0, 0, 0.6);
color: #fff;
z-index: 1;
}
.aksrt-homepage__posts > .posts-item.list .thumb-items {
display: none;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb {
display: block;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .item-thumbnail {
display: none;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items {
display: block;
margin: 0 -3px 6px;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span {
position: relative;
display: inline-block;
width: calc(25% - 6px);
height: 0;
margin: 0 3px;
padding-bottom: calc(var(--posts-list-scale, 70%) / 4);
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items.contain > span {
overflow: hidden;
background: var(--color-line, rgba(50, 50, 50, 0.06));
border-radius: var(--radius, 8px);
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span > img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span > img {
object-fit: cover;
transition: transform 0.3s;
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb:hover .thumb-items > span > img {
transform: scale(1.02);
}
.aksrt-homepage__posts > .posts-item.list.mult-thumb .item-body {
width: 100%;
padding: 2px 0 0;
}
.aksrt-homepage__posts > .posts-item.list .item-body,
.posts-row.ajaxpager > .posts-item.list .item-body,
#index-tab-main > .posts-item.list .item-body {
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 8px;
margin: 0;
padding: 2px 0;
min-width: 0;
flex: 1 1 auto;
}
.aksrt-homepage__posts .item-heading,
#index-tab-main .item-heading,
.posts-row.ajaxpager .item-heading {
margin: 0 0 2px;
font-size: 18px;
font-weight: 500;
line-height: 1.4;
min-height: 0;
max-height: 2.8em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: var(--color-ink);
}
.aksrt-homepage__posts .item-heading a,
#index-tab-main .item-heading a,
.posts-row.ajaxpager .item-heading a {
color: inherit;
text-decoration: none;
}
.aksrt-homepage__posts .item-heading a:hover,
#index-tab-main .item-heading a:hover,
.posts-row.ajaxpager .item-heading a:hover {
color: var(--color-primary, var(--color-primary));
}
.aksrt-homepage__posts .item-excerpt,
#index-tab-main .item-excerpt,
.posts-row.ajaxpager .item-excerpt {
margin: 0;
color: var(--main-color, #4e5358);
font-size: 14px;
line-height: 1.6;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.aksrt-homepage__posts .item-tags,
#index-tab-main .item-tags,
.posts-row.ajaxpager .item-tags {
display: flex;
align-items: center;
min-height: 0;
margin-bottom: 0;
gap: 5px;
flex-wrap: wrap;
overflow: visible;
}
.aksrt-homepage__posts .item-tags .badge {
min-height: auto;
margin-right: 0;
padding: 2px 5px;
border-radius: 4px;
font-size: 11px;
line-height: 1.4;
font-weight: 400;
}
.aksrt-homepage__posts .item-meta,
#index-tab-main .item-meta,
.posts-row.ajaxpager .item-meta {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0;
padding-top: 0;
border-top: 0;
font-size: 13px;
color: var(--color-ink-3, #777);
}
.aksrt-homepage__posts .meta-author,
.aksrt-homepage__posts .meta-right {
display: flex;
align-items: center;
gap: 8px;
}
.aksrt-homepage__posts .meta-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
}
.aksrt-homepage__posts .meta-name {
color: var(--color-ink);
font-weight: 500;
}
.aksrt-homepage__posts .meta-dot {
width: 4px;
height: 4px;
border-radius: 999px;
background: var(--color-ink-4, #b1b1b1);
}
.aksrt-homepage__posts .meta-item {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 13px;
white-space: nowrap;
color: var(--color-ink-3, #777);
}
.aksrt-homepage__posts .meta-item i {
font-size: 12px;
}
/* ================================================================
10. POST CARDS — CARD MODE (3-column grid)
================================================================ */
.aksrt-homepage__posts[data-view="card"] {
display: block;
margin: -8px;
font-size: 0;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item {
width: calc(33.333% - 16px);
margin: 8px;
padding: 10px;
display: inline-block;
vertical-align: top;
font-size: 1rem;
background: var(--surface-1, #fff);
border: none;
border-radius: var(--main-radius, var(--radius, 8px));
box-shadow: var(--shadow-sm);
overflow: hidden;
transition: box-shadow 0.3s ease;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item:hover {
box-shadow: var(--shadow);
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style2 {
background: transparent;
box-shadow: none;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style2:hover {
background: var(--surface-1, #fff);
box-shadow: 0 0 10px var(--shadow, rgba(116, 116, 116, 0.08));
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 {
padding: 0;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-thumbnail {
position: relative;
display: block;
width: 100%;
height: 0;
padding-top: 0;
padding-bottom: var(--posts-card-scale, 70%);
min-width: unset;
border-radius: var(--radius, 8px);
overflow: hidden;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-thumbnail > a {
position: absolute;
inset: 0;
display: block;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-thumbnail img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item:hover .item-thumbnail img {
transform: scale(1.02);
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.mult-thumb .thumb-items {
display: none;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.mult-thumb .item-thumbnail {
display: block;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-thumbnail,
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-thumbnail img {
border-radius: var(--radius, 8px) var(--radius, 8px) 0 0;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-body {
width: 100%;
margin: 10px 0 0;
padding: 0;
gap: 6px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-body {
margin: 0;
padding: 10px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-heading {
font-size: 14px;
font-weight: 500;
line-height: 1.4;
min-height: 2.8em;
-webkit-line-clamp: 2;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-excerpt {
display: none;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-meta {
font-size: 12px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .meta-right {
display: none;
}
/* ================================================================
VISIBILITY LOCK — homepage post stream must always paint
================================================================ */
posts.posts-item,
article.posts-item,
#index-tab-main > .posts-item,
.home-tab-content .posts-item {
opacity: 1 !important;
visibility: visible !important;
color: var(--color-ink, #333);
font-size: 1rem !important;
}
#index-tab-main > .posts-item.list,
.home-tab-content .posts-item.list {
.flex {
display: flex !important;
width: 100%;
box-sizing: border-box;
}
/* Minimal flex utilities used by post cards (zibll-style helpers) */
.flex { display: flex !important; }
.flex.xx,
.xx { flex-direction: column; }
.xx {
flex-direction: column;
}
.flex1,
.flex-1 { flex: 1 1 auto; min-width: 0; }
.jsb { justify-content: space-between; }
.ac { align-items: center; }
.jb { justify-content: space-between; }
.flex-1 {
flex: 1 1 auto;
min-width: 0;
}
.jsb,
.jb {
justify-content: space-between;
}
.ac {
align-items: center;
}
@@ -1,55 +1,3 @@
transition: transform 0.3s;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.mult-thumb .thumb-items {
display: none;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.mult-thumb .item-thumbnail {
display: block;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-thumbnail,
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-thumbnail img {
border-radius: var(--radius, 8px) var(--radius, 8px) 0 0;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item:hover .item-thumbnail img {
transform: scale(1.02);
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-body {
width: 100%;
margin: 10px 0 0;
padding: 0;
gap: 6px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item.style3 .item-body {
margin: 0;
padding: 10px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-heading {
font-size: 14px;
font-weight: 500;
line-height: 1.4;
min-height: 2.8em;
-webkit-line-clamp: 2;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-excerpt {
display: none;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .item-meta {
font-size: 12px;
}
.aksrt-homepage__posts[data-view="card"] > .posts-item .meta-right {
display: none;
}
/* ================================================================
11. PAGINATION
================================================================ */
@@ -231,7 +231,4 @@
padding: 0.7em;
}
.posts-item .item-thumbnail {
width: 138px;
}
}
-60
View File
@@ -1,60 +0,0 @@
/**
* Post Card · CMS (grid-cms layout)
*/
.post-grid--grid-cms {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
.post-grid--grid-cms .posts-item.card.posts-item--cms {
height: 100%;
border-radius: 16px;
}
.post-grid--grid-cms .posts-item--cms .item-body {
gap: 10px;
}
.post-grid--grid-cms .posts-item--cms .item-excerpt {
-webkit-line-clamp: 2;
}
.post-grid--grid-cms .posts-item--cms .item-cms-strip {
display: flex;
flex-wrap: wrap;
gap: 8px;
list-style: none;
margin: 0;
padding: 0;
}
.post-grid--grid-cms .posts-item--cms .item-cms-strip li {
display: inline-flex;
align-items: center;
min-height: 24px;
padding: 0 10px;
border-radius: 999px;
background: var(--surface-2);
color: var(--color-ink-4);
font-size: 12px;
font-weight: 500;
}
.post-grid--grid-cms .posts-item--cms .item-cms-strip li::before {
display: none;
}
@media (max-width: 640px) {
.post-grid--grid-cms {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
}
}
@media (max-width: 380px) {
.post-grid--grid-cms {
grid-template-columns: 1fr;
}
}
File diff suppressed because it is too large Load Diff
+96
View File
@@ -4,6 +4,102 @@
(function () {
"use strict";
function renderGradientBackground(container) {
if (!container || container.dataset.aksrtGradientState || typeof window.Grade !== "function") return;
var source = container.querySelector("img");
if (!source || (!source.classList.contains("lazyloaded") && !(source.complete && source.naturalWidth > 0))) return;
container.dataset.aksrtGradientState = "loading";
window.Grade(container, null, function (result) {
var gradientData = result && result[0];
var colors = gradientData && gradientData.gradientData;
if (!gradientData || !colors || !colors[0] || !colors[1]) {
delete container.dataset.aksrtGradientState;
return;
}
var opacity = container.getAttribute("data-opacity");
var colorName = opacity ? "rgba" : "rgb";
var alpha = opacity ? "," + opacity : "";
var color1 = colorName + "(" + colors[0].rgba.slice(0, 3).join(",") + alpha + ") 10%";
var color2 = colorName + "(" + colors[1].rgba.slice(0, 3).join(",") + alpha + ") 80%";
gradientData.element.classList.add("gradient-bg-loaded");
gradientData.element.style.backgroundImage = "linear-gradient(135deg, " + color1 + ", " + color2 + ")";
container.dataset.aksrtGradientState = "loaded";
});
}
function initGradientBackgrounds(root) {
var scope = root || document;
if (scope.nodeType === 1 && scope.matches(".gradient-bg")) {
renderGradientBackground(scope);
}
scope.querySelectorAll(".gradient-bg").forEach(renderGradientBackground);
}
document.addEventListener("lazyloaded", function (event) {
if (!(event.target instanceof Element) || !event.target.matches(".gradient-bg img")) return;
renderGradientBackground(event.target.closest(".gradient-bg"));
}, true);
function initPostThumbMedia(root) {
var scope = root || document;
scope.querySelectorAll("[data-post-thumb-slider]").forEach(function (el) {
if (el.dataset.aksrtThumbSliderBound === "1" || typeof Swiper === "undefined") return;
el.dataset.aksrtThumbSliderBound = "1";
new Swiper(el, {
slidesPerView: 1,
loop: el.querySelectorAll(".swiper-slide").length > 1,
pagination: {
el: el.querySelector(".swiper-pagination"),
clickable: true,
},
});
});
scope.querySelectorAll("[data-post-thumb-video]").forEach(function (box) {
if (box.dataset.aksrtThumbVideoBound === "1") return;
box.dataset.aksrtThumbVideoBound = "1";
var video = box.querySelector("video");
if (!video) return;
function loadVideo() {
if (!video.getAttribute("src") && video.dataset.src) {
video.setAttribute("src", video.dataset.src);
video.load();
}
}
function playVideo() {
loadVideo();
var result = video.play();
var postItem = box.closest(".posts-item");
if (postItem) postItem.classList.add("thumb-dplayer-playing");
if (result && typeof result.catch === "function") result.catch(function () {});
}
function pauseVideo() {
video.pause();
var postItem = box.closest(".posts-item");
if (postItem) postItem.classList.remove("thumb-dplayer-playing");
}
var item = box.closest(".item-thumbnail") || box;
item.addEventListener("mouseenter", playVideo);
item.addEventListener("mouseleave", pauseVideo);
item.addEventListener("focusin", playVideo);
item.addEventListener("focusout", pauseVideo);
});
}
window.AksrtFrontend.register("aksrt-post-thumb-media", function (root) {
initPostThumbMedia(root);
initGradientBackgrounds(root);
});
function initRelatedSwiper() {
var el = document.querySelector(".related-swiper[data-swiper-config]");
if (!el || typeof Swiper === "undefined") return;
+28 -17
View File
@@ -9,7 +9,7 @@
}
function initAjaxPagination(root) {
var containers = (root || document).querySelectorAll(".aksrt-pagination-ajax, .ajax-pagination");
var containers = (root || document).querySelectorAll("[data-aksrt-pagination], .aksrt-comment-pagination");
if (!containers.length) return;
var triggerText = (window.aksrtData && aksrtData.ajaxTrigger) || "加载更多";
@@ -29,8 +29,9 @@
var commentOrder = container.dataset.commentOrder || "newest";
var loading = false;
var failed = false;
var loadMoreBtn = container.querySelector(".aksrt-load-more-btn, .ajax-next");
var indicator = container.querySelector(".aksrt-loading-indicator");
var clickTarget = container.querySelector(".next-page.ajax-next, .ias-btn") || container;
var loadMoreBtn = container.querySelector("[data-aksrt-load-more]") || clickTarget;
var indicator = container.querySelector(".post_ajax_loader");
var successfulAutoLoads = 0;
var iasMax = isComment
? ((window.aksrtData && typeof aksrtData.commentIasMax !== "undefined") ? parseInt(aksrtData.commentIasMax, 10) : 0)
@@ -68,13 +69,19 @@
function stopInfinite(markNomore) {
container.classList.remove("ias-pending");
if (markNomore && loadMoreBtn) {
loadMoreBtn.textContent = nomoreText;
loadMoreBtn.innerHTML = nomoreText;
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.classList.add("ajax-nomore");
var nextWrap = loadMoreBtn.closest(".next-page");
if (nextWrap) nextWrap.classList.add("no-more");
loadMoreBtn.style.pointerEvents = "none";
} else if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore")) {
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.textContent = failed ? errorText : triggerText;
if (failed) {
loadMoreBtn.textContent = errorText;
} else {
loadMoreBtn.innerHTML = triggerText;
}
loadMoreBtn.style.pointerEvents = "";
}
}
@@ -164,7 +171,7 @@
target.appendChild(newItems[0]);
}
}
if (isComment && window.AksrtFrontend) {
if (window.AksrtFrontend) {
AksrtFrontend.rehydrate(target, "ajax");
}
nextPage++;
@@ -191,13 +198,16 @@
if (indicator) indicator.style.display = "none";
if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) {
loadMoreBtn.classList.remove("is-loading");
loadMoreBtn.textContent = triggerText;
loadMoreBtn.innerHTML = triggerText;
if (mode === "infinite") {
var lazyTrigger = clickTarget.closest(".next-page") || clickTarget;
if (iasMax > 0 && successfulAutoLoads >= iasMax) {
stopInfinite(true);
container.classList.remove("ias-pending");
lazyTrigger.classList.remove("lazyload", "lazyloaded", "lazyloading");
lazyTrigger.removeAttribute("lazyload-action");
} else {
loadMoreBtn.classList.remove("lazyloaded", "lazyloading");
loadMoreBtn.classList.add("lazyload");
lazyTrigger.classList.remove("lazyloaded", "lazyloading");
lazyTrigger.classList.add("lazyload");
if (window.lazySizes && lazySizes.loader) {
lazySizes.loader.checkElems();
}
@@ -208,7 +218,7 @@
}
if (loadMoreBtn) {
loadMoreBtn.addEventListener("click", function(e) {
clickTarget.addEventListener("click", function(e) {
e.preventDefault();
if (loadMoreBtn.dataset.jumpComments === "1") {
var jumpInput = container.querySelector(".jump-input");
@@ -228,14 +238,14 @@
var archiveMax = parseInt(archiveJumpInput.dataset.maxPages || String(maxPages), 10) || maxPages;
archiveTargetPage = Math.max(1, Math.min(archiveMax, archiveTargetPage));
var archiveUrl = new URL(window.location.href);
archiveUrl.searchParams.set("paged", String(archiveTargetPage));
archiveUrl.searchParams.set(loadMoreBtn.dataset.pageVar || "paged", String(archiveTargetPage));
window.location.href = archiveUrl.toString();
return;
}
var fromAuto = loadMoreBtn.dataset.lazyAuto === "1";
delete loadMoreBtn.dataset.lazyAuto;
var fromAuto = clickTarget.dataset.lazyAuto === "1";
delete clickTarget.dataset.lazyAuto;
if (fromAuto && iasMax > 0 && successfulAutoLoads >= iasMax) {
stopInfinite(true);
container.classList.remove("ias-pending");
return;
}
// Manual click can recover from a previous auto-load failure.
@@ -246,8 +256,9 @@
if (mode === "infinite") {
if ((isComment && window.aksrtData && aksrtData.commentIasAuto === false) || !hasAjaxConfig()) {
loadMoreBtn.classList.remove("lazyload");
loadMoreBtn.removeAttribute("lazyload-action");
var autoTrigger = clickTarget.closest(".next-page") || clickTarget;
autoTrigger.classList.remove("lazyload");
autoTrigger.removeAttribute("lazyload-action");
return;
}
container.classList.add("ias-pending");
+1
View File
@@ -0,0 +1 @@
(function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Grade=e()}})(function(){return function e(t,n,r){function i(o,s){if(!n[o]){if(!t[o]){var u="function"==typeof require&&require;if(!s&&u)return u(o,!0);if(a)return a(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var g=n[o]={exports:{}};t[o][0].call(g.exports,function(e){var n=t[o][1][e];return i(n||e)},g,g.exports,e,t,n,r)}return n[o].exports}for(var a="function"==typeof require&&require,o=0;o<r.length;o++)i(r[o]);return i}({1:[function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}return function(t,n,r){return n&&e(t.prototype,n),r&&e(t,r),t}}(),a=["webkit"],o=function(){function e(t,n,i){r(this,e),this.callback=i||null,this.container=t;var a=this.container.querySelector(n||"img");if(this.gradientData=[],a&&this.container){this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d"),this.imageData=[];var o=this;o.image=new Image,o.image.crossOrigin="Anonymous",o.image.src=a.getAttribute("data-src")||a.getAttribute("src"),o.image.onload=function(){o.readImage()}}}return i(e,[{key:"readImage",value:function(){this.imageDimensions={width:100,height:100},this.render()}},{key:"getImageData",value:function(){var e=this.ctx.getImageData(0,0,this.imageDimensions.width,this.imageDimensions.height).data;this.imageData=Array.from(e)}},{key:"getChunkedImageData",value:function(){var e=4,t=this.imageData.reduce(function(t,n,r){var i=Math.floor(r/e);return t[i]||(t[i]=[]),t[i].push(n),t},[]),n=t.filter(function(e){var t=e[0],n=e[1],r=e[2],i=Math.max(Math.abs(t-n),Math.abs(t-r),Math.abs(n-r));return i>20});return(!n||n.length<2)&&(n=t.filter(function(e){return e.slice(0,2).every(function(e){return e<245})&&e.slice(0,2).every(function(e){return e>10})})),n}},{key:"getRGBAGradientValues",value:function(e){return e.map(function(e,t){return"rgb("+e.rgba.slice(0,3).join(",")+") "+(0==t?"0%":"75%")}).join(",")}},{key:"getCSSGradientProperty",value:function(e){var t=this.getRGBAGradientValues(e);return a.map(function(e){return"background-image: -"+e+"-linear-gradient(\n 135deg,\n "+t+"\n )"}).concat(["background-image: linear-gradient(\n 135deg,\n "+t+"\n )"]).join(";")}},{key:"getMiddleRGB",value:function(e,t){var n=0,r=(n+1)/2,i=1-r,a=[parseInt(e[0]*r+t[0]*i),parseInt(e[1]*r+t[1]*i),parseInt(e[2]*r+t[2]*i)];return a}},{key:"getSortedValues",value:function(e){var t=Object.keys(e).map(function(t){var n=t,r=t.split("|"),i=(299*r[0]+587*r[1]+114*r[2])/1e3;return{rgba:n.split("|"),occurs:e[t],brightness:i}}).sort(function(e,t){return e.occurs-t.occurs}).reverse().slice(0,10);return t.sort(function(e,t){return e.brightness-t.brightness}).reverse()}},{key:"getTextProperty",value:function(e){var t=this.getMiddleRGB(e[0].rgba.slice(0,3),e[1].rgba.slice(0,3)),n=Math.round((299*parseInt(t[0])+587*parseInt(t[1])+114*parseInt(t[2]))/1e3);return n>125?"color: #000":"color: #fff"}},{key:"getTopValues",value:function(e){var t=this.getSortedValues(e);return[t[0],t[t.length-1]]}},{key:"getUniqValues",value:function(e){return e.reduce(function(e,t){var n=t.join("|");return e[n]?(e[n]=++e[n],e):(e[n]=1,e)},{})}},{key:"renderGradient",value:function(){var e=window.localStorage,t="grade-"+this.image.getAttribute("src"),n=null;if(e&&e.getItem(t)&&(n=JSON.parse(e.getItem(t))),!n||!n[0]){var r=this.getChunkedImageData();n=this.getTopValues(this.getUniqValues(r)),e&&e.setItem(t,JSON.stringify(n))}if(this.callback)return this.gradientData=n,void this.callback([{element:this.container,gradientData:n}]);var i=this.getCSSGradientProperty(n),a=this.getTextProperty(n),o=(this.container.getAttribute("style")||"")+"; "+i+"; "+a;this.container.setAttribute("style",o)}},{key:"render",value:function(){this.canvas.width=this.imageDimensions.width,this.canvas.height=this.imageDimensions.height,this.ctx.drawImage(this.image,0,0,this.imageDimensions.width,this.imageDimensions.height),this.getImageData(),this.renderGradient()}}]),e}();t.exports=function(e,t,n){var r=function(e,t,n){var r=new o(e,t,n),i=r.gradientData;return i.length?{element:e,gradientData:i}:null},i=(NodeList.prototype.isPrototypeOf(e)?Array.from(e).map(function(e){return r(e,t,n)}):[r(e,t,n)]).filter(Boolean);if(i.length)return n(i)}},{}]},{},[1])(1)});
+5 -110
View File
@@ -1,7 +1,7 @@
/**
* Homepage Fixed - Interactive Layer (v2.0 Zibll-aligned)
*
* Unified AJAX protocol for homepage tabs, sort, pagination, and view toggle.
* Unified AJAX protocol for homepage tabs, sort, and pagination.
* Uses Zibll-style ajaxpager DOM conventions and custom event flow:
* post_ajax.before → fetch → post_ajax.ed → aksrt:posts:rendered
*
@@ -11,34 +11,18 @@
(function () {
'use strict';
var STORAGE_KEY = 'aksrt_homepage_view_mode';
var SORT_QUERY_KEY = 'home_sort';
var DEFAULT_SORT = 'modified';
var DEFAULT_VIEW = 'list';
var REST_PATH = '/wp-json/aksrt/v1/homepage/tab-posts';
/* ============================================================
* Utilities
* ============================================================ */
function safeLocalStorage() {
try { return window.localStorage; } catch (e) { return null; }
}
function getViewMode() {
var store = safeLocalStorage();
if (store) {
var saved = store.getItem(STORAGE_KEY);
if (saved === 'card' || saved === 'list') return saved;
}
return getDefaultView();
}
function setViewMode(mode) {
var store = safeLocalStorage();
if (store) { try { store.setItem(STORAGE_KEY, mode); } catch (e) { /* */ } }
}
function getRenderedPostCount(scope) {
if (!scope) return 0;
return scope.querySelectorAll('.posts-item, .posts-mini').length;
@@ -63,8 +47,8 @@
}
function getDefaultView() {
var configured = window.aksrtData && aksrtData.homepageDefaultView;
return configured === 'card' || configured === 'list' ? configured : DEFAULT_VIEW;
var panel = document.querySelector('[data-home-tab-panel][data-view]');
return panel && panel.getAttribute('data-view') === 'card' ? 'card' : 'list';
}
function isSortAjaxEnabled(sortbar) {
@@ -119,8 +103,8 @@
}
function getStreamViewMode(stream) {
var activeBtn = stream.querySelector('.aksrt-homepage__view-btn.is-active');
return (activeBtn && activeBtn.getAttribute('data-view-mode')) || getViewMode();
var panel = getActivePanel(stream);
return panel && panel.getAttribute('data-view') === 'card' ? 'card' : 'list';
}
function getActivePanel(stream) {
@@ -153,7 +137,6 @@
'&page=' + encodeURIComponent(options.page || 1) +
'&per_page=' + encodeURIComponent(options.perPage || 10) +
'&sort=' + encodeURIComponent(options.sort || getDefaultSort()) +
'&view_mode=' + encodeURIComponent(options.viewMode || getDefaultView()) +
'&page_url=' + encodeURIComponent(options.pageUrl || window.location.href) +
'&panel=' + encodeURIComponent(options.panel || 'latest') +
'&source=' + encodeURIComponent(options.source || 'home') +
@@ -731,93 +714,6 @@
});
}
/* ============================================================
* View Mode Toggle
* ============================================================ */
function applyViewMode(buttons, postsContainers, mode) {
buttons.forEach(function (btn) {
var btnMode = btn.getAttribute('data-view-mode');
var isActive = btnMode === mode;
btn.classList.toggle('is-active', isActive);
btn.setAttribute('aria-pressed', isActive ? 'true' : 'false');
});
Array.prototype.forEach.call(postsContainers, function (container) {
container.setAttribute('data-view', mode);
container.querySelectorAll('.posts-item').forEach(function (item) {
item.classList.toggle('card', mode === 'card');
item.classList.toggle('list', mode === 'list');
});
});
}
function initViewToggle() {
var toggles = document.querySelectorAll('[data-aksrt-view-toggle]');
toggles.forEach(function (toggle) {
var buttons = toggle.querySelectorAll('.aksrt-homepage__view-btn');
var stream = toggle.closest('.aksrt-homepage__stream');
if (!stream) return;
function getPostsContainers() {
return stream.querySelectorAll('[data-home-tab-panel]');
}
function getActiveMode() {
var activeBtn = toggle.querySelector('.aksrt-homepage__view-btn.is-active');
return (activeBtn && activeBtn.getAttribute('data-view-mode')) || getViewMode();
}
var initialMode = getViewMode();
var activePanel = getActivePanel(stream);
var renderedMode = activePanel ? activePanel.getAttribute('data-view') : '';
applyViewMode(buttons, getPostsContainers(), initialMode);
// The persisted preference may differ from the server-rendered mode.
// Class toggling alone leaves list-only markup and its `.flex` utility in
// place, which forces a supposed card to occupy a full row. Fetch the
// matching renderer immediately so DOM and visual mode stay atomic.
if (activePanel && renderedMode !== initialMode) {
requestHomepagePanel(activePanel, {
termId: getPanelTermId(activePanel),
page: 1,
perPage: getHomepagePerPage(activePanel),
sort: getCurrentSort(stream),
viewMode: initialMode,
panel: activePanel.getAttribute('data-home-tab-panel') || 'latest',
source: 'home',
tabOrderby: getPanelTabOrderby(activePanel)
});
}
buttons.forEach(function (btn) {
btn.addEventListener('click', function () {
var mode = btn.getAttribute('data-view-mode') || getDefaultView();
var activePanel = getActivePanel(stream);
if (!activePanel || mode === getActiveMode()) return;
setViewMode(mode);
applyViewMode(buttons, getPostsContainers(), mode);
requestHomepagePanel(activePanel, {
termId: getPanelTermId(activePanel),
page: 1,
perPage: getHomepagePerPage(activePanel),
sort: getCurrentSort(stream),
viewMode: mode,
panel: activePanel.getAttribute('data-home-tab-panel') || 'latest',
source: 'home',
tabOrderby: getPanelTabOrderby(activePanel)
});
});
});
// Re-apply view mode after AJAX content renders
stream.addEventListener('aksrt:posts:rendered', function () {
applyViewMode(buttons, getPostsContainers(), getActiveMode());
});
});
}
/* ============================================================
* Init
* ============================================================ */
@@ -827,7 +723,6 @@
initSortbarDropdown();
initSortbar();
initStickyTabs();
initViewToggle();
}
if (document.readyState === 'loading') {
+2 -19
View File
@@ -36,7 +36,6 @@ if (!$term instanceof WP_Term) {
$archive_settings = class_exists('\AKSRT\Core\TermMeta')
? \AKSRT\Core\TermMeta::resolve_archive_settings($term)
: [
'layout_mode' => 'card',
'header_style' => 'cover',
'show_sidebar' => true,
'show_cover' => false,
@@ -44,7 +43,6 @@ $archive_settings = class_exists('\AKSRT\Core\TermMeta')
'filter_bar' => true,
'cover_url' => '',
'description' => (string) $term->description,
'card_style' => 'standard',
];
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
@@ -52,8 +50,6 @@ $has_sidebar = function_exists('aksrt_archive_has_sidebar')
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('cat_sidebar'));
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
$header_style = (string) $archive_settings['header_style'];
$layout_mode = (string) $archive_settings['layout_mode'];
$card_style = (string) ($archive_settings['card_style'] ?? 'standard');
$filter_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_filter_settings()
: ['enabled' => false, 'sort' => false, 'ajax_enabled' => false];
@@ -68,20 +64,9 @@ $custom_dimensions = is_array($cat_filter_config['dimensions'] ?? null) ? $cat_f
$sort_state = function_exists('aksrt_archive_get_sort_state') ? aksrt_archive_get_sort_state() : ['current' => 'latest', 'links' => []];
$current_sort = $sort_state['current'];
$sort_links = $sort_state['links'];
$render_overrides = [
'context' => 'category',
];
// Term meta layout_mode is authoritative only when explicitly set (non-empty preset).
$layout_preset = (string) ($archive_settings['layout_preset'] ?? '');
if ($layout_preset !== '' && in_array((string) $layout_mode, ['list', 'card', 'mini', 'grid-cms'], true)) {
$render_overrides['mode'] = (string) $layout_mode;
if (!empty($card_style)) {
$render_overrides['card_style'] = (string) $card_style;
}
}
$render_config = function_exists('aksrt_archive_get_render_config')
? aksrt_archive_get_render_config($render_overrides)
: array_merge(['context' => 'category', 'mode' => 'list'], $render_overrides);
? aksrt_archive_get_render_config(['context' => 'category'])
: ['context' => 'category'];
$filter_terms = [];
$filter_all_url = get_category_link($term->term_id);
@@ -226,8 +211,6 @@ if (function_exists('aksrt_archive_open_shell')) {
'ajax' => $ajax_filter,
'ajax_archive' => 'category',
'term_id' => (int) $term->term_id,
'layout_mode' => $layout_mode,
'card_style' => $card_style,
'aria_label' => __('分类筛选', 'aksrt'),
'meta_text' => sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count),
'groups' => $filter_groups,
-4
View File
@@ -130,8 +130,6 @@ if (!function_exists('aksrt_archive_render_filter_bar')) :
'ajax' => false,
'ajax_archive' => '',
'term_id' => 0,
'layout_mode' => 'card',
'card_style' => 'standard',
'aria_label' => '',
'meta_text' => '',
'groups' => [],
@@ -157,8 +155,6 @@ if (!function_exists('aksrt_archive_render_filter_bar')) :
data-ajax-archive="<?php echo esc_attr((string) $args['ajax_archive']); ?>"
data-term-id="<?php echo (int) $args['term_id']; ?>"
data-query-context="archive"
data-render-mode="<?php echo esc_attr((string) $args['layout_mode']); ?>"
data-card-style="<?php echo esc_attr((string) $args['card_style']); ?>"
<?php endif; ?>
>
<?php foreach ($groups as $group) : ?>
+16 -171
View File
@@ -16,45 +16,15 @@ if (!function_exists('aksrt_archive_get_list_style_settings')) :
* @return array<string,mixed>
*/
function aksrt_archive_get_list_style_settings(): array {
$settings = class_exists('\AKSRT\Settings\Settings')
return class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_list_style_settings()
: [];
return [
'list_type' => (string) ($settings['list_type'] ?? 'thumb'),
'render_mode' => (string) ($settings['render_mode'] ?? 'list'),
'list_type_token' => (string) ($settings['list_type_token'] ?? ($settings['list_type'] ?? 'thumb')),
'list_style' => (string) ($settings['list_style'] ?? ''),
'card_variant' => (string) ($settings['card_variant'] ?? ''),
// Back-compat: older callers expected card_style as list_type token.
'card_style' => (string) ($settings['list_type'] ?? ($settings['card_style'] ?? 'thumb')),
'card_cat_ids' => is_array($settings['card_cat_ids'] ?? null) ? array_map('intval', $settings['card_cat_ids']) : [],
'card_home' => !empty($settings['card_home']),
'card_tag' => !empty($settings['card_tag']),
'card_author' => !empty($settings['card_author']),
'list_show_type' => (string) ($settings['list_show_type'] ?? 'separate'),
'list_img_position'=> (string) ($settings['list_img_position'] ?? 'left'),
'show_author' => !isset($settings['show_author']) || !empty($settings['show_author']),
'show_date' => !isset($settings['show_date']) || !empty($settings['show_date']),
'show_views' => !isset($settings['show_views']) || !empty($settings['show_views']),
'show_comments' => !empty($settings['show_comments']),
'show_likes' => !empty($settings['show_likes']),
'target_blank' => !empty($settings['target_blank']),
'heading_bold' => !empty($settings['heading_bold']),
'badge_show' => !isset($settings['badge_show']) || !empty($settings['badge_show']),
'excerpt_length' => (int) ($settings['excerpt_length'] ?? 120),
];
}
endif;
if (!function_exists('aksrt_resolve_post_list_render_config')) :
/**
* Zibll-aligned list/card resolver for all surfaces.
*
* Decision order mirrors zib_mian_posts_while():
* 1) explicit override mode/type
* 2) global list_type (post_card_style): only "card" is card grid; thumb/text/thumb_if_has are list
* 3) surface card switches (home/tag/author/cat IDs)
* Build the Zibll-shaped arguments used by every list surface.
*
* @param array<string,mixed> $args {
* @type string $context home|category|tag|author|search|archive
@@ -66,16 +36,10 @@ if (!function_exists('aksrt_resolve_post_list_render_config')) :
function aksrt_resolve_post_list_render_config(array $args = []): array {
$args = wp_parse_args($args, [
'context' => '',
'term_id' => 0,
'overrides' => [],
]);
$overrides = is_array($args['overrides']) ? $args['overrides'] : [];
if (!empty($args['context']) && empty($overrides['context'])) {
// keep context on root for merge below
}
$context = sanitize_key((string) ($args['context'] !== '' ? $args['context'] : ($overrides['context'] ?? '')));
$context = sanitize_key((string) ($args['context'] ?: ($overrides['context'] ?? '')));
if ($context === '') {
if (function_exists('is_front_page') && (is_front_page() || is_home())) {
$context = 'home';
@@ -91,125 +55,20 @@ if (!function_exists('aksrt_resolve_post_list_render_config')) :
$context = 'archive';
}
}
$term_id = (int) ($args['term_id'] ?? 0);
if ($term_id <= 0) {
$term_id = (int) get_queried_object_id();
}
$list = aksrt_archive_get_list_style_settings();
$list_type = sanitize_key((string) ($list['list_type'] ?? 'thumb'));
if ($list_type === '' || $list_type === 'standard') {
$list_type = 'thumb';
}
// Explicit caller mode wins (term meta, AJAX view toggle, etc.).
$forced_mode = '';
if (isset($overrides['mode']) && is_string($overrides['mode']) && $overrides['mode'] !== '') {
$forced_mode = sanitize_key($overrides['mode']);
} elseif (isset($overrides['type']) && sanitize_key((string) $overrides['type']) === 'card') {
$forced_mode = 'card';
} elseif (!empty($overrides['is_card'])) {
$forced_mode = 'card';
}
$mode = $forced_mode !== ''
? $forced_mode
: (($list_type === 'card') ? 'card' : 'list');
// Surface card overrides (only when not already card from list_type / force).
if ($mode !== 'card' && $forced_mode === '') {
if ($context === 'home' && !empty($list['card_home'])) {
$mode = 'card';
} elseif ($context === 'tag' && !empty($list['card_tag'])) {
$mode = 'card';
} elseif ($context === 'author' && !empty($list['card_author'])) {
$mode = 'card';
} elseif ($term_id > 0 && !empty($list['card_cat_ids']) && in_array($term_id, array_map('intval', (array) $list['card_cat_ids']), true)) {
$mode = 'card';
}
}
if (!in_array($mode, ['list', 'card', 'mini', 'grid-cms'], true)) {
$mode = 'list';
}
// Visual variant: list → style2; card → style3 (Zibll fieldsets).
$style = '';
if ($mode === 'card') {
$style = sanitize_key((string) ($list['card_variant'] ?? ''));
if (!in_array($style, ['', 'style3'], true)) {
$style = '';
}
} else {
$style = sanitize_key((string) ($list['list_style'] ?? ''));
if (!in_array($style, ['', 'style2'], true)) {
$style = '';
}
}
if (isset($overrides['style']) && is_string($overrides['style'])) {
$style = sanitize_key($overrides['style']);
if (in_array($style, ['default', 'null'], true)) {
$style = '';
}
}
$excerpt_words = max(8, (int) round(((int) ($list['excerpt_length'] ?? 120)) / 4));
$config = [
'context' => $context,
'mode' => $mode,
// list_type token for text / thumb_if_has hooks (renderer card_style key).
'card_style' => $list_type,
'style' => $style === 'default' ? '' : $style,
'list_show_type' => (string) ($list['list_show_type'] ?? 'separate'),
'list_img_position' => (string) ($list['list_img_position'] ?? 'left'),
'thumb_if_has' => ($list_type === 'thumb_if_has'),
'show_thumb' => ($list_type !== 'text'),
'show_author' => !empty($list['show_author']),
'show_date' => !empty($list['show_date']),
'show_views' => !empty($list['show_views']),
'show_comments' => !empty($list['show_comments']),
'show_likes' => !empty($list['show_likes']),
'target_blank' => !empty($list['target_blank']),
'heading_bold' => !empty($list['heading_bold']),
'badge_show' => !empty($list['badge_show']),
'show_excerpt' => $mode !== 'card',
'excerpt_words' => $excerpt_words,
'is_home' => $context === 'home',
'is_category' => $context === 'category',
'is_tag' => $context === 'tag',
'is_author' => $context === 'author',
'is_search' => $context === 'search',
];
// Multi-thumb from global thumb settings + category allowlist.
if (class_exists('\AKSRT\Settings\Settings')) {
$thumb_settings = \AKSRT\Settings\Settings::get_post_thumb_settings();
if (!empty($thumb_settings['multi_enabled'])) {
$config['multi_thumb_enabled'] = true;
$config['multi_thumb_limit'] = (int) ($thumb_settings['multi_count'] ?? 4);
$config['multi_thumb_min'] = 3;
}
if ($context === 'category' && !empty($thumb_settings['mult_thumb_cat_ids']) && $term_id > 0) {
if (in_array($term_id, array_map('intval', (array) $thumb_settings['mult_thumb_cat_ids']), true)) {
$config['multi_thumb_enabled'] = true;
$config['multi_thumb_limit'] = (int) ($thumb_settings['multi_count'] ?? 4);
$config['multi_thumb_min'] = 3;
foreach (['type', 'no_author', 'no_margin', 'is_mult_thumb', 'is_no_thumb', 'is_card', 'highlight_query', 'wrapper_class', 'skip_wrapper'] as $key) {
if (array_key_exists($key, $overrides)) {
$config[$key] = $overrides[$key];
}
}
}
// Merge remaining overrides last (except mode/style already handled carefully).
foreach ($overrides as $key => $value) {
if (in_array($key, ['mode', 'type', 'is_card', 'style', 'context'], true)) {
continue;
}
$config[$key] = $value;
}
// Re-apply forced mode after merge if present.
if ($forced_mode !== '' && in_array($forced_mode, ['list', 'card', 'mini', 'grid-cms'], true)) {
$config['mode'] = $forced_mode;
}
$config['context'] = $context;
$config['style'] = $style === 'default' ? '' : $style;
return $config;
return aksrt_renderer_config($config);
}
endif;
@@ -222,18 +81,9 @@ if (!function_exists('aksrt_archive_get_render_config')) :
*/
function aksrt_archive_get_render_config(array $overrides = []): array {
$context = sanitize_key((string) ($overrides['context'] ?? ''));
$term_id = isset($overrides['term_id']) ? (int) $overrides['term_id'] : 0;
// Only treat explicit mode as override; empty string means follow global resolver.
$resolver_overrides = $overrides;
if (isset($resolver_overrides['mode']) && $resolver_overrides['mode'] === '') {
unset($resolver_overrides['mode']);
}
return aksrt_resolve_post_list_render_config([
'context' => $context,
'term_id' => $term_id,
'overrides' => $resolver_overrides,
'overrides' => $overrides,
]);
}
endif;
@@ -351,15 +201,14 @@ if (!function_exists('aksrt_archive_render_stream')) :
$classes[] = 'is-ajax-enabled';
}
$mode = (string) ($render_config['mode'] ?? 'card');
$card_style = (string) ($render_config['card_style'] ?? 'standard');
$mode = (string) ($render_config['mode'] ?? 'list');
$has_posts = $query instanceof WP_Query ? $query->have_posts() : have_posts();
?>
<div
class="<?php echo esc_attr(implode(' ', array_unique($classes))); ?>"
data-page-kind="<?php echo esc_attr((string) $args['page_kind']); ?>"
data-render-mode="<?php echo esc_attr($mode); ?>"
data-card-style="<?php echo esc_attr($card_style); ?>"
data-list-type="<?php echo esc_attr((string) ($render_config['list_type'] ?? 'thumb')); ?>"
<?php if (!empty($args['ajax_enabled'])) : ?>
data-ajax-wrap
data-query-context="<?php echo esc_attr((string) $args['query_context']); ?>"
@@ -372,18 +221,14 @@ if (!function_exists('aksrt_archive_render_stream')) :
<?php if ($has_posts) : ?>
<?php
if (function_exists('aksrt_render_post_list')) {
aksrt_render_post_list($query, array_merge($render_config, ['wrapper_class' => 'content-stream__list']));
}
aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']), $query);
?>
<div class="content-stream__footer">
<?php
if (function_exists('aksrt_render_pagination')) {
aksrt_render_pagination($query, [
'query_context' => (string) $args['query_context'],
'render_config' => $render_config,
]);
}
?>
</div>
<?php else : ?>
+1 -70
View File
@@ -1,44 +1,12 @@
<?php
/**
* AKSRT Flow Helper
*
* v4 起主题统一为 CMS / 资源站形态,不再提供 blog 形态分流。
* 历史 helper 全部保留签名以保证兼容,但实际行为已收敛:
*
* aksrt_get_site_profile() 始终返回 'cms'
* aksrt_get_term_flow() 始终返回 'inherit'(不再读取 term meta)
* aksrt_is_resource_post() 仍按 _aksrt_is_resource meta 判定
* aksrt_get_post_flow() 始终返回 'cms'
* aksrt_is_cms_mode() 始终返回 true
* aksrt_flow_layout_for() 始终返回 'grid-cms'
* Resource-post predicate.
*
* @package AKSRT
*/
defined('ABSPATH') || exit;
if (!function_exists('aksrt_get_site_profile')) {
/**
* @deprecated v4 起恒为 'cms',保留仅为向后兼容。
* @return string 'cms'
*/
function aksrt_get_site_profile(): string {
return 'cms';
}
}
if (!function_exists('aksrt_get_term_flow')) {
/**
* @deprecated v4 起恒为 'inherit',term meta 不再生效。
* @param int $term_id
* @return string
*/
function aksrt_get_term_flow(int $term_id): string {
unset($term_id);
return 'inherit';
}
}
if (!function_exists('aksrt_is_resource_post')) {
/**
* 是否为资源帖:普通 post + _aksrt_is_resource meta。
@@ -54,40 +22,3 @@ if (!function_exists('aksrt_is_resource_post')) {
return (bool) get_post_meta($p->ID, '_aksrt_is_resource', true);
}
}
if (!function_exists('aksrt_get_post_flow')) {
/**
* @deprecated v4 起恒为 'cms'。
*
* @param int|WP_Post|null $post
* @return string 'cms'
*/
function aksrt_get_post_flow($post = null): string {
unset($post);
return 'cms';
}
}
if (!function_exists('aksrt_is_cms_mode')) {
/**
* 当前请求是否处在 CMS 形态。v4 起全站统一 CMS。
*
* @return bool
*/
function aksrt_is_cms_mode(): bool {
return true;
}
}
if (!function_exists('aksrt_flow_layout_for')) {
/**
* @deprecated v4 起恒为 'grid-cms'。
*
* @param string $flow
* @return string
*/
function aksrt_flow_layout_for(string $flow): string {
unset($flow);
return 'grid-cms';
}
}
+5 -75
View File
@@ -77,8 +77,6 @@ if (!function_exists('aksrt_resolve_single_post_display_settings')) :
'related_enabled' => true,
'related_count' => 6,
'related_style' => 'img',
'related_source' => 'category',
'related_type' => 'category',
'related_orderby' => 'views',
'related_title' => '',
'breadcrumbs_single_text' => true,
@@ -130,17 +128,6 @@ if (!function_exists('aksrt_resolve_single_post_display_settings')) :
if (!array_key_exists('authordesc', $settings) && array_key_exists('authordesc_s', $settings)) {
$settings['authordesc'] = !empty($settings['authordesc_s']);
}
// related_style / related_source (legacy related_type held either).
if (!array_key_exists('related_style', $settings) || !in_array((string) $settings['related_style'], ['img', 'list', 'text'], true)) {
$legacy = sanitize_key((string) ($settings['related_type'] ?? ''));
$settings['related_style'] = in_array($legacy, ['img', 'list', 'text'], true) ? $legacy : 'img';
}
if (!array_key_exists('related_source', $settings) || !in_array((string) $settings['related_source'], ['category', 'tag', 'random'], true)) {
$legacy = sanitize_key((string) ($settings['related_type'] ?? ''));
$settings['related_source'] = in_array($legacy, ['category', 'tag', 'random'], true) ? $legacy : 'category';
}
// Keep related_type as source alias for older callers.
$settings['related_type'] = (string) $settings['related_source'];
$meta = class_exists('\AKSRT\Core\PostMetaBox') ? \AKSRT\Core\PostMetaBox::get_meta($post->ID) : [];
// show_toc new route: '' follow | '1' on | '0' off
if (array_key_exists('show_toc', $meta) && $meta['show_toc'] !== '' && $meta['show_toc'] !== null) {
@@ -398,7 +385,7 @@ if (!function_exists('aksrt_render_mini_post_item')) :
/**
* Render a compact sidebar post item for homepage widgets.
*
* Thin wrapper over the unified shared renderer (mode=mini).
* Render the dedicated Zibll mini-list item.
*
* @param int|WP_Post|null $post
* @param array<string, mixed> $args
@@ -408,72 +395,15 @@ if (!function_exists('aksrt_render_mini_post_item')) :
if (!($post instanceof WP_Post)) {
return;
}
// Set up the global post so the shared renderer works
global $wp_query;
$original_post = isset($wp_query->post) ? $wp_query->post : null;
$wp_query->post = $post;
setup_postdata($post);
if (function_exists('aksrt_render_post')) {
$config = [
'mode' => 'mini',
'context' => 'sidebar',
'show_thumb' => true,
'show_meta' => !isset($args['show_views']) || !empty($args['show_views']),
];
if (!empty($args['class']) && is_string($args['class'])) {
$config['extra_class'] = $args['class'];
$config = aksrt_renderer_config([]);
$data = aksrt_renderer_collect_post_data($config);
if ($data) {
echo aksrt_renderer_mini($data, $config); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
echo aksrt_render_post($config); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
} else {
// Fallback: self-contained mini rendering
$post_id = (int) $post->ID;
$title = get_the_title($post_id);
$permalink = get_permalink($post_id);
$thumb_url = has_post_thumbnail($post_id)
? (string) get_the_post_thumbnail_url($post_id, 'medium')
: '';
if ($thumb_url === '' && function_exists('aksrt_get_first_content_image_url')) {
$thumb_url = (string) aksrt_get_first_content_image_url($post_id);
}
$placeholder_url = get_template_directory_uri() . '/assets/img/placeholder.svg';
$view_count = function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post_id) : 0;
$show_views = !isset($args['show_views']) || !empty($args['show_views']);
$date_text = function_exists('aksrt_time_ago')
? aksrt_time_ago((string) $post->post_date)
: get_the_date('Y-m-d', $post_id);
$classes = ['posts-mini'];
if (!empty($args['class']) && is_string($args['class'])) {
foreach (preg_split('/\s+/', trim($args['class'])) as $extra_class) {
$extra_class = sanitize_html_class($extra_class);
if ($extra_class !== '') {
$classes[] = $extra_class;
}
}
}
?>
<article class="<?php echo esc_attr(implode(' ', array_unique($classes))); ?>">
<a href="<?php echo esc_url($permalink); ?>" class="item-thumbnail" aria-label="<?php echo esc_attr($title); ?>">
<img<?php echo aksrt_get_lazy_attr('lazy_posts_thumb', (string) ($thumb_url !== '' ? $thumb_url : $placeholder_url), 'fit-cover'); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> alt="<?php echo esc_attr($title); ?>">
</a>
<div class="posts-mini-con">
<h4 class="item-heading">
<a href="<?php echo esc_url($permalink); ?>"><?php echo esc_html($title); ?></a>
</h4>
<div class="item-meta">
<span class="meta-item"><?php echo esc_html($date_text); ?></span>
<?php if ($show_views) : ?>
<span class="meta-dot" aria-hidden="true"></span>
<span class="meta-item">
<i class="fas fa-eye" aria-hidden="true"></i>
<?php echo esc_html(function_exists('aksrt_format_number') ? aksrt_format_number($view_count) : (string) $view_count); ?>
</span>
<?php endif; ?>
</div>
</div>
</article>
<?php
}
// Restore original post
if ($original_post instanceof WP_Post) {
$wp_query->post = $original_post;
setup_postdata($original_post);
-28
View File
@@ -130,19 +130,6 @@ if (!function_exists('aksrt_homepage_get_tab_posts_per_page')) {
}
}
if (!function_exists('aksrt_homepage_get_default_view_mode')) {
/**
* Default homepage view mode.
*/
function aksrt_homepage_get_default_view_mode(): string {
if (class_exists('\AKSRT\Settings\Settings')) {
return \AKSRT\Settings\Settings::homepage_default_view();
}
return 'list';
}
}
if (!function_exists('aksrt_homepage_sticky_tabs_enabled')) {
/**
* Whether homepage tabs should stick below the primary nav.
@@ -373,21 +360,6 @@ if (!function_exists('aksrt_homepage_render_sortbar')) {
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$view_mode = 'list';
if (function_exists('aksrt_resolve_post_list_render_config')) {
$home_view_cfg = aksrt_resolve_post_list_render_config(['context' => 'home']);
$view_mode = (($home_view_cfg['mode'] ?? 'list') === 'card') ? 'card' : 'list';
}
?>
<div class="aksrt-homepage__view-toggle" data-aksrt-view-toggle>
<button type="button" class="aksrt-homepage__view-btn<?php echo $view_mode === 'list' ? ' is-active' : ''; ?>" data-view-mode="list" aria-label="<?php esc_attr_e('列表视图', 'aksrt'); ?>">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M1 3h14v2H1V3zm0 4h14v2H1V7zm0 4h14v2H1v-2z"/></svg>
</button>
<button type="button" class="aksrt-homepage__view-btn<?php echo $view_mode === 'card' ? ' is-active' : ''; ?>" data-view-mode="card" aria-label="<?php esc_attr_e('卡片视图', 'aksrt'); ?>">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M1 1h4v4H1V1zm5 0h4v4H6V1zm5 0h4v4h-4V1zM1 6h4v4H1V6zm5 0h4v4H6V6zm5 0h4v4h-4V6zM1 11h4v4H1v-4zm5 0h4v4H6v-4zm5 0h4v4h-4v-4z"/></svg>
</button>
</div>
</div>
<?php
}
+2 -2
View File
@@ -127,8 +127,8 @@ if (!function_exists('aksrt_get_win_runtime_config')) :
'signup_url' => $signup,
'ajax_url' => admin_url('admin-ajax.php'),
'ajaxpager' => '1',
'ajax_trigger' => (string) ($list_style['ajax_trigger_text'] ?? __('加载更多', 'aksrt')),
'ajax_nomore' => (string) ($list_style['ajax_nomore_text'] ?? __('没有更多了', 'aksrt')),
'ajax_trigger' => (string) ($list_style['ajax_trigger'] ?? '<i class="fa fa-angle-right"></i>加载更多'),
'ajax_nomore' => (string) ($list_style['ajax_nomore'] ?? __('没有更多内容了', 'aksrt')),
'qj_loading' => __('加载中...', 'aksrt'),
'is_mobile' => $is_mobile ? '1' : '',
'is_logged_in' => is_user_logged_in() ? '1' : '',
+6 -8
View File
@@ -481,15 +481,13 @@ if (!function_exists('aksrt_render_related_graphic_card')) :
* Minimal Zibll style-3 related thumb card.
*/
function aksrt_render_related_graphic_card(WP_Post $post): string {
$title = get_the_title($post);
$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);
$thumb = get_the_post_thumbnail_url($post, 'medium_large');
if (!$thumb) {
$thumb = get_theme_file_uri('assets/img/thumbnail-lg.svg');
if (!file_exists(get_theme_file_path('assets/img/thumbnail-lg.svg'))) {
$thumb = includes_url('images/media/default.png');
}
}
$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'));
$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);
+44 -76
View File
@@ -568,12 +568,8 @@ if (!function_exists('aksrt_single_related_query_args')) :
*/
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_source'] ?? ($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)) {
if (!in_array($orderby, ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'], true)) {
$orderby = 'views';
}
@@ -587,23 +583,7 @@ if (!function_exists('aksrt_single_related_query_args')) :
'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.
$cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']);
if (!empty($cat_ids) && !is_wp_error($cat_ids)) {
$tax_query[] = [
@@ -620,7 +600,6 @@ if (!function_exists('aksrt_single_related_query_args')) :
'terms' => array_map('intval', $tag_ids),
];
}
}
if (count($tax_query) > 1) {
$args['tax_query'] = $tax_query;
@@ -636,6 +615,17 @@ if (!function_exists('aksrt_single_related_query_args')) :
case 'modified':
$args['orderby'] = 'modified';
break;
case 'like':
$args['meta_key'] = '_aksrt_like_count';
$args['orderby'] = ['meta_value_num' => 'DESC', 'date' => 'DESC'];
break;
case 'favorite':
$args['meta_key'] = '_aksrt_favorite_count';
$args['orderby'] = ['meta_value_num' => 'DESC', 'date' => 'DESC'];
break;
case 'comment_count':
$args['orderby'] = 'comment_count';
break;
case 'views':
$args['meta_query'] = [
'relation' => 'OR',
@@ -651,10 +641,8 @@ if (!function_exists('aksrt_single_related_query_args')) :
];
$args['orderby'] = ['views_value' => 'DESC', 'date' => 'DESC'];
break;
case 'relevance':
default:
// Prefer date when no search relevance context.
$args['orderby'] = 'date';
$args['orderby'] = 'modified';
break;
}
@@ -695,74 +683,54 @@ if (!function_exists('aksrt_single_render_related')) :
];
$query = new WP_Query($args);
if (!$query->have_posts()) {
wp_reset_postdata();
$empty_wrap = 'theme-box relates' . ($style === 'img' ? ' relates-thumb' : '');
?>
<div class="<?php echo esc_attr($empty_wrap); ?>">
<div class="box-body notop">
<div class="title-theme"><?php echo esc_html($title); ?></div>
</div>
<div class="zib-widget">
<?php echo function_exists('aksrt_get_null') ? aksrt_get_null(__('暂无相关文章', 'aksrt'), 40, 'null.svg', 'related-null', 220) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
</div>
<?php
return;
}
$wrap_class = 'theme-box relates' . ($style === 'img' ? ' relates-thumb' : '');
$posts_html = '';
$mini_cfg = aksrt_renderer_config([
'type' => 'list',
'show_number' => false,
]);
while ($query->have_posts()) {
$query->the_post();
$post = get_post();
if (!($post instanceof WP_Post)) {
continue;
}
if ($style === 'list') {
$data = aksrt_renderer_collect_post_data($mini_cfg);
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);
} else {
$subtitle = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post->ID, 'subtitle', '')
: '';
$posts_html .= '<li><a class="icon-circle" href="' . esc_url(get_permalink($post)) . '">' . esc_html(get_the_title($post) . $subtitle) . '</a></li>';
}
}
wp_reset_postdata();
if ($posts_html === '') {
$posts_html = '<li>' . esc_html__('暂无相关文章', 'aksrt') . '</li>';
}
?>
<div class="<?php echo esc_attr($wrap_class); ?>">
<div class="box-body notop">
<div class="title-theme"><?php echo esc_html($title); ?></div>
</div>
<div class="zib-widget">
<?php if ($style === 'list' && function_exists('aksrt_render_post_list')) : ?>
<?php
// New route: single query only.
aksrt_render_post_list($query, [
'context' => 'related',
'mode' => 'mini',
'show_thumb' => true,
'show_meta' => true,
'show_number' => false,
]);
?>
<?php else : ?>
<?php
$posts_html = '';
while ($query->have_posts()) {
$query->the_post();
$p = get_post();
if (!($p instanceof WP_Post)) {
continue;
}
if ($style === 'img' && function_exists('aksrt_render_related_graphic_card')) {
$posts_html .= aksrt_render_related_graphic_card($p);
} else {
$posts_html .= '<li><a class="icon-circle" href="' . esc_url(get_permalink($p)) . '">' . esc_html(get_the_title($p)) . '</a></li>';
}
}
?>
<?php if ($style === 'img') : ?>
<div class="swiper-container swiper-scroll">
<div class="swiper-wrapper">
<?php echo $posts_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</div>
<div class="swiper-wrapper"><?php echo $posts_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
<?php else : ?>
<ul class="no-thumb">
<?php echo $posts_html !== '' ? $posts_html : '<li>' . esc_html__('暂无相关文章', 'aksrt') . '</li>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
</ul>
<?php endif; ?>
<ul class="no-thumb"><?php echo $posts_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></ul>
<?php endif; ?>
</div>
</div>
<?php
wp_reset_postdata();
}
endif;
+3 -48
View File
@@ -206,12 +206,7 @@ if (!function_exists('aksrt_homepage_get_latest_panel_contract')) {
unset($query_vars['paged'], $query_vars['page']);
$render_config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config(['context' => 'home'])
: [
'mode' => 'list',
'context' => 'home',
];
$render_config = aksrt_resolve_post_list_render_config(['context' => 'home']);
$render_config['skip_wrapper'] = true;
return [
@@ -364,57 +359,17 @@ if (!function_exists('aksrt_get_post_gallery_images')) {
}
}
if (!function_exists('aksrt_homepage_render_post_card')) {
/**
* Render a homepage post card inline.
*/
function aksrt_homepage_render_post_card(): void {
if (!function_exists('aksrt_render_post')) {
return;
}
$config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config(['context' => 'home'])
: [
'mode' => 'list',
'context' => 'home',
];
echo aksrt_render_post($config); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
if (!function_exists('aksrt_homepage_render_posts_grid')) {
/**
* Render a list block for the fixed homepage.
*/
function aksrt_homepage_render_posts_grid(?WP_Query $query = null, bool $with_pagination = false): void {
$query = $query instanceof WP_Query ? $query : aksrt_homepage_get_latest_query();
$config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config(['context' => 'home'])
: [
'mode' => 'list',
'context' => 'home',
];
$config = aksrt_resolve_post_list_render_config(['context' => 'home']);
$config['skip_wrapper'] = true;
$view = (($config['mode'] ?? 'list') === 'card') ? 'card' : 'list';
if (function_exists('aksrt_render_post_list')) {
// Panel already provides posts-row.ajaxpager; avoid nested wrapper.
aksrt_render_post_list($query, $config, true);
} else {
if (!$query->have_posts()) {
echo '<div class="no-results"><p>' . esc_html__('暂无文章', 'aksrt') . '</p></div>';
} else {
echo '<div class="posts-row aksrt-homepage__posts" data-view="' . esc_attr($view) . '">';
while ($query->have_posts()) {
$query->the_post();
aksrt_homepage_render_post_card();
}
echo '</div>';
}
wp_reset_postdata();
}
aksrt_render_post_list($config, $query, true);
if ($with_pagination) {
aksrt_homepage_render_pagination($query);
+175 -284
View File
@@ -1,9 +1,6 @@
<?php
/**
* Shared post renderer helpers for archive, widget, and related lists.
*
* Centralizes card/list/mini rendering to avoid duplicating DOM contracts
* across category.php, search.php, widgets, and home modules.
* Zibll 9.0 article-list configuration and image resolution.
*
* @package AKSRT
*/
@@ -11,355 +8,249 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_renderer_defaults')) {
/**
* Base renderer config defaults.
*
* @return array<string,mixed>
*/
/** @return array<string,mixed> */
function aksrt_renderer_defaults(): array {
return [
'context' => 'archive',
'mode' => 'list',
'columns' => 1,
'style' => 'default',
'card_style' => 'standard',
'heading_bold' => true,
'show_excerpt' => true,
'excerpt_words' => 36,
'show_meta' => true,
'show_author' => true,
'show_date' => true,
'show_views' => true,
'show_comments' => true,
'show_likes' => false,
'show_badges' => true,
'show_tags' => true,
'tag_limit' => 3,
'category_limit' => 1,
'show_thumb' => true,
'thumb_ratio' => '4:3',
'thumb_fit' => 'cover',
'thumb_if_has' => false,
'target_blank' => false,
'title_html' => '',
'excerpt_html' => '',
'badge_show' => true,
'show_resource_badge' => true,
'multi_thumb_enabled' => true,
'multi_thumb_limit' => 4,
'multi_thumb_min' => 2,
'list_show_type' => 'separate',
'list_img_position' => 'left',
'number_offset' => 0,
'show_number' => false,
'extra_class' => '',
'type' => 'auto',
'no_author' => false,
'no_margin' => false,
'is_mult_thumb' => false,
'is_no_thumb' => false,
'is_card' => false,
'is_category' => is_category(),
'is_search' => is_search(),
'is_home' => is_home() || is_front_page(),
'is_author' => is_author(),
'is_tag' => is_tag(),
'highlight_query' => '',
'wrapper_class' => '',
'skip_wrapper' => false,
];
}
}
if (!function_exists('aksrt_renderer_config')) {
/**
* Merge renderer config with context-aware defaults.
* Resolve the one canonical Zibll list/card decision matrix.
*
* @param array<string,mixed> $config User config.
* @param array<string,mixed> $args Caller arguments.
* @return array<string,mixed>
*/
function aksrt_renderer_config(array $config = []): array {
$cfg = wp_parse_args($config, aksrt_renderer_defaults());
function aksrt_renderer_config(array $args = []): array {
$args = wp_parse_args($args, aksrt_renderer_defaults());
$list = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_list_style_settings()
: [];
$thumb = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_thumb_settings()
: [];
$cfg['mode'] = in_array($cfg['mode'], ['list', 'card', 'mini', 'grid-cms'], true)
? $cfg['mode']
: 'list';
$cfg['columns'] = max(1, min(6, (int) $cfg['columns']));
$cfg['excerpt_words'] = max(0, (int) $cfg['excerpt_words']);
$cfg['tag_limit'] = max(0, (int) $cfg['tag_limit']);
$cfg['category_limit'] = max(0, (int) $cfg['category_limit']);
$cfg['multi_thumb_limit'] = max(1, min(6, (int) $cfg['multi_thumb_limit']));
$cfg['multi_thumb_min'] = max(1, min(6, (int) $cfg['multi_thumb_min']));
if ($cfg['mode'] === 'mini') {
$cfg['show_excerpt'] = false;
$cfg['show_badges'] = false;
$cfg['show_tags'] = false;
$cfg['show_author'] = false;
$cfg['show_comments'] = false;
$list_type = sanitize_key((string) ($list['list_type'] ?? 'thumb'));
$forced_type = sanitize_key((string) $args['type']);
$is_forced = in_array($forced_type, ['list', 'card'], true);
$is_card = $forced_type === 'card' || (!$is_forced && !empty($args['is_card']));
if (!$is_forced && !$is_card && $list_type === 'card') {
$is_card = true;
}
if (!$is_forced && !$is_card) {
$is_card = (!empty($args['is_tag']) && !empty($list['list_card_tag']))
|| (!empty($args['is_home']) && !empty($list['list_card_home']))
|| (!empty($args['is_author']) && !empty($list['list_card_author']));
}
if (!$is_forced && !$is_card) {
$term_id = (int) get_queried_object_id();
$is_card = $term_id > 0 && in_array($term_id, (array) ($list['list_card_cat'] ?? []), true);
}
if (in_array((string) $cfg['style'], ['default', 'null'], true)) {
$cfg['style'] = '';
}
$cfg['list_img_position'] = in_array(sanitize_key((string) ($cfg['list_img_position'] ?? 'left')), ['left', 'right'], true)
? sanitize_key((string) ($cfg['list_img_position'] ?? 'left'))
: 'left';
$cfg['list_show_type'] = in_array(sanitize_key((string) ($cfg['list_show_type'] ?? 'separate')), ['separate', 'no_margin'], true)
? sanitize_key((string) ($cfg['list_show_type'] ?? 'separate'))
: 'separate';
return $cfg;
}
$list_option = (array) ($list['list_list_option'] ?? []);
$card_option = (array) ($list['list_card_option'] ?? []);
$style = sanitize_key((string) (($is_card ? $card_option : $list_option)['style'] ?? 'null'));
if (!in_array($style, ['style2', 'style3'], true)) {
$style = '';
}
if (!function_exists('aksrt_renderer_sanitize_inline_html')) {
/**
* Sanitize small inline HTML snippets used by render config overrides.
*
* @param string $html Raw HTML.
* @return string
*/
function aksrt_renderer_sanitize_inline_html(string $html): string {
return wp_kses($html, [
'span' => ['class' => true, 'aria-hidden' => true],
'em' => ['class' => true],
'strong' => ['class' => true],
'i' => ['class' => true, 'aria-hidden' => true],
'small'=> ['class' => true],
]);
$args['mode'] = $is_card ? 'card' : 'list';
$args['list_type'] = $list_type;
$args['style'] = $style;
$args['img_position'] = sanitize_key((string) ($list_option['img_position'] ?? 'left')) === 'right' ? 'right' : 'left';
$args['list_scale'] = max(50, min(200, (int) ($list_option['scale'] ?? 70)));
$args['card_scale'] = max(20, min(200, (int) ($card_option['scale'] ?? 70)));
$args['no_margin'] = !empty($args['no_margin']) || (($list['list_show_type'] ?? 'separate') === 'no_margin');
$args['no_author'] = !empty($args['no_author']) || !empty($args['is_author']);
$args['target_blank'] = !empty($list['target_blank']);
$args['heading_bold'] = !empty($list['item_heading_bold']);
$args['show_author'] = !empty($list['post_list_author']);
$args['show_time'] = !empty($list['post_list_time']);
$args['time_source'] = (string) ($list['post_time_source'] ?? 'modified');
$args['list_orderby'] = (string) ($list['list_orderby'] ?? 'modified');
$args['mobile_meta'] = (string) ($list['list_meta_show'] ?? 'like');
$args['badge_types'] = (array) ($list['list_badge_show'] ?? ['pay', 'cat', 'tag']);
$args['mult_thumb_cat'] = (array) ($list['mult_thumb_cat'] ?? []);
$args['thumb'] = $thumb;
$args['allow_conditional_layout'] = aksrt_is_show_sidebar() || wp_is_mobile();
$page = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_page_settings()
: [];
$comments = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_comment_display_settings()
: [];
$args['show_like'] = !array_key_exists('like_enabled', $page) || !empty($page['like_enabled']);
$args['close_comments'] = !empty($comments['close_comments']);
return $args;
}
}
if (!function_exists('aksrt_renderer_highlight_text')) {
/**
* Highlight plain text query matches using a safe span wrapper.
*
* @param string $text Plain text.
* @param string $query Highlight query.
* @return string
*/
function aksrt_renderer_highlight_text(string $text, string $query): string {
$text = wp_strip_all_tags($text);
$query = trim($query);
if ($query === '' || $text === '') {
return esc_html($text);
}
$words = preg_split('/\s+/u', $query) ?: [];
$words = array_values(array_unique(array_filter(array_map('trim', $words), static function ($word) {
return $word !== '' && mb_strlen($word) >= 2;
})));
if (empty($words)) {
$words = array_values(array_unique(array_filter(preg_split('/\s+/u', $query) ?: [])));
if (!$words) {
return esc_html($text);
}
$pattern = '/' . implode('|', array_map(static function ($word) {
return preg_quote($word, '/');
}, $words)) . '/iu';
$escaped = esc_html($text);
return (string) preg_replace($pattern, '<span class="focus-color">$0</span>', $escaped);
$pattern = '/' . implode('|', array_map(static fn(string $word): string => preg_quote($word, '/'), $words)) . '/iu';
return (string) preg_replace($pattern, '<span class="focus-color">$0</span>', esc_html($text));
}
}
if (!function_exists('aksrt_renderer_title_markup')) {
/**
* Resolve title markup, supporting highlighted search terms.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_title_markup(array $data, array $cfg): string {
if (!empty($cfg['title_html'])) {
return aksrt_renderer_sanitize_inline_html((string) $cfg['title_html']);
$title = aksrt_renderer_highlight_text((string) $data['title'], (string) ($cfg['highlight_query'] ?? ''));
if ((string) ($data['subtitle'] ?? '') !== '') {
$title .= '<span class="focus-color"> ' . esc_html((string) $data['subtitle']) . '</span>';
}
$title = aksrt_renderer_highlight_text((string) $data['title'], (string) $cfg['highlight_query']);
$subtitle = trim((string) ($data['subtitle'] ?? ''));
if ($subtitle !== '') {
$title .= '<span class="focus-color"> ' . esc_html($subtitle) . '</span>';
}
return $title;
}
}
if (!function_exists('aksrt_renderer_excerpt_markup')) {
/**
* Resolve excerpt markup, supporting highlighted search terms.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_excerpt_markup(array $data, array $cfg): string {
if (!empty($cfg['excerpt_html'])) {
return aksrt_renderer_sanitize_inline_html((string) $cfg['excerpt_html']);
}
return aksrt_renderer_highlight_text((string) $data['excerpt'], (string) $cfg['highlight_query']);
return aksrt_renderer_highlight_text((string) $data['excerpt'], (string) ($cfg['highlight_query'] ?? ''));
}
}
if (!function_exists('aksrt_renderer_curated_images')) {
/**
* Resolve curated cover images from post meta.
*
* @param int $post_id Post ID.
* @param int $limit Maximum items.
* @return string[]
*/
function aksrt_renderer_curated_images(int $post_id, int $limit = 6): array {
/** @return string[] */
function aksrt_renderer_curated_images(int $post_id, string $size, int $limit = 8): array {
$raw = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'featured_slide', '')
: '';
$images = [];
$limit = max(1, min(8, $limit));
if (!class_exists('\AKSRT\Core\PostMetaBox')) {
return [];
foreach (array_filter(preg_split('/[\s,]+/', $raw) ?: []) as $token) {
$image = aksrt_renderer_sized_image((string) $token, $size);
if ($image !== '') {
$images[] = $image;
}
$cover_images = (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'cover_images', '');
if ($cover_images === '') {
return [];
}
$cover_ids = array_filter(array_map('absint', preg_split('/[\s,]+/', $cover_images) ?: []));
foreach ($cover_ids as $attachment_id) {
$url = (string) wp_get_attachment_image_url($attachment_id, 'medium_large');
if ($url !== '') {
$images[] = esc_url_raw($url);
return array_slice($images, 0, $limit);
}
}
return array_slice(array_values(array_unique($images)), 0, $limit);
}
}
if (!function_exists('aksrt_renderer_attachment_images')) {
/**
* Resolve gallery/content images for list cards.
*
* @param int $post_id Post ID.
* @param int $limit Maximum items.
* @return string[]
*/
function aksrt_renderer_attachment_images(int $post_id, int $limit = 6): array {
$images = [];
$limit = max(1, min(8, $limit));
if (function_exists('aksrt_get_post_gallery_images')) {
$images = aksrt_get_post_gallery_images($post_id, $limit);
}
if (function_exists('aksrt_get_first_content_image_url')) {
$first_content_image = (string) aksrt_get_first_content_image_url($post_id);
if ($first_content_image !== '') {
$images[] = esc_url_raw($first_content_image);
}
}
return array_slice(array_values(array_unique(array_filter($images))), 0, $limit);
}
}
if (!function_exists('aksrt_renderer_random_fallback_image')) {
/**
* Pick a deterministic fallback image when "random cover" is enabled.
*
* @param int $post_id Post ID.
* @return string
*/
function aksrt_renderer_random_fallback_image(int $post_id): string {
$candidates = [
get_template_directory_uri() . '/assets/img/person_background.png',
get_template_directory_uri() . '/assets/img/placeholder.svg',
];
if (class_exists('\AKSRT\Settings\Settings')) {
$thumb = \AKSRT\Settings\Settings::get_post_thumb_settings();
$spare = $thumb['spare_images'] ?? [];
if (is_array($spare) && !empty($spare)) {
foreach ($spare as $url) {
if (is_string($url) && $url !== '') {
array_unshift($candidates, $url);
}
}
}
}
$candidates = array_values(array_unique(array_filter(array_map('esc_url_raw', $candidates))));
if (empty($candidates)) {
if (!function_exists('aksrt_renderer_sized_image')) {
function aksrt_renderer_sized_image(string $image, string $size): string {
$image = trim($image);
if ($image === '') {
return '';
}
$attachment_id = ctype_digit($image) ? (int) $image : 0;
if ($attachment_id === 0) {
$lookup_url = (string) preg_replace('/\?.*$/', '', $image);
$attachment_id = attachment_url_to_postid($lookup_url);
if ($attachment_id === 0) {
$full_url = (string) preg_replace('/-\d+x\d+(?=\.[^.]+$)/', '', $lookup_url);
$attachment_id = attachment_url_to_postid($full_url);
}
}
if ($attachment_id > 0) {
$sized = wp_get_attachment_image_url($attachment_id, $size);
if (is_string($sized) && $sized !== '') {
return esc_url_raw($sized);
}
}
return esc_url_raw($image);
}
}
$index = $post_id % count($candidates);
return $candidates[$index] ?? $candidates[0];
if (!function_exists('aksrt_renderer_content_images')) {
/** @return string[] */
function aksrt_renderer_content_images(int $post_id, string $size): array {
$content = strip_shortcodes((string) get_post_field('post_content', $post_id, 'raw'));
if ($content === '' || !preg_match_all('/<img.*?(?: |\t|\r|\n)?src=[\'\"]?(.+?)[\'\"]?(?:(?: |\t|\r|\n)+.*?)?>/sim', $content, $matches)) {
return [];
}
$images = [];
foreach ((array) ($matches[1] ?? []) as $image) {
$sized = aksrt_renderer_sized_image((string) $image, $size);
if ($sized !== '') {
$images[] = $sized;
}
}
return $images;
}
}
if (!function_exists('aksrt_renderer_category_image')) {
function aksrt_renderer_category_image(int $post_id, string $size): string {
foreach ((array) get_the_category($post_id) as $category) {
$attachment_id = (int) get_term_meta((int) $category->term_id, 'aksrt_category_cover_id', true);
if ($attachment_id > 0) {
return aksrt_renderer_sized_image((string) $attachment_id, $size);
}
}
return '';
}
}
if (!function_exists('aksrt_renderer_spare_image')) {
function aksrt_renderer_spare_image(array $thumb): string {
$images = array_values(array_filter((array) ($thumb['spare_thumbnail'] ?? [])));
return $images ? (string) $images[array_rand($images)] : '';
}
}
if (!function_exists('aksrt_renderer_resolve_images')) {
/**
* Resolve primary + gallery images for a post based on card settings.
*
* @param int $post_id Post ID.
* @param array<string,mixed> $cfg Renderer config.
* @return array{primary:string,gallery:string[],has_thumb:bool,has_real:bool}
*/
/** @return array{primary:string,gallery:string[],slides:string[],has_real:bool,is_slide:bool,video_url:string} */
function aksrt_renderer_resolve_images(int $post_id, array $cfg): array {
$thumb_url = get_the_post_thumbnail_url($post_id, 'medium_large');
$curated_images = !empty($cfg['thumb_if_has']) || !empty($cfg['multi_thumb_enabled'])
? aksrt_renderer_curated_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
: [];
$content_images = !empty($cfg['multi_thumb_enabled'])
? aksrt_renderer_attachment_images($post_id, max(4, (int) $cfg['multi_thumb_limit']))
: [];
$thumb = (array) ($cfg['thumb'] ?? []);
$size = (string) ($thumb['thumb_postfirstimg_size'] ?? 'medium');
$slides = aksrt_renderer_curated_images($post_id, $size);
$gallery = aksrt_renderer_content_images($post_id, $size);
$video_url = class_exists('\AKSRT\Core\PostMetaBox')
? esc_url_raw((string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'featured_video', ''))
: '';
$gallery = [];
if (!empty($curated_images)) {
$gallery = array_merge($gallery, $curated_images);
$thumbnail_id = (int) get_post_thumbnail_id($post_id);
$primary = $thumbnail_id > 0 ? aksrt_renderer_sized_image((string) $thumbnail_id, $size) : '';
if ($primary === '') {
$primary = class_exists('\AKSRT\Core\PostMetaBox')
? esc_url_raw((string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'thumbnail_url', ''))
: '';
}
if (!empty($content_images)) {
$gallery = array_merge($gallery, $content_images);
if ($primary === '' && !empty($thumb['thumb_postfirstimg_s']) && !empty($gallery[0])) {
$primary = (string) $gallery[0];
}
if ($primary === '' && !empty($thumb['thumb_catimg_s'])) {
$primary = aksrt_renderer_category_image($post_id, $size);
}
$gallery = array_values(array_unique(array_filter($gallery)));
$primary = is_string($thumb_url) && $thumb_url !== '' ? esc_url_raw($thumb_url) : '';
$has_real = $primary !== '';
if ($primary === '' && !empty($curated_images[0])) {
$primary = $curated_images[0];
$has_real = true;
}
if ($primary === '' && !empty($content_images[0])) {
$primary = $content_images[0];
$has_real = true;
}
$thumb_settings = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_thumb_settings() : [];
$fallback = sanitize_key((string) ($thumb_settings['fallback'] ?? 'placeholder'));
if ($primary === '' && empty($cfg['thumb_if_has'])) {
if ($fallback === 'random') {
$primary = aksrt_renderer_random_fallback_image($post_id);
} elseif ($fallback === 'placeholder') {
$primary = aksrt_get_lazy_thumb();
if ($primary === '') {
$primary = aksrt_renderer_spare_image($thumb);
}
if ($primary === '') {
$primary = (string) aksrt_get_lazy_thumb();
}
return [
'primary' => $primary,
'primary' => esc_url_raw($primary),
'gallery' => $gallery,
'has_thumb' => $primary !== '',
'slides' => $slides,
'has_real' => $has_real,
'is_slide' => !empty($thumb['list_thumb_slides_s']) && count($slides) > 0,
'video_url' => $video_url,
];
}
}
if (!function_exists('aksrt_renderer_thumb_ratio_value')) {
/**
* Convert ratio tokens like 4:3 to CSS aspect ratio values.
*
* @param string $ratio Ratio token.
* @return string
*/
function aksrt_renderer_thumb_ratio_value(string $ratio): string {
$ratio = trim($ratio);
if (preg_match('/^(\d+)\s*:\s*(\d+)$/', $ratio, $matches)) {
$w = max(1, (int) $matches[1]);
$h = max(1, (int) $matches[2]);
return $w . ' / ' . $h;
}
return '4 / 3';
}
}
+124 -269
View File
@@ -1,6 +1,6 @@
<?php
/**
* Shared post renderer helpers.
* Zibll 9.0 article-list DOM contract.
*
* @package AKSRT
*/
@@ -8,351 +8,206 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_renderer_list')) {
/**
* Render a horizontal list card.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_list(array $data, array $cfg): string {
$classes = ['posts-item', 'list', 'ajax-item', 'flex', 'posts-item--' . ($cfg['card_style'] ?: 'standard')];
if ($cfg['style'] !== '' && $cfg['style'] !== 'default') {
$classes[] = $cfg['style'];
}
if ($cfg['style'] === 'style2') {
$classes[] = 'xx';
$classes = ['posts-item', 'list', 'ajax-item'];
if ((string) ($cfg['style'] ?? '') !== '') {
$classes[] = (string) $cfg['style'];
}
if (!empty($data['is_mult_thumb'])) {
$classes[] = 'mult-thumb';
}
if ($cfg['context'] === 'home') {
$classes[] = 'posts-item--home-list';
}
if (empty($data['has_img'])) {
if (!empty($data['is_no_thumb'])) {
$classes[] = 'no-thumb';
}
if (($cfg['list_show_type'] ?? 'separate') === 'no_margin') {
$classes[] = 'no-margin';
if (!empty($cfg['no_margin'])) {
$classes[] = 'no_margin';
}
$classes[] = ($cfg['style'] ?? '') === 'style2' ? 'flex xx' : 'flex';
$title = '<h2 class="item-heading' . (!empty($cfg['heading_bold']) ? ' font-bold' : '') . '"><a' . (!empty($cfg['target_blank']) ? ' target="_blank"' : '') . ' href="' . esc_url((string) $data['permalink']) . '">' . aksrt_renderer_title_markup($data, $cfg) . '</a></h2>';
$excerpt_class = ($cfg['style'] ?? '') === 'style2' ? 'text-ellipsis-2' : 'text-ellipsis';
$excerpt = '<div class="item-excerpt muted-color ' . $excerpt_class . ' mb6">' . aksrt_renderer_excerpt_markup($data, $cfg) . '</div>';
$thumb = aksrt_renderer_thumb($data, $cfg);
$mult = aksrt_renderer_mult_thumb($data);
if (!empty($data['is_mult_thumb'])) {
// Zibll's multi-image mode replaces the primary graphic; both must
// never be emitted for the same list item.
$thumb = '';
$mult = aksrt_renderer_mult_thumb($data, $cfg);
$graphic = '';
if ($thumb !== '' && empty($data['is_mult_thumb'])) {
$graphic = '<div class="post-graphic' . (($cfg['img_position'] ?? 'left') === 'right' ? ' order1' : '') . '">' . $thumb . '</div>';
}
if ($cfg['card_style'] === 'text' || ($cfg['card_style'] === 'thumb_if_has' && empty($data['has_img']))) {
$thumb = '';
$mult = '';
$classes[] = 'no-thumb';
$classes[] = 'posts-item--text';
}
$title_markup = aksrt_renderer_title_markup($data, $cfg);
$excerpt_markup = aksrt_renderer_excerpt_markup($data, $cfg);
$badges = aksrt_renderer_badges($data, $cfg);
$meta = aksrt_renderer_meta($data, $cfg);
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
$heading_cls = !empty($cfg['heading_bold']) ? 'item-heading item-heading--bold' : 'item-heading';
$title_html = '<h2 class="' . $heading_cls . '"><a href="' . esc_url($data['permalink']) . '" rel="bookmark"' . $target_attr . '>' . $title_markup . '</a></h2>';
$excerpt_html = $cfg['show_excerpt'] && $excerpt_markup !== ''
? '<p class="item-excerpt">' . $excerpt_markup . '</p>'
: '';
$html = '<article ' . aksrt_renderer_article_attrs($classes, $data) . '>';
if ($cfg['style'] === 'style2') {
$html .= $title_html;
$html .= '<div class="flex">';
$html .= $thumb;
$html .= '<div class="item-body flex xx flex1 jsb">';
$html .= $mult ?: $excerpt_html;
$html .= $badges;
$html .= $meta;
$html .= '</div></div>';
$meta = aksrt_renderer_meta($data, $cfg, false);
$html = '<posts class="' . esc_attr(implode(' ', $classes)) . '">';
if (($cfg['style'] ?? '') === 'style2') {
$html .= $title . '<div class="flex">' . $graphic . '<div class="item-body flex xx flex1 jsb">';
$html .= $mult !== '' ? $mult : $excerpt;
$html .= $badges . $meta . '</div></div>';
} else {
$html .= $thumb;
$html .= '<div class="item-body flex xx flex1 jsb">';
$html .= $title_html;
$html .= $mult ?: $excerpt_html;
$html .= '<div>' . $badges . $meta . '</div>';
$html .= '</div>';
$html .= $graphic . '<div class="item-body flex xx flex1 jsb">' . $title;
$html .= $mult !== '' ? $mult : $excerpt;
$html .= '<div>' . $badges . $meta . '</div></div>';
}
$html .= '</article>';
return $html;
return $html . '</posts>';
}
}
if (!function_exists('aksrt_renderer_card')) {
/**
* Render a vertical card or CMS resource card.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_card(array $data, array $cfg): string {
$classes = ['posts-item', 'card', 'ajax-item', 'posts-item--' . ($cfg['card_style'] ?: 'standard')];
if ($cfg['style'] !== '' && $cfg['style'] !== 'default') {
$classes[] = $cfg['style'];
}
if ($cfg['context'] === 'home') {
$classes[] = 'posts-item--home';
}
if ($cfg['mode'] === 'grid-cms') {
$classes[] = 'posts-item--cms';
}
if (!empty($data['is_mult_thumb'])) {
$classes[] = 'mult-thumb';
}
if (empty($data['has_img'])) {
$classes[] = 'no-thumb';
$classes = ['posts-item', 'card', 'ajax-item'];
if ((string) ($cfg['style'] ?? '') !== '') {
$classes[] = (string) $cfg['style'];
}
$title = '<h2 class="item-heading' . (!empty($cfg['heading_bold']) ? ' font-bold' : '') . '"><a' . (!empty($cfg['target_blank']) ? ' target="_blank"' : '') . ' href="' . esc_url((string) $data['permalink']) . '">' . aksrt_renderer_title_markup($data, $cfg) . '</a></h2>';
$thumb = aksrt_renderer_thumb($data, $cfg);
$badges = aksrt_renderer_badges($data, $cfg);
$resource_strip = aksrt_renderer_resource_strip($data);
$meta = aksrt_renderer_meta($data, $cfg);
$title_markup = aksrt_renderer_title_markup($data, $cfg);
$html = '<article ' . aksrt_renderer_article_attrs($classes, $data) . '>';
$html .= $thumb;
$html .= '<div class="item-body">';
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
$heading_cls = !empty($cfg['heading_bold']) ? 'item-heading item-heading--bold' : 'item-heading';
$html .= '<h2 class="' . $heading_cls . '"><a href="' . esc_url($data['permalink']) . '" rel="bookmark"' . $target_attr . '>' . $title_markup . '</a></h2>';
$html .= $badges;
if ($cfg['mode'] === 'grid-cms') {
$html .= $resource_strip;
}
$html .= $meta;
$html .= '</div></article>';
return $html;
$html = '<posts class="' . esc_attr(implode(' ', $classes)) . '">';
$html .= aksrt_renderer_thumb($data, $cfg);
$html .= '<div class="item-body">' . $title;
$html .= aksrt_renderer_badges($data, $cfg);
$html .= aksrt_renderer_meta($data, $cfg, true);
return $html . '</div></posts>';
}
}
if (!function_exists('aksrt_renderer_mini')) {
/**
* Render a compact mini card.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_mini(array $data, array $cfg): string {
$classes = ['posts-mini'];
if (!empty($cfg['extra_class'])) {
$classes[] = $cfg['extra_class'];
function aksrt_renderer_mini(array $data, array $cfg = []): string {
$target = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
$thumb = '';
$show_thumb = !array_key_exists('show_thumb', $cfg) || !empty($cfg['show_thumb']);
$show_meta = !array_key_exists('show_meta', $cfg) || !empty($cfg['show_meta']);
if ($show_thumb && (string) $data['primary'] !== '') {
$default_attr = empty($data['has_real']) ? ' data-thumb="default"' : '';
$thumb = '<div class="mr10"><div class="item-thumbnail"><a' . $target . ' href="' . esc_url((string) $data['permalink']) . '"><img' . $default_attr . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['primary'], 'fit-cover radius8') . ' alt="' . esc_attr((string) $data['title']) . '"></a></div></div>';
}
if (empty($data['has_img'])) {
$classes[] = 'posts-mini--no-thumb';
$number = '';
if (!empty($cfg['show_number'])) {
$number_value = max(1, (int) ($cfg['number_offset'] ?? 1));
$colors = ['c-red', 'c-yellow', 'c-purple', 'c-blue', 'c-green'];
$number_class = $colors[$number_value - 1] ?? '';
$number = '<span class="badg badg-sm mr3 ' . esc_attr($number_class) . '">' . $number_value . '</span>';
}
$number_html = '';
if ($cfg['show_number'] && $cfg['number_offset'] > 0) {
$badge_colors = ['c-red', 'c-yellow', 'c-orange', 'c-blue', 'c-green'];
$index = $cfg['number_offset'] - 1;
$color = $badge_colors[$index] ?? '';
$number_html = '<span class="badg badg-sm mr3 ' . esc_attr($color) . '">' . (int) $cfg['number_offset'] . '</span>';
$title_class = $show_thumb ? ' text-ellipsis-2' : ' text-ellipsis' . (!$show_meta && empty($cfg['show_number']) ? ' icon-circle' : '');
$title = esc_html((string) $data['title']);
if ((string) ($data['subtitle'] ?? '') !== '') {
$title .= '<span class="focus-color">' . esc_html((string) $data['subtitle']) . '</span>';
}
$thumb_html = '';
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
if ($cfg['show_thumb'] && !empty($data['has_img'])) {
$fit_class = !empty($cfg['thumb_fit']) && $cfg['thumb_fit'] === 'contain' ? 'fit-contain' : 'fit-cover';
$thumb_attr = aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['img_url'], $fit_class);
$thumb_img = '<img' . $thumb_attr . ' alt="' . esc_attr($data['title']) . '">';
$thumb_html = '<a href="' . esc_url($data['permalink']) . '" class="item-thumbnail" aria-label="' . esc_attr($data['title']) . '"' . $target_attr . '>' . $thumb_img . '</a>';
$meta = '';
if ($show_meta) {
$show_time = !empty($cfg['show_time']);
if (!empty($cfg['show_author'])) {
$author_value = $show_time ? (string) $data['time_ago'] : (string) $data['author_name'];
$meta_left = '<item class="meta-author flex ac"><img class="avatar-mini" src="' . esc_url((string) $data['avatar_url']) . '" alt="" width="20" height="20"><span class="ml6">' . esc_html($author_value) . '</span></item>';
} else {
$meta_left = $show_time ? '<item title="' . esc_attr((string) $data['time_title']) . '" class="icon-circle mln3">' . esc_html((string) $data['time_ago']) . '</item>' : '';
}
$heading_class = 'item-heading' . (!empty($cfg['heading_bold']) ? ' item-heading--bold' : '') . (!empty($data['has_img']) ? ' text-ellipsis-2' : ' text-ellipsis');
$title_html = '<h2 class="' . esc_attr($heading_class) . '">' . $number_html . '<a href="' . esc_url($data['permalink']) . '"' . $target_attr . '>' . aksrt_renderer_title_markup($data, $cfg) . '</a></h2>';
$html = '<div class="' . esc_attr(implode(' ', array_unique($classes))) . '">';
$html .= $thumb_html;
$html .= '<div class="posts-mini-con flex xx flex1 jsb">';
$html .= $title_html;
$html .= aksrt_renderer_meta($data, $cfg);
$html .= '</div></div>';
return $html;
$pay = aksrt_renderer_pay_badge($data, true);
if ($meta_left === '') {
$meta_left = $pay;
$pay = '';
}
if ($meta_left === '') {
$meta_left = '<item></item>';
}
$meta_right = $pay . '<item class="meta-view">' . aksrt_renderer_svg_icon('view') . esc_html(aksrt_renderer_meta_number((int) $data['views'])) . '</item>';
$meta = '<div class="item-meta muted-2-color flex jsb ac' . (!$show_thumb ? ' mt6' : '') . '">' . $meta_left . '<div class="meta-right">' . $meta_right . '</div></div>';
}
$class = trim('posts-mini ' . (string) ($cfg['class'] ?? ''));
return '<div class="' . esc_attr($class) . '">' . $thumb . '<div class="posts-mini-con flex xx flex1 jsb"><h2 class="item-heading' . esc_attr($title_class) . '">' . $number . '<a' . $target . ' href="' . esc_url((string) $data['permalink']) . '">' . $title . '</a></h2>' . $meta . '</div></div>';
}
}
if (!function_exists('aksrt_render_post')) {
/**
* Render one post item inside a loop.
*
* @param array<string,mixed> $config Renderer config overrides.
* @return string
*/
function aksrt_render_post(array $config = []): string {
$cfg = aksrt_renderer_config($config);
function aksrt_render_post(array $args = []): string {
$cfg = aksrt_renderer_config($args);
$data = aksrt_renderer_collect_post_data($cfg);
if (empty($data)) {
if (!$data) {
return '';
}
switch ($cfg['mode']) {
case 'card':
case 'grid-cms':
return aksrt_renderer_card($data, $cfg);
case 'mini':
return aksrt_renderer_mini($data, $cfg);
case 'list':
default:
return aksrt_renderer_list($data, $cfg);
}
return ($cfg['mode'] ?? 'list') === 'card'
? aksrt_renderer_card($data, $cfg)
: aksrt_renderer_list($data, $cfg);
}
}
if (!function_exists('aksrt_render_post_list')) {
/**
* Render a query loop with the unified renderer.
* Render the canonical article stream.
*
* @param WP_Query|null $query Posts query. Falls back to global loop.
* @param array<string,mixed> $config Renderer config overrides.
* @param bool $echo Whether to echo output.
* @return string
* @param array<string,mixed> $args Zibll-shaped list arguments.
* @param WP_Query|null $query Optional custom query.
*/
function aksrt_render_post_list(?WP_Query $query = null, array $config = [], bool $echo = true): string {
$cfg = aksrt_renderer_config($config);
$wrapper_classes = ['posts-row', 'ajaxpager'];
if ($cfg['context'] === 'home') {
$wrapper_classes[] = 'aksrt-homepage__posts';
$wrapper_classes[] = 'post-grid';
$wrapper_classes[] = 'post-grid--home';
} elseif ($cfg['mode'] !== 'mini') {
$wrapper_classes[] = 'post-grid';
$wrapper_classes[] = 'post-grid--' . $cfg['mode'];
function aksrt_render_post_list(array $args = [], ?WP_Query $query = null, bool $echo = true): string {
$cfg = aksrt_renderer_config($args);
$has_posts = $query instanceof WP_Query ? $query->have_posts() : have_posts();
if (!$has_posts) {
$html = aksrt_get_null(__('暂无内容', 'aksrt'), 100, 'null-post.svg');
if ($echo) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return '';
}
return $html;
}
if (!empty($config['wrapper_class']) && is_string($config['wrapper_class'])) {
foreach (preg_split('/\s+/', trim($config['wrapper_class'])) as $extra_class) {
$extra_class = sanitize_html_class($extra_class);
if ($extra_class !== '') {
$wrapper_classes[] = $extra_class;
$classes = ['posts-row', 'ajaxpager'];
foreach (preg_split('/\s+/', trim((string) ($cfg['wrapper_class'] ?? ''))) ?: [] as $class) {
$class = sanitize_html_class($class);
if ($class !== '') {
$classes[] = $class;
}
}
}
$wrapper_attr = 'class="' . esc_attr(implode(' ', array_unique($wrapper_classes))) . '"';
if ($cfg['columns'] > 1) {
$wrapper_attr .= ' style="--aksrt-widget-columns:' . (int) $cfg['columns'] . '"';
}
$data_view = in_array($cfg['mode'], ['card', 'grid-cms'], true) ? 'card' : $cfg['mode'];
// Homepage panel already provides .posts-row.ajaxpager — allow callers to skip nesting.
$skip_wrapper = !empty($config['skip_wrapper']);
$html = '';
$html = empty($cfg['skip_wrapper'])
? '<div class="' . esc_attr(implode(' ', array_unique($classes))) . '" data-view="' . esc_attr((string) $cfg['mode']) . '">'
: '';
if ($query instanceof WP_Query) {
if (!$query->have_posts()) {
$html = '<div class="no-results"><p>' . esc_html__('暂无文章', 'aksrt') . '</p></div>';
} else {
if (!$skip_wrapper) {
$html .= '<div ' . $wrapper_attr . ' data-view="' . esc_attr($data_view) . '">';
}
$number = $cfg['number_offset'];
while ($query->have_posts()) {
$query->the_post();
if ($cfg['mode'] === 'mini' && $cfg['show_number']) {
$number++;
$config['number_offset'] = $number;
$cfg = aksrt_renderer_config($config);
}
$html .= aksrt_render_post($cfg);
}
if (!$skip_wrapper) {
$html .= '</div>';
}
}
wp_reset_postdata();
} else {
if (!have_posts()) {
$html = '<div class="no-results"><p>' . esc_html__('暂无文章', 'aksrt') . '</p></div>';
} else {
if (!$skip_wrapper) {
$html .= '<div ' . $wrapper_attr . ' data-view="' . esc_attr($data_view) . '">';
}
while (have_posts()) {
the_post();
$html .= aksrt_render_post($cfg);
}
if (!$skip_wrapper) {
}
if (empty($cfg['skip_wrapper'])) {
$html .= '</div>';
}
}
}
if ($echo) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return '';
}
return $html;
}
}
if (!function_exists('aksrt_render_mini_list')) {
/**
* Convenience wrapper for mini lists.
*
* @param WP_Query $query Posts query.
* @param array<string,mixed> $config Renderer config overrides.
* @param bool $echo Whether to echo output.
* @return string
*/
function aksrt_render_mini_list(WP_Query $query, array $config = [], bool $echo = true): string {
$config['mode'] = 'mini';
return aksrt_render_post_list($query, $config, $echo);
function aksrt_render_mini_list(WP_Query $query, array $args = [], bool $echo = true): string {
$cfg = aksrt_renderer_config($args);
$html = '';
$number = max(0, (int) ($args['number_offset'] ?? 0));
while ($query->have_posts()) {
$query->the_post();
$data = aksrt_renderer_collect_post_data($cfg);
if (!$data) {
continue;
}
$number++;
$html .= aksrt_renderer_mini($data, array_merge($cfg, ['number_offset' => $number]));
}
wp_reset_postdata();
if ($echo) {
echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return '';
}
return $html;
}
}
if (!function_exists('aksrt_render_placeholder')) {
/**
* Render skeleton placeholders for loading states.
*
* @param string $mode list|card|mini.
* @param int $count Number of placeholders.
* @return string
*/
function aksrt_render_placeholder(string $mode = 'list', int $count = 3): string {
$count = max(1, min(12, $count));
$html = '';
if ($mode === 'card') {
$item = '<div class="posts-item card"><div class="item-thumbnail"><div class="radius8 item-thumbnail placeholder"></div></div>'
. '<div class="item-body"><h2 class="item-excerpt placeholder t1 item-heading"></h2>'
. '<p class="mt10 placeholder k2"></p>'
. '<i class="flex jsb"><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
$html = str_repeat($item, $count);
} elseif ($mode === 'mini') {
$item = '<div class="posts-mini"><div class="item-thumbnail"><div class="radius8 placeholder"></div></div>'
. '<div class="posts-mini-con"><div class="placeholder t1"></div>'
. '<div class="placeholder k2 mt6"></div></div></div>';
$html = str_repeat($item, $count);
} else {
$item = '<div class="posts-item list flex"><div class="post-graphic"><div class="radius8 item-thumbnail placeholder"></div></div>'
. '<div class="item-body flex xx flex1 jsb"><p class="placeholder t1"></p>'
. '<h4 class="item-excerpt placeholder k1"></h4><p class="placeholder k2"></p>'
. '<i><i class="placeholder s1"></i><i class="placeholder s1 ml10"></i></i></div></div>';
$html = str_repeat($item, $count);
}
return $html;
$class = $mode === 'card' ? 'posts-item card' : 'posts-item list flex';
$item = '<posts class="' . esc_attr($class) . '"><div class="post-graphic"><div class="item-thumbnail placeholder"></div></div><div class="item-body flex xx flex1 jsb"><div class="placeholder t1"></div><div class="placeholder k2"></div><div class="placeholder s1"></div></div></posts>';
return str_repeat($item, $count);
}
}
+200 -219
View File
@@ -1,6 +1,6 @@
<?php
/**
* Shared post renderer helpers.
* Zibll 9.0 article-list data and markup fragments.
*
* @package AKSRT
*/
@@ -8,12 +8,7 @@
defined('ABSPATH') || exit;
if (!function_exists('aksrt_renderer_collect_post_data')) {
/**
* Aggregate the runtime data needed by all renderer modes.
*
* @param array<string,mixed> $cfg Renderer config.
* @return array<string,mixed>
*/
/** @return array<string,mixed> */
function aksrt_renderer_collect_post_data(array $cfg): array {
$post_id = (int) get_the_ID();
if ($post_id <= 0) {
@@ -21,278 +16,264 @@ if (!function_exists('aksrt_renderer_collect_post_data')) {
}
$images = aksrt_renderer_resolve_images($post_id, $cfg);
$gallery_images = array_slice($images['gallery'], 0, $cfg['multi_thumb_limit']);
$is_mult_thumb = !empty($cfg['multi_thumb_enabled'])
&& count($gallery_images) >= (int) $cfg['multi_thumb_min']
&& $cfg['mode'] === 'list';
$author_id = (int) get_post_field('post_author', $post_id);
$categories = get_the_category($post_id) ?: [];
$tags = get_the_tags($post_id) ?: [];
$content_images = (array) $images['gallery'];
$gallery = array_slice($content_images, 0, 4);
$allow_conditional = !empty($cfg['allow_conditional_layout']) && ($cfg['mode'] ?? 'list') === 'list';
$is_mult_thumb = false;
$is_no_thumb = false;
if ($allow_conditional) {
if (($cfg['is_no_thumb'] ?? false) !== 'disable'
&& (($cfg['list_type'] ?? 'thumb') === 'text'
|| (($cfg['list_type'] ?? 'thumb') === 'thumb_if_has' && empty($images['has_real'])))) {
$is_no_thumb = true;
} elseif (($cfg['is_mult_thumb'] ?? false) !== 'disable' && count($gallery) > 2) {
$is_mult_thumb = has_post_format(['image', 'gallery'], $post_id);
if (!$is_mult_thumb) {
foreach ($categories as $category) {
if (in_array((int) $category->term_id, array_map('intval', (array) ($cfg['mult_thumb_cat'] ?? [])), true)) {
$is_mult_thumb = true;
break;
}
}
}
}
}
if (($cfg['is_no_thumb'] ?? false) === true) {
$is_no_thumb = true;
$is_mult_thumb = false;
}
if (($cfg['is_mult_thumb'] ?? false) === true && count($gallery) > 2 && !$is_no_thumb) {
$is_mult_thumb = true;
}
$custom_excerpt = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'custom_excerpt', '')
: '';
$custom_subtitle = (string) get_post_meta($post_id, '_subtitle', true);
$excerpt_source = $custom_excerpt !== '' ? $custom_excerpt : (string) get_the_excerpt($post_id);
if ($excerpt_source === '') {
$excerpt_source = (string) get_post_field('post_content', $post_id, 'raw');
$excerpt = $custom_excerpt !== '' ? $custom_excerpt : (string) get_the_excerpt($post_id);
if ($excerpt === '') {
$excerpt = (string) get_post_field('post_content', $post_id, 'raw');
}
$excerpt = wp_trim_words(wp_strip_all_tags($excerpt_source), $cfg['excerpt_words'], '...');
$author_id = (int) get_post_field('post_author', $post_id);
$author = get_userdata($author_id);
$time_value = ($cfg['time_source'] ?? 'modified') === 'date'
? (string) get_the_time('Y-m-d H:i:s', $post_id)
: (string) get_the_modified_time('Y-m-d H:i:s', $post_id);
$time_timestamp = strtotime($time_value) ?: (int) get_the_time('U', $post_id);
$resource_meta = function_exists('aksrt_get_resource_meta') ? aksrt_get_resource_meta($post_id) : [];
$resource_strip = function_exists('aksrt_resource_meta_strip') ? aksrt_resource_meta_strip($resource_meta) : [];
$subtitle = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post_id, 'subtitle', '')
: '';
$pay_meta = aksrt_get_pay_meta($post_id);
if (!is_array($pay_meta)) {
$pay_meta = [];
}
return [
'post_id' => $post_id,
'permalink' => get_permalink($post_id),
'title' => get_the_title($post_id),
'subtitle' => $custom_subtitle,
'excerpt' => $excerpt,
'img_url' => $images['primary'],
'has_img' => !empty($images['has_thumb']),
'has_real_img' => !empty($images['has_real']),
'gallery_images' => $gallery_images,
'subtitle' => $subtitle,
'excerpt' => wp_trim_words(wp_strip_all_tags($excerpt), 30, '...'),
'primary' => (string) $images['primary'],
'has_real' => !empty($images['has_real']),
'gallery' => $gallery,
'gallery_count' => count($content_images),
'slides' => array_slice((array) $images['slides'], 0, 8),
'is_slide' => !empty($images['is_slide']),
'video_url' => (string) $images['video_url'],
'is_mult_thumb' => $is_mult_thumb,
'is_video_cover' => !empty(get_post_meta($post_id, '_aksrt_featured_video', true)) || !empty(get_post_meta($post_id, '_video_url', true)),
'author_id' => $author_id,
'avatar_url' => get_avatar_url($author_id, ['size' => 40]),
'author_name' => get_the_author(),
'is_no_thumb' => $is_no_thumb,
'categories' => $categories,
'tags' => $tags,
'comment_count' => (int) get_comments_number($post_id),
'views' => function_exists('aksrt_get_post_views') ? (int) aksrt_get_post_views($post_id) : 0,
'likes' => function_exists('aksrt_get_post_likes') ? (int) aksrt_get_post_likes($post_id) : 0,
'time_ago' => human_time_diff((int) get_the_time('U', $post_id), current_time('timestamp')),
'resource_badge' => function_exists('aksrt_resource_card_badge_text') ? (string) aksrt_resource_card_badge_text($post_id) : '',
'resource_strip' => $resource_strip,
'is_resource' => function_exists('aksrt_is_resource_post') ? aksrt_is_resource_post($post_id) : false,
'author_id' => $author_id,
'author_name' => $author instanceof WP_User ? $author->display_name : '',
'avatar_url' => get_avatar_url($author_id, ['size' => 40]),
'time_title' => $time_value,
'time_ago' => human_time_diff($time_timestamp, current_time('timestamp')) . __('前', 'aksrt'),
'views' => (int) aksrt_get_post_views($post_id),
'likes' => (int) aksrt_get_post_likes($post_id),
'comments' => (int) get_comments_number($post_id),
'comments_open' => comments_open($post_id),
'comments_link' => get_comments_link($post_id),
'post_type' => (string) get_post_type($post_id),
'is_sticky' => is_sticky($post_id),
'thumb_ratio_css' => aksrt_renderer_thumb_ratio_value((string) $cfg['thumb_ratio']),
'thumb_ratio_key' => (string) $cfg['thumb_ratio'],
'resource_badge' => (string) aksrt_resource_card_badge_text($post_id),
'pay_type' => sanitize_key((string) ($pay_meta['pay_type'] ?? 'no')),
'pay_limit' => (int) ($pay_meta['pay_limit'] ?? 0),
];
}
}
if (!function_exists('aksrt_renderer_article_attrs')) {
/**
* Build the article wrapper attributes.
*
* @param string[] $classes CSS classes.
* @param array<string,mixed> $data Collected post data.
* @return string
*/
function aksrt_renderer_article_attrs(array $classes, array $data): string {
$classes[] = 'thumb-ratio--' . str_replace(':', '-', (string) $data['thumb_ratio_key']);
return sprintf(
'class="%s" style="%s"',
esc_attr(implode(' ', array_unique(array_filter($classes)))),
esc_attr('--aksrt-thumb-ratio:' . $data['thumb_ratio_css'])
);
}
}
if (!function_exists('aksrt_renderer_thumb')) {
/**
* Render the primary thumbnail.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_thumb(array $data, array $cfg): string {
if (!$cfg['show_thumb'] || empty($data['has_img'])) {
if (!empty($data['is_no_thumb']) || (string) $data['primary'] === '') {
return '';
}
$is_contain = (($cfg['thumb']['list_thumb_fit'] ?? '') === 'contain');
$target = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
$content = '';
$format_icon = '';
$default_attr = empty($data['has_real']) ? ' data-thumb="default"' : '';
$has_video_thumb = (string) $data['video_url'] !== '' && !empty($cfg['thumb']['list_thumb_video_s']);
$has_slides = !$has_video_thumb && !empty($data['is_slide']) && count((array) $data['slides']) > 0;
$img = (string) $data['img_url'];
$title = (string) $data['title'];
$href = (string) $data['permalink'];
$is_sticky = !empty($data['is_sticky']);
$has_real_img = !empty($data['has_real_img']);
$fit_class = !empty($cfg['thumb_fit']) && $cfg['thumb_fit'] === 'contain' ? 'fit-contain' : 'fit-cover';
$img_attr = aksrt_get_lazy_attr('lazy_posts_thumb', $img, $fit_class);
$img_html = '<img' . $img_attr . ' alt="' . esc_attr($title) . '">';
$sticky_html = ($is_sticky && !empty($cfg['badge_show']))
? '<badge class="img-badge left jb-red">' . esc_html__('置顶', 'aksrt') . '</badge>'
: '';
$format_badge = '';
if (!empty($data['is_mult_thumb']) && !empty($data['gallery_images'])) {
$format_badge = '<badge class="b-black opacity8 abs-right">' . esc_html(count($data['gallery_images'])) . '</badge>';
} elseif (!empty($data['is_video_cover'])) {
$format_badge = '<badge class="b-black opacity8 abs-right"><i class="fas fa-play-circle" aria-hidden="true"></i></badge>';
if ($has_video_thumb) {
$muted = !empty($cfg['thumb']['list_thumb_video_mute_s']);
$content .= '<div class="video-thumb-box" video-url="' . esc_url((string) $data['video_url']) . '" data-volume="' . ($muted ? 'none' : '100') . '" data-post-thumb-video>';
$content .= '<div class="img-thumb"><img' . $default_attr . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['primary'], 'fit-cover radius8') . ' alt="' . esc_attr((string) $data['title']) . '"></div>';
$content .= '<div class="video-thumb"><div class="dplayer dplayer-thumb"><video playsinline loop preload="none"' . ($muted ? ' muted' : '') . ' data-src="' . esc_url((string) $data['video_url']) . '"></video></div></div></div>';
$format_icon = '<i class="fa fa-play-circle em12 mt6 c-white opacity8" aria-hidden="true"></i>';
} elseif ($has_slides) {
$content .= '<div class="relative zib-slider"><div class="new-swiper item-thumbnail' . ($is_contain ? ' gradient-background' : '') . '" data-loop="true" data-effect="slide" data-autoplay="1" data-post-thumb-slider><div class="swiper-wrapper">';
foreach ((array) $data['slides'] as $image) {
$content .= '<div class="swiper-slide' . ($is_contain ? ' gradient-bg' : '') . '"' . ($is_contain ? ' data-opacity="0.2"' : '') . '><a' . $target . ' href="' . esc_url((string) $data['permalink']) . '"><img' . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $image, 'radius8') . ' alt="' . esc_attr((string) $data['title']) . '"></a></div>';
}
$content .= '</div><div class="swiper-pagination kaoyou"></div></div></div>';
$format_icon = '<badge class="b-black opacity8 mt6"><i class="fa fa-image mr3" aria-hidden="true"></i>' . count((array) $data['slides']) . '</badge>';
} else {
$content .= '<a' . $target . ' href="' . esc_url((string) $data['permalink']) . '"><img' . $default_attr . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $data['primary'], 'fit-cover radius8') . ' alt="' . esc_attr((string) $data['title']) . '"></a>';
$format = get_post_format((int) $data['post_id']);
if ((string) $data['video_url'] !== '') {
$format_icon = '<i class="fa fa-play-circle em12 mt6 c-white opacity8" aria-hidden="true"></i>';
} elseif (in_array($format, ['image', 'gallery'], true) && (int) $data['gallery_count'] > 0) {
$format_icon = '<badge class="b-black opacity8 mr6 mt6"><i class="fa fa-image mr3" aria-hidden="true"></i>' . (int) $data['gallery_count'] . '</badge>';
} elseif ($format === 'video') {
$format_icon = '<i class="fa fa-play-circle em12 mr6 mt6 c-white opacity8" aria-hidden="true"></i>';
}
}
$empty_class = $has_real_img ? '' : ' item-thumbnail--fallback';
$target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
if ($cfg['mode'] === 'list') {
$graphic_class = 'post-graphic';
if (($cfg['list_img_position'] ?? 'left') === 'right') {
$graphic_class .= ' order1';
if ($format_icon !== '') {
$content .= '<div class="abs-center right-top">' . $format_icon . '</div>';
}
return '<div class="' . esc_attr($graphic_class) . '"><div class="item-thumbnail' . esc_attr($empty_class) . '"><a href="' . esc_url($href) . '" rel="bookmark"' . $target_attr . '>'
. $img_html . '</a>' . $sticky_html . $format_badge . '</div></div>';
if (!empty($data['is_sticky'])) {
$content .= '<badge class="img-badge left jb-red">' . esc_html__('置顶', 'aksrt') . '</badge>';
}
return '<div class="item-thumbnail' . esc_attr($empty_class) . '"><a href="' . esc_url($href) . '" rel="bookmark"' . $target_attr . '>'
. $img_html . '</a>' . $sticky_html . $format_badge . '</div>';
$fit = $is_contain && !$has_slides ? ' contain gradient-bg' : '';
$opacity = $is_contain && !$has_slides ? ' data-opacity="0.2"' : '';
return '<div class="item-thumbnail' . $fit . '"' . $opacity . '>' . $content . '</div>';
}
}
if (!function_exists('aksrt_renderer_mult_thumb')) {
/**
* Render the multi-image strip used by list cards.
*
* @param array<string,mixed> $data Collected post data.
* @return string
*/
function aksrt_renderer_mult_thumb(array $data): string {
if (empty($data['is_mult_thumb']) || empty($data['gallery_images'])) {
function aksrt_renderer_mult_thumb(array $data, array $cfg): string {
if (empty($data['is_mult_thumb'])) {
return '';
}
$html = '<a class="thumb-items contain" href="' . esc_url($data['permalink']) . '" aria-label="' . esc_attr($data['title']) . '">';
foreach (array_slice($data['gallery_images'], 0, 4) as $image) {
$html .= '<span>';
$html .= '<img' . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $image, 'fit-cover') . ' alt="">';
$html .= '</span>';
$fit = (($cfg['thumb']['list_thumb_fit'] ?? '') === 'contain') ? ' contain' : '';
$target = !empty($cfg['target_blank']) ? ' target="_blank"' : '';
$html = '<a' . $target . ' class="thumb-items' . $fit . '" href="' . esc_url((string) $data['permalink']) . '">';
foreach (array_slice((array) $data['gallery'], 0, 4) as $index => $image) {
$badge = '';
if ($index === 3 && (int) ($data['gallery_count'] ?? 0) > 4) {
$badge = '<div class="abs-center right-top"><badge class="b-black mr6 mt6 em09"><i class="fa fa-image mr3" aria-hidden="true"></i>+' . ((int) $data['gallery_count'] - 4) . '</badge></div>';
}
$html .= '<span>' . $badge . '<img' . aksrt_get_lazy_attr('lazy_posts_thumb', (string) $image, 'fit-cover radius8') . ' alt=""></span>';
}
return $html . '</a>';
}
}
$html .= '</a>';
return $html;
if (!function_exists('aksrt_renderer_pay_badge')) {
function aksrt_renderer_pay_badge(array $data, bool $compact = false): string {
if ((string) $data['resource_badge'] === '') {
return '';
}
$is_vip = (int) ($data['pay_limit'] ?? 0) > 0 || (string) ($data['pay_type'] ?? '') === 'vip';
$vip_level = max(1, min(2, (int) ($data['pay_limit'] ?? 1)));
$class = $is_vip ? 'jb-vip' . $vip_level : 'c-yellow';
$type_labels = function_exists('aksrt_get_pay_types') ? aksrt_get_pay_types() : [];
$title = (string) ($type_labels[(string) ($data['pay_type'] ?? '')] ?? __('付费内容', 'aksrt'));
$icon = $is_vip ? aksrt_renderer_vip_icon($vip_level) : '';
if ($compact) {
return '<item class="meta-pay badg badg-sm mr6 ' . esc_attr($class) . '" data-toggle="tooltip" title="' . esc_attr($title) . '">' . $icon . esc_html((string) $data['resource_badge']) . '</item>';
}
$tooltip = $is_vip ? ' data-toggle="tooltip" title="' . esc_attr($title) . '"' : '';
return '<a rel="nofollow" href="' . esc_url((string) $data['permalink']) . '#posts-pay"' . $tooltip . ' class="meta-pay but ' . esc_attr($is_vip ? $class : 'jb-yellow') . '">' . $icon . esc_html((string) $data['resource_badge']) . '</a>';
}
}
if (!function_exists('aksrt_renderer_vip_icon')) {
function aksrt_renderer_vip_icon(int $level): string {
if (!class_exists('\AKSRT\Settings\Settings')) {
return '<i class="fas fa-crown mr3" aria-hidden="true"></i>';
}
$settings = \AKSRT\Settings\Settings::get_payment_vip_settings();
$url = esc_url((string) ($settings['levels'][$level]['img_icon'] ?? ''));
return $url !== ''
? '<img class="mr3" src="' . $url . '" alt="" aria-hidden="true">'
: '<i class="fas fa-crown mr3" aria-hidden="true"></i>';
}
}
if (!function_exists('aksrt_renderer_badges')) {
/**
* Render taxonomy and resource badges.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_badges(array $data, array $cfg): string {
$items = [];
if (!empty($cfg['show_resource_badge']) && $data['resource_badge'] !== '') {
$items[] = '<a href="' . esc_url($data['permalink']) . '#posts-pay" class="meta-pay but jb-yellow">' . esc_html($data['resource_badge']) . '</a>';
$types = (array) ($cfg['badge_types'] ?? []);
if (in_array('pay', $types, true)) {
$pay = aksrt_renderer_pay_badge($data);
if ($pay !== '') {
$items[] = $pay;
}
if (!empty($cfg['show_badges']) && !empty($cfg['badge_show']) && $cfg['category_limit'] > 0) {
foreach (array_slice($data['categories'], 0, $cfg['category_limit']) as $category) {
$items[] = '<a href="' . esc_url(get_category_link($category->term_id)) . '" class="but"><i class="far fa-folder-open" aria-hidden="true"></i> ' . esc_html($category->name) . '</a>';
}
if (empty($cfg['is_category']) && in_array('cat', $types, true)) {
$colors = ['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red'];
foreach (array_slice((array) $data['categories'], 0, 3) as $index => $category) {
$items[] = '<a class="but ' . esc_attr($colors[$index % count($colors)]) . '" title="' . esc_attr__('查看更多分类文章', 'aksrt') . '" href="' . esc_url(get_category_link($category->term_id)) . '"><i class="fa fa-folder-open-o" aria-hidden="true"></i>' . esc_html($category->name) . '</a>';
}
}
if (empty($cfg['is_tag']) && in_array('tag', $types, true)) {
$tags = (array) $data['tags'];
usort($tags, static fn($left, $right): int => (int) $right->count <=> (int) $left->count);
foreach (array_slice($tags, 0, 3) as $tag) {
$items[] = '<a class="but" title="' . esc_attr__('查看此标签更多文章', 'aksrt') . '" href="' . esc_url(get_tag_link($tag->term_id)) . '"># ' . esc_html($tag->name) . '</a>';
}
}
return $items ? '<div class="item-tags scroll-x no-scrollbar mb6">' . implode('', $items) . '</div>' : '';
}
}
if (!empty($cfg['show_tags']) && $cfg['tag_limit'] > 0) {
foreach (array_slice($data['tags'], 0, $cfg['tag_limit']) as $tag) {
$items[] = '<a href="' . esc_url(get_tag_link($tag->term_id)) . '" class="but">#' . esc_html($tag->name) . '</a>';
if (!function_exists('aksrt_renderer_meta_number')) {
function aksrt_renderer_meta_number(int $number): string {
return (string) aksrt_format_number($number);
}
}
if (!empty($data['is_resource'])) {
$items[] = '<span class="but jb-vip">VIP</span>';
}
if (empty($items)) {
return '';
}
return '<div class="item-tags scroll-x no-scrollbar">' . implode('', $items) . '</div>';
}
}
if (!function_exists('aksrt_renderer_resource_strip')) {
/**
* Render compact resource meta chips.
*
* @param array<string,mixed> $data Collected post data.
* @return string
*/
function aksrt_renderer_resource_strip(array $data): string {
if (empty($data['resource_strip']) || !is_array($data['resource_strip'])) {
return '';
}
$html = '<ul class="item-cms-strip">';
foreach ($data['resource_strip'] as $item) {
$html .= '<li>' . esc_html((string) $item) . '</li>';
}
$html .= '</ul>';
return $html;
if (!function_exists('aksrt_renderer_svg_icon')) {
function aksrt_renderer_svg_icon(string $name): string {
return '<svg class="icon" aria-hidden="true"><use xlink:href="#icon-' . esc_attr($name) . '"></use></svg>';
}
}
if (!function_exists('aksrt_renderer_meta')) {
/**
* Render the meta bar.
*
* @param array<string,mixed> $data Collected post data.
* @param array<string,mixed> $cfg Renderer config.
* @return string
*/
function aksrt_renderer_meta(array $data, array $cfg): string {
if (!$cfg['show_meta']) {
return '';
function aksrt_renderer_meta(array $data, array $cfg, bool $is_card = false): string {
$show_time = !empty($cfg['show_time']);
$meta_left = '';
if (empty($cfg['no_author']) && !empty($cfg['show_author'])) {
$name = (!$is_card || !$show_time) && (string) $data['author_name'] !== ''
? '<span class="' . ($show_time ? 'hide-sm ' : '') . 'ml6">' . esc_html((string) $data['author_name']) . '</span>'
: '';
$time = $show_time
? '<span title="' . esc_attr((string) $data['time_title']) . '" class="' . ($is_card ? 'ml6' : 'icon-circle') . '">' . esc_html((string) $data['time_ago']) . '</span>'
: '';
$avatar = '<img class="avatar-mini" src="' . esc_url((string) $data['avatar_url']) . '" alt="" width="20" height="20">';
$meta_left = '<item class="meta-author flex ac">' . $avatar . $name . $time . '</item>';
} elseif ($show_time) {
$meta_left = '<item title="' . esc_attr((string) $data['time_title']) . '" class="icon-circle mln3">' . esc_html((string) $data['time_ago']) . '</item>';
}
$format_number = function_exists('aksrt_format_number') ? 'aksrt_format_number' : 'strval';
if ($cfg['mode'] === 'mini') {
$items = [];
if (!empty($cfg['show_date'])) {
$items[] = '<span class="meta-item">' . esc_html($data['time_ago']) . '</span>';
$right = '';
if (!empty($data['comments_open']) && empty($cfg['close_comments'])) {
$right .= '<item class="meta-comm"><a rel="nofollow" data-toggle="tooltip" title="' . esc_attr__('去评论', 'aksrt') . '" href="' . esc_url((string) $data['comments_link']) . '">' . aksrt_renderer_svg_icon('comment') . esc_html((string) $data['comments']) . '</a></item>';
}
if (!empty($cfg['show_views'])) {
$items[] = '<span class="meta-item"><i class="fas fa-eye" aria-hidden="true"></i> ' . esc_html($format_number($data['views'])) . '</span>';
$right .= '<item class="meta-view">' . aksrt_renderer_svg_icon('view') . esc_html(aksrt_renderer_meta_number((int) $data['views'])) . '</item>';
if (!empty($cfg['show_like']) && (string) ($data['post_type'] ?? '') === 'post') {
$right .= '<item class="meta-like">' . aksrt_renderer_svg_icon('like') . esc_html(aksrt_renderer_meta_number((int) $data['likes'])) . '</item>';
}
if (empty($items)) {
return '';
}
return '<div class="item-meta">' . implode('<span class="meta-dot" aria-hidden="true"></span>', $items) . '</div>';
}
$left = [];
if (!empty($cfg['show_author'])) {
$left[] = '<item class="meta-author flex ac"><img' . aksrt_get_lazy_attr('lazy_avatar', (string) $data['avatar_url'], 'avatar-mini', aksrt_get_lazy_thumb('sm')) . ' alt="" width="20" height="20"><name class="meta-name">' . esc_html($data['author_name']) . '</name></item>';
}
if (!empty($cfg['show_date'])) {
$left[] = '<item class="meta-time">' . esc_html($data['time_ago']) . '</item>';
}
$right = [];
if (!empty($cfg['show_comments'])) {
$right[] = '<item class="meta-view"><i class="fas fa-comment" aria-hidden="true"></i> ' . esc_html((string) $data['comment_count']) . '</item>';
}
if (!empty($cfg['show_views'])) {
$right[] = '<item class="meta-view"><i class="fas fa-eye" aria-hidden="true"></i> ' . esc_html($format_number($data['views'])) . '</item>';
}
if (!empty($cfg['show_likes'])) {
$right[] = '<item class="meta-view"><i class="fas fa-thumbs-up" aria-hidden="true"></i> ' . esc_html($format_number($data['likes'])) . '</item>';
}
if (empty($left) && empty($right)) {
return '';
}
$html = '<div class="item-meta muted-2-color flex jsb ac">';
if (!empty($left)) {
$html .= '<div class="meta-author-group flex ac">' . implode('<span class="meta-dot" aria-hidden="true"></span>', $left) . '</div>';
}
if (!empty($right)) {
$html .= '<div class="meta-right">' . implode('', $right) . '</div>';
}
$html .= '</div>';
return $html;
return '<div class="item-meta muted-2-color flex jsb ac">' . $meta_left . '<div class="meta-right">' . $right . '</div></div>';
}
}
@@ -24,38 +24,51 @@ if (!function_exists('aksrt_render_pagination')) {
* }
*/
function aksrt_render_pagination(?WP_Query $query = null, array $args = []): void {
global $wp_query;
$q = $query ?? $wp_query;
if (!($q instanceof WP_Query)) {
return;
}
$page_var = sanitize_key((string) ($args['page_var'] ?? 'paged')) ?: 'paged';
$current_page = isset($args['current_page'])
? max(1, (int) $args['current_page'])
: max(1, (int) $q->get('paged'), (int) get_query_var('paged'), (int) get_query_var('page'));
$max_pages = (int) $q->max_num_pages;
$pagination_mode = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_pagination_mode()
: 'numbered';
if ($pagination_mode === 'numbered') {
global $wp_query;
$q = $query ?? $wp_query;
if (!($q instanceof WP_Query) || $q->max_num_pages <= 1) {
if ($max_pages <= 1) {
return;
}
$current_page = max(1, (int) $q->get('paged'), (int) get_query_var('paged'), (int) get_query_var('page'));
$max_pages = (int) $q->max_num_pages;
$links = paginate_links([
$paginate_args = [
'total' => $max_pages,
'current' => $current_page,
'mid_size' => 2,
'prev_text' => '<span aria-hidden="true">‹</span>',
'next_text' => '<span aria-hidden="true">›</span>',
'type' => 'plain',
]);
];
if ($page_var !== 'paged') {
$big = 999999999;
$paginate_args['base'] = str_replace((string) $big, '%#%', esc_url_raw(add_query_arg($page_var, $big)));
$paginate_args['format'] = '';
}
$links = paginate_links($paginate_args);
if (empty($links)) {
return;
}
echo '<div class="pagenav ajax-pag aksrt-pagination content-stream__footer-nav">';
echo '<div class="pagenav ajax-pag" data-aksrt-pagination data-mode="numbered" data-page-var="' . esc_attr($page_var) . '" data-max-pages="' . esc_attr((string) $max_pages) . '">';
echo wp_kses_post($links);
if ($max_pages > 8) {
echo '<span class="pag-jump">';
echo '<input type="number" class="jump-input" min="1" max="' . esc_attr((string) $max_pages) . '" value="' . esc_attr((string) $current_page) . '" data-max-pages="' . esc_attr((string) $max_pages) . '" />';
echo '<button type="button" class="ajax-next order-ajax-next" data-jump-archive="1">' . esc_html__('跳转', 'aksrt') . '</button>';
echo '<button type="button" class="ajax-next order-ajax-next" data-jump-archive="1" data-page-var="' . esc_attr($page_var) . '">' . esc_html__('跳转', 'aksrt') . '</button>';
echo '</span>';
}
echo '</div>';
@@ -63,14 +76,6 @@ if (!function_exists('aksrt_render_pagination')) {
}
// load_more / infinite 模式
global $wp_query;
$q = $query ?? $wp_query;
if (!($q instanceof WP_Query)) {
return;
}
$max_pages = (int) $q->max_num_pages;
$current_page = max(1, (int) $q->get('paged'), (int) get_query_var('paged'), (int) get_query_var('page'));
if ($current_page >= $max_pages) {
return;
}
@@ -96,27 +101,29 @@ if (!function_exists('aksrt_render_pagination')) {
: [];
$trigger_text = __('加载更多', 'aksrt');
$ias_max = 3;
if (class_exists('\AKSRT\Settings\Settings')) {
$list_settings = \AKSRT\Settings\Settings::get_post_list_style_settings();
if (!empty($list_settings['ajax_trigger_text'])) {
$trigger_text = (string) $list_settings['ajax_trigger_text'];
if (!empty($list_settings['ajax_trigger'])) {
$trigger_text = (string) $list_settings['ajax_trigger'];
}
$ias_max = max(0, (int) ($list_settings['ias_max'] ?? 3));
}
$auto_load = $pagination_mode === 'infinite' && ($ias_max === 0 || $current_page <= $ias_max);
?>
<div class="text-center theme-pagination ajax-pag ajax-pagination aksrt-pagination-ajax content-stream__footer-nav"
<div class="text-center theme-pagination ajax-pag"
data-aksrt-pagination
data-mode="<?php echo esc_attr($pagination_mode); ?>"
data-next-page="<?php echo esc_attr((string) ($current_page + 1)); ?>"
data-max-pages="<?php echo esc_attr((string) $max_pages); ?>"
data-page-var="<?php echo esc_attr($page_var); ?>"
data-query-context="<?php echo esc_attr($query_context); ?>"
data-query-vars="<?php echo esc_attr(wp_json_encode($query_vars)); ?>"
data-render-config="<?php echo esc_attr(wp_json_encode($render_config)); ?>">
<div class="next-page ajax-next aksrt-load-more-btn<?php echo $pagination_mode === 'infinite' ? ' lazyload' : ''; ?>"<?php echo $pagination_mode === 'infinite' ? ' lazyload-action="ias"' : ''; ?> role="button" tabindex="0">
<?php echo esc_html($trigger_text); ?>
</div>
<div class="aksrt-loading-indicator" style="display:none;" aria-hidden="true">
<i class="fas fa-spinner fa-spin"></i>
<span><?php esc_html_e('加载中…', 'aksrt'); ?></span>
<div class="next-page ajax-next<?php echo $auto_load ? ' lazyload' : ''; ?>"<?php echo $auto_load ? ' lazyload-action="ias"' : ''; ?>>
<a href="<?php echo esc_url(add_query_arg($page_var, $current_page + 1)); ?>" data-aksrt-load-more><?php echo wp_kses_post($trigger_text); ?></a>
</div>
<div class="post_ajax_loader" style="display:none;" aria-hidden="true"><?php echo function_exists('aksrt_render_placeholder') ? aksrt_render_placeholder('list', 2) : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
</div>
<?php
}
+2 -4
View File
@@ -29,13 +29,11 @@ $has_sidebar = function_exists('aksrt_is_show_sidebar') ? aksrt_is_show_sidebar(
<div class="content-wrap">
<div class="content-layout">
<?php
$render_config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config(['context' => 'archive'])
: ['context' => 'archive', 'mode' => 'list'];
$render_config = aksrt_resolve_post_list_render_config(['context' => 'archive']);
?>
<?php if (have_posts()) : ?>
<?php aksrt_render_post_list(null, $render_config); ?>
<?php aksrt_render_post_list($render_config); ?>
<?php aksrt_render_pagination(); ?>
<?php else : ?>
<div class="no-results">
+3 -6
View File
@@ -29,9 +29,6 @@ $hot_keywords = function_exists('aksrt_get_search_hot_keywords') ? aksrt_get_sea
$filter_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_filter_settings()
: ['enabled' => false, 'categories' => true, 'tags' => false, 'sort' => true];
$list_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_list_style_settings()
: ['render_mode' => 'card', 'card_style' => 'standard'];
$other_page_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_other_page_settings()
: ['page_search_sidebar' => true];
@@ -150,7 +147,7 @@ if (function_exists('aksrt_archive_open_shell')) {
]);
?>
<section class="content-stream content-stream--search content-stream--archive" data-page-kind="search" data-render-mode="<?php echo esc_attr((string) $render_config['mode']); ?>" data-card-style="<?php echo esc_attr((string) $render_config['card_style']); ?>">
<section class="content-stream content-stream--search content-stream--archive" data-page-kind="search" data-render-mode="<?php echo esc_attr((string) $render_config['mode']); ?>" data-list-type="<?php echo esc_attr((string) $render_config['list_type']); ?>">
<form role="search" method="get" class="search-hero-form search-page-form" action="<?php echo esc_url(home_url('/')); ?>" data-aksrt-search-form="search-page" data-default-search-type="<?php echo esc_attr($search_type); ?>" data-default-category="<?php echo esc_attr((string) $current_category); ?>">
<input type="hidden" name="aksrt_search_type" value="<?php echo esc_attr($search_type); ?>">
<?php if ($search_category_enabled) : ?>
@@ -275,8 +272,8 @@ if (function_exists('aksrt_archive_open_shell')) {
<?php endif; ?>
</div>
<?php if ($loop_query->have_posts() && function_exists('aksrt_render_post_list')) : ?>
<?php aksrt_render_post_list($loop_query, array_merge($render_config, ['wrapper_class' => 'content-stream__list'])); ?>
<?php if ($loop_query->have_posts()) : ?>
<?php aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']), $loop_query); ?>
<div class="content-stream__footer">
<?php aksrt_render_pagination($loop_query, ['query_context' => 'search', 'render_config' => $render_config]); ?>
</div>
+28 -44
View File
@@ -203,7 +203,9 @@ class Admin {
public static function sanitize_public_query_vars(array $vars): array {
$allowed = [
'post_type', 'post_status', 'posts_per_page', 'paged', 'orderby', 'order',
'cat', 'tag', 'tag_id', 'category_name', 's', 'post__not_in', 'category__not_in',
'cat', 'tag', 'tag_id', 'category_name', 's', 'author', 'author_name', 'offset',
'post__in', 'post__not_in', 'category__in', 'category__not_in', 'tag__in',
'tag__not_in', 'tag_slug__in', 'ignore_sticky_posts',
];
$clean = [];
foreach ($allowed as $key) {
@@ -223,15 +225,28 @@ class Admin {
if (isset($clean['paged'])) {
$clean['paged'] = max(1, (int) $clean['paged']);
}
foreach (['author', 'cat', 'tag_id', 'offset'] as $int_key) {
if (isset($clean[$int_key])) {
$clean[$int_key] = max(0, (int) $clean[$int_key]);
}
}
foreach (['post__in', 'post__not_in', 'category__in', 'category__not_in', 'tag__in', 'tag__not_in'] as $ids_key) {
if (isset($clean[$ids_key])) {
$clean[$ids_key] = array_values(array_filter(array_map('absint', (array) $clean[$ids_key])));
}
}
if (isset($clean['ignore_sticky_posts'])) {
$clean['ignore_sticky_posts'] = !empty($clean['ignore_sticky_posts']);
}
return $clean;
}
public static function sanitize_public_render_config(array $config): array {
$clean = [];
if (isset($config['mode'])) {
$mode = sanitize_key((string) $config['mode']);
if (in_array($mode, ['list', 'card', 'mini', 'grid-cms'], true)) {
$clean['mode'] = $mode;
if (isset($config['type'])) {
$type = sanitize_key((string) $config['type']);
if (in_array($type, ['auto', 'list', 'card'], true)) {
$clean['type'] = $type;
}
}
if (isset($config['context'])) {
@@ -240,44 +255,18 @@ class Admin {
if (isset($config['query_context'])) {
$clean['query_context'] = sanitize_key((string) $config['query_context']);
}
if (isset($config['style'])) {
$style = sanitize_key((string) $config['style']);
if (in_array($style, ['', 'style2', 'style3'], true)) {
$clean['style'] = $style;
}
}
if (isset($config['card_style'])) {
$card_style = sanitize_key((string) $config['card_style']);
$allowed = ['thumb', 'text', 'thumb_if_has', 'card', 'compact', 'large_thumb', 'multi_thumb', 'standard'];
if (in_array($card_style, $allowed, true)) {
$clean['card_style'] = $card_style;
}
}
if (isset($config['list_show_type'])) {
$v = sanitize_key((string) $config['list_show_type']);
if (in_array($v, ['separate', 'no_margin'], true)) {
$clean['list_show_type'] = $v;
}
}
if (isset($config['list_img_position'])) {
$v = sanitize_key((string) $config['list_img_position']);
if (in_array($v, ['left', 'right'], true)) {
$clean['list_img_position'] = $v;
}
}
foreach (['thumb_if_has', 'show_thumb', 'show_author', 'show_date', 'show_views', 'show_comments', 'show_likes', 'show_excerpt', 'heading_bold', 'target_blank', 'badge_show', 'multi_thumb_enabled', 'skip_wrapper'] as $bool_key) {
foreach (['no_author', 'no_margin', 'is_card', 'skip_wrapper'] as $bool_key) {
if (array_key_exists($bool_key, $config)) {
$clean[$bool_key] = !empty($config[$bool_key]);
}
}
if (isset($config['multi_thumb_limit'])) {
$clean['multi_thumb_limit'] = max(1, min(6, (int) $config['multi_thumb_limit']));
foreach (['is_mult_thumb', 'is_no_thumb'] as $layout_key) {
if (array_key_exists($layout_key, $config)) {
$clean[$layout_key] = $config[$layout_key] === 'disable' ? 'disable' : !empty($config[$layout_key]);
}
if (isset($config['multi_thumb_min'])) {
$clean['multi_thumb_min'] = max(1, min(6, (int) $config['multi_thumb_min']));
}
if (isset($config['excerpt_words'])) {
$clean['excerpt_words'] = max(0, min(200, (int) $config['excerpt_words']));
if (isset($config['highlight_query'])) {
$clean['highlight_query'] = sanitize_text_field((string) $config['highlight_query']);
}
return $clean;
}
@@ -294,8 +283,6 @@ class Admin {
$render_config = json_decode(wp_unslash((string) ($_POST['render_config'] ?? '{}')), true);
$query_vars = is_array($query_vars) ? self::sanitize_public_query_vars($query_vars) : [];
$render_config = is_array($render_config) ? self::sanitize_public_render_config($render_config) : [];
// Prefer server-side resolution so AJAX matches SSR styles even if client config is slim.
if (function_exists('aksrt_resolve_post_list_render_config')) {
$context = sanitize_key((string) ($render_config['context'] ?? 'archive'));
if ($context === '') {
$context = 'archive';
@@ -304,8 +291,7 @@ class Admin {
'context' => $context,
'overrides' => $render_config,
]);
$render_config = array_merge($resolved, $render_config);
}
$render_config = array_merge($resolved, $render_config, ['skip_wrapper' => true]);
$query_vars['paged'] = $page;
if (empty($query_vars['post_status'])) {
$query_vars['post_status'] = 'publish';
@@ -316,9 +302,7 @@ class Admin {
$query = new \WP_Query($query_vars);
ob_start();
if (function_exists('aksrt_render_post_list')) {
aksrt_render_post_list($query, $render_config, true);
}
aksrt_render_post_list($render_config, $query, true);
$html = (string) ob_get_clean();
wp_send_json_success([
+3 -26
View File
@@ -129,7 +129,6 @@ class GlobalController extends ControllerBase {
'page' => ['type' => 'integer', 'default' => 1, 'sanitize_callback' => 'absint'],
'per_page' => ['type' => 'integer', 'default' => 10, 'sanitize_callback' => 'absint'],
'sort' => ['type' => 'string', 'default' => 'modified', 'sanitize_callback' => 'sanitize_text_field'],
'view_mode' => ['type' => 'string', 'default' => 'list', 'sanitize_callback' => 'sanitize_text_field'],
'page_url' => ['type' => 'string', 'default' => '', 'sanitize_callback' => 'esc_url_raw'],
'panel' => ['type' => 'string', 'default' => 'latest', 'sanitize_callback' => 'sanitize_key'],
'source' => ['type' => 'string', 'default' => 'home', 'sanitize_callback' => 'sanitize_key'],
@@ -239,7 +238,6 @@ class GlobalController extends ControllerBase {
$page = max(1, (int) $request->get_param('page'));
$per_page = min(50, max(1, (int) $request->get_param('per_page')));
$sort = sanitize_key($request->get_param('sort') ?: 'modified');
$view_mode = sanitize_key($request->get_param('view_mode') ?: 'list');
$tab_orderby = sanitize_key($request->get_param('tab_orderby') ?: '');
$panel = sanitize_key($request->get_param('panel') ?: 'latest');
@@ -294,32 +292,11 @@ class GlobalController extends ControllerBase {
$query = new \WP_Query($args);
$mode = in_array($view_mode, ['card', 'list'], true) ? $view_mode : '';
$html = '';
if (function_exists('aksrt_render_post_list')) {
$overrides = ['context' => 'home'];
if ($mode !== '') {
$overrides['mode'] = $mode;
}
$render_config = function_exists('aksrt_resolve_post_list_render_config')
? aksrt_resolve_post_list_render_config([
$render_config = aksrt_resolve_post_list_render_config([
'context' => 'home',
'overrides' => $overrides,
])
: array_merge(['mode' => ($mode !== '' ? $mode : 'list'), 'context' => 'home'], $overrides);
]);
$render_config['skip_wrapper'] = true;
$html = aksrt_render_post_list($query, $render_config, false);
} elseif ($query->have_posts()) {
while ($query->have_posts()) {
$query->the_post();
if (function_exists('aksrt_homepage_render_post_card')) {
ob_start();
aksrt_homepage_render_post_card();
$html .= ob_get_clean();
}
}
wp_reset_postdata();
}
$html = aksrt_render_post_list($render_config, $query, false);
$has_more = $page < (int) $query->max_num_pages;
-17
View File
@@ -62,26 +62,9 @@ trait HomepageSettingsRest {
$settings['home_lists'] = $this->sanitize_home_lists($params['home_lists'], $allowed_sorts);
}
// --- AKSRT presentation extras (optional, not Zibll dual-write) ---
if (isset($params['homepage_card_style'])) {
$settings['homepage_card_style'] = in_array($params['homepage_card_style'], ['default', 'style2', 'style3'], true)
? $params['homepage_card_style']
: 'default';
}
if (array_key_exists('homepage_multi_thumb_enabled', $params)) {
$settings['homepage_multi_thumb_enabled'] = !empty($params['homepage_multi_thumb_enabled']);
}
if (isset($params['homepage_multi_thumb_min_images'])) {
$settings['homepage_multi_thumb_min_images'] = max(2, min(4, (int) $params['homepage_multi_thumb_min_images']));
}
if (array_key_exists('homepage_sidebar_mini_enabled', $params)) {
$settings['homepage_sidebar_mini_enabled'] = !empty($params['homepage_sidebar_mini_enabled']);
}
if (isset($params['homepage_default_view'])) {
$settings['homepage_default_view'] = in_array($params['homepage_default_view'], ['list', 'card'], true)
? $params['homepage_default_view']
: 'list';
}
if (array_key_exists('homepage_sort_ajax_enabled', $params)) {
$settings['homepage_sort_ajax_enabled'] = !empty($params['homepage_sort_ajax_enabled']);
}
+75 -219
View File
@@ -13,258 +13,114 @@ class SchemaPostListList {
return [
'key' => 'list',
'title' => '文章列表',
'version' => '2.0.0',
'version' => '9.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
[
'key' => 'order',
'title' => '默认排序与置顶',
'fields' => [
[
'key' => 'list_orderby',
'label' => '默认排序方式',
'type' => 'radio',
'default' => 'modified',
'options' => [
['key' => 'list_orderby', 'label' => '默认排序方式', 'type' => 'radio', 'default' => 'modified', 'options' => [
['label' => '发布时间', 'value' => 'date'],
['label' => '更新时间', 'value' => 'modified'],
['label' => 'WP默认', 'value' => 'default'],
],
'help' => '文章列表全局默认排序(对齐 Zibll list_orderby)',
],
[
'key' => 'sticky_s',
'label' => '显示置顶文章',
'type' => 'checkbox',
'default' => ['home', 'cat'],
'options' => [
]],
['key' => 'post_time_source', 'label' => '时间显示数据源', 'type' => 'radio', 'default' => 'modified', 'options' => [
['label' => '发布时间', 'value' => 'date'],
['label' => '更新时间', 'value' => 'modified'],
]],
['key' => 'sticky_s', 'label' => '显示置顶文章', 'type' => 'checkbox', 'default' => ['home', 'cat'], 'options' => [
['label' => '首页', 'value' => 'home'],
['label' => '分类页', 'value' => 'cat'],
['label' => '标签页', 'value' => 'tag'],
],
],
]],
],
],
[
'key' => 'display',
'title' => '列表显示',
'fields' => [
[
'key' => 'target_blank',
'label' => '新窗口打开文章',
'type' => 'switch',
'default' => false,
],
[
'key' => 'item_heading_bold',
'label' => '列表标题粗体显示',
'type' => 'switch',
'default' => false,
],
[
'key' => 'post_list_author',
'label' => '显示文章作者',
'type' => 'switch',
'default' => true,
],
[
'key' => 'post_list_time',
'label' => '显示文章时间',
'type' => 'switch',
'default' => true,
'help' => '建议作者与时间至少开启一项',
],
[
'key' => 'list_meta_show',
'label' => '列表小部件(移动端优先)',
'type' => 'radio',
'default' => 'like',
'options' => [
['key' => 'target_blank', 'label' => '新窗口打开文章', 'type' => 'switch', 'default' => false],
['key' => 'item_heading_bold', 'label' => '列表标题粗体显示', 'type' => 'switch', 'default' => false],
['key' => 'post_list_author', 'label' => '显示文章作者', 'type' => 'switch', 'default' => true],
['key' => 'post_list_time', 'label' => '显示文章时间', 'type' => 'switch', 'default' => true],
['key' => 'list_meta_show', 'label' => '列表小部件(移动端优先)', 'type' => 'radio', 'default' => 'like', 'options' => [
['label' => '阅读量', 'value' => 'view'],
['label' => '点赞数', 'value' => 'like'],
['label' => '评论', 'value' => 'comm'],
],
'help' => '移动端空间不足时始终保留的部件',
],
[
'key' => 'list_badge_show',
'label' => '列表徽章',
'type' => 'checkbox',
'default' => ['pay', 'tag', 'cat'],
'options' => [
['label' => '付费', 'value' => 'pay'],
]],
['key' => 'list_badge_show', 'label' => '显示列表标签', 'type' => 'checkbox', 'default' => ['pay', 'cat', 'tag'], 'options' => [
['label' => '付费信息', 'value' => 'pay'],
['label' => '分类', 'value' => 'cat'],
['label' => '标签', 'value' => 'tag'],
]],
],
],
[
'key' => 'layout',
'title' => '列表模式与 UI',
'fields' => [
['key' => 'list_show_type', 'label' => '列表样式', 'type' => 'radio', 'default' => 'separate', 'options' => [
['label' => '分离', 'value' => 'separate'],
['label' => '无间距', 'value' => 'no_margin'],
]],
['key' => 'list_type', 'label' => '默认列表模式', 'type' => 'radio', 'default' => 'thumb', 'options' => [
['label' => '列表文字模式', 'value' => 'text'],
['label' => '列表图文模式', 'value' => 'thumb'],
['label' => '列表自动图文模式', 'value' => 'thumb_if_has'],
['label' => '卡片模式', 'value' => 'card'],
], 'help' => '文字、自动图文和多图模式仅在开启侧栏的页面有效。'],
['key' => 'list_card_home', 'label' => '首页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_tag', 'label' => '标签页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_author', 'label' => '用户页列表卡片模式', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
['key' => 'list_card_cat', 'label' => '分类页自定义卡片模式', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
[
'key' => 'list_list_option',
'label' => '列表模式 UI 配置',
'type' => 'fieldset',
'sanitize_type' => 'array',
'default' => ['style' => 'null', 'img_position' => 'left', 'scale' => 70],
'fields' => [
['key' => 'style', 'label' => '列表样式', 'type' => 'radio', 'default' => 'null', 'options' => [
['label' => '默认', 'value' => 'null'],
['label' => '样式 2', 'value' => 'style2'],
]],
['key' => 'img_position', 'label' => '缩略图位置', 'type' => 'radio', 'default' => 'left', 'options' => [
['label' => '靠左', 'value' => 'left'],
['label' => '靠右', 'value' => 'right'],
]],
['key' => 'scale', 'label' => '缩略图长宽比例(%)', 'type' => 'number', 'default' => 70, 'min' => 50, 'max' => 200, 'step' => 5],
],
],
[
'key' => 'list_card_option',
'label' => '卡片模式 UI 配置',
'type' => 'fieldset',
'sanitize_type' => 'array',
'default' => ['style' => 'null', 'scale' => 70],
'fields' => [
['key' => 'style', 'label' => '卡片样式', 'type' => 'radio', 'default' => 'null', 'options' => [
['label' => '默认', 'value' => 'null'],
['label' => '样式 3', 'value' => 'style3'],
]],
['key' => 'scale', 'label' => '缩略图长宽比例(%)', 'type' => 'number', 'default' => 70, 'min' => 20, 'max' => 200, 'step' => 5],
],
],
['key' => 'mult_thumb_cat', 'label' => '列表多图显示分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'help' => '文章格式为图片或画廊时默认显示 4 张缩略图;卡片模式或无侧栏时无效。'],
],
],
[
'key' => 'paging',
'title' => '列表翻页',
'fields' => [
[
'key' => 'paging_ajax_s',
'label' => '列表翻页模式',
'type' => 'radio',
'default' => '1',
'options' => [
['key' => 'paging_ajax_s', 'label' => '列表翻页模式', 'type' => 'radio', 'default' => '1', 'options' => [
['label' => 'AJAX 追加列表翻页', 'value' => '1'],
['label' => '数字翻页按钮', 'value' => '0'],
],
'help' => '对齐 Zibll paging_ajax_s;单页数量仍受 WP 阅读设置影响',
],
[
'key' => 'paging_ajax_ias_s',
'label' => 'AJAX 自动加载',
'type' => 'switch',
'default' => true,
'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1'],
'help' => '滚动到列表尽头时自动加载下一页(Zibll paging_ajax_ias_s)',
],
[
'key' => 'ias_max',
'label' => '自动加载页数',
'type' => 'number',
'default' => 3,
'min' => 0,
'max' => 10,
'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1'],
'help' => '0 表示不限制,直到加载完全部列表',
],
[
'key' => 'ajax_trigger',
'label' => '加载更多文案',
'type' => 'input',
'default' => '加载更多',
'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1'],
],
[
'key' => 'ajax_nomore',
'label' => '没有更多文案',
'type' => 'input',
'default' => '没有更多了',
'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1'],
],
],
],
[
'key' => 'card',
'title' => '卡片与布局',
'fields' => [
[
'key' => 'post_card_style',
'label' => '默认列表模式',
'type' => 'select',
'default' => 'thumb',
'options' => [
['label' => '列表图文模式', 'value' => 'thumb'],
['label' => '紧凑卡', 'value' => 'compact'],
['label' => '大图卡', 'value' => 'large_thumb'],
['label' => '多图卡', 'value' => 'multi_thumb'],
['label' => '列表文字模式', 'value' => 'text'],
['label' => '列表自动图文', 'value' => 'thumb_if_has'],
['label' => '卡片模式', 'value' => 'card'],
],
],
[
'key' => 'post_excerpt_length',
'label' => '摘要长度',
'type' => 'number',
'default' => 120,
'min' => 20,
'max' => 500,
],
[
'key' => 'post_list_per_page',
'label' => '每页文章数',
'type' => 'number',
'default' => 12,
'min' => 4,
'max' => 48,
],
[
'key' => 'list_show_type',
'label' => '列表间距样式',
'type' => 'radio',
'default' => 'separate',
'options' => [
['label' => '分隔卡片', 'value' => 'separate'],
['label' => '无间距', 'value' => 'no_margin'],
],
],
[
'key' => 'list_list_style',
'label' => '列表样式变体',
'type' => 'radio',
'default' => '',
'options' => [
['label' => '默认', 'value' => ''],
['label' => '样式2', 'value' => 'style2'],
],
],
[
'key' => 'list_list_img_position',
'label' => '列表缩略图位置',
'type' => 'radio',
'default' => 'left',
'options' => [
['label' => '左侧', 'value' => 'left'],
['label' => '右侧', 'value' => 'right'],
],
],
[
'key' => 'list_list_scale',
'label' => '列表缩略图比例 (%)',
'type' => 'number',
'default' => 70,
'min' => 50,
'max' => 200,
],
[
'key' => 'list_card_style',
'label' => '卡片样式变体',
'type' => 'radio',
'default' => '',
'options' => [
['label' => '默认', 'value' => ''],
['label' => '样式3', 'value' => 'style3'],
],
],
[
'key' => 'list_card_scale',
'label' => '卡片缩略图比例 (%)',
'type' => 'number',
'default' => 70,
'min' => 20,
'max' => 200,
],
[
'key' => 'list_card_home',
'label' => '首页卡片模式',
'type' => 'switch',
'default' => false,
],
[
'key' => 'list_card_cat',
'label' => '分类卡片模式',
'type' => 'taxonomy_select',
'taxonomy' => 'category',
'multiple' => true,
'default' => [],
],
[
'key' => 'list_card_tag',
'label' => '标签页卡片模式',
'type' => 'switch',
'default' => false,
],
[
'key' => 'list_card_author',
'label' => '用户页卡片模式',
'type' => 'switch',
'default' => false,
],
]],
['key' => 'paging_ajax_ias_s', 'label' => 'AJAX 自动加载', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1']],
['key' => 'ias_max', 'label' => '自动加载页数', 'type' => 'number', 'default' => 3, 'min' => 0, 'max' => 10, 'dependsOn' => ['key' => 'paging_ajax_s', 'value' => '1']],
['key' => 'ajax_trigger', 'label' => '翻页按钮文字', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '<i class="fa fa-angle-right"></i>加载更多'],
['key' => 'ajax_nomore', 'label' => '列表全部加载完毕文案', 'type' => 'textarea', 'sanitize_type' => 'html', 'default' => '没有更多内容了'],
],
],
],
+18 -42
View File
@@ -18,43 +18,28 @@ class SchemaPostList {
'thumb' => [
'key' => 'thumb',
'title' => '列表缩略图',
'version' => '1.0.0',
'version' => '9.0.0',
'option_key' => 'aksrt_settings',
'groups' => [
['key' => 'source', 'title' => '图片来源', 'fields' => [
['key' => 'thumb_priority', 'label' => '图片优先级', 'type' => 'input', 'default' => 'featured,attachment,resource,custom'],
['key' => 'thumb_fallback', 'label' => '无图时处理', 'type' => 'radio', 'default' => 'placeholder', 'options' => [
['label' => '显示占位图', 'value' => 'placeholder'],
['label' => '隐藏缩略图', 'value' => 'hide'],
['label' => '随机封面', 'value' => 'random'],
]],
['key' => 'thumb_spare_images', 'label' => '备用缩略图', 'type' => 'gallery', 'default' => [], 'dependsOn' => ['key' => 'thumb_fallback', 'value' => 'random'], 'description' => '当 thumb_fallback 为 random 时从此图库随机选取'],
['key' => 'thumb_aspect_ratio', 'label' => '图片比例', 'type' => 'select', 'default' => '16:9', 'options' => [
['label' => '16:9 宽屏', 'value' => '16:9'],
['label' => '4:3 标准', 'value' => '4:3'],
['label' => '1:1 正方形', 'value' => '1:1'],
['label' => '自适应', 'value' => 'auto'],
]],
['key' => 'list_thumb_fit', 'label' => '缩略图填充方式', 'type' => 'radio', 'default' => 'cover', 'options' => [
['label' => '填充裁剪', 'value' => 'cover'],
['label' => '完整显示', 'value' => 'contain'],
]],
['key' => 'source', 'title' => '图片来源', 'description' => '缩略图获取优先级:文章特色图像 > 外链特色图像 > 文章首图 > 分类封面图 > 备用缩略图 > 懒加载预载图。', 'fields' => [
['key' => 'list_thumb_slides_s', 'label' => '幻灯片缩略图', 'type' => 'switch', 'default' => true, 'description' => '允许幻灯片作为列表缩略图'],
['key' => 'list_thumb_video_s', 'label' => '视频缩略图', 'type' => 'switch', 'default' => true, 'description' => '鼠标悬停时自动播放视频缩略图'],
['key' => 'list_thumb_video_mute_s', 'label' => '视频缩略图静音', 'type' => 'switch', 'default' => true, 'dependsOn' => ['key' => 'list_thumb_video_s', 'value' => true], 'description' => '视频缩略图播放时静音'],
['key' => 'thumb_postfirstimg_s', 'label' => '自动获取文章首图', 'type' => 'switch', 'default' => true, 'description' => '自动从文章内容中提取第一张图片作为缩略图'],
['key' => 'thumb_catimg_s', 'label' => '自动获取分类封面', 'type' => 'switch', 'default' => true, 'description' => '无特色图时使用分类封面图作为缩略图'],
['key' => 'list_thumb_fit', 'label' => '缩略图填充方式', 'type' => 'radio', 'default' => '', 'options' => [
['label' => 'cover 铺满', 'value' => ''],
['label' => 'contain 等比例缩放', 'value' => 'contain'],
]],
['key' => 'thumb_postfirstimg_size', 'label' => '首图尺寸', 'type' => 'radio', 'default' => 'medium', 'dependsOn' => ['key' => 'thumb_postfirstimg_s', 'value' => true], 'options' => [
['label' => '缩略图', 'value' => 'thumbnail'],
['label' => '中等', 'value' => 'medium'],
['label' => '大', 'value' => 'large'],
['label' => '原始', 'value' => 'full'],
], 'description' => '自动获取文章首图时使用的 WP 图片尺寸'],
['key' => 'spare_thumbnail', 'label' => '备用缩略图', 'type' => 'repeater', 'sanitize_type' => 'array', 'default' => [['img' => get_template_directory_uri() . '/assets/img/thumbnail.svg']], 'addText' => '添加备用缩略图', 'fields' => [
['key' => 'img', 'label' => '图片', 'type' => 'media', 'default' => ''],
]],
['key' => 'multi', 'title' => '多图模式', 'fields' => [
['key' => 'thumb_multi_enabled', 'label' => '多图卡片', 'type' => 'switch', 'default' => false],
['key' => 'thumb_multi_count', 'label' => '最多显示几张', 'type' => 'number', 'default' => 3, 'min' => 2, 'max' => 6, 'dependsOn' => ['key' => 'thumb_multi_enabled', 'value' => true]],
['key' => 'mult_thumb_cat', 'label' => '按分类多图显示', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'dependsOn' => ['key' => 'thumb_multi_enabled', 'value' => true]],
]],
],
],
@@ -103,32 +88,23 @@ class SchemaPostList {
['key' => 'post_lightbox_play', 'label' => '灯箱播放', 'type' => 'switch', 'default' => false],
]],
['key' => 'related', 'title' => '相关推荐', 'fields' => [
['key' => 'post_related_enabled', 'label' => '显示相关推荐', 'type' => 'switch', 'default' => true],
['key' => 'post_related_count', 'label' => '推荐数量', 'type' => 'number', 'default' => 6, 'min' => 1, 'max' => 20],
['key' => 'post_related_style', 'label' => '展示样式', 'type' => 'radio', 'default' => 'img', 'options' => [
['key' => 'post_related_s', 'label' => '显示相关推荐', 'type' => 'switch', 'default' => true],
['key' => 'post_related_type', 'label' => '展示样式', 'type' => 'radio', 'default' => 'img', 'options' => [
['label' => '图文卡片', 'value' => 'img'],
['label' => '列表', 'value' => 'list'],
['label' => '纯文字', 'value' => 'text'],
], 'description' => '对齐 Zibll 相关文章三种样式'],
['key' => 'post_related_source', 'label' => '推荐来源', 'type' => 'radio', 'default' => 'category', 'options' => [
['label' => '同分类/标签', 'value' => 'category'],
['label' => '同标签', 'value' => 'tag'],
['label' => '随机', 'value' => 'random'],
]],
// Legacy alias kept so old saved values still round-trip.
['key' => 'post_related_type', 'label' => '推荐方式(兼容)', 'type' => 'radio', 'default' => 'category', 'options' => [
['label' => '同分类', 'value' => 'category'],
['label' => '同标签', 'value' => 'tag'],
['label' => '随机', 'value' => 'random'],
]],
['key' => 'related_title', 'label' => '推荐标题', 'type' => 'input', 'default' => '相关推荐'],
['key' => 'post_related_n', 'label' => '推荐数量', 'type' => 'number', 'default' => 6, 'min' => 4, 'max' => 12, 'step' => 2],
['key' => 'post_related_orderby', 'label' => '推荐排序', 'type' => 'select', 'default' => 'views', 'options' => [
['label' => '浏览量', 'value' => 'views'],
['label' => '随机', 'value' => 'rand'],
['label' => '相关度', 'value' => 'relevance'],
['label' => '时间', 'value' => 'date'],
['label' => '更新时间', 'value' => 'modified'],
['label' => '发布时间', 'value' => 'date'],
['label' => '浏览量', 'value' => 'views'],
['label' => '点赞量', 'value' => 'like'],
['label' => '评论量', 'value' => 'comment_count'],
['label' => '收藏数量', 'value' => 'favorite'],
['label' => '随机', 'value' => 'rand'],
]],
['key' => 'post_related_title', 'label' => '推荐标题', 'type' => 'input', 'default' => '相关推荐'],
]],
['key' => 'interactions', 'title' => '互动功能', 'fields' => [
['key' => 'post_like_enabled', 'label' => '点赞', 'type' => 'switch', 'default' => true],
-2
View File
@@ -71,7 +71,6 @@ class DomainModel {
return [
'cover_images' => '',
'custom_excerpt' => '',
'layout_override' => '',
'header_image_mode' => '',
'show_toc' => '',
'show_reading_progress' => '',
@@ -105,7 +104,6 @@ class DomainModel {
return [
'cover_url' => '',
'description' => '',
'layout_mode' => '',
'show_sidebar' => '',
'show_header' => true,
'show_featured' => false,
+1 -1
View File
@@ -214,7 +214,7 @@ class HomeModules {
return [
['type' => 'hero', 'config' => []],
['type' => 'tabs', 'config' => ['count' => 8]],
['type' => 'recent', 'config' => ['count' => 8, 'layout' => 'grid-cms']],
['type' => 'recent', 'config' => ['count' => 8, 'layout' => 'card']],
['type' => 'ranking', 'config' => ['metric' => 'downloads', 'count' => 6]],
];
}
+3 -41
View File
@@ -163,14 +163,6 @@ class PostMetaBox {
<small class="description"><?php esc_html_e('用于列表多图模式,填写附件 ID(逗号分隔)。', 'aksrt'); ?></small>
</p>
<p><strong><?php esc_html_e('列表布局覆盖', 'aksrt'); ?></strong></p>
<select name="aksrt_post_meta[layout_override]" class="widefat">
<option value="" <?php selected($meta['layout_override'], ''); ?>><?php esc_html_e('跟随全局', 'aksrt'); ?></option>
<option value="card" <?php selected($meta['layout_override'], 'card'); ?>><?php esc_html_e('卡片', 'aksrt'); ?></option>
<option value="list" <?php selected($meta['layout_override'], 'list'); ?>><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="mini" <?php selected($meta['layout_override'], 'mini'); ?>><?php esc_html_e('迷你', 'aksrt'); ?></option>
</select>
<p><strong><?php esc_html_e('侧栏布局(Zibll show_layout)', 'aksrt'); ?></strong></p>
<?php
$show_layout = (string) get_post_meta($post->ID, '_aksrt_show_layout', true);
@@ -253,7 +245,7 @@ class PostMetaBox {
<p><strong><?php esc_html_e('外链特色图', 'aksrt'); ?></strong></p>
<input type="url" name="aksrt_post_meta[thumbnail_url]" value="<?php echo esc_url($meta['thumbnail_url']); ?>" class="widefat" placeholder="<?php esc_attr_e('外部图片 URL(覆盖特色图片)', 'aksrt'); ?>">
<small class="description"><?php esc_html_e('填入外部图片 URL 将替代特色图片。', 'aksrt'); ?></small>
<small class="description"><?php esc_html_e('未设置文章特色图片时使用此外链图片。', 'aksrt'); ?></small>
<p><strong><?php esc_html_e('幻灯片封面', 'aksrt'); ?></strong></p>
<input type="text" name="aksrt_post_meta[featured_slide]" value="<?php echo esc_attr($meta['featured_slide']); ?>" class="widefat" placeholder="<?php esc_attr_e('图片 URL,逗号分隔', 'aksrt'); ?>">
@@ -359,7 +351,7 @@ class PostMetaBox {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (!current_user_can('edit_post', $post_id)) return;
// Zibll show_layout (independent of list layout_override).
// Zibll show_layout.
$show_layout = isset($_POST['aksrt_show_layout']) ? sanitize_key((string) wp_unslash($_POST['aksrt_show_layout'])) : '';
if (!in_array($show_layout, ['no_sidebar', 'sidebar_left', 'sidebar_right'], true)) {
$show_layout = '';
@@ -391,7 +383,6 @@ class PostMetaBox {
$sanitized = [
'cover_images' => isset($raw['cover_images']) ? sanitize_text_field($raw['cover_images']) : '',
'custom_excerpt' => isset($raw['custom_excerpt']) ? sanitize_textarea_field($raw['custom_excerpt']) : '',
'layout_override' => isset($raw['layout_override']) ? sanitize_key($raw['layout_override']) : '',
'header_image_mode' => isset($raw['header_image_mode']) ? sanitize_key($raw['header_image_mode']) : '',
// New route: '' follow | '1' force on | '0' force off.
'show_toc' => isset($raw['show_toc']) && in_array((string) $raw['show_toc'], ['', '0', '1'], true) ? (string) $raw['show_toc'] : '',
@@ -417,11 +408,6 @@ class PostMetaBox {
'maxheight' => isset($raw['maxheight']) ? max(0, (int) $raw['maxheight']) : 0,
];
$valid_modes = ['', 'card', 'list', 'mini', 'grid-cms'];
if (!in_array($sanitized['layout_override'], $valid_modes, true)) {
$sanitized['layout_override'] = '';
}
$valid_header_modes = ['', 'featured', 'auto', 'none'];
if (!in_array($sanitized['header_image_mode'], $valid_header_modes, true)) {
$sanitized['header_image_mode'] = '';
@@ -476,10 +462,9 @@ class PostMetaBox {
echo esc_html($views > 0 ? (string) $views : '—');
} elseif ($column === 'aksrt_likes') {
$likes = (int) ($meta['likes'] ?? 0);
$layout = esc_attr($meta['layout_override'] ?? '');
$toc = esc_attr($meta['show_toc'] ?? '');
$mh = (int) ($meta['maxheight'] ?? 0);
echo '<span class="aksrt-col-data" data-views="' . (int) ($meta['views'] ?? 0) . '" data-likes="' . $likes . '" data-layout="' . $layout . '" data-toc="' . $toc . '" data-maxheight="' . $mh . '">' . esc_html($likes > 0 ? (string) $likes : '—') . '</span>';
echo '<span class="aksrt-col-data" data-views="' . (int) ($meta['views'] ?? 0) . '" data-likes="' . $likes . '" data-toc="' . $toc . '" data-maxheight="' . $mh . '">' . esc_html($likes > 0 ? (string) $likes : '—') . '</span>';
} elseif ($column === 'aksrt_sales') {
$this->render_sales_column($post_id);
}
@@ -631,17 +616,6 @@ class PostMetaBox {
<span class="title"><?php esc_html_e('点赞数', 'aksrt'); ?></span>
<input type="number" name="aksrt_qe_likes" min="0" step="1" value="" class="aksrt-qe-likes">
</label>
<label class="alignleft">
<span class="title"><?php esc_html_e('布局', 'aksrt'); ?></span>
<select name="aksrt_qe_layout" class="aksrt-qe-layout">
<option value="-1"><?php esc_html_e('— 不更改 —', 'aksrt'); ?></option>
<option value=""><?php esc_html_e('跟随全局', 'aksrt'); ?></option>
<option value="card"><?php esc_html_e('卡片', 'aksrt'); ?></option>
<option value="list"><?php esc_html_e('列表', 'aksrt'); ?></option>
<option value="mini"><?php esc_html_e('迷你', 'aksrt'); ?></option>
<option value="grid-cms"><?php esc_html_e('CMS网格', 'aksrt'); ?></option>
</select>
</label>
<label class="alignleft">
<span class="title"><?php esc_html_e('目录树', 'aksrt'); ?></span>
<select name="aksrt_qe_toc" class="aksrt-qe-toc">
@@ -771,14 +745,6 @@ class PostMetaBox {
? max(0, (int) $_POST['aksrt_qe_likes'])
: (int) ($existing['likes'] ?? 0);
$layout = $existing['layout_override'] ?? '';
if (isset($_POST['aksrt_qe_layout']) && $_POST['aksrt_qe_layout'] !== '-1') {
$layout = sanitize_key((string) $_POST['aksrt_qe_layout']);
if (!in_array($layout, ['', 'card', 'list', 'mini', 'grid-cms'], true)) {
$layout = $existing['layout_override'] ?? '';
}
}
$toc = $existing['show_toc'] ?? '';
if (isset($_POST['aksrt_qe_toc']) && $_POST['aksrt_qe_toc'] !== '-1') {
$toc_v = sanitize_key((string) $_POST['aksrt_qe_toc']);
@@ -791,7 +757,6 @@ class PostMetaBox {
$existing['views'] = $views;
$existing['likes'] = $likes;
$existing['layout_override'] = $layout;
$existing['show_toc'] = $toc;
$existing['maxheight'] = $mh;
@@ -835,7 +800,6 @@ class PostMetaBox {
var v = data.getAttribute('data-views') || '';
var l = data.getAttribute('data-likes') || '';
var layout = data.getAttribute('data-layout') || '';
var toc = data.getAttribute('data-toc') || '';
var mh = data.getAttribute('data-maxheight') || '';
@@ -844,8 +808,6 @@ class PostMetaBox {
if (qv) qv.value = v;
if (ql) ql.value = l;
var qLayout = form.querySelector('.aksrt-qe-layout');
if (qLayout) qLayout.value = layout;
var qToc = form.querySelector('.aksrt-qe-toc');
if (qToc) qToc.value = toc;
var qMh = form.querySelector('.aksrt-qe-maxheight');
+2 -5
View File
@@ -316,7 +316,7 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
|| is_page_template('templates/pages/page-user-home.php');
$is_fixed_homepage = function_exists('aksrt_is_fixed_homepage_request') && aksrt_is_fixed_homepage_request();
// CMS 形态判定(可能在博客流页面也需要 grid-cms 卡片样式)
// CMS route detection.
$is_cms_mode = function_exists('aksrt_is_cms_mode') ? aksrt_is_cms_mode() : false;
// 首页/归档/搜索:卡片 + 侧栏
if ($is_home_or_archive) {
@@ -755,10 +755,8 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
$enqueue_module('pages');
}
// grid-cms 卡片样式
// Resource archives use the canonical Zibll article-card stylesheet.
if ($is_resource_archive || $is_single_resource || ($is_cms_mode && ($is_home_or_archive || $is_static_page) && !$is_fixed_homepage)) {
$enqueue_module('post-card-cms');
// 确保 post-card 基础样式也加载(若上面分支未加)
if (!$is_home_or_archive && !$is_single_post && !$is_static_page) {
$enqueue_module('post-card');
}
@@ -797,7 +795,6 @@ $hmb_js = get_template_directory() . '/assets/js/header-newadd.js';
$enqueue_module('home-modules');
$enqueue_module('post-card'); // 模块内复用
$enqueue_module('header-components'); // header components 模块依赖
$enqueue_module('post-card-cms'); // grid-cms 卡片依赖
wp_enqueue_script(
'aksrt-cms-home-modules',
+22 -9
View File
@@ -72,8 +72,9 @@ trait ScriptsPart1c {
// ===== 7. JS =====
// Swiper.js(首页/归档/CMS 模块/单篇文章页加载)
$needs_swiper = $is_home_or_archive || $is_single_post || $is_single_resource;
// Swiper.js(文章列表媒体及单篇资源页加载)
$needs_post_list_media = wp_style_is('aksrt-post-card', 'enqueued');
$needs_swiper = $needs_post_list_media || $is_home_or_archive || $is_single_post || $is_single_resource;
if ($needs_swiper) {
wp_enqueue_style(
'aksrt-swiper',
@@ -119,6 +120,17 @@ trait ScriptsPart1c {
true
);
if ($needs_post_list_media) {
$grade_js = get_template_directory() . '/assets/js/grade.min.js';
wp_enqueue_script(
'aksrt-grade',
$base_uri . '/assets/js/grade.min.js',
[],
file_exists($grade_js) ? (string) filemtime($grade_js) : $version,
true
);
}
$notyf_js = get_template_directory() . '/assets/js/notyf.js';
wp_enqueue_script(
'aksrt-notyf',
@@ -173,6 +185,8 @@ trait ScriptsPart1c {
$deps = ['aksrt-rehydrate', 'aksrt-runtime'];
if ($handle === 'aksrt-core-slider') {
$deps = array_merge($deps, $needs_swiper ? ['aksrt-swiper'] : []);
} elseif ($handle === 'aksrt-core-misc' && $needs_post_list_media) {
$deps[] = 'aksrt-grade';
} elseif ($handle === 'aksrt-core-modal-runtime') {
$deps = array_merge($deps, [
'aksrt-core-modal-telemetry',
@@ -558,15 +572,14 @@ trait ScriptsPart1c {
'commentPaginationMode' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_comment_pagination_mode() : 'numbered',
'homepagePostsPerPage' => function_exists('aksrt_homepage_get_tab_posts_per_page') ? aksrt_homepage_get_tab_posts_per_page() : (int) get_option('posts_per_page'),
'homepageDefaultSort' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_default_sort() : 'modified',
'homepageDefaultView' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_default_view() : 'list',
'homepageSortAjax' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sort_ajax_enabled() : true,
'homepageStickyTabs' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::homepage_sticky_tabs_enabled() : true,
'homePostsListEnabled' => !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::home_posts_list_enabled(),
'homeListSortEnabled' => !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::home_list_sort_enabled(),
'homeListSortDropdown' => class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::home_list_sort_dropdown(),
// AJAX 翻页文案
'ajaxTrigger' => (class_exists('\AKSRT\Settings\Settings') && ($t = \AKSRT\Settings\Settings::get_post_list_style_settings()['ajax_trigger_text'] ?? '') !== '') ? $t : __('加载更多', 'aksrt'),
'ajaxNomore' => (class_exists('\AKSRT\Settings\Settings') && ($n = \AKSRT\Settings\Settings::get_post_list_style_settings()['ajax_nomore_text'] ?? '') !== '') ? $n : __('没有更多了', 'aksrt'),
'ajaxTrigger' => (class_exists('\AKSRT\Settings\Settings') && ($t = \AKSRT\Settings\Settings::get_post_list_style_settings()['ajax_trigger'] ?? '') !== '') ? $t : '<i class="fa fa-angle-right"></i>加载更多',
'ajaxNomore' => (class_exists('\AKSRT\Settings\Settings') && ($n = \AKSRT\Settings\Settings::get_post_list_style_settings()['ajax_nomore'] ?? '') !== '') ? $n : __('没有更多内容了', 'aksrt'),
'ajaxLoading' => __('正在加载...', 'aksrt'),
'iasMax' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['ias_max'] : 3,
'commentIasAuto' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_comment_display_settings()['paging_ajax_ias'] : true,
@@ -585,10 +598,10 @@ trait ScriptsPart1c {
'identity' => true,
'badge' => true,
],
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_enabled'] : true,
'stickyPages' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_pages'] : ['home', 'cat'],
'badgeShow' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['badge_show'] : true,
'badgeTypes' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['badge_types'] : ['pay', 'tag', 'cat'],
'stickyEnabled' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s']) : true,
'stickyPages' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['sticky_s'] : ['home', 'cat'],
'badgeShow' => class_exists('\AKSRT\Settings\Settings') ? !empty(\AKSRT\Settings\Settings::get_post_list_style_settings()['list_badge_show']) : true,
'badgeTypes' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['list_badge_show'] : ['pay', 'tag', 'cat'],
'search' => [
'mode' => class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::search_mode() : 'default',
'placeholder' => function_exists('aksrt_get_search_placeholder') ? aksrt_get_search_placeholder() : __('开启精彩搜索...', 'aksrt'),
+7 -8
View File
@@ -274,10 +274,6 @@ class Shortcodes {
* 用统一渲染器输出文章列表,避免内容里手写卡片循环。
*/
public function render_posts(array $atts = [], string $content = ''): string {
if (!function_exists('aksrt_render_post_list')) {
return '';
}
$a = shortcode_atts([
'count' => 6,
'cat' => '',
@@ -309,10 +305,13 @@ class Shortcodes {
$mode = in_array($a['mode'], ['card', 'list', 'mini'], true) ? $a['mode'] : 'card';
$query = new \WP_Query($query_args);
$html = aksrt_render_post_list($query, [
'context' => 'widget',
'mode' => $mode,
], false);
if ($mode === 'mini') {
$html = aksrt_render_mini_list($query, [], false);
} else {
$html = aksrt_render_post_list([
'type' => $mode === 'card' ? 'card' : 'list',
], $query, false);
}
wp_reset_postdata();
return '<div class="aksrt-shortcode-posts">' . $html . '</div>';
-50
View File
@@ -66,7 +66,6 @@ class TermMeta {
'header_style' => 'cover',
'cover_enabled' => true,
'desc_enabled' => true,
'list_mode' => 'grid',
'filter_bar' => true,
'sidebar' => true,
'per_page' => 12,
@@ -77,25 +76,6 @@ class TermMeta {
$saved = [];
}
// Empty term layout_mode means follow global post-list settings (Zibll).
// Do not fall back to page-display list_mode=grid which forced card everywhere.
$layout_preset = isset($saved['layout_mode']) ? sanitize_key((string) $saved['layout_mode']) : '';
if ($layout_preset === '') {
$resolved_layout = ['mode' => '', 'card_style' => ''];
$layout_mode = '';
} else {
$layout_mode = $layout_preset;
$layout_map = [
'grid' => ['mode' => 'card', 'card_style' => 'thumb'],
'card' => ['mode' => 'card', 'card_style' => 'thumb'],
'list' => ['mode' => 'list', 'card_style' => 'thumb'],
'compact' => ['mode' => 'list', 'card_style' => 'compact'],
'mini' => ['mode' => 'mini', 'card_style' => 'thumb'],
'grid-cms' => ['mode' => 'grid-cms', 'card_style' => 'thumb'],
];
$resolved_layout = $layout_map[$layout_mode] ?? ['mode' => 'list', 'card_style' => 'thumb'];
}
$header_style = (string) ($global['header_style'] ?? 'simple');
if (!in_array($header_style, ['cover', 'simple', 'none'], true)) {
$header_style = 'simple';
@@ -126,9 +106,6 @@ class TermMeta {
return [
'taxonomy' => $taxonomy,
'layout_mode' => $resolved_layout['mode'],
'card_style' => $resolved_layout['card_style'],
'layout_preset' => $layout_mode,
'header_style' => $header_style,
'show_sidebar' => $sidebar_enabled,
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']) && $cover_url !== '',
@@ -191,15 +168,6 @@ class TermMeta {
<input type="url" name="aksrt_term_meta[cover_url]" id="aksrt_term_cover_url" value="" placeholder="https://...">
<p class="description"><?php esc_html_e('分类/标签页顶部封面图,留空则不显示。', 'aksrt'); ?></p>
</div>
<div class="form-field">
<label for="aksrt_term_layout"><?php esc_html_e('列表布局', 'aksrt'); ?></label>
<select name="aksrt_term_meta[layout_mode]" id="aksrt_term_layout">
<option value=""><?php esc_html_e('跟随全局设置', 'aksrt'); ?></option>
<option value="card"><?php esc_html_e('卡片模式', 'aksrt'); ?></option>
<option value="list"><?php esc_html_e('列表模式', 'aksrt'); ?></option>
<option value="mini"><?php esc_html_e('迷你模式', 'aksrt'); ?></option>
</select>
</div>
<?php
}
@@ -223,17 +191,6 @@ class TermMeta {
<p class="description"><?php esc_html_e('覆盖默认的分类/标签描述。', 'aksrt'); ?></p>
</td>
</tr>
<tr class="form-field">
<th scope="row"><label for="aksrt_term_layout"><?php esc_html_e('列表布局', 'aksrt'); ?></label></th>
<td>
<select name="aksrt_term_meta[layout_mode]" id="aksrt_term_layout">
<option value="" <?php selected($meta['layout_mode'], ''); ?>><?php esc_html_e('跟随全局设置', 'aksrt'); ?></option>
<option value="card" <?php selected($meta['layout_mode'], 'card'); ?>><?php esc_html_e('卡片模式', 'aksrt'); ?></option>
<option value="list" <?php selected($meta['layout_mode'], 'list'); ?>><?php esc_html_e('列表模式', 'aksrt'); ?></option>
<option value="mini" <?php selected($meta['layout_mode'], 'mini'); ?>><?php esc_html_e('迷你模式', 'aksrt'); ?></option>
</select>
</td>
</tr>
<tr class="form-field">
<th scope="row"><?php esc_html_e('显示设置', 'aksrt'); ?></th>
<td>
@@ -269,18 +226,11 @@ class TermMeta {
$sanitized = [
'cover_url' => isset($raw['cover_url']) ? esc_url_raw($raw['cover_url']) : '',
'description' => isset($raw['description']) ? sanitize_textarea_field($raw['description']) : '',
'layout_mode' => isset($raw['layout_mode']) ? sanitize_key($raw['layout_mode']) : '',
'show_sidebar' => isset($raw['show_sidebar']) ? '1' : '',
'show_header' => isset($raw['show_header']) ? true : false,
'show_featured' => isset($raw['show_featured']) ? true : false,
];
// Only allow valid layout modes
$valid_modes = ['', 'card', 'list', 'mini', 'grid-cms'];
if (!in_array($sanitized['layout_mode'], $valid_modes, true)) {
$sanitized['layout_mode'] = '';
}
update_term_meta($term_id, self::META_KEY, $sanitized);
}
}
+11 -2
View File
@@ -77,8 +77,8 @@ class InlineStyleBuilder {
'--aksrt-logo-width' => $logo_width . 'px',
'--nav-height' => '60px',
'--single-cover-scale' => '35%',
'--posts-list-scale' => (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['list_scale'] ?? 70 : 70) . '%',
'--posts-card-scale' => (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_post_list_style_settings()['card_scale'] ?? 70 : 70) . '%',
'--posts-list-scale' => (class_exists('\AKSRT\Settings\Settings') ? (\AKSRT\Settings\Settings::get_post_list_style_settings()['list_list_option']['scale'] ?? 70) : 70) . '%',
'--posts-card-scale' => (class_exists('\AKSRT\Settings\Settings') ? (\AKSRT\Settings\Settings::get_post_list_style_settings()['list_card_option']['scale'] ?? 70) : 70) . '%',
];
echo '<style id="aksrt-runtime-vars">:root{';
@@ -106,6 +106,15 @@ class InlineStyleBuilder {
}
echo '}</style>' . "\n";
if (class_exists('\AKSRT\Settings\Settings')) {
$list_settings = \AKSRT\Settings\Settings::get_post_list_style_settings();
$mobile_meta = sanitize_key((string) ($list_settings['list_meta_show'] ?? 'like'));
if (!in_array($mobile_meta, ['view', 'like', 'comm'], true)) {
$mobile_meta = 'like';
}
echo '<style id="aksrt-post-list-mobile-meta">@media (max-width:640px){.meta-right .meta-' . esc_attr($mobile_meta) . '{display:unset!important}}</style>' . "\n";
}
// Zibll header_theme_custom → --header-bg / --header-color on .header.
if (class_exists('\AKSRT\Settings\Settings')) {
$ht = \AKSRT\Settings\Settings::get_header_theme_config();
+2 -49
View File
@@ -170,55 +170,8 @@ trait DisplayPageGroups {
$filter = self::get_post_filter_settings();
return [
'list' => [
'post_card_style' => $list['list_type'] ?? $list['card_style'] ?? 'thumb',
'post_list_author' => $list['show_author'],
'post_show_date' => $list['show_date'],
'post_show_views' => $list['show_views'],
'post_show_category' => $list['show_category'],
'post_show_comments' => $list['show_comments'],
'post_show_likes' => $list['show_likes'],
'post_excerpt_length' => $list['excerpt_length'],
'paging_ajax_s' => PostDisplay::is_list_paging_ajax() ? '1' : '0',
'paging_ajax_ias_s' => PostDisplay::is_list_paging_ajax_ias(),
'post_pagination_mode' => self::get_post_pagination_mode(),
'render_mode' => $list['render_mode'],
'sticky_s' => $list['sticky_pages'],
'list_badge_show' => $list['badge_types'],
'target_blank' => $list['target_blank'],
'item_heading_bold' => $list['heading_bold'],
'list_meta_show' => $list['mobile_meta'],
'list_show_type' => $list['list_show_type'],
'ias_max' => $list['ias_max'],
'ajax_trigger' => $list['ajax_trigger_text'],
'ajax_nomore' => $list['ajax_nomore_text'],
'ajax_nomore_text' => $list['ajax_nomore_text'],
'post_list_columns' => $list['columns'],
'post_list_per_page' => $list['per_page'],
'list_list_style' => $list['list_style'],
'list_list_img_position' => $list['list_img_position'],
'list_list_scale' => $list['list_scale'],
'list_card_style' => $list['card_variant'] ?? '',
'list_card_scale' => $list['card_scale'],
'list_card_home' => $list['card_home'],
'list_card_cat' => $list['card_cat_ids'] ?? [],
'list_card_tag' => $list['card_tag'],
'list_card_author' => $list['card_author'],
],
'thumb' => [
'thumb_priority' => implode(',', $thumb['priority']),
'thumb_fallback' => $thumb['fallback'],
'thumb_spare_images' => $thumb['spare_images'],
'thumb_aspect_ratio' => $thumb['aspect_ratio'],
'thumb_multi_enabled' => $thumb['multi_enabled'],
'thumb_multi_count' => $thumb['multi_count'],
'list_thumb_fit' => $thumb['fit'],
'list_thumb_slides_s' => $thumb['slides_s'],
'list_thumb_video_s' => $thumb['video_s'],
'list_thumb_video_mute_s' => $thumb['video_mute_s'],
'thumb_postfirstimg_s' => $thumb['postfirstimg_s'],
'thumb_catimg_s' => $thumb['catimg_s'],
],
'list' => $list,
'thumb' => $thumb,
'filter' => [
'filter_enabled' => $filter['enabled'],
'filter_categories' => $filter['categories'],
+4 -6
View File
@@ -259,13 +259,11 @@ trait FacadeBulk {
'reading_progress' => !isset($settings['post_reading_progress']) || !empty($settings['post_reading_progress']),
'breadcrumbs' => !isset($settings['breadcrumbs_single_s']) || !empty($settings['breadcrumbs_single_s']),
'breadcrumbs_home' => trim((string) ($settings['breadcrumbs_home_text'] ?? '')) ?: __('首页', 'aksrt'),
'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_style' => 'img',
'related_source' => 'category',
'related_type' => 'category',
'related_enabled' => !isset($settings['post_related_s']) || !empty($settings['post_related_s']),
'related_count' => max(4, min(12, (int) ($settings['post_related_n'] ?? 6))),
'related_style' => in_array(($settings['post_related_type'] ?? 'img'), ['img', 'list', 'text'], true) ? (string) $settings['post_related_type'] : 'img',
'related_orderby' => 'views',
'related_title' => sanitize_text_field((string) ($settings['post_related_title'] ?? '相关推荐')),
'related_title' => sanitize_text_field((string) ($settings['related_title'] ?? '相关推荐')),
];
}
-35
View File
@@ -212,12 +212,7 @@ class HomeCms {
'home_exclude_cats' => $exclude_cats,
'home_lists' => self::normalize_homepage_tabs($tabs_raw),
// AKSRT presentation extras.
'homepage_card_style' => isset($s['homepage_card_style']) ? sanitize_key((string) $s['homepage_card_style']) : 'default',
'homepage_multi_thumb_enabled' => !empty($s['homepage_multi_thumb_enabled']),
'homepage_multi_thumb_min_images' => (int) ($s['homepage_multi_thumb_min_images'] ?? 3),
'homepage_sidebar_mini_enabled' => !isset($s['homepage_sidebar_mini_enabled']) || !empty($s['homepage_sidebar_mini_enabled']),
'homepage_default_view' => isset($s['homepage_default_view']) ? sanitize_key((string) $s['homepage_default_view']) : 'list',
'homepage_sort_ajax_enabled' => !isset($s['homepage_sort_ajax_enabled']) || !empty($s['homepage_sort_ajax_enabled']),
'homepage_sticky_tabs_enabled' => !isset($s['homepage_sticky_tabs_enabled']) || !empty($s['homepage_sticky_tabs_enabled']),
'home_list_sort_dropdown' => !empty($s['home_list_sort_dropdown']),
@@ -227,16 +222,6 @@ class HomeCms {
'homepage_module_banner_enabled' => !isset($s['homepage_module_banner_enabled']) || !empty($s['homepage_module_banner_enabled']),
];
if (!in_array($settings['homepage_card_style'], ['default', 'style2', 'style3'], true)) {
$settings['homepage_card_style'] = 'default';
}
$settings['homepage_multi_thumb_min_images'] = max(2, min(4, $settings['homepage_multi_thumb_min_images']));
if (!in_array($settings['homepage_default_view'], ['list', 'card'], true)) {
$settings['homepage_default_view'] = 'list';
}
return $settings;
}
@@ -259,21 +244,6 @@ class HomeCms {
return self::homepage_tabs();
}
public static function homepage_card_style(): string {
$settings = self::get_homepage_fixed_settings();
return $settings['homepage_card_style'];
}
public static function homepage_multi_thumb_enabled(): bool {
$settings = self::get_homepage_fixed_settings();
return !empty($settings['homepage_multi_thumb_enabled']);
}
public static function homepage_multi_thumb_min_images(): int {
$settings = self::get_homepage_fixed_settings();
return (int) $settings['homepage_multi_thumb_min_images'];
}
public static function homepage_sidebar_mini_enabled(): bool {
$settings = self::get_homepage_fixed_settings();
return !empty($settings['homepage_sidebar_mini_enabled']);
@@ -284,11 +254,6 @@ class HomeCms {
return $settings['home_list1_orderby_option']['orderby'] ?? 'date';
}
public static function homepage_default_view(): string {
$settings = self::get_homepage_fixed_settings();
return $settings['homepage_default_view'];
}
public static function homepage_sort_ajax_enabled(): bool {
$settings = self::get_homepage_fixed_settings();
return !empty($settings['homepage_sort_ajax_enabled']);
-30
View File
@@ -54,29 +54,6 @@ trait HomepageReader {
return HomeCms::homepage_tabs();
}
/**
* Homepage card view variant.
*
* @return string default|style2|style3
*/
public static function homepage_card_style(): string {
return HomeCms::homepage_card_style();
}
/**
* Whether homepage list cards should switch to the multi-thumb layout.
*/
public static function homepage_multi_thumb_enabled(): bool {
return HomeCms::homepage_multi_thumb_enabled();
}
/**
* Minimum image count required to trigger the multi-thumb layout.
*/
public static function homepage_multi_thumb_min_images(): int {
return HomeCms::homepage_multi_thumb_min_images();
}
/**
* Whether homepage sidebar widgets should use the compact mini list style.
*/
@@ -91,13 +68,6 @@ trait HomepageReader {
return HomeCms::homepage_default_sort();
}
/**
* Default homepage view mode.
*/
public static function homepage_default_view(): string {
return HomeCms::homepage_default_view();
}
/**
* Whether the homepage sortbar should refresh through AJAX.
*/
+79 -213
View File
@@ -11,177 +11,92 @@ class PostDisplay {
public static function get_post_list_style_settings(): array {
$settings = Settings::get_all();
// post_card_style storage key mirrors Zibll list_type.
$list_type = sanitize_key((string) ($settings['post_card_style'] ?? 'thumb'));
$allowed_list_types = ['thumb', 'compact', 'large_thumb', 'multi_thumb', 'text', 'thumb_if_has', 'card', 'standard'];
if (!in_array($list_type, $allowed_list_types, true)) {
$list_type = sanitize_key((string) ($settings['list_type'] ?? 'thumb'));
if (!in_array($list_type, ['text', 'thumb', 'thumb_if_has', 'card'], true)) {
$list_type = 'thumb';
}
// Historical aliases → Zibll list_type tokens.
if ($list_type === 'standard') {
$list_type = 'thumb';
}
// Non-Zibll extras stay list-mode variants (never force card grid).
if (in_array($list_type, ['compact', 'large_thumb', 'multi_thumb'], true)) {
// Keep token for CSS hooks; render as list.
}
// Zibll: only list_type=card is card mode; thumb/text/thumb_if_has are list.
$render_mode_map = [
'thumb' => 'list',
'compact' => 'list',
'large_thumb' => 'list',
'multi_thumb' => 'list',
'text' => 'list',
'thumb_if_has' => 'list',
'card' => 'card',
];
$list_option = is_array($settings['list_list_option'] ?? null) ? $settings['list_list_option'] : [];
$list_style = sanitize_key((string) ($list_option['style'] ?? 'null'));
$list_style = $list_style === 'style2' ? 'style2' : 'null';
$list_position = sanitize_key((string) ($list_option['img_position'] ?? 'left'));
$list_position = $list_position === 'right' ? 'right' : 'left';
$sticky_pages_raw = $settings['sticky_s'] ?? null;
if (is_array($sticky_pages_raw)) {
$allowed_pages = ['home', 'cat', 'tag'];
$sticky_pages = array_values(array_intersect(array_map('sanitize_key', $sticky_pages_raw), $allowed_pages));
} else {
$sticky_pages = ['home', 'cat'];
}
$card_option = is_array($settings['list_card_option'] ?? null) ? $settings['list_card_option'] : [];
$card_style = sanitize_key((string) ($card_option['style'] ?? 'null'));
$card_style = $card_style === 'style3' ? 'style3' : 'null';
$badge_types_raw = $settings['list_badge_show'] ?? null;
if (is_array($badge_types_raw)) {
$allowed_types = ['pay', 'cat', 'tag'];
$badge_types = array_values(array_intersect(array_map('sanitize_key', $badge_types_raw), $allowed_types));
} else {
$badge_types = ['pay', 'cat', 'tag'];
}
$sticky_pages = is_array($settings['sticky_s'] ?? null)
? array_values(array_intersect(array_map('sanitize_key', $settings['sticky_s']), ['home', 'cat', 'tag']))
: ['home', 'cat'];
$badge_types = is_array($settings['list_badge_show'] ?? null)
? array_values(array_intersect(array_map('sanitize_key', $settings['list_badge_show']), ['pay', 'cat', 'tag']))
: ['pay', 'cat', 'tag'];
$card_cat_ids = is_array($settings['list_card_cat'] ?? null)
? array_values(array_filter(array_map('intval', $settings['list_card_cat'])))
: [];
$show_author = array_key_exists('post_list_author', $settings)
? !empty($settings['post_list_author'])
: (!isset($settings['post_show_author']) || !empty($settings['post_show_author']));
$show_date = array_key_exists('post_list_time', $settings)
? !empty($settings['post_list_time'])
: (!isset($settings['post_show_date']) || !empty($settings['post_show_date']));
$target_blank = array_key_exists('target_blank', $settings)
? !empty($settings['target_blank'])
: !empty($settings['post_target_blank']);
$ajax_trigger = '';
if (array_key_exists('ajax_trigger', $settings)) {
$ajax_trigger = sanitize_text_field((string) $settings['ajax_trigger']);
} elseif (array_key_exists('ajax_trigger_text', $settings)) {
$ajax_trigger = sanitize_text_field((string) $settings['ajax_trigger_text']);
}
$ajax_nomore = '';
if (array_key_exists('ajax_nomore', $settings)) {
$ajax_nomore = sanitize_text_field((string) $settings['ajax_nomore']);
} elseif (array_key_exists('ajax_nomore_text', $settings)) {
$ajax_nomore = sanitize_text_field((string) $settings['ajax_nomore_text']);
}
$list_style = sanitize_key((string) ($settings['list_list_style'] ?? ''));
if (!in_array($list_style, ['', 'style2'], true)) {
$list_style = '';
}
$card_variant = sanitize_key((string) ($settings['list_card_style'] ?? ''));
if (!in_array($card_variant, ['', 'style3'], true)) {
$card_variant = '';
}
$card_cat_ids = [];
if (is_array($settings['list_card_cat'] ?? null)) {
$card_cat_ids = array_values(array_filter(array_map('intval', $settings['list_card_cat'])));
}
$time_source = sanitize_key((string) ($settings['post_time_source'] ?? 'modified'));
$mobile_meta = sanitize_key((string) ($settings['list_meta_show'] ?? 'like'));
$list_show_type = sanitize_key((string) ($settings['list_show_type'] ?? 'separate'));
$list_orderby = sanitize_key((string) ($settings['list_orderby'] ?? 'modified'));
return [
// Zibll list_type (storage: post_card_style).
'list_type' => $list_type,
// Base mode from list_type only (surface card overrides applied by resolver).
'render_mode' => $render_mode_map[$list_type] ?? 'list',
// List item subtype token for text/thumb_if_has class hooks (not card variant).
'list_type_token' => $list_type,
// List mode visual variant (Zibll list_list_option.style).
'list_style' => $list_style,
// Card mode visual variant (Zibll list_card_option.style).
'card_variant' => $card_variant,
// Back-compat aliases used by older callers (prefer list_type / card_variant).
'card_style' => $list_type,
'show_author' => $show_author,
'show_date' => $show_date,
'show_views' => !isset($settings['post_show_views']) || !empty($settings['post_show_views']),
'show_category' => !isset($settings['post_show_category']) || !empty($settings['post_show_category']),
'show_comments' => !isset($settings['post_meta_comments']) || !empty($settings['post_meta_comments']),
'show_likes' => !isset($settings['post_meta_likes']) || !empty($settings['post_meta_likes']),
'badge_types' => $badge_types,
'badge_show' => !empty($badge_types),
'sticky_pages' => $sticky_pages,
'sticky_enabled' => !empty($sticky_pages),
'target_blank' => $target_blank,
'heading_bold' => !empty($settings['item_heading_bold']),
'mobile_meta' => in_array(sanitize_key((string) ($settings['list_meta_show'] ?? 'like')), ['view', 'like', 'comm'], true) ? sanitize_key((string) ($settings['list_meta_show'] ?? 'like')) : 'like',
'list_show_type' => in_array(sanitize_key((string) ($settings['list_show_type'] ?? 'separate')), ['separate', 'no_margin'], true) ? sanitize_key((string) ($settings['list_show_type'] ?? 'separate')) : 'separate',
'excerpt_length' => max(20, min(500, (int) ($settings['post_excerpt_length'] ?? 120))),
'list_orderby' => in_array($list_orderby, ['date', 'modified', 'default'], true) ? $list_orderby : 'modified',
'post_time_source' => $time_source === 'date' ? 'date' : 'modified',
'sticky_s' => $sticky_pages,
'target_blank' => !empty($settings['target_blank']),
'item_heading_bold' => !empty($settings['item_heading_bold']),
'post_list_author' => !array_key_exists('post_list_author', $settings) || !empty($settings['post_list_author']),
'post_list_time' => !array_key_exists('post_list_time', $settings) || !empty($settings['post_list_time']),
'paging_ajax_s' => (string) ($settings['paging_ajax_s'] ?? '1') === '1',
'paging_ajax_ias_s' => !array_key_exists('paging_ajax_ias_s', $settings) || !empty($settings['paging_ajax_ias_s']),
'ias_max' => max(0, min(10, (int) ($settings['ias_max'] ?? 3))),
'paging_ajax' => self::is_list_paging_ajax($settings),
'paging_ajax_ias' => self::is_list_paging_ajax_ias($settings),
'ajax_trigger_text' => $ajax_trigger,
'ajax_nomore_text' => $ajax_nomore,
'list_orderby' => in_array(sanitize_key((string) ($settings['list_orderby'] ?? 'modified')), ['modified', 'date', 'default'], true) ? sanitize_key((string) ($settings['list_orderby'] ?? 'modified')) : 'modified',
'columns' => max(1, min(3, (int) ($settings['post_list_columns'] ?? 1))),
'per_page' => max(4, min(48, (int) ($settings['post_list_per_page'] ?? 12))),
'list_img_position' => in_array(sanitize_key((string) ($settings['list_list_img_position'] ?? 'left')), ['left', 'right'], true) ? sanitize_key((string) ($settings['list_list_img_position'] ?? 'left')) : 'left',
'list_scale' => max(50, min(200, (int) ($settings['list_list_scale'] ?? 70))),
'card_scale' => max(20, min(200, (int) ($settings['list_card_scale'] ?? 70))),
'card_home' => !empty($settings['list_card_home']),
'card_cat' => !empty($card_cat_ids),
'card_cat_ids' => $card_cat_ids,
'card_tag' => !empty($settings['list_card_tag']),
'card_author' => !empty($settings['list_card_author']),
'list_meta_show' => in_array($mobile_meta, ['view', 'like', 'comm'], true) ? $mobile_meta : 'like',
'list_badge_show' => $badge_types,
'list_show_type' => in_array($list_show_type, ['separate', 'no_margin'], true) ? $list_show_type : 'separate',
'list_card_home' => !empty($settings['list_card_home']),
'list_card_tag' => !empty($settings['list_card_tag']),
'list_card_author' => !empty($settings['list_card_author']),
'list_card_cat' => $card_cat_ids,
'list_list_option' => [
'style' => $list_style,
'img_position' => $list_position,
'scale' => max(50, min(200, (int) ($list_option['scale'] ?? 70))),
],
'list_card_option' => [
'style' => $card_style,
'scale' => max(20, min(200, (int) ($card_option['scale'] ?? 70))),
],
'mult_thumb_cat' => is_array($settings['mult_thumb_cat'] ?? null) ? array_values(array_filter(array_map('intval', $settings['mult_thumb_cat']))) : [],
'ajax_trigger' => wp_kses_post((string) ($settings['ajax_trigger'] ?? '<i class="fa fa-angle-right"></i>加载更多')),
'ajax_nomore' => wp_kses_post((string) ($settings['ajax_nomore'] ?? '没有更多内容了')),
];
}
public static function get_post_thumb_settings(): array {
$settings = Settings::get_all();
$priority_raw = (string) ($settings['thumb_priority'] ?? 'featured,attachment,resource,custom');
$priority = preg_split('/[\s,]+/', strtolower($priority_raw)) ?: [];
$priority = array_values(array_unique(array_filter(array_map('sanitize_key', $priority))));
$allowed_sources = ['featured', 'attachment', 'resource', 'custom', 'category_cover', 'post_first_img'];
$priority = array_values(array_filter($priority, static function (string $source) use ($allowed_sources): bool {
return in_array($source, $allowed_sources, true);
}));
if (empty($priority)) {
$priority = ['featured', 'attachment', 'resource', 'custom'];
$spare_settings = array_key_exists('spare_thumbnail', $settings)
? (array) $settings['spare_thumbnail']
: [['img' => get_template_directory_uri() . '/assets/img/thumbnail.svg']];
$spare = [];
foreach ($spare_settings as $item) {
$url = is_array($item) ? (string) ($item['img'] ?? '') : '';
if ($url !== '') {
$spare[] = esc_url_raw($url);
}
$fallback = sanitize_key((string) ($settings['thumb_fallback'] ?? 'placeholder'));
if (!in_array($fallback, ['placeholder', 'hide', 'random'], true)) {
$fallback = 'placeholder';
}
$aspect_ratio = (string) ($settings['thumb_aspect_ratio'] ?? '16:9');
if (!in_array($aspect_ratio, ['16:9', '4:3', '1:1', 'auto'], true)) {
$aspect_ratio = '16:9';
}
$fit = sanitize_key((string) ($settings['list_thumb_fit'] ?? ''));
return [
'priority' => $priority,
'fallback' => $fallback,
'spare_images' => is_array($settings['thumb_spare_images'] ?? null) ? $settings['thumb_spare_images'] : [],
'aspect_ratio' => $aspect_ratio,
'multi_enabled' => !empty($settings['thumb_multi_enabled']),
'multi_count' => max(2, min(6, (int) ($settings['thumb_multi_count'] ?? 3))),
'fit' => in_array(sanitize_key((string) ($settings['list_thumb_fit'] ?? 'cover')), ['cover', 'contain'], true) ? sanitize_key((string) ($settings['list_thumb_fit'] ?? 'cover')) : 'cover',
'slides_s' => !isset($settings['list_thumb_slides_s']) || !empty($settings['list_thumb_slides_s']),
'video_s' => !isset($settings['list_thumb_video_s']) || !empty($settings['list_thumb_video_s']),
'video_mute_s' => !isset($settings['list_thumb_video_mute_s']) || !empty($settings['list_thumb_video_mute_s']),
'postfirstimg_s' => !isset($settings['thumb_postfirstimg_s']) || !empty($settings['thumb_postfirstimg_s']),
'catimg_s' => !isset($settings['thumb_catimg_s']) || !empty($settings['thumb_catimg_s']),
'postfirstimg_size'=> in_array(sanitize_key((string) ($settings['thumb_postfirstimg_size'] ?? 'medium')), ['thumbnail', 'medium', 'large', 'full'], true) ? sanitize_key((string) ($settings['thumb_postfirstimg_size'] ?? 'medium')) : 'medium',
'mult_thumb_cat_ids' => is_array($settings['mult_thumb_cat'] ?? null) ? array_map('intval', $settings['mult_thumb_cat']) : [],
'list_thumb_slides_s' => !array_key_exists('list_thumb_slides_s', $settings) || !empty($settings['list_thumb_slides_s']),
'list_thumb_video_s' => !array_key_exists('list_thumb_video_s', $settings) || !empty($settings['list_thumb_video_s']),
'list_thumb_video_mute_s' => !array_key_exists('list_thumb_video_mute_s', $settings) || !empty($settings['list_thumb_video_mute_s']),
'thumb_postfirstimg_s' => !array_key_exists('thumb_postfirstimg_s', $settings) || !empty($settings['thumb_postfirstimg_s']),
'thumb_catimg_s' => !array_key_exists('thumb_catimg_s', $settings) || !empty($settings['thumb_catimg_s']),
'list_thumb_fit' => $fit === 'contain' ? 'contain' : '',
'thumb_postfirstimg_size' => in_array(sanitize_key((string) ($settings['thumb_postfirstimg_size'] ?? 'medium')), ['thumbnail', 'medium', 'large', 'full'], true) ? sanitize_key((string) ($settings['thumb_postfirstimg_size'] ?? 'medium')) : 'medium',
'spare_thumbnail' => $spare,
];
}
@@ -262,28 +177,13 @@ class PostDisplay {
$maxheight = 1000;
}
// Related: split Zibll display style (img/list/text) from query source (category/tag/random).
// Legacy post_related_type held either style (img|list|text) or source (category|tag|random).
$raw_related_type = sanitize_key((string) ($settings['post_related_type'] ?? ''));
$raw_related_style = sanitize_key((string) ($settings['post_related_style'] ?? ''));
$raw_related_source = sanitize_key((string) ($settings['post_related_source'] ?? ''));
$related_style = sanitize_key((string) ($settings['post_related_type'] ?? 'img'));
if (!in_array($related_style, ['img', 'list', 'text'], true)) {
$related_style = 'img';
if (in_array($raw_related_style, ['img', 'list', 'text'], true)) {
$related_style = $raw_related_style;
} elseif (in_array($raw_related_type, ['img', 'list', 'text'], true)) {
$related_style = $raw_related_type;
}
$related_source = 'category';
if (in_array($raw_related_source, ['category', 'tag', 'random'], true)) {
$related_source = $raw_related_source;
} elseif (in_array($raw_related_type, ['category', 'tag', 'random'], true)) {
$related_source = $raw_related_type;
}
$related_orderby = sanitize_key((string) ($settings['post_related_orderby'] ?? 'views'));
if (!in_array($related_orderby, ['rand', 'relevance', 'date', 'views', 'modified'], true)) {
if (!in_array($related_orderby, ['modified', 'date', 'views', 'like', 'comment_count', 'favorite', 'rand'], true)) {
$related_orderby = 'views';
}
@@ -317,15 +217,11 @@ class PostDisplay {
'lightbox_full' => !isset($settings['post_lightbox_full']) || !empty($settings['post_lightbox_full']),
'lightbox_down' => !empty($settings['post_lightbox_down']),
'lightbox_play' => !empty($settings['post_lightbox_play']),
'related_enabled' => !isset($settings['post_related_enabled']) || !empty($settings['post_related_enabled']),
'related_count' => max(1, min(20, (int) ($settings['post_related_count'] ?? 6))),
// Display style (Zibll img/list/text) + query source (category/tag/random).
'related_enabled' => !isset($settings['post_related_s']) || !empty($settings['post_related_s']),
'related_count' => max(4, min(12, (int) ($settings['post_related_n'] ?? 6))),
'related_style' => $related_style,
'related_source' => $related_source,
// Legacy alias: older code treated related_type as source.
'related_type' => $related_source,
'related_orderby' => $related_orderby,
'related_title' => sanitize_text_field((string) ($settings['post_related_title'] ?? '相关推荐')),
'related_title' => sanitize_text_field((string) ($settings['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']),
// Canonical + legacy share.
@@ -534,44 +430,23 @@ class PostDisplay {
}
public static function apply_list_per_page($query): void {
if (is_admin() || !$query->is_main_query()) {
return;
}
if (!$query->is_archive() && !$query->is_search() && !$query->is_home()) {
return;
}
$s = Settings::get_all();
$per_page = max(4, min(48, (int) ($s['post_list_per_page'] ?? 12)));
if ($per_page !== 12) {
$query->set('posts_per_page', $per_page);
}
unset($query);
}
public static function get_archive_layout(): string {
$settings = self::get_post_list_style_settings();
return $settings['render_mode'];
return ($settings['list_type'] ?? 'thumb') === 'card' ? 'card' : 'list';
}
public static function get_post_pagination_mode(): string {
$settings = Settings::get_all();
// Zibll: paging_ajax_s '1' = ajax append, '0' = numeric buttons.
if (array_key_exists('paging_ajax_s', $settings)) {
$ajax = self::is_list_paging_ajax($settings);
if (!$ajax) {
if (!self::is_list_paging_ajax($settings)) {
return 'numbered';
}
return self::is_list_paging_ajax_ias($settings) ? 'infinite' : 'load_more';
}
// Temporary bridge for installs still holding the previous schema key only.
$mode = (string) ($settings['post_pagination_mode'] ?? 'numbered');
if ($mode === 'numeric') {
$mode = 'numbered';
}
return in_array($mode, ['numbered', 'load_more', 'infinite'], true) ? $mode : 'numbered';
}
/**
* Whether list pagination uses AJAX append (Zibll paging_ajax_s).
*
@@ -579,12 +454,7 @@ class PostDisplay {
*/
public static function is_list_paging_ajax(?array $settings = null): bool {
$settings = $settings ?? Settings::get_all();
if (!array_key_exists('paging_ajax_s', $settings)) {
// Fallback from previous 3-mode key.
$mode = (string) ($settings['post_pagination_mode'] ?? 'numbered');
return in_array($mode, ['load_more', 'infinite', 'ajax'], true);
}
$raw = $settings['paging_ajax_s'];
$raw = $settings['paging_ajax_s'] ?? '1';
if (is_bool($raw)) {
return $raw;
}
@@ -601,11 +471,7 @@ class PostDisplay {
if (!self::is_list_paging_ajax($settings)) {
return false;
}
if (!array_key_exists('paging_ajax_ias_s', $settings)) {
$mode = (string) ($settings['post_pagination_mode'] ?? '');
return $mode === 'infinite';
}
return !empty($settings['paging_ajax_ias_s']);
return !array_key_exists('paging_ajax_ias_s', $settings) || !empty($settings['paging_ajax_ias_s']);
}
public static function get_comment_pagination_mode(): string {
+4 -7
View File
@@ -646,9 +646,9 @@ class Settings {
/**
* Post-list card style and meta display settings.
* Zibll 9.0 post-list display settings.
*
* @return array{card_style:string,render_mode:string,show_author:bool,show_date:bool,show_views:bool,show_category:bool,show_comments:bool,show_likes:bool,excerpt_length:int}
* @return array<string,mixed>
*/
public static function get_post_list_style_settings(): array {
return PostDisplay::get_post_list_style_settings();
@@ -659,7 +659,7 @@ class Settings {
/**
* Post-list thumbnail policy settings.
*
* @return array{priority:string[],fallback:string,aspect_ratio:string,multi_enabled:bool,multi_count:int,lazy:bool}
* @return array<string,mixed>
*/
public static function get_post_thumb_settings(): array {
return PostDisplay::get_post_thumb_settings();
@@ -949,10 +949,7 @@ class Settings {
/**
* 获取归档布局模式。
*
* 从 post_list.list schema 的 render_mode 字段读取。
* 后台可在 /post-list/list 页面配置。
*
* @return string grid-cms|list|compact
* @return string card|list
*/
public static function get_archive_layout(): string {
return PostDisplay::get_archive_layout();
+28 -27
View File
@@ -31,48 +31,49 @@ final class PostRenderer {
$style = (string) ($instance['style'] ?? 'card');
$mode = in_array($style, ['list', 'card', 'mini'], true) ? $style : 'card';
$config = [
'mode' => $mode,
'context' => 'widget',
'columns' => $mode === 'card' ? 3 : 1,
'show_thumb' => $slug !== 'hot_posts' || !empty($instance['show_thumbnail']),
'show_meta' => $mode !== 'mini',
'show_excerpt' => $mode === 'list',
'show_author' => false,
'show_comments' => true,
'show_likes' => true,
'show_tags' => false,
'show_badges' => true,
'type' => $mode === 'card' ? 'card' : 'list',
'show_number' => $slug === 'hot_posts',
'show_thumb' => $slug !== 'hot_posts' || !array_key_exists('show_thumbnail', $instance) || !empty($instance['show_thumbnail']),
'show_meta' => true,
'number_offset' => ($page - 1) * max(1, (int) ($instance['count'] ?? 8)),
'wrapper_class' => 'aksrt-widget-posts aksrt-widget-posts--' . $slug,
'widget_style' => $mode,
];
if ($slug === 'oneline_posts') {
return self::oneline($query, $config, !empty($instance['autoplay']));
}
if (function_exists('aksrt_render_post_list')) {
return aksrt_render_post_list($query, $config, false);
if ($mode === 'mini') {
return aksrt_render_mini_list($query, $config, false);
}
$html = '<div class="aksrt-widget-posts aksrt-widget-posts--fallback">';
while ($query->have_posts()) {
$query->the_post();
$html .= '<article><a href="' . esc_url(get_permalink()) . '">' . get_the_post_thumbnail(get_the_ID(), 'medium') . '<strong>' . esc_html(get_the_title()) . '</strong></a></article>';
}
wp_reset_postdata();
return $html . '</div>';
return aksrt_render_post_list($config, $query, false);
}
private static function oneline(\WP_Query $query, array $config, bool $autoplay): string {
$slides = '';
$items = '';
$style = in_array(($config['widget_style'] ?? 'card'), ['list', 'card', 'mini'], true)
? (string) $config['widget_style']
: 'card';
while ($query->have_posts()) {
$query->the_post();
$item = function_exists('aksrt_render_post')
? aksrt_render_post(array_merge($config, ['skip_wrapper' => true]))
: '<a href="' . esc_url(get_permalink()) . '">' . esc_html(get_the_title()) . '</a>';
$slides .= '<div class="swiper-slide">' . $item . '</div>';
if ($style === 'mini') {
$data = aksrt_renderer_collect_post_data($config);
$items .= $data ? aksrt_renderer_mini($data, array_merge($config, [
'class' => 'posts-item',
'show_thumb' => true,
'show_meta' => true,
'show_number' => false,
])) : '';
} else {
$items .= aksrt_render_post(array_merge($config, [
'type' => $style === 'card' ? 'card' : 'list',
'is_mult_thumb' => 'disable',
'is_no_thumb' => 'disable',
]));
}
}
wp_reset_postdata();
return '<div class="swiper aksrt-widget-swiper aksrt-widget-oneline" data-aksrt-swiper="scroll" data-autoplay="' . ($autoplay ? '1' : '0') . '"><div class="swiper-wrapper">' . $slides . '</div><button class="swiper-button-prev" type="button" aria-label="' . esc_attr__('上一组', 'aksrt') . '"></button><button class="swiper-button-next" type="button" aria-label="' . esc_attr__('下一组', 'aksrt') . '"></button></div>';
$autoplay_attr = $autoplay ? ' data-autoplay="true"' : '';
return '<div class="widget-main-post mb20 style-' . esc_attr($style) . '"><div class="swiper-container swiper-scroll" data-slideClass="posts-item"' . $autoplay_attr . '><div class="swiper-wrapper">' . $items . '</div><div class="swiper-button-prev"></div><div class="swiper-button-next"></div></div></div>';
}
private static function pagination(array $instance, int $page, int $max_pages): string {
+2 -19
View File
@@ -18,14 +18,12 @@ if (!$term instanceof WP_Term) {
$archive_settings = class_exists('\AKSRT\Core\TermMeta')
? \AKSRT\Core\TermMeta::resolve_archive_settings($term)
: [
'layout_mode' => 'card',
'header_style' => 'simple',
'show_sidebar' => true,
'show_cover' => false,
'show_desc' => !empty($term->description),
'cover_url' => '',
'description' => (string) $term->description,
'card_style' => 'standard',
];
$has_sidebar = function_exists('aksrt_archive_has_sidebar')
@@ -33,8 +31,6 @@ $has_sidebar = function_exists('aksrt_archive_has_sidebar')
: (!empty($archive_settings['show_sidebar']) && is_active_sidebar('tag_sidebar'));
$description = !empty($archive_settings['show_desc']) ? wp_kses_post((string) $archive_settings['description']) : '';
$header_style = (string) $archive_settings['header_style'];
$layout_mode = (string) $archive_settings['layout_mode'];
$card_style = (string) ($archive_settings['card_style'] ?? 'standard');
$filter_settings = class_exists('\AKSRT\Settings\Settings')
? \AKSRT\Settings\Settings::get_post_filter_settings()
: ['enabled' => false, 'sort' => false, 'ajax_enabled' => false];
@@ -45,20 +41,9 @@ $ajax_filter = !empty($filter_settings['ajax_enabled']) && !empty($filter_settin
$sort_state = function_exists('aksrt_archive_get_sort_state') ? aksrt_archive_get_sort_state() : ['current' => 'latest', 'links' => []];
$current_sort = $sort_state['current'];
$sort_links = $sort_state['links'];
$render_overrides = [
'context' => 'tag',
];
// Term meta layout_mode is authoritative only when explicitly set (non-empty preset).
$layout_preset = (string) ($archive_settings['layout_preset'] ?? '');
if ($layout_preset !== '' && in_array((string) $layout_mode, ['list', 'card', 'mini', 'grid-cms'], true)) {
$render_overrides['mode'] = (string) $layout_mode;
if (!empty($card_style)) {
$render_overrides['card_style'] = (string) $card_style;
}
}
$render_config = function_exists('aksrt_archive_get_render_config')
? aksrt_archive_get_render_config($render_overrides)
: array_merge(['context' => 'tag', 'mode' => 'list'], $render_overrides);
? aksrt_archive_get_render_config(['context' => 'tag'])
: ['context' => 'tag'];
$cat_filter_terms = [];
$current_filter_cat = sanitize_text_field((string) get_query_var('aksrt_filter_cat', ''));
@@ -132,8 +117,6 @@ if (function_exists('aksrt_archive_open_shell')) {
'ajax' => $ajax_filter,
'ajax_archive' => 'tag',
'term_id' => (int) $term->term_id,
'layout_mode' => $layout_mode,
'card_style' => $card_style,
'aria_label' => __('标签工具条', 'aksrt'),
'meta_text' => sprintf(_n('%d 篇文章', '%d 篇文章', (int) $term->count, 'aksrt'), (int) $term->count),
'groups' => $filter_groups,
-4
View File
@@ -52,11 +52,7 @@ if (function_exists('aksrt_archive_open_shell')) {
<?php endwhile; ?>
</div>
<?php
if (function_exists('aksrt_render_pagination')) {
aksrt_render_pagination($query);
} else {
echo '<div class="aksrt-pagination">' . paginate_links(['total' => $query->max_num_pages]) . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
wp_reset_postdata();
?>
<?php else : ?>
+1 -21
View File
@@ -8,24 +8,4 @@
defined('ABSPATH') || exit;
$args = isset($args) && is_array($args) ? $args : [];
$forced_layout = (string) get_query_var('aksrt_post_card_layout', '');
$mode = 'card';
if (in_array($forced_layout, ['card', 'list', 'grid-cms'], true)) {
$mode = $forced_layout;
} elseif (!empty($args['mode']) && in_array($args['mode'], ['card', 'list', 'grid-cms', 'mini'], true)) {
$mode = (string) $args['mode'];
} elseif (function_exists('aksrt_get_post_flow') && aksrt_get_post_flow(get_the_ID()) === 'cms') {
$mode = 'grid-cms';
} elseif (class_exists('\AKSRT\Settings\Settings')) {
$archive_layout = \AKSRT\Settings\Settings::get_archive_layout();
$mode = in_array($archive_layout, ['card', 'list', 'grid-cms'], true) ? $archive_layout : 'card';
}
$scene = (string) get_query_var('aksrt_post_card_scene', '');
$context = ($scene === 'home' || is_front_page() || (is_home() && !is_paged())) ? 'home' : 'archive';
echo aksrt_render_post(array_merge($args, [
'mode' => $mode,
'context' => $context,
])); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo aksrt_render_post($args); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
@@ -15,7 +15,7 @@ $layout = (string) ($cfg['layout'] ?? 'card');
$columns = max(1, min(6, (int) ($cfg['columns'] ?? 4)));
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list', 'masonry', 'grid-cms'], true)) {
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
@@ -53,15 +53,13 @@ $more_link = $cat_id > 0 ? get_category_link($cat_id) : '';
<?php endif; ?>
</header>
<div class="post-grid post-grid--<?php echo esc_attr($layout); ?>" style="--cols:<?php echo (int) $columns; ?>;">
<div class="posts-row ajaxpager">
<?php
set_query_var('aksrt_post_card_layout', $layout);
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
}
set_query_var('aksrt_post_card_layout', '');
wp_reset_postdata();
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
</section>
+7 -10
View File
@@ -16,7 +16,7 @@ $count = max(1, min(20, (int) ($cfg['count'] ?? 6)));
$layout = (string) ($cfg['layout'] ?? 'card');
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list', 'masonry', 'grid-cms'], true)) {
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
@@ -49,16 +49,13 @@ $archive_link = get_permalink(get_option('page_for_posts'));
<?php endif; ?>
</header>
<div class="post-grid post-grid--<?php echo esc_attr($layout); ?>">
<div class="posts-row ajaxpager">
<?php
set_query_var('aksrt_post_card_layout', $layout);
set_query_var('aksrt_post_card_scene', 'home');
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
}
set_query_var('aksrt_post_card_layout', '');
set_query_var('aksrt_post_card_scene', '');
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
</section>
+8 -12
View File
@@ -16,11 +16,11 @@ if (!is_array($cfg)) $cfg = [];
$cat_ids = isset($cfg['cat_ids']) && is_array($cfg['cat_ids']) ? array_map('intval', $cfg['cat_ids']) : [];
$cat_ids = array_filter($cat_ids, fn($x) => $x > 0);
$count = max(1, min(20, (int) ($cfg['count'] ?? 8)));
$layout = (string) ($cfg['layout'] ?? 'grid-cms');
$layout = (string) ($cfg['layout'] ?? 'card');
$title = (string) ($cfg['title'] ?? '');
if (!in_array($layout, ['card', 'list', 'masonry', 'grid-cms'], true)) {
$layout = 'grid-cms';
if (!in_array($layout, ['card', 'list'], true)) {
$layout = 'card';
}
if (empty($cat_ids)) {
@@ -86,15 +86,11 @@ $active_id = $tabs[0]['id'];
<?php if ($query->have_posts()) : ?>
<div class="posts-row ajaxpager tab-content">
<?php
set_query_var('aksrt_post_card_layout', $layout === 'list' ? 'list' : 'card');
set_query_var('aksrt_post_card_scene', 'home');
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
}
set_query_var('aksrt_post_card_layout', '');
set_query_var('aksrt_post_card_scene', '');
wp_reset_postdata();
aksrt_render_post_list([
'type' => $layout === 'card' ? 'card' : 'list',
'is_home' => true,
'skip_wrapper' => true,
], $query);
?>
</div>
<?php else : ?>
+11 -4
View File
@@ -43,15 +43,22 @@ if (!$query->have_posts()) {
<header class="resource-related__header">
<h2 class="resource-related__title"><?php esc_html_e('相关资源', 'aksrt'); ?></h2>
</header>
<div class="resource-related__grid post-grid post-grid--card">
<div class="resource-related__grid zib-widget">
<div class="swiper-container swiper-scroll">
<div class="swiper-wrapper">
<?php
set_query_var('aksrt_post_card_layout', 'card');
while ($query->have_posts()) {
$query->the_post();
get_template_part('templates/parts/content', 'post-card');
$post = get_post();
if ($post instanceof WP_Post) {
echo aksrt_render_related_graphic_card($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
set_query_var('aksrt_post_card_layout', '');
wp_reset_postdata();
?>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
</div>
</section>
+9 -20
View File
@@ -20,29 +20,18 @@ extract($args, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_ext
'post_status' => 'publish',
'paged' => $posts_page,
'no_found_rows' => false,
'update_post_meta_cache' => false,
]);
if ($author_posts->have_posts()) :
?>
<div class="profile-list">
<?php while ($author_posts->have_posts()) : $author_posts->the_post(); ?>
<?php
get_template_part('templates/parts/content', 'profile-list-item', [
'action' => [
'type' => 'view',
'url' => get_permalink(),
'label' => __('阅读', 'aksrt'),
],
$author_render_config = aksrt_resolve_post_list_render_config([
'context' => 'author',
]);
aksrt_render_post_list(array_merge($author_render_config, ['wrapper_class' => 'profile-list']), $author_posts);
aksrt_render_pagination($author_posts, [
'query_context' => 'author',
'render_config' => $author_render_config,
'page_var' => 'posts_page',
'current_page' => $posts_page,
]);
?>
<?php endwhile; ?>
</div>
<?php
aksrt_render_profile_pagination((int) $author_posts->max_num_pages, $posts_page, 'posts_page');
wp_reset_postdata();
else :
aksrt_render_profile_empty($svg_empty_post, __('TA 还没有发布文章', 'aksrt'));
endif;
?>
</div>
<?php endif; ?>
+31 -7
View File
@@ -8,16 +8,40 @@ $env:ZIBLL_THEME_PATH='C:\Users\Xing\Desktop\zibll'
docker compose -f tests/visual/docker-compose.zibll.yml up -d
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp core install --url=https://localhost:9443 --title='Zibll Reference' --admin_user=visual_admin --admin_password='AKSRT-Visual-2026!' --admin_email=visual@example.test --skip-email
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp theme activate zibll
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php create
~~~
Create or clean AKSRT fixtures inside its WordPress container:
Run the same theme-neutral article generator inside the AKSRT WordPress
container. Mount `tests/visual/` read-only at `/visual-fixtures` in that stack:
~~~powershell
wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php create
wp eval-file wp-content/themes/AKSRT-theme/tools/wp-cli-visual-fixtures.php cleanup
wp eval-file /visual-fixtures/article-fixtures.php create
wp eval-file /visual-fixtures/article-fixtures.php cleanup
~~~
Use tests/fixtures/widget-golden-v1.json for the 222 component captures and
tests/fixtures/page-visual-matrix-v1.json for page-level captures. Remove
the reference stack with docker compose down -v only after fixture cleanup
and screenshot verification.
The shared generator creates the same authors, category, tag, dates, images,
featured/no-image/multi-image/first-image/external-cover posts, slide and video
metadata, pay/VIP states, sticky state, and interaction counters in both sites.
Fixture-only Zibll and AKSRT metadata are written together so the rendered
business state is identical without adding compatibility code to either theme.
Use `tests/visual/article-visual-matrix-v1.json` for the article-only captures.
Every reference/candidate pair must record the screenshot, DOM, computed styles,
bounding boxes, console messages, and failed network requests. The matrix pins
1440, 1024, 768, 767, 641, 640, and 390 widths across light/dark,
sidebar/no-sidebar, list/card modes, media states, and all pagination states.
The acceptance gate is at most 1 CSS px geometry delta and 1.5% pixel
difference, with no visible mismatch, console error, unexpected failed request,
or critical/serious accessibility issue.
Clean the reference fixture before removing its volumes:
~~~powershell
docker compose -f tests/visual/docker-compose.zibll.yml run --rm wp eval-file /visual-fixtures/article-fixtures.php cleanup
docker compose -f tests/visual/docker-compose.zibll.yml down -v
~~~
The older `tools/wp-cli-visual-fixtures.php`, widget golden file, and broad page
matrix remain dedicated to non-article component regression tests and are not
the truth source for this replacement.
+294
View File
@@ -0,0 +1,294 @@
<?php
/**
* Theme-neutral article fixtures shared by the Zibll and AKSRT visual sites.
*
* Usage: wp eval-file /visual-fixtures/article-fixtures.php create|cleanup
*/
defined('ABSPATH') || exit;
if (!defined('WP_CLI') || !WP_CLI) {
return;
}
$action = sanitize_key((string) ($args[0] ?? ''));
$manifest_key = 'zibll_article_visual_fixture_v1';
$prefix = 'zibll_article_v1_';
if (!in_array($action, ['create', 'cleanup'], true)) {
WP_CLI::error('Expected create or cleanup.');
}
$cleanup = static function (array $manifest) use ($manifest_key): void {
require_once ABSPATH . 'wp-admin/includes/user.php';
foreach (array_reverse((array) ($manifest['posts'] ?? [])) as $post_id) {
unstick_post((int) $post_id);
wp_delete_post((int) $post_id, true);
}
foreach (array_reverse((array) ($manifest['attachments'] ?? [])) as $attachment_id) {
wp_delete_attachment((int) $attachment_id, true);
}
$upload = wp_upload_dir();
$upload_root = wp_normalize_path((string) ($upload['basedir'] ?? ''));
foreach (array_reverse((array) ($manifest['files'] ?? [])) as $file) {
$file = wp_normalize_path((string) $file);
if ($upload_root !== '' && str_starts_with($file, trailingslashit($upload_root)) && is_file($file)) {
unlink($file);
}
}
foreach (array_reverse((array) ($manifest['terms'] ?? [])) as $term) {
wp_delete_term((int) $term['id'], (string) $term['taxonomy']);
}
foreach (array_reverse((array) ($manifest['users'] ?? [])) as $user_id) {
wp_delete_user((int) $user_id);
}
foreach ((array) ($manifest['options'] ?? []) as $name => $state) {
if (!empty($state['exists'])) {
update_option((string) $name, $state['value'], false);
} else {
delete_option((string) $name);
}
}
delete_option($manifest_key);
};
$existing = get_option($manifest_key, []);
if ($action === 'cleanup') {
if (!is_array($existing) || !$existing) {
WP_CLI::success('No article visual fixtures found.');
return;
}
$cleanup($existing);
WP_CLI::success('Article visual fixtures removed.');
return;
}
if (is_array($existing) && $existing) {
WP_CLI::error('Fixtures already exist. Run cleanup first.');
}
$manifest = [
'created_at' => gmdate('c'),
'users' => [],
'terms' => [],
'attachments' => [],
'posts' => [],
'files' => [],
'options' => [],
];
foreach (['aksrt_settings', 'zibll_options'] as $option_name) {
$manifest['options'][$option_name] = [
'exists' => get_option($option_name, null) !== null,
'value' => get_option($option_name),
];
}
update_option($manifest_key, $manifest, false);
try {
$visual_options = [
'list_type' => 'thumb',
'list_show_type' => 'separate',
'list_list_option' => ['style' => 'null', 'img_position' => 'left', 'scale' => 70],
'list_card_option' => ['style' => 'null', 'scale' => 70],
'post_list_author' => true,
'post_list_time' => true,
'post_time_source' => 'modified',
'list_meta_show' => 'like',
'list_badge_show' => ['pay', 'cat', 'tag'],
'list_thumb_slides_s' => true,
'list_thumb_video_s' => true,
'list_thumb_video_mute_s' => true,
'thumb_postfirstimg_s' => true,
'thumb_catimg_s' => true,
'list_thumb_fit' => '',
'paging_ajax_s' => '1',
'paging_ajax_ias_s' => true,
'ias_max' => 3,
'post_related_s' => true,
'post_related_type' => 'img',
'post_related_n' => 6,
'post_related_orderby' => 'views',
'related_title' => '相关推荐',
];
foreach (['aksrt_settings', 'zibll_options'] as $option_name) {
$current = get_option($option_name, []);
update_option($option_name, array_merge(is_array($current) ? $current : [], $visual_options), false);
}
$authors = [
['login' => $prefix . 'author_a', 'name' => '视觉作者甲'],
['login' => $prefix . 'author_b', 'name' => '视觉作者乙'],
];
$author_ids = [];
foreach ($authors as $author) {
$user_id = wp_insert_user([
'user_login' => $author['login'],
'user_pass' => 'AKSRT-Visual-2026!',
'user_email' => $author['login'] . '@example.test',
'display_name' => $author['name'],
'description' => '固定作者简介,用于文章卡片视觉对比。',
'role' => 'author',
]);
if (is_wp_error($user_id)) {
throw new RuntimeException($user_id->get_error_message());
}
$author_ids[] = (int) $user_id;
$manifest['users'][] = (int) $user_id;
}
$category = wp_insert_term('视觉文章', 'category', ['slug' => 'zibll-article-fixture']);
$tag = wp_insert_term('视觉标签', 'post_tag', ['slug' => 'zibll-visual-tag']);
if (is_wp_error($category) || is_wp_error($tag)) {
throw new RuntimeException('Unable to create article fixture terms.');
}
$category_id = (int) $category['term_id'];
$tag_id = (int) $tag['term_id'];
$manifest['terms'][] = ['id' => $category_id, 'taxonomy' => 'category'];
$manifest['terms'][] = ['id' => $tag_id, 'taxonomy' => 'post_tag'];
require_once ABSPATH . 'wp-admin/includes/image.php';
$upload = wp_upload_dir();
if (!empty($upload['error'])) {
throw new RuntimeException((string) $upload['error']);
}
$attachment_ids = [];
$colors = [
[240, 68, 148],
[58, 134, 255],
[92, 191, 147],
[255, 175, 56],
[126, 87, 194],
];
foreach ($colors as $index => $rgb) {
$filename = $prefix . 'image-' . ($index + 1) . '.jpg';
$filepath = trailingslashit($upload['path']) . $filename;
if (function_exists('imagecreatetruecolor')) {
$image = imagecreatetruecolor(1200, 800);
$background = imagecolorallocate($image, $rgb[0], $rgb[1], $rgb[2]);
$foreground = imagecolorallocate($image, 255, 255, 255);
imagefill($image, 0, 0, $background);
imagestring($image, 5, 48, 48, 'Zibll 9.0 Article Fixture ' . ($index + 1), $foreground);
imagejpeg($image, $filepath, 92);
imagedestroy($image);
} else {
$pixel = base64_decode('/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////2wBDAf//////////////////////////////////////////////////////////////////////////////////////wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAX/xAAUEAEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAEf/8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABBQJ//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPwF//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPwF//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAGPwJ//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPyF//9oADAMBAAIAAwAAABAf/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAwEBPxB//8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAgBAgEBPxB//8QAFBABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQABPxB//9k=', true);
file_put_contents($filepath, $pixel);
}
$attachment_id = wp_insert_attachment([
'post_mime_type' => 'image/jpeg',
'post_title' => '视觉图片 ' . ($index + 1),
'post_status' => 'inherit',
], $filepath);
if (is_wp_error($attachment_id)) {
throw new RuntimeException($attachment_id->get_error_message());
}
$attachment_id = (int) $attachment_id;
wp_update_attachment_metadata($attachment_id, wp_generate_attachment_metadata($attachment_id, $filepath));
$attachment_ids[] = $attachment_id;
$manifest['attachments'][] = $attachment_id;
}
$image_urls = array_map(static fn(int $id): string => (string) wp_get_attachment_image_url($id, 'full'), $attachment_ids);
$video_path = trailingslashit($upload['path']) . $prefix . 'fixture-video.mp4';
file_put_contents($video_path, '');
$manifest['files'][] = $video_path;
$video_url = trailingslashit($upload['url']) . basename($video_path);
$term_other = (array) get_term_meta($category_id, 'zib_other_data', true);
$term_other['cover_image'] = $image_urls[4];
update_term_meta($category_id, 'zib_other_data', $term_other);
update_term_meta($category_id, 'cover_image', $image_urls[4]);
update_term_meta($category_id, 'aksrt_category_cover_id', $attachment_ids[4]);
$scenarios = [
['slug' => 'featured', 'title' => '特色图文章:标准列表与卡片基线', 'featured' => 0],
['slug' => 'no-image', 'title' => '无图长标题文章用于检查纯文字模式与两行截断', 'no_image' => true],
['slug' => 'multi', 'title' => '多图文章:四张真实内容图片', 'featured' => 1, 'gallery' => true],
['slug' => 'external', 'title' => '自定义外链封面文章', 'external' => 2],
['slug' => 'slides', 'title' => '幻灯片封面文章', 'featured' => 3, 'slides' => true],
['slug' => 'video', 'title' => '视频悬停封面文章', 'featured' => 4, 'video' => true],
['slug' => 'paid', 'title' => '付费阅读文章价格徽章', 'featured' => 0, 'pay' => 'reading'],
['slug' => 'vip', 'title' => '会员专属文章 VIP 徽章', 'featured' => 1, 'pay' => 'vip'],
['slug' => 'first-image', 'title' => '文章首图作为缩略图', 'first_image' => 3],
];
foreach ($scenarios as $index => $scenario) {
$content = '<p>固定摘要正文,用于 Zibll 与 AKSRT 的文章列表逐像素比较。</p>';
if (!empty($scenario['gallery'])) {
$content .= implode('', array_map(static fn(string $url): string => '<img src="' . esc_url($url) . '" alt="">', array_slice($image_urls, 0, 4)));
} elseif (isset($scenario['first_image'])) {
$content .= '<img src="' . esc_url($image_urls[(int) $scenario['first_image']]) . '" alt="">';
}
$post_id = wp_insert_post([
'post_type' => 'post',
'post_status' => 'publish',
'post_author' => $author_ids[$index % 2],
'post_title' => $scenario['title'],
'post_name' => $prefix . $scenario['slug'],
'post_excerpt' => '固定文章摘要用于检查字号、行高、留白和截断。',
'post_content' => $content,
'post_date' => gmdate('Y-m-d H:i:s', strtotime('2026-07-01 08:00:00') + ($index * DAY_IN_SECONDS)),
'post_date_gmt' => gmdate('Y-m-d H:i:s', strtotime('2026-07-01 00:00:00') + ($index * DAY_IN_SECONDS)),
], true);
if (is_wp_error($post_id)) {
throw new RuntimeException($post_id->get_error_message());
}
$post_id = (int) $post_id;
$manifest['posts'][] = $post_id;
wp_set_post_terms($post_id, [$category_id], 'category');
wp_set_post_terms($post_id, [$tag_id], 'post_tag');
if (isset($scenario['featured'])) {
set_post_thumbnail($post_id, $attachment_ids[(int) $scenario['featured']]);
}
if (!empty($scenario['gallery'])) {
set_post_format($post_id, 'gallery');
}
$post_meta = [
'subtitle' => ' · 固定副标题',
];
$zib_meta = (array) get_post_meta($post_id, 'zib_other_data', true);
$zib_meta['subtitle'] = $post_meta['subtitle'];
if (isset($scenario['external'])) {
$post_meta['thumbnail_url'] = $image_urls[(int) $scenario['external']];
$zib_meta['thumbnail_url'] = $post_meta['thumbnail_url'];
}
if (!empty($scenario['slides'])) {
$post_meta['featured_slide'] = implode(',', array_slice($attachment_ids, 0, 4));
$zib_meta['featured_slide'] = $post_meta['featured_slide'];
}
if (!empty($scenario['video'])) {
$post_meta['featured_video'] = $video_url;
$zib_meta['featured_video'] = $video_url;
}
$zib_meta['views'] = 1200 + ($index * 137);
$zib_meta['like'] = 30 + ($index * 7);
update_post_meta($post_id, 'zib_other_data', $zib_meta);
update_post_meta($post_id, 'aksrt_post_meta', $post_meta);
update_post_meta($post_id, 'aksrt_post_views', $zib_meta['views']);
update_post_meta($post_id, '_aksrt_like_count', $zib_meta['like']);
update_post_meta($post_id, '_aksrt_favorite_count', 10 + $index);
$pay_type = (string) ($scenario['pay'] ?? 'no');
$pay_limit = $pay_type === 'vip' ? 2 : 0;
$pay_price = $pay_type === 'reading' ? 19.9 : 0;
update_post_meta($post_id, '_aksrt_pay', [
'pay_type' => $pay_type,
'pay_modo' => '0',
'price' => $pay_price,
'pay_limit' => $pay_limit,
]);
update_post_meta($post_id, 'posts_zibpay', [
'pay_type' => $pay_type,
'pay_modo' => '0',
'pay_price' => $pay_price,
'pay_limit' => $pay_limit,
]);
}
if ($manifest['posts']) {
stick_post((int) $manifest['posts'][0]);
}
update_option($manifest_key, $manifest, false);
WP_CLI::success('Created identical Zibll/AKSRT article fixtures.');
} catch (Throwable $error) {
$cleanup($manifest);
WP_CLI::error('Fixture creation rolled back: ' . $error->getMessage());
}
@@ -0,0 +1,45 @@
{
"schema": "aksrt.zibll-article-visual-matrix.v1",
"reference": "https://localhost:9443",
"candidate": "https://localhost",
"fixture": "/visual-fixtures/article-fixtures.php",
"viewports": [
{ "id": "desktop-wide", "width": 1440, "height": 1100 },
{ "id": "desktop", "width": 1024, "height": 1000 },
{ "id": "tablet", "width": 768, "height": 1000 },
{ "id": "breakpoint-767", "width": 767, "height": 1000 },
{ "id": "breakpoint-641", "width": 641, "height": 960 },
{ "id": "breakpoint-640", "width": 640, "height": 960 },
{ "id": "mobile", "width": 390, "height": 844 }
],
"colorSchemes": ["light", "dark"],
"layouts": ["sidebar", "no-sidebar"],
"listModes": ["thumb", "text", "thumb_if_has", "card"],
"styles": ["default", "style2", "style3"],
"routes": [
{ "id": "home", "path": "/", "states": ["default", "hover", "loading", "error"] },
{ "id": "archive", "path": "/?post_type=post", "states": ["default", "numbered", "load-more", "infinite", "no-more"] },
{ "id": "category", "path": "/category/zibll-article-fixture/", "states": ["default", "empty"] },
{ "id": "tag", "path": "/tag/zibll-visual-tag/", "states": ["default", "empty"] },
{ "id": "search", "path": "/?s=视觉", "states": ["default", "empty"] },
{ "id": "author-a", "path": "/author/zibll_article_v1_author_a/", "states": ["default", "numbered", "load-more"] },
{ "id": "single-related", "path": "/zibll_article_v1_featured/", "states": ["img", "list", "text"] }
],
"postScenarios": ["featured", "no-image", "multi", "external", "slides", "video", "paid", "vip", "first-image"],
"capture": {
"selector": ".posts-row, .profile-list, .relates",
"artifacts": ["screenshot", "dom", "computed-style", "bounding-box", "console", "network"],
"computedProperties": [
"display", "position", "width", "height", "margin", "padding", "gap", "font-size", "line-height",
"border-radius", "box-shadow", "background-color", "color", "object-fit"
]
},
"acceptance": {
"dimensionDeltaPxMax": 1,
"pixelDifferencePercentMax": 1.5,
"consoleErrorsMax": 0,
"unexpectedNetworkFailuresMax": 0,
"a11yCriticalMax": 0,
"a11ySeriousMax": 0
}
}
+8
View File
@@ -32,6 +32,10 @@ services:
source: ${ZIBLL_THEME_PATH}
target: /var/www/html/wp-content/themes/zibll
read_only: true
- type: bind
source: .
target: /visual-fixtures
read_only: true
caddy:
image: caddy:2.8-alpine
@@ -62,6 +66,10 @@ services:
source: ${ZIBLL_THEME_PATH}
target: /var/www/html/wp-content/themes/zibll
read_only: true
- type: bind
source: .
target: /visual-fixtures
read_only: true
entrypoint: ["wp"]
volumes:
@@ -0,0 +1,39 @@
{
"schema": "aksrt.zibll-article-baseline.v1",
"referencePath": "C:\\Users\\Xing\\Desktop\\zibll",
"theme": "Zibll",
"version": "9.0",
"capturedAt": "2026-08-18",
"files": {
"style.css": {
"bytes": 927,
"sha256": "4e9e319d92cea3862aefd520d5519b81ea22f9147b203b77eafedd0447148aea"
},
"css/main.css": {
"bytes": 292331,
"sha256": "87ff1aeaf78369934c875422ab3ebc13ad65f30de78f6caabf091c780a27a158"
},
"inc/functions/zib-posts-list.php": {
"bytes": 34255,
"sha256": "ced813182c0b0fc5847ca5c04a57fa4379581ace13a6d40dfb7dfabb55e13ae4"
},
"inc/options/admin-options.php": {
"bytes": 700302,
"sha256": "3d2cf89bc01a1ec98297d16c14023a820170568e716daedf421fd0386b38486b"
},
"js/main.min.js": {
"bytes": 74446,
"sha256": "0fdd02246a5b68480d11ced1dfd2bfa19788fef16ba069eb021fcfb5cfaf60f9"
},
"js/libs/grade.min.js": {
"bytes": 4833,
"sha256": "8d74bf6ab34f4c44203766cc639818c4daf0a7c1978ddefb4ebfde9daaa16040"
}
},
"excluded": [
"topics taxonomy",
"single article body",
"comments",
"header and footer"
]
}
+241 -62
View File
@@ -7,79 +7,258 @@ const test = require('node:test');
const root = path.resolve(__dirname, '..', '..');
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
const join = (files) => files.map(read).join('\n');
test('post list DOM follows the Zibll flex and multi-image contract', () => {
const articleFiles = [
'src/Settings/PostDisplay.php',
'src/Admin/Schema/PostList/SchemaPostListList.php',
'src/Admin/Schema/SchemaPostList.php',
'assets/admin/src/schema/sections/post-list/list.js',
'assets/admin/src/schema/sections/post-list/thumb.js',
'assets/admin/src/schema/sections/post-list/page.js',
'inc/helpers/posts-renderer/config.php',
'inc/helpers/posts-renderer/post-data.php',
'inc/helpers/posts-renderer/markup.php',
'inc/helpers/aksrt-archive.php',
'inc/helpers/aksrt-single-render.php',
];
test('the article stream exposes only the new typed renderer contract', () => {
const markup = read('inc/helpers/posts-renderer/markup.php');
const consumers = join([
'index.php',
'category.php',
'tag.php',
'search.php',
'archive-resources.php',
'src/Admin/Admin.php',
'src/Admin/GlobalController.php',
'src/Core/Shortcodes.php',
'src/WidgetSystem/PostRenderer.php',
'inc/helpers/homepage/content.php',
'templates/parts/user-home/panels-core.php',
]);
assert.match(markup, /function aksrt_render_post_list\(array \$args = \[\], \?WP_Query \$query = null, bool \$echo = true\): string/);
assert.doesNotMatch(consumers, /function_exists\(['"]aksrt_render_post_list/);
assert.doesNotMatch(consumers, /aksrt_render_post_list\(\s*\$query\s*,/);
assert.doesNotMatch(consumers, /content-post-card|aksrt_homepage_render_post_card/);
});
test('settings use Zibll list modes, grouped options, and destructive related keys', () => {
const source = join(articleFiles);
const listSchema = read('assets/admin/src/schema/sections/post-list/list.js');
const pageSchema = read('assets/admin/src/schema/sections/post-list/page.js');
const legacyKeys = [
'post_card_style',
'list_list_style',
'list_card_style',
'grid-cms',
'large_thumb',
'multi_thumb',
'layout_override',
'homepage_card_style',
'homepage_multi_thumb',
'homepage_default_view',
'post_related_enabled',
'post_related_count',
'post_related_style',
'post_related_source',
'post_related_title',
];
assert.match(listSchema, /list_type: 'thumb'/);
for (const value of ['text', 'thumb', 'thumb_if_has', 'card']) {
assert.match(listSchema, new RegExp(`value: '${value}'`));
}
assert.match(listSchema, /list_list_option: \{ style: 'null', img_position: 'left', scale: 70 \}/);
assert.match(listSchema, /list_card_option: \{ style: 'null', scale: 70 \}/);
assert.match(pageSchema, /post_related_s: true/);
assert.match(pageSchema, /post_related_type: 'img'/);
assert.match(pageSchema, /related_title: '相关推荐'/);
assert.match(pageSchema, /post_related_n: 6/);
assert.match(pageSchema, /post_related_orderby: 'views'/);
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/);
});
test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', () => {
const config = read('inc/helpers/posts-renderer/config.php');
const data = read('inc/helpers/posts-renderer/post-data.php');
const forced = config.indexOf("$is_forced = in_array($forced_type, ['list', 'card'], true)");
const globalCard = config.indexOf("$list_type === 'card'");
const contextCard = config.indexOf("$list['list_card_tag']");
const categoryCard = config.indexOf("$list['list_card_cat']");
assert.ok(forced >= 0 && forced < globalCard && globalCard < contextCard && contextCard < categoryCard);
assert.match(config, /\$is_card = \$forced_type === 'card' \|\| \(!\$is_forced/);
const hasReal = config.indexOf("$has_real = $primary !== '';");
const spareCall = config.lastIndexOf('aksrt_renderer_spare_image($thumb)');
const lazy = config.lastIndexOf('aksrt_get_lazy_thumb');
assert.ok(hasReal >= 0 && hasReal < spareCall && spareCall < lazy);
assert.match(data, /'thumb_if_has' && empty\(\$images\['has_real'\]\)/);
assert.match(data, /count\(\$gallery\) > 2/);
assert.match(data, /has_post_format\(\['image', 'gallery'\]/);
});
test('image resolution counts only content images and applies the configured WordPress size', () => {
const config = read('inc/helpers/posts-renderer/config.php');
const data = read('inc/helpers/posts-renderer/post-data.php');
const thumbSchema = read('assets/admin/src/schema/sections/post-list/thumb.js');
const phpSchema = read('src/Admin/Schema/SchemaPostList.php');
assert.match(config, /function aksrt_renderer_content_images\(int \$post_id, string \$size\)/);
assert.match(config, /strip_shortcodes\(\(string\) get_post_field\('post_content'/);
assert.match(config, /\$slides = aksrt_renderer_curated_images\(\$post_id, \$size\);\s*\$gallery = aksrt_renderer_content_images\(\$post_id, \$size\);/);
assert.match(config, /get_post_thumbnail_id\(\$post_id\).*aksrt_renderer_sized_image\(\(string\) \$thumbnail_id, \$size\)/s);
assert.match(config, /aksrt_renderer_category_image\(\$post_id, \$size\)/);
assert.doesNotMatch(config, /aksrt_get_post_gallery_images|cover_images|featured_video_episode|_aksrt_featured_video|_video_url|_aksrt_thumbnail_url|_thumbnail_url/);
assert.doesNotMatch(config, /\['aksrt_category_cover_id',/);
assert.match(data, /'gallery_count'\s*=>\s*count\(\$content_images\)/);
assert.match(data, /\(int\) \$data\['gallery_count'\] - 4/);
assert.match(thumbSchema, /spareThumbnailDefault = \[\{ img: `\$\{themeRoot\}\/assets\/img\/thumbnail\.svg` \}\]/);
assert.match(phpSchema, /'spare_thumbnail'.*'default' => \[\['img' => get_template_directory_uri\(\) \. '\/assets\/img\/thumbnail\.svg'\]\]/);
});
test('list, card, media, badge and Mini DOM follow dedicated Zibll structures', () => {
const markup = read('inc/helpers/posts-renderer/markup.php');
const data = read('inc/helpers/posts-renderer/post-data.php');
const cardStart = markup.indexOf('function aksrt_renderer_card');
const miniStart = markup.indexOf('function aksrt_renderer_mini');
const card = markup.slice(cardStart, miniStart);
const card = markup.slice(markup.indexOf('function aksrt_renderer_card'), markup.indexOf('function aksrt_renderer_mini'));
const mini = markup.slice(markup.indexOf('function aksrt_renderer_mini'), markup.indexOf('function aksrt_render_post'));
assert.match(markup, /\['posts-item', 'list', 'ajax-item', 'flex'/);
assert.match(markup, /if \(!empty\(\$data\['is_mult_thumb'\]\)\)[\s\S]*?\$thumb = '';/);
assert.doesNotMatch(card, /item-excerpt|renderer_excerpt_markup/);
assert.match(data, /&& \$cfg\['mode'\] === 'list'/);
assert.match(data, /<a class=\"thumb-items contain\"/);
assert.match(markup, /\['posts-item', 'list', 'ajax-item'\]/);
assert.match(markup, /\$classes\[\] = .*'flex xx'.*'flex'/s);
assert.match(markup, /<posts class=/);
assert.match(markup, /<div class="post-graphic/);
assert.match(markup, /<div class="item-body flex xx flex1 jsb/);
assert.doesNotMatch(card, /item-excerpt|renderer_excerpt_markup|thumb-items/);
assert.match(data, /<div class="item-thumbnail/);
assert.match(data, /relative zib-slider.*new-swiper item-thumbnail/s);
assert.match(data, /swiper-slide.*gradient-bg.*data-opacity="0\.2"/s);
assert.match(data, /\$fit = \$is_contain && !\$has_slides \? ' contain gradient-bg' : ''/);
assert.match(data, /video-thumb-box.*data-post-thumb-video/s);
assert.match(data, /class="thumb-items/);
assert.match(data, /empty\(\$cfg\['is_category'\]\).*in_array\('cat'/s);
assert.match(data, /empty\(\$cfg\['is_tag'\]\).*in_array\('tag'/s);
assert.doesNotMatch(data, /meta-mobile-show/);
assert.match(mini, /<div class="mr10"><div class="item-thumbnail"><a/);
assert.match(mini, /posts-mini-con flex xx flex1 jsb/);
assert.match(mini, /\['c-red', 'c-yellow', 'c-purple', 'c-blue', 'c-green'\]/);
assert.match(mini, /aksrt_renderer_pay_badge\(\$data, true\)/);
assert.match(data, /meta-pay badg badg-sm mr6/);
assert.doesNotMatch(mini, /posts-mini-row/);
assert.doesNotMatch(markup, /--posts-(?:list|card)-scale|<posts[^>]+style=/);
assert.match(data, /\['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red'\]/);
assert.match(data, /查看更多分类文章/);
assert.match(data, /usort\(\$tags,.*->count.*->count/s);
assert.match(data, /查看此标签更多文章/);
const commentMeta = data.indexOf("aksrt_renderer_svg_icon('comment')");
const viewMeta = data.indexOf("aksrt_renderer_svg_icon('view')", commentMeta);
const likeMeta = data.indexOf("aksrt_renderer_svg_icon('like')", viewMeta);
assert.ok(commentMeta >= 0 && commentMeta < viewMeta && viewMeta < likeMeta);
assert.match(data, /comments_open.*close_comments/s);
assert.match(data, /show_like.*post_type.*=== 'post'/s);
assert.match(data, /<use xlink:href="#icon-/);
});
test('homepage card mode keeps the Zibll inline-block three-column flow', () => {
const css = read('assets/css/frontend/homepage-fixed/base-2.css');
const responsive = read('assets/css/frontend/homepage-fixed/ajax-tab-error-state.css');
const homepageScript = read('assets/js/homepage-fixed.js');
const scripts = read('src/Core/ScriptsPart1b.php');
const cardModeStart = css.indexOf('.aksrt-homepage__posts[data-view="card"] {');
const cardItemStart = css.indexOf('.aksrt-homepage__posts[data-view="card"] > .posts-item {');
const cardMode = css.slice(cardModeStart, cardItemStart);
const cardItem = css.slice(cardItemStart, css.indexOf('\n}', cardItemStart) + 2);
const visibilityStart = css.indexOf('VISIBILITY LOCK');
const visibilityEnd = css.indexOf('/* Minimal flex utilities', visibilityStart);
const visibility = css.slice(visibilityStart, visibilityEnd);
test('post-card CSS owns the exact desktop and 767/640 responsive matrix', () => {
const css = read('assets/css/frontend/post-card.css');
const mobile767 = css.slice(css.indexOf('@media (max-width: 767px)'), css.indexOf('@media (max-width: 640px)'));
const mobile640 = css.slice(css.indexOf('@media (max-width: 640px)'));
assert.match(cardMode, /display:\s*block/);
assert.match(cardMode, /font-size:\s*0/);
assert.match(cardItem, /width:\s*calc\(33\.333% - 16px\)/);
assert.match(cardItem, /display:\s*inline-block/);
assert.doesNotMatch(visibility, /\.posts-item[\s\S]*?display:\s*block\s*!important/);
assert.match(scripts, /\$homepage_style_parts\s*=\s*\[/);
assert.match(scripts, /filemtime\(\$part_path\)/);
assert.doesNotMatch(scripts, /wp_enqueue_style\(\s*'aksrt-homepage-fixed',\s*\$base_uri\s*\.\s*'\/assets\/css\/frontend\/homepage-fixed\.css'/);
const tabletStart = responsive.indexOf('@media (max-width: 991.98px)');
const mobileStart = responsive.indexOf('@media (max-width: 767px)');
const smallMobileStart = responsive.indexOf('@media (max-width: 640px)');
const tablet = responsive.slice(tabletStart, mobileStart);
const mobile = responsive.slice(mobileStart, smallMobileStart);
const smallMobile = responsive.slice(smallMobileStart);
assert.doesNotMatch(tablet, /data-view="card"/);
assert.match(mobile, /data-view="card"[\s\S]*?width:\s*calc\(50% - 10px\)/);
assert.match(mobile, /data-view="card"[\s\S]*?margin:\s*-5px/);
assert.doesNotMatch(smallMobile, /data-view="card"[\s\S]*?width:\s*100%/);
assert.match(homepageScript, /var initialMode = getViewMode\(\)/);
assert.match(homepageScript, /var renderedMode = activePanel \? activePanel\.getAttribute\('data-view'\)/);
assert.match(homepageScript, /renderedMode !== initialMode[\s\S]*?requestHomepagePanel\(activePanel/);
assert.match(css, /\.posts-row\s*\{[^}]*margin-right:\s*-8px;[^}]*margin-left:\s*-8px;/s);
assert.match(css, /\.posts-item\.card\s*\{[^}]*width:\s*calc\(33\.333% - 16px\)/s);
assert.match(css, /\.fluid-widget-wrap \.posts-item\.card,[\s\S]*?\.site-layout-1 \.posts-item\.card\s*\{[^}]*width:\s*calc\(25% - 16px\)/);
assert.match(css, /\.site-layout-1 \.posts-item:not\(\.card\)\s*\{[^}]*width:\s*calc\(50% - 16px\)/s);
assert.match(mobile767, /\.posts-item\.card\s*\{[^}]*width:\s*calc\(50% - 10px\)/s);
assert.doesNotMatch(mobile767, /\.posts-mini\s*\{[^}]*width:\s*100%/s);
assert.match(mobile640, /\.posts-mini\s*\{[^}]*width:\s*100%/s);
assert.match(mobile640, /\.posts-item:not\(\.no-thumb\) \.meta-right item\s*\{[^}]*display:\s*none/s);
assert.doesNotMatch(css, /\.aksrt-|post-card-cms|grid-cms/);
});
test('site shell has one float owner and fluid zones sit outside main', () => {
const layout = read('assets/css/frontend/layout.css');
const archive = read('inc/helpers/aksrt-archive.php');
const shell = layout.slice(layout.indexOf('Site shell'), layout.indexOf('页面标题区'));
test('related posts, author page and widgets use their dedicated renderers', () => {
const related = read('inc/helpers/aksrt-single-render.php');
const relatedCard = read('inc/helpers/aksrt-single-components.php');
const resourceRelated = read('templates/parts/resource-related.php');
const author = read('templates/parts/user-home/panels-core.php');
const authorPosts = author.slice(author.indexOf("in_array('posts'"), author.indexOf("in_array('comments'"));
const widgets = read('src/WidgetSystem/PostRenderer.php');
assert.doesNotMatch(shell, /display:\s*grid/);
assert.match(shell, /width:\s*var\(--sidebar-width, 311px\)/);
assert.match(shell, /calc\(var\(--sidebar-width, 311px\) \+ 15px\)/);
assert.ok(archive.indexOf("$args['top_fluid']") < archive.indexOf('<main id="main-content"'));
assert.ok(archive.lastIndexOf("$args['bottom_fluid']") > archive.indexOf('</main>'));
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, /<li><a class="icon-circle"/);
assert.doesNotMatch(related, /topics|post_related_source/);
assert.match(relatedCard, /aksrt_renderer_resolve_images/);
assert.match(resourceRelated, /swiper-container swiper-scroll/);
assert.match(resourceRelated, /aksrt_render_related_graphic_card/);
assert.doesNotMatch(resourceRelated, /aksrt_render_post_list/);
assert.match(authorPosts, /aksrt_render_post_list/);
assert.match(authorPosts, /aksrt_render_pagination/);
assert.match(authorPosts, /'page_var'\s*=>\s*'posts_page'/);
assert.doesNotMatch(authorPosts, /aksrt_render_profile_pagination/);
assert.match(widgets, /aksrt_render_mini_list/);
assert.match(widgets, /swiper-container swiper-scroll/);
});
test('widget shell uses Zibll wrappers without overriding post link colors', () => {
const renderer = read('src/WidgetSystem/FrontendRenderer.php');
const css = read('assets/widgets/frontend.css');
test('AJAX pagination can recover, caps auto loading and rehydrates appended media', () => {
const php = read('inc/helpers/template-tags/pagination-permissions.php');
const admin = read('src/Admin/Admin.php');
const js = read('assets/js/core/pagination.js');
const media = read('assets/js/core/misc.js');
assert.match(renderer, /'zib-widget-wrap'/);
assert.match(renderer, /\$classes\[\] = 'zib-widget'/);
assert.match(renderer, /aksrt-widget__inner container/);
assert.doesNotMatch(css, /\.aksrt-widget a\s*\{[^}]*color:\s*inherit/);
assert.match(css, /\.aksrt-widget\.zib-widget\s*\{/);
assert.match(php, /class="text-center theme-pagination ajax-pag"/);
assert.match(php, /class="next-page ajax-next/);
assert.match(php, /lazyload-action="ias"/);
assert.match(php, /class="post_ajax_loader"/);
assert.match(php, /data-page-var/);
assert.match(admin, /'author', 'author_name'/);
assert.match(admin, /'category__in'.*'tag__in'/s);
assert.match(admin, /\$query_vars\['paged'\] = \$page/);
assert.match(js, /failed = false;\s*loadNextPage\(fromAuto\)/);
assert.match(js, /successfulAutoLoads >= iasMax/);
assert.match(js, /AksrtFrontend\.rehydrate\(target, "ajax"\)/);
assert.match(js, /classList\.add\("lazyload"\)/);
assert.match(media, /thumb-dplayer-playing/);
assert.match(media, /data-post-thumb-slider/);
assert.match(media, /data-post-thumb-video/);
assert.match(media, /window\.Grade\(container/);
assert.match(media, /linear-gradient\(135deg/);
assert.match(media, /aksrtGradientState/);
assert.match(read('src/Core/ScriptsPart1c.php'), /'aksrt-grade'/);
});
test('the pinned Zibll baseline records all source checksums', () => {
const baseline = JSON.parse(read('tests/visual/zibll-9.0-article-baseline.json'));
assert.equal(baseline.theme, 'Zibll');
assert.equal(baseline.version, '9.0');
const files = Object.values(baseline.files);
assert.ok(files.length >= 6);
for (const file of files) {
assert.match(file.sha256, /^[a-f0-9]{64}$/);
}
});
test('article visual matrix pins every breakpoint, state and strict threshold', () => {
const matrix = JSON.parse(read('tests/visual/article-visual-matrix-v1.json'));
assert.deepEqual(matrix.viewports.map((item) => item.width), [1440, 1024, 768, 767, 641, 640, 390]);
assert.deepEqual(matrix.colorSchemes, ['light', 'dark']);
assert.deepEqual(matrix.layouts, ['sidebar', 'no-sidebar']);
assert.deepEqual(matrix.listModes, ['thumb', 'text', 'thumb_if_has', 'card']);
assert.deepEqual(matrix.styles, ['default', 'style2', 'style3']);
assert.ok(matrix.routes.some((route) => route.id === 'author-a'));
assert.ok(matrix.routes.some((route) => route.states.includes('infinite')));
assert.ok(matrix.postScenarios.includes('no-image'));
assert.ok(matrix.postScenarios.includes('multi'));
assert.ok(matrix.postScenarios.includes('video'));
assert.ok(matrix.postScenarios.includes('slides'));
assert.equal(matrix.acceptance.dimensionDeltaPxMax, 1);
assert.equal(matrix.acceptance.pixelDifferencePercentMax, 1.5);
});