fix(creator-studio): caret overlap in toolbar and hidden sub-categories
工具条下拉触发器的下三角原本绝对定位在按钮右下角,按钮宽度不够时会压到 居中的文字上。改为文字 + 三角同行排版(.jio-creator__tool-meta),宽度不足 时只压缩文字。 分类只查询 parent=0,导致二级 / 三级分类在投稿页完全不可选。新增 SubmitCategories 统一解析: - 取全部分类并按父子关系深度优先展平,带上 depth / path; - 白名单按"选了上级即包含其全部子级"展开,未配置白名单时不做额外过滤; - 创作者工作台、经典投稿表单、保存校验与审核应用共用同一份结果。 选择器按 depth 缩进,搜索同时匹配完整路径。 同时把 assets/creator-studio/build 与 assets/admin/build 统一为整目录忽略 (不再跟踪 .gitkeep),避免构建产物反复被误提交。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
330df53195
commit
beb898b1ab
12 files changed
+291
-53
No files matched your search
@@ -127,7 +127,7 @@ final class CreatorRest {
|
||||
? (array) get_post_meta($source->ID, DomainModel::POST_META_KEY, true)
|
||||
: [];
|
||||
|
||||
$terms = $this->term_payload($settings, $source, $snapshot);
|
||||
$terms = $this->term_payload($settings, $source, $snapshot);
|
||||
$viewer = $user_id > 0 ? get_userdata($user_id) : null;
|
||||
$ident = $this->identity_payload($viewer instanceof \WP_User ? $viewer : null);
|
||||
|
||||
@@ -551,21 +551,15 @@ final class CreatorRest {
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function term_payload(array $settings, ?\WP_Post $source, array $snapshot): array {
|
||||
$cat_args = [
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
];
|
||||
if (!empty($settings['allowed_cat_ids'])) {
|
||||
$cat_args['include'] = $settings['allowed_cat_ids'];
|
||||
} else {
|
||||
$cat_args['parent'] = 0;
|
||||
}
|
||||
$terms = get_terms($cat_args);
|
||||
// 分类树按层级深度优先展平,二级 / 三级分类不会因为 parent=0 过滤而消失。
|
||||
$categories = [];
|
||||
foreach (is_array($terms) ? $terms : [] as $term) {
|
||||
$categories[] = ['id' => (int) $term->term_id, 'name' => (string) $term->name];
|
||||
foreach (SubmitCategories::tree($settings) as $node) {
|
||||
$categories[] = [
|
||||
'id' => (int) $node['term']->term_id,
|
||||
'name' => (string) $node['term']->name,
|
||||
'depth' => (int) $node['depth'],
|
||||
'path' => (string) $node['path'],
|
||||
];
|
||||
}
|
||||
|
||||
$current = $source instanceof \WP_Post && $snapshot === []
|
||||
|
||||
@@ -103,10 +103,9 @@ trait FrontSubmitSave {
|
||||
}
|
||||
}
|
||||
|
||||
// Allowed category whitelist.
|
||||
$allowed = $settings['allowed_cat_ids'] ?? [];
|
||||
if (!empty($allowed) && !empty($categories)) {
|
||||
$categories = array_values(array_intersect($categories, $allowed));
|
||||
// Allowed category whitelist: 选中上级分类时,其全部子分类同样可选。
|
||||
if (!empty($categories)) {
|
||||
$categories = SubmitCategories::filter_ids($categories, $settings);
|
||||
if (!$is_draft && empty($categories)) {
|
||||
return ['success' => false, 'message' => __('所选分类不可用', 'jio')];
|
||||
}
|
||||
|
||||
@@ -427,9 +427,8 @@ final class SubmissionVersion {
|
||||
|
||||
if (isset($snapshot['categories']) && is_array($snapshot['categories'])) {
|
||||
$cats = array_values(array_unique(array_filter(array_map('absint', $snapshot['categories']))));
|
||||
$allowed = is_array($settings['allowed_cat_ids'] ?? null) ? array_map('intval', $settings['allowed_cat_ids']) : [];
|
||||
if ($allowed !== [] && $cats !== []) {
|
||||
$cats = array_values(array_intersect($cats, $allowed));
|
||||
if ($cats !== []) {
|
||||
$cats = SubmitCategories::filter_ids($cats, $settings);
|
||||
}
|
||||
wp_set_post_categories($post_id, $cats);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* 投稿可选分类的解析。
|
||||
*
|
||||
* 前台投稿的分类白名单(「投稿允许选择的分类」)按"选了上级就包含其全部子级"
|
||||
* 的语义生效:白名单里出现某个分类时,它的所有后代分类都可选;白名单为空时
|
||||
* 全部分类可选。列表本身始终是完整的层级树,而不是只有顶级分类——否则二级、
|
||||
* 三级分类会从投稿页里彻底消失。
|
||||
*
|
||||
* 创作者工作台(CreatorRest)与经典投稿表单(templates/parts/submit)共用这里
|
||||
* 的结果,避免两处各自拼一套查询。
|
||||
*
|
||||
* @package Jio
|
||||
*/
|
||||
|
||||
namespace Jio\Core;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
final class SubmitCategories {
|
||||
|
||||
/**
|
||||
* 请求内缓存:一次投稿页渲染 / 一次 REST 请求只查一次分类表。
|
||||
*
|
||||
* @var array<string, array{terms: \WP_Term[], tree: array<int, array{term: \WP_Term, depth: int, path: string}>, ids: array<int, true>}>
|
||||
*/
|
||||
private static array $cache = [];
|
||||
|
||||
/**
|
||||
* 可选的分类树,按层级深度优先展平。
|
||||
*
|
||||
* 每个元素都带 `depth`(顶级为 0)与 `path`(从顶级到自身的名称串),
|
||||
* 前端可以按需缩进或显示完整路径。
|
||||
*
|
||||
* @param array<string, mixed> $settings 投稿设置(读取 allowed_cat_ids)。
|
||||
* @return array<int, array{term: \WP_Term, depth: int, path: string}>
|
||||
*/
|
||||
public static function tree(array $settings): array {
|
||||
return self::resolve($settings)['tree'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 一次解析出「可选分类树」与「允许的分类 ID 集合」。
|
||||
*
|
||||
* @param array<string, mixed> $settings
|
||||
* @return array{terms: \WP_Term[], tree: array<int, array{term: \WP_Term, depth: int, path: string}>, ids: array<int, true>}
|
||||
*/
|
||||
private static function resolve(array $settings): array {
|
||||
$signature = self::signature($settings);
|
||||
if (isset(self::$cache[$signature])) {
|
||||
return self::$cache[$signature];
|
||||
}
|
||||
|
||||
$whitelist = self::whitelist($settings);
|
||||
// 白名单为空 = 全部分类可选,此时不做 ID 过滤,避免多一次全表查询,
|
||||
// 也避免分类查询异常时把正常投稿误判成"所选分类不可用"。
|
||||
$allowed = $whitelist === [] ? null : self::expand_whitelist($whitelist);
|
||||
|
||||
$args = [
|
||||
'taxonomy' => 'category',
|
||||
'hide_empty' => false,
|
||||
'orderby' => 'name',
|
||||
'order' => 'ASC',
|
||||
'number' => 0,
|
||||
];
|
||||
if ($allowed !== null) {
|
||||
// include 只收窄查询范围,父子关系仍由 flatten() 复原。
|
||||
$args['include'] = array_keys($allowed);
|
||||
}
|
||||
|
||||
$terms = get_terms($args);
|
||||
$terms = is_array($terms) ? $terms : [];
|
||||
if ($allowed !== null) {
|
||||
$terms = array_values(array_filter(
|
||||
$terms,
|
||||
static fn($term): bool => isset($allowed[(int) $term->term_id])
|
||||
));
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
foreach ($terms as $term) {
|
||||
$ids[(int) $term->term_id] = true;
|
||||
}
|
||||
|
||||
self::$cache[$signature] = [
|
||||
'terms' => $terms,
|
||||
'tree' => self::flatten($terms),
|
||||
'ids' => $ids,
|
||||
];
|
||||
|
||||
return self::$cache[$signature];
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤提交上来的分类 ID,只保留允许的部分。
|
||||
*
|
||||
* 白名单未启用时原样返回(不查分类表),启用时按"含后代"的集合过滤。
|
||||
*
|
||||
* @param int[] $ids
|
||||
* @param array<string, mixed> $settings
|
||||
* @return int[]
|
||||
*/
|
||||
public static function filter_ids(array $ids, array $settings): array {
|
||||
$whitelist = self::whitelist($settings);
|
||||
if ($whitelist === []) {
|
||||
return array_values(array_unique(array_filter(array_map('intval', $ids))));
|
||||
}
|
||||
$allowed = self::expand_whitelist($whitelist);
|
||||
$kept = [];
|
||||
foreach ($ids as $id) {
|
||||
$id = (int) $id;
|
||||
if ($id > 0 && isset($allowed[$id])) {
|
||||
$kept[] = $id;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($kept));
|
||||
}
|
||||
|
||||
/**
|
||||
* 展开白名单:选中分类 + 其全部后代。
|
||||
*
|
||||
* @param int[] $whitelist
|
||||
* @return array<int, true>
|
||||
*/
|
||||
private static function expand_whitelist(array $whitelist): array {
|
||||
$ids = [];
|
||||
foreach ($whitelist as $id) {
|
||||
$ids[$id] = true;
|
||||
$children = get_term_children($id, 'category');
|
||||
foreach (is_array($children) ? $children : [] as $child) {
|
||||
$ids[(int) $child] = true;
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置里显式选中的分类(未展开后代)。
|
||||
*
|
||||
* @param array<string, mixed> $settings
|
||||
* @return int[]
|
||||
*/
|
||||
private static function whitelist(array $settings): array {
|
||||
if (!is_array($settings['allowed_cat_ids'] ?? null)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_filter(array_map('intval', $settings['allowed_cat_ids'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* 把分类按父子关系整理成深度优先序列。
|
||||
*
|
||||
* 父级不在集合里(例如只授权了某个子分类)的分类直接作为根节点,
|
||||
* 不会被静默丢弃。
|
||||
*
|
||||
* @param \WP_Term[] $terms
|
||||
* @return array<int, array{term: \WP_Term, depth: int, path: string}>
|
||||
*/
|
||||
private static function flatten(array $terms): array {
|
||||
$by_parent = [];
|
||||
$present = [];
|
||||
foreach ($terms as $term) {
|
||||
$present[(int) $term->term_id] = true;
|
||||
}
|
||||
foreach ($terms as $term) {
|
||||
$parent = (int) $term->parent;
|
||||
if (!isset($present[$parent])) {
|
||||
$parent = 0;
|
||||
}
|
||||
$by_parent[$parent][] = $term;
|
||||
}
|
||||
|
||||
$out = [];
|
||||
$walk = static function (int $parent, int $depth, string $prefix) use (&$walk, $by_parent, &$out): void {
|
||||
foreach ($by_parent[$parent] ?? [] as $term) {
|
||||
$path = $prefix === '' ? (string) $term->name : $prefix . ' / ' . $term->name;
|
||||
$out[] = ['term' => $term, 'depth' => $depth, 'path' => $path];
|
||||
$walk((int) $term->term_id, $depth + 1, $path);
|
||||
}
|
||||
};
|
||||
$walk(0, 0, '');
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $settings
|
||||
*/
|
||||
private static function signature(array $settings): string {
|
||||
$whitelist = self::whitelist($settings);
|
||||
sort($whitelist);
|
||||
return implode(',', $whitelist);
|
||||
}
|
||||
|
||||
private function __construct() {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user