Skip to content

Commit

Permalink
Ensured replies remain visible when parent comment is being edited (#…
Browse files Browse the repository at this point in the history
…21756)

REF https://linear.app/ghost/issue/PLG-274/

- Currently, replies are hidden when the parent comment is being edited. This PR ensures that replies remain visible when the parent comment is being edited.
- To achieve this, the `CommentComponent` now checks if the parent comment is being edited. If so, the comment content is swapped by the EditForm, while the comment's avatar, header, menu, and replies remain visible.
- The Form component now only renders the FormEditor. A FormWrapper component is used to wrap the avatar and comment header.
- This Form component is used in the EditForm without the FormWrapper, as it is already wrapped in the CommentComponent.
- The ReplyForm and the MainForm use the FormWrapper.
- The Avatar component now also accepts a `member` prop, which is used to display the avatar image. This is because it's no longer used inside the Form component.
  • Loading branch information
sanne-san authored Dec 2, 2024
1 parent 9f9aee5 commit 4e806f7
Show file tree
Hide file tree
Showing 8 changed files with 252 additions and 146 deletions.
23 changes: 11 additions & 12 deletions apps/comments-ui/src/components/content/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ReactComponent as AvatarIcon} from '../../images/icons/avatar.svg';
import {Comment, useAppContext} from '../../AppContext';
import {Comment, Member, useAppContext} from '../../AppContext';
import {getInitials, getMemberInitialsFromComment} from '../../utils/helpers';

function getDimensionClasses() {
Expand All @@ -19,13 +19,15 @@ export const BlankAvatar = () => {

type AvatarProps = {
comment?: Comment;
member?: Member;
};
export const Avatar: React.FC<AvatarProps> = ({comment}) => {
// #TODO greyscale the avatar image when it's hidden
const {member, avatarSaturation, t} = useAppContext();

export const Avatar: React.FC<AvatarProps> = ({comment, member: propMember}) => {
const {member: contextMember, avatarSaturation, t} = useAppContext();
const dimensionClasses = getDimensionClasses();

const memberName = member?.name ?? comment?.member?.name;
const activeMember = propMember || comment?.member || contextMember;
const memberName = activeMember?.name;

const getHashOfString = (str: string) => {
let hash = 0;
Expand All @@ -41,9 +43,7 @@ export const Avatar: React.FC<AvatarProps> = ({comment}) => {
};

const generateHSL = (): [number, number, number] => {
const commentMember = (comment ? comment.member : member);

if (!commentMember || !commentMember.name) {
if (!activeMember || !activeMember.name) {
return [0,0,10];
}

Expand All @@ -54,7 +54,7 @@ export const Avatar: React.FC<AvatarProps> = ({comment}) => {
const lRangeBottom = lRangeTop - 20;
const lRange = [lRangeBottom, lRangeTop];

const hash = getHashOfString(commentMember.name);
const hash = getHashOfString(activeMember.name);
const h = normalizeHash(hash, hRange[0], hRange[1]);
const l = normalizeHash(hash, lRange[0], lRange[1]);

Expand All @@ -66,8 +66,7 @@ export const Avatar: React.FC<AvatarProps> = ({comment}) => {
};

const memberInitials = (comment && getMemberInitialsFromComment(comment, t)) ||
(member && getInitials(member.name || '')) || '';
const commentMember = (comment ? comment.member : member);
(activeMember && getInitials(activeMember.name || '')) || '';

const bgColor = HSLtoString(generateHSL());
const avatarStyle = {
Expand All @@ -83,7 +82,7 @@ export const Avatar: React.FC<AvatarProps> = ({comment}) => {
(<div className={`flex items-center justify-center rounded-full bg-neutral-900 dark:bg-white/70 ${dimensionClasses}`} data-testid="avatar-background">
<AvatarIcon className="stroke-white dark:stroke-black/60" />
</div>)}
{commentMember && <img alt="Avatar" className={`absolute left-0 top-0 rounded-full ${dimensionClasses}`} data-testid="avatar-image" src={commentMember.avatar_image} />}
{activeMember?.avatar_image && <img alt="Avatar" className={`absolute left-0 top-0 rounded-full ${dimensionClasses}`} data-testid="avatar-image" src={activeMember.avatar_image} />}
</>
);

Expand Down
93 changes: 47 additions & 46 deletions apps/comments-ui/src/components/content/Comment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,36 @@ const AnimatedComment: React.FC<AnimatedCommentProps> = ({comment, parent}) => {
show={true}
appear
>
<EditableComment comment={comment} parent={parent} />
<CommentComponent comment={comment} parent={parent} />
</Transition>
);
};

type EditableCommentProps = AnimatedCommentProps;
const EditableComment: React.FC<EditableCommentProps> = ({comment, parent}) => {
const {openCommentForms} = useAppContext();
export const CommentComponent: React.FC<CommentProps> = ({comment, parent}) => {
const {dispatchAction, admin} = useAppContext();
const labs = useLabs();
const {showDeletedMessage, showHiddenMessage, showCommentContent} = useCommentVisibility(comment, admin, labs);

const form = openCommentForms.find(openForm => openForm.id === comment.id && openForm.type === 'edit');
const isInEditMode = !!form;
const openEditMode = useCallback(() => {
const newForm: OpenCommentForm = {
id: comment.id,
type: 'edit',
hasUnsavedChanges: false,
in_reply_to_id: comment.in_reply_to_id,
in_reply_to_snippet: comment.in_reply_to_snippet
};
dispatchAction('openCommentForm', newForm);
}, [comment.id, dispatchAction]);

if (isInEditMode) {
return (<EditForm comment={comment} openForm={form} parent={parent} />);
} else {
return (<CommentComponent comment={comment} parent={parent} />);
if (showDeletedMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
} else if (showCommentContent && !showHiddenMessage) {
return <PublishedComment comment={comment} openEditMode={openEditMode} parent={parent} />;
} else if (!labs.commentImprovements && comment.status !== 'published' || showHiddenMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
}

return null;
};

type CommentProps = AnimatedCommentProps;
Expand Down Expand Up @@ -74,33 +87,6 @@ const useCommentVisibility = (comment: Comment, admin: boolean, labs: {commentIm
};
};

export const CommentComponent: React.FC<CommentProps> = ({comment, parent}) => {
const {dispatchAction, admin} = useAppContext();
const labs = useLabs();
const {showDeletedMessage, showHiddenMessage, showCommentContent} = useCommentVisibility(comment, admin, labs);

const openEditMode = useCallback(() => {
const newForm: OpenCommentForm = {
id: comment.id,
type: 'edit',
hasUnsavedChanges: false,
in_reply_to_id: comment.in_reply_to_id,
in_reply_to_snippet: comment.in_reply_to_snippet
};
dispatchAction('openCommentForm', newForm);
}, [comment.id, dispatchAction]);

if (showDeletedMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
} else if (showCommentContent && !showHiddenMessage) {
return <PublishedComment comment={comment} openEditMode={openEditMode} parent={parent} />;
} else if (!labs.commentImprovements && comment.status !== 'published' || showHiddenMessage) {
return <UnpublishedComment comment={comment} openEditMode={openEditMode} />;
}

return null;
};

type PublishedCommentProps = CommentProps & {
openEditMode: () => void;
}
Expand All @@ -112,6 +98,10 @@ const PublishedComment: React.FC<PublishedCommentProps> = ({comment, parent, ope
const isHidden = labs.commentImprovements && admin && comment.status === 'hidden';
const hiddenClass = isHidden ? 'opacity-30' : '';

// Check if this comment is being edited
const editForm = openCommentForms.find(openForm => openForm.id === comment.id && openForm.type === 'edit');
const isInEditMode = !!editForm;

// currently a reply-to-reply form is displayed inside the top-level PublishedComment component
// so we need to check for a match of either the comment id or the parent id
const openForm = openCommentForms.find(f => (f.id === comment.id || f.parent_id === comment.id) && f.type === 'reply');
Expand Down Expand Up @@ -148,15 +138,26 @@ const PublishedComment: React.FC<PublishedCommentProps> = ({comment, parent, ope

return (
<CommentLayout avatar={avatar} className={hiddenClass} hasReplies={hasReplies}>
<CommentHeader className={hiddenClass} comment={comment} />
<CommentBody className={hiddenClass} html={comment.html} />
<CommentMenu
comment={comment}
highlightReplyButton={highlightReplyButton}
openEditMode={openEditMode}
openReplyForm={openReplyForm}
parent={parent}
/>
<div>
{isInEditMode ? (
<>
<CommentHeader className={hiddenClass} comment={comment} />
<EditForm comment={comment} openForm={editForm} parent={parent} />
</>
) : (
<>
<CommentHeader className={hiddenClass} comment={comment} />
<CommentBody className={hiddenClass} html={comment.html} />
<CommentMenu
comment={comment}
highlightReplyButton={highlightReplyButton}
openEditMode={openEditMode}
openReplyForm={openReplyForm}
parent={parent}
/>
</>
)}
</div>
<RepliesContainer comment={comment} />
{displayReplyForm && <ReplyFormBox comment={comment} openForm={openForm} />}
</CommentLayout>
Expand Down
28 changes: 13 additions & 15 deletions apps/comments-ui/src/components/content/forms/EditForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Form from './Form';
import {Comment, OpenCommentForm, useAppContext} from '../../../AppContext';
import {Form} from './Form';
import {getEditorConfig} from '../../../utils/editor';
import {isMobile} from '../../../utils/helpers';
import {useCallback, useEffect} from 'react';
Expand Down Expand Up @@ -60,20 +60,18 @@ const EditForm: React.FC<Props> = ({comment, openForm, parent}) => {
}, [dispatchAction, openForm]);

return (
<div className='px-2 pb-2 pt-3'>
<div className='mt-[-16px] pr-3'>
<Form
close={close}
comment={comment}
editor={editor}
isOpen={true}
openForm={openForm}
reduced={isMobile()}
submit={submit}
submitSize={'small'}
submitText={t('Save')}
/>
</div>
<div className="relative w-full">
<Form
close={close}
comment={comment}
editor={editor}
isOpen={true}
openForm={openForm}
reduced={isMobile()}
submit={submit}
submitSize={'small'}
submitText={t('Save')}
/>
</div>
);
};
Expand Down
Loading

0 comments on commit 4e806f7

Please sign in to comment.