diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index b4121379..039e2e5e 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -525,6 +525,7 @@ ui:
tip_answer: >-
Use comments to reply to other users or notify them of changes. If you are
adding new information, edit your post instead of commenting.
+ tip_vote: It adds something useful to the post
edit_answer:
title: Edit Answer
default_reason: Edit answer
@@ -784,6 +785,11 @@ ui:
answered: answered
closed_in: Closed in
show_exist: Show existing question.
+ useful: Useful
+ question_useful: It is useful and clear
+ question_un_useful: It is unclear or not useful
+ answer_useful: It is useful
+ answer_un_useful: It is not useful
answers:
title: Answers
score: Score
@@ -801,10 +807,19 @@ ui:
edit link to refine and improve your existing answer, instead.
empty: Answer cannot be empty.
characters: content must be at least 6 characters in length.
+ tips:
+ header_1: Thanks for your answer
+ li1_1: Please be sure to answer the question . Provide details and share your research.
+ li1_2: Back up any statements you make with references or personal experience.
+ header_2: But avoid ...
+ li2_1: Asking for help, seeking clarification, or responding to other answers.
+
reopen:
+ confirm_btn: Reopen
title: Reopen this post
content: Are you sure you want to reopen?
success: This post has been reopened
+
delete:
title: Delete this post
question: >-
diff --git a/ui/src/components/Actions/index.tsx b/ui/src/components/Actions/index.tsx
index 8ba6a1d4..518708ec 100644
--- a/ui/src/components/Actions/index.tsx
+++ b/ui/src/components/Actions/index.tsx
@@ -1,5 +1,5 @@
import { memo, FC, useState, useEffect } from 'react';
-import { Button, ButtonGroup } from 'react-bootstrap';
+import { Button, ButtonGroup, Tooltip, OverlayTrigger } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
@@ -12,6 +12,7 @@ import { bookmark, postVote } from '@/services';
interface Props {
className?: string;
+ source: 'question' | 'answer';
data: {
id: string;
votesCount: number;
@@ -24,7 +25,7 @@ interface Props {
};
}
-const Index: FC = ({ className, data }) => {
+const Index: FC = ({ className, data, source }) => {
const [votes, setVotes] = useState(0);
const [like, setLike] = useState(false);
const [hate, setHated] = useState(false);
@@ -101,21 +102,40 @@ const Index: FC = ({ className, data }) => {
return (
- handleVote('up')}>
-
-
+
+ {source === 'question'
+ ? t('question_detail.question_useful')
+ : t('question_detail.answer_useful')}
+
+ }>
+ handleVote('up')}>
+
+ {t('question_detail.useful')}
+
+
{votes}
- handleVote('down')}>
-
-
+
+ {source === 'question'
+ ? t('question_detail.question_un_useful')
+ : t('question_detail.answer_un_useful')}
+
+ }>
+ handleVote('down')}>
+
+
+
{!data?.hideCollect && (
•
-
-
- {voteCount > 0 && {voteCount} }
-
+ {t('tip_vote')}}>
+
+
+ {voteCount > 0 && {voteCount} }
+
+
-
+
,
);
}
diff --git a/ui/src/components/Operate/index.tsx b/ui/src/components/Operate/index.tsx
index d8b5d36d..06e9377e 100644
--- a/ui/src/components/Operate/index.tsx
+++ b/ui/src/components/Operate/index.tsx
@@ -69,7 +69,7 @@ const Index: FC = ({
if (type === 'question') {
Modal.confirm({
title: t('title'),
- content: hasAnswer ? `${t('question')}
` : `${t('other')}
`,
+ content: hasAnswer ? t('question') : t('other'),
cancelBtnVariant: 'link',
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
@@ -90,7 +90,7 @@ const Index: FC = ({
if (type === 'answer' && aid) {
Modal.confirm({
title: t('title'),
- content: isAccepted ? t('answer_accepted') : `${t('other')}
`,
+ content: isAccepted ? t('answer_accepted') : t('other'),
cancelBtnVariant: 'link',
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
@@ -128,6 +128,7 @@ const Index: FC = ({
title: t('title', { keyPrefix: 'question_detail.reopen' }),
content: t('content', { keyPrefix: 'question_detail.reopen' }),
cancelBtnVariant: 'link',
+ confirmText: t('confirm_btn', { keyPrefix: 'question_detail.reopen' }),
onConfirm: () => {
reopenQuestion({
question_id: qid,
diff --git a/ui/src/pages/Admin/Answers/index.tsx b/ui/src/pages/Admin/Answers/index.tsx
index cf53e269..39a7946f 100644
--- a/ui/src/pages/Admin/Answers/index.tsx
+++ b/ui/src/pages/Admin/Answers/index.tsx
@@ -58,7 +58,7 @@ const Answers: FC = () => {
content:
item.accepted === 2
? t('answer_accepted', { keyPrefix: 'delete' })
- : `${t('other', { keyPrefix: 'delete' })}
`,
+ : t('other', { keyPrefix: 'delete' }),
cancelBtnVariant: 'link',
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
diff --git a/ui/src/pages/Admin/Questions/index.tsx b/ui/src/pages/Admin/Questions/index.tsx
index 124fd307..d11043b2 100644
--- a/ui/src/pages/Admin/Questions/index.tsx
+++ b/ui/src/pages/Admin/Questions/index.tsx
@@ -67,8 +67,8 @@ const Questions: FC = () => {
title: t('title', { keyPrefix: 'delete' }),
content:
item.answer_count > 0
- ? `${t('question', { keyPrefix: 'delete' })}
`
- : `${t('other', { keyPrefix: 'delete' })}
`,
+ ? t('question', { keyPrefix: 'delete' })
+ : t('other', { keyPrefix: 'delete' }),
cancelBtnVariant: 'link',
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
diff --git a/ui/src/pages/Admin/Seo/index.tsx b/ui/src/pages/Admin/Seo/index.tsx
index ffabb59b..6c979dc0 100644
--- a/ui/src/pages/Admin/Seo/index.tsx
+++ b/ui/src/pages/Admin/Seo/index.tsx
@@ -19,14 +19,14 @@ const Index: FC = () => {
type: 'number',
title: t('permalink.label'),
description: t('permalink.text'),
- enum: [1, 2, 3, 4],
+ enum: [4, 3, 2, 1],
enumNames: [
- '/questions/10010000000000001/post-title',
- '/questions/10010000000000001',
- '/questions/D1D1/post-title',
'/questions/D1D1',
+ '/questions/D1D1/post-title',
+ '/questions/10010000000000001',
+ '/questions/10010000000000001/post-title',
],
- default: 1,
+ default: 4,
},
robots: {
type: 'string',
diff --git a/ui/src/pages/Questions/Detail/components/Answer/index.tsx b/ui/src/pages/Questions/Detail/components/Answer/index.tsx
index 7da9f028..dfbefa3d 100644
--- a/ui/src/pages/Questions/Detail/components/Answer/index.tsx
+++ b/ui/src/pages/Questions/Detail/components/Answer/index.tsx
@@ -1,5 +1,5 @@
import { memo, FC, useEffect, useRef } from 'react';
-import { Button, Alert } from 'react-bootstrap';
+import { Button, Alert, Badge } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link, useSearchParams } from 'react-router-dom';
@@ -20,8 +20,7 @@ interface Props {
data: AnswerItem;
/** router answer id */
aid?: string;
- /** is author */
- isAuthor: boolean;
+ canAccept: boolean;
questionTitle: string;
slugTitle: string;
isLogged: boolean;
@@ -30,11 +29,11 @@ interface Props {
const Index: FC = ({
aid,
data,
- isAuthor,
isLogged,
questionTitle = '',
slugTitle,
callback,
+ canAccept = false,
}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'question_detail',
@@ -77,12 +76,21 @@ const Index: FC = ({
{t('post_deleted', { keyPrefix: 'messages' })}
)}
+ {data?.accepted === 2 && (
+
+
+
+ Best answer
+
+
+ )}
= ({
}}
/>
- {data?.accepted === 2 && (
+ {canAccept && (
-
- {t('answers.btn_accepted')}
-
- )}
-
- {isAuthor && data.accepted === 1 && (
-
- {t('answers.btn_accept')}
+
+ {data.accepted === 2
+ ? t('answers.btn_accepted')
+ : t('answers.btn_accept')}
+
)}
diff --git a/ui/src/pages/Questions/Detail/components/Question/index.tsx b/ui/src/pages/Questions/Detail/components/Question/index.tsx
index 9eeb66e1..254d2779 100644
--- a/ui/src/pages/Questions/Detail/components/Question/index.tsx
+++ b/ui/src/pages/Questions/Detail/components/Question/index.tsx
@@ -114,6 +114,7 @@ const Index: FC = ({ data, initPage, hasAnswer, isLogged }) => {
void;
}
@@ -39,6 +40,7 @@ const Index: FC = ({ visible = false, data, callback }) => {
const [focusType, setFocusType] = useState('');
const [editorFocusState, setEditorFocusState] = useState(false);
const [hasDraft, setHasDraft] = useState(false);
+ const [showTips, setShowTips] = useState(data.loggedUserRank < 100);
usePromptWithUnload({
when: Boolean(formData.content.value),
@@ -212,29 +214,58 @@ const Index: FC = ({ visible = false, data, callback }) => {
)}
{showEditor && (
- {
- setFormData({
- content: {
- value: val,
- isInvalid: false,
- errorMsg: '',
- },
- });
- }}
- onFocus={() => {
- setFocusType('answer');
- }}
- onBlur={() => {
- setFocusType('');
- }}
- />
+ <>
+ {
+ setFormData({
+ content: {
+ value: val,
+ isInvalid: false,
+ errorMsg: '',
+ },
+ });
+ }}
+ onFocus={() => {
+ setFocusType('answer');
+ }}
+ onBlur={() => {
+ setFocusType('');
+ }}
+ />
+
+ setShowTips(false)}
+ dismissible
+ className="mt-3">
+ {t('tips.header_1')}
+
+
+ }}
+ />
+
+ {t('tips.li1_2')}
+
+
+ }}
+ />
+
+
+
+ >
)}
diff --git a/ui/src/pages/Questions/Detail/index.tsx b/ui/src/pages/Questions/Detail/index.tsx
index a6b3cc2d..7397c527 100644
--- a/ui/src/pages/Questions/Detail/index.tsx
+++ b/ui/src/pages/Questions/Detail/index.tsx
@@ -56,7 +56,9 @@ const Index = () => {
const userInfo = loggedUserInfoStore((state) => state.user);
const isAuthor = userInfo?.username === question?.user_info?.username;
const isAdmin = userInfo?.role_id === 2;
+ const isModerator = userInfo?.role_id === 3;
const isLogged = Boolean(userInfo?.access_token);
+ const loggedUserRank = userInfo?.rank;
const { state: locationState } = useLocation();
useEffect(() => {
@@ -221,7 +223,7 @@ const Index = () => {
data={item}
questionTitle={question?.title || ''}
slugTitle={question?.url_title}
- isAuthor={isAuthor}
+ canAccept={isAuthor || isAdmin || isModerator}
callback={initPage}
isLogged={isLogged}
/>
@@ -247,6 +249,7 @@ const Index = () => {
data={{
qid,
answered: question?.answered,
+ loggedUserRank,
}}
callback={writeAnswerCallback}
/>
diff --git a/ui/src/pages/Questions/EditAnswer/index.tsx b/ui/src/pages/Questions/EditAnswer/index.tsx
index 680547f0..5c8e9798 100644
--- a/ui/src/pages/Questions/EditAnswer/index.tsx
+++ b/ui/src/pages/Questions/EditAnswer/index.tsx
@@ -1,4 +1,4 @@
-import React, { useState, useRef, useEffect } from 'react';
+import React, { useState, useRef, useEffect, useLayoutEffect } from 'react';
import { Container, Row, Col, Form, Button, Card } from 'react-bootstrap';
import { useParams, useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
@@ -23,18 +23,7 @@ interface FormDataItem {
content: Type.FormValue;
description: Type.FormValue;
}
-const initFormData = {
- content: {
- value: '',
- isInvalid: false,
- errorMsg: '',
- },
- description: {
- value: '',
- isInvalid: false,
- errorMsg: '',
- },
-};
+
const Index = () => {
const { aid = '', qid = '' } = useParams();
const [focusType, setForceType] = useState('');
@@ -42,12 +31,36 @@ const Index = () => {
const { t } = useTranslation('translation', { keyPrefix: 'edit_answer' });
const navigate = useNavigate();
+ const initFormData = {
+ content: {
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ },
+ description: {
+ value: '',
+ isInvalid: false,
+ errorMsg: '',
+ },
+ };
+
const { data } = useQueryAnswerInfo(aid);
const [formData, setFormData] = useState(initFormData);
const [immData, setImmData] = useState(initFormData);
const [contentChanged, setContentChanged] = useState(false);
- initFormData.content.value = data?.info.content || '';
+ useLayoutEffect(() => {
+ if (data?.info?.content) {
+ setFormData({
+ ...formData,
+ content: {
+ value: data.info.content,
+ isInvalid: false,
+ errorMsg: '',
+ },
+ });
+ }
+ }, [data?.info?.content]);
const { data: revisions = [] } = useQueryRevisions(aid);
@@ -147,9 +160,11 @@ const Index = () => {
const handleSelectedRevision = (e) => {
const index = e.target.value;
const revision = revisions[index];
- formData.content.value = revision.content.content;
- setImmData({ ...formData });
- setFormData({ ...formData });
+ if (revision?.content) {
+ formData.content.value = revision.content.content;
+ setImmData({ ...formData });
+ setFormData({ ...formData });
+ }
};
const backPage = () => {
@@ -192,7 +207,7 @@ const Index = () => {
{t('form.fields.revision.label')}
-
+
{revisions.map(({ create_at, reason, user_info }, index) => {
const date = dayjs(create_at * 1000)
.tz()