diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index 9be207f7..ef1b0bc9 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1747,19 +1747,21 @@ ui: write: page_title: Write restrict_answer: - title: Restrict answer + title: Answer write label: Each user can only write one answer for the same question - text: "Turn off to allow users to write multiple answers to the same question, which may cause answers to be unfocused." + text: "Turn off to allow users to write multiple answers to the same question, which may cause answers to be unfocused." recommend_tags: label: Recommend tags - text: "Please input tag slug above, one tag per line." + text: "Recommend tags will show in the dropdown list by default." + msg: + contain_reserved: "recommended tags cannot contain reserved tags" required_tag: - title: Required tag - label: Set recommend tag as required + title: Set required tags + label: Set “Recommend tags” as required tags text: "Every new question must have at least one recommend tag." reserved_tags: label: Reserved tags - text: "Reserved tags can only be added to a post by moderator." + text: "Reserved tags can only be used by moderator." seo: page_title: SEO permalink: diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 0acd2f02..ff7c0902 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -425,9 +425,9 @@ export interface AdminSettingsLegal { export interface AdminSettingsWrite { restrict_answer?: boolean; - recommend_tags?: string[]; - required_tag?: string; - reserved_tags?: string[]; + recommend_tags?: Tag[]; + required_tag?: boolean; + reserved_tags?: Tag[]; } export interface AdminSettingsSeo { diff --git a/ui/src/components/Editor/utils/index.ts b/ui/src/components/Editor/utils/index.ts index 406acb8d..d49da78c 100644 --- a/ui/src/components/Editor/utils/index.ts +++ b/ui/src/components/Editor/utils/index.ts @@ -97,6 +97,9 @@ export const useEditor = ({ wordWrap: 'break-word', wordBreak: 'break-all', }, + '.ͼ7, .ͼ6': { + textDecoration: 'none', + }, }); const startState = EditorState.create({ diff --git a/ui/src/components/TagSelector/index.tsx b/ui/src/components/TagSelector/index.tsx index 845ace24..1204a686 100644 --- a/ui/src/components/TagSelector/index.tsx +++ b/ui/src/components/TagSelector/index.tsx @@ -41,6 +41,8 @@ interface IProps { showRequiredTag?: boolean; autoFocus?: boolean; isInvalid?: boolean; + tagStyleMode?: 'default' | 'simple'; + formText?: string; errMsg?: string; } @@ -55,6 +57,8 @@ const TagSelector: FC = ({ showRequiredTag = false, autoFocus = false, isInvalid = false, + formText = '', + tagStyleMode = 'default', errMsg = '', }) => { const containerRef = useRef(null); @@ -366,8 +370,12 @@ const TagSelector: FC = ({ key={item.slug_name} className={classNames( 'badge-tag rounded-1 m-1 flex-shrink-0', - item.reserved && 'badge-tag-reserved', - item.recommend && 'badge-tag-required', + tagStyleMode === 'default' && + item.reserved && + 'badge-tag-reserved', + tagStyleMode === 'default' && + item.recommend && + 'badge-tag-required', index === repeatIndex && 'bg-fade-out', )}> {item.display_name} @@ -435,7 +443,7 @@ const TagSelector: FC = ({ )} - {!hiddenDescription && {t('hint')}} + {!hiddenDescription && {formText || t('hint')}} {errMsg} ); diff --git a/ui/src/pages/Admin/Write/index.tsx b/ui/src/pages/Admin/Write/index.tsx index 42806111..442c58f6 100644 --- a/ui/src/pages/Admin/Write/index.tsx +++ b/ui/src/pages/Admin/Write/index.tsx @@ -19,8 +19,9 @@ import { FC, useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Form, Button } from 'react-bootstrap'; -import { SchemaForm, JSONSchema, initFormData, UISchema } from '@/components'; +import { TagSelector } from '@/components'; import type * as Type from '@/common/interface'; import { useToast } from '@/hooks'; import { @@ -30,80 +31,84 @@ import { import { handleFormError, scrollToElementTop } from '@/utils'; import { writeSettingStore } from '@/stores'; +const initFormData = { + reserved_tags: { + value: [] as Type.Tag[], // Replace `Type.Tag` with the correct type for `reserved_tags.value` + errorMsg: '', + isInvalid: false, + }, + recommend_tags: { + value: [] as Type.Tag[], + errorMsg: '', + isInvalid: false, + }, + required_tag: { + value: false, + errorMsg: '', + isInvalid: false, + }, + restrict_answer: { + value: false, + errorMsg: '', + isInvalid: false, + }, +}; + const Index: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.write', }); const Toast = useToast(); - const schema: JSONSchema = { - title: t('page_title'), - properties: { - restrict_answer: { - type: 'boolean', - title: t('restrict_answer.title'), - description: t('restrict_answer.text'), - default: true, - }, - recommend_tags: { - type: 'string', - title: t('recommend_tags.label'), - description: t('recommend_tags.text'), - }, - required_tag: { - type: 'boolean', - title: t('required_tag.title'), - description: t('required_tag.text'), - }, - reserved_tags: { - type: 'string', - title: t('reserved_tags.label'), - description: t('reserved_tags.text'), - }, - }, + const [formData, setFormData] = useState(initFormData); + + const handleValueChange = (value) => { + setFormData({ + ...formData, + ...value, + }); }; - const uiSchema: UISchema = { - restrict_answer: { - 'ui:widget': 'switch', - 'ui:options': { - label: t('restrict_answer.label'), - }, - }, - recommend_tags: { - 'ui:widget': 'textarea', - 'ui:options': { - rows: 10, - }, - }, - required_tag: { - 'ui:widget': 'switch', - 'ui:options': { - label: t('required_tag.label'), - }, - }, - reserved_tags: { - 'ui:widget': 'textarea', - 'ui:options': { - rows: 10, - }, - }, + + const checkValidated = (): boolean => { + let bol = true; + const { recommend_tags, reserved_tags } = formData; + // 找出 recommend_tags 和 reserved_tags 中是否有重复的标签 + // 通过标签中的 slug_name 来去重 + const repeatTag = recommend_tags.value.filter((tag) => + reserved_tags.value.some((rTag) => rTag?.slug_name === tag?.slug_name), + ); + if (repeatTag.length > 0) { + handleValueChange({ + recommend_tags: { + ...recommend_tags, + errorMsg: t('recommend_tags.msg.contain_reserved'), + isInvalid: true, + }, + }); + bol = false; + const ele = document.getElementById('recommend_tags'); + scrollToElementTop(ele); + } else { + handleValueChange({ + recommend_tags: { + ...recommend_tags, + errorMsg: '', + isInvalid: false, + }, + }); + } + return bol; }; - const [formData, setFormData] = useState(initFormData(schema)); const onSubmit = (evt) => { evt.preventDefault(); evt.stopPropagation(); - let recommend_tags = []; - if (formData.recommend_tags.value?.trim()) { - recommend_tags = formData.recommend_tags.value.trim().split('\n'); - } - let reserved_tags = []; - if (formData.reserved_tags.value?.trim()) { - reserved_tags = formData.reserved_tags.value.trim().split('\n'); + if (!checkValidated()) { + return; } const reqParams: Type.AdminSettingsWrite = { - recommend_tags, - reserved_tags, + recommend_tags: formData.recommend_tags.value, + reserved_tags: formData.reserved_tags.value, required_tag: formData.required_tag.value, restrict_answer: formData.restrict_answer.value, }; @@ -130,12 +135,12 @@ const Index: FC = () => { const initData = () => { getRequireAndReservedTag().then((res) => { if (Array.isArray(res.recommend_tags)) { - formData.recommend_tags.value = res.recommend_tags.join('\n'); + formData.recommend_tags.value = res.recommend_tags; } formData.required_tag.value = res.required_tag; formData.restrict_answer.value = res.restrict_answer; if (Array.isArray(res.reserved_tags)) { - formData.reserved_tags.value = res.reserved_tags.join('\n'); + formData.reserved_tags.value = res.reserved_tags; } setFormData({ ...formData }); }); @@ -145,20 +150,103 @@ const Index: FC = () => { initData(); }, []); - const handleOnChange = (data) => { - setFormData(data); - }; + // const handleOnChange = (data) => { + // setFormData(data); + // }; return ( <>

{t('page_title')}

- +
+ + {t('reserved_tags.label')} + { + handleValueChange({ + reserved_tags: { + value: val, + errorMsg: '', + isInvalid: false, + }, + }); + }} + showRequiredTag={false} + maxTagLength={0} + tagStyleMode="simple" + formText={t('reserved_tags.text')} + isInvalid={formData.reserved_tags.isInvalid} + errMsg={formData.reserved_tags.errorMsg} + /> + + + + {t('recommend_tags.label')} + { + handleValueChange({ + recommend_tags: { + value: val, + errorMsg: '', + isInvalid: false, + }, + }); + }} + showRequiredTag={false} + tagStyleMode="simple" + formText={t('recommend_tags.text')} + isInvalid={formData.recommend_tags.isInvalid} + errMsg={formData.recommend_tags.errorMsg} + /> + + + + {t('required_tag.title')} + { + handleValueChange({ + required_tag: { + value: evt.target.checked, + errorMsg: '', + isInvalid: false, + }, + }); + }} + /> + {t('required_tag.text')} + + {formData.required_tag.errorMsg} + + + + + {t('restrict_answer.title')} + { + handleValueChange({ + restrict_answer: { + value: evt.target.checked, + errorMsg: '', + isInvalid: false, + }, + }); + }} + /> + {t('restrict_answer.text')} + + {formData.restrict_answer.errorMsg} + + + + + + +
); }; diff --git a/ui/src/stores/writeSetting.ts b/ui/src/stores/writeSetting.ts index 0ea04962..8e7c1f52 100644 --- a/ui/src/stores/writeSetting.ts +++ b/ui/src/stores/writeSetting.ts @@ -30,7 +30,7 @@ const Index = create((set) => ({ write: { restrict_answer: true, recommend_tags: [], - required_tag: '', + required_tag: false, reserved_tags: [], }, update: (params) =>