diff --git a/assets/admin/src/schema/sections/post-list/feature.js b/assets/admin/src/schema/sections/post-list/feature.js index b479281c..4be7ebdd 100644 --- a/assets/admin/src/schema/sections/post-list/feature.js +++ b/assets/admin/src/schema/sections/post-list/feature.js @@ -2,19 +2,121 @@ export const featureSchema = { key: 'post_list.feature', title: '文章功能', icon: 'fa fas fa-magic', - description: '配置文章页的互动功能开关。', + description: '配置文章目录树、图片灯箱、点赞与新建文章的预置参数。', defaults: { - feature_pdf_export: false, - feature_print: true, + article_nav: true, + article_nav_mobile_nav_s: true, + imagelightbox: true, + imagelightbox_type: 'group', + imagelightbox_thumbs_s: ['pc_s', 'm_s'], + imagelightbox_play_s: ['pc_s', 'm_s'], + imagelightbox_zoom_s: ['pc_s'], + imagelightbox_full_s: ['pc_s'], + imagelightbox_down_s: ['pc_s'], + post_like_s: true, + post_default_mate: { views: { min: 20, max: 50 }, like: { min: 5, max: 15 } }, }, groups: [ { - key: 'tools', - title: '工具功能', - icon: 'fa fas fa-wrench', + key: 'feature', + title: '', fields: [ - { key: 'feature_print', label: '打印文章', type: 'switch', default: true }, - { key: 'feature_pdf_export', label: 'PDF 导出', type: 'switch', default: false, help: '实验性功能' }, + { key: 'article_nav', label: '文章目录树', type: 'switch', default: true, description: '默认开关,每篇文章可单独设置。开启后请自行添加文章目录树模块到侧边栏(文章内标题超过3个才会显示)' }, + { + key: 'article_nav_tutorial', + label: '', + type: 'content', + ui_only: true, + dependsOn: { key: 'article_nav', value: true }, + content: '

【查看官网教程】

', + }, + { key: 'article_nav_mobile_nav_s', label: '在移动端弹出菜单内显示', type: 'switch', default: true, dependsOn: { key: 'article_nav', value: true }, description: '如需显示在侧边栏等其它位置,请使用小工具模块添加' }, + { key: 'imagelightbox', label: '图片灯箱', type: 'switch', default: true, description: '点击图片查看原图功能,共两种模式' }, + { + key: 'imagelightbox_tutorial', + label: '', + type: 'content', + ui_only: true, + dependsOn: { key: 'imagelightbox', value: true }, + content: '

【查看官网教程】

', + }, + { + key: 'imagelightbox_type', + label: '图片灯箱模式', + type: 'radio', + default: 'group', + dependsOn: { key: 'imagelightbox', value: true }, + options: [ + { label: '组合模式(将文章的全部图片组合成一组灯箱)', value: 'group' }, + { label: '单张模式(点击某张图片仅显示该图片)', value: 'alone' }, + ], + }, + { + key: 'imagelightbox_thumbs_s', + label: '缩略图导航', + type: 'checkbox', + default: ['pc_s', 'm_s'], + dependsOn: { all: [{ key: 'imagelightbox', value: true }, { key: 'imagelightbox_type', value: 'group' }] }, + options: [ + { label: 'PC端开启', value: 'pc_s' }, + { label: '移动端开启', value: 'm_s' }, + ], + }, + { + key: 'imagelightbox_play_s', + label: '播放按钮', + type: 'checkbox', + default: ['pc_s', 'm_s'], + dependsOn: { all: [{ key: 'imagelightbox', value: true }, { key: 'imagelightbox_type', value: 'group' }] }, + options: [ + { label: 'PC端开启', value: 'pc_s' }, + { label: '移动端开启', value: 'm_s' }, + ], + }, + { + key: 'imagelightbox_zoom_s', + label: '缩放按钮', + type: 'checkbox', + default: ['pc_s'], + dependsOn: { key: 'imagelightbox', value: true }, + options: [ + { label: 'PC端开启', value: 'pc_s' }, + { label: '移动端开启', value: 'm_s' }, + ], + }, + { + key: 'imagelightbox_full_s', + label: '全屏按钮', + type: 'checkbox', + default: ['pc_s'], + dependsOn: { key: 'imagelightbox', value: true }, + options: [ + { label: 'PC端开启', value: 'pc_s' }, + { label: '移动端开启', value: 'm_s' }, + ], + }, + { + key: 'imagelightbox_down_s', + label: '下载按钮', + type: 'checkbox', + default: ['pc_s'], + dependsOn: { key: 'imagelightbox', value: true }, + options: [ + { label: 'PC端开启', value: 'pc_s' }, + { label: '移动端开启', value: 'm_s' }, + ], + }, + { key: 'post_like_s', label: '文章点赞', type: 'switch', default: true }, + { + key: 'post_default_mate', + label: '文章预置参数', + type: 'fieldset', + default: { views: { min: 20, max: 50 }, like: { min: 5, max: 15 } }, + fields: [ + { key: 'views', label: '阅读量', type: 'between_number', default: { min: 20, max: 50 } }, + { key: 'like', label: '点赞数', type: 'between_number', default: { min: 5, max: 15 }, description: '新建文章时系统会根据此处设置的区间获取随机值填入对应项' }, + ], + }, ], }, ], diff --git a/assets/admin/src/schema/sections/post-list/page.js b/assets/admin/src/schema/sections/post-list/page.js index 231d6c82..278629ef 100644 --- a/assets/admin/src/schema/sections/post-list/page.js +++ b/assets/admin/src/schema/sections/post-list/page.js @@ -2,7 +2,7 @@ export const pageSchema = { key: 'post_list.page', title: '文章页面', icon: 'fa fas fa-file-alt', - description: '配置单篇文章页面的布局、图片灯箱、版权区和互动功能。', + description: '配置单篇文章页面的导航、封面、版权区与互动功能。', defaults: { post_reading_progress: true, breadcrumbs_single_s: true, @@ -10,12 +10,6 @@ export const pageSchema = { post_prevnext_s: true, post_authordesc_s: true, post_single_hide_time_s: false, - post_lightbox: true, - post_lightbox_thumbs: true, - post_lightbox_zoom: true, - post_lightbox_full: true, - post_lightbox_down: false, - post_lightbox_play: false, post_related_s: true, post_related_type: 'img', related_title: '相关推荐', @@ -25,17 +19,12 @@ export const pageSchema = { post_copyright_enabled: true, post_copyright_text: '', share_s: true, - post_like_enabled: true, post_favorite_enabled: true, post_front_content: '', post_after_content: '', post_yiyan_header: false, post_yiyan_footer: false, post_yiyan_box: false, - post_default_views_min: 20, - post_default_views_max: 50, - post_default_likes_min: 5, - post_default_likes_max: 15, post_article_image_cover: true, post_article_image_cover_scale: 35, post_article_slide_cover: true, @@ -89,19 +78,6 @@ export const pageSchema = { { key: 'post_article_video_cover_scale', label: '视频封面高度比例(%)', type: 'number', default: 56, min: 20, max: 100, dependsOn: { key: 'post_article_video_cover', value: true } }, ], }, - { - key: 'media', - title: '媒体功能', - icon: 'fa fas fa-image', - fields: [ - { key: 'post_lightbox', label: '图片灯箱', type: 'switch', default: true, help: '点击图片放大查看' }, - { key: 'post_lightbox_thumbs', label: '灯箱缩略图', type: 'switch', default: true, dependsOn: { key: 'post_lightbox', value: true }, help: '灯箱底部显示缩略图导航' }, - { key: 'post_lightbox_zoom', label: '灯箱缩放', type: 'switch', default: true, dependsOn: { key: 'post_lightbox', value: true }, help: '灯箱显示缩放按钮' }, - { key: 'post_lightbox_full', label: '灯箱全屏', type: 'switch', default: true, dependsOn: { key: 'post_lightbox', value: true }, help: '灯箱显示全屏按钮' }, - { key: 'post_lightbox_down', label: '灯箱下载', type: 'switch', default: false, dependsOn: { key: 'post_lightbox', value: true }, help: '灯箱显示下载按钮' }, - { key: 'post_lightbox_play', label: '灯箱播放', type: 'switch', default: false, dependsOn: { key: 'post_lightbox', value: true }, help: '灯箱显示播放按钮(自动轮播)' }, - ], - }, { key: 'related', title: '相关推荐', @@ -131,7 +107,6 @@ export const pageSchema = { title: '互动功能', icon: 'fa fas fa-heart', fields: [ - { key: 'post_like_enabled', label: '点赞', type: 'switch', default: true }, { key: 'post_favorite_enabled', label: '收藏', type: 'switch', default: true }, { key: 'share_s', label: '分享按钮', type: 'switch', default: true }, { key: 'post_copyright_enabled', label: '版权信息', type: 'switch', default: true }, @@ -166,16 +141,5 @@ export const pageSchema = { { key: 'post_yiyan_box', label: '一言盒子', type: 'switch', default: false, help: '在相关文章前显示一言盒子' }, ], }, - { - key: 'meta', - title: '默认数值', - icon: 'fa fas fa-chart-bar', - fields: [ - { key: 'post_default_views_min', label: '默认浏览最小值', type: 'number', default: 20, min: 0, max: 9999 }, - { key: 'post_default_views_max', label: '默认浏览最大值', type: 'number', default: 50, min: 1, max: 9999 }, - { key: 'post_default_likes_min', label: '默认点赞最小值', type: 'number', default: 5, min: 0, max: 9999 }, - { key: 'post_default_likes_max', label: '默认点赞最大值', type: 'number', default: 15, min: 1, max: 9999 }, - ], - }, ], } diff --git a/inc/core-hooks.php b/inc/core-hooks.php index 2ef7035d..fc9f1dc8 100644 --- a/inc/core-hooks.php +++ b/inc/core-hooks.php @@ -31,6 +31,35 @@ add_filter('comment_text', function ($comment_text, $comment = null, $args = []) return $comment_text; }, 20, 3); +// ============================================================ +// 文章预置参数:新建文章按区间随机填入浏览/点赞(参考主题 post_default_mate) +// ============================================================ + +add_action('save_post', static function ($post_id, $post = null): void { + $post_id = (int) $post_id; + if ($post_id <= 0 || wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) { + return; + } + if (!($post instanceof \WP_Post) || $post->post_type !== 'post') { + return; + } + if (!class_exists('\AKSRT\Settings\Settings')) { + return; + } + $page = \AKSRT\Settings\Settings::get_post_page_settings(); + $views_min = max(0, (int) ($page['default_views_min'] ?? 20)); + $views_max = max($views_min, (int) ($page['default_views_max'] ?? 50)); + $likes_min = max(0, (int) ($page['default_likes_min'] ?? 5)); + $likes_max = max($likes_min, (int) ($page['default_likes_max'] ?? 15)); + + if ('' === (string) get_post_meta($post_id, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, true)) { + update_post_meta($post_id, \AKSRT\Core\DomainModel::POST_VIEW_COUNT_KEY, wp_rand($views_min, $views_max)); + } + if ('' === (string) get_post_meta($post_id, '_aksrt_like_count', true)) { + update_post_meta($post_id, '_aksrt_like_count', wp_rand($likes_min, $likes_max)); + } +}, 10, 2); + // ============================================================ // 前端隐藏 WordPress Admin Bar diff --git a/inc/helpers/aksrt-frontend-tags.php b/inc/helpers/aksrt-frontend-tags.php index 4b7491e5..d7cef069 100644 --- a/inc/helpers/aksrt-frontend-tags.php +++ b/inc/helpers/aksrt-frontend-tags.php @@ -52,8 +52,6 @@ if (!function_exists('aksrt_resolve_single_post_display_settings')) : 'yiyan_header' => false, 'yiyan_footer' => false, 'yiyan_box' => false, - 'print_enabled' => true, - 'pdf_export_enabled' => false, ]; // Normalize legacy aliases → canonical render keys (idempotent). if (!array_key_exists('share_enabled', $settings) && array_key_exists('share_s', $settings)) { diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php index bd0c5f2e..8c0765f7 100644 --- a/inc/helpers/aksrt-single-render.php +++ b/inc/helpers/aksrt-single-render.php @@ -602,11 +602,19 @@ if (!function_exists('aksrt_single_render_article')) : function aksrt_single_render_article(array $ctx): void { $settings = is_array($ctx['settings'] ?? null) ? $ctx['settings'] : []; $maxheight = (int) $ctx['content_maxheight']; + // 文章目录树(参考主题 article_nav):全局开关 + 每篇文章「不显示目录树」覆盖。 + $article_nav_on = !isset($settings['article_nav']) || !empty($settings['article_nav']); + if ($article_nav_on) { + $extension_meta = get_post_meta(get_the_ID(), \AKSRT\Core\DomainModel::POST_META_KEY, true); + if (is_array($extension_meta) && !empty($extension_meta['no_article-navs'])) { + $article_nav_on = false; + } + } ?>
> -
+
0 ? ' style="max-height:' . esc_attr((string) $maxheight) . 'px;"' : ''; ?> data-maxheight=" 0 ? max(0, $maxheight - 80) : 0)); ?>"> diff --git a/inc/helpers/header/aksrt-header-mobile.php b/inc/helpers/header/aksrt-header-mobile.php index 53a066e6..788a8170 100644 --- a/inc/helpers/header/aksrt-header-mobile.php +++ b/inc/helpers/header/aksrt-header-mobile.php @@ -102,7 +102,14 @@ if (!function_exists('aksrt_nav_mobile_content')) : echo $menu; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped if (is_singular('post')) { - echo '
'; + $page = class_exists('\AKSRT\Settings\Settings') + ? \AKSRT\Settings\Settings::get_post_page_settings() + : []; + $article_nav = !isset($page['article_nav']) || !empty($page['article_nav']); + $article_nav_mobile = !isset($page['article_nav_mobile']) || !empty($page['article_nav_mobile']); + if ($article_nav && $article_nav_mobile) { + echo '
'; + } } // User box at bottom of drawer. diff --git a/src/Admin/Schema/SchemaPostList.php b/src/Admin/Schema/SchemaPostList.php index d30c7a2c..e1e15d1d 100644 --- a/src/Admin/Schema/SchemaPostList.php +++ b/src/Admin/Schema/SchemaPostList.php @@ -76,15 +76,7 @@ class SchemaPostList { ['key' => 'post_article_video_cover', 'label' => '视频封面', 'type' => 'switch', 'default' => true, 'description' => '文章有视频时以视频播放器作为封面'], ['key' => 'post_article_video_cover_scale', 'label' => '视频封面高度比例(%)', 'type' => 'number', 'default' => 56, 'min' => 20, 'max' => 100], ]], - ['key' => 'media', 'title' => '媒体功能', 'fields' => [ - ['key' => 'post_lightbox', 'label' => '图片灯箱', 'type' => 'switch', 'default' => true], - ['key' => 'post_lightbox_thumbs', 'label' => '灯箱缩略图', 'type' => 'switch', 'default' => true], - ['key' => 'post_lightbox_zoom', 'label' => '灯箱缩放', 'type' => 'switch', 'default' => true], - ['key' => 'post_lightbox_full', 'label' => '灯箱全屏', 'type' => 'switch', 'default' => true], - ['key' => 'post_lightbox_down', 'label' => '灯箱下载', 'type' => 'switch', 'default' => false], - ['key' => 'post_lightbox_play', 'label' => '灯箱播放', 'type' => 'switch', 'default' => false], - ]], - ['key' => 'related', 'title' => '相关推荐', 'fields' => [ + ['key' => 'related', 'title' => '相关推荐', 'fields' => [ ['key' => 'post_related_s', 'label' => '显示相关推荐', 'type' => 'switch', 'default' => true], ['key' => 'post_related_type', 'label' => '展示样式', 'type' => 'radio', 'default' => 'img', 'options' => [ ['label' => '图文卡片', 'value' => 'img'], @@ -104,7 +96,6 @@ class SchemaPostList { ]], ]], ['key' => 'interactions', 'title' => '互动功能', 'fields' => [ - ['key' => 'post_like_enabled', 'label' => '点赞', 'type' => 'switch', 'default' => true], ['key' => 'post_favorite_enabled', 'label' => '收藏', 'type' => 'switch', 'default' => true], ['key' => 'share_s', 'label' => '分享按钮', 'type' => 'switch', 'default' => true], ['key' => 'post_copyright_enabled', 'label' => '版权信息', 'type' => 'switch', 'default' => true], @@ -131,10 +122,6 @@ class SchemaPostList { ['key' => 'post_yiyan_box', 'label' => '一言盒子', 'type' => 'switch', 'default' => false], ]], ['key' => 'meta', 'title' => 'Meta设置', 'fields' => [ - ['key' => 'post_default_views_min', 'label' => '默认浏览最小值', 'type' => 'number', 'default' => 20, 'min' => 0, 'max' => 9999], - ['key' => 'post_default_views_max', 'label' => '默认浏览最大值', 'type' => 'number', 'default' => 50, 'min' => 1, 'max' => 9999], - ['key' => 'post_default_likes_min', 'label' => '默认点赞最小值', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 9999], - ['key' => 'post_default_likes_max', 'label' => '默认点赞最大值', 'type' => 'number', 'default' => 15, 'min' => 1, 'max' => 9999], ['key' => 'post_views_s', 'label' => '浏览统计', 'type' => 'switch', 'default' => true], ]], ], @@ -186,12 +173,42 @@ class SchemaPostList { 'feature' => [ 'key' => 'feature', 'title' => '文章功能', - 'version' => '1.0.0', + 'version' => '2.0.0', 'option_key' => 'aksrt_settings', 'groups' => [ - ['key' => 'tools', 'title' => '工具功能', 'fields' => [ - ['key' => 'feature_print', 'label' => '打印文章', 'type' => 'switch', 'default' => true], - ['key' => 'feature_pdf_export', 'label' => 'PDF 导出', 'type' => 'switch', 'default' => false], + ['key' => 'feature', 'title' => '文章功能', 'fields' => [ + ['key' => 'article_nav', 'label' => '文章目录树', 'type' => 'switcher', 'default' => true, 'description' => '默认开关,每篇文章可单独设置。开启后请自行添加文章目录树模块到侧边栏(文章内标题超过3个才会显示)'], + ['key' => 'article_nav_mobile_nav_s', 'label' => '在移动端弹出菜单内显示', 'type' => 'switcher', 'default' => true, 'dependsOn' => ['key' => 'article_nav', 'value' => true], 'description' => '如需显示在侧边栏等其它位置,请使用小工具模块添加'], + ['key' => 'imagelightbox', 'label' => '图片灯箱', 'type' => 'switcher', 'default' => true, 'description' => '点击图片查看原图功能,共两种模式'], + ['key' => 'imagelightbox_type', 'label' => '图片灯箱模式', 'type' => 'radio', 'default' => 'group', 'dependsOn' => ['key' => 'imagelightbox', 'value' => true], 'options' => [ + ['label' => '组合模式(将文章的全部图片组合成一组灯箱)', 'value' => 'group'], + ['label' => '单张模式(点击某张图片仅显示该图片)', 'value' => 'alone'], + ]], + ['key' => 'imagelightbox_thumbs_s', 'label' => '缩略图导航', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'dependsOn' => ['all' => [['key' => 'imagelightbox', 'value' => true], ['key' => 'imagelightbox_type', 'value' => 'group']]], 'options' => [ + ['label' => 'PC端开启', 'value' => 'pc_s'], + ['label' => '移动端开启', 'value' => 'm_s'], + ]], + ['key' => 'imagelightbox_play_s', 'label' => '播放按钮', 'type' => 'checkbox', 'default' => ['pc_s', 'm_s'], 'dependsOn' => ['all' => [['key' => 'imagelightbox', 'value' => true], ['key' => 'imagelightbox_type', 'value' => 'group']]], 'options' => [ + ['label' => 'PC端开启', 'value' => 'pc_s'], + ['label' => '移动端开启', 'value' => 'm_s'], + ]], + ['key' => 'imagelightbox_zoom_s', 'label' => '缩放按钮', 'type' => 'checkbox', 'default' => ['pc_s'], 'dependsOn' => ['key' => 'imagelightbox', 'value' => true], 'options' => [ + ['label' => 'PC端开启', 'value' => 'pc_s'], + ['label' => '移动端开启', 'value' => 'm_s'], + ]], + ['key' => 'imagelightbox_full_s', 'label' => '全屏按钮', 'type' => 'checkbox', 'default' => ['pc_s'], 'dependsOn' => ['key' => 'imagelightbox', 'value' => true], 'options' => [ + ['label' => 'PC端开启', 'value' => 'pc_s'], + ['label' => '移动端开启', 'value' => 'm_s'], + ]], + ['key' => 'imagelightbox_down_s', 'label' => '下载按钮', 'type' => 'checkbox', 'default' => ['pc_s'], 'dependsOn' => ['key' => 'imagelightbox', 'value' => true], 'options' => [ + ['label' => 'PC端开启', 'value' => 'pc_s'], + ['label' => '移动端开启', 'value' => 'm_s'], + ]], + ['key' => 'post_like_s', 'label' => '文章点赞', 'type' => 'switcher', 'default' => true], + ['key' => 'post_default_mate', 'label' => '文章预置参数', 'type' => 'fieldset', 'default' => ['views' => ['min' => 20, 'max' => 50], 'like' => ['min' => 5, 'max' => 15]], 'fields' => [ + ['key' => 'views', 'label' => '阅读量', 'type' => 'between_number', 'default' => ['min' => 20, 'max' => 50]], + ['key' => 'like', 'label' => '点赞数', 'type' => 'between_number', 'default' => ['min' => 5, 'max' => 15], 'description' => '新建文章时系统会根据此处设置的区间获取随机值填入对应项'], + ]], ]], ], ], diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index f4390e29..33cb9084 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -173,7 +173,8 @@ trait ScriptsPart1b { $is_paid_content_single ); - if (!empty($singular_assets['needs_print'])) { + // 打印样式(media=print):内容页常驻,无设置开关(参考主题未提供该配置)。 + if ($is_single_post || $is_single_resource || $is_static_page) { $print_path = get_template_directory() . '/assets/css/frontend/print.css'; wp_enqueue_style( 'aksrt-print', diff --git a/src/Core/ScriptsPart1c.php b/src/Core/ScriptsPart1c.php index 9d50fe7d..dd4cf586 100644 --- a/src/Core/ScriptsPart1c.php +++ b/src/Core/ScriptsPart1c.php @@ -41,7 +41,6 @@ trait ScriptsPart1c { 'needs_reward' => false, 'needs_yiyan' => false, 'needs_share' => false, - 'needs_print' => false, ]; if (!is_singular()) { @@ -139,7 +138,6 @@ trait ScriptsPart1c { $requirements['needs_reward'] = $is_article && !empty($settings['rewards_enabled']); $requirements['needs_yiyan'] = $has_yiyan; $requirements['needs_share'] = $is_article && !empty($settings['share_enabled']); - $requirements['needs_print'] = !empty($settings['print_enabled']); return $requirements; } @@ -157,6 +155,10 @@ trait ScriptsPart1c { ? \AKSRT\Settings\Settings::get_post_page_settings() : []; $lightbox_on = !isset($post_page_settings['lightbox']) || !empty($post_page_settings['lightbox']); + $lightbox_type = sanitize_key((string) ($post_page_settings['lightbox_type'] ?? 'group')); + if (!in_array($lightbox_type, ['group', 'alone'], true)) { + $lightbox_type = 'group'; + } wp_enqueue_script( 'bootstrap', $base_uri . '/assets/js/runtime/libs/bootstrap.min.js', @@ -187,7 +189,7 @@ trait ScriptsPart1c { ? \AKSRT\Settings\PaymentRuntime::get_paid_content_settings()['display']['currency_unit'] : __('元', 'aksrt'), 'imgbox' => $lightbox_on, - 'imgbox_type' => 'group', + 'imgbox_type' => $lightbox_on ? $lightbox_type : 'group', 'imgbox_thumbs' => $lightbox_on && (!isset($post_page_settings['lightbox_thumbs']) || !empty($post_page_settings['lightbox_thumbs'])), 'imgbox_zoom' => $lightbox_on && (!isset($post_page_settings['lightbox_zoom']) || !empty($post_page_settings['lightbox_zoom'])), 'imgbox_full' => $lightbox_on && (!isset($post_page_settings['lightbox_full']) || !empty($post_page_settings['lightbox_full'])), diff --git a/src/Settings/PostDisplay.php b/src/Settings/PostDisplay.php index 01a68cbc..9afb7354 100644 --- a/src/Settings/PostDisplay.php +++ b/src/Settings/PostDisplay.php @@ -200,6 +200,28 @@ class PostDisplay { $maxheight = 1000; } + // 图片灯箱(参考主题 imagelightbox*):pc_s/m_s 复选框归并为单一开关。 + $lightbox_type = sanitize_key((string) ($settings['imagelightbox_type'] ?? 'group')); + if (!in_array($lightbox_type, ['group', 'alone'], true)) { + $lightbox_type = 'group'; + } + $lightbox_button = static function ($value, bool $default): bool { + if (!is_array($value)) { + return $default; + } + return !empty($value['pc_s']) || !empty($value['m_s']); + }; + $lightbox_thumbs = $lightbox_button($settings['imagelightbox_thumbs_s'] ?? null, true); + $lightbox_zoom = $lightbox_button($settings['imagelightbox_zoom_s'] ?? null, true); + $lightbox_full = $lightbox_button($settings['imagelightbox_full_s'] ?? null, true); + $lightbox_down = $lightbox_button($settings['imagelightbox_down_s'] ?? null, true); + $lightbox_play = $lightbox_button($settings['imagelightbox_play_s'] ?? null, true); + + // 文章预置参数:新建文章时按区间随机填入浏览/点赞(参考主题 post_default_mate)。 + $default_mate = is_array($settings['post_default_mate'] ?? null) ? $settings['post_default_mate'] : []; + $views_range = is_array($default_mate['views'] ?? null) ? $default_mate['views'] : []; + $like_range = is_array($default_mate['like'] ?? null) ? $default_mate['like'] : []; + $related_style = sanitize_key((string) ($settings['post_related_type'] ?? 'img')); if (!in_array($related_style, ['img', 'list', 'text'], true)) { $related_style = 'img'; @@ -229,18 +251,19 @@ class PostDisplay { 'article_slide_cover' => !isset($settings['post_article_slide_cover']) || !empty($settings['post_article_slide_cover']), 'article_video_cover' => !isset($settings['post_article_video_cover']) || !empty($settings['post_article_video_cover']), 'article_video_cover_scale'=> max(20, min(100, (int) ($settings['post_article_video_cover_scale'] ?? 56))), - 'lightbox' => !isset($settings['post_lightbox']) || !empty($settings['post_lightbox']), - 'lightbox_thumbs' => !isset($settings['post_lightbox_thumbs']) || !empty($settings['post_lightbox_thumbs']), - 'lightbox_zoom' => !isset($settings['post_lightbox_zoom']) || !empty($settings['post_lightbox_zoom']), - 'lightbox_full' => !isset($settings['post_lightbox_full']) || !empty($settings['post_lightbox_full']), - 'lightbox_down' => !empty($settings['post_lightbox_down']), - 'lightbox_play' => !empty($settings['post_lightbox_play']), + 'lightbox' => !isset($settings['imagelightbox']) || !empty($settings['imagelightbox']), + 'lightbox_type' => $lightbox_type, + 'lightbox_thumbs' => $lightbox_thumbs, + 'lightbox_zoom' => $lightbox_zoom, + 'lightbox_full' => $lightbox_full, + 'lightbox_down' => $lightbox_down, + 'lightbox_play' => $lightbox_play, 'related_enabled' => !isset($settings['post_related_s']) || !empty($settings['post_related_s']), 'related_count' => max(4, min(12, (int) ($settings['post_related_n'] ?? 6))), 'related_style' => $related_style, 'related_orderby' => $related_orderby, 'related_title' => sanitize_text_field((string) ($settings['related_title'] ?? '相关推荐')), - 'like_enabled' => !isset($settings['post_like_enabled']) || !empty($settings['post_like_enabled']), + 'like_enabled' => !isset($settings['post_like_s']) || !empty($settings['post_like_s']), 'favorite_enabled' => !isset($settings['post_favorite_enabled']) || !empty($settings['post_favorite_enabled']), // Canonical + legacy share. 'share_enabled' => $share_enabled, @@ -269,12 +292,12 @@ class PostDisplay { 'yiyan_header' => !empty($settings['post_yiyan_header']), 'yiyan_footer' => !empty($settings['post_yiyan_footer']), 'yiyan_box' => !empty($settings['post_yiyan_box']), - 'default_views_min' => max(0, min(9999, (int) ($settings['post_default_views_min'] ?? 20))), - 'default_views_max' => max(1, min(9999, (int) ($settings['post_default_views_max'] ?? 50))), - 'default_likes_min' => max(0, min(9999, (int) ($settings['post_default_likes_min'] ?? 5))), - 'default_likes_max' => max(1, min(9999, (int) ($settings['post_default_likes_max'] ?? 15))), - 'print_enabled' => !isset($settings['feature_print']) || !empty($settings['feature_print']), - 'pdf_export_enabled' => !empty($settings['feature_pdf_export']), + 'default_views_min' => max(0, min(9999, (int) ($views_range['min'] ?? 20))), + 'default_views_max' => max(1, min(9999, (int) ($views_range['max'] ?? 50))), + 'default_likes_min' => max(0, min(9999, (int) ($like_range['min'] ?? 5))), + 'default_likes_max' => max(1, min(9999, (int) ($like_range['max'] ?? 15))), + 'article_nav' => !isset($settings['article_nav']) || !empty($settings['article_nav']), + 'article_nav_mobile' => !isset($settings['article_nav_mobile_nav_s']) || !empty($settings['article_nav_mobile_nav_s']), ]; } diff --git a/tests/admin/feature-settings-reference-contract.test.cjs b/tests/admin/feature-settings-reference-contract.test.cjs new file mode 100644 index 00000000..c0339bfc --- /dev/null +++ b/tests/admin/feature-settings-reference-contract.test.cjs @@ -0,0 +1,232 @@ +'use strict'; + +/** + * 「文章功能」后台设置页契约(对齐参考主题 1:1): + * 1) PHP schema(SchemaPostList.php feature section)字段键、顺序、默认值与 + * JS schema(assets/admin/src/schema/sections/post-list/feature.js)一致; + * 2) 参考主题字段全集存在:article_nav / article_nav_mobile_nav_s / + * imagelightbox / imagelightbox_type / imagelightbox_thumbs_s / + * imagelightbox_play_s / imagelightbox_zoom_s / imagelightbox_full_s / + * imagelightbox_down_s / post_like_s / post_default_mate(views/like); + * 3) 旧 AKSRT 私有/异名字段(feature_print / feature_pdf_export / + * post_lightbox* / post_like_enabled / post_default_*_min/max)已彻底移除, + * 文章页面(page)不再承载这些字段,无双路兼容; + * 4) 运行时(PostDisplay::get_post_page_settings)只读取新键,灯箱复选框 + * pc_s/m_s 归并为开关,目录树与预置参数均接入运行时。 + */ + +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const test = require('node:test'); + +const root = path.resolve(__dirname, '..', '..'); +const php = fs.readFileSync(path.join(root, 'src/Admin/Schema/SchemaPostList.php'), 'utf8'); +const js = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/feature.js'), 'utf8'); +const pageJs = fs.readFileSync(path.join(root, 'assets/admin/src/schema/sections/post-list/page.js'), 'utf8'); +const runtime = fs.readFileSync(path.join(root, 'src/Settings/PostDisplay.php'), 'utf8'); +const scripts1c = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1c.php'), 'utf8'); +const scripts1b = fs.readFileSync(path.join(root, 'src/Core/ScriptsPart1b.php'), 'utf8'); +const frontendTags = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-frontend-tags.php'), 'utf8'); +const coreHooks = fs.readFileSync(path.join(root, 'inc/core-hooks.php'), 'utf8'); +const singleRender = fs.readFileSync(path.join(root, 'inc/helpers/aksrt-single-render.php'), 'utf8'); +const mobileHeader = fs.readFileSync(path.join(root, 'inc/helpers/header/aksrt-header-mobile.php'), 'utf8'); + +const REFERENCE_KEYS = [ + 'article_nav', + 'article_nav_mobile_nav_s', + 'imagelightbox', + 'imagelightbox_type', + 'imagelightbox_thumbs_s', + 'imagelightbox_play_s', + 'imagelightbox_zoom_s', + 'imagelightbox_full_s', + 'imagelightbox_down_s', + 'post_like_s', + 'post_default_mate', +]; + +// 旧 AKSRT 私有 / 异名字段(打印与 PDF 导出、旧灯箱六开关、旧点赞与默认数值四键)。 +const LEGACY_KEYS = [ + 'feature_print', + 'feature_pdf_export', + 'post_lightbox', + 'post_lightbox_thumbs', + 'post_lightbox_zoom', + 'post_lightbox_full', + 'post_lightbox_down', + 'post_lightbox_play', + 'post_like_enabled', + 'post_default_views_min', + 'post_default_views_max', + 'post_default_likes_min', + 'post_default_likes_max', +]; + +/** Extract a PHP section body by its section key (balanced brackets). */ +function phpSection(src, sectionKey) { + const start = src.indexOf(`'${sectionKey}' => [`); + assert.notEqual(start, -1, `PHP schema has a ${sectionKey} section`); + const open = src.indexOf('[', start); + let depth = 0; + for (let i = open; i < src.length; i++) { + const ch = src[i]; + if (ch === "'" || ch === '"') { + const quote = ch; + i++; + while (i < src.length) { + if (src[i] === '\\') { i += 2; continue; } + if (src[i] === quote) break; + i++; + } + continue; + } + if (ch === '[') depth++; + else if (ch === ']') { + depth--; + if (depth === 0) return src.slice(open, i + 1); + } + } + throw new Error(`unbalanced ${sectionKey} section`); +} + +const featurePhp = phpSection(php, 'feature'); +const pagePhp = phpSection(php, 'page'); + +test('PHP feature section 字段键与参考主题一致且顺序相同', () => { + // 字段定义均为 ['key' => '...', 'label' => ...] 开头;dependsOn 内的 + // ['key' => '...', 'value' => ...] 不是字段定义,须排除。 + const found = []; + const re = /'key'\s*=>\s*'([a-z0-9_]+)',\s*'label'/g; + let m; + while ((m = re.exec(featurePhp)) !== null) found.push(m[1]); + // post_default_mate 的 fieldset 父字段先于子字段 views / like 出现。 + const expected = [...REFERENCE_KEYS.slice(0, REFERENCE_KEYS.indexOf('post_default_mate')), 'post_default_mate', 'views', 'like']; + assert.deepEqual(found, expected); +}); + +test('PHP feature section 不含旧私有/异名字段', () => { + for (const legacy of LEGACY_KEYS) { + assert.ok(!featurePhp.includes(`'${legacy}'`), `feature section 残留旧字段: ${legacy}`); + } +}); + +test('PHP feature section 默认值对齐参考主题', () => { + assert.match(featurePhp, /'article_nav',\s*'label'\s*=>\s*'文章目录树',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); + assert.match(featurePhp, /'article_nav_mobile_nav_s',\s*'label'\s*=>\s*'在移动端弹出菜单内显示',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); + assert.match(featurePhp, /'imagelightbox',\s*'label'\s*=>\s*'图片灯箱',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); + assert.match(featurePhp, /'imagelightbox_type',\s*'label'\s*=>\s*'图片灯箱模式',\s*'type'\s*=>\s*'radio',\s*'default'\s*=>\s*'group'/); + assert.match(featurePhp, /'imagelightbox_thumbs_s',\s*'label'\s*=>\s*'缩略图导航',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s',\s*'m_s'\]/); + assert.match(featurePhp, /'imagelightbox_play_s',\s*'label'\s*=>\s*'播放按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s',\s*'m_s'\]/); + assert.match(featurePhp, /'imagelightbox_zoom_s',\s*'label'\s*=>\s*'缩放按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/); + assert.match(featurePhp, /'imagelightbox_full_s',\s*'label'\s*=>\s*'全屏按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/); + assert.match(featurePhp, /'imagelightbox_down_s',\s*'label'\s*=>\s*'下载按钮',\s*'type'\s*=>\s*'checkbox',\s*'default'\s*=>\s*\['pc_s'\]/); + assert.match(featurePhp, /'post_like_s',\s*'label'\s*=>\s*'文章点赞',\s*'type'\s*=>\s*'switcher',\s*'default'\s*=>\s*true/); + assert.match(featurePhp, /'post_default_mate',\s*'label'\s*=>\s*'文章预置参数',\s*'type'\s*=>\s*'fieldset'/); + assert.match(featurePhp, /'views',\s*'label'\s*=>\s*'阅读量',\s*'type'\s*=>\s*'between_number',\s*'default'\s*=>\s*\['min'\s*=>\s*20,\s*'max'\s*=>\s*50\]/); + assert.match(featurePhp, /'like',\s*'label'\s*=>\s*'点赞数',\s*'type'\s*=>\s*'between_number',\s*'default'\s*=>\s*\['min'\s*=>\s*5,\s*'max'\s*=>\s*15\]/); +}); + +test('PHP feature section 选项与依赖对齐参考主题', () => { + // 灯箱模式两种选项。 + assert.ok(featurePhp.includes("'组合模式(将文章的全部图片组合成一组灯箱)'"), 'imagelightbox_type 缺少组合模式'); + assert.ok(featurePhp.includes("'单张模式(点击某张图片仅显示该图片)'"), 'imagelightbox_type 缺少单张模式'); + // 每个按钮复选框均带 PC/移动端两项。 + assert.equal((featurePhp.match(/'PC端开启'/g) || []).length, 5, '五个灯箱按钮复选框均应含 PC端开启'); + assert.equal((featurePhp.match(/'移动端开启'/g) || []).length, 5, '五个灯箱按钮复选框均应含 移动端开启'); + // 依赖:移动端菜单 / 灯箱模式 / 缩略图+播放(组合模式)/ 缩放+全屏+下载。 + assert.match(featurePhp, /'article_nav_mobile_nav_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'article_nav',\s*'value'\s*=>\s*true\]/); + assert.match(featurePhp, /'imagelightbox_type'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/); + assert.match(featurePhp, /'imagelightbox_thumbs_s'[\s\S]*?'dependsOn'\s*=>\s*\['all'/); + assert.match(featurePhp, /'imagelightbox_play_s'[\s\S]*?'dependsOn'\s*=>\s*\['all'/); + assert.match(featurePhp, /'imagelightbox_zoom_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/); + assert.match(featurePhp, /'imagelightbox_full_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/); + assert.match(featurePhp, /'imagelightbox_down_s'[\s\S]*?'dependsOn'\s*=>\s*\['key'\s*=>\s*'imagelightbox',\s*'value'\s*=>\s*true\]/); +}); + +test('PHP page section 不再承载迁移字段(无双路)', () => { + for (const legacy of LEGACY_KEYS) { + assert.ok(!pagePhp.includes(`'${legacy}'`), `page section 残留迁移字段: ${legacy}`); + } +}); + +test('JS schema 与 PHP 字段键一致(ui_only 教程行除外)', () => { + const keys = []; + const re = /key:\s*'([a-z0-9_]+)',\s*label:/g; + let m; + while ((m = re.exec(js)) !== null) keys.push(m[1]); + const persisted = keys.filter((k) => REFERENCE_KEYS.includes(k)); + assert.deepEqual(persisted, REFERENCE_KEYS); + for (const legacy of LEGACY_KEYS) { + assert.ok(!keys.includes(legacy), `JS schema 残留旧字段: ${legacy}`); + } + // 参考主题 desc 中的官网教程链接以 ui_only content 行承载。 + assert.match(js, /ui_only:\s*true/); + assert.match(js, /https:\/\/www\.zibll\.com\/1717\.html/); + assert.match(js, /https:\/\/www\.zibll\.com\/683\.html/); +}); + +test('JS schema 默认值与选项对齐参考主题', () => { + assert.match(js, /article_nav:\s*true/); + assert.match(js, /article_nav_mobile_nav_s:\s*true/); + assert.match(js, /imagelightbox:\s*true/); + assert.match(js, /imagelightbox_type:\s*'group'/); + assert.match(js, /imagelightbox_thumbs_s:\s*\['pc_s',\s*'m_s'\]/); + assert.match(js, /imagelightbox_play_s:\s*\['pc_s',\s*'m_s'\]/); + assert.match(js, /imagelightbox_zoom_s:\s*\['pc_s'\]/); + assert.match(js, /imagelightbox_full_s:\s*\['pc_s'\]/); + assert.match(js, /imagelightbox_down_s:\s*\['pc_s'\]/); + assert.match(js, /post_like_s:\s*true/); + assert.match(js, /post_default_mate:\s*\{\s*views:\s*\{\s*min:\s*20,\s*max:\s*50\s*\},\s*like:\s*\{\s*min:\s*5,\s*max:\s*15\s*\}\s*\}/); + assert.match(js, /dependsOn:\s*\{\s*key:\s*'article_nav',\s*value:\s*true\s*\}/); + assert.match(js, /dependsOn:\s*\{\s*all:\s*\[\{ key: 'imagelightbox', value: true \},\s*\{ key: 'imagelightbox_type', value: 'group' \}\]\s*\}/); +}); + +test('JS page schema 不再承载迁移字段(无双路)', () => { + for (const legacy of LEGACY_KEYS) { + assert.ok(!pageJs.includes(`'${legacy}'`), `page.js 残留迁移字段: ${legacy}`); + } +}); + +test('旧字段在 schema 与运行时中均已移除(无双路)', () => { + for (const legacy of LEGACY_KEYS) { + assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`); + assert.ok(!runtime.includes(legacy), `PostDisplay 残留旧字段: ${legacy}`); + assert.ok(!js.includes(legacy), `feature.js 残留旧字段: ${legacy}`); + assert.ok(!pageJs.includes(legacy), `page.js 残留旧字段: ${legacy}`); + assert.ok(!frontendTags.includes(legacy), `aksrt-frontend-tags 残留旧字段: ${legacy}`); + } + assert.ok(!scripts1b.includes('needs_print'), 'ScriptsPart1b 残留 needs_print 分支'); + assert.ok(!scripts1c.includes('needs_print'), 'ScriptsPart1c 残留 needs_print 需求'); + assert.ok(!scripts1c.includes("'imgbox_type' => 'group'"), 'imgbox_type 仍为硬编码'); +}); + +test('运行时只读取新键并接入灯箱/目录树/预置参数', () => { + // 灯箱六项 + 模式。 + assert.match(runtime, /\$settings\['imagelightbox'\]/); + assert.match(runtime, /\$settings\['imagelightbox_type'\]/); + assert.match(runtime, /\$settings\['imagelightbox_thumbs_s'\]/); + assert.match(runtime, /\$settings\['imagelightbox_play_s'\]/); + assert.match(runtime, /\$settings\['imagelightbox_zoom_s'\]/); + assert.match(runtime, /\$settings\['imagelightbox_full_s'\]/); + assert.match(runtime, /\$settings\['imagelightbox_down_s'\]/); + assert.match(runtime, /'lightbox_type'\s*=>\s*\$lightbox_type/); + // 复选框 pc_s/m_s 归并。 + assert.match(runtime, /!empty\(\$value\['pc_s'\]\) \|\| !empty\(\$value\['m_s'\]\)/); + // 点赞 / 预置参数 / 目录树。 + assert.match(runtime, /\$settings\['post_like_s'\]/); + assert.match(runtime, /\$settings\['post_default_mate'\]/); + assert.match(runtime, /\$settings\['article_nav'\]/); + assert.match(runtime, /\$settings\['article_nav_mobile_nav_s'\]/); + // JS 配置透传灯箱模式。 + assert.match(scripts1c, /'imgbox_type'\s*=>\s*\$lightbox_on \? \$lightbox_type : 'group'/); + // 目录树门禁:内容壳 data-nav / no-nav,移动端菜单盒。 + assert.match(singleRender, /data-nav="posts"/); + assert.match(singleRender, /no-nav/); + assert.match(singleRender, /no_article-navs/); + assert.match(mobileHeader, /article_nav_mobile/); + // 预置参数:新建文章随机填入。 + assert.match(coreHooks, /save_post/); + assert.match(coreHooks, /POST_VIEW_COUNT_KEY/); + assert.match(coreHooks, /_aksrt_like_count/); +}); diff --git a/tools/admin-contract-report.json b/tools/admin-contract-report.json index a7f8d0f8..324aec70 100644 --- a/tools/admin-contract-report.json +++ b/tools/admin-contract-report.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-09-24T05:35:38+00:00", + "generated_at": "2026-09-24T06:24:30+00:00", "errors": [], "warnings": [ "customPage tools-update section 'tools/update' not in PHP schema (ok if pure operational page)",