feat(topics): restore topic taxonomy workflow

This commit is contained in:
kate522 committed 2026-11-10 22:31:11 +08:00
1 parent f3dd1ec3b1
commit a254cbd3e9
59 files changed
+771 -192

No files matched your search

@@ -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">
+3
View File
@@ -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: {} },
+7
View File
@@ -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)}