* chore: configure WDYR for react profiling * perf: reduced post content re-rendering * perf: post content view and it child optimization * perf: add memoization in post editor * perf: add memoization in postCommnetsView * perf: improved endorsed comment view rendering * perf: improved re-rendering in reply component * fix: uncomment questionType commentsView * fix: removed console errors in postContent area * perf: reduced postType and postId dependancy * perf: improved re-rendering in discussionHome * perf: improved re-rendering of postsList and its child components * perf: improved re-rendering of legacyTopic and learner sidebar * fix: postFilterBar filter was not updating * fix: resolve duplicate comment posts issue * fix: memory leaking issue in comments view * fix: duplicate topic posts in inContext sidebar * perf: add lazy loading * chore: remove WDYR configuration * fix: alert banner padding * chore: update package-lock file * fix: bind tour API call with buttons
183 lines
6.1 KiB
JavaScript
183 lines
6.1 KiB
JavaScript
import React, { useCallback, useMemo, useState } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import * as timeago from 'timeago.js';
|
|
|
|
import { useIntl } from '@edx/frontend-platform/i18n';
|
|
import { Avatar, useToggle } from '@edx/paragon';
|
|
|
|
import HTMLLoader from '../../../../components/HTMLLoader';
|
|
import { AvatarOutlineAndLabelColors, ContentActions } from '../../../../data/constants';
|
|
import {
|
|
ActionsDropdown, AlertBanner, AuthorLabel, Confirmation,
|
|
} from '../../../common';
|
|
import timeLocale from '../../../common/time-locale';
|
|
import { ContentTypes } from '../../../data/constants';
|
|
import { useAlertBannerVisible } from '../../../data/hooks';
|
|
import { selectCommentOrResponseById } from '../../data/selectors';
|
|
import { editComment, removeComment } from '../../data/thunks';
|
|
import messages from '../../messages';
|
|
import CommentEditor from './CommentEditor';
|
|
|
|
const Reply = ({ responseId }) => {
|
|
timeago.register('time-locale', timeLocale);
|
|
const {
|
|
id, abuseFlagged, author, authorLabel, endorsed, lastEdit, closed, closedBy,
|
|
closeReason, createdAt, threadId, parentId, rawBody, renderedBody, editByLabel, closedByLabel,
|
|
} = useSelector(selectCommentOrResponseById(responseId));
|
|
const intl = useIntl();
|
|
const dispatch = useDispatch();
|
|
const [isEditing, setEditing] = useState(false);
|
|
const [isDeleting, showDeleteConfirmation, hideDeleteConfirmation] = useToggle(false);
|
|
const [isReporting, showReportConfirmation, hideReportConfirmation] = useToggle(false);
|
|
const colorClass = AvatarOutlineAndLabelColors[authorLabel];
|
|
const hasAnyAlert = useAlertBannerVisible({
|
|
author,
|
|
abuseFlagged,
|
|
lastEdit,
|
|
closed,
|
|
});
|
|
|
|
const handleDeleteConfirmation = useCallback(() => {
|
|
dispatch(removeComment(id));
|
|
hideDeleteConfirmation();
|
|
}, [id, hideDeleteConfirmation]);
|
|
|
|
const handleReportConfirmation = useCallback(() => {
|
|
dispatch(editComment(id, { flagged: !abuseFlagged }));
|
|
hideReportConfirmation();
|
|
}, [abuseFlagged, id, hideReportConfirmation]);
|
|
|
|
const handleEditContent = useCallback(() => {
|
|
setEditing(true);
|
|
}, []);
|
|
|
|
const handleReplyEndorse = useCallback(() => {
|
|
dispatch(editComment(id, { endorsed: !endorsed }, ContentActions.ENDORSE));
|
|
}, [endorsed, id]);
|
|
|
|
const handleAbusedFlag = useCallback(() => {
|
|
if (abuseFlagged) {
|
|
dispatch(editComment(id, { flagged: !abuseFlagged }));
|
|
} else {
|
|
showReportConfirmation();
|
|
}
|
|
}, [abuseFlagged, id, showReportConfirmation]);
|
|
|
|
const handleCloseEditor = useCallback(() => {
|
|
setEditing(false);
|
|
}, []);
|
|
|
|
const actionHandlers = useMemo(() => ({
|
|
[ContentActions.EDIT_CONTENT]: handleEditContent,
|
|
[ContentActions.ENDORSE]: handleReplyEndorse,
|
|
[ContentActions.DELETE]: showDeleteConfirmation,
|
|
[ContentActions.REPORT]: handleAbusedFlag,
|
|
}), [handleEditContent, handleReplyEndorse, showDeleteConfirmation, handleAbusedFlag]);
|
|
|
|
return (
|
|
<div className="d-flex flex-column mt-2.5 " data-testid={`reply-${id}`} role="listitem">
|
|
<Confirmation
|
|
isOpen={isDeleting}
|
|
title={intl.formatMessage(messages.deleteCommentTitle)}
|
|
description={intl.formatMessage(messages.deleteCommentDescription)}
|
|
onClose={hideDeleteConfirmation}
|
|
comfirmAction={handleDeleteConfirmation}
|
|
closeButtonVaraint="tertiary"
|
|
confirmButtonText={intl.formatMessage(messages.deleteConfirmationDelete)}
|
|
/>
|
|
{!abuseFlagged && (
|
|
<Confirmation
|
|
isOpen={isReporting}
|
|
title={intl.formatMessage(messages.reportCommentTitle)}
|
|
description={intl.formatMessage(messages.reportCommentDescription)}
|
|
onClose={hideReportConfirmation}
|
|
comfirmAction={handleReportConfirmation}
|
|
confirmButtonVariant="danger"
|
|
/>
|
|
)}
|
|
{hasAnyAlert && (
|
|
<div className="d-flex">
|
|
<div className="d-flex invisible">
|
|
<Avatar />
|
|
</div>
|
|
<div className="w-100">
|
|
<AlertBanner
|
|
author={author}
|
|
abuseFlagged={abuseFlagged}
|
|
closed={closed}
|
|
closedBy={closedBy}
|
|
closeReason={closeReason}
|
|
lastEdit={lastEdit}
|
|
editByLabel={editByLabel}
|
|
closedByLabel={closedByLabel}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="d-flex">
|
|
<div className="d-flex mr-3 mt-2.5">
|
|
<Avatar
|
|
className={`ml-0.5 mt-0.5 border-0 ${colorClass ? `outline-${colorClass}` : 'outline-anonymous'}`}
|
|
alt={author}
|
|
style={{
|
|
width: '32px',
|
|
height: '32px',
|
|
}}
|
|
/>
|
|
</div>
|
|
<div
|
|
className="bg-light-300 pl-4 pt-2.5 pr-2.5 pb-10px flex-fill"
|
|
style={{ borderRadius: '0rem 0.375rem 0.375rem' }}
|
|
>
|
|
<div className="d-flex flex-row justify-content-between" style={{ height: '24px' }}>
|
|
<AuthorLabel
|
|
author={author}
|
|
authorLabel={authorLabel}
|
|
labelColor={colorClass && `text-${colorClass}`}
|
|
linkToProfile
|
|
postCreatedAt={createdAt}
|
|
postOrComment
|
|
/>
|
|
<div className="ml-auto d-flex">
|
|
<ActionsDropdown
|
|
actionHandlers={actionHandlers}
|
|
contentType={ContentTypes.COMMENT}
|
|
iconSize="inline"
|
|
id={id}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{isEditing ? (
|
|
<CommentEditor
|
|
comment={{
|
|
id,
|
|
threadId,
|
|
parentId,
|
|
rawBody,
|
|
author,
|
|
lastEdit,
|
|
}}
|
|
onCloseEditor={handleCloseEditor}
|
|
/>
|
|
) : (
|
|
<HTMLLoader
|
|
componentId="reply"
|
|
htmlNode={renderedBody}
|
|
cssClassName="html-loader text-break font-style text-primary-500"
|
|
testId={id}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
Reply.propTypes = {
|
|
responseId: PropTypes.string.isRequired,
|
|
};
|
|
|
|
export default React.memo(Reply);
|