feat(topics): restore topic taxonomy workflow
This commit is contained in:
1 parent
f3dd1ec3b1
commit
a254cbd3e9
59 files changed
+771
-192
No files matched your search
@@ -60,6 +60,15 @@ export const pageDisplaySection = {
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'tag',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-topics',
|
||||
name: 'PageDisplayTopics',
|
||||
label: '专题页面',
|
||||
route: '/page-display/topics',
|
||||
title: '专题页面',
|
||||
canonicalDomain: 'page_display',
|
||||
section: 'topics',
|
||||
}),
|
||||
schemaPage({
|
||||
key: 'page-display-user-home',
|
||||
name: 'PageDisplayUserHome',
|
||||
|
||||
@@ -63,12 +63,13 @@ export const searchSchema = {
|
||||
{ key: 'search_custom_key_s', label: '自定义匹配范围', type: 'switch', default: true, help: '启用后可配置搜索匹配字段范围' },
|
||||
{ key: 'search_custom_key', label: '匹配字段', type: 'checkbox', default: ['title', 'content', 'excerpt', 'category', 'post_tag'], dependsOn: { key: 'search_custom_key_s', value: true }, options: [
|
||||
{ label: '标题', value: 'title' }, { label: '内容', value: 'content' }, { label: '简介', value: 'excerpt' },
|
||||
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' },
|
||||
{ label: '分类', value: 'category' }, { label: '标签', value: 'post_tag' }, { label: '专题', value: 'topics' },
|
||||
{ label: '评论', value: 'coment' }, { label: '作者昵称', value: 'user' },
|
||||
] },
|
||||
{ key: 'search_facets', label: '搜索筛选', type: 'fieldset', sanitizeType: 'array', default: {}, dependsOn: { key: 'search_ms_s', value: false }, help: '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', fields: [
|
||||
{ key: 'category', label: '[文章] 分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [] },
|
||||
{ key: 'post_tag', label: '[文章] 标签', type: 'taxonomy_select', taxonomy: 'post_tag', multiple: true, default: [] },
|
||||
{ key: 'topics', label: '[文章] 专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [] },
|
||||
] },
|
||||
],
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ export const filterSchema = {
|
||||
key: 'filter_intro',
|
||||
type: 'content',
|
||||
ui_only: true,
|
||||
content: '<p class="jio-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="jio-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、标签等系统已存在的类型</span></p>',
|
||||
content: '<p class="jio-hint"><b>文章高级筛选功能:</b>通过此功能可实现更加复杂、精细化的内容筛选<br/>添加筛选类型和内容值之后,编写文章时候,可以进行选择,且在前台也可以实现精准筛选<br/>例如影视网站:[类型]有[剧情/喜剧/悬疑/惊悚/犯罪],[地区]有[大陆/美国/日韩/港台/印度],[年份]有[2022/2021/2020/10年代/00年代]等<br/><span class="jio-warn">注意事项:添加类型key时候,只能使用英文加下划线,不能有空格,且尽量复杂一点(不能为tag,category,topics等系统已存在的类型),避免与其他mate_key重复!类型名称不能为分类、专题、标签等系统已存在的类型</span></p>',
|
||||
},
|
||||
{
|
||||
key: 'custom_filter_show',
|
||||
@@ -28,6 +28,7 @@ export const filterSchema = {
|
||||
options: [
|
||||
{ label: '分类页面显示', value: 'cat' },
|
||||
{ label: '标签页面显示', value: 'tag' },
|
||||
{ label: '专题页面显示', value: 'topics' },
|
||||
],
|
||||
description: '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选',
|
||||
},
|
||||
@@ -87,6 +88,18 @@ export const filterSchema = {
|
||||
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false, description: '用下拉框显示更多内容' },
|
||||
],
|
||||
},
|
||||
{ key: 'topics_s', label: '专题筛选', type: 'switch', default: false, description: '在所选分类页显示的专题筛选列表' },
|
||||
{
|
||||
key: 'topics_lists',
|
||||
label: ' ',
|
||||
type: 'fieldset',
|
||||
default: { lists: [], dropdown: false },
|
||||
dependsOn: { key: 'topics_s', value: true },
|
||||
fields: [
|
||||
{ key: 'lists', label: '按钮列表', type: 'async_select', source: 'topics', multiple: true, sortable: true, default: [] },
|
||||
{ key: 'dropdown', label: '下拉列表', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{ key: 'orderby_s', label: '排序方式', type: 'switch', default: true, description: '在所选分类页显示排序方式按钮' },
|
||||
{
|
||||
key: 'orderby_lists',
|
||||
|
||||
@@ -13,11 +13,12 @@ export const listSchema = {
|
||||
post_list_author: true,
|
||||
post_list_time: true,
|
||||
list_meta_show: 'like',
|
||||
list_badge_show: ['pay', 'cat', 'tag'],
|
||||
list_badge_show: ['pay', 'tag', 'topics', 'cat'],
|
||||
list_show_type: 'separate',
|
||||
list_type: 'thumb',
|
||||
list_card_home: false,
|
||||
list_card_tag: false,
|
||||
list_card_topics: false,
|
||||
list_card_author: false,
|
||||
list_card_cat: [],
|
||||
list_list_option: { style: 'null', img_position: 'left', scale: 70 },
|
||||
@@ -39,7 +40,7 @@ export const listSchema = {
|
||||
{ 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' },
|
||||
{ label: '首页', value: 'home' }, { label: '分类页', value: 'cat' }, { label: '标签页', value: 'tag' }, { label: '专题页', value: 'topics' },
|
||||
] },
|
||||
],
|
||||
},
|
||||
@@ -52,8 +53,8 @@ export const listSchema = {
|
||||
{ key: 'list_meta_show', label: '列表小部件(移动端优先)', type: 'radio', default: 'like', options: [
|
||||
{ label: '阅读量', value: 'view' }, { label: '点赞数', value: 'like' }, { label: '评论', value: 'comm' },
|
||||
] },
|
||||
{ key: 'list_badge_show', label: '显示列表标签', type: 'checkbox', default: ['pay', 'cat', 'tag'], options: [
|
||||
{ label: '付费信息', value: 'pay' }, { label: '分类', value: 'cat' }, { label: '标签', value: 'tag' },
|
||||
{ key: 'list_badge_show', label: '显示列表标签', type: 'checkbox', default: ['pay', 'tag', 'topics', 'cat'], options: [
|
||||
{ label: '付费信息', value: 'pay' }, { label: '分类', value: 'cat' }, { label: '专题', value: 'topics' }, { label: '标签', value: 'tag' },
|
||||
] },
|
||||
],
|
||||
},
|
||||
@@ -70,8 +71,9 @@ export const listSchema = {
|
||||
], 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_topics', 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_card_cat', label: '分类、专题页自定义卡片模式', type: 'taxonomy_select', taxonomy: 'category,topics', 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' },
|
||||
|
||||
@@ -7,6 +7,8 @@ export const submitSchema = {
|
||||
post_article_s: true,
|
||||
post_article_btn_txte: '发布文章',
|
||||
post_article_cat: [],
|
||||
post_article_topics_s: false,
|
||||
post_article_topics: [],
|
||||
post_article_custom_filter_s: true,
|
||||
post_article_tag: true,
|
||||
post_article_user: 1,
|
||||
@@ -34,6 +36,8 @@ export const submitSchema = {
|
||||
},
|
||||
{ key: 'post_article_btn_txte', label: '按钮文字', type: 'input', default: '发布文章', dependsOn: { key: 'post_article_s', value: true }, description: '投稿按钮的显示的文字,推荐4个字更加协调' },
|
||||
{ key: 'post_article_cat', label: '投稿允许选择的分类', type: 'taxonomy_select', taxonomy: 'category', multiple: true, default: [], placeholder: '输入关键词以搜索分类', dependsOn: { key: 'post_article_s', value: true }, description: '投稿允许选择的分类,为空则允许选择全部分类,所选分类下如果有子分类则会自动显示,如果选择了上级分类,则不要再选该分类下的子分类。注意:请确保所选分类包含子分类总数不要超过50个,否则可能会引起页面崩溃' },
|
||||
{ key: 'post_article_topics_s', label: '允许选择专题', type: 'switch', default: false, dependsOn: { key: 'post_article_s', value: true } },
|
||||
{ key: 'post_article_topics', label: '投稿允许选择的专题', type: 'taxonomy_select', taxonomy: 'topics', multiple: true, default: [], dependsOn: { all: [{ key: 'post_article_s', value: true }, { key: 'post_article_topics_s', value: true }] } },
|
||||
{ key: 'post_article_custom_filter_s', label: '允许选择高级筛选', type: 'switch', default: true, dependsOn: { key: 'post_article_s', value: true } },
|
||||
{
|
||||
key: 'submit_filter_notice',
|
||||
|
||||
@@ -19,9 +19,10 @@ type Props = {
|
||||
options: CategoryOption[];
|
||||
value: number[];
|
||||
onChange: (ids: number[]) => void;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export function CategorySelect({ options, value, onChange }: Props) {
|
||||
export function CategorySelect({ options, value, onChange, label = __('分类', 'jio') }: Props) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const { triggerRef, panelRef, position } = useAnchoredPanel<HTMLDivElement>({
|
||||
@@ -71,16 +72,16 @@ export function CategorySelect({ options, value, onChange }: Props) {
|
||||
<input
|
||||
type="search"
|
||||
value={query}
|
||||
placeholder={__('搜索分类', 'jio')}
|
||||
aria-label={__('搜索分类', 'jio')}
|
||||
placeholder={sprintf(__('搜索%s', 'jio'), label)}
|
||||
aria-label={sprintf(__('搜索%s', 'jio'), label)}
|
||||
// 打开即可输入:分类列表通常很长,键盘操作比翻找更快。
|
||||
autoFocus
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="jio-creator-picker__list" aria-label={__('分类', 'jio')}>
|
||||
<div className="jio-creator-picker__list" aria-label={label}>
|
||||
{filtered.length === 0 && (
|
||||
<p className="jio-creator-picker__empty">{__('没有匹配的分类', 'jio')}</p>
|
||||
<p className="jio-creator-picker__empty">{sprintf(__('没有匹配的%s', 'jio'), label)}</p>
|
||||
)}
|
||||
{filtered.map((option) => {
|
||||
const active = selected.has(option.id);
|
||||
@@ -133,7 +134,7 @@ export function CategorySelect({ options, value, onChange }: Props) {
|
||||
>
|
||||
<span className="jio-creator-select__tokens">
|
||||
{chosen.length === 0 ? (
|
||||
<span className="jio-creator-select__placeholder">{__('选择分类(可多选)', 'jio')}</span>
|
||||
<span className="jio-creator-select__placeholder">{sprintf(__('选择%s(可多选)', 'jio'), label)}</span>
|
||||
) : (
|
||||
chosen.map((option) => (
|
||||
<span key={option.id} className="jio-creator-select__token">
|
||||
@@ -143,7 +144,7 @@ export function CategorySelect({ options, value, onChange }: Props) {
|
||||
<button
|
||||
type="button"
|
||||
className="jio-creator-select__token-x"
|
||||
aria-label={sprintf(__('移除分类 %s', 'jio'), option.name)}
|
||||
aria-label={sprintf(__('移除%s %s', 'jio'), label, option.name)}
|
||||
onClick={(event) => {
|
||||
// 令牌上的叉号只移除这一项,不要顺带展开面板。
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -19,6 +19,7 @@ type Featured = { type: string; data: Record<string, any>; mediaId?: number };
|
||||
|
||||
export type TaxonomyDef = {
|
||||
categories: CategoryOption[];
|
||||
topics: { enabled: boolean; items: CategoryOption[] };
|
||||
/** 标签是否开放;开放时附带站点已有标签,用于输入建议。 */
|
||||
tags: { enabled: boolean; suggestions: string[] };
|
||||
customDefs: Array<{ key: string; name: string; values: Array<{ key: string; name: string }> }>;
|
||||
@@ -34,12 +35,14 @@ type Props = {
|
||||
canPay: boolean;
|
||||
taxonomy: TaxonomyDef;
|
||||
categories: number[];
|
||||
topics: number[];
|
||||
tags: string[];
|
||||
custom: Record<string, string[]>;
|
||||
onFeaturedChange: (featured: Featured) => void;
|
||||
onScheduleChange: (enabled: boolean, at: string) => void;
|
||||
onPayEnabledChange: (enabled: boolean) => void;
|
||||
onCategoriesChange: (categories: number[]) => void;
|
||||
onTopicsChange: (topics: number[]) => void;
|
||||
onTagsChange: (tags: string[]) => void;
|
||||
onCustomChange: (custom: Record<string, string[]>) => void;
|
||||
/** 打开发布抽屉并直接定位到付费配置。 */
|
||||
@@ -69,12 +72,14 @@ export function PublishSettings({
|
||||
canPay,
|
||||
taxonomy,
|
||||
categories,
|
||||
topics,
|
||||
tags,
|
||||
custom,
|
||||
onFeaturedChange,
|
||||
onScheduleChange,
|
||||
onPayEnabledChange,
|
||||
onCategoriesChange,
|
||||
onTopicsChange,
|
||||
onTagsChange,
|
||||
onCustomChange,
|
||||
onOpenPaySettings,
|
||||
@@ -141,6 +146,13 @@ export function PublishSettings({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{taxonomy.topics.enabled && taxonomy.topics.items.length > 0 && (
|
||||
<div className="jio-creator__setting-row jio-creator__setting-row--stack">
|
||||
<div className="jio-creator__setting-copy"><span className="jio-creator__setting-label">{__('专题', 'jio')}</span></div>
|
||||
<CategorySelect options={taxonomy.topics.items} value={topics} onChange={onTopicsChange} label={__('专题', 'jio')} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{taxonomy.tags.enabled && (
|
||||
<div className="jio-creator__setting-row jio-creator__setting-row--stack">
|
||||
<div className="jio-creator__setting-copy">
|
||||
|
||||
@@ -57,6 +57,7 @@ export type Draft = {
|
||||
content: string;
|
||||
schedule: Schedule;
|
||||
categories: number[];
|
||||
topics: number[];
|
||||
tags: string[];
|
||||
custom: Record<string, string[]>;
|
||||
featured: Featured;
|
||||
@@ -66,6 +67,7 @@ export type Draft = {
|
||||
export function initialDraft(bootstrap: Record<string, any>): Draft {
|
||||
const pay = bootstrap.pay || {};
|
||||
const selected = (bootstrap.categories && bootstrap.categories.selected) || [];
|
||||
const selectedTopics = (bootstrap.topics && bootstrap.topics.selected) || [];
|
||||
const tags = (bootstrap.tags && bootstrap.tags.items) || [];
|
||||
return {
|
||||
postId: Number(bootstrap.sourceId || 0),
|
||||
@@ -75,6 +77,7 @@ export function initialDraft(bootstrap: Record<string, any>): Draft {
|
||||
content: String(bootstrap.content || ''),
|
||||
schedule: { enabled: false, at: '' },
|
||||
categories: selected.map(Number),
|
||||
topics: selectedTopics.map(Number),
|
||||
tags: tags.slice(),
|
||||
custom: bootstrap.custom || {},
|
||||
featured: bootstrap.featured || { type: '', data: {} },
|
||||
|
||||
@@ -207,6 +207,7 @@ function Studio(): JSX.Element {
|
||||
title: current.title,
|
||||
content: current.content,
|
||||
categories: current.categories,
|
||||
topics: current.topics,
|
||||
tags: current.tags,
|
||||
custom: current.custom,
|
||||
featured: current.featured,
|
||||
@@ -859,6 +860,10 @@ function Studio(): JSX.Element {
|
||||
canPay={!!capabilities.canPay}
|
||||
taxonomy={{
|
||||
categories: (bootstrap.categories && bootstrap.categories.items) || [],
|
||||
topics: {
|
||||
enabled: !!bootstrap.topics?.enabled,
|
||||
items: (bootstrap.topics && bootstrap.topics.items) || [],
|
||||
},
|
||||
tags: {
|
||||
enabled: !!bootstrap.tags?.enabled,
|
||||
suggestions: (bootstrap.tags && bootstrap.tags.suggestions) || [],
|
||||
@@ -866,12 +871,14 @@ function Studio(): JSX.Element {
|
||||
customDefs: bootstrap.customDefs || [],
|
||||
}}
|
||||
categories={draft.categories}
|
||||
topics={draft.topics}
|
||||
tags={draft.tags}
|
||||
custom={draft.custom}
|
||||
onFeaturedChange={(featured) => patchDraft({ featured })}
|
||||
onScheduleChange={(enabled, at) => patchDraft({ schedule: { enabled, at } })}
|
||||
onPayEnabledChange={(enabled) => patchDraft({ pay: { ...draftRef.current.pay, enabled } })}
|
||||
onCategoriesChange={(categories) => patchDraft({ categories })}
|
||||
onTopicsChange={(topics) => patchDraft({ topics })}
|
||||
onTagsChange={(tags) => patchDraft({ tags })}
|
||||
onCustomChange={(custom) => patchDraft({ custom })}
|
||||
onOpenPaySettings={() => setDrawerOpen(true)}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
define('JIO_VERSION', '3.4.1-dev');
|
||||
define('JIO_VERSION', '3.4.2-dev');
|
||||
define('JIO_DIR', get_template_directory());
|
||||
define('JIO_URI', get_template_directory_uri());
|
||||
|
||||
|
||||
@@ -385,11 +385,6 @@ add_action('pre_get_posts', function ($query) {
|
||||
// 站内搜索:分类/标签名搜索(内容类型始终为 post)
|
||||
if ($query->is_search() && empty($query->get('post_type'))) {
|
||||
$search_type = sanitize_key((string) $query->get('type'));
|
||||
$trem = absint($query->get('trem'));
|
||||
if ($trem > 0) {
|
||||
$query->set('cat', $trem);
|
||||
}
|
||||
|
||||
if (in_array($search_type, ['category', 'tag'], true)) {
|
||||
$taxonomy = $search_type === 'category' ? 'category' : 'post_tag';
|
||||
$term_ids = get_terms([
|
||||
|
||||
@@ -80,6 +80,9 @@ if (!function_exists('jio_header_slide_current_page_type')) {
|
||||
if (is_tag()) {
|
||||
return 'tag';
|
||||
}
|
||||
if (function_exists('is_tax') && is_tax('topics')) {
|
||||
return 'topics';
|
||||
}
|
||||
if (is_singular('post')) {
|
||||
return 'single';
|
||||
}
|
||||
|
||||
@@ -122,7 +122,9 @@ if (!function_exists('jio_homepage_get_tab_panes_html')) {
|
||||
|
||||
$html = '';
|
||||
foreach ($tab_configs as $index => $tab_cfg) {
|
||||
$config = jio_archive_get_render_config(['context' => 'category', 'term_id' => (int) $tab_cfg['term_id']]);
|
||||
$term = $tab_cfg['term'] ?? null;
|
||||
$context = $term instanceof WP_Term && $term->taxonomy === 'topics' ? 'topics' : 'category';
|
||||
$config = jio_archive_get_render_config(['context' => $context, 'term_id' => (int) $tab_cfg['term_id']]);
|
||||
$is_card = ($config['mode'] ?? 'list') === 'card';
|
||||
$placeholder = jio_render_placeholder($is_card ? 'card' : 'list', $is_card ? (wp_is_mobile() ? 4 : 12) : (wp_is_mobile() ? 4 : 6));
|
||||
$html .= '<div class="ajaxpager tab-pane fade" id="index-tab-' . (int) $index . '">';
|
||||
|
||||
@@ -21,8 +21,10 @@ if (!function_exists('jio_archive_render_term_header')) :
|
||||
}
|
||||
$term = get_queried_object();
|
||||
$is_tag = $term instanceof WP_Term && $term->taxonomy === 'post_tag';
|
||||
$is_topic = $term instanceof WP_Term && $term->taxonomy === 'topics';
|
||||
$paged = max(1, (int) get_query_var('paged', 1));
|
||||
$title = '<i class="fa fas ' . ($is_tag ? 'fa-tags' : 'fa-folder-open') . ' em12 mr10 ml6" aria-hidden="true"></i>' . esc_html((string) $args['title']);
|
||||
$icon = $is_topic ? 'fa-cube' : ($is_tag ? 'fa-tags' : 'fa-folder-open');
|
||||
$title = '<i class="fa fas ' . $icon . ' em12 mr10 ml6" aria-hidden="true"></i>' . esc_html((string) $args['title']);
|
||||
global $wp_query;
|
||||
if (!empty($args['post_count']) && $paged === 1) {
|
||||
$count = $wp_query instanceof WP_Query ? (int) $wp_query->found_posts : 0;
|
||||
@@ -34,7 +36,7 @@ if (!function_exists('jio_archive_render_term_header')) :
|
||||
$edit_url = !$is_tag && current_user_can('edit_term', (int) $term->term_id)
|
||||
? get_edit_term_link((int) $term->term_id, $term->taxonomy)
|
||||
: false;
|
||||
$edit_label = __('编辑此分类', 'jio');
|
||||
$edit_label = $is_topic ? __('编辑此专题', 'jio') : ($is_tag ? __('编辑此标签', 'jio') : __('编辑此分类', 'jio'));
|
||||
$edit_button = $edit_url
|
||||
? '<a class="admin-edit" href="' . esc_url($edit_url) . '" title="' . esc_attr($edit_label) . '"><i class="fa fas fa-edit" aria-hidden="true"></i><span class="screen-reader-text">' . esc_html($edit_label) . '</span></a>'
|
||||
: '';
|
||||
@@ -111,15 +113,15 @@ endif;
|
||||
|
||||
if (!function_exists('jio_archive_build_term_filter_group')) :
|
||||
/**
|
||||
* Build one ordered category/tag filter group from the page-display fieldset.
|
||||
* Build one ordered term filter group from the page-display fieldset.
|
||||
*
|
||||
* @param string $taxonomy category|post_tag.
|
||||
* @param string $taxonomy category|post_tag|topics.
|
||||
* @param array<string,mixed> $option lists/dropdown/dropdown_lists fieldset.
|
||||
* @param string $label Visible group label.
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
function jio_archive_build_term_filter_group(string $taxonomy, array $option, string $label): array {
|
||||
if (!in_array($taxonomy, ['category', 'post_tag'], true)) {
|
||||
if (!in_array($taxonomy, ['category', 'post_tag', 'topics'], true)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -169,7 +171,8 @@ if (!function_exists('jio_archive_build_term_filter_group')) :
|
||||
}
|
||||
}
|
||||
if ($child_ids) {
|
||||
$child_group = jio_archive_build_term_filter_group($taxonomy, ['lists' => array_unique($child_ids)], $taxonomy === 'category' ? __('子分类', 'jio') : __('子标签', 'jio'));
|
||||
$child_label = $taxonomy === 'topics' ? __('子专题', 'jio') : ($taxonomy === 'category' ? __('子分类', 'jio') : __('子标签', 'jio'));
|
||||
$child_group = jio_archive_build_term_filter_group($taxonomy, ['lists' => array_unique($child_ids)], $child_label);
|
||||
if ($child_group) {
|
||||
$group['children'][] = $child_group;
|
||||
}
|
||||
|
||||
@@ -37,6 +37,8 @@ if (!function_exists('jio_resolve_post_list_render_config')) :
|
||||
$context = 'category';
|
||||
} elseif (is_tag()) {
|
||||
$context = 'tag';
|
||||
} elseif (function_exists('is_tax') && is_tax('topics')) {
|
||||
$context = 'topics';
|
||||
} elseif (is_author()) {
|
||||
$context = 'author';
|
||||
} elseif (is_search()) {
|
||||
@@ -55,6 +57,7 @@ if (!function_exists('jio_resolve_post_list_render_config')) :
|
||||
'is_home' => $context === 'home',
|
||||
'is_category' => $context === 'category',
|
||||
'is_tag' => $context === 'tag',
|
||||
'is_topics' => $context === 'topics',
|
||||
'is_author' => $context === 'author',
|
||||
'is_search' => $context === 'search',
|
||||
];
|
||||
|
||||
@@ -48,7 +48,7 @@ if (!function_exists('jio_homepage_get_post_types')) {
|
||||
|
||||
if (!function_exists('jio_homepage_get_tab_configs')) {
|
||||
/**
|
||||
* Homepage category tabs with full config.
|
||||
* Homepage term tabs with full config.
|
||||
*
|
||||
* Returns enabled tabs in config order, each with 'term', 'title', 'orderby'.
|
||||
* Preserves every configured row in the saved order.
|
||||
@@ -76,7 +76,7 @@ if (!function_exists('jio_homepage_get_tab_configs')) {
|
||||
if (!$term || is_wp_error($term)) {
|
||||
continue;
|
||||
}
|
||||
if ($term->taxonomy !== 'category' || (int) $term->count === 0) {
|
||||
if (!in_array($term->taxonomy, ['category', 'topics'], true) || (int) $term->count === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ endif;
|
||||
|
||||
if (!function_exists('jio_get_single_article_tags_html')) :
|
||||
/**
|
||||
* Categories → tags.
|
||||
* Categories, topics and tags.
|
||||
*/
|
||||
function jio_get_single_article_tags_html(int $post_id): string {
|
||||
$cats = get_the_category($post_id) ?: [];
|
||||
@@ -230,6 +230,13 @@ if (!function_exists('jio_get_single_article_tags_html')) :
|
||||
'<i class="fa fas fa-folder-open" aria-hidden="true"></i> ',
|
||||
['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red']
|
||||
);
|
||||
$topics = get_the_terms($post_id, 'topics');
|
||||
$topic_html = is_array($topics) ? jio_get_colored_term_tags(
|
||||
$topics,
|
||||
'but ml6 radius',
|
||||
'<i class="fa fas fa-cube" aria-hidden="true"></i> ',
|
||||
['c-blue', 'c-yellow', 'c-green', 'c-purple', 'c-red']
|
||||
) : '';
|
||||
|
||||
$tags_html = '';
|
||||
$tags = get_the_tags($post_id) ?: [];
|
||||
@@ -246,7 +253,7 @@ if (!function_exists('jio_get_single_article_tags_html')) :
|
||||
}
|
||||
}
|
||||
|
||||
$top = $cat_html;
|
||||
$top = $cat_html . $topic_html;
|
||||
if ($top === '' && $tags_html === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -40,13 +40,16 @@ endif;
|
||||
|
||||
if (!function_exists('jio_widget_term_cover_url')) :
|
||||
/**
|
||||
* Category cover image URL, resolved through the theme's canonical chain:
|
||||
* Term cover image URL, resolved through the theme's canonical chain:
|
||||
* explicit term cover → latest thumbnailed post inside the term → optional
|
||||
* default placeholder. The reference hides term cards without a real cover
|
||||
* while the aggregation card always falls back to a default image, so the
|
||||
* last step is opt-in via $default_fallback.
|
||||
* default placeholder. The last step is opt-in via $default_fallback.
|
||||
*/
|
||||
function jio_widget_term_cover_url(int $term_id, bool $default_fallback = false): string {
|
||||
function jio_widget_term_cover_url(int $term_id, bool $default_fallback = false, string $taxonomy = 'category'): string {
|
||||
$taxonomy = $taxonomy === 'topics' ? 'topics' : 'category';
|
||||
$cover = class_exists('Jio\\Core\\TermMeta') ? \Jio\Core\TermMeta::get_meta_value($term_id, 'cover_url', '') : '';
|
||||
if (is_string($cover) && $cover !== '') {
|
||||
return esc_url_raw($cover);
|
||||
}
|
||||
$cover_id = (int) get_term_meta($term_id, 'jio_category_cover_id', true);
|
||||
if ($cover_id > 0) {
|
||||
$url = wp_get_attachment_url($cover_id);
|
||||
@@ -54,21 +57,29 @@ if (!function_exists('jio_widget_term_cover_url')) :
|
||||
return (string) $url;
|
||||
}
|
||||
}
|
||||
$posts = get_posts([
|
||||
'category__in' => [$term_id],
|
||||
$term_query = $taxonomy === 'topics'
|
||||
? ['tax_query' => [['taxonomy' => 'topics', 'field' => 'term_id', 'terms' => [$term_id]]]]
|
||||
: ['category__in' => [$term_id]];
|
||||
$posts = get_posts(array_merge($term_query, [
|
||||
'posts_per_page' => 1,
|
||||
'post_status' => 'publish',
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'meta_query' => [['key' => '_thumbnail_id', 'compare' => 'EXISTS']],
|
||||
'fields' => 'ids',
|
||||
]);
|
||||
]));
|
||||
if (!empty($posts)) {
|
||||
$url = get_the_post_thumbnail_url((int) $posts[0], 'medium_large');
|
||||
if ($url) {
|
||||
return (string) $url;
|
||||
}
|
||||
}
|
||||
if ($default_fallback && $taxonomy === 'topics' && class_exists('Jio\\Settings\\Settings')) {
|
||||
$settings = \Jio\Settings\Settings::get_topics_page_settings();
|
||||
if (!empty($settings['default_cover'])) {
|
||||
return esc_url_raw((string) $settings['default_cover']);
|
||||
}
|
||||
}
|
||||
return $default_fallback ? get_template_directory_uri() . '/assets/img/placeholder.svg' : '';
|
||||
}
|
||||
endif;
|
||||
@@ -190,20 +201,22 @@ endif;
|
||||
|
||||
if (!function_exists('jio_widget_term_aggregation')) :
|
||||
/**
|
||||
* Category aggregation card: cover + description + latest posts list.
|
||||
* Term aggregation card: cover + description + latest posts list.
|
||||
*
|
||||
* @param array<string,mixed> $args {term_id, target_blank, orderby, order, count}.
|
||||
* @param array<string,mixed> $args {term_id, taxonomy, target_blank, orderby, order, count}.
|
||||
*/
|
||||
function jio_widget_term_aggregation(array $args = []): string {
|
||||
$args = wp_parse_args($args, [
|
||||
'term_id' => 0,
|
||||
'taxonomy' => 'category',
|
||||
'target_blank' => false,
|
||||
'orderby' => 'modified',
|
||||
'order' => 'DESC',
|
||||
'count' => 4,
|
||||
]);
|
||||
$term_id = (int) $args['term_id'];
|
||||
$term = $term_id > 0 ? get_term($term_id, 'category') : null;
|
||||
$taxonomy = $args['taxonomy'] === 'topics' ? 'topics' : 'category';
|
||||
$term = $term_id > 0 ? get_term($term_id, $taxonomy) : null;
|
||||
if (!$term instanceof WP_Term || is_wp_error($term)) {
|
||||
return '';
|
||||
}
|
||||
@@ -214,11 +227,11 @@ if (!function_exists('jio_widget_term_aggregation')) :
|
||||
}
|
||||
$href = esc_url((string) $href);
|
||||
|
||||
$img = jio_widget_term_cover_url((int) $term->term_id, true);
|
||||
$img = jio_widget_term_cover_url((int) $term->term_id, true, $taxonomy);
|
||||
$img_html = jio_graphic_card([
|
||||
'class' => '',
|
||||
'img' => $img,
|
||||
'alt' => __('分类', 'jio') . '-' . $term->name,
|
||||
'alt' => ($taxonomy === 'topics' ? __('专题', 'jio') : __('分类', 'jio')) . '-' . $term->name,
|
||||
'link' => ['url' => $href, 'target' => !empty($args['target_blank']) ? '_blank' : ''],
|
||||
'height_scale' => 0,
|
||||
'mask_opacity' => 0,
|
||||
@@ -229,13 +242,16 @@ if (!function_exists('jio_widget_term_aggregation')) :
|
||||
$name = '<a class="em14 key-color text-ellipsis"' . $target . ' href="' . $href . '">' . esc_html($term->name) . '</a>';
|
||||
$description = wp_kses_post((string) $term->description);
|
||||
if ($description === '' && current_user_can('manage_categories')) {
|
||||
$description = esc_html__('请在WordPress后台-文章-文章分类中添加描述!', 'jio')
|
||||
. '<a href="' . esc_url(get_edit_term_link($term_id, 'category')) . '">' . esc_html__('立即编辑', 'jio') . '</a>';
|
||||
$description = $taxonomy === 'topics'
|
||||
? esc_html__('请在WordPress后台-文章-专题中添加描述!', 'jio')
|
||||
: esc_html__('请在WordPress后台-文章-文章分类中添加描述!', 'jio');
|
||||
$description .= '<a href="' . esc_url(get_edit_term_link($term_id, $taxonomy)) . '">' . esc_html__('立即编辑', 'jio') . '</a>';
|
||||
}
|
||||
$description = '<div class="text-ellipsis-2 muted-color">' . $description . '</div>';
|
||||
|
||||
$query_args = [
|
||||
'cat' => (string) $term->term_id,
|
||||
'topics' => $taxonomy === 'topics' ? (string) $term->term_id : '',
|
||||
'cat' => $taxonomy === 'category' ? (string) $term->term_id : '',
|
||||
'orderby' => (string) $args['orderby'],
|
||||
'order' => strtoupper((string) $args['order']) === 'ASC' ? 'ASC' : 'DESC',
|
||||
'count' => max(1, (int) $args['count']),
|
||||
|
||||
@@ -23,6 +23,7 @@ if (!function_exists('jio_renderer_defaults')) {
|
||||
'is_home' => is_home() || is_front_page(),
|
||||
'is_author' => is_author(),
|
||||
'is_tag' => is_tag(),
|
||||
'is_topics' => function_exists('is_tax') && is_tax('topics'),
|
||||
'highlight_query' => '',
|
||||
'wrapper_class' => '',
|
||||
'skip_wrapper' => false,
|
||||
@@ -56,6 +57,7 @@ if (!function_exists('jio_renderer_config')) {
|
||||
}
|
||||
if (!$is_forced && !$is_card) {
|
||||
$is_card = (!empty($args['is_tag']) && !empty($list['list_card_tag']))
|
||||
|| (!empty($args['is_topics']) && !empty($list['list_card_topics']))
|
||||
|| (!empty($args['is_home']) && !empty($list['list_card_home']))
|
||||
|| (!empty($args['is_author']) && !empty($list['list_card_author']));
|
||||
}
|
||||
@@ -83,7 +85,7 @@ if (!function_exists('jio_renderer_config')) {
|
||||
$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['badge_types'] = (array) ($list['list_badge_show'] ?? ['pay', 'cat', 'tag']);
|
||||
$args['badge_types'] = (array) ($list['list_badge_show'] ?? ['pay', 'topics', 'cat', 'tag']);
|
||||
$args['mult_thumb_cat'] = (array) ($list['mult_thumb_cat'] ?? []);
|
||||
$args['thumb'] = $thumb;
|
||||
$args['allow_conditional_layout'] = jio_is_show_sidebar() || wp_is_mobile();
|
||||
|
||||
@@ -17,6 +17,8 @@ if (!function_exists('jio_renderer_collect_post_data')) {
|
||||
|
||||
$images = jio_renderer_resolve_images($post_id, $cfg);
|
||||
$categories = get_the_category($post_id) ?: [];
|
||||
$topics = get_the_terms($post_id, 'topics');
|
||||
$topics = is_array($topics) ? $topics : [];
|
||||
$tags = get_the_tags($post_id) ?: [];
|
||||
$content_images = (array) $images['gallery'];
|
||||
$gallery = array_slice($content_images, 0, 4);
|
||||
@@ -84,6 +86,7 @@ if (!function_exists('jio_renderer_collect_post_data')) {
|
||||
'is_mult_thumb' => $is_mult_thumb,
|
||||
'is_no_thumb' => $is_no_thumb,
|
||||
'categories' => $categories,
|
||||
'topics' => $topics,
|
||||
'tags' => $tags,
|
||||
'author_id' => $author_id,
|
||||
'author_name' => $author instanceof WP_User ? $author->display_name : '',
|
||||
@@ -261,6 +264,15 @@ if (!function_exists('jio_renderer_badges')) {
|
||||
$items[] = '<a class="but ' . esc_attr($colors[$index % count($colors)]) . '" title="' . esc_attr__('查看更多分类文章', 'jio') . '" href="' . esc_url(get_category_link($category->term_id)) . '"><i class="fa fas fa-folder-open" aria-hidden="true"></i>' . esc_html($category->name) . '</a>';
|
||||
}
|
||||
}
|
||||
if (empty($cfg['is_topics']) && in_array('topics', $types, true)) {
|
||||
foreach (array_slice((array) ($data['topics'] ?? []), 0, 3) as $topic) {
|
||||
$link = get_term_link($topic);
|
||||
if (is_wp_error($link)) {
|
||||
continue;
|
||||
}
|
||||
$items[] = '<a class="but" title="' . esc_attr__('查看此专题更多文章', 'jio') . '" href="' . esc_url($link) . '"><i class="fa fas fa-cube" aria-hidden="true"></i>' . esc_html($topic->name) . '</a>';
|
||||
}
|
||||
}
|
||||
if (empty($cfg['is_tag']) && in_array('tag', $types, true)) {
|
||||
$tags = (array) $data['tags'];
|
||||
usort($tags, static fn($left, $right): int => (int) $right->count <=> (int) $left->count);
|
||||
|
||||
@@ -298,7 +298,7 @@ function jio_get_search_facets_data(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
$allowed = ['category', 'post_tag'];
|
||||
$allowed = ['category', 'post_tag', 'topics'];
|
||||
$data = [];
|
||||
foreach ($raw as $taxonomy => $ids) {
|
||||
$taxonomy = sanitize_key((string) $taxonomy);
|
||||
@@ -400,6 +400,7 @@ function jio_get_search_tab_nav_filter(string $type): string {
|
||||
$labels = [
|
||||
'category' => __('分类', 'jio'),
|
||||
'post_tag' => __('标签', 'jio'),
|
||||
'topics' => __('专题', 'jio'),
|
||||
];
|
||||
$current = isset($_REQUEST['trem']) ? absint(wp_unslash($_REQUEST['trem'])) : 0;
|
||||
foreach (jio_get_search_facets_data() as $taxonomy => $terms) {
|
||||
|
||||
@@ -28,6 +28,9 @@ function jio_resolve_sidebar_id(): string {
|
||||
if (is_category()) {
|
||||
return 'cat_sidebar';
|
||||
}
|
||||
if (function_exists('is_tax') && is_tax('topics')) {
|
||||
return 'cat_sidebar';
|
||||
}
|
||||
|
||||
if (is_tag()) {
|
||||
return 'tag_sidebar';
|
||||
@@ -183,7 +186,7 @@ function jio_is_show_sidebar_filter(): bool {
|
||||
if (is_page() && empty($cfg['page'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_category() && empty($cfg['category'])) {
|
||||
if ((is_category() || (function_exists('is_tax') && is_tax('topics'))) && empty($cfg['category'])) {
|
||||
return false;
|
||||
}
|
||||
if (is_tag() && empty($cfg['tag'])) {
|
||||
@@ -194,7 +197,7 @@ function jio_is_show_sidebar_filter(): bool {
|
||||
}
|
||||
|
||||
// Category/tag term meta can still force off (existing Jio contract).
|
||||
if ((is_category() || is_tag()) && class_exists('\Jio\Core\TermMeta') && method_exists('\Jio\Core\TermMeta', 'resolve_archive_settings')) {
|
||||
if ((is_category() || is_tag() || (function_exists('is_tax') && is_tax('topics'))) && class_exists('\Jio\Core\TermMeta') && method_exists('\Jio\Core\TermMeta', 'resolve_archive_settings')) {
|
||||
$term = get_queried_object();
|
||||
if ($term instanceof \WP_Term) {
|
||||
$resolved = \Jio\Core\TermMeta::resolve_archive_settings($term);
|
||||
@@ -214,7 +217,7 @@ function jio_is_show_sidebar_filter(): bool {
|
||||
if (is_page()) {
|
||||
return !empty($cfg['page']);
|
||||
}
|
||||
if (is_category()) {
|
||||
if (is_category() || (function_exists('is_tax') && is_tax('topics'))) {
|
||||
return !empty($cfg['category']);
|
||||
}
|
||||
if (is_tag()) {
|
||||
|
||||
@@ -3,11 +3,8 @@
|
||||
* Admin post list enhancements: interaction + sales columns and the
|
||||
* quick/bulk edit fields behind them.
|
||||
*
|
||||
* Mirrors the reference theme's post list: a sortable “销售数据” column, a
|
||||
* product-type filter, and four quick-edit field groups (interaction data,
|
||||
* article config, pay parameters, custom filters). The reference theme hooks
|
||||
* those boxes onto its topics taxonomy column; this project has no topics
|
||||
* taxonomy, so they hang off the interaction-data column instead.
|
||||
* Adds a sortable sales column, a product-type filter, and quick-edit fields
|
||||
* for interaction data, article config, pay parameters, and custom filters.
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
@@ -280,7 +277,7 @@ class PostListEnhance {
|
||||
* @param string $post_type
|
||||
*/
|
||||
public function render_edit_boxes($column_name, $post_type): void {
|
||||
if ($post_type !== 'post' || (string) $column_name !== self::COLUMN_STATS) {
|
||||
if ($post_type !== 'post' || (string) $column_name !== 'taxonomy-topics') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ class PageDisplayController extends ControllerBase {
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/schema/page_display/search/(?P<source>posts|categories|tags)', [
|
||||
register_rest_route(self::API_NAMESPACE, '/settings/schema/page_display/search/(?P<source>posts|categories|tags|topics|terms)', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'search_options'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
@@ -52,7 +52,7 @@ class PageDisplayController extends ControllerBase {
|
||||
return $this->success(['data' => $items]);
|
||||
}
|
||||
|
||||
$taxonomy = $source === 'categories' ? 'category' : 'post_tag';
|
||||
$taxonomy = ['categories' => 'category', 'tags' => 'post_tag', 'topics' => 'topics', 'terms' => ['category', 'topics']][$source] ?? 'category';
|
||||
$terms = get_terms([
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => false,
|
||||
@@ -64,8 +64,9 @@ class PageDisplayController extends ControllerBase {
|
||||
if (is_wp_error($terms)) {
|
||||
return $this->error($terms->get_error_message(), 400);
|
||||
}
|
||||
$items = array_map(static function ($term): array {
|
||||
return ['label' => $term->name, 'value' => (int) $term->term_id];
|
||||
$items = array_map(static function ($term) use ($source): array {
|
||||
$label = $source === 'terms' ? ($term->taxonomy === 'topics' ? __('专题:', 'jio') : __('分类:', 'jio')) . $term->name : $term->name;
|
||||
return ['label' => $label, 'value' => (int) $term->term_id];
|
||||
}, $terms);
|
||||
return $this->success(['data' => $items]);
|
||||
}
|
||||
|
||||
@@ -297,12 +297,13 @@ class GlobalSchema {
|
||||
['key' => 'search_custom_key_s', 'label' => '自定义匹配范围', 'type' => 'switch', 'default' => true, 'help' => '启用后可配置搜索匹配字段范围(标题/内容/分类/标签等)'],
|
||||
['key' => 'search_custom_key', 'label' => '匹配字段', 'type' => 'checkbox', 'default' => ['title', 'content', 'excerpt', 'category', 'post_tag'], 'options' => [
|
||||
['label' => '标题', 'value' => 'title'], ['label' => '内容', 'value' => 'content'], ['label' => '简介', 'value' => 'excerpt'],
|
||||
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'],
|
||||
['label' => '分类', 'value' => 'category'], ['label' => '标签', 'value' => 'post_tag'], ['label' => '专题', 'value' => 'topics'],
|
||||
['label' => '评论', 'value' => 'coment'], ['label' => '作者昵称', 'value' => 'user'],
|
||||
]],
|
||||
['key' => 'search_facets', 'label' => '搜索筛选', 'type' => 'fieldset', 'sanitize_type' => 'array', 'default' => [], 'dependsOn' => ['key' => 'search_ms_s', 'value' => false], 'help' => '搜索结果中显示的筛选列表;启用 Meilisearch 后由搜索引擎生成关联筛选。', 'fields' => [
|
||||
['key' => 'category', 'label' => '[文章] 分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => []],
|
||||
['key' => 'post_tag', 'label' => '[文章] 标签', 'type' => 'taxonomy_select', 'taxonomy' => 'post_tag', 'multiple' => true, 'default' => []],
|
||||
['key' => 'topics', 'label' => '[文章] 专题', 'type' => 'taxonomy_select', 'taxonomy' => 'topics', 'multiple' => true, 'default' => []],
|
||||
]],
|
||||
]],
|
||||
['key' => 'category-search', 'title' => '分类搜索', 'fields' => [
|
||||
|
||||
@@ -33,6 +33,7 @@ class SchemaPostListList {
|
||||
['label' => '首页', 'value' => 'home'],
|
||||
['label' => '分类页', 'value' => 'cat'],
|
||||
['label' => '标签页', 'value' => 'tag'],
|
||||
['label' => '专题页', 'value' => 'topics'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
@@ -49,10 +50,11 @@ class SchemaPostListList {
|
||||
['label' => '点赞数', 'value' => 'like'],
|
||||
['label' => '评论', 'value' => 'comm'],
|
||||
]],
|
||||
['key' => 'list_badge_show', 'label' => '显示列表标签', 'type' => 'checkbox', 'default' => ['pay', 'cat', 'tag'], 'options' => [
|
||||
['key' => 'list_badge_show', 'label' => '显示列表标签', 'type' => 'checkbox', 'default' => ['pay', 'tag', 'topics', 'cat'], 'options' => [
|
||||
['label' => '付费信息', 'value' => 'pay'],
|
||||
['label' => '分类', 'value' => 'cat'],
|
||||
['label' => '标签', 'value' => 'tag'],
|
||||
['label' => '专题', 'value' => 'topics'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
@@ -72,8 +74,9 @@ class SchemaPostListList {
|
||||
], '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_topics', '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_card_cat', 'label' => '分类、专题页自定义卡片模式', 'type' => 'taxonomy_select', 'taxonomy' => 'category,topics', 'multiple' => true, 'default' => [], 'dependsOn' => ['key' => 'list_type', 'not' => true, 'value' => 'card']],
|
||||
[
|
||||
'key' => 'list_list_option',
|
||||
'label' => '列表模式 UI 配置',
|
||||
|
||||
@@ -22,6 +22,7 @@ class SchemaPageDisplay {
|
||||
'homepage' => self::homepage(),
|
||||
'category' => self::category(),
|
||||
'tag' => self::tag(),
|
||||
'topics' => self::topics(),
|
||||
'user-home' => self::user_home(),
|
||||
'other' => self::other(),
|
||||
],
|
||||
@@ -60,7 +61,7 @@ class SchemaPageDisplay {
|
||||
private static function header_components(): array {
|
||||
return self::section('header-components', '顶部多功能组件', [
|
||||
self::notice('顶部多功能组件显示在导航栏下方,支持视频、图片、幻灯片、搜索和图标卡片;各组件可按电脑端、移动端单独启用。'),
|
||||
self::field('header_slider_show', '启用顶部多功能组件', 'checkbox', [], ['description' => '选择允许显示组件的页面;未选择任何页面时,前端不会显示。', 'options' => self::options(['home' => '首页', 'category' => '分类页', 'tag' => '标签页', 'author' => '公开用户主页', 'user_center' => '用户中心', 'single' => '文章页', 'search' => '搜索页', 'page' => '其它页面'])]),
|
||||
self::field('header_slider_show', '启用顶部多功能组件', 'checkbox', [], ['description' => '选择允许显示组件的页面;未选择任何页面时,前端不会显示。', 'options' => self::options(['home' => '首页', 'category' => '分类页', 'tag' => '标签页', 'topics' => '专题页', 'author' => '公开用户主页', 'user_center' => '用户中心', 'single' => '文章页', 'search' => '搜索页', 'page' => '其它页面'])]),
|
||||
self::field('header_slider_show_type', '顶部多功能组件的显示规则', 'radio', '', ['dependsOn' => self::not_empty('header_slider_show'), 'inline' => true, 'options' => self::options(['' => '全部显示', 'only_pc' => '仅在PC端显示', 'only_sm' => '仅在移动端显示'])]),
|
||||
self::field('header_slider_nav_color', '导航栏文字颜色', 'color', '#ffffff', ['dependsOn' => self::not_empty('header_slider_show')]),
|
||||
self::field('header_slider', '背景内容', 'group', self::header_slider_defaults(), ['dependsOn' => self::not_empty('header_slider_show'), 'min' => 1, 'button_title' => '添加背景项目', 'fields' => self::slider_item_fields()]),
|
||||
@@ -112,7 +113,7 @@ class SchemaPageDisplay {
|
||||
self::field('index_list_title', '显示标题', 'textarea', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'sanitize_type' => 'html', 'rows' => 1]),
|
||||
self::field('home_exclude_posts', '排除文章', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'posts', 'multiple' => true, 'sortable' => true]),
|
||||
self::field('home_exclude_cats', '排除分类', 'async_select', '', ['dependsOn' => self::enabled('home_posts_list_s'), 'source' => 'categories', 'multiple' => true, 'sortable' => true]),
|
||||
self::field('home_lists', '更多栏目', 'group', [], ['dependsOn' => self::enabled('home_posts_list_s'), 'button_title' => '添加栏目', 'fields' => [self::field('title', '自定义标题', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 1]), self::field('term_id', '显示内容', 'async_select', '', ['source' => 'categories']), self::field('orderby', '排序方式设置', 'select', '', ['options' => self::post_order_options(true)])]]),
|
||||
self::field('home_lists', '更多栏目', 'group', [], ['dependsOn' => self::enabled('home_posts_list_s'), 'button_title' => '添加栏目', 'fields' => [self::field('title', '自定义标题', 'textarea', '', ['sanitize_type' => 'html', 'rows' => 1]), self::field('term_id', '显示内容', 'async_select', '', ['source' => 'terms']), self::field('orderby', '排序方式设置', 'select', '', ['options' => self::post_order_options(true)])]]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -123,11 +124,13 @@ class SchemaPageDisplay {
|
||||
self::field('cat_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_cat_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
|
||||
self::field('cat_orderby_s', '排序方式按钮', 'switcher', true),
|
||||
self::field('cat_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('cat_orderby_s'), 'fields' => self::orderby_fields()]),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
|
||||
self::field('ajax_list_cat_cat', '分类筛选', 'switcher', true),
|
||||
self::field('ajax_list_option_cat_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_cat'), 'fields' => self::ajax_fields('categories')]),
|
||||
self::field('ajax_list_cat_tag', '标签筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_cat_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_tag'), 'fields' => self::ajax_fields('tags')]),
|
||||
self::field('ajax_list_cat_topics', '专题筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_cat_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_cat_topics'), 'fields' => self::ajax_fields('topics')]),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -138,11 +141,29 @@ class SchemaPageDisplay {
|
||||
self::field('tag_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['dependsOn' => self::enabled('page_cover_tag_s'), 'help' => '默认封面图,建议尺寸2000x800。']),
|
||||
self::field('tag_orderby_s', '排序方式按钮', 'switcher', true),
|
||||
self::field('tag_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('tag_orderby_s'), 'fields' => self::orderby_fields()]),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签菜单,通过 AJAX 获取内容。'),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
|
||||
self::field('ajax_list_tag_cat', '分类筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_tag_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_cat'), 'fields' => self::ajax_fields('categories')]),
|
||||
self::field('ajax_list_tag_tag', '标签筛选', 'switcher', true),
|
||||
self::field('ajax_list_option_tag_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_tag'), 'fields' => self::ajax_fields('tags')]),
|
||||
self::field('ajax_list_tag_topics', '专题筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_tag_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_tag_topics'), 'fields' => self::ajax_fields('topics')]),
|
||||
]);
|
||||
}
|
||||
|
||||
private static function topics(): array {
|
||||
return self::section('topics', '专题页面', [
|
||||
self::field('topics_post_count_s', '显示文章数量', 'switcher', true),
|
||||
self::field('topics_default_cover', '封面图', 'upload', self::asset('assets/img/user_t.jpg'), ['help' => '默认封面图,建议尺寸2000x800。']),
|
||||
self::field('topics_orderby_s', '排序方式按钮', 'switcher', true),
|
||||
self::field('topics_orderby_option', '排序方式设置', 'fieldset', [], ['dependsOn' => self::enabled('topics_orderby_s'), 'fields' => self::orderby_fields()]),
|
||||
self::notice('AJAX筛选菜单列表:显示分类、标签、专题菜单,通过 AJAX 获取内容。'),
|
||||
self::field('ajax_list_topics_cat', '分类筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_topics_cat', '分类筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_cat'), 'fields' => self::ajax_fields('categories')]),
|
||||
self::field('ajax_list_topics_tag', '标签筛选', 'switcher', false),
|
||||
self::field('ajax_list_option_topics_tag', '标签筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_tag'), 'fields' => self::ajax_fields('tags')]),
|
||||
self::field('ajax_list_topics_topics', '专题筛选', 'switcher', true),
|
||||
self::field('ajax_list_option_topics_topics', '专题筛选设置', 'fieldset', [], ['dependsOn' => self::enabled('ajax_list_topics_topics'), 'fields' => self::ajax_fields('topics')]),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ class SchemaPostList {
|
||||
['key' => 'custom_filter_show', 'label' => '高级筛选默认显示', 'type' => 'checkbox', 'default' => [], 'options' => [
|
||||
['label' => '分类页面显示', 'value' => 'cat'],
|
||||
['label' => '标签页面显示', 'value' => 'tag'],
|
||||
['label' => '专题页面显示', 'value' => 'topics'],
|
||||
], 'description' => '勾选后将会在对应页面默认显示全部筛选,您可以在下方为单独的分类添加自定义筛选'],
|
||||
['key' => 'custom_filter', 'label' => '自定义高级筛选明细', 'type' => 'repeater', 'default' => [], 'addText' => '添加筛选类别', 'description' => '添加筛选类型及明细', 'fields' => [
|
||||
['key' => 'name', 'label' => '类型名称', 'type' => 'input', 'default' => ''],
|
||||
@@ -157,6 +158,11 @@ class SchemaPostList {
|
||||
['key' => 'lists', 'label' => '按钮列表', 'type' => 'async_select', 'source' => 'tags', 'multiple' => true, 'sortable' => true, 'default' => [], 'description' => '选择并排序需要显示的按钮'],
|
||||
['key' => 'dropdown', 'label' => '下拉列表', 'type' => 'switch', 'default' => false, 'description' => '用下拉框显示更多内容'],
|
||||
]],
|
||||
['key' => 'topics_s', 'label' => '专题筛选', 'type' => 'switch', 'default' => false, 'description' => '在所选分类页显示的专题筛选列表'],
|
||||
['key' => 'topics_lists', 'label' => ' ', 'type' => 'fieldset', 'default' => ['lists' => [], 'dropdown' => false], 'dependsOn' => ['key' => 'topics_s', 'value' => true], 'fields' => [
|
||||
['key' => 'lists', 'label' => '按钮列表', 'type' => 'async_select', 'source' => 'topics', 'multiple' => true, 'sortable' => true, 'default' => []],
|
||||
['key' => 'dropdown', 'label' => '下拉列表', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'orderby_s', 'label' => '排序方式', 'type' => 'switch', 'default' => true, 'description' => '在所选分类页显示排序方式按钮'],
|
||||
['key' => 'orderby_lists', 'label' => ' ', 'type' => 'fieldset', 'default' => ['lists' => ['modified', 'views', 'like', 'comment_count'], 'dropdown' => false], 'dependsOn' => ['key' => 'orderby_s', 'value' => true], 'fields' => [
|
||||
['key' => 'lists', 'label' => '显示排序方式', 'type' => 'select', 'multiple' => true, 'sortable' => true, 'default' => ['modified', 'views', 'like', 'comment_count'], 'options' => [
|
||||
@@ -264,6 +270,8 @@ class SchemaPostList {
|
||||
['key' => 'post_article_s', 'label' => '启用前台投稿', 'type' => 'switcher', 'default' => true, 'description' => '此功能启用后,可以在权限管理中设置用户的发布、审核权限'],
|
||||
['key' => 'post_article_btn_txte', 'label' => '按钮文字', 'type' => 'input', 'default' => '发布文章', 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '投稿按钮的显示的文字,推荐4个字更加协调'],
|
||||
['key' => 'post_article_cat', 'label' => '投稿允许选择的分类', 'type' => 'taxonomy_select', 'taxonomy' => 'category', 'multiple' => true, 'default' => [], 'placeholder' => '输入关键词以搜索分类', 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '投稿允许选择的分类,为空则允许选择全部分类,所选分类下如果有子分类则会自动显示,如果选择了上级分类,则不要再选该分类下的子分类。注意:请确保所选分类包含子分类总数不要超过50个,否则可能会引起页面崩溃'],
|
||||
['key' => 'post_article_topics_s', 'label' => '允许选择专题', 'type' => 'switch', 'default' => false, 'dependsOn' => ['key' => 'post_article_s', 'value' => true]],
|
||||
['key' => 'post_article_topics', 'label' => '投稿允许选择的专题', 'type' => 'taxonomy_select', 'taxonomy' => 'topics', 'multiple' => true, 'default' => [], 'dependsOn' => ['all' => [['key' => 'post_article_s', 'value' => true], ['key' => 'post_article_topics_s', 'value' => true]]]],
|
||||
['key' => 'post_article_custom_filter_s', 'label' => '允许选择高级筛选', 'type' => 'switcher', 'default' => true, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '此功能启用后,请先在文章列表/高级筛选中添加高级筛选明细'],
|
||||
['key' => 'post_article_tag', 'label' => '允许插入标签', 'type' => 'switcher', 'default' => true, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '前台投稿时候允许设置文章标签'],
|
||||
['key' => 'post_article_user', 'label' => '免登陆投稿发布用户', 'type' => 'user_select', 'default' => 1, 'dependsOn' => ['key' => 'post_article_s', 'value' => true], 'description' => '当选择投稿权限为无需登录就能投稿时,投稿文章的用户'],
|
||||
|
||||
@@ -143,6 +143,7 @@ final class CreatorRest {
|
||||
'permalink' => $source instanceof \WP_Post ? get_permalink($source) : '',
|
||||
'isPublished'=> $source instanceof \WP_Post && $source->post_status === 'publish',
|
||||
'categories' => $terms['categories'],
|
||||
'topics' => $terms['topics'],
|
||||
'tags' => $terms['tags'],
|
||||
'custom' => $terms['custom'],
|
||||
'customDefs' => $terms['customDefs'],
|
||||
@@ -222,6 +223,7 @@ final class CreatorRest {
|
||||
'title' => (string) $request->get_param('title'),
|
||||
'content' => (string) $request->get_param('content'),
|
||||
'categories' => (array) $request->get_param('categories'),
|
||||
'topics' => (array) $request->get_param('topics'),
|
||||
'tags' => (array) $request->get_param('tags'),
|
||||
'custom' => (array) $request->get_param('custom'),
|
||||
'featured' => (array) $request->get_param('featured'),
|
||||
@@ -250,6 +252,7 @@ final class CreatorRest {
|
||||
'post_title' => $payload['title'],
|
||||
'post_content' => $payload['content'],
|
||||
'category' => $payload['categories'],
|
||||
'topics' => $payload['topics'],
|
||||
'tags' => implode(',', array_map('strval', $payload['tags'])),
|
||||
'featured_data'=> $payload['featured'],
|
||||
'schedule' => $payload['schedule'],
|
||||
@@ -295,6 +298,7 @@ final class CreatorRest {
|
||||
'title' => (string) $request->get_param('title'),
|
||||
'content' => (string) $request->get_param('content'),
|
||||
'categories' => (array) $request->get_param('categories'),
|
||||
'topics' => (array) $request->get_param('topics'),
|
||||
'tags' => (array) $request->get_param('tags'),
|
||||
'custom' => (array) $request->get_param('custom'),
|
||||
'featured' => (array) $request->get_param('featured'),
|
||||
@@ -334,6 +338,7 @@ final class CreatorRest {
|
||||
'post_title' => $payload['title'],
|
||||
'post_content' => $payload['content'],
|
||||
'category' => $payload['categories'],
|
||||
'topics' => $payload['topics'],
|
||||
'tags' => implode(',', array_map('strval', $payload['tags'])),
|
||||
'featured_data' => $payload['featured'],
|
||||
'schedule' => $payload['schedule'],
|
||||
@@ -519,6 +524,7 @@ final class CreatorRest {
|
||||
|
||||
return [
|
||||
'categories' => array_values(array_filter(array_map('absint', (array) $payload['categories']))),
|
||||
'topics' => array_values(array_filter(array_map('absint', (array) $payload['topics']))),
|
||||
'tags' => array_values(array_filter(array_map('sanitize_text_field', (array) $payload['tags']))),
|
||||
'custom' => is_array($payload['custom']) ? $payload['custom'] : [],
|
||||
'featured' => is_array($payload['featured']) ? $payload['featured'] : [],
|
||||
@@ -567,6 +573,21 @@ final class CreatorRest {
|
||||
$current = $source instanceof \WP_Post && $snapshot === []
|
||||
? array_map('intval', wp_get_post_categories($source->ID))
|
||||
: array_map('intval', (array) ($snapshot['categories'] ?? []));
|
||||
$topics = [];
|
||||
if (!empty($settings['allow_topics'])) {
|
||||
foreach (SubmitTopics::tree($settings) as $node) {
|
||||
$topics[] = [
|
||||
'id' => (int) $node['term']->term_id,
|
||||
'name' => (string) $node['term']->name,
|
||||
'depth' => (int) $node['depth'],
|
||||
'path' => (string) $node['path'],
|
||||
];
|
||||
}
|
||||
}
|
||||
$selected_topics = $source instanceof \WP_Post && $snapshot === []
|
||||
? wp_get_post_terms($source->ID, 'topics', ['fields' => 'ids'])
|
||||
: ($snapshot['topics'] ?? []);
|
||||
$selected_topics = is_array($selected_topics) ? SubmitTopics::filter_ids($selected_topics, $settings) : [];
|
||||
$tags = $source instanceof \WP_Post && $snapshot === []
|
||||
? wp_get_post_tags($source->ID, ['fields' => 'names'])
|
||||
: array_map('strval', (array) ($snapshot['tags'] ?? []));
|
||||
@@ -610,6 +631,7 @@ final class CreatorRest {
|
||||
|
||||
return [
|
||||
'categories' => ['items' => $categories, 'selected' => $current],
|
||||
'topics' => ['enabled' => !empty($settings['allow_topics']), 'items' => $topics, 'selected' => $selected_topics],
|
||||
'tags' => [
|
||||
'items' => is_array($tags) ? array_values($tags) : [],
|
||||
'enabled' => $tag_enabled,
|
||||
|
||||
@@ -148,7 +148,6 @@ class DomainModel {
|
||||
'description' => '',
|
||||
'show_sidebar' => '',
|
||||
'show_header' => true,
|
||||
'show_featured' => false,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -231,6 +231,11 @@ trait FrontSubmitSave {
|
||||
if (isset($data['category'])) {
|
||||
wp_set_post_categories((int) $in_id, $categories);
|
||||
}
|
||||
if (!empty($settings['allow_topics'])) {
|
||||
$raw_topics = $data['topics'] ?? [];
|
||||
$topic_ids = SubmitTopics::filter_ids(is_array($raw_topics) ? $raw_topics : [$raw_topics], $settings);
|
||||
wp_set_post_terms((int) $in_id, $topic_ids, 'topics');
|
||||
}
|
||||
if (!empty($settings['allow_tags']) && array_key_exists('tags', $data) && $tags_raw === '') {
|
||||
wp_set_post_tags((int) $in_id, []);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class Setup {
|
||||
add_filter('pre_option_link_manager_enabled', '__return_true');
|
||||
add_action('init', [$this, 'register_nav_menus'], 0);
|
||||
add_action('init', [$this, 'register_post_types'], 1);
|
||||
add_action('init', [$this, 'register_taxonomies'], 1);
|
||||
add_action('init', [$this, 'register_meta_fields'], 5);
|
||||
// 文章浏览量统计(单次访问)
|
||||
add_action('wp', 'jio_set_post_views');
|
||||
@@ -124,6 +125,31 @@ class Setup {
|
||||
]);
|
||||
}
|
||||
|
||||
public function register_taxonomies(): void {
|
||||
register_taxonomy('topics', ['post'], [
|
||||
'description' => __('添加文章专题', 'jio'),
|
||||
'hierarchical' => true,
|
||||
'labels' => [
|
||||
'name' => __('专题', 'jio'),
|
||||
'singular_name' => __('专题', 'jio'),
|
||||
'search_items' => __('搜索专题', 'jio'),
|
||||
'all_items' => __('所有专题', 'jio'),
|
||||
'parent_item' => __('父专题', 'jio'),
|
||||
'parent_item_colon' => __('父专题:', 'jio'),
|
||||
'edit_item' => __('编辑专题', 'jio'),
|
||||
'update_item' => __('更新专题', 'jio'),
|
||||
'add_new_item' => __('添加新专题', 'jio'),
|
||||
'new_item_name' => __('新专题名称', 'jio'),
|
||||
'menu_name' => __('专题', 'jio'),
|
||||
],
|
||||
'show_ui' => true,
|
||||
'show_in_menu' => true,
|
||||
'show_in_rest' => true,
|
||||
'show_admin_column' => true,
|
||||
'query_var' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册资源 post meta + 分类 term meta(jio_flow)
|
||||
*
|
||||
|
||||
@@ -441,6 +441,10 @@ final class SubmissionVersion {
|
||||
}
|
||||
wp_set_post_categories($post_id, $cats);
|
||||
}
|
||||
if (!empty($settings['allow_topics']) && array_key_exists('topics', $snapshot)) {
|
||||
$topics = is_array($snapshot['topics']) ? $snapshot['topics'] : [];
|
||||
wp_set_post_terms($post_id, SubmitTopics::filter_ids($topics, $settings), 'topics');
|
||||
}
|
||||
if (array_key_exists('tags', $snapshot)) {
|
||||
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : [];
|
||||
wp_set_post_tags($post_id, array_values(array_filter(array_map('sanitize_text_field', $tags))));
|
||||
@@ -480,6 +484,9 @@ final class SubmissionVersion {
|
||||
if (isset($snapshot['categories']) && is_array($snapshot['categories'])) {
|
||||
$clean['categories'] = array_values(array_unique(array_filter(array_map('absint', $snapshot['categories']))));
|
||||
}
|
||||
if (isset($snapshot['topics']) && is_array($snapshot['topics'])) {
|
||||
$clean['topics'] = array_values(array_unique(array_filter(array_map('absint', $snapshot['topics']))));
|
||||
}
|
||||
if (isset($snapshot['tags'])) {
|
||||
$tags = is_array($snapshot['tags']) ? $snapshot['tags'] : preg_split('/[,,\n]+/u', (string) $snapshot['tags']);
|
||||
$clean['tags'] = array_values(array_filter(array_map('sanitize_text_field', (array) $tags)));
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Topics available to front-end authors.
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace Jio\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class SubmitTopics {
|
||||
/** @return array<int, array{term:\WP_Term, depth:int, path:string}> */
|
||||
public static function tree(array $settings): array {
|
||||
if (empty($settings['allow_topics'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$terms = get_terms([
|
||||
'taxonomy' => 'topics',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
'number' => 0,
|
||||
]);
|
||||
if (!is_array($terms)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$allowed = self::allowed_ids($settings);
|
||||
if ($allowed !== null) {
|
||||
$terms = array_values(array_filter($terms, static fn($term): bool => isset($allowed[(int) $term->term_id])));
|
||||
}
|
||||
|
||||
$present = [];
|
||||
foreach ($terms as $term) {
|
||||
$present[(int) $term->term_id] = true;
|
||||
}
|
||||
$children = [];
|
||||
foreach ($terms as $term) {
|
||||
$parent = (int) $term->parent;
|
||||
$children[isset($present[$parent]) ? $parent : 0][] = $term;
|
||||
}
|
||||
$tree = [];
|
||||
$walk = static function (int $parent, int $depth, string $prefix) use (&$walk, &$tree, $children): void {
|
||||
foreach ($children[$parent] ?? [] as $term) {
|
||||
$path = $prefix === '' ? (string) $term->name : $prefix . ' / ' . $term->name;
|
||||
$tree[] = ['term' => $term, 'depth' => $depth, 'path' => $path];
|
||||
$walk((int) $term->term_id, $depth + 1, $path);
|
||||
}
|
||||
};
|
||||
$walk(0, 0, '');
|
||||
return $tree;
|
||||
}
|
||||
|
||||
/** @return int[] */
|
||||
public static function filter_ids(array $ids, array $settings): array {
|
||||
if (empty($settings['allow_topics'])) {
|
||||
return [];
|
||||
}
|
||||
$allowed = self::allowed_ids($settings);
|
||||
$ids = array_values(array_unique(array_filter(array_map('absint', $ids))));
|
||||
$valid = [];
|
||||
foreach ($ids as $id) {
|
||||
if (($allowed === null || isset($allowed[$id])) && term_exists($id, 'topics')) {
|
||||
$valid[] = $id;
|
||||
}
|
||||
}
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/** @return array<int,true>|null */
|
||||
private static function allowed_ids(array $settings): ?array {
|
||||
$roots = array_values(array_unique(array_filter(array_map('absint', (array) ($settings['allowed_topic_ids'] ?? [])))));
|
||||
if ($roots === []) {
|
||||
return null;
|
||||
}
|
||||
$allowed = [];
|
||||
foreach ($roots as $id) {
|
||||
$allowed[$id] = true;
|
||||
$descendants = get_term_children($id, 'topics');
|
||||
foreach (is_array($descendants) ? $descendants : [] as $child) {
|
||||
$allowed[(int) $child] = true;
|
||||
}
|
||||
}
|
||||
return $allowed;
|
||||
}
|
||||
}
|
||||
@@ -91,13 +91,6 @@ class Templates {
|
||||
return $templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load dedicated taxonomy templates.
|
||||
*/
|
||||
public function load_taxonomy_template(string $template): string {
|
||||
return $template;
|
||||
}
|
||||
|
||||
public function render_archive_shortcode(array $attrs = [], string $content = ''): string {
|
||||
$template = JIO_DIR . '/templates/shortcodes/archive.php';
|
||||
if (!file_exists($template)) {
|
||||
|
||||
+102
-22
@@ -12,7 +12,7 @@ class TermMeta {
|
||||
private static $instance = null;
|
||||
|
||||
/** Supported taxonomies */
|
||||
private $taxonomies = ['category', 'post_tag'];
|
||||
private $taxonomies = ['category', 'post_tag', 'topics'];
|
||||
|
||||
/** Meta key for storing term-level settings */
|
||||
const META_KEY = DomainModel::TERM_META_KEY;
|
||||
@@ -31,9 +31,61 @@ class TermMeta {
|
||||
add_action("edited_{$tax}", [$this, 'save_term_meta']);
|
||||
}
|
||||
|
||||
add_action('admin_enqueue_scripts', [$this, 'enqueue_topic_media']);
|
||||
add_filter('manage_edit-topics_columns', [$this, 'topic_columns']);
|
||||
add_filter('manage_topics_custom_column', [$this, 'render_topic_column'], 10, 3);
|
||||
add_filter('topics_row_actions', [$this, 'topic_row_actions'], 10, 2);
|
||||
add_action('pre_get_posts', [$this, 'filter_archive_query']);
|
||||
}
|
||||
|
||||
public function topic_columns(array $columns): array {
|
||||
return ['jio_cover' => __('图像', 'jio')] + $columns;
|
||||
}
|
||||
|
||||
public function render_topic_column(string $content, string $column, int $term_id): string {
|
||||
if ($column !== 'jio_cover') {
|
||||
return $content;
|
||||
}
|
||||
$url = esc_url_raw((string) self::get_meta_value($term_id, 'cover_url'));
|
||||
return $url === ''
|
||||
? '—'
|
||||
: '<img src="' . esc_url($url) . '" alt="" style="width:55px;height:28px;object-fit:cover">';
|
||||
}
|
||||
|
||||
public function topic_row_actions(array $actions, \WP_Term $term): array {
|
||||
$actions['jio_term_id'] = sprintf(esc_html__('ID:%d', 'jio'), (int) $term->term_id);
|
||||
return $actions;
|
||||
}
|
||||
|
||||
public function enqueue_topic_media(string $hook): void {
|
||||
if (!in_array($hook, ['edit-tags.php', 'term.php'], true)) {
|
||||
return;
|
||||
}
|
||||
$screen = get_current_screen();
|
||||
if (!$screen || $screen->taxonomy !== 'topics') {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_media();
|
||||
wp_add_inline_script('media-views', <<<'JS'
|
||||
document.addEventListener('click', function (event) {
|
||||
const button = event.target.closest('[data-jio-topic-cover-select]');
|
||||
if (!button || typeof wp === 'undefined' || !wp.media) return;
|
||||
event.preventDefault();
|
||||
const field = button.closest('.form-field');
|
||||
const input = field && field.querySelector('[name="jio_term_meta[cover_url]"]');
|
||||
if (!input) return;
|
||||
const frame = wp.media({ title: button.textContent.trim(), library: { type: 'image' }, multiple: false });
|
||||
frame.on('select', function () {
|
||||
const image = frame.state().get('selection').first();
|
||||
if (image) input.value = image.get('url') || '';
|
||||
});
|
||||
frame.open();
|
||||
});
|
||||
JS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get term meta with defaults.
|
||||
*/
|
||||
@@ -59,17 +111,25 @@ class TermMeta {
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public static function resolve_archive_settings(\WP_Term $term): array {
|
||||
$taxonomy = $term->taxonomy === 'post_tag' ? 'post_tag' : 'category';
|
||||
$global = $taxonomy === 'post_tag' && class_exists('\Jio\Settings\Settings')
|
||||
? \Jio\Settings\Settings::get_tag_page_settings()
|
||||
: (class_exists('\Jio\Settings\Settings') ? \Jio\Settings\Settings::get_category_page_settings() : [
|
||||
$taxonomy = in_array($term->taxonomy, ['category', 'post_tag', 'topics'], true) ? $term->taxonomy : 'category';
|
||||
if (class_exists('\Jio\Settings\Settings')) {
|
||||
if ($taxonomy === 'topics') {
|
||||
$global = \Jio\Settings\Settings::get_topics_page_settings();
|
||||
} elseif ($taxonomy === 'post_tag') {
|
||||
$global = \Jio\Settings\Settings::get_tag_page_settings();
|
||||
} else {
|
||||
$global = \Jio\Settings\Settings::get_category_page_settings();
|
||||
}
|
||||
} else {
|
||||
$global = [
|
||||
'header_style' => 'cover',
|
||||
'cover_enabled' => true,
|
||||
'desc_enabled' => true,
|
||||
'filter_bar' => true,
|
||||
'sidebar' => true,
|
||||
'sidebar' => $taxonomy !== 'topics',
|
||||
'per_page' => 12,
|
||||
]);
|
||||
];
|
||||
}
|
||||
|
||||
$saved = get_term_meta($term->term_id, self::META_KEY, true);
|
||||
if (!is_array($saved)) {
|
||||
@@ -96,7 +156,7 @@ class TermMeta {
|
||||
|
||||
$cover_url = isset($saved['cover_url']) ? esc_url_raw((string) $saved['cover_url']) : '';
|
||||
if ($cover_url === '' && !empty($global['cover_enabled'])) {
|
||||
$cover_id = (int) get_term_meta($term->term_id, 'jio_category_cover_id', true);
|
||||
$cover_id = $taxonomy === 'category' ? (int) get_term_meta($term->term_id, 'jio_category_cover_id', true) : 0;
|
||||
$cover_url = $cover_id > 0 ? (string) wp_get_attachment_url($cover_id) : '';
|
||||
if ($cover_url === '') {
|
||||
$cover_url = esc_url_raw((string) ($global['default_cover'] ?? ''));
|
||||
@@ -108,9 +168,7 @@ class TermMeta {
|
||||
$description = (string) $term->description;
|
||||
}
|
||||
|
||||
// 分类高级筛选语义:cat 页按 cat_custom_filter 全量覆盖
|
||||
// 筛选项目,未匹配或 tag 页回退默认配置,custom_filter_show 决定自定义筛选显示。
|
||||
$page = $taxonomy === 'post_tag' ? 'tag' : 'cat';
|
||||
$page = $taxonomy === 'post_tag' ? 'tag' : ($taxonomy === 'topics' ? 'topics' : 'cat');
|
||||
$custom_filter = class_exists('\Jio\Settings\Settings')
|
||||
? \Jio\Settings\Settings::get_archive_custom_filter_config((int) $term->term_id, $page)
|
||||
: ['custom_enabled' => false, 'custom_filters' => 'all', 'overrides' => []];
|
||||
@@ -120,6 +178,8 @@ class TermMeta {
|
||||
$ajax_category = is_array($global['ajax_category'] ?? null) ? $global['ajax_category'] : [];
|
||||
$ajax_tag_enabled = !empty($global['ajax_tag_enabled']);
|
||||
$ajax_tag = is_array($global['ajax_tag'] ?? null) ? $global['ajax_tag'] : [];
|
||||
$ajax_topics_enabled = !empty($global['ajax_topics_enabled']);
|
||||
$ajax_topics = is_array($global['ajax_topics'] ?? null) ? $global['ajax_topics'] : [];
|
||||
$orderby_enabled = !empty($global['orderby_enabled']);
|
||||
$orderby_option = is_array($global['orderby_option'] ?? null) ? $global['orderby_option'] : [];
|
||||
if (array_key_exists('cat_s', $overrides)) {
|
||||
@@ -130,6 +190,10 @@ class TermMeta {
|
||||
$ajax_tag_enabled = !empty($overrides['tag_s']);
|
||||
$ajax_tag = is_array($overrides['tag_lists'] ?? null) ? $overrides['tag_lists'] : $ajax_tag;
|
||||
}
|
||||
if (array_key_exists('topics_s', $overrides)) {
|
||||
$ajax_topics_enabled = !empty($overrides['topics_s']);
|
||||
$ajax_topics = is_array($overrides['topics_lists'] ?? null) ? $overrides['topics_lists'] : $ajax_topics;
|
||||
}
|
||||
if (array_key_exists('orderby_s', $overrides)) {
|
||||
$orderby_enabled = !empty($overrides['orderby_s']);
|
||||
$orderby_option = is_array($overrides['orderby_lists'] ?? null) ? $overrides['orderby_lists'] : $orderby_option;
|
||||
@@ -143,16 +207,18 @@ class TermMeta {
|
||||
'header_style' => $header_style,
|
||||
'show_sidebar' => $sidebar_enabled,
|
||||
'show_cover' => $header_style === 'cover' && !empty($global['cover_enabled']),
|
||||
'show_desc' => $taxonomy === 'post_tag' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
|
||||
'filter_bar' => !empty($global['filter_bar']) || $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || !empty($custom_filter['custom_enabled']),
|
||||
'show_desc' => $taxonomy !== 'category' ? $description !== '' : (!empty($global['desc_enabled']) && $description !== ''),
|
||||
'filter_bar' => !empty($global['filter_bar']) || $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || !empty($custom_filter['custom_enabled']),
|
||||
'post_count' => !empty($global['post_count']),
|
||||
'orderby_enabled' => $orderby_enabled,
|
||||
'orderby' => $orderby,
|
||||
'orderby_option'=> $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_topics_enabled' => $ajax_topics_enabled,
|
||||
'ajax_category' => $ajax_category,
|
||||
'ajax_tag' => $ajax_tag,
|
||||
'ajax_topics' => $ajax_topics,
|
||||
'custom_filter_enabled' => !empty($custom_filter['custom_enabled']),
|
||||
'custom_filter_keys' => $custom_filter['custom_filters'] ?? 'all',
|
||||
'custom_dimensions' => is_array($filter_all['custom_filter'] ?? null) ? $filter_all['custom_filter'] : [],
|
||||
@@ -191,6 +257,13 @@ class TermMeta {
|
||||
$term = get_term_by('slug', $slug, 'post_tag');
|
||||
}
|
||||
}
|
||||
} elseif ($query->is_tax('topics')) {
|
||||
$path = trim((string) ($query->get('topics') ?: $query->get('term')), '/');
|
||||
$segments = explode('/', $path);
|
||||
$slug = sanitize_title((string) end($segments));
|
||||
if ($slug !== '') {
|
||||
$term = get_term_by('slug', $slug, 'topics');
|
||||
}
|
||||
}
|
||||
|
||||
if (!$term instanceof \WP_Term) {
|
||||
@@ -245,11 +318,15 @@ class TermMeta {
|
||||
* Render fields for "Add new term" form.
|
||||
*/
|
||||
public function render_add_fields(string $taxonomy): void {
|
||||
wp_nonce_field('jio_term_meta', 'jio_term_meta_nonce');
|
||||
?>
|
||||
<div class="form-field">
|
||||
<label for="jio_term_cover_url"><?php esc_html_e('封面图 URL', 'jio'); ?></label>
|
||||
<input type="url" name="jio_term_meta[cover_url]" id="jio_term_cover_url" value="" placeholder="https://...">
|
||||
<p class="description"><?php esc_html_e('分类/标签页顶部封面图,留空则不显示。', 'jio'); ?></p>
|
||||
<?php if ($taxonomy === 'topics') : ?>
|
||||
<button type="button" class="button" data-jio-topic-cover-select><?php esc_html_e('上传/添加图像', 'jio'); ?></button>
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php esc_html_e('归档页顶部封面图,留空则使用默认封面。', 'jio'); ?></p>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@@ -259,19 +336,23 @@ class TermMeta {
|
||||
*/
|
||||
public function render_edit_fields(\WP_Term $term): void {
|
||||
$meta = self::get_meta($term->term_id);
|
||||
wp_nonce_field('jio_term_meta', 'jio_term_meta_nonce');
|
||||
?>
|
||||
<tr class="form-field">
|
||||
<th scope="row"><label for="jio_term_cover_url"><?php esc_html_e('封面图 URL', 'jio'); ?></label></th>
|
||||
<td>
|
||||
<input type="url" name="jio_term_meta[cover_url]" id="jio_term_cover_url" value="<?php echo esc_url($meta['cover_url']); ?>" class="regular-text" placeholder="https://...">
|
||||
<p class="description"><?php esc_html_e('分类/标签页顶部封面图,留空则不显示。', 'jio'); ?></p>
|
||||
<?php if ($term->taxonomy === 'topics') : ?>
|
||||
<button type="button" class="button" data-jio-topic-cover-select><?php esc_html_e('上传/添加图像', 'jio'); ?></button>
|
||||
<?php endif; ?>
|
||||
<p class="description"><?php esc_html_e('归档页顶部封面图,留空则使用默认封面。', 'jio'); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-field">
|
||||
<th scope="row"><label for="jio_term_desc"><?php esc_html_e('自定义简介', 'jio'); ?></label></th>
|
||||
<td>
|
||||
<textarea name="jio_term_meta[description]" id="jio_term_desc" rows="3" class="large-text"><?php echo esc_textarea($meta['description']); ?></textarea>
|
||||
<p class="description"><?php esc_html_e('覆盖默认的分类/标签描述。', 'jio'); ?></p>
|
||||
<p class="description"><?php esc_html_e('覆盖默认的术语描述。', 'jio'); ?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="form-field">
|
||||
@@ -284,10 +365,6 @@ class TermMeta {
|
||||
<label>
|
||||
<input type="checkbox" name="jio_term_meta[show_sidebar]" value="1" <?php checked($meta['show_sidebar'] === '1'); ?>>
|
||||
<?php esc_html_e('强制显示侧栏', 'jio'); ?>
|
||||
</label><br>
|
||||
<label>
|
||||
<input type="checkbox" name="jio_term_meta[show_featured]" value="1" <?php checked(!empty($meta['show_featured'])); ?>>
|
||||
<?php esc_html_e('显示推荐文章位', 'jio'); ?>
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -301,17 +378,20 @@ class TermMeta {
|
||||
if (!isset($_POST['jio_term_meta']) || !is_array($_POST['jio_term_meta'])) {
|
||||
return;
|
||||
}
|
||||
$nonce = isset($_POST['jio_term_meta_nonce']) ? sanitize_text_field(wp_unslash((string) $_POST['jio_term_meta_nonce'])) : '';
|
||||
if ($nonce === '' || !wp_verify_nonce($nonce, 'jio_term_meta')) {
|
||||
return;
|
||||
}
|
||||
if (!current_user_can('manage_categories')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$raw = $_POST['jio_term_meta'];
|
||||
$raw = wp_unslash($_POST['jio_term_meta']);
|
||||
$sanitized = [
|
||||
'cover_url' => isset($raw['cover_url']) ? esc_url_raw($raw['cover_url']) : '',
|
||||
'description' => isset($raw['description']) ? sanitize_textarea_field($raw['description']) : '',
|
||||
'show_sidebar' => isset($raw['show_sidebar']) ? '1' : '',
|
||||
'show_header' => isset($raw['show_header']) ? true : false,
|
||||
'show_featured' => isset($raw['show_featured']) ? true : false,
|
||||
];
|
||||
|
||||
update_term_meta($term_id, self::META_KEY, $sanitized);
|
||||
|
||||
@@ -51,6 +51,7 @@ trait DisplayPageGroups {
|
||||
'home',
|
||||
'category',
|
||||
'tag',
|
||||
'topics',
|
||||
'author',
|
||||
'user_center',
|
||||
'single',
|
||||
@@ -206,6 +207,7 @@ trait DisplayPageGroups {
|
||||
'category_recommend' => self::get_category_recommend_settings(),
|
||||
'category' => self::get_category_page_settings(),
|
||||
'tag' => self::get_tag_page_settings(),
|
||||
'topics' => self::get_topics_page_settings(),
|
||||
'user_home' => self::get_user_home_settings(),
|
||||
'other' => self::get_other_page_settings(),
|
||||
];
|
||||
|
||||
@@ -42,6 +42,7 @@ trait FacadeBulk {
|
||||
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
|
||||
$ajax_category_enabled = !empty($settings['ajax_list_cat_cat']);
|
||||
$ajax_tag_enabled = !empty($settings['ajax_list_cat_tag']);
|
||||
$ajax_topics_enabled = !empty($settings['ajax_list_cat_topics']);
|
||||
$orderby_enabled = !empty($settings['cat_orderby_s']);
|
||||
$post_count = !empty($settings['cat_post_count_s']);
|
||||
|
||||
@@ -51,7 +52,7 @@ trait FacadeBulk {
|
||||
'desc_enabled' => true,
|
||||
'default_cover' => esc_url_raw((string) ($settings['cat_default_cover'] ?? '')),
|
||||
'list_mode' => 'grid',
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || $post_count,
|
||||
'sidebar' => true,
|
||||
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'post_count' => $post_count,
|
||||
@@ -60,8 +61,10 @@ trait FacadeBulk {
|
||||
'orderby_option' => $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_topics_enabled' => $ajax_topics_enabled,
|
||||
'ajax_category' => is_array($settings['ajax_list_option_cat_cat'] ?? null) ? $settings['ajax_list_option_cat_cat'] : [],
|
||||
'ajax_tag' => is_array($settings['ajax_list_option_cat_tag'] ?? null) ? $settings['ajax_list_option_cat_tag'] : [],
|
||||
'ajax_topics' => is_array($settings['ajax_list_option_cat_topics'] ?? null) ? $settings['ajax_list_option_cat_topics'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -78,6 +81,7 @@ trait FacadeBulk {
|
||||
$orderby = sanitize_key((string) (($orderby_option['lists'][0] ?? 'modified')));
|
||||
$ajax_category_enabled = !empty($settings['ajax_list_tag_cat']);
|
||||
$ajax_tag_enabled = !empty($settings['ajax_list_tag_tag']);
|
||||
$ajax_topics_enabled = !empty($settings['ajax_list_tag_topics']);
|
||||
$orderby_enabled = !empty($settings['tag_orderby_s']);
|
||||
$post_count = !empty($settings['tag_post_count_s']);
|
||||
|
||||
@@ -89,17 +93,49 @@ trait FacadeBulk {
|
||||
'sidebar' => true,
|
||||
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'post_count' => $post_count,
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $orderby_enabled || $post_count,
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled || $post_count,
|
||||
'orderby' => $orderby,
|
||||
'orderby_enabled'=> $orderby_enabled,
|
||||
'orderby_option' => $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_topics_enabled' => $ajax_topics_enabled,
|
||||
'ajax_category' => is_array($settings['ajax_list_option_tag_cat'] ?? null) ? $settings['ajax_list_option_tag_cat'] : [],
|
||||
'ajax_tag' => is_array($settings['ajax_list_option_tag_tag'] ?? null) ? $settings['ajax_list_option_tag_tag'] : [],
|
||||
'ajax_topics' => is_array($settings['ajax_list_option_tag_topics'] ?? null) ? $settings['ajax_list_option_tag_topics'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
public static function get_topics_page_settings(): array {
|
||||
$settings = self::read_domain('page_display', 'topics');
|
||||
$orderby_option = self::normalize_page_orderby_option($settings['topics_orderby_option'] ?? []);
|
||||
$ajax_category_enabled = !empty($settings['ajax_list_topics_cat']);
|
||||
$ajax_tag_enabled = !empty($settings['ajax_list_topics_tag']);
|
||||
$ajax_topics_enabled = !array_key_exists('ajax_list_topics_topics', $settings) || !empty($settings['ajax_list_topics_topics']);
|
||||
$orderby_enabled = !array_key_exists('topics_orderby_s', $settings) || !empty($settings['topics_orderby_s']);
|
||||
$post_count = !array_key_exists('topics_post_count_s', $settings) || !empty($settings['topics_post_count_s']);
|
||||
|
||||
return [
|
||||
'header_style' => 'cover',
|
||||
'cover_enabled' => true,
|
||||
'default_cover' => esc_url_raw((string) ($settings['topics_default_cover'] ?? get_template_directory_uri() . '/assets/img/user_t.jpg')),
|
||||
'list_mode' => 'grid',
|
||||
'sidebar' => false,
|
||||
'per_page' => max(1, (int) get_option('posts_per_page', 10)),
|
||||
'post_count' => $post_count,
|
||||
'filter_bar' => $ajax_category_enabled || $ajax_tag_enabled || $ajax_topics_enabled || $orderby_enabled,
|
||||
'orderby' => sanitize_key((string) ($orderby_option['lists'][0] ?? 'modified')),
|
||||
'orderby_enabled' => $orderby_enabled,
|
||||
'orderby_option' => $orderby_option,
|
||||
'ajax_category_enabled' => $ajax_category_enabled,
|
||||
'ajax_tag_enabled' => $ajax_tag_enabled,
|
||||
'ajax_topics_enabled' => $ajax_topics_enabled,
|
||||
'ajax_category' => is_array($settings['ajax_list_option_topics_cat'] ?? null) ? $settings['ajax_list_option_topics_cat'] : [],
|
||||
'ajax_tag' => is_array($settings['ajax_list_option_topics_tag'] ?? null) ? $settings['ajax_list_option_topics_tag'] : [],
|
||||
'ajax_topics' => is_array($settings['ajax_list_option_topics_topics'] ?? null) ? $settings['ajax_list_option_topics_topics'] : [],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
/** @return array{header_style:string,header_cover_img:string} */
|
||||
|
||||
@@ -27,11 +27,11 @@ class PostDisplay {
|
||||
$card_style = $card_style === 'style3' ? 'style3' : 'null';
|
||||
|
||||
$sticky_pages = is_array($settings['sticky_s'] ?? null)
|
||||
? array_values(array_intersect(array_map('sanitize_key', $settings['sticky_s']), ['home', 'cat', 'tag']))
|
||||
? array_values(array_intersect(array_map('sanitize_key', $settings['sticky_s']), ['home', 'cat', 'tag', 'topics']))
|
||||
: ['home', 'cat'];
|
||||
$badge_types = is_array($settings['list_badge_show'] ?? null)
|
||||
? array_values(array_intersect(array_map('sanitize_key', $settings['list_badge_show']), ['pay', 'cat', 'tag']))
|
||||
: ['pay', 'cat', 'tag'];
|
||||
? array_values(array_intersect(array_map('sanitize_key', $settings['list_badge_show']), ['pay', 'cat', 'tag', 'topics']))
|
||||
: ['pay', 'tag', 'topics', 'cat'];
|
||||
$card_cat_ids = is_array($settings['list_card_cat'] ?? null)
|
||||
? array_values(array_filter(array_map('intval', $settings['list_card_cat'])))
|
||||
: [];
|
||||
@@ -58,6 +58,7 @@ class PostDisplay {
|
||||
'list_show_type' => in_array($list_show_type, ['separate', 'no_margin'], true) ? $list_show_type : 'separate',
|
||||
'list_card_home' => !empty($settings['list_card_home']),
|
||||
'list_card_tag' => !empty($settings['list_card_tag']),
|
||||
'list_card_topics' => !empty($settings['list_card_topics']),
|
||||
'list_card_author' => !empty($settings['list_card_author']),
|
||||
'list_card_cat' => $card_cat_ids,
|
||||
'list_list_option' => [
|
||||
@@ -104,7 +105,7 @@ class PostDisplay {
|
||||
$settings = Settings::get_all();
|
||||
|
||||
return [
|
||||
'custom_filter_show' => is_array($settings['custom_filter_show'] ?? null) ? array_values(array_intersect(array_map('sanitize_key', $settings['custom_filter_show']), ['cat', 'tag'])) : [],
|
||||
'custom_filter_show' => is_array($settings['custom_filter_show'] ?? null) ? array_values(array_intersect(array_map('sanitize_key', $settings['custom_filter_show']), ['cat', 'tag', 'topics'])) : [],
|
||||
'custom_filter' => self::sanitize_custom_filter_defs($settings['custom_filter'] ?? []),
|
||||
'cat_custom_filter' => self::sanitize_cat_custom_filter($settings['cat_custom_filter'] ?? []),
|
||||
];
|
||||
@@ -132,6 +133,8 @@ class PostDisplay {
|
||||
'cat_lists' => $rule['cat_lists'],
|
||||
'tag_s' => $rule['tag_s'],
|
||||
'tag_lists' => $rule['tag_lists'],
|
||||
'topics_s' => $rule['topics_s'],
|
||||
'topics_lists' => $rule['topics_lists'],
|
||||
'orderby_s' => $rule['orderby_s'],
|
||||
'orderby_lists' => $rule['orderby_lists'],
|
||||
],
|
||||
@@ -373,6 +376,8 @@ class PostDisplay {
|
||||
'allowed_cat_ids' => is_array($settings['post_article_cat'] ?? null) ? array_values(array_filter(array_map('intval', $settings['post_article_cat']))) : [],
|
||||
'default_user_id' => max(0, (int) ($settings['post_article_user'] ?? 1)),
|
||||
'allow_tags' => !isset($settings['post_article_tag']) || !empty($settings['post_article_tag']),
|
||||
'allow_topics' => !empty($settings['post_article_topics_s']),
|
||||
'allowed_topic_ids' => is_array($settings['post_article_topics'] ?? null) ? array_values(array_filter(array_map('absint', $settings['post_article_topics']))) : [],
|
||||
'custom_filter_s' => !isset($settings['post_article_custom_filter_s']) || !empty($settings['post_article_custom_filter_s']),
|
||||
'pay_free_s' => !isset($settings['post_article_pay_free_s']) || !empty($settings['post_article_pay_free_s']),
|
||||
'pay_price_min' => max(0, (int) ($cash_limit['min'] ?? 0)),
|
||||
@@ -539,6 +544,8 @@ class PostDisplay {
|
||||
'cat_lists' => self::sanitize_filter_option_lists($item['cat_lists'] ?? []),
|
||||
'tag_s' => !empty($item['tag_s']),
|
||||
'tag_lists' => self::sanitize_filter_option_lists($item['tag_lists'] ?? []),
|
||||
'topics_s' => !empty($item['topics_s']),
|
||||
'topics_lists' => self::sanitize_filter_option_lists($item['topics_lists'] ?? []),
|
||||
'orderby_s' => !empty($item['orderby_s']),
|
||||
'orderby_lists' => self::sanitize_filter_orderby_lists($item['orderby_lists'] ?? []),
|
||||
'custom_filters' => is_array($item['custom_filters'] ?? null)
|
||||
|
||||
@@ -108,7 +108,7 @@ class ThemeSearch {
|
||||
$items = preg_split('/[,,\s]+/', (string) $raw) ?: [];
|
||||
}
|
||||
$items = array_map(static fn($k) => trim(sanitize_key((string) $k)), $items);
|
||||
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'coment', 'user'];
|
||||
$valid = ['title', 'content', 'excerpt', 'category', 'post_tag', 'topics', 'coment', 'user'];
|
||||
return array_values(array_intersect(array_filter(array_unique($items)), $valid));
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ class ThemeSearch {
|
||||
$terms = [(string) $query->get('s')];
|
||||
}
|
||||
$groups = [];
|
||||
$taxonomies = ['post_tag', 'category'];
|
||||
$taxonomies = ['post_tag', 'category', 'topics'];
|
||||
foreach ($terms as $term) {
|
||||
$like = (!empty($query->query_vars['exact']) ? '' : '%') . $wpdb->esc_like((string) $term) . (!empty($query->query_vars['exact']) ? '' : '%');
|
||||
$parts = [];
|
||||
@@ -212,7 +212,7 @@ class ThemeSearch {
|
||||
if (in_array('user', $keys, true)) {
|
||||
$join .= " LEFT JOIN {$wpdb->users} AS jio_search_users ON ({$wpdb->posts}.post_author = jio_search_users.ID)";
|
||||
}
|
||||
if (array_intersect(['post_tag', 'category'], $keys)) {
|
||||
if (array_intersect(['post_tag', 'category', 'topics'], $keys)) {
|
||||
$join .= " LEFT JOIN {$wpdb->term_relationships} AS jio_search_tr ON ({$wpdb->posts}.ID = jio_search_tr.object_id)";
|
||||
$join .= " LEFT JOIN {$wpdb->term_taxonomy} AS jio_search_tt ON (jio_search_tr.term_taxonomy_id = jio_search_tt.term_taxonomy_id)";
|
||||
$join .= " LEFT JOIN {$wpdb->terms} AS jio_search_terms ON (jio_search_tt.term_id = jio_search_terms.term_id)";
|
||||
|
||||
@@ -206,8 +206,11 @@ final class FormRenderer {
|
||||
}
|
||||
|
||||
private static function terms(string $id, string $name, array $field, $value): void {
|
||||
$taxonomy = (string) ($field['taxonomy'] ?? 'category');
|
||||
$terms = get_terms(['taxonomy' => $taxonomy, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
|
||||
$taxonomies = array_values(array_unique(array_filter(array_map('sanitize_key', (array) ($field['taxonomy'] ?? 'category')))));
|
||||
if ($taxonomies === []) {
|
||||
$taxonomies = ['category'];
|
||||
}
|
||||
$terms = get_terms(['taxonomy' => $taxonomies, 'hide_empty' => false, 'number' => 500, 'orderby' => 'name']);
|
||||
if (is_wp_error($terms)) {
|
||||
$terms = [];
|
||||
}
|
||||
@@ -216,7 +219,7 @@ final class FormRenderer {
|
||||
foreach ($terms as $term) {
|
||||
$by_id[(int) $term->term_id] = $term;
|
||||
}
|
||||
echo '<div class="jio-widget-terms" data-jio-terms="' . esc_attr($taxonomy) . '">';
|
||||
echo '<div class="jio-widget-terms" data-jio-terms="' . esc_attr(implode(',', $taxonomies)) . '">';
|
||||
echo '<input type="hidden" class="jio-widget-terms__value" id="' . esc_attr($id) . '" name="' . esc_attr($name) . '" value="' . esc_attr(implode(',', $selected)) . '">';
|
||||
echo '<div class="jio-widget-terms__selected">';
|
||||
foreach ($selected as $term_id) {
|
||||
@@ -226,7 +229,7 @@ final class FormRenderer {
|
||||
echo '<span class="jio-widget-terms__chip" data-term-id="' . esc_attr((string) $term_id) . '"><span class="dashicons dashicons-move" aria-hidden="true"></span>' . esc_html((string) $by_id[$term_id]->name) . '<button type="button" class="button-link-delete jio-widget-terms__remove" aria-label="' . esc_attr__('移除', 'jio') . '">×</button></span>';
|
||||
}
|
||||
echo '</div>';
|
||||
echo '<input type="search" class="widefat jio-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'jio') . '" aria-label="' . esc_attr__('筛选可选分类', 'jio') . '">';
|
||||
echo '<input type="search" class="widefat jio-widget-terms__search" placeholder="' . esc_attr__('输入关键词筛选', 'jio') . '" aria-label="' . esc_attr__('筛选可选术语', 'jio') . '">';
|
||||
echo '<div class="jio-widget-terms__options">';
|
||||
foreach ($terms as $term) {
|
||||
$term_id = (int) $term->term_id;
|
||||
|
||||
@@ -53,6 +53,14 @@ final class PostQuery {
|
||||
if ($tags) {
|
||||
$args['tag__in'] = $tags;
|
||||
}
|
||||
$topics = RendererHelpers::ids((string) ($config['topics'] ?? ''));
|
||||
if ($topics) {
|
||||
$args['tax_query'] = [[
|
||||
'taxonomy' => 'topics',
|
||||
'field' => 'term_id',
|
||||
'terms' => $topics,
|
||||
]];
|
||||
}
|
||||
|
||||
$days = max(0, (int) ($config['limit_day'] ?? 0));
|
||||
if ($days > 0) {
|
||||
|
||||
@@ -110,7 +110,7 @@ final class ContentRenderer {
|
||||
if (!$term_ids) {
|
||||
return '';
|
||||
}
|
||||
$terms = get_terms(['taxonomy' => 'category', 'include' => $term_ids, 'orderby' => 'include']);
|
||||
$terms = get_terms(['taxonomy' => ['category', 'topics'], 'include' => $term_ids, 'orderby' => 'include']);
|
||||
if (is_wp_error($terms) || !$terms) {
|
||||
return '';
|
||||
}
|
||||
@@ -134,7 +134,7 @@ final class ContentRenderer {
|
||||
$card = [
|
||||
'type' => (string) ($instance['type'] ?? 'style-1'),
|
||||
'class' => 'mb10',
|
||||
'img' => jio_widget_term_cover_url((int) $term->term_id),
|
||||
'img' => jio_widget_term_cover_url((int) $term->term_id, false, (string) $term->taxonomy),
|
||||
'alt' => $name . '-' . $description,
|
||||
'link' => ['url' => esc_url((string) $href), 'target' => $target_blank],
|
||||
'text1' => $name,
|
||||
@@ -179,8 +179,13 @@ final class ContentRenderer {
|
||||
$animation = (string) ($instance['obs_animation'] ?? '');
|
||||
$html = '';
|
||||
foreach ($term_ids as $key => $term_id) {
|
||||
$term = get_term((int) $term_id);
|
||||
if (!$term instanceof \WP_Term || !in_array($term->taxonomy, ['category', 'topics'], true)) {
|
||||
continue;
|
||||
}
|
||||
$card = jio_widget_term_aggregation([
|
||||
'term_id' => (int) $term_id,
|
||||
'taxonomy' => (string) $term->taxonomy,
|
||||
'target_blank' => !empty($instance['target_blank']),
|
||||
'orderby' => (string) ($instance['orderby'] ?? 'modified'),
|
||||
'order' => (($instance['order'] ?? 'desc') === 'asc') ? 'ASC' : 'DESC',
|
||||
|
||||
@@ -76,7 +76,7 @@ final class WidgetSchemas {
|
||||
];
|
||||
case 'tag_cloud':
|
||||
return [
|
||||
['id' => 'taxonomy', 'type' => 'select', 'label' => '分类法', 'default' => 'post_tag', 'options' => ['post_tag' => '标签', 'category' => '分类']],
|
||||
['id' => 'taxonomy', 'type' => 'select', 'label' => '分类法', 'default' => 'post_tag', 'options' => ['post_tag' => '标签', 'category' => '分类', 'topics' => '专题']],
|
||||
['id' => 'number', 'type' => 'spinner', 'label' => '数量', 'default' => 20, 'min' => 1, 'max' => 100],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序', 'default' => 'count', 'options' => ['count' => '内容数量', 'name' => '名称', 'term_id' => 'ID', 'rand' => '随机']],
|
||||
['id' => 'show_count', 'type' => 'switcher', 'label' => '显示数量', 'default' => false],
|
||||
@@ -249,6 +249,7 @@ final class WidgetSchemas {
|
||||
return [
|
||||
['id' => 'load_mode', 'type' => 'radio', 'label' => '模块加载方式', 'default' => 'ajax', 'options' => ['detail' => '直接加载', 'ajax' => 'ajax懒加载'], 'description' => 'ajax懒加载:当页面加载完后,根据用户需要在加载当前模块内容,可提高页面渲染效率'],
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
|
||||
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids', 'description' => '填写专题 ID,多个 ID 用逗号隔开'],
|
||||
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
@@ -276,6 +277,7 @@ final class WidgetSchemas {
|
||||
'fields' => [
|
||||
['id' => 'title', 'type' => 'textarea', 'label' => '标题(必填)', 'default' => '', 'allow_html' => true, 'attributes' => ['rows' => 1], 'description' => '栏目显示的标题,支持HTML代码,注意代码规范'],
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids'],
|
||||
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids'],
|
||||
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'limit_day', 'type' => 'spinner', 'label' => '发布时间限制', 'default' => 0, 'min' => 0, 'step' => 5, 'unit' => '天', 'description' => '仅显示最近多少天发布的文章,为0则不限制'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
@@ -288,6 +290,7 @@ final class WidgetSchemas {
|
||||
private static function oneline_posts_fields(): array {
|
||||
return [
|
||||
['id' => 'cat', 'type' => 'text', 'label' => '分类限制', 'default' => '', 'sanitize' => 'reference_ids', 'description' => '限制请填写对应的ID,多个ID请用英文逗号隔开。如:1,2,3。支持负数进行排除,例如:-1,-2,-3。'],
|
||||
['id' => 'topics', 'type' => 'text', 'label' => '专题限制', 'default' => '', 'sanitize' => 'ids', 'description' => '填写专题 ID,多个 ID 用逗号隔开'],
|
||||
['id' => 'pay_type', 'type' => 'checkbox', 'label' => '商品类型筛选', 'default' => [], 'options' => self::pay_types(), 'description' => '不做其它筛选'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => '', 'options' => self::posts_orderby()],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序']],
|
||||
@@ -312,7 +315,7 @@ final class WidgetSchemas {
|
||||
|
||||
private static function term_cards_fields(): array {
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有封面则不会显示'],
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类、专题', 'taxonomy' => ['category', 'topics'], 'default' => [], 'description' => '选择并排序需要的分类、专题,如没有封面则不会显示'],
|
||||
['id' => 'type', 'type' => 'radio', 'label' => '卡片样式', 'default' => 'style-1', 'options' => ['style-1' => '简单样式', 'style-2' => '样式二', 'style-3' => '样式三', 'style-4' => '样式四']],
|
||||
['id' => 'height_scale', 'type' => 'spinner', 'label' => '卡片长宽比例', 'default' => 60, 'min' => 20, 'max' => 300, 'step' => 5, 'unit' => '%'],
|
||||
['id' => 'pc_row', 'type' => 'button_set', 'label' => 'PC端单行排列数量', 'default' => 2, 'options' => self::row_options()],
|
||||
@@ -325,7 +328,7 @@ final class WidgetSchemas {
|
||||
private static function term_aggregation_fields(): array {
|
||||
return array_merge(self::obs_animation_fields(), [
|
||||
['id' => 'pc_row', 'type' => 'slider', 'label' => '单行布局', 'default' => 2, 'min' => 1, 'max' => 2, 'step' => 1, 'unit' => '个', 'description' => '请根据此模块放置位置的宽度合理调整单行数量'],
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类', 'taxonomy' => 'category', 'default' => [], 'description' => '选择并排序需要的分类,如选择的分类下没有文章则不会显示'],
|
||||
['id' => 'term_id', 'type' => 'terms', 'label' => '添加分类、专题', 'taxonomy' => ['category', 'topics'], 'default' => [], 'description' => '选择并排序需要的分类、专题,如没有文章则不会显示'],
|
||||
['id' => 'orderby', 'type' => 'select', 'label' => '排序方式', 'default' => 'modified', 'options' => self::posts_orderby(), 'dependency' => ['term_id', '!=', '']],
|
||||
['id' => 'order', 'type' => 'radio', 'label' => '顺序', 'default' => 'desc', 'options' => ['desc' => '降序', 'asc' => '升序'], 'dependency' => ['term_id', '!=', '']],
|
||||
['id' => 'count', 'type' => 'spinner', 'label' => '最大文章数量', 'default' => 4, 'min' => 1, 'max' => 20, 'step' => 1, 'unit' => '篇', 'dependency' => ['term_id', '!=', ''], 'description' => '<div class="c-yellow">请确保所选的分类内的文章数量均超过此数量,否则会出现布局错位的问题!</div>'],
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
/**
|
||||
* Topic archive template.
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
get_header();
|
||||
|
||||
$term = get_queried_object();
|
||||
if (!$term instanceof WP_Term || $term->taxonomy !== 'topics') {
|
||||
get_footer();
|
||||
return;
|
||||
}
|
||||
|
||||
$settings = \Jio\Core\TermMeta::resolve_archive_settings($term);
|
||||
$has_sidebar = jio_archive_has_sidebar(!empty($settings['show_sidebar']));
|
||||
$sort_state = jio_archive_get_sort_state((array) ($settings['orderby_option'] ?? []));
|
||||
$filter_groups = [];
|
||||
|
||||
foreach ([
|
||||
'category' => ['key' => 'ajax_category', 'label' => __('分类', 'jio')],
|
||||
'post_tag' => ['key' => 'ajax_tag', 'label' => __('标签', 'jio')],
|
||||
'topics' => ['key' => 'ajax_topics', 'label' => __('专题', 'jio')],
|
||||
] as $taxonomy => $filter) {
|
||||
$key = $filter['key'];
|
||||
if (empty($settings[$key . '_enabled'])) {
|
||||
continue;
|
||||
}
|
||||
$group = jio_archive_build_term_filter_group($taxonomy, (array) ($settings[$key] ?? []), $filter['label']);
|
||||
if ($group) {
|
||||
$filter_groups[] = $group;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($settings['custom_filter_enabled'])) {
|
||||
$filter_groups = array_merge(
|
||||
$filter_groups,
|
||||
jio_archive_build_custom_filter_groups(
|
||||
(array) ($settings['custom_dimensions'] ?? []),
|
||||
$settings['custom_filter_keys'] ?? 'all'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
jio_archive_open_shell([
|
||||
'main_class' => 'site-main container',
|
||||
'has_sidebar' => $has_sidebar,
|
||||
'top_fluid' => ['all_top_fluid', 'cat_top_fluid'],
|
||||
'page_kind' => 'topics',
|
||||
]);
|
||||
|
||||
jio_archive_render_widget_zone(['cat_top_content']);
|
||||
jio_archive_render_term_header([
|
||||
'header_style' => $settings['header_style'],
|
||||
'show_cover' => !empty($settings['show_cover']),
|
||||
'cover_url' => (string) ($settings['cover_url'] ?? ''),
|
||||
'title' => $term->name,
|
||||
'description' => !empty($settings['show_desc']) ? $settings['description'] : '',
|
||||
'post_count' => !empty($settings['post_count']),
|
||||
]);
|
||||
|
||||
ob_start();
|
||||
if (!empty($settings['filter_bar'])) {
|
||||
jio_archive_render_filter_bar([
|
||||
'aria_label' => __('专题筛选', 'jio'),
|
||||
'groups' => $filter_groups,
|
||||
'sort_enabled' => !empty($settings['orderby_enabled']),
|
||||
'sort_current' => $sort_state['current'],
|
||||
'sort_links' => $sort_state['links'],
|
||||
'sort_labels' => \Jio\Settings\Settings::homepage_sort_labels(),
|
||||
'sort_option' => (array) ($settings['orderby_option'] ?? []),
|
||||
]);
|
||||
}
|
||||
$filter_html = (string) ob_get_clean();
|
||||
|
||||
jio_archive_render_stream(null, jio_archive_get_render_config(['context' => 'topics']), [
|
||||
'page_kind' => 'topics',
|
||||
'query_context' => 'archive',
|
||||
'empty_text' => __('该专题下暂无文章', 'jio'),
|
||||
'filter_html' => $filter_html,
|
||||
]);
|
||||
|
||||
jio_archive_render_widget_zone(['cat_bottom_content']);
|
||||
jio_archive_close_shell([
|
||||
'has_sidebar' => $has_sidebar,
|
||||
'bottom_fluid' => ['cat_bottom_fluid', 'all_bottom_fluid'],
|
||||
]);
|
||||
get_footer();
|
||||
@@ -96,6 +96,12 @@ $categories = array_map(
|
||||
\Jio\Core\SubmitCategories::tree($settings)
|
||||
);
|
||||
$current_cats = $edit_post ? wp_get_post_categories($edit_post->ID) : [];
|
||||
$topics = array_map(
|
||||
static fn(array $node): \WP_Term => $node['term'],
|
||||
\Jio\Core\SubmitTopics::tree($settings)
|
||||
);
|
||||
$current_topics = $edit_post ? wp_get_post_terms($edit_post->ID, 'topics', ['fields' => 'ids']) : [];
|
||||
$current_topics = is_array($current_topics) ? array_map('intval', $current_topics) : [];
|
||||
|
||||
$custom_dimensions = [];
|
||||
$current_cf = [];
|
||||
@@ -181,6 +187,8 @@ return [
|
||||
'featured_args' => $featured_args,
|
||||
'categories' => $categories,
|
||||
'current_cats' => $current_cats,
|
||||
'topics' => $topics,
|
||||
'current_topics' => $current_topics,
|
||||
'custom_dimensions' => $custom_dimensions,
|
||||
'current_cf' => $current_cf,
|
||||
'current_title' => $current_title,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Reference-theme article parameters sidebar.
|
||||
* Article parameters sidebar.
|
||||
*
|
||||
* @var array $ctx
|
||||
* @package Jio
|
||||
@@ -41,6 +41,16 @@ require_once get_template_directory() . '/templates/parts/submit/term-checks.php
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (!empty($settings['allow_topics']) && !empty($ctx['topics'])) : ?>
|
||||
<div class="newpost-topics-drop drop-select dropdown dependency-box">
|
||||
<button type="button" data-toggle-class="open" data-target=".newpost-topics-drop" class="flex ac jsb drop-btn form-control mb6">
|
||||
<span class="title-theme222"><i class="fa fas fa-cube mr6" aria-hidden="true"></i><?php esc_html_e('专题', 'jio'); ?></span><i class="ml6 fa fas fa-angle-right em12"></i>
|
||||
</button>
|
||||
<div class="dropdown-menu fluid"><div class="mini-scrollbar padding-w10 select-drop-box">
|
||||
<?php jio_submit_render_term_checks($ctx['topics'], $ctx['current_topics'], 'topics'); ?>
|
||||
</div></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($settings['custom_filter_s']) && !empty($ctx['custom_dimensions'])) : ?>
|
||||
<?php foreach ($ctx['custom_dimensions'] as $dimension) :
|
||||
|
||||
@@ -13,11 +13,20 @@ defined('ABSPATH') || exit;
|
||||
if (!function_exists('jio_submit_render_term_checks')) :
|
||||
function jio_submit_render_term_checks(array $terms, array $checked, string $name, int $parent = 0, int $depth = 0): void {
|
||||
$children_map = [];
|
||||
$present = [];
|
||||
foreach ($terms as $term) {
|
||||
if ($term instanceof WP_Term) {
|
||||
$present[(int) $term->term_id] = true;
|
||||
}
|
||||
}
|
||||
foreach ($terms as $term) {
|
||||
if (!$term instanceof WP_Term) {
|
||||
continue;
|
||||
}
|
||||
$pid = (int) $term->parent;
|
||||
if ($pid !== 0 && !isset($present[$pid])) {
|
||||
$pid = 0;
|
||||
}
|
||||
if (!isset($children_map[$pid])) {
|
||||
$children_map[$pid] = [];
|
||||
}
|
||||
@@ -38,20 +47,6 @@ if (!function_exists('jio_submit_render_term_checks')) :
|
||||
}
|
||||
};
|
||||
|
||||
// If include list was used without hierarchy, still render flat.
|
||||
if (empty($children_map) && !empty($terms)) {
|
||||
foreach ($terms as $term) {
|
||||
if (!$term instanceof WP_Term) {
|
||||
continue;
|
||||
}
|
||||
$tid = (int) $term->term_id;
|
||||
echo '<div><label class="jio-submit-check">';
|
||||
echo '<input type="checkbox" name="' . esc_attr($name) . '[]" value="' . esc_attr((string) $tid) . '"' . (in_array($tid, $checked, true) ? ' checked' : '') . '>';
|
||||
echo '<span>' . esc_html($term->name) . '</span></label></div>';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$render($parent, $depth);
|
||||
}
|
||||
endif;
|
||||
@@ -1,9 +1,4 @@
|
||||
/**
|
||||
* 高级筛选设置页参考主题 1:1 契约。
|
||||
*
|
||||
* 参考实现「高级筛选」区块与前台链路(分类/标签归档的 AJAX 选项菜单、自定义
|
||||
* 筛选按钮、每页筛选条数)。
|
||||
*/
|
||||
/** 高级筛选设置页与归档查询的字段契约。 */
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const fs = require('node:fs');
|
||||
@@ -23,9 +18,9 @@ const tagTpl = read('tag.php');
|
||||
const listJs = read('assets/js/runtime/main.js');
|
||||
const globalController = read('src/Admin/GlobalController.php');
|
||||
|
||||
// 参考主题字段键(顺序即参考主题定义顺序)。
|
||||
// 保存字段的顺序。
|
||||
const REFERENCE_KEYS = ['custom_filter_show', 'custom_filter', 'cat_custom_filter'];
|
||||
// Jio 私有旧字段(参考主题无此配置,须移除避免双路)。
|
||||
// 已移除的旧字段。
|
||||
const LEGACY_KEYS = [
|
||||
'filter_enabled',
|
||||
'filter_categories',
|
||||
@@ -37,8 +32,6 @@ const LEGACY_KEYS = [
|
||||
'filter_tag_show_cat',
|
||||
'filter_tag_show_tag',
|
||||
];
|
||||
// 参考主题字段中本项目不做的部分(无 topics 专题分类法)。
|
||||
const EXCLUDED_KEYS = ['topics_s', 'topics_lists'];
|
||||
|
||||
test('JS schema 字段键与顺序对齐参考主题', () => {
|
||||
const found = [...js.matchAll(/key:\s*'([a-z0-9_]+)',\s*(?:label|type):/g)].map((m) => m[1]);
|
||||
@@ -53,7 +46,7 @@ test('JS schema 字段键与顺序对齐参考主题', () => {
|
||||
assert.ok(new RegExp(`key:\\s*'${sub}',\\s*label:`).test(js), `custom_filter 缺少子字段 ${sub}`);
|
||||
}
|
||||
const catSub = js.slice(js.indexOf("key: 'cat_custom_filter'"));
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'topics_s', 'topics_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catIdx = catOrder.map((k) => catSub.indexOf(`key: '${k}'`));
|
||||
assert.ok(catIdx.every((v) => v !== -1), 'cat_custom_filter 缺少子字段');
|
||||
assert.ok(catIdx.every((v, i) => i === 0 || v > catIdx[i - 1]), 'cat_custom_filter 子字段顺序应符合参考主题');
|
||||
@@ -66,7 +59,7 @@ test('PHP schema 字段键与顺序对齐参考主题', () => {
|
||||
const order = REFERENCE_KEYS.map((k) => filterSection.indexOf(`'${k}'`));
|
||||
assert.ok(order.every((v) => v !== -1), `PHP schema 缺少字段: ${REFERENCE_KEYS.filter((k) => order.indexOf(-1) === -1)}`);
|
||||
assert.ok(order.every((v, i) => i === 0 || v > order[i - 1]), 'PHP 字段顺序应符合参考主题');
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catOrder = ['cats', 'cat_s', 'cat_lists', 'tag_s', 'tag_lists', 'topics_s', 'topics_lists', 'orderby_s', 'orderby_lists', 'custom_filters'];
|
||||
const catSub = filterSection.slice(filterSection.indexOf("'cat_custom_filter'"));
|
||||
const catIdx = catOrder.map((k) => catSub.indexOf(`'${k}'`));
|
||||
assert.ok(catIdx.every((v) => v !== -1), 'PHP cat_custom_filter 缺少子字段');
|
||||
@@ -77,36 +70,36 @@ test('JS schema 默认值、选项与依赖对齐参考主题', () => {
|
||||
assert.match(js, /custom_filter_show:\s*\[\]/);
|
||||
assert.match(js, /custom_filter:\s*\[\]/);
|
||||
assert.match(js, /cat_custom_filter:\s*\[\]/);
|
||||
for (const option of ['分类页面显示', '标签页面显示']) {
|
||||
for (const option of ['分类页面显示', '标签页面显示', '专题页面显示']) {
|
||||
assert.ok(js.includes(option), `custom_filter_show 缺少选项 ${option}`);
|
||||
}
|
||||
// 排序方式默认值与参考主题一致(orderby() default)。
|
||||
// 排序方式默认值。
|
||||
assert.match(js, /default:\s*\[\s*'modified',\s*'views',\s*'like',\s*'comment_count'\s*\]/);
|
||||
for (const option of ['更新', '发布', '浏览', '点赞', '评论', '收藏', '随机']) {
|
||||
assert.ok(js.includes(`label: '${option}'`), `排序方式缺少选项 ${option}`);
|
||||
}
|
||||
// 依赖链(参考主题 dependency)。
|
||||
// 表单依赖链。
|
||||
assert.match(js, /key: 'cat_lists',[\s\S]{0,200}?dependsOn:\s*\{\s*key:\s*'cat_s',\s*value:\s*true\s*\}/);
|
||||
assert.match(js, /key: 'tag_lists',[\s\S]{0,200}?dependsOn:\s*\{\s*key:\s*'tag_s',\s*value:\s*true\s*\}/);
|
||||
assert.match(js, /key: 'topics_lists',[\s\S]{0,200}?dependsOn:\s*\{\s*key:\s*'topics_s',\s*value:\s*true\s*\}/);
|
||||
assert.match(js, /key: 'orderby_lists',[\s\S]{0,220}?dependsOn:\s*\{\s*key:\s*'orderby_s',\s*value:\s*true\s*\}/);
|
||||
// custom_filters 为动态选项 checkbox(引用 custom_filter 维度定义)。
|
||||
assert.match(js, /key: 'custom_filters',[\s\S]{0,400}?options:\s*'custom_filter'/);
|
||||
// 提示行承载筛选说明,且不得再指向第三方站点。
|
||||
assert.match(js, /type:\s*'content'/);
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
assert.doesNotMatch(js, /zibll\.com/);
|
||||
assert.match(js, /避免与其他mate_key重复/);
|
||||
});
|
||||
|
||||
test('旧字段与 topics 在 schema 中均已移除(无双路)', () => {
|
||||
test('旧字段已移除,专题筛选由单一字段组驱动', () => {
|
||||
for (const legacy of LEGACY_KEYS) {
|
||||
assert.ok(!js.includes(legacy), `JS schema 残留旧字段: ${legacy}`);
|
||||
assert.ok(!php.includes(legacy), `PHP schema 残留旧字段: ${legacy}`);
|
||||
assert.ok(!postDisplay.includes(`$settings['${legacy}']`), `PostDisplay 残留旧字段读取: ${legacy}`);
|
||||
}
|
||||
for (const excluded of EXCLUDED_KEYS) {
|
||||
assert.ok(!js.includes(excluded), `JS schema 不应包含 ${excluded}(本项目无专题分类法)`);
|
||||
assert.ok(!php.includes(excluded), `PHP schema 不应包含 ${excluded}`);
|
||||
for (const key of ['topics_s', 'topics_lists']) {
|
||||
assert.ok(js.includes(`key: '${key}'`), `JS schema 缺少 ${key}`);
|
||||
assert.ok(php.includes(`'key' => '${key}'`), `PHP schema 缺少 ${key}`);
|
||||
}
|
||||
// 运行时只读新键。
|
||||
assert.match(postDisplay, /\$settings\['custom_filter_show'\]/);
|
||||
@@ -119,7 +112,7 @@ test('JS schema 与 PHP schema 一致性', () => {
|
||||
for (const key of REFERENCE_KEYS) {
|
||||
assert.ok(php.includes(`'${key}'`), `PHP schema 缺少 ${key}`);
|
||||
}
|
||||
// addText 与参考主题 button_title 一致。
|
||||
// 添加按钮文案。
|
||||
assert.match(js, /addText:\s*'添加筛选类别'/);
|
||||
assert.match(js, /addText:\s*'添加值'/);
|
||||
assert.match(js, /addText:\s*'添加自定义分类筛选'/);
|
||||
@@ -147,7 +140,7 @@ test('前台链路:覆盖与门禁', () => {
|
||||
});
|
||||
|
||||
test('前台链路:维度筛选渲染与查询过滤', () => {
|
||||
// 渲染:维度 chip 组(全部 chip 参考主题 focus 语义)。
|
||||
// 渲染维度按钮组。
|
||||
assert.match(filtersHelper, /function jio_archive_build_custom_filter_groups\(/);
|
||||
assert.match(filtersHelper, /ajax-replace="true" class="ajax-next/);
|
||||
assert.match(filtersHelper, /\$keys !== 'all' && !in_array\(\$dim_key, \(array\) \$keys, true\)/);
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* 「前台投稿」后台设置页契约(对齐参考主题 1:1,不含专题):
|
||||
* 1) PHP schema(SchemaPostList.php submit section)字段键、顺序、默认值与
|
||||
* JS schema(assets/admin/src/schema/sections/post-list/submit.js)一致;
|
||||
* 2) 参考主题字段全集存在:post_article_s / post_article_btn_txte / post_article_cat /
|
||||
* post_article_custom_filter_s / post_article_tag / post_article_user /
|
||||
* post_article_title_strlen_limit / post_article_pay_free_s /
|
||||
* post_article_pay_price_limit / post_article_pay_vip_price_s /
|
||||
* post_article_pay_vip_1_discount / post_article_pay_vip_2_discount /
|
||||
* post_t_placeholder / post_c_placeholder;
|
||||
* 3) 专题(topics)相关字段不存在;
|
||||
* 4) 旧 Jio 私有字段(front_submit_*、post_article_title_strlen_limit_min/max、
|
||||
* post_article_pay_price_min/max)已彻底移除,无双路兼容;
|
||||
* 5) 运行时(PostDisplay::get_submit_settings)只读取新键。
|
||||
*/
|
||||
/** 前台投稿设置的字段顺序、默认值与运行时契约。 */
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
@@ -30,6 +16,8 @@ const REFERENCE_KEYS = [
|
||||
'post_article_s',
|
||||
'post_article_btn_txte',
|
||||
'post_article_cat',
|
||||
'post_article_topics_s',
|
||||
'post_article_topics',
|
||||
'post_article_custom_filter_s',
|
||||
'post_article_tag',
|
||||
'post_article_user',
|
||||
@@ -114,9 +102,9 @@ test('PHP submit section 字段键与参考主题一致且顺序相同', () => {
|
||||
assert.deepEqual(found, PHP_FIELD_ORDER);
|
||||
});
|
||||
|
||||
test('PHP submit section 不含专题字段', () => {
|
||||
assert.doesNotMatch(submitPhp, /post_article_topics/);
|
||||
assert.doesNotMatch(submitPhp, /'topics'/);
|
||||
test('PHP submit section 包含专题开关与可选专题', () => {
|
||||
assert.match(submitPhp, /'post_article_topics_s',\s*'label'\s*=>\s*'允许选择专题',\s*'type'\s*=>\s*'switch',\s*'default'\s*=>\s*false/);
|
||||
assert.match(submitPhp, /'post_article_topics',\s*'label'\s*=>\s*'投稿允许选择的专题',\s*'type'\s*=>\s*'taxonomy_select',\s*'taxonomy'\s*=>\s*'topics'/);
|
||||
});
|
||||
|
||||
test('PHP submit section 默认值对齐参考主题', () => {
|
||||
@@ -154,7 +142,7 @@ test('JS schema 与 PHP 字段键一致(ui_only content 行除外)', () => {
|
||||
for (const legacy of LEGACY_KEYS) {
|
||||
assert.ok(!keys.includes(legacy), `JS schema 残留旧字段: ${legacy}`);
|
||||
}
|
||||
// 两个 ui_only 提示行承载参考主题 desc 中的【去设置】链接。
|
||||
// 两个 ui_only 提示行承载【去设置】链接。
|
||||
assert.match(js, /permissions\/capabilities/);
|
||||
assert.match(js, /post-list\/filter/);
|
||||
assert.match(js, /ui_only:\s*true/);
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
define('ABSPATH', __DIR__);
|
||||
|
||||
class WP_Term {
|
||||
public function __construct(
|
||||
public int $term_id,
|
||||
public int $parent,
|
||||
public string $name
|
||||
) {}
|
||||
}
|
||||
|
||||
function absint($value): int { return abs((int) $value); }
|
||||
function esc_attr($value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); }
|
||||
function esc_html($value): string { return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8'); }
|
||||
function get_terms(array $args): array {
|
||||
if ($args['taxonomy'] !== 'topics') return [];
|
||||
return [new WP_Term(1, 0, 'Parent'), new WP_Term(2, 1, 'Child'), new WP_Term(3, 1, 'Sibling'), new WP_Term(4, 0, 'Other')];
|
||||
}
|
||||
function get_term_children(int $id, string $taxonomy): array {
|
||||
return $taxonomy === 'topics' && $id === 1 ? [2, 3] : [];
|
||||
}
|
||||
function term_exists(int $id, string $taxonomy): bool {
|
||||
return $taxonomy === 'topics' && in_array($id, [1, 2, 3, 4], true);
|
||||
}
|
||||
|
||||
require dirname(__DIR__, 2) . '/src/Core/SubmitTopics.php';
|
||||
require dirname(__DIR__, 2) . '/templates/parts/submit/term-checks.php';
|
||||
|
||||
$root = ['allow_topics' => true, 'allowed_topic_ids' => [1]];
|
||||
$child = ['allow_topics' => true, 'allowed_topic_ids' => [2]];
|
||||
$disabled = ['allow_topics' => false, 'allowed_topic_ids' => []];
|
||||
$tree = static fn(array $settings): array => array_map(
|
||||
static fn(array $node): array => ['id' => $node['term']->term_id, 'depth' => $node['depth']],
|
||||
\Jio\Core\SubmitTopics::tree($settings)
|
||||
);
|
||||
|
||||
ob_start();
|
||||
jio_submit_render_term_checks([new WP_Term(2, 1, 'Child')], [], 'topics');
|
||||
$orphan_html = ob_get_clean();
|
||||
|
||||
echo json_encode([
|
||||
'root_tree' => $tree($root),
|
||||
'child_tree' => $tree($child),
|
||||
'disabled_tree' => $tree($disabled),
|
||||
'filtered' => \Jio\Core\SubmitTopics::filter_ids([2, 4, 999, 2], $child),
|
||||
'disabled_filtered' => \Jio\Core\SubmitTopics::filter_ids([2], $disabled),
|
||||
'orphan_html' => $orphan_html,
|
||||
], JSON_THROW_ON_ERROR);
|
||||
@@ -0,0 +1,22 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const result = JSON.parse(execFileSync('php', [path.join(__dirname, 'topics-harness.php')], { encoding: 'utf8' }));
|
||||
|
||||
test('topic whitelist includes descendants and preserves a selected child without its parent', () => {
|
||||
assert.deepEqual(result.root_tree, [
|
||||
{ id: 1, depth: 0 },
|
||||
{ id: 2, depth: 1 },
|
||||
{ id: 3, depth: 1 },
|
||||
]);
|
||||
assert.deepEqual(result.child_tree, [{ id: 2, depth: 0 }]);
|
||||
assert.match(result.orphan_html, /name="topics\[\]" value="2"/);
|
||||
});
|
||||
|
||||
test('topic submissions accept only existing terms within the configured whitelist', () => {
|
||||
assert.deepEqual(result.filtered, [2]);
|
||||
assert.deepEqual(result.disabled_tree, []);
|
||||
assert.deepEqual(result.disabled_filtered, []);
|
||||
});
|
||||
@@ -548,7 +548,8 @@ test('publish settings use a multi-select category picker and a creatable tag in
|
||||
assert.match(category, /createPortal/);
|
||||
assert.match(category, /type="checkbox"/);
|
||||
assert.match(category, /onChange\(/);
|
||||
assert.match(category, /选择分类(可多选)/);
|
||||
assert.match(category, /选择%s(可多选)/);
|
||||
assert.match(settings, /label=\{__\('专题', 'jio'\)\}/);
|
||||
// 旧的分类平铺复选列表已移除(自定义分类法仍是复选标签,不受影响)。
|
||||
assert.doesNotMatch(settings, /toggleCategory/);
|
||||
assert.doesNotMatch(settings, /categories\.includes\(/);
|
||||
|
||||
@@ -11,7 +11,7 @@ test('modern widget schemas expose reference field names and defaults', () => {
|
||||
for (const field of ['mini_opt', 'paginate', 'tabs', "'id' => 'cat'", "'id' => 'pay_type'", 'layout_ratio', 'slide_scale', 'm_layout_type', 'm_cover_ratio', 'image_layer', 'image_select']) {
|
||||
assert.match(source, new RegExp(field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')));
|
||||
}
|
||||
assert.doesNotMatch(source, /'id' => 'topics'/);
|
||||
assert.match(source, /'id' => 'topics'/);
|
||||
assert.match(source, /'热门推荐'.*'orderby' => 'views'/s);
|
||||
assert.match(source, /'最近更新'.*'orderby' => 'modified'/s);
|
||||
assert.match(source, /'猜你喜欢'.*'orderby' => 'rand'/s);
|
||||
|
||||
@@ -62,15 +62,15 @@ function topLevelFieldIds(name) {
|
||||
return [...functionBody(name).matchAll(/self::field\('([^']+)'/g)].map(match => match[1])
|
||||
}
|
||||
|
||||
test('page display exposes exactly eight ordered routes', () => {
|
||||
const expected = ['header-nav', 'header-components', 'footer', 'homepage', 'category', 'tag', 'user-home', 'other']
|
||||
test('page display exposes nine ordered routes', () => {
|
||||
const expected = ['header-nav', 'header-components', 'footer', 'homepage', 'category', 'tag', 'topics', 'user-home', 'other']
|
||||
const schemaOrder = [...schema.matchAll(/^\t\t\t\t'([^']+)'\s+=> self::/gm)].map(match => match[1])
|
||||
assert.deepEqual(schemaOrder, expected)
|
||||
const routeOrder = [...routes.matchAll(/section:\s*'([^']+)'/g)].map(match => match[1])
|
||||
assert.deepEqual(routeOrder, expected)
|
||||
})
|
||||
|
||||
test('top-level field IDs match the legacy V9.0 eight-page contract', () => {
|
||||
test('top-level field IDs cover all nine page settings', () => {
|
||||
assert.deepEqual(topLevelFieldIds('header_nav'), [
|
||||
'header_theme_custom', 'header_theme_bg_custom', 'header_theme_bg', 'header_theme_color_custom',
|
||||
'header_layout', 'mobile_header_layout', 'mobile_navbar_align', 'nav_fixed', 'nav_scroll_down_hide',
|
||||
@@ -96,10 +96,17 @@ test('top-level field IDs match the legacy V9.0 eight-page contract', () => {
|
||||
assert.deepEqual(topLevelFieldIds('category'), [
|
||||
'page_cover_cat_s', 'cat_post_count_s', 'cat_default_cover', 'cat_orderby_s', 'cat_orderby_option',
|
||||
'ajax_list_cat_cat', 'ajax_list_option_cat_cat', 'ajax_list_cat_tag', 'ajax_list_option_cat_tag',
|
||||
'ajax_list_cat_topics', 'ajax_list_option_cat_topics',
|
||||
])
|
||||
assert.deepEqual(topLevelFieldIds('tag'), [
|
||||
'tag_post_count_s', 'page_cover_tag_s', 'tag_default_cover', 'tag_orderby_s', 'tag_orderby_option',
|
||||
'ajax_list_tag_cat', 'ajax_list_option_tag_cat', 'ajax_list_tag_tag', 'ajax_list_option_tag_tag',
|
||||
'ajax_list_tag_topics', 'ajax_list_option_tag_topics',
|
||||
])
|
||||
assert.deepEqual(topLevelFieldIds('topics'), [
|
||||
'topics_post_count_s', 'topics_default_cover', 'topics_orderby_s', 'topics_orderby_option',
|
||||
'ajax_list_topics_cat', 'ajax_list_option_topics_cat', 'ajax_list_topics_tag',
|
||||
'ajax_list_option_topics_tag', 'ajax_list_topics_topics', 'ajax_list_option_topics_topics',
|
||||
])
|
||||
assert.deepEqual(topLevelFieldIds('user_home'), ['author_main_tab_opt', 'author_main_tab_swiper'])
|
||||
assert.deepEqual(topLevelFieldIds('other'), ['page_header_style', 'page_header_cover_img'])
|
||||
@@ -115,7 +122,7 @@ test('defaults, nested structures, dependencies, and excluded product families a
|
||||
assert.match(schema, /'header_slider_card_s'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/)
|
||||
assert.match(schema, /'header_slider_filter_blur'.*?'dependsOn' => self::not_empty\('header_slider_show'\)/)
|
||||
assert.match(schema, /'all' => \[self::enabled\('home_posts_list_s'\), self::enabled\('home_list1_orderby_s'\)\]/)
|
||||
for (const dead of ['nav_newposts_btns', 'forum_home', 'forum_plate', 'forum_post', 'ajax_list_cat_topics', 'ajax_list_tag_topics']) {
|
||||
for (const dead of ['nav_newposts_btns', 'forum_home', 'forum_plate', 'forum_post']) {
|
||||
assert.equal(schema.includes(dead), false, `legacy/excluded field remains: ${dead}`)
|
||||
}
|
||||
assert.equal(headerMenuButtons.includes('nav_newposts_btns'), false)
|
||||
@@ -209,7 +216,7 @@ test('schema REST saves are privileged, section-whitelisted, and deeply sanitize
|
||||
assert.match(provider, /wp_kses_post/)
|
||||
assert.match(provider, /esc_url_raw/)
|
||||
assert.match(provider, /has an invalid value/)
|
||||
assert.match(controller, /search\/\(\?P<source>posts\|categories\|tags\)/)
|
||||
assert.match(controller, /search\/\(\?P<source>posts\|categories\|tags\|topics\|terms\)/)
|
||||
assert.match(controller, /'permission_callback' => \[\$this, 'check_permission'\]/)
|
||||
})
|
||||
|
||||
|
||||
@@ -13,15 +13,11 @@ const pay = read('inc/helpers/jio-pay.php')
|
||||
const metabox = read('src/Core/PostMetaBox.php')
|
||||
const resourceBox = read('src/Core/ResourceMetaBox.php')
|
||||
|
||||
test('quick and bulk edit boxes hook the post list with no topics column', () => {
|
||||
// 参考主题把配置项挂在「专题」分类法列上;本项目没有专题,统一挂在互动数据列。
|
||||
test('quick and bulk edit boxes hook the topics taxonomy column', () => {
|
||||
assert.match(list, /quick_edit_custom_box/)
|
||||
assert.match(list, /bulk_edit_custom_box/)
|
||||
assert.match(list, /COLUMN_STATS/)
|
||||
// 代码里不得再出现专题分类法列(类文档块的说明除外,只看 namespace 之后的部分)。
|
||||
const code = list.slice(list.indexOf('namespace Jio'))
|
||||
assert.doesNotMatch(code, /taxonomy-topics/)
|
||||
assert.doesNotMatch(code, /topics/)
|
||||
assert.match(code, /\$column_name !== 'taxonomy-topics'/)
|
||||
})
|
||||
|
||||
test('every quick-edit field group is rendered', () => {
|
||||
|
||||
@@ -87,8 +87,8 @@ test('settings use legacy list modes, grouped options, and destructive related k
|
||||
for (const key of legacyKeys) {
|
||||
assert.equal(source.includes(key), false, `legacy article key remains: ${key}`);
|
||||
}
|
||||
// bare `topics` is legal again: the related-posts query mirrors the legacy taxonomy OR.
|
||||
assert.doesNotMatch(source, /list_card_topics|is_topics/);
|
||||
assert.match(listSchema, /list_card_topics: false/);
|
||||
assert.match(source, /list_card_topics/);
|
||||
});
|
||||
|
||||
test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', () => {
|
||||
@@ -275,7 +275,7 @@ test('related posts, author page and widgets use their dedicated renderers', ()
|
||||
|
||||
// 翻页 / 切 tab 是整页替换语义(author.js 最终就是 panel.innerHTML = html),
|
||||
// 骨架必须独占内容区。若骨架只是追加在旧卡片下方,加载期会先看到
|
||||
// 「第 1 页卡片 + 脚下一摞骨架」再换页。对齐参考主题 post_ajax() 的
|
||||
// 「第 1 页卡片 + 脚下一摞骨架」再换页。
|
||||
// beforeSend:replace 时 $item.remove() + find(pag).remove()。
|
||||
// 刻意不按 data-view 收窄:card 模式下真实卡片同样是直接子级 .posts-item,
|
||||
// 也要一起收起,否则卡片面板旧内容不会让位。
|
||||
@@ -572,7 +572,7 @@ test('profile routes load versioned leaf styles and settings dialogs use the sha
|
||||
assert.match(userCenterCreditsCss, /#panel-credits\.dashboard-panel\s*\{[^}]*background:\s*transparent;[^}]*box-shadow:\s*none;/);
|
||||
assert.match(userCenterCreditsCss, /\.uc-asset-tabs\s*\{[^}]*background:\s*var\(--surface-1,\s*#fff\);[^}]*border:[^;}]+;[^}]*box-shadow:[^;}]+;/);
|
||||
assert.doesNotMatch(profileCredits, /credits-signin\.php|data-jio-signin-calendar/);
|
||||
// 签到详情弹窗为参考主题 1:1 结构(src/Credits/Signin.php),日历已随
|
||||
// 签到详情弹窗的日历已随
|
||||
// 旧版 lite 弹窗一并移除;用户中心里的孤儿渲染器也已整包移除。
|
||||
assert.equal(fs.existsSync(path.join(root, 'inc/helpers/user-center-renderers/credits-signin.php')), false);
|
||||
assert.doesNotMatch(userCenterCreditsCss + read('assets/css/frontend/enhancements/components/custom-amount.css'), /uc-signin-calendar/);
|
||||
|
||||
@@ -113,7 +113,10 @@ final class WidgetSystemTest {
|
||||
], 3);
|
||||
$this->same('1,-2,3', $reference['cat'], 'reference cat preserves cleaned signed IDs for WP_Query descendant semantics');
|
||||
$this->false(isset($reference['category__in']) || isset($reference['category__not_in']), 'reference cat does not narrow matches to direct categories');
|
||||
$this->false(isset($reference['tax_query']), 'topic filters are removed with the topics feature');
|
||||
$this->false(isset($reference['tax_query']), 'queries without a topic filter remain unrestricted');
|
||||
$topic_query = PostQuery::arguments(['count' => 6, 'topics' => '7,8'], 1);
|
||||
$this->same('topics', $topic_query['tax_query'][0]['taxonomy'], 'topic query uses the topics taxonomy');
|
||||
$this->same([7, 8], $topic_query['tax_query'][0]['terms'], 'topic IDs reach the taxonomy query');
|
||||
$this->same('jio_post_views', $reference['meta_key'], 'reference view ordering uses numeric view meta');
|
||||
$this->false(isset($reference['meta_query']), 'modern reference queries do not apply commerce filters by default');
|
||||
$this->same(1, $reference['paged'], 'no-pagination queries stay on first page');
|
||||
@@ -148,7 +151,9 @@ final class WidgetSystemTest {
|
||||
'order' => 'asc',
|
||||
]]]);
|
||||
$this->same('1,-2,3', $tab['tabs'][0]['cat'], 'tab cat keeps signed reference IDs');
|
||||
$this->false(isset($tab['tabs'][0]['topics']), 'topic filters are gone from tab configuration');
|
||||
$this->same('', $tab['tabs'][0]['topics'], 'tab exposes the topic filter');
|
||||
$topic_tab = FieldSchema::normalize($tab_fields, ['tabs' => [['title' => '专题', 'topics' => '7,8']]]);
|
||||
$this->same('7,8', $topic_tab['tabs'][0]['topics'], 'tab preserves selected topic IDs');
|
||||
$this->same(['1', '6'], $tab['tabs'][0]['pay_type'], 'tab commerce filters are preserved');
|
||||
$this->contains('<b>栏目</b>', $tab['tabs'][0]['title'], 'tab title keeps controlled HTML');
|
||||
$this->false(str_contains($tab['tabs'][0]['title'], '<script>'), 'tab title strips unsafe HTML');
|
||||
|
||||
@@ -120,6 +120,7 @@ namespace {
|
||||
function get_the_ID(): int { return (int) ($GLOBALS['tab_fixture_current_post']->ID ?? 0); }
|
||||
function strip_shortcodes(string $value): string { return $value; }
|
||||
function get_the_category(int $id = 0): array { unset($id); return []; }
|
||||
function get_the_terms(int $id, string $taxonomy): array { unset($id, $taxonomy); return []; }
|
||||
function get_the_tags(int $id = 0): array { unset($id); return []; }
|
||||
function get_the_excerpt(int $id = 0): string { unset($id); return '首页通用文章列表的长摘要,这里只用于卡片和列表样式的内容渲染'; }
|
||||
function get_post_field(string $field, int $id = 0, string $context = 'display'): string {
|
||||
|
||||
Reference in new issue
Block a user