blob: bd0ecbb5bf3d3afbcedfef56f3f4e1c0039a4cb4 [file]
/*
* 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 { memo, FC, useEffect, useRef } from 'react';
import { Button, Alert, Badge } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { useSearchParams } from 'react-router-dom';
import {
Actions,
Operate,
UserCard,
Icon,
Comment,
htmlRender,
ImgViewer,
} from '@/components';
import { scrollToElementTop, bgFadeOut } from '@/utils';
import { AnswerItem } from '@/common/interface';
import { acceptanceAnswer } from '@/services';
import { useRenderHtmlPlugin } from '@/utils/pluginKit';
interface Props {
data: AnswerItem;
/** router answer id */
aid?: string;
canAccept: boolean;
questionTitle: string;
isLogged: boolean;
callback: (type: string) => void;
}
const Index: FC<Props> = ({
aid,
data,
isLogged,
questionTitle = '',
callback,
canAccept = false,
}) => {
const { t } = useTranslation('translation', {
keyPrefix: 'question_detail',
});
const [searchParams] = useSearchParams();
const answerRef = useRef<HTMLDivElement>(null);
useRenderHtmlPlugin(answerRef.current?.querySelector('.fmt') as HTMLElement);
const acceptAnswer = () => {
acceptanceAnswer({
question_id: data.question_id,
answer_id: data.accepted === 2 ? '0' : data.id,
}).then(() => {
callback?.('');
});
};
useEffect(() => {
if (!answerRef?.current) {
return;
}
htmlRender(answerRef.current.querySelector('.fmt'), {
copySuccessText: t('copied', { keyPrefix: 'messages' }),
copyText: t('copy', { keyPrefix: 'messages' }),
});
}, [answerRef.current]);
useEffect(() => {
if (aid === data.id) {
setTimeout(() => {
const element = answerRef.current;
scrollToElementTop(element);
if (!searchParams.get('commentId')) {
bgFadeOut(answerRef.current);
}
}, 100);
}
}, [data.id]);
if (!data?.id) {
return null;
}
return (
<div id={data.id} ref={answerRef} className="answer-item py-4">
{data.status === 10 && (
<Alert variant="danger" className="mb-4">
{t('post_deleted', { keyPrefix: 'messages' })}
</Alert>
)}
{data.status === 11 && (
<Alert variant="secondary" className="mb-4">
{t('post_pending', { keyPrefix: 'messages' })}
</Alert>
)}
<div className="d-flex justify-content-between mb-3">
<div style={{ minWidth: '196px' }}>
<UserCard
data={data?.user_info}
time={Number(data.create_time)}
updateTime={Number(data.update_time)}
updateTimePrefix={t('edit')}
isLogged={isLogged}
timelinePath={`/posts/${data.question_id}/${data.id}/timeline`}
/>
</div>
{data?.accepted === 2 && (
<div className="lh-1">
<Badge bg="success" pill>
<Icon name="check-circle-fill me-1" />
Best answer
</Badge>
</div>
)}
</div>
<ImgViewer>
<article
className="fmt text-break text-wrap"
dangerouslySetInnerHTML={{ __html: data?.html }}
/>
</ImgViewer>
<div className="d-flex align-items-center my-4">
<Actions
source="answer"
data={{
id: data?.id,
isHate: data?.vote_status === 'vote_down',
isLike: data?.vote_status === 'vote_up',
votesCount: data?.vote_count,
hideCollect: true,
collected: data?.collected,
collectCount: 0,
username: data?.user_info?.username,
}}
/>
{canAccept && (
<Button
variant={data.accepted === 2 ? 'success' : 'outline-success'}
className="ms-3"
onClick={acceptAnswer}>
<Icon name="check-circle-fill" className="me-2" />
<span>
{data.accepted === 2
? t('answers.btn_accepted')
: t('answers.btn_accept')}
</span>
</Button>
)}
</div>
<Comment
objectId={data.id}
mode="answer"
commentId={searchParams.get('commentId')}>
<Operate
qid={data.question_id}
aid={data.id}
memberActions={data?.member_actions}
type="answer"
isAccepted={data.accepted === 2}
title={questionTitle}
callback={callback}
/>
</Comment>
</div>
);
};
export default memo(Index);