Skip to content

Commit

Permalink
feat: sorting comments with fixing new comments (#3551)
Browse files Browse the repository at this point in the history
  • Loading branch information
sshanzel authored Oct 1, 2024
1 parent 3987298 commit 3c90aac
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 35 deletions.
5 changes: 5 additions & 0 deletions packages/shared/src/components/icons/Sort/Time/filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions packages/shared/src/components/icons/Sort/Time/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React, { ReactElement } from 'react';
import Icon, { IconProps } from '../../../Icon';
import OutlinedIcon from './outlined.svg';
import FilledIcon from './filled.svg';

export const TimeSortIcon = (props: IconProps): ReactElement => (
<Icon {...props} IconPrimary={OutlinedIcon} IconSecondary={FilledIcon} />
);
5 changes: 5 additions & 0 deletions packages/shared/src/components/icons/Sort/Time/outlined.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 33 additions & 22 deletions packages/shared/src/components/modals/post/CommentModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
} from '../../fields/MarkdownInput/CommentMarkdownInput';
import { useMutateComment } from '../../../hooks/post/useMutateComment';
import { useVisualViewport } from '../../../hooks/utils/useVisualViewport';
import { RequestKey, generateQueryKey } from '../../../lib/query';
import { Comment, PostCommentsData } from '../../../graphql/comments';
import { useNotificationToggle } from '../../../hooks/notifications';
import { NotificationPromptSource } from '../../../lib/log';
Expand All @@ -25,28 +24,9 @@ import { useAuthContext } from '../../../contexts/AuthContext';
import CommentContainer from '../../comments/CommentContainer';
import { WriteCommentContext } from '../../../contexts/WriteCommentContext';
import useCommentById from '../../../hooks/comments/useCommentById';
import { getAllCommentsQuery } from '../../../lib/query';

const getCommentFromCache = ({
client,
postId,
commentId,
}: {
client: QueryClient;
postId: string;
commentId?: string;
}): Comment | undefined => {
if (!commentId) {
return undefined;
}

const queryKey = generateQueryKey(RequestKey.PostComments, null, postId);

const data = client.getQueryData<PostCommentsData>(queryKey);

if (!data) {
return undefined;
}

const getComment = (data: PostCommentsData, commentId: string) => {
// eslint-disable-next-line no-restricted-syntax
for (const item of data?.postComments?.edges) {
if (item.node.id === commentId) {
Expand All @@ -65,6 +45,37 @@ const getCommentFromCache = ({
return undefined;
};

interface GetCommentFromCacheProps {
client: QueryClient;
postId: string;
commentId?: string;
}

const getCommentFromCache = ({
client,
postId,
commentId,
}: GetCommentFromCacheProps): Comment | undefined => {
if (!commentId) {
return undefined;
}

const keys = getAllCommentsQuery(postId);
// eslint-disable-next-line no-restricted-syntax
for (const key of keys) {
const comment = getComment(
client.getQueryData<PostCommentsData>(key),
commentId,
);

if (comment) {
return comment;
}
}

return undefined;
};

export interface CommentModalProps
extends LazyModalCommonProps,
CommentMarkdownInputProps {
Expand Down
9 changes: 6 additions & 3 deletions packages/shared/src/components/post/PostComments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
Comment,
POST_COMMENTS_QUERY,
PostCommentsData,
SortCommentsBy,
} from '../../graphql/comments';
import { Post } from '../../graphql/posts';
import MainComment, { MainCommentProps } from '../comments/MainComment';
Expand All @@ -19,15 +20,16 @@ import { useRequestProtocol } from '../../hooks/useRequestProtocol';
import { initialDataKey } from '../../lib/constants';
import { Origin } from '../../lib/log';
import { CommentClassName } from '../fields/MarkdownInput/CommentMarkdownInput';
import { generateQueryKey, RequestKey } from '../../lib/query';
import { useDeleteComment } from '../../hooks/comments/useDeleteComment';
import { lazyCommentThreshold } from '../utilities';
import { isNullOrUndefined } from '../../lib/func';
import { useCommentContentPreferenceMutationSubscription } from './useCommentContentPreferenceMutationSubscription';
import { generateCommentsQueryKey } from '../../lib/query';

interface PostCommentsProps {
post: Post;
origin: Origin;
sortBy?: SortCommentsBy;
permissionNotificationCommentId?: string;
joinNotificationCommentId?: string;
modalParentSelector?: () => HTMLElement;
Expand All @@ -40,6 +42,7 @@ interface PostCommentsProps {
export function PostComments({
post,
origin,
sortBy,
onShare,
onClickUpvote,
modalParentSelector,
Expand All @@ -52,14 +55,14 @@ export function PostComments({
const container = useRef<HTMLDivElement>();
const { tokenRefreshed } = useContext(AuthContext);
const { requestMethod } = useRequestProtocol();
const queryKey = generateQueryKey(RequestKey.PostComments, null, id);
const queryKey = generateCommentsQueryKey({ postId: id, sortBy });
const { data: comments, isLoading: isLoadingComments } =
useQuery<PostCommentsData>(
queryKey,
() =>
requestMethod(
POST_COMMENTS_QUERY,
{ postId: id, [initialDataKey]: comments, first: 500 },
{ postId: id, [initialDataKey]: comments, first: 500, sortBy },
{ requestKey: JSON.stringify(queryKey) },
),
{
Expand Down
32 changes: 31 additions & 1 deletion packages/shared/src/components/post/PostEngagements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { NewComment, NewCommentRef } from './NewComment';
import { PostActions } from './PostActions';
import { PostComments } from './PostComments';
import { PostUpvotesCommentsCount } from './PostUpvotesCommentsCount';
import { Comment } from '../../graphql/comments';
import { Comment, SortCommentsBy } from '../../graphql/comments';
import { Origin } from '../../lib/log';
import {
isSourcePublicSquad,
Expand All @@ -23,6 +23,9 @@ import { useActions } from '../../hooks';
import { ActionType } from '../../graphql/actions';
import { AdAsComment } from '../comments/AdAsComment';
import { PostContentReminder } from './common/PostContentReminder';
import { Typography, TypographyType } from '../typography/Typography';
import { Button, ButtonIconPosition, ButtonSize } from '../buttons/Button';
import { TimeSortIcon } from '../icons/Sort/Time';

const AuthorOnboarding = dynamic(
() => import(/* webpackChunkName: "authorOnboarding" */ './AuthorOnboarding'),
Expand All @@ -43,6 +46,7 @@ function PostEngagements({
}: PostEngagementsProps): ReactElement {
const { completeAction } = useActions();
const postQueryKey = ['post', post.id];
const [sortBy, setSortBy] = useState(SortCommentsBy.OldestFirst);
const { user, showLogin } = useAuthContext();
const commentRef = useRef<NewCommentRef>();
const [authorOnboarding, setAuthorOnboarding] = useState(false);
Expand Down Expand Up @@ -101,6 +105,31 @@ function PostEngagements({
/>
<PostContentReminder post={post} />
<PostContentShare post={post} />
<span className="mt-6 flex flex-row items-center">
<Typography type={TypographyType.Callout}>Sort:</Typography>
<Button
className="ml-1 !px-0"
iconPosition={ButtonIconPosition.Right}
size={ButtonSize.Small}
icon={
<TimeSortIcon
secondary
className={sortBy === SortCommentsBy.OldestFirst && 'rotate-180'}
/>
}
onClick={() =>
setSortBy(
sortBy === SortCommentsBy.NewestFirst
? SortCommentsBy.OldestFirst
: SortCommentsBy.NewestFirst,
)
}
>
{sortBy === SortCommentsBy.NewestFirst
? 'Newest first'
: 'Oldest first'}
</Button>
</span>
<NewComment
className={{ container: 'mt-6 hidden tablet:flex' }}
post={post}
Expand All @@ -110,6 +139,7 @@ function PostEngagements({
<AdAsComment postId={post.id} />
<PostComments
post={post}
sortBy={sortBy}
origin={logOrigin}
onShare={(comment) => openShareComment(comment, post)}
onClickUpvote={(id, count) => onShowUpvoted(id, count, 'comment')}
Expand Down
19 changes: 17 additions & 2 deletions packages/shared/src/graphql/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,24 @@ export const COMMENT_FEED_QUERY = gql`
${COMMENT_WITH_PARENT_FRAGMENT}
`;

export enum SortCommentsBy {
NewestFirst = 'newest',
OldestFirst = 'oldest',
}

export const POST_COMMENTS_QUERY = gql`
query PostComments($postId: ID!, $after: String, $first: Int) {
postComments(postId: $postId, after: $after, first: $first) {
query PostComments(
$postId: ID!
$after: String
$first: Int
$sortBy: SortCommentsBy
) {
postComments(
postId: $postId
after: $after
first: $first
sortBy: $sortBy
) {
edges {
node {
...CommentWithChildrenFragment
Expand Down
28 changes: 24 additions & 4 deletions packages/shared/src/hooks/post/useMutateComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import {
} from '../../graphql/comments';
import { LogEvent } from '../../lib/log';
import { postLogEvent } from '../../lib/feed';
import { generateQueryKey, RequestKey } from '../../lib/query';
import {
generateQueryKey,
getAllCommentsQuery,
RequestKey,
} from '../../lib/query';
import { useBackgroundRequest } from '../companion';
import { updatePostCache } from '../usePostById';
import { Edge } from '../../graphql/common';
Expand Down Expand Up @@ -81,8 +85,7 @@ export const useMutateComment = ({
return;
}

const comments = generateQueryKey(RequestKey.PostComments, null, postId);
client.setQueryData<PostCommentsData>(comments, (data) => {
const updateQueryData = (data: PostCommentsData) => {
if (!data) {
return data;
}
Expand All @@ -92,7 +95,7 @@ export const useMutateComment = ({
const edge = generateCommentEdge(comment);

if (!parentCommentId) {
copy.postComments.edges.push(edge);
copy.postComments.edges.unshift(edge);
return copy;
}

Expand Down Expand Up @@ -126,6 +129,23 @@ export const useMutateComment = ({
children: parent.node.children,
};
return copy;
};

const forInvalidation = [];

getAllCommentsQuery(postId).forEach((queryKey) => {
client.setQueryData<PostCommentsData>(queryKey, (data) => {
if (!data) {
forInvalidation.push(queryKey);
return null;
}

return updateQueryData(data);
});
});

forInvalidation.forEach(async (queryKey) => {
await client.invalidateQueries(queryKey);
});

if (!editCommentId) {
Expand Down
10 changes: 7 additions & 3 deletions packages/shared/src/hooks/usePostById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from '../graphql/posts';
import { PostCommentsData } from '../graphql/comments';
import {
generateQueryKey,
getAllCommentsQuery,
RequestKey,
updatePostContentPreference,
} from '../lib/query';
Expand Down Expand Up @@ -77,8 +77,8 @@ export const removePostComments = (
commentId: string,
parentId: string,
): void => {
const key = generateQueryKey(RequestKey.PostComments, null, post.id);
client.setQueryData<PostCommentsData>(key, (data) => {
const keys = getAllCommentsQuery(post.id);
const removeCachedComment = (data: PostCommentsData) => {
if (!data) {
return data;
}
Expand Down Expand Up @@ -106,6 +106,10 @@ export const removePostComments = (
);

return data;
};

keys.forEach((key) => {
client.setQueryData(key, removeCachedComment);
});
};

Expand Down
22 changes: 22 additions & 0 deletions packages/shared/src/lib/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { SharedFeedPage } from '../components/utilities';
import {
Comment as PostComment,
Author as UserAuthor,
SortCommentsBy,
} from '../graphql/comments';
import {
ContentPreferenceStatus,
Expand Down Expand Up @@ -426,3 +427,24 @@ export const updateCommentContentPreference = ({

return newData;
};

type QueryKeyReturnType = ReturnType<typeof generateQueryKey>;

interface GenerateCommentsQueryKeyProps {
postId: string;
sortBy: SortCommentsBy;
}

export const generateCommentsQueryKey = ({
postId,
sortBy,
}: GenerateCommentsQueryKeyProps): QueryKeyReturnType =>
generateQueryKey(RequestKey.PostComments, null, { postId, sortBy });

export const getAllCommentsQuery = (postId: string): QueryKeyReturnType[] => {
const sorting = Object.values(SortCommentsBy).map((sortBy) =>
generateCommentsQueryKey({ postId, sortBy }),
);

return sorting;
};

0 comments on commit 3c90aac

Please sign in to comment.