diff --git a/packages/sanity/src/desk/comments/src/context/comments/CommentsProvider.tsx b/packages/sanity/src/desk/comments/src/context/comments/CommentsProvider.tsx index 022e2b1f29e4..30105b5104b8 100644 --- a/packages/sanity/src/desk/comments/src/context/comments/CommentsProvider.tsx +++ b/packages/sanity/src/desk/comments/src/context/comments/CommentsProvider.tsx @@ -134,6 +134,64 @@ const CommentsProviderInner = memo(function CommentsProviderInner( const currentUser = useCurrentUser() const {name: workspaceName, dataset, projectId} = useWorkspace() + const threadItemsByStatus: ThreadItemsByStatus = useMemo(() => { + if (!schemaType || !currentUser) return EMPTY_COMMENTS_DATA + // Since we only make one query to get all comments using the order `_createdAt desc` – we + // can't know for sure that the comments added through the real time listener will be in the + // correct order. In order to avoid that comments are out of order, we make an additional + // sort here. The comments can be out of order if e.g a comment creation fails and is retried + // later. + const sorted = orderBy(data, ['_createdAt'], ['desc']) + + const items = buildCommentThreadItems({ + comments: sorted, + schemaType, + currentUser, + documentValue, + }) + + return { + open: items.filter((item) => item.parentComment.status === 'open'), + resolved: items.filter((item) => item.parentComment.status === 'resolved'), + } + }, [currentUser, data, documentValue, schemaType]) + + const getThreadLength = useCallback( + (threadId: string) => { + return threadItemsByStatus.open.filter((item) => item.threadId === threadId).length + }, + [threadItemsByStatus.open], + ) + + const getComment = useCallback((id: string) => data?.find((c) => c._id === id), [data]) + + const getCommentPath = useCallback( + (id: string) => { + const comment = getComment(id) + if (!comment) return null + + return comment.target.path.field + }, + [getComment], + ) + + const handleSetSelectedPath = useCallback((nextPath: SelectedPath) => { + // If the path is being set to null, immediately set the selected path to null. + if (nextPath === null) { + setSelectedPath(null) + return + } + + // Sometimes, a path is cleared (set to null) but at the same time a new path is set. + // In this case, we want to make sure that the selected path is set to the new path + // and not the cleared path. Therefore, we set the selected path in a timeout to make + // sure that the selected path is set after the cleared path. + // todo: can this be done in a better way? + setTimeout(() => { + setSelectedPath(nextPath) + }) + }, []) + const handleOnCreate = useCallback( async (payload: CommentPostPayload) => { // If the comment we try to create already exists in the local state and has @@ -233,6 +291,7 @@ const CommentsProviderInner = memo(function CommentsProviderInner( projectId, schemaType, workspace: workspaceName, + getThreadLength, // The following callbacks runs when the comment operations are executed. // They are used to update the local state of the comments immediately after // a comment operation has been executed. This is done to avoid waiting for @@ -253,6 +312,7 @@ const CommentsProviderInner = memo(function CommentsProviderInner( projectId, schemaType, workspaceName, + getThreadLength, handleOnCreate, handleOnCreateError, handleOnEdit, @@ -261,57 +321,6 @@ const CommentsProviderInner = memo(function CommentsProviderInner( ), ) - const threadItemsByStatus: ThreadItemsByStatus = useMemo(() => { - if (!schemaType || !currentUser) return EMPTY_COMMENTS_DATA - // Since we only make one query to get all comments using the order `_createdAt desc` – we - // can't know for sure that the comments added through the real time listener will be in the - // correct order. In order to avoid that comments are out of order, we make an additional - // sort here. The comments can be out of order if e.g a comment creation fails and is retried - // later. - const sorted = orderBy(data, ['_createdAt'], ['desc']) - - const items = buildCommentThreadItems({ - comments: sorted, - schemaType, - currentUser, - documentValue, - }) - - return { - open: items.filter((item) => item.parentComment.status === 'open'), - resolved: items.filter((item) => item.parentComment.status === 'resolved'), - } - }, [currentUser, data, documentValue, schemaType]) - - const getComment = useCallback((id: string) => data?.find((c) => c._id === id), [data]) - - const getCommentPath = useCallback( - (id: string) => { - const comment = getComment(id) - if (!comment) return null - - return comment.target.path.field - }, - [getComment], - ) - - const handleSetSelectedPath = useCallback((nextPath: SelectedPath) => { - // If the path is being set to null, immediately set the selected path to null. - if (nextPath === null) { - setSelectedPath(null) - return - } - - // Sometimes, a path is cleared (set to null) but at the same time a new path is set. - // In this case, we want to make sure that the selected path is set to the new path - // and not the cleared path. Therefore, we set the selected path in a timeout to make - // sure that the selected path is set after the cleared path. - // todo: can this be done in a better way? - setTimeout(() => { - setSelectedPath(nextPath) - }) - }, []) - const ctxValue = useMemo( () => ({ diff --git a/packages/sanity/src/desk/comments/src/hooks/useCommentOperations.ts b/packages/sanity/src/desk/comments/src/hooks/useCommentOperations.ts index 8b1380bc32d0..e916135c1701 100644 --- a/packages/sanity/src/desk/comments/src/hooks/useCommentOperations.ts +++ b/packages/sanity/src/desk/comments/src/hooks/useCommentOperations.ts @@ -3,6 +3,7 @@ import {uuid} from '@sanity/uuid' import {CurrentUser, SchemaType} from '@sanity/types' import {SanityClient} from '@sanity/client' import { + CommentContext, CommentCreatePayload, CommentEditPayload, CommentOperations, @@ -27,6 +28,8 @@ export interface CommentOperationsHookOptions { schemaType: SchemaType | undefined workspace: string + getThreadLength?: (threadId: string) => number + onCreate?: (comment: CommentPostPayload) => void onCreateError: (id: string, error: Error) => void onEdit?: (id: string, comment: CommentEditPayload) => void @@ -43,6 +46,7 @@ export function useCommentOperations( dataset, documentId, documentType, + getThreadLength, onCreate, onCreateError, onEdit, @@ -74,6 +78,23 @@ export function useCommentOperations( // create the comment. const commentId = comment?.id || uuid() + // Get the current thread length of the thread the comment is being added to. + // We add 1 to the length to account for the comment being added. + const currentThreadLength = (getThreadLength?.(comment.threadId) || 0) + 1 + + const { + documentTitle = '', + url = '', + workspaceTitle = '', + } = getNotificationValue({commentId}) || {} + + const notification: CommentContext['notification'] = { + currentThreadLength, + documentTitle, + url, + workspaceTitle, + } + const nextComment = { _id: commentId, _type: 'comment', @@ -88,7 +109,7 @@ export function useCommentOperations( payload: { workspace, }, - notification: getNotificationValue({commentId}), + notification, tool: activeTool?.name || '', }, target: { @@ -129,6 +150,7 @@ export function useCommentOperations( documentId, documentType, getNotificationValue, + getThreadLength, onCreate, onCreateError, projectId, diff --git a/packages/sanity/src/desk/comments/src/types.ts b/packages/sanity/src/desk/comments/src/types.ts index abad6b9ce72c..921f339b5794 100644 --- a/packages/sanity/src/desk/comments/src/types.ts +++ b/packages/sanity/src/desk/comments/src/types.ts @@ -80,6 +80,7 @@ export interface CommentContext { documentTitle: string url: string workspaceTitle: string + currentThreadLength?: number } }