Skip to content

Commit

Permalink
Merge branch 'pinterest:master' into external/group-permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
jij1949 authored Jul 19, 2023
2 parents 533448b + 78c84d5 commit e63a465
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 73 deletions.
4 changes: 4 additions & 0 deletions querybook/webapp/const/keyMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ const DEFAULT_KEY_MAP = {
key: 'Enter',
name: 'Confirm Modal',
},
submitComment: {
key: 'Cmd-Enter',
name: 'Submit Comment',
},
},
dataDoc: {
saveDataDoc: {
Expand Down
18 changes: 18 additions & 0 deletions querybook/webapp/redux/comment/action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ export function deleteComment(commentId: number): ThunkResult<Promise<void>> {
};
}

export function undoDeleteComment(
commentId: number
): ThunkResult<Promise<ICommentRaw>> {
return async (dispatch) => {
const { data: newComment } = await CommentResource.undoDelete(
commentId
);

dispatch({
type: '@@comment/RECEIVE_COMMENTS',
payload: {
comments: [newComment],
},
});

return newComment;
};
}
export function updateComment(
commentId: number,
text: ContentState
Expand Down
4 changes: 4 additions & 0 deletions querybook/webapp/resource/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export const CommentResource = {
}),
softDelete: (commentId: number) =>
ds.delete<null>(`/comment/${commentId}/`),
undoDelete: (commentId: number) =>
ds.update<ICommentRaw>(`/comment/${commentId}/`, {
archived: false,
}),
};

export const CellCommentResource = {
Expand Down
13 changes: 9 additions & 4 deletions querybook/webapp/ui/Comment/AddReactionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,33 +49,38 @@ export const AddReactionButton: React.FunctionComponent<IProps> = ({
const addReactionButtonRef = React.useRef<HTMLAnchorElement>();

const [showEmojis, setShowEmojis] = React.useState(false);
const [isLoading, setIsLoading] = React.useState(false);

const handleEmojiClick = React.useCallback(
(emoji: string) => {
const existingReaction = reactionsByEmoji[emoji]?.find(
(reaction) => reaction.created_by === uid
);
setIsLoading(true);
if (existingReaction) {
dispatch(
deleteReactionByCommentId(commentId, existingReaction.id)
);
).finally(() => setIsLoading(false));
} else {
dispatch(addReactionByCommentId(commentId, emoji));
dispatch(addReactionByCommentId(commentId, emoji)).finally(() =>
setIsLoading(false)
);
}
},
[commentId, dispatch, uid, reactionsByEmoji]
[reactionsByEmoji, uid, dispatch, commentId]
);

return (
<div className="AddReactionButton">
<IconButton
icon="Plus"
icon={isLoading ? 'Loading' : 'Plus'}
invertCircle
size={18}
tooltip="React"
tooltipPos={tooltipPos}
ref={addReactionButtonRef}
onClick={() => setShowEmojis(true)}
disabled={isLoading}
/>
{showEmojis ? (
<Popover
Expand Down
146 changes: 93 additions & 53 deletions querybook/webapp/ui/Comment/Comment.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import clsx from 'clsx';
import * as DraftJS from 'draft-js';
import * as React from 'react';
import { useSelector } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';

import { UserAvatar } from 'components/UserBadge/UserAvatar';
import { UserName } from 'components/UserBadge/UserName';
import { IComment, IReaction } from 'const/comment';
import { fromNow } from 'lib/utils/datetime';
import { IStoreState } from 'redux/store/types';
import { undoDeleteComment } from 'redux/comment/action';
import { Dispatch, IStoreState } from 'redux/store/types';
import { IconButton } from 'ui/Button/IconButton';
import { RichTextEditor } from 'ui/RichTextEditor/RichTextEditor';
import { StyledText } from 'ui/StyledText/StyledText';
Expand All @@ -18,11 +19,12 @@ import { Reactions } from './Reactions';
interface IProps {
comment: IComment;
editComment: (text: DraftJS.ContentState) => void;
deleteComment: () => void;
deleteComment: () => Promise<void>;
isBeingEdited: boolean;
isBeingRepliedTo: boolean;
isChild: boolean;
onCreateChildComment: () => void;
onReplyingToClick: () => void;
}

const formatReactionsByEmoji = (
Expand All @@ -45,9 +47,16 @@ export const Comment: React.FunctionComponent<IProps> = ({
isBeingRepliedTo,
isChild,
onCreateChildComment,
onReplyingToClick,
}) => {
const dispatch: Dispatch = useDispatch();

const userInfo = useSelector((state: IStoreState) => state.user.myUserInfo);

const [isDeleting, setIsDeleting] = React.useState<boolean>(false);
const [recentlyArchived, setRecentlyArchived] =
React.useState<boolean>(false);

const {
id,
text,
Expand All @@ -68,6 +77,12 @@ export const Comment: React.FunctionComponent<IProps> = ({
[reactions]
);

const handleUndoDeleteComment = React.useCallback(() => {
dispatch(undoDeleteComment(comment.id)).finally(() =>
setRecentlyArchived(false)
);
}, [comment.id, dispatch]);

return (
<div
className={clsx({
Expand Down Expand Up @@ -107,60 +122,85 @@ export const Comment: React.FunctionComponent<IProps> = ({
</StyledText>
) : null}
{isBeingRepliedTo ? (
<StyledText
className="mr8"
color="accent"
weight="bold"
isItalic
cursor="default"
<span
onClick={onReplyingToClick}
aria-label="Cancel Reply To"
data-balloon-pos="up"
>
replying to
</StyledText>
<StyledText
className="mr8"
color="accent"
weight="bold"
isItalic
cursor="pointer"
>
replying to
</StyledText>
</span>
) : null}
{archived ? null : (
<div className="Comment-top-right-buttons flex-row">
{isAuthor && !isBeingEdited ? (
<div className="Comment-edit">
<IconButton
icon="Edit"
invertCircle
size={18}
tooltip="Edit Comment"
tooltipPos="left"
onClick={() => editComment(text)}
/>
<IconButton
className="ml8"
icon="Trash"
invertCircle
size={18}
tooltip="Delete Comment"
tooltipPos="left"
onClick={deleteComment}
/>
</div>
) : null}
{isChild ? null : (
<div className="ml8">
<IconButton
icon="MessageCircle"
invertCircle
size={18}
tooltip="Reply to comment"
tooltipPos="left"
onClick={onCreateChildComment}
<div className="Comment-top-right-buttons flex-row">
{archived || isDeleting ? null : (
<>
{isAuthor && !isBeingEdited ? (
<div className="Comment-edit">
<IconButton
icon="Edit"
invertCircle
size={18}
tooltip="Edit Comment"
tooltipPos="left"
onClick={() => editComment(text)}
/>
<IconButton
className="ml8"
icon="Trash"
invertCircle
size={18}
tooltip="Delete Comment"
tooltipPos="left"
onClick={() => {
setIsDeleting(true);
deleteComment().then(() => {
setRecentlyArchived(true);
setIsDeleting(false);
});
}}
/>
</div>
) : null}
{isChild ? null : (
<div className="ml8">
<IconButton
icon="MessageCircle"
invertCircle
size={18}
tooltip="Reply to comment"
tooltipPos="left"
onClick={onCreateChildComment}
/>
</div>
)}
<div className="mh8">
<AddReactionButton
reactionsByEmoji={reactionsByEmoji}
commentId={id}
uid={userInfo.uid}
/>
</div>
)}
<div className="mh8">
<AddReactionButton
reactionsByEmoji={reactionsByEmoji}
commentId={id}
uid={userInfo.uid}
/>
</div>
</div>
)}
</>
)}
{recentlyArchived ? (
<IconButton
className="ml8"
icon="RotateCcw"
invertCircle
size={18}
tooltip="Undo Delete Comment"
tooltipPos="left"
onClick={handleUndoDeleteComment}
/>
) : null}
</div>
</div>
</div>
<div className="Comment-text mt4">
Expand Down
5 changes: 4 additions & 1 deletion querybook/webapp/ui/Comment/Comments.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
color: var(--text-lightest-0);
}
}
.IconButton.disabled {
color: var(--icon-light);
}
}
.CommentThread {
margin-left: 32px;
Expand Down Expand Up @@ -129,7 +132,7 @@
.Reaction {
background-color: var(--bg-light);
border-radius: var(--border-radius-sm);
cursor: default;
cursor: pointer;
&.active {
background-color: var(--color-accent-lightest-0);
}
Expand Down
23 changes: 17 additions & 6 deletions querybook/webapp/ui/Comment/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
IComment,
} from 'const/comment';
import { useShallowSelector } from 'hooks/redux/useShallowSelector';
import { getShortcutSymbols, KeyMap } from 'lib/utils/keyboard';
import {
createChildComment,
createComment,
Expand All @@ -34,6 +35,10 @@ interface IProps {

const emptyCommentValue = DraftJs.ContentState.createFromText('');

const ON_SUBMIT_SHORTCUT = getShortcutSymbols(
KeyMap.overallUI.submitComment.key
);

export const Comments: React.FunctionComponent<IProps> = ({
entityType,
entityId,
Expand Down Expand Up @@ -130,9 +135,7 @@ export const Comments: React.FunctionComponent<IProps> = ({
}, []);

const handleArchiveComment = React.useCallback(
(commentId: number) => {
dispatch(deleteComment(commentId));
},
(commentId: number) => dispatch(deleteComment(commentId)),
[dispatch]
);

Expand All @@ -153,7 +156,11 @@ export const Comments: React.FunctionComponent<IProps> = ({
onCreateChildComment={() =>
handleEditComment(undefined, undefined, comment.id)
}
isBeingRepliedTo={editingCommentParentId === comment.id}
isBeingRepliedTo={
editingCommentParentId === comment.id &&
editingCommentId !== comment.id
}
onReplyingToClick={handleCommentClear}
/>
) : null;

Expand Down Expand Up @@ -215,7 +222,11 @@ export const Comments: React.FunctionComponent<IProps> = ({
return (
<>
<UserAvatar uid={userInfo?.uid} tiny />
<RichTextField name="text" />
<RichTextField
name="text"
autoFocus
onSubmit={submitForm}
/>
<IconButton
icon="XCircle"
onClick={() => {
Expand All @@ -237,7 +248,7 @@ export const Comments: React.FunctionComponent<IProps> = ({
noPadding
size={18}
className="mr4"
tooltip="Comment"
tooltip={`Comment (${ON_SUBMIT_SHORTCUT})`}
tooltipPos="left"
disabled={isTextEmpty}
/>
Expand Down
Loading

0 comments on commit e63a465

Please sign in to comment.