diff --git a/archive-resources.php b/archive-resources.php index facd0a68..fc37256f 100644 --- a/archive-resources.php +++ b/archive-resources.php @@ -160,22 +160,16 @@ $has_filter = $current_orderby !== 'date' || $current_price !== 'all' || $price_ 'archive', - 'overrides' => ['mode' => 'card'], - ]) - : ['context' => 'archive', 'mode' => 'card']; + $render_config = aksrt_resolve_post_list_render_config([ + 'context' => 'archive', + '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_pagination(null, [ - 'query_context' => 'archive', - 'render_config' => $render_config, - ]); - } + aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list'])); + aksrt_render_pagination(null, [ + 'query_context' => 'archive', + 'render_config' => $render_config, + ]); ?>
diff --git a/assets/admin/src/components/form/AksField.vue b/assets/admin/src/components/form/AksField.vue index b395a720..cdacf063 100644 --- a/assets/admin/src/components/form/AksField.vue +++ b/assets/admin/src/components/form/AksField.vue @@ -359,6 +359,17 @@
+ +
+ +
+
{ 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; diff --git a/assets/admin/src/pages/content/HomepageSettings.vue b/assets/admin/src/pages/content/HomepageSettings.vue index b1a369f1..6f924e09 100644 --- a/assets/admin/src/pages/content/HomepageSettings.vue +++ b/assets/admin/src/pages/content/HomepageSettings.vue @@ -140,7 +140,7 @@ - 首页文章列表/卡片模式、样式变体与缩略图比例已统一到「文章&列表 → 文章列表」设置(对齐 Zibll 全局 list_type / list_card_home)。此处不再单独维护 homepage_card_style / 默认视图。 + 首页文章列表/卡片模式、样式变体与缩略图比例统一由「文章&列表 → 文章列表」中的 Zibll 全局字段控制。 @@ -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 diff --git a/assets/admin/src/schema/sections/page-display/index.js b/assets/admin/src/schema/sections/page-display/index.js index d010c494..36a21bad 100644 --- a/assets/admin/src/schema/sections/page-display/index.js +++ b/assets/admin/src/schema/sections/page-display/index.js @@ -313,7 +313,7 @@ export const pageDisplaySchemas = { user_home_tabs_mobile_hide: '', user_home_sidebar: false, user_home_cover: true, - user_home_route_identifier: 'nicename', + user_home_route_identifier: 'nicename', user_home_follow: true, user_home_posts_per_page: 10, user_home_avatar: true, @@ -333,7 +333,7 @@ export const pageDisplaySchemas = { icon: 'fas fa-th-large', fields: [ { key: 'user_home_cover', label: '封面区域', type: 'switch', default: true, help: '用户主页顶部封面图' }, - { key: 'user_home_route_identifier', label: '主页 URL 标识', type: 'select', default: 'nicename', options: [{ label: '用户名 URL slug', value: 'nicename' }, { label: '数字用户 ID', value: 'uid' }] }, + { key: 'user_home_route_identifier', label: '主页 URL 标识', type: 'select', default: 'nicename', options: [{ label: '用户名 URL slug', value: 'nicename' }, { label: '数字用户 ID', value: 'uid' }] }, { key: 'user_home_sidebar', label: '侧边栏(已弃用)', type: 'switch', default: false, help: '公开主页为通栏布局(对齐 Zibll author);运行时强制关闭。交易服务请去用户中心。' }, { key: 'user_home_follow', label: '关注按钮', type: 'switch', default: true }, { key: 'user_home_posts_per_page', label: '每页文章数', type: 'number', default: 10, min: 5, max: 50 }, @@ -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 }, ], diff --git a/assets/admin/src/schema/sections/post-list/list.js b/assets/admin/src/schema/sections/post-list/list.js index 4bcc6a73..14c41a67 100644 --- a/assets/admin/src/schema/sections/post-list/list.js +++ b/assets/admin/src/schema/sections/post-list/list.js @@ -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: '加载更多', + 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: 'list_badge_show', - label: '列表徽章', - type: 'checkbox', - default: ['pay', 'tag', 'cat'], - options: [ - { label: '付费', value: 'pay' }, - { label: '分类', value: 'cat' }, - { label: '标签', value: 'tag' }, - ], - }, + { 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: '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: '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' }, + { 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', 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: 'card', - title: '卡片与布局', - icon: 'fas fa-th-large', - fields: [ - { - key: 'post_card_style', - label: '默认列表模式', - type: 'select', - default: 'thumb', - options: [ - { label: '列表图文模式', value: 'thumb' }, - { label: '列表文字模式', value: 'text' }, - { 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' }, - ], - }, - { - 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: '加载更多' }, + { key: 'ajax_nomore', label: '列表全部加载完毕文案', type: 'textarea', default: '没有更多内容了' }, ], }, ], diff --git a/assets/admin/src/schema/sections/post-list/page.js b/assets/admin/src/schema/sections/post-list/page.js index 14a6bdd9..0abac034 100644 --- a/assets/admin/src/schema/sections/post-list/page.js +++ b/assets/admin/src/schema/sections/post-list/page.js @@ -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 } }, ], }, { diff --git a/assets/admin/src/schema/sections/post-list/thumb.js b/assets/admin/src/schema/sections/post-list/thumb.js index 8cc16e4c..9d54cacb 100644 --- a/assets/admin/src/schema/sections/post-list/thumb.js +++ b/assets/admin/src/schema/sections/post-list/thumb.js @@ -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: '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: '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: '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_postfirstimg_size', label: '缩略图大小', type: 'radio', default: 'medium', options: [ + { label: '小尺寸', value: 'thumbnail' }, + { label: '中尺寸', value: 'medium' }, + { label: '大尺寸', value: 'large' }, + { label: '原图', value: 'full' }, + ] }, + { key: 'spare_thumbnail', label: '备用缩略图', type: 'repeater', default: spareThumbnailDefault, addText: '添加备用缩略图', fields: [ + { key: 'img', label: '图片', type: 'media', default: '' }, + ], help: '添加多张图片时自动随机获取' }, ], }, ], diff --git a/assets/admin/src/utils/home-module-schema.js b/assets/admin/src/utils/home-module-schema.js index ddda5607..38bdb800 100644 --- a/assets/admin/src/utils/home-module-schema.js +++ b/assets/admin/src/utils/home-module-schema.js @@ -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 } }, ], }, diff --git a/assets/css/core/tokens.css b/assets/css/core/tokens.css index ab73d485..607bb73f 100644 --- a/assets/css/core/tokens.css +++ b/assets/css/core/tokens.css @@ -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; } diff --git a/assets/css/frontend/homepage-fixed/ajax-tab-error-state.css b/assets/css/frontend/homepage-fixed/ajax-tab-error-state.css index 8065e566..ae5b6732 100644 --- a/assets/css/frontend/homepage-fixed/ajax-tab-error-state.css +++ b/assets/css/frontend/homepage-fixed/ajax-tab-error-state.css @@ -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; diff --git a/assets/css/frontend/homepage-fixed/base-2.css b/assets/css/frontend/homepage-fixed/base-2.css index cc181e3f..2adccb66 100644 --- a/assets/css/frontend/homepage-fixed/base-2.css +++ b/assets/css/frontend/homepage-fixed/base-2.css @@ -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; +} diff --git a/assets/css/frontend/homepage-fixed/base-3.css b/assets/css/frontend/homepage-fixed/base-3.css index c6a47d29..ea43f0c8 100644 --- a/assets/css/frontend/homepage-fixed/base-3.css +++ b/assets/css/frontend/homepage-fixed/base-3.css @@ -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 ================================================================ */ diff --git a/assets/css/frontend/nav/zib-mobile-navbar.css b/assets/css/frontend/nav/zib-mobile-navbar.css index 5142e472..72364903 100644 --- a/assets/css/frontend/nav/zib-mobile-navbar.css +++ b/assets/css/frontend/nav/zib-mobile-navbar.css @@ -231,7 +231,4 @@ padding: 0.7em; } - .posts-item .item-thumbnail { - width: 138px; - } } diff --git a/assets/css/frontend/post-card-cms.css b/assets/css/frontend/post-card-cms.css deleted file mode 100644 index 491fb686..00000000 --- a/assets/css/frontend/post-card-cms.css +++ /dev/null @@ -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; - } -} diff --git a/assets/css/frontend/post-card.css b/assets/css/frontend/post-card.css index 01260b4a..87a21329 100644 --- a/assets/css/frontend/post-card.css +++ b/assets/css/frontend/post-card.css @@ -1,654 +1,603 @@ /** - * AKSRT Post Card / List — 1:1 Zibll Alignment - * - * 对齐 zibll main.css 的 .posts-item 系统 - * - 默认 = 列表模式(无额外类名) - * - .card = 卡片模式 - * - * 布局:inline-block(对齐 zibll,不用 Grid) + * Zibll 9.0 article list/card contract. + * Source baseline: css/main.css (SHA-256 recorded in tests/visual). */ -/* ============================================================ - * 列表容器 — 对齐 zibll .posts-row - * ============================================================ */ -.post-grid { - margin-left: -8px; - margin-right: -8px; - font-size: 0; /* 消除 inline-block 间隙 */ +.posts-row { + margin-right: -8px; + margin-bottom: 15px; + margin-left: -8px; } -.post-grid--list { - margin-left: 0; - margin-right: 0; +.posts-row > *:not(.post_ajax_loader) { + margin-right: 8px !important; + margin-left: 8px !important; } -/* ============================================================ - * 通用 posts-item 基础 — 对齐 zibll line 7585 - * ============================================================ */ -posts.posts-item, -article.posts-item, .posts-item { - opacity: 1 !important; - visibility: visible !important; - padding: 20px; - margin: 15px 0; - background: var(--main-bg-color, var(--surface-1, #fff)); - border: none; - overflow: hidden; - transition: box-shadow 0.2s ease; - box-shadow: 0 0 10px var(--main-shadow, var(--shadow-sm)); - border-radius: var(--main-radius, var(--radius, 8px)); - font-size: 1rem; - color: var(--main-color, #4e5358); - opacity: 1; -} - -/* Custom element is inline by default. */ -posts.posts-item { - display: block; -} - -.posts-item.list { - display: flex; - width: 100%; - font-size: 1rem; -} - -/* No-thumb list cards: body fills full width (no float hole for missing thumb). */ -.posts-item.list.no-thumb .item-body, -.posts-item.no-thumb .item-body { - width: 100%; - margin: 0; -} - -/* ============================================================ - * hover — 对齐 zibll(仅阴影略强,无粉边 / 无抬升) - * ============================================================ */ -.posts-item:hover { - box-shadow: 0 0 10px rgba(116, 116, 116, 0.14); -} - -html.dark .posts-item:hover, -body.dark-theme .posts-item:hover { - box-shadow: 0 0 10px rgba(24, 24, 24, 0.18); -} - -.posts-item.card { - width: calc(33.333% - 16px); - display: inline-block; - margin: 8px; - padding: 10px; - vertical-align: top; - transition: box-shadow .25s ease, border-color .25s ease, transform .25s ease; - font-size: 1rem; /* 重置容器 font-size: 0 */ -} - -@media (max-width: 991px) { - .posts-item.card { - width: calc(33.333% - 12px); - margin: 6px; - } -} - -@media (max-width: 767px) { - .posts-item.card { - width: calc(50% - 10px); - margin: 5px; - padding: 8px; - } - - .posts-item:not(.card) .item-thumbnail { - width: 138px; - } - - .posts-mini .item-thumbnail, - .posts-item.list.style2 .item-thumbnail { - width: 90px; - } - - .posts-item .post-graphic:not(.order1) { - margin-right: 15px; - } - - .posts-item .order1.post-graphic { - margin-left: 15px; - } - - .posts-item .item-heading { - font-size: 15px; - } - - .posts-item:not(.no-thumb) .item-excerpt { - display: none; - } - - .posts-item.mult-thumb .thumb-items > span { - width: calc(33.333% - 6px); - padding-bottom: calc(var(--posts-list-scale, 70%) / 3); - } - - .posts-item.mult-thumb .thumb-items > span:nth-child(4) { - display: none; - } -} - -/* ============================================================ - * 缩略图 — 对齐 zibll line 7604 - * ============================================================ */ -.item-thumbnail { - position: relative; - overflow: hidden; - border-radius: var(--main-radius, 8px); -} - -/* 列表模式缩略图 */ -.posts-item:not(.card) .item-thumbnail { - width: 190px; - height: 0; - padding-bottom: var(--posts-list-scale, 70%); + margin: 15px 0; + padding: 20px; + overflow: hidden; + background: var(--main-bg-color, #fff); + border-radius: var(--main-radius, 8px); + box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); + transition: 0.2s; } .posts-item .post-graphic:not(.order1) { - margin-right: 20px; + margin-right: 20px; } .posts-item .order1.post-graphic { - margin-right: 0; - margin-left: 20px; + margin-right: 0; + margin-left: 20px; } -/* 卡片模式缩略图 */ -.posts-item.card .item-thumbnail { - width: 100%; - height: 0; - padding-bottom: var(--posts-card-scale, 70%); +.posts-item .item-thumbnail { + position: relative; + display: block; + width: 190px; + height: 0; + padding-bottom: var(--posts-list-scale, 70%); + overflow: hidden; + border-radius: var(--main-radius, 8px); } -.item-thumbnail a { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: block; +.item-thumbnail.contain img { + object-fit: contain; } -.item-thumbnail img, -.item-thumbnail .fit-cover { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - transition: transform 0.3s ease; +.swiper-slide.gradient-bg .dplayer-poster img, +.swiper-slide.gradient-bg > * > img { + object-fit: contain !important; + border-radius: 0 !important; } -.posts-item:hover .item-thumbnail img { - transform: scale(1.05); +.item-thumbnail .swiper-wrapper { + height: 100% !important; } -/* 图片徽章 */ -.img-badge { - position: absolute; - top: 8px; - padding: 2px 8px; - border-radius: 4px; - font-size: 11px; - font-weight: 600; - color: #fff; - z-index: 2; +.list .item-thumbnail .zib-slider, +.list .item-thumbnail.new-swiper { + position: absolute; + width: 100%; + height: 100%; + margin: 0 !important; } -.img-badge.left { - left: 8px; +.item-thumbnail .dplayer.dplayer-thumb { + position: absolute; + inset: 0; + z-index: 1; + padding-bottom: var(--posts-list-scale, 70%); + background: #000; + opacity: 0; + transition: opacity 0.3s; } -.badge-sticky { - background: var(--color-error, #ef4444); +.card .item-thumbnail .dplayer.dplayer-thumb { + padding-bottom: var(--posts-card-scale, 70%); } -/* ============================================================ - * 标题 — 对齐 zibll line 7669 - * ============================================================ */ -.item-heading { - margin-top: 0; - margin-bottom: 5px; - font-size: 18px; - line-height: 1.4em; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; - max-height: 2.8em; +.item-thumbnail .dplayer-thumb video, +.item-thumbnail .swiper-wrapper, +.item-thumbnail img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; } -.item-heading--bold { - font-weight: 700; +.item-thumbnail .swiper-pagination { + right: 0 !important; + bottom: 0 !important; + left: 0 !important; + padding: 0 2px !important; + text-align: center !important; } -.fit-cover { - object-fit: cover; +.posts-item.thumb-dplayer-playing .dplayer-thumb { + opacity: 1; } -.fit-contain { - object-fit: contain; +.posts-item.thumb-dplayer-playing .abs-center { + opacity: 0; +} + +.posts-item .item-heading { + display: -webkit-box; + max-height: 2.8em; + margin-top: 0; + margin-bottom: 5px; + overflow: hidden; + font-size: 18px; + line-height: 1.4em; + white-space: normal; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.font-bold { + font-weight: 700; +} + +.list.style2 .item-thumbnail { + width: 130px; +} + +.list.style2 .item-heading { + display: unset; + max-height: 1.4em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.posts-item .item-heading > a { + color: var(--key-color, #333); +} + +.posts-item .item-heading > a:hover { + color: var(--focus-color, #f04494); +} + +.posts-item .item-excerpt { + margin: 0 0 6px; + overflow: hidden; + color: var(--muted-color, #777); + text-overflow: ellipsis; + white-space: nowrap; +} + +.posts-item .item-meta item { + margin-right: 8px; + font-size: 13px; +} + +.posts-item .item-meta svg.icon, +.posts-mini .item-meta svg.icon { + width: 1em; + height: 1em; + margin-right: 3px; + fill: currentColor; + vertical-align: -0.15em; +} + +.posts-item .meta-right item:last-child { + margin-right: 0; +} + +.item-meta .avatar-mini { + width: 20px; + height: 20px; + border-radius: 50%; + transform: translateY(-1px); +} + +.item-meta .meta-pay { + padding: 0 4px; +} + +.item-meta .meta-pay img { + width: 1em; + height: 1em; + object-fit: contain; + vertical-align: -0.15em; +} + +.posts-item .item-meta a, +.posts-mini .item-meta a { + color: inherit; +} + +.posts-item .item-tags a { + margin-right: 5px; + padding: 2px 5px; + font-size: 11px; +} + +.posts-item .item-tags a .fa { + font-size: 1em; +} + +.posts-item.card { + display: inline-block; + width: calc(33.333% - 16px); + margin: 8px; + padding: 10px; + vertical-align: top; + transition: 0.3s; +} + +.posts-item.card:last-child { + margin-bottom: 20px; +} + +.posts-item.card .item-body { + width: 100%; + margin: 10px 0 0; + padding: 0; +} + +.posts-item.card .item-meta item { + font-size: 12px; +} + +.posts-mini-con .item-heading, +.posts-item.card .item-heading { + font-size: 14px; } .posts-item.card .item-heading { - font-size: 14px; - min-height: 2.8em; /* 对齐 zibll:定高使无摘要卡片底部 meta 对齐;line-height/max-height 继承基类 1.4em/2.8em */ + min-height: 2.8em; } -.item-heading a { - color: var(--key-color, #333); -} - -.item-heading a:hover { - color: var(--focus-color, #f04494); -} - -/* ============================================================ - * 摘要 — 对齐 zibll 单行 ellipsis - * ============================================================ */ -.item-excerpt { - font-size: 13px; - color: var(--muted-color, #777); - line-height: 1.5; - margin: 5px 0; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} - -/* ============================================================ - * 元信息 — 对齐 zibll line 7701 - * ============================================================ */ -.item-meta { - font-size: 13px; - color: var(--muted-2-color, #999); - margin-top: 10px; -} - -.item-meta item { - margin-right: 8px; -} - -.meta-author { - display: inline-flex; - align-items: center; - gap: 6px; -} - -.meta-avatar { - width: 20px; - height: 20px; - border-radius: 50%; - object-fit: cover; -} - -.meta-time { - font-size: 12px; -} - -.meta-right { - display: inline-flex; - align-items: center; - gap: 10px; - float: right; -} - -.meta-item { - display: inline-flex; - align-items: center; - gap: 3px; -} - -.meta-item i { - font-size: 11px; - opacity: 0.7; -} - -.meta-dot { - width: 4px; - height: 4px; - border-radius: 999px; - background: currentColor; - opacity: 0.3; - display: inline-block; -} - -/* ============================================================ - * 标签 — 对齐 zibll line 7723 - * ============================================================ */ -.item-tags { - margin-top: 8px; -} - -.item-tags .badge { - display: inline-block; - font-size: 11px; - padding: 2px 5px; - margin-right: 5px; - border-radius: 50px; - text-decoration: none; - transition: 0.15s; -} - -.badge-price { - background: rgba(239, 68, 68, 0.1); - color: var(--color-error, #ef4444); -} - -.badge-category { - background: var(--focus-shadow-color, rgba(253, 83, 161, 0.12)); - color: var(--focus-color, #f04494); -} - -.badge-category:hover { - background: var(--focus-shadow-color, rgba(253, 83, 161, 0.2)); - color: var(--focus-color, #f04494); - text-decoration: none; -} - -.badge-tag { - background: var(--muted-bg-color, #eee); - color: var(--muted-2-color, #999); -} - -.badge-tag:hover { - background: var(--focus-shadow-color, rgba(253, 83, 161, 0.12)); - color: var(--focus-color, #f04494); - text-decoration: none; -} - -/* ============================================================ - * 卡片模式 body — 对齐 zibll padding: 10px 整体 - * ============================================================ */ -.posts-item.card .item-body { - width: 100%; - margin: 10px 0 0; - padding: 0; -} - -/* ============================================================ - * 列表模式 clearfix — 对齐 zibll float 布局 - * ============================================================ */ -/* ============================================================ - * Mini 文章列表 — 对齐 zibll .posts-mini line 7903 - * ============================================================ */ -.posts-mini { - padding: 10px; - display: inline-flex; - width: 50%; - vertical-align: top; - font-size: 1rem; -} - -.posts-mini .item-thumbnail { - width: 96px; - min-width: 96px; - height: 0; - padding-bottom: 70%; - float: none; - margin-right: 12px; - margin-top: 2px; -} - -.posts-mini .item-heading { - font-size: 14px; - line-height: 1.5; - -webkit-line-clamp: 2; - max-height: 3em; -} - -@media (max-width: 767px) { - .posts-mini { - width: 100%; - } -} - -/* ============================================================ - * AJAX 翻页 — 对齐 zibll - * ============================================================ */ -.ajax-pagination { - text-align: center; - padding: 20px 0; -} - -.ajax-pagination .ajax-next { - display: inline-flex; - align-items: center; - gap: 8px; - padding: 5px 15px; - border-radius: 30px; - background: var(--muted-bg-color, #eee); - color: var(--muted-color, #777); - font-size: 14px; - cursor: pointer; - transition: 0.15s; - border: none; - text-decoration: none; -} - -.ajax-pagination .ajax-next:hover { - background: var(--focus-shadow-color, rgba(253, 83, 161, 0.12)); - color: var(--focus-color, #f04494); - text-decoration: none; -} - -.ajax-pagination .ajax-next.is-loading { - pointer-events: none; - opacity: 0.7; -} - -.ajax-pagination .ajax-nomore { - color: var(--muted-2-color, #999); - font-size: 13px; -} - -/* ============================================================ - * 骨架屏(AKSRT 保留,Zibll 无此特性) - * ============================================================ */ -.skeleton-card { - background: var(--main-bg-color, #fff); - border-radius: var(--main-radius, 8px); - overflow: hidden; - box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); -} - -.skeleton-card__thumb { - width: 100%; - padding-top: var(--posts-card-scale, 70%); - background: var(--muted-bg-color, #eee); - animation: skeleton-pulse 1.5s ease-in-out infinite; -} - -.skeleton-card__body { - padding: 10px; -} - -.skeleton-card__line { - height: 14px; - border-radius: 4px; - background: var(--muted-bg-color, #eee); - margin-bottom: 8px; - animation: skeleton-pulse 1.5s ease-in-out infinite; -} - -.skeleton-card__line--short { - width: 60%; -} - -.skeleton-card__line--meta { - width: 40%; - height: 12px; - margin-top: 12px; -} - -@keyframes skeleton-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -/* ============================================================ - * 卡片样式变体 — 对齐 zibll main.css 7682–7900 - * ============================================================ */ - -/* 卡片标签占位高度(无标签卡片不塌陷) */ .posts-item.card .item-tags { - min-height: 23px; + min-height: 23px; +} + +.posts-item.card .item-thumbnail { + width: 100%; + padding-bottom: var(--posts-card-scale, 70%); } -/* style2:透明底,hover 才出卡片底 + 阴影 */ .posts-item.card.style2 { - background: transparent; - box-shadow: none; + background: transparent; + box-shadow: none; } .posts-item.card.style2:hover { - background: var(--main-bg-color, #fff); - box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); + background: var(--main-bg-color, #fff); + box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08)); } -/* style3:padding 0,缩略图顶部圆角,body 内 padding 10px */ .posts-item.card.style3 { - padding: 0; + padding: 0; } .posts-item.card.style3 .item-thumbnail, .posts-item.card.style3 .item-thumbnail img { - border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; + border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; } .posts-item.card.style3 .item-body { - padding: 10px; - margin: 0; + margin: 0; + padding: 10px; } -/* 窄缩略图列表变体 .list.style2:130px 缩略图 + 单行标题 ellipsis */ -.posts-item.list.style2 .item-thumbnail { - width: 130px; -} - -.posts-item.list.style2 .item-heading { - text-overflow: ellipsis; - white-space: nowrap; - display: unset; - max-height: 1.4em; -} - -/* 多图卡片 .mult-thumb:九宫格缩略图(每行 4 列) */ .posts-item.mult-thumb .thumb-items { - margin: 0 -3px 6px -3px; + margin: 0 -3px 6px; } .posts-item.mult-thumb .thumb-items > span { - width: calc(25% - 6px); - display: inline-block; - height: 0; - padding-bottom: calc(var(--posts-list-scale, 70%) / 4); - margin: 0 3px; - position: relative; + position: relative; + display: inline-block; + width: calc(25% - 6px); + height: 0; + margin: 0 3px; + padding-bottom: calc(var(--posts-list-scale, 70%) / 4); } .posts-item.mult-thumb .thumb-items > span > img { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; } .posts-item.mult-thumb .thumb-items.contain > span { - background: var(--main-border-color, rgba(50, 50, 50, 0.06)); - border-radius: var(--main-radius, 8px); - overflow: hidden; + overflow: hidden; + background: var(--main-border-color, rgba(50, 50, 50, 0.06)); + border-radius: var(--main-radius, 8px); } .posts-item.mult-thumb .thumb-items.contain > span > img { - object-fit: contain; - border-radius: 0; + object-fit: contain; + border-radius: 0; } -/* 无间距连排 .no_margin:首尾圆角,中间无圆角无阴影 */ .posts-item.no_margin { - margin: 0; - box-shadow: none; - border-radius: 0; + margin: 0; + border-radius: 0; + box-shadow: none; } -.posts-item.no_margin:first-of-type { - margin-top: 10px; - border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; -} - -.posts-item.no_margin:last-of-type { - margin-bottom: 15px; - border-radius: 0 0 var(--main-radius, 8px) var(--main-radius, 8px); -} - -.posts-item + .pagenav, -.posts-item + .aksrt-pagination, -.posts-item + .pagination, -.posts-item + .navigation.pagination { - margin-top: -15px; -} - -.posts-item.card + .pagenav, -.posts-item.card + .aksrt-pagination, -.posts-item.card + .pagination, -.posts-item.card + .navigation.pagination { - margin-top: -10px; -} - -/* mini 列表缩略图 hover 放大(对齐 zibll .posts-item:hover 缩略图) */ -.posts-mini:hover .item-thumbnail img { - transform: scale(1.05); -} - -/* 瀑布流 .waterfall-box:缩略图保持原始比例(配合瀑布流 JS 时启用) */ -.waterfall-box { - position: relative; -} - -.waterfall-box .card .item-thumbnail { - padding-bottom: 0; - height: auto; -} - -.waterfall-box .card .item-thumbnail img { - position: relative; - width: 100%; - height: 100%; - object-fit: cover; -} - -/* ============================================================ - * 暗色模式 — 对齐 zibll - * ============================================================ */ -.theme-dark .posts-item, -html.dark .posts-item { - background: var(--main-bg-color, #323335); - border-color: var(--main-border-color, rgba(114, 114, 114, 0.1)); -} - -/* ============================================================ - * 无缩略图状态 - * ============================================================ */ -.posts-item.no-thumb { - overflow: visible; +.posts-item.no-thumb .item-excerpt { + height: auto; } .posts-item.no-thumb .item-body { - width: 100%; + width: 100%; } -.posts-item.has-no-thumb { - overflow: visible; +.posts-item + .pagenav { + margin-top: -15px; } -.posts-item.has-no-thumb .item-body { +.posts-item.card + .pagenav { + margin-top: -10px; +} + +.no_margin:first-of-type { + margin-top: 10px; + border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0; +} + +.no_margin:last-of-type { + margin-bottom: 15px; + border-radius: 0 0 var(--main-radius, 8px) var(--main-radius, 8px); +} + +.posts-mini { + padding: 10px; + display: inline-flex; + width: 50%; + vertical-align: top; +} + +.posts-mini-con { + flex: auto; + overflow: hidden; +} + +.posts-mini .item-thumbnail { + width: 96px; + height: 0; + padding-bottom: var(--posts-list-scale, 70%); + position: relative; + overflow: hidden; + border-radius: var(--main-radius, 8px); +} + +.posts-mini .item-thumbnail img { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; +} + +.posts-mini .item-heading { + margin: 0; +} + +.posts-mini .item-heading.icon-circle { + margin-left: -5px; +} + +.posts-mini .item-meta { + font-size: 12px; +} + +.posts-mini-con .badg { + min-width: 18px; +} + +.sidebar .posts-mini, +.mobile-nav-widget .posts-mini { + width: 100% !important; +} + +.mobile-nav-widget .posts-mini { + padding-right: 0; + padding-left: 0; +} + +.mobile-navbar.right .posts-mini .meta-author > span, +.mobile-navbar.left .posts-mini .meta-author > span { + display: none; +} + +.posts-mini:last-of-type { + margin-bottom: 0; +} + +.posts-mini-lists { + padding: 5px; +} + +.posts-mini-lists .tab-nav-theme { + padding: 0 5px 2px; +} + +.mobile-nav-widget .posts-mini-lists .tab-nav-theme { + margin: 0 -5px; + padding: 0; +} + +.widget-main-post.style-list .widget-ajaxpager, +.widget-main-post.style-card .widget-ajaxpager, +.widget-tab-post.index-tab .tab-content > .tab-pane { + margin: -8px -8px 15px; +} + +.widget-tab-post.style-list .tab-content > .tab-pane { + margin-bottom: 25px; +} + +.widget-main-post.style-list .posts-item, +.widget-tab-post.style-list .posts-item { + margin-right: 8px; + margin-left: 8px; +} + +.posts-mini:hover img, +.posts-item:hover .item-thumbnail img, +.posts-item.mult-thumb .thumb-items > span > img:hover { + transform: scale(1.02); +} + +.swiper-scroll .posts-item { + flex-shrink: 0; + margin: 0 16px 0 0 !important; +} + +.swiper-scroll .posts-item.list { + width: calc(50% - 8px) !important; + min-width: 520px !important; +} + +.swiper-scroll .posts-item.posts-mini { + width: 360px !important; +} + +.swiper-scroll .posts-item.card { + width: calc(33.333% - 11px) !important; +} + +.img-badge { + position: absolute; + top: 8px; + z-index: 2; +} + +.img-badge.left { + left: 8px; +} + +@media (min-width: 992px) { + .fluid-widget-wrap .posts-item.card, + .site-layout-1 .posts-item.card { + width: calc(25% - 16px); + } + + .fluid-widget-wrap .posts-item:not(.card), + .site-layout-1 .posts-item:not(.card) { + display: inline-flex; + width: calc(50% - 16px); + margin: 8px; + } + + .fluid-widget-wrap .posts-item.no_margin, + .site-layout-1 .posts-item.no_margin { + border-radius: var(--main-radius, 8px); + } + + .fluid-widget-wrap .posts-mini, + .site-layout-1 .posts-mini { + width: 33.333%; + } + + .sidebar .posts-item.card, + .sidebar .posts-item.list { + width: 100% !important; + min-width: 0 !important; + margin: 0; + } +} + +@media (max-width: 767px) { + .posts-row { + margin-right: -5px; + margin-left: -5px; + } + + .posts-row > *:not(.post_ajax_loader) { + margin-right: 5px !important; + margin-left: 5px !important; + } + + .posts-item .item-thumbnail { + width: 138px; + } + + .posts-mini .item-thumbnail, + .list.style2 .item-thumbnail { + width: 90px; + } + + .posts-item .post-graphic:not(.order1) { + margin-right: 15px; + } + + .posts-item .order1.post-graphic { + margin-left: 15px; + } + + .posts-item .item-heading { + font-size: 15px; + } + + .posts-item:not(.no-thumb) .item-excerpt { + display: none; + } + + .posts-item.card { + width: calc(50% - 10px); + margin: 5px; + } + + .widget-main-post.style-list .widget-ajaxpager, + .widget-main-post.style-card .widget-ajaxpager, + .widget-tab-post.index-tab .tab-content > .tab-pane { + margin: -5px -5px 20px; + } + + .widget-tab-post.style-list .tab-content > .tab-pane, + .widget-main-post.style-list .widget-ajaxpager { + margin-top: -15px; + } + + .widget-tab-post.style-list .posts-item { + margin-right: 5px; + margin-left: 5px; + } + + .swiper-scroll .posts-item { + margin-right: 10px !important; + } + + .posts-item.card:nth-child(odd) { + margin-right: 5px; + } + + .posts-item.card:nth-child(even) { + margin-left: 5px; + } + + .posts-item.mult-thumb .thumb-items > span { + width: calc(33.333% - 6px); + padding-bottom: calc(var(--posts-list-scale, 70%) / 3); + } + + .posts-item.mult-thumb .thumb-items > span:nth-child(4) { + display: none; + } + + .swiper-scroll .posts-item.card { + width: calc(50% - 5px) !important; + } + + .swiper-scroll .posts-item.list { + width: 100% !important; + min-width: auto !important; + } +} + +@media (max-width: 640px) { + .posts-item:not(.no-thumb) .meta-right item { + display: none; + } + + .posts-mini { width: 100%; + } + + .posts-item { + margin: 10px 0; + padding: 10px; + } + + .zib-widget .posts-item.no_margin { + padding: 10px 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .posts-item, + .posts-item img { + transition: none; + } } diff --git a/assets/js/core/misc.js b/assets/js/core/misc.js index b5e370db..9a66295a 100644 --- a/assets/js/core/misc.js +++ b/assets/js/core/misc.js @@ -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; diff --git a/assets/js/core/pagination.js b/assets/js/core/pagination.js index 8d8b2b18..c3566ee0 100644 --- a/assets/js/core/pagination.js +++ b/assets/js/core/pagination.js @@ -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"); diff --git a/assets/js/grade.min.js b/assets/js/grade.min.js new file mode 100644 index 00000000..ef05f45a --- /dev/null +++ b/assets/js/grade.min.js @@ -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;o20});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)}); diff --git a/assets/js/homepage-fixed.js b/assets/js/homepage-fixed.js index 29c483c8..91a2f9e8 100644 --- a/assets/js/homepage-fixed.js +++ b/assets/js/homepage-fixed.js @@ -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') { diff --git a/category.php b/category.php index 31e996bd..0fa2201e 100644 --- a/category.php +++ b/category.php @@ -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); +$render_config = function_exists('aksrt_archive_get_render_config') + ? 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, diff --git a/inc/helpers/aksrt-archive-filters.php b/inc/helpers/aksrt-archive-filters.php index d4f18bfb..2e818b80 100644 --- a/inc/helpers/aksrt-archive-filters.php +++ b/inc/helpers/aksrt-archive-filters.php @@ -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="" data-term-id="" data-query-context="archive" - data-render-mode="" - data-card-style="" > diff --git a/inc/helpers/aksrt-archive.php b/inc/helpers/aksrt-archive.php index d7dca8f6..79a5ffbc 100644 --- a/inc/helpers/aksrt-archive.php +++ b/inc/helpers/aksrt-archive.php @@ -16,45 +16,15 @@ if (!function_exists('aksrt_archive_get_list_style_settings')) : * @return array */ 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 $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, + 'context' => $context, + '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(); ?>
data-ajax-wrap data-query-context="" @@ -372,18 +221,14 @@ if (!function_exists('aksrt_archive_render_stream')) : 'content-stream__list'])); - } + aksrt_render_post_list(array_merge($render_config, ['wrapper_class' => 'content-stream__list']), $query); ?> diff --git a/inc/helpers/aksrt-flow.php b/inc/helpers/aksrt-flow.php index 6d48d754..fae2b61f 100644 --- a/inc/helpers/aksrt-flow.php +++ b/inc/helpers/aksrt-flow.php @@ -1,44 +1,12 @@ 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'; - } + } } diff --git a/inc/helpers/aksrt-frontend-tags.php b/inc/helpers/aksrt-frontend-tags.php index ca0977cb..51500128 100644 --- a/inc/helpers/aksrt-frontend-tags.php +++ b/inc/helpers/aksrt-frontend-tags.php @@ -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 $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']; - } - 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; - } - } - } - ?> - - post = $original_post; setup_postdata($original_post); diff --git a/inc/helpers/aksrt-homepage.php b/inc/helpers/aksrt-homepage.php index 1b823cfa..442cf337 100644 --- a/inc/helpers/aksrt-homepage.php +++ b/inc/helpers/aksrt-homepage.php @@ -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')) {
- 'home']); - $view_mode = (($home_view_cfg['mode'] ?? 'list') === 'card') ? 'card' : 'list'; - } - ?> -
- - -
$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'] ?? '加载更多'), + 'ajax_nomore' => (string) ($list_style['ajax_nomore'] ?? __('没有更多内容了', 'aksrt')), 'qj_loading' => __('加载中...', 'aksrt'), 'is_mobile' => $is_mobile ? '1' : '', 'is_logged_in' => is_user_logged_in() ? '1' : '', diff --git a/inc/helpers/aksrt-single-components.php b/inc/helpers/aksrt-single-components.php index 5166a11f..fff049b5 100644 --- a/inc/helpers/aksrt-single-components.php +++ b/inc/helpers/aksrt-single-components.php @@ -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); diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php index 0d74f20c..b1a21392 100644 --- a/inc/helpers/aksrt-single-render.php +++ b/inc/helpers/aksrt-single-render.php @@ -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,39 +583,22 @@ 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[] = [ - 'taxonomy' => 'category', - 'field' => 'term_id', - 'terms' => array_map('intval', $cat_ids), - ]; - } - $tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']); - if (!empty($tag_ids) && !is_wp_error($tag_ids)) { - $tax_query[] = [ - 'taxonomy' => 'post_tag', - 'field' => 'term_id', - 'terms' => array_map('intval', $tag_ids), - ]; - } + $cat_ids = wp_get_post_terms($post_id, 'category', ['fields' => 'ids']); + if (!empty($cat_ids) && !is_wp_error($cat_ids)) { + $tax_query[] = [ + 'taxonomy' => 'category', + 'field' => 'term_id', + 'terms' => array_map('intval', $cat_ids), + ]; + } + $tag_ids = wp_get_post_terms($post_id, 'post_tag', ['fields' => 'ids']); + if (!empty($tag_ids) && !is_wp_error($tag_ids)) { + $tax_query[] = [ + 'taxonomy' => 'post_tag', + 'field' => 'term_id', + 'terms' => array_map('intval', $tag_ids), + ]; } if (count($tax_query) > 1) { @@ -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' : ''); - ?> -
-
-
-
-
- -
-
- '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 .= '
  • ' . esc_html(get_the_title($post) . $subtitle) . '
  • '; + } + } + wp_reset_postdata(); + if ($posts_html === '') { + $posts_html = '
  • ' . esc_html__('暂无相关文章', 'aksrt') . '
  • '; + } ?>
    - - 'related', - 'mode' => 'mini', - 'show_thumb' => true, - 'show_meta' => true, - 'show_number' => false, - ]); - ?> + +
    +
    +
    +
    +
    - 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 .= '
  • ' . esc_html(get_the_title($p)) . '
  • '; - } - } - ?> - -
    -
    - -
    -
    -
    -
    - -
      - ' . esc_html__('暂无相关文章', 'aksrt') . ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?> -
    - +
    '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 '

    ' . esc_html__('暂无文章', 'aksrt') . '

    '; - } else { - echo '
    '; - while ($query->have_posts()) { - $query->the_post(); - aksrt_homepage_render_post_card(); - } - echo '
    '; - } - wp_reset_postdata(); - } + // Panel already provides posts-row.ajaxpager; avoid nested wrapper. + aksrt_render_post_list($config, $query, true); if ($with_pagination) { aksrt_homepage_render_pagination($query); diff --git a/inc/helpers/posts-renderer/config.php b/inc/helpers/posts-renderer/config.php index a0a6866b..c6b39f66 100644 --- a/inc/helpers/posts-renderer/config.php +++ b/inc/helpers/posts-renderer/config.php @@ -1,9 +1,6 @@ - */ + /** @return array */ 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' => '', - 'highlight_query' => '', + '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 $config User config. + * @param array $args Caller arguments. * @return array */ - 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'] = ''; + $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 = ''; } - $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; - } -} + $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']); -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], - ]); + 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, '$0', $escaped); + $pattern = '/' . implode('|', array_map(static fn(string $word): string => preg_quote($word, '/'), $words)) . '/iu'; + return (string) preg_replace($pattern, '$0', esc_html($text)); } } if (!function_exists('aksrt_renderer_title_markup')) { - /** - * Resolve title markup, supporting highlighted search terms. - * - * @param array $data Collected post data. - * @param array $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 .= ' ' . esc_html((string) $data['subtitle']) . ''; } - - $title = aksrt_renderer_highlight_text((string) $data['title'], (string) $cfg['highlight_query']); - $subtitle = trim((string) ($data['subtitle'] ?? '')); - if ($subtitle !== '') { - $title .= ' ' . esc_html($subtitle) . ''; - } - return $title; } } if (!function_exists('aksrt_renderer_excerpt_markup')) { - /** - * Resolve excerpt markup, supporting highlighted search terms. - * - * @param array $data Collected post data. - * @param array $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 []; - } - - $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); + foreach (array_filter(preg_split('/[\s,]+/', $raw) ?: []) as $token) { + $image = aksrt_renderer_sized_image((string) $token, $size); + if ($image !== '') { + $images[] = $image; } } - - return array_slice(array_values(array_unique($images)), 0, $limit); + return array_slice($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('//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 $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 === '') { + $primary = aksrt_renderer_spare_image($thumb); } - 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 = (string) aksrt_get_lazy_thumb(); } return [ - 'primary' => $primary, - 'gallery' => $gallery, - 'has_thumb' => $primary !== '', - 'has_real' => $has_real, + 'primary' => esc_url_raw($primary), + 'gallery' => $gallery, + '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'; - } -} diff --git a/inc/helpers/posts-renderer/markup.php b/inc/helpers/posts-renderer/markup.php index c777ad22..28c71bcd 100644 --- a/inc/helpers/posts-renderer/markup.php +++ b/inc/helpers/posts-renderer/markup.php @@ -1,6 +1,6 @@ $data Collected post data. - * @param array $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 = '

    ' . aksrt_renderer_title_markup($data, $cfg) . '

    '; + $excerpt_class = ($cfg['style'] ?? '') === 'style2' ? 'text-ellipsis-2' : 'text-ellipsis'; + $excerpt = '
    ' . aksrt_renderer_excerpt_markup($data, $cfg) . '
    '; $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 = '
    ' . $thumb . '
    '; } - - 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 = '

    ' . $title_markup . '

    '; - $excerpt_html = $cfg['show_excerpt'] && $excerpt_markup !== '' - ? '

    ' . $excerpt_markup . '

    ' - : ''; - - $html = '
    '; - - if ($cfg['style'] === 'style2') { - $html .= $title_html; - $html .= '
    '; - $html .= $thumb; - $html .= '
    '; - $html .= $mult ?: $excerpt_html; - $html .= $badges; - $html .= $meta; - $html .= '
    '; + $meta = aksrt_renderer_meta($data, $cfg, false); + $html = ''; + if (($cfg['style'] ?? '') === 'style2') { + $html .= $title . '
    ' . $graphic . '
    '; + $html .= $mult !== '' ? $mult : $excerpt; + $html .= $badges . $meta . '
    '; } else { - $html .= $thumb; - $html .= '
    '; - $html .= $title_html; - $html .= $mult ?: $excerpt_html; - $html .= '
    ' . $badges . $meta . '
    '; - $html .= '
    '; + $html .= $graphic . '
    ' . $title; + $html .= $mult !== '' ? $mult : $excerpt; + $html .= '
    ' . $badges . $meta . '
    '; } - - $html .= '
    '; - return $html; + return $html . ''; } } if (!function_exists('aksrt_renderer_card')) { - /** - * Render a vertical card or CMS resource card. - * - * @param array $data Collected post data. - * @param array $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 = '

    ' . aksrt_renderer_title_markup($data, $cfg) . '

    '; - $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 = '
    '; - $html .= $thumb; - $html .= '
    '; - $target_attr = !empty($cfg['target_blank']) ? ' target="_blank"' : ''; - $heading_cls = !empty($cfg['heading_bold']) ? 'item-heading item-heading--bold' : 'item-heading'; - $html .= '

    ' . $title_markup . '

    '; - - $html .= $badges; - - if ($cfg['mode'] === 'grid-cms') { - $html .= $resource_strip; - } - - $html .= $meta; - $html .= '
    '; - - return $html; + $html = ''; + $html .= aksrt_renderer_thumb($data, $cfg); + $html .= '
    ' . $title; + $html .= aksrt_renderer_badges($data, $cfg); + $html .= aksrt_renderer_meta($data, $cfg, true); + return $html . '
    '; } } if (!function_exists('aksrt_renderer_mini')) { - /** - * Render a compact mini card. - * - * @param array $data Collected post data. - * @param array $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 = '
    '; } - 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 = '' . $number_value . ''; } - - $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 = '' . (int) $cfg['number_offset'] . ''; + $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 .= '' . esc_html((string) $data['subtitle']) . ''; } - - $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 = ''; - - $thumb_html = '' . $thumb_img . ''; + $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 = '' . esc_html($author_value) . ''; + } else { + $meta_left = $show_time ? '' . esc_html((string) $data['time_ago']) . '' : ''; + } + $pay = aksrt_renderer_pay_badge($data, true); + if ($meta_left === '') { + $meta_left = $pay; + $pay = ''; + } + if ($meta_left === '') { + $meta_left = ''; + } + $meta_right = $pay . '' . aksrt_renderer_svg_icon('view') . esc_html(aksrt_renderer_meta_number((int) $data['views'])) . ''; + $meta = '
    ' . $meta_left . '
    ' . $meta_right . '
    '; } - - $heading_class = 'item-heading' . (!empty($cfg['heading_bold']) ? ' item-heading--bold' : '') . (!empty($data['has_img']) ? ' text-ellipsis-2' : ' text-ellipsis'); - $title_html = '

    ' . $number_html . '' . aksrt_renderer_title_markup($data, $cfg) . '

    '; - - $html = '
    '; - $html .= $thumb_html; - $html .= '
    '; - $html .= $title_html; - $html .= aksrt_renderer_meta($data, $cfg); - $html .= '
    '; - - return $html; + $class = trim('posts-mini ' . (string) ($cfg['class'] ?? '')); + return '
    ' . $thumb . '

    ' . $number . '' . $title . '

    ' . $meta . '
    '; } } if (!function_exists('aksrt_render_post')) { - /** - * Render one post item inside a loop. - * - * @param array $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 $config Renderer config overrides. - * @param bool $echo Whether to echo output. - * @return string + * @param array $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']) + ? '
    ' + : ''; if ($query instanceof WP_Query) { - if (!$query->have_posts()) { - $html = '

    ' . esc_html__('暂无文章', 'aksrt') . '

    '; - } else { - if (!$skip_wrapper) { - $html .= '
    '; - } - $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 .= '
    '; - } + while ($query->have_posts()) { + $query->the_post(); + $html .= aksrt_render_post($cfg); } wp_reset_postdata(); } else { - if (!have_posts()) { - $html = '

    ' . esc_html__('暂无文章', 'aksrt') . '

    '; - } else { - if (!$skip_wrapper) { - $html .= '
    '; - } - while (have_posts()) { - the_post(); - $html .= aksrt_render_post($cfg); - } - if (!$skip_wrapper) { - $html .= '
    '; - } + while (have_posts()) { + the_post(); + $html .= aksrt_render_post($cfg); } } + if (empty($cfg['skip_wrapper'])) { + $html .= '
    '; + } 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 $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 = '
    ' - . '

    ' - . '

    ' - . '
    '; - $html = str_repeat($item, $count); - } elseif ($mode === 'mini') { - $item = '
    ' - . '
    ' - . '
    '; - $html = str_repeat($item, $count); - } else { - $item = '
    ' - . '

    ' - . '

    ' - . '
    '; - $html = str_repeat($item, $count); - } - - return $html; + $class = $mode === 'card' ? 'posts-item card' : 'posts-item list flex'; + $item = '
    '; + return str_repeat($item, $count); } } diff --git a/inc/helpers/posts-renderer/post-data.php b/inc/helpers/posts-renderer/post-data.php index 8bbecd71..65a07941 100644 --- a/inc/helpers/posts-renderer/post-data.php +++ b/inc/helpers/posts-renderer/post-data.php @@ -1,6 +1,6 @@ $cfg Renderer config. - * @return array - */ + /** @return array */ 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, - '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(), - '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, - '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'], + 'post_id' => $post_id, + 'permalink' => get_permalink($post_id), + 'title' => get_the_title($post_id), + '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_no_thumb' => $is_no_thumb, + 'categories' => $categories, + 'tags' => $tags, + '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), + '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 $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 $data Collected post data. - * @param array $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 = ''; - - $sticky_html = ($is_sticky && !empty($cfg['badge_show'])) - ? '' . esc_html__('置顶', 'aksrt') . '' - : ''; - - $format_badge = ''; - if (!empty($data['is_mult_thumb']) && !empty($data['gallery_images'])) { - $format_badge = '' . esc_html(count($data['gallery_images'])) . ''; - } elseif (!empty($data['is_video_cover'])) { - $format_badge = ''; - } - - $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 ($has_video_thumb) { + $muted = !empty($cfg['thumb']['list_thumb_video_mute_s']); + $content .= '
    '; + $content .= '
    '; + $content .= '
    '; + $format_icon = ''; + } elseif ($has_slides) { + $content .= '
    '; + foreach ((array) $data['slides'] as $image) { + $content .= '
    '; + } + $content .= '
    '; + $format_icon = '' . count((array) $data['slides']) . ''; + } else { + $content .= ''; + $format = get_post_format((int) $data['post_id']); + if ((string) $data['video_url'] !== '') { + $format_icon = ''; + } elseif (in_array($format, ['image', 'gallery'], true) && (int) $data['gallery_count'] > 0) { + $format_icon = '' . (int) $data['gallery_count'] . ''; + } elseif ($format === 'video') { + $format_icon = ''; } - return '
    ' - . $img_html . '' . $sticky_html . $format_badge . '
    '; } - return '
    ' - . $img_html . '' . $sticky_html . $format_badge . '
    '; + if ($format_icon !== '') { + $content .= '
    ' . $format_icon . '
    '; + } + if (!empty($data['is_sticky'])) { + $content .= '' . esc_html__('置顶', 'aksrt') . ''; + } + + $fit = $is_contain && !$has_slides ? ' contain gradient-bg' : ''; + $opacity = $is_contain && !$has_slides ? ' data-opacity="0.2"' : ''; + return '
    ' . $content . '
    '; } } if (!function_exists('aksrt_renderer_mult_thumb')) { - /** - * Render the multi-image strip used by list cards. - * - * @param array $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 = ''; - - foreach (array_slice($data['gallery_images'], 0, 4) as $image) { - $html .= ''; - $html .= ''; - $html .= ''; + $fit = (($cfg['thumb']['list_thumb_fit'] ?? '') === 'contain') ? ' contain' : ''; + $target = !empty($cfg['target_blank']) ? ' target="_blank"' : ''; + $html = ''; + foreach (array_slice((array) $data['gallery'], 0, 4) as $index => $image) { + $badge = ''; + if ($index === 3 && (int) ($data['gallery_count'] ?? 0) > 4) { + $badge = '
    +' . ((int) $data['gallery_count'] - 4) . '
    '; + } + $html .= '' . $badge . ''; } + return $html . '
    '; + } +} - $html .= ''; - 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 '' . $icon . esc_html((string) $data['resource_badge']) . ''; + } + $tooltip = $is_vip ? ' data-toggle="tooltip" title="' . esc_attr($title) . '"' : ''; + return '' . $icon . esc_html((string) $data['resource_badge']) . ''; + } +} + +if (!function_exists('aksrt_renderer_vip_icon')) { + function aksrt_renderer_vip_icon(int $level): string { + if (!class_exists('\AKSRT\Settings\Settings')) { + return ''; + } + $settings = \AKSRT\Settings\Settings::get_payment_vip_settings(); + $url = esc_url((string) ($settings['levels'][$level]['img_icon'] ?? '')); + return $url !== '' + ? '' + : ''; } } if (!function_exists('aksrt_renderer_badges')) { - /** - * Render taxonomy and resource badges. - * - * @param array $data Collected post data. - * @param array $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[] = '' . esc_html($data['resource_badge']) . ''; - } - - 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[] = ' ' . esc_html($category->name) . ''; + $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_tags']) && $cfg['tag_limit'] > 0) { - foreach (array_slice($data['tags'], 0, $cfg['tag_limit']) as $tag) { - $items[] = '#' . esc_html($tag->name) . ''; + 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[] = '' . esc_html($category->name) . ''; } } - - if (!empty($data['is_resource'])) { - $items[] = 'VIP'; + 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[] = '# ' . esc_html($tag->name) . ''; + } } - - if (empty($items)) { - return ''; - } - - return '
    ' . implode('', $items) . '
    '; + return $items ? '
    ' . implode('', $items) . '
    ' : ''; } } -if (!function_exists('aksrt_renderer_resource_strip')) { - /** - * Render compact resource meta chips. - * - * @param array $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 ''; - } +if (!function_exists('aksrt_renderer_meta_number')) { + function aksrt_renderer_meta_number(int $number): string { + return (string) aksrt_format_number($number); + } +} - $html = '
      '; - foreach ($data['resource_strip'] as $item) { - $html .= '
    • ' . esc_html((string) $item) . '
    • '; - } - $html .= '
    '; - - return $html; +if (!function_exists('aksrt_renderer_svg_icon')) { + function aksrt_renderer_svg_icon(string $name): string { + return ''; } } if (!function_exists('aksrt_renderer_meta')) { - /** - * Render the meta bar. - * - * @param array $data Collected post data. - * @param array $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'] !== '' + ? '' . esc_html((string) $data['author_name']) . '' + : ''; + $time = $show_time + ? '' . esc_html((string) $data['time_ago']) . '' + : ''; + $avatar = ''; + $meta_left = '' . $avatar . $name . $time . ''; + } elseif ($show_time) { + $meta_left = '' . esc_html((string) $data['time_ago']) . ''; } - $format_number = function_exists('aksrt_format_number') ? 'aksrt_format_number' : 'strval'; - - if ($cfg['mode'] === 'mini') { - $items = []; - if (!empty($cfg['show_date'])) { - $items[] = '' . esc_html($data['time_ago']) . ''; - } - if (!empty($cfg['show_views'])) { - $items[] = ' ' . esc_html($format_number($data['views'])) . ''; - } - - if (empty($items)) { - return ''; - } - - return '
    ' . implode('', $items) . '
    '; + $right = ''; + if (!empty($data['comments_open']) && empty($cfg['close_comments'])) { + $right .= '' . aksrt_renderer_svg_icon('comment') . esc_html((string) $data['comments']) . ''; + } + $right .= '' . aksrt_renderer_svg_icon('view') . esc_html(aksrt_renderer_meta_number((int) $data['views'])) . ''; + if (!empty($cfg['show_like']) && (string) ($data['post_type'] ?? '') === 'post') { + $right .= '' . aksrt_renderer_svg_icon('like') . esc_html(aksrt_renderer_meta_number((int) $data['likes'])) . ''; } - $left = []; - if (!empty($cfg['show_author'])) { - $left[] = '' . esc_html($data['author_name']) . ''; - } - if (!empty($cfg['show_date'])) { - $left[] = '' . esc_html($data['time_ago']) . ''; - } - - $right = []; - if (!empty($cfg['show_comments'])) { - $right[] = ' ' . esc_html((string) $data['comment_count']) . ''; - } - if (!empty($cfg['show_views'])) { - $right[] = ' ' . esc_html($format_number($data['views'])) . ''; - } - if (!empty($cfg['show_likes'])) { - $right[] = ' ' . esc_html($format_number($data['likes'])) . ''; - } - - if (empty($left) && empty($right)) { - return ''; - } - - $html = '
    '; - if (!empty($left)) { - $html .= '
    ' . implode('', $left) . '
    '; - } - if (!empty($right)) { - $html .= '
    ' . implode('', $right) . '
    '; - } - $html .= '
    '; - - return $html; + return '
    ' . $meta_left . '
    ' . $right . '
    '; } } diff --git a/inc/helpers/template-tags/pagination-permissions.php b/inc/helpers/template-tags/pagination-permissions.php index 9a6a4ad4..bb311d14 100644 --- a/inc/helpers/template-tags/pagination-permissions.php +++ b/inc/helpers/template-tags/pagination-permissions.php @@ -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' => '', 'next_text' => '', '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 ' ' . paginate_links(['total' => $query->max_num_pages]) . '
    '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped - } + aksrt_render_pagination($query); wp_reset_postdata(); ?> diff --git a/templates/parts/content-post-card.php b/templates/parts/content-post-card.php index fec023b0..2dfc0781 100644 --- a/templates/parts/content-post-card.php +++ b/templates/parts/content-post-card.php @@ -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 diff --git a/templates/parts/home-modules/module-category.php b/templates/parts/home-modules/module-category.php index 5a625472..af4f38a5 100644 --- a/templates/parts/home-modules/module-category.php +++ b/templates/parts/home-modules/module-category.php @@ -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) : ''; -
    +
    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); ?>
    diff --git a/templates/parts/home-modules/module-recent.php b/templates/parts/home-modules/module-recent.php index b718486d..4c608fba 100644 --- a/templates/parts/home-modules/module-recent.php +++ b/templates/parts/home-modules/module-recent.php @@ -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')); -
    +
    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); ?>
    diff --git a/templates/parts/home-modules/module-tabs.php b/templates/parts/home-modules/module-tabs.php index a858d6b3..62e190f2 100644 --- a/templates/parts/home-modules/module-tabs.php +++ b/templates/parts/home-modules/module-tabs.php @@ -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']; have_posts()) : ?>
    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); ?>
    diff --git a/templates/parts/resource-related.php b/templates/parts/resource-related.php index 35ffb3c1..982bb22a 100644 --- a/templates/parts/resource-related.php +++ b/templates/parts/resource-related.php @@ -43,15 +43,22 @@ if (!$query->have_posts()) { -
    - have_posts()) { - $query->the_post(); - get_template_part('templates/parts/content', 'post-card'); - } - set_query_var('aksrt_post_card_layout', ''); - wp_reset_postdata(); - ?> + diff --git a/templates/parts/user-home/panels-core.php b/templates/parts/user-home/panels-core.php index 6112f91b..97594feb 100644 --- a/templates/parts/user-home/panels-core.php +++ b/templates/parts/user-home/panels-core.php @@ -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()) : - ?> -
    - have_posts()) : $author_posts->the_post(); ?> - [ - 'type' => 'view', - 'url' => get_permalink(), - 'label' => __('阅读', 'aksrt'), - ], - ]); - ?> - -
    - max_num_pages, $posts_page, 'posts_page'); - wp_reset_postdata(); - else : - aksrt_render_profile_empty($svg_empty_post, __('TA 还没有发布文章', 'aksrt')); - endif; + $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, + ]); + wp_reset_postdata(); ?>
    diff --git a/tests/visual/README.md b/tests/visual/README.md index d3479448..c1a15050 100644 --- a/tests/visual/README.md +++ b/tests/visual/README.md @@ -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. diff --git a/tests/visual/article-fixtures.php b/tests/visual/article-fixtures.php new file mode 100644 index 00000000..7eb6c0f9 --- /dev/null +++ b/tests/visual/article-fixtures.php @@ -0,0 +1,294 @@ + $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 = '

    固定摘要正文,用于 Zibll 与 AKSRT 的文章列表逐像素比较。

    '; + if (!empty($scenario['gallery'])) { + $content .= implode('', array_map(static fn(string $url): string => '', array_slice($image_urls, 0, 4))); + } elseif (isset($scenario['first_image'])) { + $content .= ''; + } + $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()); +} diff --git a/tests/visual/article-visual-matrix-v1.json b/tests/visual/article-visual-matrix-v1.json new file mode 100644 index 00000000..d284802d --- /dev/null +++ b/tests/visual/article-visual-matrix-v1.json @@ -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 + } +} diff --git a/tests/visual/docker-compose.zibll.yml b/tests/visual/docker-compose.zibll.yml index 15e111f9..5934d7e0 100644 --- a/tests/visual/docker-compose.zibll.yml +++ b/tests/visual/docker-compose.zibll.yml @@ -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: diff --git a/tests/visual/zibll-9.0-article-baseline.json b/tests/visual/zibll-9.0-article-baseline.json new file mode 100644 index 00000000..0e3da1e5 --- /dev/null +++ b/tests/visual/zibll-9.0-article-baseline.json @@ -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" + ] +} diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs index 185c2450..7157e2e7 100644 --- a/tests/widget-system/post-visual-contract.test.cjs +++ b/tests/widget-system/post-visual-contract.test.cjs @@ -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, /
    ]+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, / .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('
    archive.indexOf('
    ')); + 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, /
  • \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); });