feat: add flag review content

This commit is contained in:
shuai
2024-03-12 14:43:31 +08:00
parent 390712965b
commit b2e8ce765b
19 changed files with 1144 additions and 489 deletions
+11 -10
View File
@@ -1173,6 +1173,7 @@ ui:
post_lowercase: post
filter: Filter
ignore: Ignore
submit: Submit
search:
title: Search Results
keywords: Keywords
@@ -1678,8 +1679,8 @@ ui:
page_title: Write
restrict_answer:
title: Restrict answer
label: Each user can only write one answer for each question
text: "They can use the edit link to refine and improve their existing answer, instead."
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."
recommend_tags:
label: Recommend tags
text: "Please input tag slug above, one tag per line."
@@ -1814,16 +1815,16 @@ ui:
edit_tag: Edit tag
empty: No review tasks left.
approve_this_type: Do you approve this {{ type }}?
suggest_type_edit: Suggest {{ type }} edit
flag_type: Flag {{ type }}
suggest_edits: Suggested edits
flag_post: Flag post
flag_user: Flag user
filter_label: Type
queued_post: Queued post
queued_user: Queued user
flagged_post: Flagged post
flagged_user: Flagged user
suggested_post_edit: Suggested post edit
suggested_tag_edit: Suggested tag edit
reputation: reputation
flag_post_type: Flagged this post as {{ type }}
flag_user_type: Flagged this user as {{ type }}
edit_post: Edit post
list_post: List post
unlist_post: Unlist post
timeline:
undeleted: undeleted
deleted: deleted
+2 -2
View File
@@ -1628,8 +1628,8 @@ ui:
page_title: 编辑
restrict_answer:
title: 限制一个回答
label: 每个用户对于每个问题只能有一个回答
text: "用户可以使用编辑按钮优化已有的回答"
label: 每个用户只能为同一问题写一个回答
text: "关闭以允许用户对同一问题编写多个回答,这可能会导致回答不集中。"
recommend_tags:
label: 推荐标签
text: "请在上方输入标签固定链接,每行一个标签。"
+2 -2
View File
@@ -66,9 +66,9 @@ export const ADMIN_LIST_STATUS = {
variant: 'text-bg-danger',
name: 'deleted',
},
unlisted: {
unlist: {
variant: 'text-bg-secondary',
name: 'unlisted',
name: 'unlist',
},
};
+61 -3
View File
@@ -562,7 +562,7 @@ export interface TimelineRes {
timeline: TimelineItem[];
}
export interface ReviewItem {
export interface SuggestReviewItem {
type: 'question' | 'answer' | 'tag';
info: {
url_title?: string;
@@ -584,9 +584,43 @@ export interface ReviewItem {
content: Tag | QuestionDetailRes | AnswerItem;
};
}
export interface ReviewResp {
export interface SuggestReviewResp {
count: number;
list: ReviewItem[];
list: SuggestReviewItem[];
}
export interface ReasonItem {
content_type: string;
description: string;
name: string;
placeholder: string;
reason_type: number;
}
export interface FlagReviewItem {
object_type: 'question' | 'answer' | 'comment' | 'user';
object_id: string;
object_show_status: number;
object_status: number;
tags: Tag[];
title: string;
original_text: string;
reason: ReasonItem;
author_user_info: UserInfoBase;
submitter_user: UserInfoBase;
created_at: number;
submit_at: number;
comment_id: string;
question_id: string;
answer_id: string;
answer_count: number;
answer_accepted?: boolean;
flag_id: string;
}
export interface FlagReviewResp {
count: number;
list: FlagReviewItem[];
}
export interface UserRoleItem {
@@ -638,3 +672,27 @@ export interface UserPluginsConfigRes {
name: string;
slug_name: string;
}
export interface ReviewTypeItem {
label: string;
name: string;
todo_amount: number;
}
export interface PutFlagReviewParams {
operation_type:
| 'edit_post'
| 'close_post'
| 'delete_post'
| 'unlist_post'
| 'ignore_report';
flag_id: string;
close_msg?: string;
close_type?: number;
title?: string;
content?: string;
tags?: Tag[];
// mention_username_list?: any;
captcha_code?: any;
captcha_id?: any;
}
+22 -2
View File
@@ -25,14 +25,20 @@ import ReactDOM from 'react-dom/client';
import { useToast, useCaptchaModal } from '@/hooks';
import type * as Type from '@/common/interface';
import { reportList, postReport, closeQuestion, putReport } from '@/services';
import {
reportList,
postReport,
closeQuestion,
putReport,
putFlagReviewAction,
} from '@/services';
interface Params {
isBackend?: boolean;
type: Type.ReportType;
id: string;
title?: string;
action: Type.ReportAction;
action: Type.ReportAction | 'flag_review_close';
}
const useReportModal = (callback?: () => void) => {
@@ -63,6 +69,7 @@ const useReportModal = (callback?: () => void) => {
rootRef.current.root = ReactDOM.createRoot(div);
}, []);
const getList = ({ type, action, isBackend }: Params) => {
// @ts-ignore
reportList({ type, action, isBackend }).then((res) => {
setList(res);
setShow(true);
@@ -123,6 +130,19 @@ const useReportModal = (callback?: () => void) => {
});
return;
}
if (params.type === 'question' && params.action === 'flag_review_close') {
putFlagReviewAction({
flag_id: params.id,
operation_type: 'close_post',
close_type: reportType.type,
close_msg: content.value,
}).then(() => {
onClose();
asyncCallback();
});
return;
}
if (!params.isBackend && params.action === 'flag') {
rCaptcha.check(() => {
const flagReq = {
+6 -6
View File
@@ -118,13 +118,13 @@ const Answers: FC = () => {
className="text-break text-wrap"
rel="noreferrer">
{li.question_info.title}
{li.accepted === 2 && (
<Icon
name="check-circle-fill"
className="ms-2 text-success"
/>
)}
</a>
{li.accepted === 2 && (
<Icon
name="check-circle-fill"
className="ms-2 text-success"
/>
)}
</Stack>
<div
className="text-truncate-2 small"
+2 -2
View File
@@ -153,9 +153,9 @@ const Questions: FC = () => {
<span
className={classNames(
'badge',
ADMIN_LIST_STATUS.unlisted.variant,
ADMIN_LIST_STATUS.unlist.variant,
)}>
{t(ADMIN_LIST_STATUS.unlisted.name)}
{t(ADMIN_LIST_STATUS.unlist.name)}
</span>
)}
</td>
@@ -46,7 +46,7 @@ const SearchQuestion = ({ similarQuestions }) => {
<ListGroup.Item
action
as="a"
className="link-dark text-wrap text-break"
className="link-dark text-wrap text-break grid gap-0 row-gap-3"
key={item.id}
href={pathFactory.questionLanding(item.id, item.url_title)}
target="_blank">
@@ -56,7 +56,7 @@ const SearchQuestion = ({ similarQuestions }) => {
: null}
{item.accepted_answer ? (
<span className="small ms-3 text-success">
<span className="small ms-3 text-success d-inline-block">
<Icon type="bi" name="check-circle-fill" />
<span className="ms-1">
{t('x_answers', {
@@ -67,7 +67,7 @@ const SearchQuestion = ({ similarQuestions }) => {
</span>
) : (
item.answer_count > 0 && (
<span className="small ms-3 text-secondary">
<span className="small ms-3 text-secondary d-inline-block">
<Icon type="bi" name="chat-square-text-fill" />
<span className="ms-1">
{t('x_answers', {
@@ -1,34 +1,199 @@
import { useState } from 'react';
import { FC, useState } from 'react';
import { Dropdown, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Modal } from '@/components';
import { putFlagReviewAction } from '@/services';
import { useCaptchaModal, useReportModal, useToast } from '@/hooks';
import type * as Type from '@/common/interface';
import EditPostModal from '../EditPostModal';
const Index = () => {
interface IProps {
itemData: Type.FlagReviewItem | null;
curFilter: string;
objectType: Type.FlagReviewItem['object_type'] | '';
approveCallback: () => void;
}
const Index: FC<IProps> = ({
itemData,
objectType,
curFilter,
approveCallback,
}) => {
console.log(objectType);
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [isLoading, setIsLoading] = useState(false);
const [showEditPostModal, setShowEditPostModal] = useState(false);
const closeModal = useReportModal(approveCallback);
const toast = useToast();
const dCaptcha = useCaptchaModal('delete');
const handleEditPostModalState = () => {
setShowEditPostModal(!showEditPostModal);
};
const handleDelete = () => {
let content = '';
setIsLoading(true);
if (objectType === 'question') {
content =
Number(itemData?.answer_count) > 0
? t('question', { keyPrefix: 'delete' })
: t('other', { keyPrefix: 'delete' });
}
if (objectType === 'answer') {
content = itemData?.answer_accepted
? t('answer_accepted', { keyPrefix: 'delete' })
: t('other', { keyPrefix: 'delete' });
}
if (objectType === 'comment') {
content = t('other', { keyPrefix: 'delete' });
}
Modal.confirm({
title: t('title', { keyPrefix: 'delete' }),
content,
cancelBtnVariant: 'link',
confirmBtnVariant: 'danger',
confirmText: t('delete', { keyPrefix: 'btns' }),
onConfirm: () => {
dCaptcha.check(() => {
const req: Type.PutFlagReviewParams = {
operation_type: 'delete_post',
flag_id: String(itemData?.flag_id),
captcha_code: undefined,
captcha_id: undefined,
};
dCaptcha.resolveCaptchaReq(req);
delete req.captcha_code;
delete req.captcha_id;
putFlagReviewAction(req)
.then(async () => {
await dCaptcha.close();
let msg = '';
if (objectType === 'question') {
msg = t('post_deleted', { keyPrefix: 'messages' });
}
if (objectType === 'answer') {
msg = t('tip_answer_deleted');
}
if (objectType === 'answer' || objectType === 'question') {
toast.onShow({
msg,
variant: 'success',
});
}
approveCallback();
})
.catch((ex) => {
if (ex.isError) {
dCaptcha.handleCaptchaError(ex.list);
}
})
.finally(() => {
setIsLoading(false);
});
});
},
});
};
const handleAction = (type) => {
if (type === 'delete') {
handleDelete();
}
if (type === 'close') {
closeModal.onShow({
type: 'question',
id: itemData?.flag_id || '',
action: 'flag_review_close',
});
}
if (type === 'unlist') {
const keyPrefix = 'question_detail.unlist';
Modal.confirm({
title: t('title', { keyPrefix }),
content: t('content', { keyPrefix }),
cancelBtnVariant: 'link',
confirmText: t('confirm_btn', { keyPrefix }),
onConfirm: () => {
putFlagReviewAction({
operation_type: 'unlist_post',
flag_id: itemData?.flag_id || '',
}).then(() => {
toast.onShow({
msg: t(`post_${type}`, { keyPrefix: 'messages' }),
variant: 'success',
});
approveCallback();
});
},
});
}
};
const handleActionEdit = () => {
handleEditPostModalState();
};
return (
<div>
<Dropdown>
<Dropdown.Toggle
as={Button}
disabled={isLoading}
variant="outline-primary"
id="dropdown-basic">
Approve
{t('approve', { keyPrefix: 'btns' })}
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#/action-1">Deactivate user</Dropdown.Item>
<Dropdown.Item href="#/action-2">Suspend user</Dropdown.Item>
<Dropdown.Item href="#/action-3">Delete user</Dropdown.Item>
<Dropdown.Item onClick={() => handleActionEdit()}>
{t('edit_post')}
</Dropdown.Item>
{curFilter === 'normal' && objectType === 'question' && (
<Dropdown.Item onClick={() => handleAction('close')}>
{t('close', { keyPrefix: 'btns' })}
</Dropdown.Item>
)}
{curFilter !== 'deleted' && (
<Dropdown.Item onClick={() => handleAction('delete')}>
{t('delete', { keyPrefix: 'btns' })}
</Dropdown.Item>
)}
{objectType === 'question' && (
<>
<Dropdown.Divider />
{itemData?.object_show_status !== 2 && (
<Dropdown.Item onClick={() => handleAction('unlist')}>
{t('unlist_post')}
</Dropdown.Item>
)}
</>
)}
</Dropdown.Menu>
</Dropdown>
<EditPostModal
visible={showEditPostModal}
handleClose={handleEditPostModalState}
objectType={objectType}
originalData={{
flag_id: itemData?.flag_id || '',
id: itemData?.object_id || '',
title: itemData?.title || '',
content: itemData?.original_text || '',
tags: itemData?.tags || [],
question_id: itemData?.question_id || '',
answer_id: itemData?.answer_id || '',
}}
callback={approveCallback}
/>
</div>
);
@@ -1,20 +1,36 @@
import { FC, useState } from 'react';
import { FC, useState, useEffect } from 'react';
import { Modal, Button, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { modifyQuestion } from '@/services';
import { useCaptchaModal } from '@/hooks';
import { Editor, TagSelector } from '@/components';
import { handleFormError } from '@/utils';
import { putFlagReviewAction } from '@/services';
import { useCaptchaModal, usePageUsers } from '@/hooks';
import { Editor, TagSelector, Mentions, TextArea } from '@/components';
import {
// matchedUsers,
parseUserInfo,
handleFormError,
parseEditMentionUser,
} from '@/utils';
import type * as Type from '@/common/interface';
import './index.scss';
interface Props {
originalData: {
id: string;
flag_id: string;
question_id?: string;
answer_id?: string;
title: string;
content: string;
tags: Type.Tag[];
};
objectType: Type.FlagReviewItem['object_type'] | '';
visible: boolean;
handleClose: () => void;
callback?: () => void;
}
interface FormDataItem {
@@ -23,65 +39,151 @@ interface FormDataItem {
content: Type.FormValue<string>;
}
const Index: FC<Props> = ({ visible = false, handleClose }) => {
const initFormData = {
title: {
value: '',
isInvalid: false,
errorMsg: '',
},
tags: {
value: [],
isInvalid: false,
errorMsg: '',
},
content: {
value: '',
isInvalid: false,
errorMsg: '',
},
};
const initFormData = {
title: {
value: '',
isInvalid: false,
errorMsg: '',
},
tags: {
value: [],
isInvalid: false,
errorMsg: '',
},
content: {
value: '',
isInvalid: false,
errorMsg: '',
},
};
const Index: FC<Props> = ({
originalData,
visible = false,
objectType,
handleClose,
callback,
}) => {
const { t } = useTranslation('translation', { keyPrefix: 'ask' });
const [formData, setFormData] = useState<FormDataItem>(initFormData);
const [focusEditor, setFocusEditor] = useState(false);
const [loaded, setLoaded] = useState(false);
const pageUsers = usePageUsers();
const editCaptcha = useCaptchaModal('edit');
const onClose = (bol) => {
if (bol) {
callback?.();
}
handleClose();
setLoaded(false);
};
const handleInput = (data: Partial<FormDataItem>) => {
if (!loaded) {
return;
}
setFormData({
...formData,
...data,
});
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const checkValidated = (): boolean => {
let bol = true;
const { title, tags, content } = formData;
if (objectType === 'question') {
if (!title.value) {
bol = false;
formData.title = {
value: title.value,
isInvalid: true,
errorMsg: t('form.fields.title.msg.empty', {
keyPrefix: 'ask',
}),
};
}
if (!tags.value.length) {
bol = false;
formData.tags = {
value: tags.value,
isInvalid: true,
errorMsg: t('form.fields.tags.msg.empty', {
keyPrefix: 'ask',
}),
};
}
}
if (!content.value || Array.from(content.value.trim()).length < 6) {
bol = false;
formData.content = {
value: content.value,
isInvalid: true,
errorMsg: t('form.fields.answer.feedback.characters', {
keyPrefix: 'edit_answer',
}),
};
} else {
formData.content = {
value: content.value,
isInvalid: false,
errorMsg: '',
};
}
setFormData({
...formData,
});
return bol;
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
event.stopPropagation();
const params: Type.QuestionParams = {
if (!checkValidated()) {
return;
}
const params: Type.PutFlagReviewParams = {
title: formData.title.value,
content: formData.content.value,
tags: formData.tags.value,
operation_type: 'edit_post',
flag_id: originalData.flag_id,
};
if (objectType === 'answer') {
delete params.title;
delete params.tags;
}
if (objectType === 'comment') {
const { value } = formData.content;
// const users = matchedUsers(value);
// const userNames = unionBy(users.map((user) => user.userName));
const commentMarkDown = parseUserInfo(value);
// params.mention_username_list = userNames;
params.content = commentMarkDown;
delete params.title;
delete params.tags;
}
editCaptcha.check(() => {
const ep = {
...params,
id: '',
edit_summary: '',
};
const imgCode = editCaptcha.getCaptcha();
if (imgCode.verify) {
ep.captcha_code = imgCode.captcha_code;
ep.captcha_id = imgCode.captcha_id;
if (objectType === 'question') {
const imgCode = editCaptcha.getCaptcha();
if (imgCode.verify) {
params.captcha_code = imgCode.captcha_code;
params.captcha_id = imgCode.captcha_id;
}
}
modifyQuestion(ep)
.then(async (res) => {
putFlagReviewAction(params)
.then(async () => {
await editCaptcha.close();
console.log('res', res);
// navigate(pathFactory.questionLanding(qid, res?.url_title), {
// state: { isReview: res?.wait_for_review },
// });
onClose(true);
})
.catch((err) => {
if (err.isError) {
@@ -92,102 +194,180 @@ const Index: FC<Props> = ({ visible = false, handleClose }) => {
});
});
};
const handleSelected = (val) => {
if (!loaded) {
return;
}
setFormData({
...formData,
content: {
value: val,
errorMsg: '',
isInvalid: false,
},
});
};
useEffect(() => {
if (!visible) {
return;
}
formData.title.value = originalData.title;
formData.content.value = originalData.content;
formData.tags.value = originalData.tags.map((item) => {
return {
...item,
parsed_text: '',
original_text: '',
};
});
setFormData({ ...formData });
setLoaded(true);
}, [visible]);
return (
<Modal
show={visible}
onHide={handleClose}
onHide={() => onClose(false)}
className="w-100"
dialogClassName="edit-post-modal">
<Modal.Header closeButton>
<Modal.Title>Edit post</Modal.Title>
<Modal.Title>
{t('edit_post', { keyPrefix: 'page_review' })}
</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form noValidate onSubmit={handleSubmit}>
<Form.Group controlId="title" className="mb-3">
<Form.Label>{t('form.fields.title.label')}</Form.Label>
<Form.Control
type="text"
value={formData.title.value}
isInvalid={formData.title.isInvalid}
onChange={(e) => {
handleInput({
title: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
});
}}
placeholder={t('form.fields.title.placeholder')}
autoFocus
contentEditable
/>
<Form noValidate onSubmit={handleSubmit}>
<Modal.Body>
{objectType === 'question' && (
<Form.Group controlId="title" className="mb-3">
<Form.Label>{t('form.fields.title.label')}</Form.Label>
<Form.Control
type="text"
value={formData.title.value}
isInvalid={formData.title.isInvalid}
onChange={(e) => {
handleInput({
title: {
value: e.target.value,
isInvalid: false,
errorMsg: '',
},
});
}}
placeholder={t('form.fields.title.placeholder')}
autoFocus
contentEditable
/>
<Form.Control.Feedback type="invalid">
{formData.title.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Control.Feedback type="invalid">
{formData.title.errorMsg}
</Form.Control.Feedback>
</Form.Group>
)}
<Form.Group controlId="body">
<Form.Label>{t('form.fields.body.label')}</Form.Label>
<Form.Control
defaultValue={formData.content.value}
isInvalid={formData.content.isInvalid}
hidden
/>
<Editor
value={formData.content.value}
onChange={(value) => {
handleInput({
content: { value, errorMsg: '', isInvalid: false },
});
}}
className={classNames(
'form-control p-0',
focusEditor ? 'focus' : '',
)}
onFocus={() => {
setFocusEditor(true);
}}
onBlur={() => {
setFocusEditor(false);
}}
/>
<Form.Control.Feedback type="invalid">
{formData.content.errorMsg}
</Form.Control.Feedback>
</Form.Group>
<Form.Group controlId="tags" className="my-3">
<Form.Label>{t('form.fields.tags.label')}</Form.Label>
<Form.Control
defaultValue={JSON.stringify(formData.tags.value)}
isInvalid={formData.tags.isInvalid}
hidden
/>
<TagSelector
value={formData.tags.value}
onChange={(value) => {
handleInput({
tags: { value, errorMsg: '', isInvalid: false },
});
}}
showRequiredTag
maxTagLength={5}
/>
<Form.Control.Feedback type="invalid">
{formData.tags.errorMsg}
</Form.Control.Feedback>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={handleClose}>
{t('close', { keyPrefix: 'btns' })}
</Button>
<Button variant="primary" onClick={handleClose}>
{t('submit', { keyPrefix: 'btns' })}
</Button>
</Modal.Footer>
{objectType !== 'comment' && (
<Form.Group controlId="body">
<Form.Label>
{objectType === 'question'
? t('form.fields.body.label')
: t('form.fields.answer.label')}
</Form.Label>
<Form.Control
defaultValue={formData.content.value}
isInvalid={formData.content.isInvalid}
hidden
/>
<Editor
value={formData.content.value}
onChange={(value) => {
handleInput({
content: { value, errorMsg: '', isInvalid: false },
});
}}
className={classNames(
'form-control p-0',
focusEditor ? 'focus' : '',
)}
onFocus={() => {
setFocusEditor(true);
}}
onBlur={() => {
setFocusEditor(false);
}}
/>
<Form.Control.Feedback type="invalid">
{formData.content.errorMsg}
</Form.Control.Feedback>
</Form.Group>
)}
{objectType === 'question' && (
<Form.Group controlId="tags" className="my-3">
<Form.Label>{t('form.fields.tags.label')}</Form.Label>
<Form.Control
defaultValue={JSON.stringify(formData.tags.value)}
isInvalid={formData.tags.isInvalid}
hidden
/>
<TagSelector
value={formData.tags.value}
onChange={(value) => {
handleInput({
tags: { value, errorMsg: '', isInvalid: false },
});
}}
showRequiredTag
maxTagLength={5}
/>
<Form.Control.Feedback type="invalid">
{formData.tags.errorMsg}
</Form.Control.Feedback>
</Form.Group>
)}
{objectType === 'comment' && (
<div className="w-100">
<div
className={classNames('custom-form-control', {
'is-invalid': formData.content.isInvalid,
})}>
<Form.Label>Comment</Form.Label>
<Mentions
pageUsers={pageUsers.getUsers()}
onSelected={handleSelected}>
<TextArea
size="sm"
rows={4}
value={parseEditMentionUser(formData.content.value)}
onChange={(e) => {
handleInput({
content: {
value: e.target.value,
errorMsg: '',
isInvalid: false,
},
});
}}
/>
</Mentions>
</div>
<Form.Control.Feedback type="invalid">
{formData.content.errorMsg}
</Form.Control.Feedback>
</div>
)}
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => onClose(false)}>
{t('close', { keyPrefix: 'btns' })}
</Button>
<Button variant="primary" type="submit">
{t('submit', { keyPrefix: 'btns' })}
</Button>
</Modal.Footer>
</Form>
</Modal>
);
};
+23 -16
View File
@@ -1,29 +1,36 @@
import { useState } from 'react';
import { FC } from 'react';
import { Card, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
const Index = () => {
import * as Type from '@/common/interface';
interface IProps {
list: Type.ReviewTypeItem[] | undefined;
checked: string;
callback: (type: string) => void;
}
const Index: FC<IProps> = ({ list, checked, callback }) => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [checked, setValue] = useState(false);
return (
<Card>
<Card.Header>{t('filter', { keyPrefix: 'btns' })}</Card.Header>
<Card.Body>
<Form.Group>
<Form.Label>{t('filter_label')}</Form.Label>
<Form.Check
type="radio"
label="Queued post (99+)"
checked={checked}
onChange={(e) => setValue(e.target.checked)}
/>
<Form.Check
type="radio"
label="Queued post (199+)"
checked={checked}
onChange={(e) => setValue(e.target.checked)}
/>
{list?.map((item) => {
return (
<Form.Check
key={item.name}
type="radio"
id={item.name}
disabled={item.todo_amount <= 0}
label={`${item.label} (${item.todo_amount})`}
checked={checked === item.name}
onChange={() => callback(item.name)}
/>
);
})}
</Form.Group>
</Card.Body>
</Card>
@@ -1,101 +1,235 @@
import { FC } from 'react';
import { Card, Badge } from 'react-bootstrap';
import { FC, useEffect, useState } from 'react';
import { Card, Alert, Stack, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { BaseUserCard, Tag, FormatTime, Avatar } from '@/components';
import classNames from 'classnames';
const tag = [
{
display_name: 'bug',
slug_name: 'bug',
original_text: '111',
recommend: true,
},
{
display_name: 'react',
slug_name: 'react',
original_text: '222',
reserved: true,
},
{
display_name: 'test',
slug_name: 'test',
original_text: '111',
recommend: false,
reserved: false,
},
];
import { getFlagReviewPostList, putFlagReviewAction } from '@/services';
import { BaseUserCard, Tag, FormatTime } from '@/components';
import { pathFactory } from '@/router/pathFactory';
import { scrollToDocTop } from '@/utils';
import type * as Type from '@/common/interface';
import { ADMIN_LIST_STATUS } from '@/common/constants';
import ApproveDropdown from '../ApproveDropdown';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const objectType = 'question';
const [noTasks, setNoTasks] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [page, setPage] = useState(1);
const [reviewResp, setReviewResp] = useState<Type.FlagReviewResp>();
const flagItemData = reviewResp?.list[0] as Type.FlagReviewItem;
console.log('reviewResp', reviewResp);
const resolveNextOne = (resp, pageNumber) => {
const { count, list = [] } = resp;
// auto rollback
if (!list.length && count && page !== 1) {
pageNumber = 1;
setPage(pageNumber);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
queryNextOne(pageNumber);
return;
}
if (pageNumber !== page) {
setPage(pageNumber);
}
setReviewResp(resp);
if (!list.length) {
setNoTasks(true);
}
setTimeout(() => {
scrollToDocTop();
}, 150);
};
const queryNextOne = (pageNumber) => {
getFlagReviewPostList(pageNumber)
.then((resp) => {
resolveNextOne(resp, pageNumber);
})
.catch((ex) => {
console.error('review next error: ', ex);
});
};
useEffect(() => {
queryNextOne(page);
}, []);
const handlingApprove = () => {
if (!flagItemData) {
return;
}
queryNextOne(page);
};
const handleIgnore = () => {
setIsLoading(true);
putFlagReviewAction({
operation_type: 'ignore_report',
flag_id: String(flagItemData?.flag_id),
})
.then(() => {
queryNextOne(page + 1);
})
.finally(() => {
setIsLoading(false);
});
};
const {
object_type,
submitter_user,
author_user_info,
object_status,
reason,
} = flagItemData || {
object_type: '',
submitter_user: null,
author_user_info: null,
reason: null,
object_status: 0,
};
let itemLink = '';
let itemId = '';
let itemTimePrefix = '';
if (object_type === 'question') {
itemLink = pathFactory.questionLanding(
String(flagItemData?.question_id),
flagItemData?.title,
);
itemId = String(flagItemData?.question_id);
itemTimePrefix = 'asked';
} else if (object_type === 'answer') {
itemLink = pathFactory.answerLanding({
// @ts-ignore
questionId: flagItemData?.question_id,
slugTitle: flagItemData?.title,
answerId: String(flagItemData?.object_id),
});
itemId = String(flagItemData?.object_id);
itemTimePrefix = 'answered';
} else if (object_type === 'comment') {
if (flagItemData?.question_id && flagItemData?.answer_id) {
itemLink = `${pathFactory.answerLanding({
questionId: flagItemData?.question_id,
answerId: flagItemData?.answer_id,
})}?commentId=${flagItemData?.comment_id}`;
} else {
itemLink = `${pathFactory.questionLanding(
String(flagItemData?.question_id),
flagItemData?.title,
)}?commentId=${flagItemData?.comment_id}`;
}
itemId = String(flagItemData?.comment_id);
itemTimePrefix = 'commented';
}
if (noTasks) return null;
return (
<Card>
<Card.Header>{t('flag_type', { type: 'post' })}</Card.Header>
<Card.Header>
{object_type !== 'user' ? t('flag_post') : t('flag_user')}
</Card.Header>
<Card.Body className="p-0">
<Alert variant="info" className="border-0 rounded-0 mb-0">
<Stack
direction="horizontal"
gap={1}
className="align-items-center mb-2">
<BaseUserCard data={submitter_user} avatarSize="24" />
{flagItemData?.submit_at && (
<FormatTime
time={flagItemData.submit_at}
className="small text-secondary"
preFix={t('proposed')}
/>
)}
</Stack>
<Stack className="align-items-start">
<p className="mb-0">
{object_type !== 'user'
? t('flag_post_type', { type: reason?.name })
: t('flag_user_type', { type: reason?.name })}
</p>
</Stack>
</Alert>
<div className="p-3">
<h5 className="mb-3">
How do I test weather variable against multiple
</h5>
{objectType === 'question' && (
<div className="mb-4">
{tag?.map((item) => {
return (
<Tag key={item.slug_name} className="me-1" data={item} />
);
})}
</div>
<small className="d-block text-secondary mb-4">
<span>{t(object_type, { keyPrefix: 'btns' })} </span>
<Link to={itemLink} target="_blank" className="link-secondary">
#{itemId}
</Link>
</small>
{object_type === 'question' && (
<>
<h5 className="mb-3">{flagItemData?.title}</h5>
<div className="mb-4">
{flagItemData?.tags?.map((item) => {
return (
<Tag key={item.slug_name} className="me-1" data={item} />
);
})}
</div>
</>
)}
<div className="small font-monospace">
Python is a multi-paradigm, dynamically typed, multi-purpose
programming language. It is designed to be quick to learn,
understand, and use, and enforces a clean and uniform syntax. Please
note that Python 2 is officially out of support as of 2020-01-01.
For version-specific Python questions, add the [python-2.7] or
[python-3.x] tag. When using a Python variant library (e.g. Pandas,
NumPy), please include it in the tags.
{flagItemData?.original_text}
</div>
<div className="d-flex align-items-center justify-content-between mt-4">
<Badge bg="success">normal</Badge>
<div className="d-flex flex-wrap align-items-center justify-content-between mt-4">
<div>
<span
className={classNames(
'badge',
ADMIN_LIST_STATUS[object_status]?.variant,
)}>
{t(ADMIN_LIST_STATUS[object_status]?.name, {
keyPrefix: 'admin.questions',
})}
</span>
{flagItemData?.object_show_status === 2 && (
<span
className={classNames(
'ms-1 badge',
ADMIN_LIST_STATUS.unlist.variant,
)}>
{t(ADMIN_LIST_STATUS.unlist.name, { keyPrefix: 'btns' })}
</span>
)}
</div>
<div className="d-flex align-items-center small">
<BaseUserCard
data={{
username: 'username',
display_name: 'username',
avatar: '',
reputation: 100,
}}
avatarSize="24"
/>
<BaseUserCard data={author_user_info} avatarSize="24" />
<FormatTime
time={1688107033}
time={Number(flagItemData?.created_at)}
className="text-secondary ms-1 flex-shrink-0"
preFix="answered"
preFix={t(itemTimePrefix, { keyPrefix: 'question_detail' })}
/>
</div>
</div>
</div>
<div className="p-3 d-flex">
<Avatar
avatar=""
size="40"
searchStr="s=48"
alt=""
className="me-2"
/>
<div className="small">
<Link to="/test">
111 <span className="text-secondary">@111</span>
</Link>
<div className="mt-1">
I'm a web developer with in-depth experience in UI/UX design.
</div>
<div className="text-secondary mt-1">280 {t('reputation')}</div>
</div>
</div>
</Card.Body>
<Card.Footer className="p-3">
<p>{t('approve_this_type', { type: 'revision' })}</p>
<Stack direction="horizontal" gap={2}>
<ApproveDropdown
objectType={object_type}
itemData={flagItemData}
curFilter={ADMIN_LIST_STATUS[object_status]?.name}
approveCallback={handlingApprove}
/>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handleIgnore}>
{t('ignore', { keyPrefix: 'btns' })}
</Button>
</Stack>
</Card.Footer>
</Card>
);
};
@@ -0,0 +1,246 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { FC, useEffect, useState } from 'react';
import { Alert, Stack, Button, Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { BaseUserCard, FormatTime, DiffContent } from '@/components';
import { getSuggestReviewList, revisionAudit } from '@/services';
import { pathFactory } from '@/router/pathFactory';
import { scrollToDocTop } from '@/utils';
import type * as Type from '@/common/interface';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [isLoading, setIsLoading] = useState(false);
const [noTasks, setNoTasks] = useState(false);
const [page, setPage] = useState(1);
const [reviewResp, setReviewResp] = useState<Type.SuggestReviewResp>();
const ro = reviewResp?.list[0];
const { info, type, unreviewed_info } = ro || {
info: null,
type: '',
unreviewed_info: null,
};
const resolveNextOne = (resp, pageNumber) => {
const { count, list = [] } = resp;
// auto rollback
if (!list.length && count && page !== 1) {
pageNumber = 1;
setPage(pageNumber);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
queryNextOne(pageNumber);
return;
}
if (pageNumber !== page) {
setPage(pageNumber);
}
setReviewResp(resp);
if (!list.length) {
setNoTasks(true);
}
setTimeout(() => {
scrollToDocTop();
}, 150);
};
const queryNextOne = (pageNumber) => {
getSuggestReviewList(pageNumber)
.then((resp) => {
resolveNextOne(resp, pageNumber);
})
.catch((ex) => {
console.error('review next error: ', ex);
});
};
const reviewInfo = unreviewed_info?.content;
const handlingApprove = () => {
if (!unreviewed_info) {
return;
}
setIsLoading(true);
revisionAudit(unreviewed_info.id, 'approve')
.then(() => {
queryNextOne(page);
})
.catch((ex) => {
console.error('revisionAudit approve error: ', ex);
})
.finally(() => {
setIsLoading(false);
});
};
const handlingReject = () => {
if (!unreviewed_info) {
return;
}
setIsLoading(true);
revisionAudit(unreviewed_info.id, 'reject')
.then(() => {
queryNextOne(page);
})
.catch((ex) => {
console.error('revisionAudit reject error: ', ex);
})
.finally(() => {
setIsLoading(false);
});
};
let itemLink = '';
let itemId = '';
let editSummary = unreviewed_info?.reason;
const editor = unreviewed_info?.user_info;
const editTime = unreviewed_info?.create_at;
if (type === 'question') {
itemLink = pathFactory.questionLanding(info?.object_id, info?.url_title);
itemId = info?.object_id;
editSummary ||= t('edit_question');
} else if (type === 'answer') {
itemLink = pathFactory.answerLanding({
// @ts-ignore
questionId: unreviewed_info.content.question_id,
slugTitle: info?.url_title,
answerId: unreviewed_info.object_id,
});
itemId = unreviewed_info.object_id;
editSummary ||= t('edit_answer');
} else if (type === 'tag') {
const tagInfo = unreviewed_info.content as Type.Tag;
itemLink = pathFactory.tagLanding(tagInfo.slug_name);
itemId = tagInfo?.tag_id || tagInfo.slug_name;
editSummary ||= t('edit_tag');
}
useEffect(() => {
queryNextOne(page);
}, []);
if (noTasks) return null;
let newData: Record<string, any> = {};
let oldData: Record<string, any> = {};
let diffOpts: Partial<{
showTitle: boolean;
showTagUrlSlug: boolean;
}> = {
showTitle: true,
showTagUrlSlug: true,
};
if (type === 'question' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
title: reviewInfo.title,
original_text: reviewInfo.content,
tags: reviewInfo.tags,
};
oldData = {
title: info.title,
original_text: info.content,
tags: info.tags,
};
}
if (type === 'answer' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
original_text: reviewInfo.content,
};
oldData = {
original_text: info.content,
};
}
if (type === 'tag' && info && reviewInfo) {
newData = {
original_text: reviewInfo.original_text,
};
oldData = {
original_text: info.content,
};
diffOpts = { showTitle: false, showTagUrlSlug: false };
}
return (
<Card>
<Card.Header>
{t('suggest_type_edit', {
type:
type === 'question' || type === 'answer'
? t('post_lowercase', { keyPrefix: 'btns' })
: type,
})}
</Card.Header>
<Card.Body className="p-0">
<Alert variant="info" className="border-0 rounded-0 mb-0">
<Stack
direction="horizontal"
gap={1}
className="align-items-center mb-2">
<BaseUserCard data={editor} avatarSize="24" />
{editTime && (
<FormatTime
time={editTime}
className="small text-secondary"
preFix={t('proposed')}
/>
)}
</Stack>
<Stack className="align-items-start">
<p className="mb-0">{editSummary}</p>
</Stack>
</Alert>
<div className="p-3">
<small className="d-block text-secondary mb-4">
<span>{t(type, { keyPrefix: 'btns' })} </span>
<Link to={itemLink} target="_blank" className="link-secondary">
#{itemId}
</Link>
</small>
<DiffContent
className="mt-2"
objectType={type}
newData={newData}
oldData={oldData}
opts={diffOpts}
/>
</div>
</Card.Body>
<Card.Footer className="p-3">
<p>{t('approve_this_type', { type: 'revision' })}</p>
<Stack direction="horizontal" gap={2}>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingApprove}>
{t('approve', { keyPrefix: 'btns' })}
</Button>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingReject}>
{t('reject', { keyPrefix: 'btns' })}
</Button>
</Stack>
</Card.Footer>
</Card>
);
};
export default Index;
+8 -1
View File
@@ -1,6 +1,13 @@
import Filter from './Filter';
import ApproveDropdown from './ApproveDropdown';
import EditPostModal from './EditPostModal';
import SuggestEditContent from './SuggestEditContent';
import FlagContent from './FlagContent';
export { Filter, ApproveDropdown, EditPostModal, FlagContent };
export {
Filter,
ApproveDropdown,
EditPostModal,
FlagContent,
SuggestEditContent,
};
+28 -226
View File
@@ -18,260 +18,62 @@
*/
import { FC, useEffect, useState } from 'react';
import { Row, Col, Alert, Stack, Button, Card } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { Row, Col } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { usePageTags } from '@/hooks';
import { BaseUserCard, FormatTime, Empty, DiffContent } from '@/components';
import { getReviewList, revisionAudit } from '@/services';
import { pathFactory } from '@/router/pathFactory';
import { scrollToDocTop } from '@/utils';
import { Empty } from '@/components';
import { getReviewType } from '@/services';
import type * as Type from '@/common/interface';
import { Filter, ApproveDropdown, FlagContent } from './components';
import { Filter, FlagContent, SuggestEditContent } from './components';
const Index: FC = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
const [isLoading, setIsLoading] = useState(false);
const [noTasks, setNoTasks] = useState(false);
const [page, setPage] = useState(1);
const [reviewResp, setReviewResp] = useState<Type.ReviewResp>();
const ro = reviewResp?.list[0];
const { info, type, unreviewed_info } = ro || {
info: null,
type: '',
unreviewed_info: null,
};
const resolveNextOne = (resp, pageNumber) => {
const { count, list = [] } = resp;
// auto rollback
if (!list.length && count && page !== 1) {
pageNumber = 1;
setPage(pageNumber);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
queryNextOne(pageNumber);
return;
}
if (pageNumber !== page) {
setPage(pageNumber);
}
setReviewResp(resp);
if (!list.length) {
setNoTasks(true);
}
setTimeout(() => {
scrollToDocTop();
}, 150);
};
const queryNextOne = (pageNumber) => {
getReviewList(pageNumber)
const [reviewTypeList, setReviewTypeList] = useState<Type.ReviewTypeItem[]>();
const [currentReviewType, setCurrentReviewType] = useState('');
const fetchReviewType = () => {
getReviewType()
.then((resp) => {
resolveNextOne(resp, pageNumber);
const filterData = resp.filter((item) => item.todo_amount > 0);
if (filterData.length > 0) {
setCurrentReviewType(filterData[0].name);
}
setReviewTypeList(resp);
})
.catch((ex) => {
console.error('review next error: ', ex);
console.error('getReviewType error: ', ex);
});
};
const reviewInfo = unreviewed_info?.content;
const handlingSkip = () => {
queryNextOne(page + 1);
};
const handlingApprove = () => {
if (!unreviewed_info) {
return;
}
setIsLoading(true);
revisionAudit(unreviewed_info.id, 'approve')
.then(() => {
queryNextOne(page);
})
.catch((ex) => {
console.error('revisionAudit approve error: ', ex);
})
.finally(() => {
setIsLoading(false);
});
};
const handlingReject = () => {
if (!unreviewed_info) {
return;
}
setIsLoading(true);
revisionAudit(unreviewed_info.id, 'reject')
.then(() => {
queryNextOne(page);
})
.catch((ex) => {
console.error('revisionAudit reject error: ', ex);
})
.finally(() => {
setIsLoading(false);
});
};
let itemLink = '';
let itemId = '';
let editSummary = unreviewed_info?.reason;
const editor = unreviewed_info?.user_info;
const editTime = unreviewed_info?.create_at;
if (type === 'question') {
itemLink = pathFactory.questionLanding(info?.object_id, info?.url_title);
itemId = info?.object_id;
editSummary ||= t('edit_question');
} else if (type === 'answer') {
itemLink = pathFactory.answerLanding({
// @ts-ignore
questionId: unreviewed_info.content.question_id,
slugTitle: info?.url_title,
answerId: unreviewed_info.object_id,
});
itemId = unreviewed_info.object_id;
editSummary ||= t('edit_answer');
} else if (type === 'tag') {
const tagInfo = unreviewed_info.content as Type.Tag;
itemLink = pathFactory.tagLanding(tagInfo.slug_name);
itemId = tagInfo?.tag_id || tagInfo.slug_name;
editSummary ||= t('edit_tag');
}
useEffect(() => {
queryNextOne(page);
fetchReviewType();
}, []);
usePageTags({
title: t('review'),
});
let newData: Record<string, any> = {};
let oldData: Record<string, any> = {};
let diffOpts: Partial<{
showTitle: boolean;
showTagUrlSlug: boolean;
}> = {
showTitle: true,
showTagUrlSlug: true,
};
if (type === 'question' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
title: reviewInfo.title,
original_text: reviewInfo.content,
tags: reviewInfo.tags,
};
oldData = {
title: info.title,
original_text: info.content,
tags: info.tags,
};
}
if (type === 'answer' && info && reviewInfo && 'content' in reviewInfo) {
newData = {
original_text: reviewInfo.content,
};
oldData = {
original_text: info.content,
};
}
if (type === 'tag' && info && reviewInfo) {
newData = {
original_text: reviewInfo.original_text,
};
oldData = {
original_text: info.content,
};
diffOpts = { showTitle: false, showTagUrlSlug: false };
}
return (
<Row className="pt-4 mb-5">
<h3 className="mb-4">{t('review')}</h3>
<Col className="page-main flex-auto">
{!noTasks && ro && (
<Card>
<Card.Header>
{t('suggest_type_edit', {
type:
type === 'question' || type === 'answer'
? t('post_lowercase', { keyPrefix: 'btns' })
: type,
})}
</Card.Header>
<Card.Body className="p-0">
<Alert variant="info" className="border-0 rounded-0 mb-0">
<Stack
direction="horizontal"
gap={1}
className="align-items-center mb-2">
<BaseUserCard data={editor} avatarSize="24" />
{editTime && (
<FormatTime
time={editTime}
className="small text-secondary"
preFix={t('proposed')}
/>
)}
</Stack>
<Stack className="align-items-start">
<p className="mb-0">{editSummary}</p>
</Stack>
</Alert>
<div className="p-3">
<small className="d-block text-secondary mb-4">
<span>{t(type, { keyPrefix: 'btns' })} </span>
<Link
to={itemLink}
target="_blank"
className="link-secondary">
#{itemId}
</Link>
</small>
<DiffContent
className="mt-2"
objectType={type}
newData={newData}
oldData={oldData}
opts={diffOpts}
/>
</div>
</Card.Body>
<Card.Footer className="p-3">
<p>{t('approve_this_type', { type: 'revision' })}</p>
<Stack direction="horizontal" gap={2}>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingApprove}>
{t('approve', { keyPrefix: 'btns' })}
</Button>
<ApproveDropdown />
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingReject}>
{t('reject', { keyPrefix: 'btns' })}
</Button>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingReject}>
{t('ignore', { keyPrefix: 'btns' })}
</Button>
<Button
variant="outline-primary"
disabled={isLoading}
onClick={handlingSkip}>
{t('skip', { keyPrefix: 'btns' })}
</Button>
</Stack>
</Card.Footer>
</Card>
)}
{noTasks && <Empty>{t('empty')}</Empty>}
{currentReviewType === 'suggested_post_edit' && <SuggestEditContent />}
{/* {currentReviewType === 'flagged_post' && <FlagContent />} */}
<FlagContent />
<Empty>{t('empty')}</Empty>
</Col>
<Col className="page-right-side mt-4 mt-xl-0">
<Filter />
<Filter
list={reviewTypeList}
checked={currentReviewType}
callback={(name) => {
setCurrentReviewType(name);
}}
/>
</Col>
</Row>
);
+1 -1
View File
@@ -41,7 +41,7 @@ const questionLanding = (questionId: string, slugTitle: string = '') => {
}
// @ts-ignore
if (/[13]/.test(seo.permalink) && slugTitle) {
return `/questions/${questionId}/${slugTitle}`;
return `/questions/${questionId}/${encodeURIComponent(slugTitle)}`;
}
return `/questions/${questionId}`;
+1
View File
@@ -29,3 +29,4 @@ export * from './timeline';
export * from './revision';
export * from './user';
export * from './Oauth';
export * from './review';
+40
View File
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// import useSWR from 'swr';
import request from '@/utils/request';
import * as Type from '@/common/interface';
export const getSuggestReviewList = (page: number) => {
const apiUrl = `/answer/api/v1/revisions/unreviewed?page=${page}`;
return request.get<Type.SuggestReviewResp>(apiUrl);
};
export const getReviewType = () => {
return request.get<Type.ReviewTypeItem[]>('/answer/api/v1/reviewing/type');
};
export const getFlagReviewPostList = (page: number) => {
const apiUrl = `/answer/api/v1/report/unreviewed/post?page=${page}`;
return request.get<Type.FlagReviewResp>(apiUrl);
};
export const putFlagReviewAction = (params: Type.PutFlagReviewParams) => {
return request.put('/answer/api/v1/report/review', params);
};
-6
View File
@@ -18,7 +18,6 @@
*/
import request from '@/utils/request';
import * as Type from '@/common/interface';
export const editCheck = (id: string, passingError: boolean = false) => {
const apiUrl = `/answer/api/v1/revisions/edit/check?id=${id}`;
@@ -34,8 +33,3 @@ export const revisionAudit = (id: string, operation: 'approve' | 'reject') => {
operation,
});
};
export const getReviewList = (page: number) => {
const apiUrl = `/answer/api/v1/revisions/unreviewed?page=${page}`;
return request.get<Type.ReviewResp>(apiUrl);
};