mirror of https://gitee.com/answerdev/answer.git
144 lines
4.5 KiB
TypeScript
144 lines
4.5 KiB
TypeScript
import { FC, useState } from 'react';
|
|
import { Button, Row, Col } from 'react-bootstrap';
|
|
import { Link } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import { Icon, BaseUserCard, DiffContent, FormatTime } from '@/components';
|
|
import { TIMELINE_NORMAL_ACTIVITY_TYPE } from '@/common/constants';
|
|
import * as Type from '@/common/interface';
|
|
import { getTimelineDetail } from '@/services';
|
|
|
|
interface Props {
|
|
data: Type.TimelineItem;
|
|
objectInfo: Type.TimelineObject;
|
|
isAdmin: boolean;
|
|
revisionList: Type.TimelineItem[];
|
|
}
|
|
const Index: FC<Props> = ({ data, isAdmin, objectInfo, revisionList }) => {
|
|
const { t } = useTranslation('translation', { keyPrefix: 'timeline' });
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [detailData, setDetailData] = useState({
|
|
new_revision: {},
|
|
old_revision: {},
|
|
});
|
|
|
|
const handleItemClick = async (id) => {
|
|
if (!isOpen) {
|
|
const revisionItem = revisionList?.find((v) => v.revision_id === id);
|
|
let oldId;
|
|
if (revisionList?.length > 0 && revisionItem) {
|
|
const idIndex = revisionList.indexOf(revisionItem) || 0;
|
|
if (idIndex === revisionList.length - 1) {
|
|
oldId = 0;
|
|
} else {
|
|
oldId = revisionList[idIndex + 1].revision_id;
|
|
}
|
|
}
|
|
const res = await getTimelineDetail({
|
|
new_revision_id: id,
|
|
old_revision_id: oldId,
|
|
});
|
|
setDetailData(res);
|
|
}
|
|
setIsOpen(!isOpen);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<tr>
|
|
<td>
|
|
<FormatTime time={data.created_at} />
|
|
<br />
|
|
{data.cancelled_at > 0 && <FormatTime time={data.cancelled_at} />}
|
|
</td>
|
|
<td>
|
|
{(data.activity_type === 'rollback' ||
|
|
data.activity_type === 'edited' ||
|
|
data.activity_type === 'asked' ||
|
|
data.activity_type === 'created' ||
|
|
(objectInfo.object_type === 'answer' &&
|
|
data.activity_type === 'answered')) && (
|
|
<Button
|
|
onClick={() => handleItemClick(data.revision_id)}
|
|
variant="link"
|
|
className="text-body p-0 btn-no-border">
|
|
<Icon
|
|
name="caret-right-fill"
|
|
className={`me-1 ${isOpen ? 'rotate-90-deg' : 'rotate-0-deg'}`}
|
|
/>
|
|
{t(data.activity_type)}
|
|
</Button>
|
|
)}
|
|
{data.activity_type === 'accept' && (
|
|
<Link
|
|
to={`/questions/${objectInfo.question_id}/${data?.object_id}`}>
|
|
{t(data.activity_type)}
|
|
</Link>
|
|
)}
|
|
|
|
{objectInfo.object_type === 'question' &&
|
|
data.activity_type === 'answered' && (
|
|
<Link
|
|
to={`/questions/${objectInfo.question_id}/${data.object_id}`}>
|
|
{t(data.activity_type)}
|
|
</Link>
|
|
)}
|
|
|
|
{data.activity_type === 'commented' && (
|
|
<Link
|
|
to={
|
|
objectInfo.object_type === 'answer'
|
|
? `/questions/${objectInfo.question_id}/${objectInfo.answer_id}?commentId=${data.object_id}`
|
|
: `/questions/${objectInfo.question_id}?commentId=${data.object_id}`
|
|
}>
|
|
{t(data.activity_type)}
|
|
</Link>
|
|
)}
|
|
|
|
{TIMELINE_NORMAL_ACTIVITY_TYPE.includes(data.activity_type) && (
|
|
<div>{t(data.activity_type)}</div>
|
|
)}
|
|
|
|
{data.cancelled && (
|
|
<div className="text-danger">{t('cancelled')}</div>
|
|
)}
|
|
</td>
|
|
<td>
|
|
{data.activity_type === 'downvote' && !isAdmin ? (
|
|
<div>{t('n_or_a')}</div>
|
|
) : (
|
|
<BaseUserCard
|
|
className="fs-normal"
|
|
data={{
|
|
username: data.username,
|
|
display_name: data.user_display_name,
|
|
}}
|
|
showAvatar={false}
|
|
showReputation={false}
|
|
/>
|
|
)}
|
|
</td>
|
|
<td>
|
|
<div dangerouslySetInnerHTML={{ __html: data.comment }} />
|
|
</td>
|
|
</tr>
|
|
<tr className={isOpen ? '' : 'd-none'}>
|
|
{/* <td /> */}
|
|
<td colSpan={5} className="p-0 py-5">
|
|
<Row className="justify-content-center">
|
|
<Col xxl={8}>
|
|
<DiffContent
|
|
objectType={objectInfo.object_type}
|
|
newData={detailData?.new_revision}
|
|
oldData={detailData?.old_revision}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</td>
|
|
</tr>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Index;
|