This is an automated email from the ASF dual-hosted git repository.
shuai pushed a commit to branch test
in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
The following commit(s) were added to refs/heads/test by this push:
new 885406dd Feat/1.3.6/UI (#1015)
885406dd is described below
commit 885406ddb37b5a7e4e91a1888b244dedd431ea1a
Author: dashuai <[email protected]>
AuthorDate: Fri Jul 12 14:13:34 2024 +0800
Feat/1.3.6/UI (#1015)
Co-authored-by: Kevin Gil <[email protected]>
Co-authored-by: sy-records <[email protected]>
Co-authored-by: robin <[email protected]>
---
docs/release/NOTICE | 2 +-
i18n/en_US.yaml | 16 +-
i18n/zh_CN.yaml | 2 +-
pkg/converter/markdown.go | 6 +-
ui/scripts/plugin.js | 9 +-
ui/src/common/color.scss | 22 +-
ui/src/common/constants.ts | 1 -
ui/src/common/interface.ts | 8 +-
ui/src/components/Editor/utils/index.ts | 3 +
ui/src/components/Mentions/index.tsx | 2 +-
ui/src/components/QuestionList/index.tsx | 7 +-
ui/src/components/TagSelector/index.tsx | 14 +-
ui/src/index.tsx | 21 ++
ui/src/pages/Admin/Write/index.tsx | 234 ++++++++++++++-------
ui/src/pages/Questions/Ask/index.tsx | 76 +++----
ui/src/pages/Questions/index.tsx | 11 +-
.../pages/Search/components/SearchHead/index.tsx | 2 +-
ui/src/pages/Search/components/Tips/index.tsx | 2 +-
ui/src/pages/Search/index.tsx | 2 +-
ui/src/pages/Tags/Detail/index.tsx | 11 +-
ui/src/services/client/question.ts | 2 +-
ui/src/stores/writeSetting.ts | 2 +-
ui/src/utils/common.ts | 35 +--
23 files changed, 304 insertions(+), 186 deletions(-)
diff --git a/docs/release/NOTICE b/docs/release/NOTICE
index 190e5f69..4eaee775 100644
--- a/docs/release/NOTICE
+++ b/docs/release/NOTICE
@@ -1,5 +1,5 @@
Apache Answer (incubating)
-Copyright 2023 The Apache Software Foundation
+Copyright 2024 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 88fd6e7f..ef1b0bc9 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1290,7 +1290,7 @@ ui:
answers: Answers
newest: Newest
active: Active
- frequent: Frequent
+ hot: Hot
score: Score
unanswered: Unanswered
modified: modified
@@ -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/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 2c438316..5c3dd862 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -1249,7 +1249,7 @@ ui:
answers: 回答
newest: 最新
active: 活跃
- frequent: 浏览量
+ hot: 热度
score: 评分
unanswered: 未回答
modified: 更新于
diff --git a/pkg/converter/markdown.go b/pkg/converter/markdown.go
index e537f554..af17d9bd 100644
--- a/pkg/converter/markdown.go
+++ b/pkg/converter/markdown.go
@@ -98,7 +98,6 @@ func (r *DangerousHTMLRenderer) RegisterFuncs(reg
renderer.NodeRendererFuncRegis
reg.Register(ast.KindRawHTML, r.renderRawHTML)
reg.Register(ast.KindLink, r.renderLink)
reg.Register(ast.KindAutoLink, r.renderAutoLink)
-
}
func (r *DangerousHTMLRenderer) renderRawHTML(w util.BufWriter, source []byte,
node ast.Node, entering bool) (ast.WalkStatus, error) {
@@ -136,7 +135,6 @@ func (r *DangerousHTMLRenderer) renderHTMLBlock(w
util.BufWriter, source []byte,
}
func (r *DangerousHTMLRenderer) renderLink(w util.BufWriter, source []byte,
node ast.Node, entering bool) (ast.WalkStatus, error) {
-
n := node.(*ast.Link)
if entering && r.renderLinkIsUrl(string(n.Destination)) {
_, _ = w.WriteString("<a href=\"")
@@ -186,5 +184,7 @@ func (r *DangerousHTMLRenderer) renderAutoLink(w
util.BufWriter, source []byte,
}
func (r *DangerousHTMLRenderer) renderLinkIsUrl(verifyUrl string) bool {
- return govalidator.IsURL(verifyUrl)
+ isURL := govalidator.IsURL(verifyUrl)
+ isPath, _ := regexp.MatchString(`^/`, verifyUrl)
+ return isURL || isPath
}
diff --git a/ui/scripts/plugin.js b/ui/scripts/plugin.js
index cdd84d17..a0d31e0c 100644
--- a/ui/scripts/plugin.js
+++ b/ui/scripts/plugin.js
@@ -19,15 +19,11 @@
const path = require('path');
const fs = require('fs');
+const humps = require('humps');
const pluginPath = path.join(__dirname, '../src/plugins');
const pluginFolders = fs.readdirSync(pluginPath);
-function pascalize(str) {
- return str.replace(/\b\w/g, (match) => match.toUpperCase())
- .replace(/[-_\s]+/g, '');
-}
-
function resetPackageJson() {
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const packageJsonContent = require(packageJsonPath);
@@ -63,7 +59,7 @@ function addPluginToIndexTs(packageName) {
const indexTsPath = path.join(pluginPath, 'index.ts');
const indexTsContent = fs.readFileSync(indexTsPath, 'utf-8');
const lines = indexTsContent.split('\n');
- const ComponentName = pascalize(packageName);
+ const ComponentName = humps.pascalize(packageName);
const importLine = `export { default as ${ComponentName} } from
'${packageName}';`;
if (!lines.includes(importLine)) {
lines.push(importLine);
@@ -94,7 +90,6 @@ pluginFolders.forEach((folder) => {
const packageJson = require(path.join(pluginFolder, 'package.json'));
const packageName = packageJson.name;
-
addPluginToPackageJson(packageName);
addPluginToIndexTs(packageName);
}
diff --git a/ui/src/common/color.scss b/ui/src/common/color.scss
index 66436873..1a96b31d 100644
--- a/ui/src/common/color.scss
+++ b/ui/src/common/color.scss
@@ -72,19 +72,27 @@
}
/** CodeMirror **/
- .CodeMirror {
+ .cm-editor {
background: var(--bs-body-bg);
color: var(--bs-body-color);
}
- .CodeMirror-cursor {
- border-left: 1px solid var(--bs-body-color);
+
+ .cm-cursor {
+ border-left-color: var(--bs-body-color);
+ }
+ .ͼ2.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground {
+ background-color: #3E4451;
}
- .cm-header, .cm-link, .cm-url {
+ /** link color **/
+ .ͼc {
+ color: rgb(60, 138, 233);
+ }
+ .ͼ5 {
color: var(--bs-body-color);
}
- div.CodeMirror-selected { background: rgba(127, 190, 244, 0.4); }
- .CodeMirror-line::selection, .CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection { background: rgba(84,174,255,0.4); }
- .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection { background:
rgba(84,174,255,0.4); }
+
+ .ͼ2 .cm-selectionBackground {background: #3E4451;}
+ /** CodeMirror end **/
.bg-light {
background-color: rgba(0, 0, 0, 0.5) !important;
diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts
index e76c60b0..c846d4dd 100644
--- a/ui/src/common/constants.ts
+++ b/ui/src/common/constants.ts
@@ -27,7 +27,6 @@ export const CAPTCHA_CODE_STORAGE_KEY = '_a_captcha_';
export const DRAFT_QUESTION_STORAGE_KEY = '_a_dq_';
export const DRAFT_ANSWER_STORAGE_KEY = '_a_da_';
export const DRAFT_TIMESIGH_STORAGE_KEY = '|_a_t_s_|';
-export const QUESTIONS_ORDER_STORAGE_KEY = '_a_qok_';
export const DEFAULT_THEME = 'system';
export const ADMIN_PRIVILEGE_CUSTOM_LEVEL = 99;
export const SKELETON_SHOW_TIME = 1000;
diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts
index 019786f1..ff7c0902 100644
--- a/ui/src/common/interface.ts
+++ b/ui/src/common/interface.ts
@@ -290,7 +290,7 @@ export interface LangsType {
export type QuestionOrderBy =
| 'newest'
| 'active'
- | 'frequent'
+ | 'hot'
| 'score'
| 'unanswered';
@@ -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/Mentions/index.tsx
b/ui/src/components/Mentions/index.tsx
index ae5ea2ff..662ffc97 100644
--- a/ui/src/components/Mentions/index.tsx
+++ b/ui/src/components/Mentions/index.tsx
@@ -107,7 +107,7 @@ const Mentions: FC<IProps> = ({ children, pageUsers,
onSelected }) => {
return;
}
- const text = `@${item?.userName}`;
+ const text = `@${item?.userName} `;
onSelected(
`${value.substring(
0,
diff --git a/ui/src/components/QuestionList/index.tsx
b/ui/src/components/QuestionList/index.tsx
index 94a300d8..d9b46dd2 100644
--- a/ui/src/components/QuestionList/index.tsx
+++ b/ui/src/components/QuestionList/index.tsx
@@ -22,7 +22,6 @@ import { ListGroup } from 'react-bootstrap';
import { NavLink, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import type { QuestionOrderBy } from '@/common/interface';
import { pathFactory } from '@/router/pathFactory';
import {
Tag,
@@ -39,15 +38,15 @@ import * as Type from '@/common/interface';
import { useSkeletonControl } from '@/hooks';
export const QUESTION_ORDER_KEYS: Type.QuestionOrderBy[] = [
- 'active',
'newest',
- 'frequent',
+ 'active',
+ 'hot',
'score',
'unanswered',
];
interface Props {
source: 'questions' | 'tag';
- order?: QuestionOrderBy;
+ order?: Type.QuestionOrderBy;
data;
isLoading: boolean;
}
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<IProps> = ({
showRequiredTag = false,
autoFocus = false,
isInvalid = false,
+ formText = '',
+ tagStyleMode = 'default',
errMsg = '',
}) => {
const containerRef = useRef<HTMLDivElement>(null);
@@ -366,8 +370,12 @@ const TagSelector: FC<IProps> = ({
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<IProps> = ({
)}
</Dropdown.Menu>
</div>
- {!hiddenDescription && <Form.Text>{t('hint')}</Form.Text>}
+ {!hiddenDescription && <Form.Text>{formText || t('hint')}</Form.Text>}
<Form.Control.Feedback type="invalid">{errMsg}</Form.Control.Feedback>
</div>
);
diff --git a/ui/src/index.tsx b/ui/src/index.tsx
index 6074ca72..e2930c08 100644
--- a/ui/src/index.tsx
+++ b/ui/src/index.tsx
@@ -60,8 +60,29 @@ const handleImgLoad = (evt: Event | UIEvent) => {
}
};
+/**
+ *Automatically jump when the href of a Link component within a matching
project is not a front-end route.
+ *
+ */
+const handleClickLink = (evt: Event) => {
+ const { target } = evt;
+
+ if (target === null || !(target instanceof Element)) {
+ return;
+ }
+ if (!/A/i.test(target.nodeName)) {
+ return;
+ }
+
+ if (target.getAttribute('href')?.includes('/answer/api/')) {
+ evt.preventDefault();
+ window.location.href = target.getAttribute('href') || '';
+ }
+};
+
document.addEventListener('error', handleImgLoad, true);
document.addEventListener('load', handleImgLoad, true);
+document.addEventListener('click', handleClickLink, true);
root.render(
<React.StrictMode>
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 (
<>
<h3 className="mb-4">{t('page_title')}</h3>
- <SchemaForm
- schema={schema}
- formData={formData}
- onSubmit={onSubmit}
- uiSchema={uiSchema}
- onChange={handleOnChange}
- />
+ <Form noValidate onSubmit={onSubmit}>
+ <Form.Group className="mb-3" controlId="reserved_tags">
+ <Form.Label>{t('reserved_tags.label')}</Form.Label>
+ <TagSelector
+ value={formData.reserved_tags.value}
+ onChange={(val) => {
+ 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}
+ />
+ </Form.Group>
+
+ <Form.Group className="mb-3" controlId="recommend_tags">
+ <Form.Label>{t('recommend_tags.label')}</Form.Label>
+ <TagSelector
+ value={formData.recommend_tags.value}
+ onChange={(val) => {
+ 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}
+ />
+ </Form.Group>
+
+ <Form.Group className="mb-3" controlId="required_tag">
+ <Form.Label>{t('required_tag.title')}</Form.Label>
+ <Form.Switch
+ label={t('required_tag.label')}
+ checked={formData.required_tag.value}
+ onChange={(evt) => {
+ handleValueChange({
+ required_tag: {
+ value: evt.target.checked,
+ errorMsg: '',
+ isInvalid: false,
+ },
+ });
+ }}
+ />
+ <Form.Text>{t('required_tag.text')}</Form.Text>
+ <Form.Control.Feedback type="invalid">
+ {formData.required_tag.errorMsg}
+ </Form.Control.Feedback>
+ </Form.Group>
+
+ <Form.Group className="mb-3" controlId="restrict_answer">
+ <Form.Label>{t('restrict_answer.title')}</Form.Label>
+ <Form.Switch
+ label={t('restrict_answer.label')}
+ checked={formData.restrict_answer.value}
+ onChange={(evt) => {
+ handleValueChange({
+ restrict_answer: {
+ value: evt.target.checked,
+ errorMsg: '',
+ isInvalid: false,
+ },
+ });
+ }}
+ />
+ <Form.Text>{t('restrict_answer.text')}</Form.Text>
+ <Form.Control.Feedback type="invalid">
+ {formData.restrict_answer.errorMsg}
+ </Form.Control.Feedback>
+ </Form.Group>
+
+ <Form.Group className="mb-3">
+ <Button type="submit">{t('save', { keyPrefix: 'btns' })}</Button>
+ </Form.Group>
+ </Form>
</>
);
};
diff --git a/ui/src/pages/Questions/Ask/index.tsx
b/ui/src/pages/Questions/Ask/index.tsx
index 40ff73e2..2b370de0 100644
--- a/ui/src/pages/Questions/Ask/index.tsx
+++ b/ui/src/pages/Questions/Ask/index.tsx
@@ -487,52 +487,17 @@ const Ask = () => {
/>
</Form.Group>
- {isEdit && (
- <Form.Group controlId="edit_summary" className="my-3">
- <Form.Label>{t('form.fields.edit_summary.label')}</Form.Label>
- <Form.Control
- type="text"
- defaultValue={formData.edit_summary.value}
- isInvalid={formData.edit_summary.isInvalid}
- placeholder={t('form.fields.edit_summary.placeholder')}
- onChange={handleSummaryChange}
- contentEditable
- />
- <Form.Control.Feedback type="invalid">
- {formData.edit_summary.errorMsg}
- </Form.Control.Feedback>
- </Form.Group>
- )}
- {!checked && (
- <div className="mt-3">
- <Button type="submit" className="me-2">
- {isEdit ? t('btn_save_edits') : t('btn_post_question')}
- </Button>
- {isEdit && (
- <Button variant="link" onClick={backPage}>
- {t('cancel', { keyPrefix: 'btns' })}
- </Button>
- )}
-
- {hasDraft && (
- <Button variant="link" onClick={deleteDraft}>
- {t('discard_draft', { keyPrefix: 'btns' })}
- </Button>
- )}
- </div>
- )}
{!isEdit && (
<>
- <Form.Check
- className="mt-5"
+ <Form.Switch
checked={checked}
- type="checkbox"
+ type="switch"
label={t('answer_question')}
onChange={(e) => setCheckState(e.target.checked)}
id="radio-answer"
/>
{checked && (
- <Form.Group controlId="answer" className="mt-4">
+ <Form.Group controlId="answer" className="mt-3">
<Form.Label>{t('form.fields.answer.label')}</Form.Label>
<Editor
value={formData.answer_content.value}
@@ -562,6 +527,41 @@ const Ask = () => {
)}
</>
)}
+
+ {isEdit && (
+ <Form.Group controlId="edit_summary" className="my-3">
+ <Form.Label>{t('form.fields.edit_summary.label')}</Form.Label>
+ <Form.Control
+ type="text"
+ defaultValue={formData.edit_summary.value}
+ isInvalid={formData.edit_summary.isInvalid}
+ placeholder={t('form.fields.edit_summary.placeholder')}
+ onChange={handleSummaryChange}
+ contentEditable
+ />
+ <Form.Control.Feedback type="invalid">
+ {formData.edit_summary.errorMsg}
+ </Form.Control.Feedback>
+ </Form.Group>
+ )}
+ {!checked && (
+ <div className="mt-3">
+ <Button type="submit" className="me-2">
+ {isEdit ? t('btn_save_edits') : t('btn_post_question')}
+ </Button>
+ {isEdit && (
+ <Button variant="link" onClick={backPage}>
+ {t('cancel', { keyPrefix: 'btns' })}
+ </Button>
+ )}
+
+ {hasDraft && (
+ <Button variant="link" onClick={deleteDraft}>
+ {t('discard_draft', { keyPrefix: 'btns' })}
+ </Button>
+ )}
+ </div>
+ )}
{checked && (
<div className="mt-3">
<Button type="submit">{t('post_question&answer')}</Button>
diff --git a/ui/src/pages/Questions/index.tsx b/ui/src/pages/Questions/index.tsx
index e0bf27ff..d81a9027 100644
--- a/ui/src/pages/Questions/index.tsx
+++ b/ui/src/pages/Questions/index.tsx
@@ -36,8 +36,7 @@ import {
} from '@/stores';
import { useQuestionList } from '@/services';
import * as Type from '@/common/interface';
-import { userCenter, floppyNavigation, Storage } from '@/utils';
-import { QUESTIONS_ORDER_STORAGE_KEY } from '@/common/constants';
+import { userCenter, floppyNavigation } from '@/utils';
import { QUESTION_ORDER_KEYS } from '@/components/QuestionList';
const Questions: FC = () => {
@@ -46,12 +45,8 @@ const Questions: FC = () => {
const { user: loggedUser } = loggedUserInfoStore((_) => _);
const [urlSearchParams] = useSearchParams();
const curPage = Number(urlSearchParams.get('page')) || 1;
- const storageOrder = Storage.get(QUESTIONS_ORDER_STORAGE_KEY);
- const curOrder =
- urlSearchParams.get('order') || storageOrder || QUESTION_ORDER_KEYS[0];
- if (curOrder !== storageOrder) {
- Storage.set(QUESTIONS_ORDER_STORAGE_KEY, curOrder);
- }
+ const curOrder = (urlSearchParams.get('order') ||
+ QUESTION_ORDER_KEYS[0]) as Type.QuestionOrderBy;
const reqParams: Type.QueryQuestionsReq = {
page_size: 20,
page: curPage,
diff --git a/ui/src/pages/Search/components/SearchHead/index.tsx
b/ui/src/pages/Search/components/SearchHead/index.tsx
index 667c16ec..a175d381 100644
--- a/ui/src/pages/Search/components/SearchHead/index.tsx
+++ b/ui/src/pages/Search/components/SearchHead/index.tsx
@@ -22,7 +22,7 @@ import { useTranslation } from 'react-i18next';
import { QueryGroup } from '@/components';
-const sortBtns = ['active', 'newest', 'relevance', 'score'];
+const sortBtns = ['relevance', 'newest', 'active', 'score'];
interface Props {
count: number;
diff --git a/ui/src/pages/Search/components/Tips/index.tsx
b/ui/src/pages/Search/components/Tips/index.tsx
index 2fd151f8..fe6a63d9 100644
--- a/ui/src/pages/Search/components/Tips/index.tsx
+++ b/ui/src/pages/Search/components/Tips/index.tsx
@@ -26,7 +26,7 @@ const Index: FC = () => {
return (
<Card>
<Card.Header>{t('search.tips.title')}</Card.Header>
- <Card.Body className="small ext-secondary">
+ <Card.Body>
<div className="mb-1">
<Trans i18nKey="search.tips.tag" components={{ 1: <code /> }} />
</div>
diff --git a/ui/src/pages/Search/index.tsx b/ui/src/pages/Search/index.tsx
index a016af70..3eeb0ec9 100644
--- a/ui/src/pages/Search/index.tsx
+++ b/ui/src/pages/Search/index.tsx
@@ -42,7 +42,7 @@ const Index = () => {
const [searchParams] = useSearchParams();
const page = searchParams.get('page') || 1;
const q = searchParams.get('q') || '';
- const order = searchParams.get('order') || 'active';
+ const order = searchParams.get('order') || 'relevance';
const [isLoading, setIsLoading] = useState(false);
const { isSkeletonShow } = useSkeletonControl(isLoading);
const [data, setData] = useState<SearchRes>({
diff --git a/ui/src/pages/Tags/Detail/index.tsx
b/ui/src/pages/Tags/Detail/index.tsx
index c2351ce3..a07236da 100644
--- a/ui/src/pages/Tags/Detail/index.tsx
+++ b/ui/src/pages/Tags/Detail/index.tsx
@@ -38,9 +38,8 @@ import {
} from '@/services';
import QuestionList, { QUESTION_ORDER_KEYS } from '@/components/QuestionList';
import HotQuestions from '@/components/HotQuestions';
-import { escapeRemove, guard, Storage, scrollToDocTop } from '@/utils';
+import { escapeRemove, guard, scrollToDocTop } from '@/utils';
import { pathFactory } from '@/router/pathFactory';
-import { QUESTIONS_ORDER_STORAGE_KEY } from '@/common/constants';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'tags' });
@@ -48,12 +47,8 @@ const Index: FC = () => {
const routeParams = useParams();
const curTagName = routeParams.tagName || '';
const [urlSearchParams] = useSearchParams();
- const storageOrder = Storage.get(QUESTIONS_ORDER_STORAGE_KEY);
- const curOrder =
- urlSearchParams.get('order') || storageOrder || QUESTION_ORDER_KEYS[0];
- if (curOrder !== storageOrder) {
- Storage.set(QUESTIONS_ORDER_STORAGE_KEY, curOrder);
- }
+ const curOrder = (urlSearchParams.get('order') ||
+ QUESTION_ORDER_KEYS[0]) as Type.QuestionOrderBy;
const curPage = Number(urlSearchParams.get('page')) || 1;
const reqParams: Type.QueryQuestionsReq = {
page_size: 20,
diff --git a/ui/src/services/client/question.ts
b/ui/src/services/client/question.ts
index d027c83b..0e382c26 100644
--- a/ui/src/services/client/question.ts
+++ b/ui/src/services/client/question.ts
@@ -40,7 +40,7 @@ export const useHotQuestions = (
params: Type.QueryQuestionsReq = {
page: 1,
page_size: 6,
- order: 'frequent',
+ order: 'hot',
in_days: 7,
},
) => {
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<IProps>((set) => ({
write: {
restrict_answer: true,
recommend_tags: [],
- required_tag: '',
+ required_tag: false,
reserved_tags: [],
},
update: (params) =>
diff --git a/ui/src/utils/common.ts b/ui/src/utils/common.ts
index 97880bf7..d9d3bee5 100644
--- a/ui/src/utils/common.ts
+++ b/ui/src/utils/common.ts
@@ -118,7 +118,7 @@ function matchedUsers(markdown) {
*/
function parseUserInfo(markdown) {
const globalReg = /\B@([\w\\_\\.\\-]+)/g;
- return markdown.replace(globalReg, '[@$1](/u/$1)');
+ return markdown.replace(globalReg, '[@$1](/users/$1)');
}
function parseEditMentionUser(markdown) {
@@ -188,6 +188,23 @@ function escapeHtml(str: string) {
return str.replace(/[&<>"'`]/g, (tag) => tagsToReplace[tag] || tag);
}
+function formatDiffPart(part: any, className: string): string {
+ if (part.value.replace(/\n/g, '').length <= 0) {
+ if (part.value.match(/\n/g)?.length > 1) {
+ const value = part.value.replace(/\n/, '');
+ return `<span class="${className}"> </span><div><span
class="${className}">${value.replace(
+ /\n/g,
+ ' ',
+ )}</span></div>`;
+ }
+ return `<div><span class="${className}">${part.value.replace(
+ /\n/g,
+ ' ',
+ )}</span></div>`;
+ }
+ return `<span class="${className}">${part.value}</span>`;
+}
+
function diffText(newText: string, oldText?: string): string {
if (!newText) {
return '';
@@ -200,22 +217,10 @@ function diffText(newText: string, oldText?: string):
string {
const diff = Diff.diffChars(escapeHtml(oldText), escapeHtml(newText));
result = diff.map((part) => {
if (part.added) {
- if (part.value.replace(/\n/g, '').length <= 0) {
- return `<span class="review-text-add d-block">${part.value.replace(
- /\n/g,
- '↵\n',
- )}</span>`;
- }
- return `<span class="review-text-add">${part.value}</span>`;
+ return formatDiffPart(part, 'review-text-add');
}
if (part.removed) {
- if (part.value.replace(/\n/g, '').length <= 0) {
- return `<span class="review-text-delete text-decoration-none
d-block">${part.value.replace(
- /\n/g,
- '↵\n',
- )}</span>`;
- }
- return `<span class="review-text-delete">${part.value}</span>`;
+ return formatDiffPart(part, 'review-text-remove text-decoration-none');
}
return part.value;