fix: review ui split
This commit is contained in:
+4
-1
@@ -1161,6 +1161,7 @@ ui:
|
||||
tag: Tag
|
||||
post_lowercase: post
|
||||
filter: Filter
|
||||
ignore: Ignore
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
@@ -1800,8 +1801,9 @@ ui:
|
||||
edit_answer: Edit answer
|
||||
edit_tag: Edit tag
|
||||
empty: No review tasks left.
|
||||
approve_tip: Do you approve this revision?
|
||||
approve_this_type: Do you approve this {{ type }}?
|
||||
suggest_type_edit: Suggest {{ type }} edit
|
||||
flag_type: Flag {{ type }}
|
||||
filter_label: Type
|
||||
queued_post: Queued post
|
||||
queued_user: Queued user
|
||||
@@ -1809,6 +1811,7 @@ ui:
|
||||
flagged_user: Flagged user
|
||||
suggested_post_edit: Suggested post edit
|
||||
suggested_tag_edit: Suggested tag edit
|
||||
reputation: reputation
|
||||
timeline:
|
||||
undeleted: undeleted
|
||||
deleted: deleted
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState } from 'react';
|
||||
import { Dropdown, Button } from 'react-bootstrap';
|
||||
|
||||
import EditPostModal from '../EditPostModal';
|
||||
|
||||
const Index = () => {
|
||||
const [showEditPostModal, setShowEditPostModal] = useState(false);
|
||||
|
||||
const handleEditPostModalState = () => {
|
||||
setShowEditPostModal(!showEditPostModal);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown>
|
||||
<Dropdown.Toggle
|
||||
as={Button}
|
||||
variant="outline-primary"
|
||||
id="dropdown-basic">
|
||||
Approve
|
||||
</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.Menu>
|
||||
</Dropdown>
|
||||
<EditPostModal
|
||||
visible={showEditPostModal}
|
||||
handleClose={handleEditPostModalState}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,4 @@
|
||||
.edit-post-modal {
|
||||
max-width: 746px;
|
||||
width: 58.3333% !important;
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import { FC, useState } 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 type * as Type from '@/common/interface';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
interface Props {
|
||||
visible: boolean;
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
interface FormDataItem {
|
||||
title: Type.FormValue<string>;
|
||||
tags: Type.FormValue<Type.Tag[]>;
|
||||
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 { t } = useTranslation('translation', { keyPrefix: 'ask' });
|
||||
const [formData, setFormData] = useState<FormDataItem>(initFormData);
|
||||
const [focusEditor, setFocusEditor] = useState(false);
|
||||
|
||||
const editCaptcha = useCaptchaModal('edit');
|
||||
|
||||
const handleInput = (data: Partial<FormDataItem>) => {
|
||||
setFormData({
|
||||
...formData,
|
||||
...data,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const params: Type.QuestionParams = {
|
||||
title: formData.title.value,
|
||||
content: formData.content.value,
|
||||
tags: formData.tags.value,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
modifyQuestion(ep)
|
||||
.then(async (res) => {
|
||||
await editCaptcha.close();
|
||||
console.log('res', res);
|
||||
// navigate(pathFactory.questionLanding(qid, res?.url_title), {
|
||||
// state: { isReview: res?.wait_for_review },
|
||||
// });
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isError) {
|
||||
editCaptcha.handleCaptchaError(err.list);
|
||||
const data = handleFormError(err, formData);
|
||||
setFormData({ ...data });
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
show={visible}
|
||||
onHide={handleClose}
|
||||
className="w-100"
|
||||
dialogClassName="edit-post-modal">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Edit post</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.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>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useState } from 'react';
|
||||
import { Card, Form } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const Index = () => {
|
||||
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)}
|
||||
/>
|
||||
</Form.Group>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,103 @@
|
||||
import { FC } from 'react';
|
||||
import { Card, Badge } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { BaseUserCard, Tag, FormatTime, Avatar } from '@/components';
|
||||
|
||||
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,
|
||||
},
|
||||
];
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
|
||||
const objectType = 'question';
|
||||
return (
|
||||
<Card>
|
||||
<Card.Header>{t('flag_type', { type: 'post' })}</Card.Header>
|
||||
<Card.Body className="p-0">
|
||||
<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>
|
||||
)}
|
||||
<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.
|
||||
</div>
|
||||
<div className="d-flex align-items-center justify-content-between mt-4">
|
||||
<Badge bg="success">normal</Badge>
|
||||
<div className="d-flex align-items-center small">
|
||||
<BaseUserCard
|
||||
data={{
|
||||
username: 'username',
|
||||
display_name: 'username',
|
||||
avatar: '',
|
||||
reputation: 100,
|
||||
}}
|
||||
avatarSize="24"
|
||||
/>
|
||||
<FormatTime
|
||||
time={1688107033}
|
||||
className="text-secondary ms-1 flex-shrink-0"
|
||||
preFix="answered"
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
||||
@@ -0,0 +1,6 @@
|
||||
import Filter from './Filter';
|
||||
import ApproveDropdown from './ApproveDropdown';
|
||||
import EditPostModal from './EditPostModal';
|
||||
import FlagContent from './FlagContent';
|
||||
|
||||
export { Filter, ApproveDropdown, EditPostModal, FlagContent };
|
||||
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Row, Col, Alert, Stack, Button, Card, Form } from 'react-bootstrap';
|
||||
import { Row, Col, Alert, Stack, Button, Card } from 'react-bootstrap';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@@ -29,6 +29,8 @@ import { pathFactory } from '@/router/pathFactory';
|
||||
import { scrollToDocTop } from '@/utils';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
import { Filter, ApproveDropdown, FlagContent } from './components';
|
||||
|
||||
const Index: FC = () => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'page_review' });
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -107,7 +109,6 @@ const Index: FC = () => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
let itemLink = '';
|
||||
let itemId = '';
|
||||
let editSummary = unreviewed_info?.reason;
|
||||
@@ -138,6 +139,47 @@ const Index: FC = () => {
|
||||
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>
|
||||
@@ -181,58 +223,18 @@ const Index: FC = () => {
|
||||
#{itemId}
|
||||
</Link>
|
||||
</small>
|
||||
{type === 'question' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
oldData={{
|
||||
title: info.title,
|
||||
original_text: info.content,
|
||||
tags: info.tags,
|
||||
}}
|
||||
newData={{
|
||||
title: reviewInfo.title,
|
||||
original_text: reviewInfo.content,
|
||||
tags: reviewInfo.tags,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{type === 'answer' &&
|
||||
info &&
|
||||
reviewInfo &&
|
||||
'content' in reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={{
|
||||
original_text: reviewInfo.content,
|
||||
}}
|
||||
oldData={{
|
||||
original_text: info.content,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === 'tag' && info && reviewInfo && (
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={{
|
||||
original_text: reviewInfo.original_text,
|
||||
}}
|
||||
oldData={{
|
||||
original_text: info.content,
|
||||
}}
|
||||
opts={{ showTitle: false, showTagUrlSlug: false }}
|
||||
/>
|
||||
)}
|
||||
<DiffContent
|
||||
className="mt-2"
|
||||
objectType={type}
|
||||
newData={newData}
|
||||
oldData={oldData}
|
||||
opts={diffOpts}
|
||||
/>
|
||||
</div>
|
||||
</Card.Body>
|
||||
<Card.Footer className="p-3">
|
||||
<p>{t('approve_tip')}</p>
|
||||
<p>{t('approve_this_type', { type: 'revision' })}</p>
|
||||
<Stack direction="horizontal" gap={2}>
|
||||
<Button
|
||||
variant="outline-primary"
|
||||
@@ -240,12 +242,19 @@ const Index: FC = () => {
|
||||
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}
|
||||
@@ -257,18 +266,12 @@ const Index: FC = () => {
|
||||
</Card>
|
||||
)}
|
||||
{noTasks && <Empty>{t('empty')}</Empty>}
|
||||
|
||||
<FlagContent />
|
||||
</Col>
|
||||
|
||||
<Col className="page-right-side mt-4 mt-xl-0">
|
||||
<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+)" />
|
||||
</Form.Group>
|
||||
</Card.Body>
|
||||
</Card>
|
||||
<Filter />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user