diff --git a/dev/test-studio/fieldActions/commentFieldAction.tsx b/dev/test-studio/fieldActions/commentFieldAction.tsx deleted file mode 100644 index 6bb6b356923d..000000000000 --- a/dev/test-studio/fieldActions/commentFieldAction.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import {CommentIcon} from '@sanity/icons' -import {useCallback} from 'react' -import {defineDocumentFieldAction} from 'sanity' -import {defineActionItem} from './define' - -export const commentAction = defineDocumentFieldAction({ - name: 'test/comment', - useAction({documentId, documentType, path}) { - const onAction = useCallback(() => { - // eslint-disable-next-line no-console - console.log('comment', {documentId, documentType, path}) - }, [documentId, documentType, path]) - - return defineActionItem({ - type: 'action', - icon: CommentIcon, - onAction, - title: 'Comment', - renderAsButton: true, - }) - }, -}) diff --git a/dev/test-studio/sanity.config.ts b/dev/test-studio/sanity.config.ts index 12c708ed827e..139d59379ef6 100644 --- a/dev/test-studio/sanity.config.ts +++ b/dev/test-studio/sanity.config.ts @@ -39,7 +39,6 @@ import {vercelTheme} from './themes/vercel' import {GoogleLogo, TailwindLogo, VercelLogo} from './components/workspaceLogos' import {copyAction} from './fieldActions/copyAction' import {assistFieldActionGroup} from './fieldActions/assistFieldActionGroup' -import {commentAction} from './fieldActions/commentFieldAction' import {customInspector} from './inspectors/custom' import {pasteAction} from './fieldActions/pasteAction' @@ -70,7 +69,7 @@ const sharedSettings = definePlugin({ }, unstable_fieldActions: (prev, ctx) => { if (['fieldActionsTest', 'stringsTest'].includes(ctx.documentType)) { - return [...prev, commentAction, assistFieldActionGroup, copyAction, pasteAction] + return [...prev, assistFieldActionGroup, copyAction, pasteAction] } return prev diff --git a/packages/sanity/package.json b/packages/sanity/package.json index 85c891c9ddd3..879119b19e7a 100644 --- a/packages/sanity/package.json +++ b/packages/sanity/package.json @@ -151,6 +151,7 @@ "@sanity/logos": "^2.0.2", "@sanity/mutator": "3.16.1", "@sanity/portable-text-editor": "3.16.1", + "@portabletext/react": "^3.0.0", "@sanity/schema": "3.16.1", "@sanity/types": "3.16.1", "@sanity/ui": "^1.7.2", diff --git a/packages/sanity/src/core/comments/__workshop__/CommentInputStory.tsx b/packages/sanity/src/core/comments/__workshop__/CommentInputStory.tsx new file mode 100644 index 000000000000..7fef52209cfc --- /dev/null +++ b/packages/sanity/src/core/comments/__workshop__/CommentInputStory.tsx @@ -0,0 +1,42 @@ +import React, {useState} from 'react' +import {Card, Container, Flex} from '@sanity/ui' +import {PortableTextBlock} from '@sanity/types' +import {useBoolean} from '@sanity/ui-workshop' +import {CommentInput} from '../components' +import {CommentMessageSerializer} from '../components/pte' +import {useCurrentUser} from '../../store' + +export default function CommentsInputStory() { + const [value, setValue] = useState(null) + const currentUser = useCurrentUser() + const expandOnFocus = useBoolean('Expand on focus', false, 'Props') + const expanded = useBoolean('Expanded', false, 'Props') + + if (!currentUser) return null + + return ( + + + + + + + + + + + + + + + + ) +} diff --git a/packages/sanity/src/core/comments/__workshop__/CommentsListStory.tsx b/packages/sanity/src/core/comments/__workshop__/CommentsListStory.tsx new file mode 100644 index 000000000000..edf81eee17a2 --- /dev/null +++ b/packages/sanity/src/core/comments/__workshop__/CommentsListStory.tsx @@ -0,0 +1,112 @@ +import React, {useCallback, useState} from 'react' +import {Container, Flex} from '@sanity/ui' +import {CommentsList} from '../components' +import {useCurrentUser} from '../../store' +import {CommentDocument, CommentMessage, CommentCreatePayload} from '../types' + +const BASE: CommentDocument = { + _id: '1', + _type: 'comment', + _createdAt: new Date().toISOString(), + _updatedAt: '2021-05-04T14:54:37Z', + authorId: 'p8U8TipFc', + status: 'open', + _rev: '1', + path: {field: JSON.stringify([])}, + workspace: 'test', + target: { + documentId: '1', + documentType: 'article', + path: { + field: JSON.stringify([]), + }, + }, + message: [ + { + _type: 'block', + _key: '36a3f0d3832d', + style: 'normal', + markDefs: [], + children: [ + { + _type: 'span', + _key: '89014dd684ce', + text: 'My first comment', + marks: [], + }, + ], + }, + ], +} + +const PROPS = [ + { + ...BASE, + }, +] + +export default function CommentsListStory() { + const [state, setState] = useState(PROPS) + + const currentUser = useCurrentUser() + + const handleReplySubmit = useCallback( + (payload: CommentCreatePayload) => { + const comment: CommentDocument = { + ...BASE, + ...payload, + _createdAt: new Date().toISOString(), + _id: `${state.length + 1}`, + authorId: currentUser?.id || 'pP5s3g90N', + parentCommentId: payload.parentCommentId, + } + + setState((prev) => [...prev, comment]) + }, + [currentUser?.id, state.length], + ) + + const handleEdit = useCallback( + (id: string, payload: CommentCreatePayload) => { + setState((prev) => + prev.map((item) => { + if (item._id === id) { + return { + ...item, + ...payload, + _updatedAt: new Date().toISOString(), + } + } + + return item + }), + ) + }, + [setState], + ) + + const handleDelete = useCallback( + (id: string) => { + setState((prev) => prev.filter((item) => item._id !== id)) + }, + [setState], + ) + + if (!currentUser) return null + + return ( + + + + + + ) +} diff --git a/packages/sanity/src/core/comments/__workshop__/MentionOptionsHookStory.tsx b/packages/sanity/src/core/comments/__workshop__/MentionOptionsHookStory.tsx new file mode 100644 index 000000000000..937cc2b1ad97 --- /dev/null +++ b/packages/sanity/src/core/comments/__workshop__/MentionOptionsHookStory.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import {Card, Code} from '@sanity/ui' +import {useMentionOptions} from '../hooks' + +export default function MentionOptionsHookStory() { + const {data, loading} = useMentionOptions({ + documentId: '1e1744ab-43d5-4fff-8a2a-28c58bf0434a', + documentType: 'author', + }) + + if (loading) return
Loading...
+ if (!data) return
No data
+ + return ( + + + {JSON.stringify(data, null, 2)} + + + ) +} diff --git a/packages/sanity/src/core/comments/__workshop__/MentionsMenuStory.tsx b/packages/sanity/src/core/comments/__workshop__/MentionsMenuStory.tsx new file mode 100644 index 000000000000..a7dbc3cf2c39 --- /dev/null +++ b/packages/sanity/src/core/comments/__workshop__/MentionsMenuStory.tsx @@ -0,0 +1,22 @@ +import {Container, Flex} from '@sanity/ui' +import React from 'react' +import {MentionsMenu} from '../components/mentions' +import {useMentionOptions} from '../hooks' + +export default function MentionsMenuStory() { + const {data, loading} = useMentionOptions({documentId: 'foo', documentType: 'bar'}) + + return ( + + + { + //... + }} + /> + + + ) +} diff --git a/packages/sanity/src/core/comments/__workshop__/index.ts b/packages/sanity/src/core/comments/__workshop__/index.ts new file mode 100644 index 000000000000..d3340ae229e4 --- /dev/null +++ b/packages/sanity/src/core/comments/__workshop__/index.ts @@ -0,0 +1,29 @@ +import {defineScope} from '@sanity/ui-workshop' +import {lazy} from 'react' + +export default defineScope({ + name: 'core/comments', + title: 'comments', + stories: [ + { + name: 'comments-input', + title: 'CommentsInput', + component: lazy(() => import('./CommentInputStory')), + }, + { + name: 'mention-options-hook', + title: 'useMentionOptions', + component: lazy(() => import('./MentionOptionsHookStory')), + }, + { + name: 'comments-list', + title: 'CommentsList', + component: lazy(() => import('./CommentsListStory')), + }, + { + name: 'mentions-menu', + title: 'MentionsMenu', + component: lazy(() => import('./MentionsMenuStory')), + }, + ], +}) diff --git a/packages/sanity/src/core/comments/components/constants.ts b/packages/sanity/src/core/comments/components/constants.ts new file mode 100644 index 000000000000..2377a8c1d062 --- /dev/null +++ b/packages/sanity/src/core/comments/components/constants.ts @@ -0,0 +1,4 @@ +import {FlexProps} from '@sanity/ui' + +export const FLEX_GAP: FlexProps['gap'] = 3 +export const AVATAR_SIZE = 25 diff --git a/packages/sanity/src/core/comments/components/icons/MentionIcon.tsx b/packages/sanity/src/core/comments/components/icons/MentionIcon.tsx new file mode 100644 index 000000000000..60c1e0a61735 --- /dev/null +++ b/packages/sanity/src/core/comments/components/icons/MentionIcon.tsx @@ -0,0 +1,27 @@ +import React, {forwardRef} from 'react' + +export const MentionIcon = forwardRef(function Icon( + props: React.SVGProps, + ref: React.Ref, +) { + return ( + + + + + ) +}) diff --git a/packages/sanity/src/core/comments/components/icons/SendIcon.tsx b/packages/sanity/src/core/comments/components/icons/SendIcon.tsx new file mode 100644 index 000000000000..ad0535013e56 --- /dev/null +++ b/packages/sanity/src/core/comments/components/icons/SendIcon.tsx @@ -0,0 +1,27 @@ +import React, {forwardRef} from 'react' + +export const SendIcon = forwardRef(function Icon( + props: React.SVGProps, + ref: React.Ref, +) { + return ( + + + + ) +}) diff --git a/packages/sanity/src/core/comments/components/icons/index.ts b/packages/sanity/src/core/comments/components/icons/index.ts new file mode 100644 index 000000000000..cbee6f9bee0a --- /dev/null +++ b/packages/sanity/src/core/comments/components/icons/index.ts @@ -0,0 +1,2 @@ +export * from './SendIcon' +export * from './MentionIcon' diff --git a/packages/sanity/src/core/comments/components/index.ts b/packages/sanity/src/core/comments/components/index.ts new file mode 100644 index 000000000000..fc6dfb55152d --- /dev/null +++ b/packages/sanity/src/core/comments/components/index.ts @@ -0,0 +1,2 @@ +export * from './pte' +export * from './list' diff --git a/packages/sanity/src/core/comments/components/list/CommentDeleteDialog.tsx b/packages/sanity/src/core/comments/components/list/CommentDeleteDialog.tsx new file mode 100644 index 000000000000..552adcad0050 --- /dev/null +++ b/packages/sanity/src/core/comments/components/list/CommentDeleteDialog.tsx @@ -0,0 +1,73 @@ +import {Dialog, Grid, Button, Stack, Text} from '@sanity/ui' +import React, {useCallback, useMemo, useEffect} from 'react' +import {useComments} from '../../hooks' + +interface CommentDeleteDialogProps { + isParent: boolean + onClose: () => void + commentId: string +} + +const DIALOG_COPY: Record< + 'thread' | 'comment', + {title: string; body: string; confirmButtonText: string} +> = { + thread: { + title: 'Delete this comment thread?', + body: 'All comments in this thread will be deleted, and once deleted cannot be recovered.', + confirmButtonText: 'Delete thread', + }, + comment: { + title: 'Delete this comment?', + body: 'Once deleted, a comment cannot be recovered.', + confirmButtonText: 'Delete comment', + }, +} + +export function CommentDeleteDialog(props: CommentDeleteDialogProps) { + const {isParent, onClose, commentId} = props + const {title, body, confirmButtonText} = DIALOG_COPY[isParent ? 'thread' : 'comment'] + const {deleteOperation, comments} = useComments() + + const handleDelete = useCallback(() => { + deleteOperation.commit(commentId) + }, [commentId, deleteOperation]) + + const commentRemoved = useMemo( + () => comments.data.find((c) => c._id === commentId) === undefined, + [commentId, comments.data], + ) + + // Todo improve this? + useEffect(() => { + if (commentRemoved && !deleteOperation.loading && !deleteOperation.error) { + onClose() + } + }, [commentRemoved, deleteOperation.error, deleteOperation.loading, onClose]) + + return ( + + + ) +} diff --git a/packages/sanity/src/core/comments/components/list/CommentsList.tsx b/packages/sanity/src/core/comments/components/list/CommentsList.tsx new file mode 100644 index 000000000000..17ea7b0eacb6 --- /dev/null +++ b/packages/sanity/src/core/comments/components/list/CommentsList.tsx @@ -0,0 +1,118 @@ +import React, {useCallback, useMemo, useState} from 'react' +import {Code, Stack} from '@sanity/ui' +import {CurrentUser} from '@sanity/types' +import {CommentCreatePayload, CommentDocument, CommentEditPayload} from '../../types' +import {CommentsListItem} from './CommentsListItem' +import {CommentDeleteDialog} from './CommentDeleteDialog' + +interface GroupedComments { + [field: string]: CommentDocument[] +} + +function groupComments(comments: CommentDocument[]) { + return comments.reduce((acc, comment) => { + const field = comment.target?.path?.field + + if (!acc[field]) { + acc[field] = [] + } + + acc[field].push(comment) + + return acc + }, {} as GroupedComments) +} + +/** + * @beta + * @hidden + */ +export interface CommentsListProps { + comments: CommentDocument[] + currentUser: CurrentUser + documentId: string + documentType: string + onDelete: (id: string) => void + onEdit: (id: string, payload: CommentEditPayload) => void + onReply: (payload: CommentCreatePayload) => void +} + +/** + * @beta + * @hidden + */ +export function CommentsList(props: CommentsListProps) { + const {documentId, documentType, currentUser, comments, onReply, onDelete, onEdit} = props + const groupedComments = useMemo(() => groupComments(comments), [comments]) + + const [showDeleteDialog, setShowDeleteDialog] = useState(false) + const [commentIdToDelete, setCommentIdToDelete] = useState<{ + commentId: string + isParent: boolean + } | null>(null) + + const onDeleteStart = useCallback( + (id: string) => { + setShowDeleteDialog(true) + setCommentIdToDelete({ + commentId: id, + isParent: + comments.find((c) => c._id === id)?.parentCommentId === null || + comments.filter((c) => c.parentCommentId === id).length > 0, + }) + }, + [comments], + ) + + const onDeleteDialogClose = useCallback(() => { + setShowDeleteDialog(false) + setCommentIdToDelete(null) + }, []) + + return ( + <> + + {Object.entries(groupedComments).map(([key, value]) => { + const currentComments = value + const parentComments = currentComments.filter((c) => !c.parentCommentId) + + return ( + + + + {key} + + + + {parentComments.map((comment) => { + const replies = currentComments.filter((c) => c.parentCommentId === comment._id) + + return ( + + ) + })} + + ) + })} + + + {showDeleteDialog && commentIdToDelete && ( + + )} + + ) +} diff --git a/packages/sanity/src/core/comments/components/list/CommentsListItem.tsx b/packages/sanity/src/core/comments/components/list/CommentsListItem.tsx new file mode 100644 index 000000000000..b3c466422e1a --- /dev/null +++ b/packages/sanity/src/core/comments/components/list/CommentsListItem.tsx @@ -0,0 +1,98 @@ +import React, {useCallback, useState} from 'react' +import {Card, Stack} from '@sanity/ui' +import styled from 'styled-components' +import {CurrentUser} from '@sanity/types' +import {CommentInput} from '../pte/comment-input' +import { + CommentCreatePayload, + CommentDocument, + CommentEditPayload, + CommentMessage, +} from '../../types' +import {createNewComment} from '../../helpers' +import {CommentsListItemLayout} from './CommentsListItemLayout' + +const EMPTY_ARRAY: [] = [] + +const RootCard = styled(Card)`` + +interface CommentsListItemProps { + currentUser: CurrentUser + documentId: string + documentType: string + onDelete: (id: string) => void + onReply: (payload: CommentCreatePayload) => void + onEdit: (id: string, payload: CommentEditPayload) => void + parentComment: CommentDocument + replies: CommentDocument[] | undefined +} + +export function CommentsListItem(props: CommentsListItemProps) { + const {documentId, documentType, currentUser, parentComment, replies, onReply, onDelete, onEdit} = + props + const [value, setValue] = useState(EMPTY_ARRAY) + const hasReplies = replies && replies?.length > 0 + + const handleSubmit = useCallback(() => { + const nextComment = createNewComment({ + message: value, + parentCommentId: parentComment._id, + status: parentComment?.status || 'open', + target: parentComment.target, + }) + + if (nextComment) { + onReply?.(nextComment) + } + + setValue(EMPTY_ARRAY) + }, [onReply, parentComment._id, parentComment?.status, parentComment.target, value]) + + return ( + + + + + + {hasReplies && ( + <> + {replies.map((reply) => ( + + ))} + + )} + + + + + + ) +} diff --git a/packages/sanity/src/core/comments/components/list/CommentsListItemLayout.tsx b/packages/sanity/src/core/comments/components/list/CommentsListItemLayout.tsx new file mode 100644 index 000000000000..4c79dc4f7a97 --- /dev/null +++ b/packages/sanity/src/core/comments/components/list/CommentsListItemLayout.tsx @@ -0,0 +1,196 @@ +import {EditIcon, TrashIcon} from '@sanity/icons' +import { + Avatar, + TextSkeleton, + Flex, + Button, + Stack, + Text, + Card, + useGlobalKeyDown, + Layer, + useClickOutside, +} from '@sanity/ui' +import React, {useCallback, useState} from 'react' +import {CurrentUser} from '@sanity/types' +import styled, {css} from 'styled-components' +import {format} from 'date-fns' +import {UserAvatar} from '../../../components' +import {TimeAgoOpts, useTimeAgo} from '../../../hooks' +import {useUser} from '../../../store' +import {CommentMessageSerializer} from '../pte' +import {CommentInput} from '../pte/comment-input' +import {CommentDocument, CommentEditPayload, CommentMessage} from '../../types' +import {AVATAR_SIZE, FLEX_GAP} from '../constants' + +const FloatingLayer = styled(Layer)` + position: absolute; + top: 0; + right: 0; +` + +const FloatingCard = styled(Card)(({theme}) => { + const {space} = theme.sanity + + return css` + gap: ${space[1]}px; + padding: ${space[1] / 2}px; + ` +}) + +const RootStack = styled(Stack)` + position: relative; + + ${FloatingLayer}:not(:focus-within) { + opacity: 0; + } + + @media (hover: hover) { + &:hover { + ${FloatingLayer} { + opacity: 1; + } + } + } +` + +interface CommentsListItemLayoutProps { + canDelete?: boolean + canEdit?: boolean + comment: CommentDocument + onDelete: (id: string) => void + onEdit: (id: string, message: CommentEditPayload) => void + currentUser: CurrentUser + documentId: string + documentType: string +} + +const TIME_AGO_OPTS: TimeAgoOpts = {agoSuffix: true} + +export function CommentsListItemLayout(props: CommentsListItemLayoutProps) { + const {canDelete, canEdit, comment, onDelete, currentUser, documentId, documentType, onEdit} = + props + const {_createdAt, authorId, message, _id} = comment + const [user] = useUser(authorId) + + const [value, setValue] = useState(message) + const [isEditing, setIsEditing] = useState(false) + const [rootElement, setRootElement] = useState(null) + const [mentionMenuOpen, setMentionMenuOpen] = useState(false) + + const date = _createdAt ? new Date(_createdAt) : new Date() + const timeAgo = useTimeAgo(date, TIME_AGO_OPTS) + + const handleDeleteClick = useCallback(() => { + onDelete(_id) + }, [_id, onDelete]) + + const handleEditSubmit = useCallback(() => { + onEdit(_id, { + message: value, + }) + setIsEditing(false) + }, [_id, onEdit, value]) + + const toggleEdit = useCallback(() => { + setIsEditing((v) => !v) + }, []) + + useGlobalKeyDown((event) => { + if (event.key === 'Escape') { + setIsEditing(false) + } + }) + + useClickOutside(() => { + if (!mentionMenuOpen) { + setIsEditing(false) + setValue(message) + } + }, [rootElement]) + + const avatar = user ? : + + const name = user?.displayName ? ( + + {user.displayName} + + ) : ( + // fix + ) + + return ( + + + {avatar} + + + {name} + + + {timeAgo} + + + + + {isEditing && ( + +
+ + + + + + )} + + {!isEditing && ( + <> + + +
+ + ) + } + + return ( + + + {count} comment{count > 1 ? 's' : ''} + + + } + > + + + ) +} diff --git a/packages/sanity/src/desk/comments/field/index.ts b/packages/sanity/src/desk/comments/field/index.ts new file mode 100644 index 000000000000..b20130880009 --- /dev/null +++ b/packages/sanity/src/desk/comments/field/index.ts @@ -0,0 +1 @@ +export * from './CommentField' diff --git a/packages/sanity/src/desk/comments/index.ts b/packages/sanity/src/desk/comments/index.ts new file mode 100644 index 000000000000..a0ed13843f9e --- /dev/null +++ b/packages/sanity/src/desk/comments/index.ts @@ -0,0 +1 @@ +export * from './plugin' diff --git a/packages/sanity/src/desk/comments/inspector/CommentsInspector.tsx b/packages/sanity/src/desk/comments/inspector/CommentsInspector.tsx new file mode 100644 index 000000000000..ae16301f2365 --- /dev/null +++ b/packages/sanity/src/desk/comments/inspector/CommentsInspector.tsx @@ -0,0 +1,95 @@ +import {Container, Flex, Spinner, Stack, Text} from '@sanity/ui' +import React, {useCallback} from 'react' +import {DocumentInspectorHeader} from '../../panes/document/documentInspector' +import { + DocumentInspectorProps, + useComments, + CommentsList, + useCurrentUser, + createNewComment, + CommentCreatePayload, + CommentEditPayload, +} from 'sanity' + +export function CommentsInspector(props: DocumentInspectorProps) { + const {onClose, documentId, documentType} = props + const {comments, createOperation, editOperation, deleteOperation} = useComments() + const currentUser = useCurrentUser() + + const handleReply = useCallback( + (payload: CommentCreatePayload) => { + const nextComment = createNewComment({ + documentId, + documentType, + message: payload.message, + parentCommentId: payload.parentCommentId, + status: payload.status, + target: payload.target, + }) + + createOperation.commit(nextComment) + }, + [createOperation, documentId, documentType], + ) + + const handleEdit = useCallback( + (id: string, payload: CommentEditPayload) => { + editOperation.commit(id, payload) + }, + [editOperation], + ) + + const handleDelete = useCallback( + (id: string) => { + deleteOperation.commit(id) + }, + [deleteOperation], + ) + + return ( + + + + {comments.loading && ( + + + + + Loading comments... + + + + )} + + {!comments.loading && comments.data.length === 0 && ( + + + + + No comments yet + + + + + )} + + {comments.data.length > 0 && !comments.loading && currentUser && ( + + + + )} + + ) +} diff --git a/packages/sanity/src/desk/comments/inspector/index.ts b/packages/sanity/src/desk/comments/inspector/index.ts new file mode 100644 index 000000000000..98e8b8582ebf --- /dev/null +++ b/packages/sanity/src/desk/comments/inspector/index.ts @@ -0,0 +1,29 @@ +import {CommentIcon} from '@sanity/icons' +import {COMMENTS_INSPECTOR_NAME} from '../../panes/document/constants' +import {CommentsInspector} from './CommentsInspector' +import { + DocumentInspectorMenuItem, + DocumentInspectorUseMenuItemProps, + defineDocumentInspector, +} from 'sanity' + +function useMenuItem(props: DocumentInspectorUseMenuItemProps): DocumentInspectorMenuItem { + // eslint-disable-next-line no-empty-pattern + const { + // documentId, + // schemaType + } = props + + return { + icon: CommentIcon, + showAsAction: true, + title: 'Comments', + // tone: hasComments ? 'primary' : undefined, + } +} + +export const commentsInspector = defineDocumentInspector({ + name: COMMENTS_INSPECTOR_NAME, + component: CommentsInspector, + useMenuItem, +}) diff --git a/packages/sanity/src/desk/comments/plugin.ts b/packages/sanity/src/desk/comments/plugin.ts new file mode 100644 index 000000000000..0723e6c89d06 --- /dev/null +++ b/packages/sanity/src/desk/comments/plugin.ts @@ -0,0 +1,15 @@ +import {commentsInspector} from './inspector' +import {CommentField} from './field' +import {definePlugin} from 'sanity' + +export const comments = definePlugin({ + name: 'sanity/desk/comments', + document: { + inspectors: [commentsInspector], + }, + form: { + components: { + field: CommentField, + }, + }, +}) diff --git a/packages/sanity/src/desk/deskTool.ts b/packages/sanity/src/desk/deskTool.ts index 8ae9a53e33b3..cdba99fe06d4 100644 --- a/packages/sanity/src/desk/deskTool.ts +++ b/packages/sanity/src/desk/deskTool.ts @@ -12,8 +12,9 @@ import {LiveEditBadge} from './documentBadges' import {getIntentState} from './getIntentState' import {router} from './router' import {DeskToolOptions} from './types' -import {validationInspector} from './panes/document/inspectors/validation' +import {comments} from './comments' import {changesInspector} from './panes/document/inspectors/changes' +import {validationInspector} from './panes/document/inspectors/validation' import {definePlugin} from 'sanity' const documentActions = [ @@ -94,6 +95,9 @@ export const deskTool = definePlugin((options) => ({ return Array.from(new Set([...prevInspectors, ...inspectors])) }, }, + + plugins: [comments()], + tools: [ { name: options?.name || 'desk', diff --git a/packages/sanity/src/desk/panes/document/DocumentPaneProvider.tsx b/packages/sanity/src/desk/panes/document/DocumentPaneProvider.tsx index afc3804d08bd..7c7c15c6b78c 100644 --- a/packages/sanity/src/desk/panes/document/DocumentPaneProvider.tsx +++ b/packages/sanity/src/desk/panes/document/DocumentPaneProvider.tsx @@ -21,16 +21,25 @@ import { } from './constants' import {DocumentInspectorMenuItemsResolver} from './DocumentInspectorMenuItemsResolver' import { + CommentsProvider, + DocumentFieldAction, + DocumentFieldActionNode, DocumentInspector, + DocumentInspectorMenuItem, DocumentPresence, - PatchEvent, - StateTree, - toMutationPatches, + EMPTY_ARRAY, + FieldActionsProvider, + FieldActionsResolver, + getDraftId, getExpandOperations, getPublishedId, + PatchEvent, setAtPath, + StateTree, + toMutationPatches, useConnectionState, useDocumentOperation, + useDocumentValuePermissions, useEditState, useFormState, useInitialValue, @@ -38,18 +47,10 @@ import { useSchema, useSource, useTemplates, + useTimelineSelector, + useTimelineStore, useUnique, useValidationStatus, - getDraftId, - useDocumentValuePermissions, - useTimelineStore, - useTimelineSelector, - DocumentFieldAction, - DocumentInspectorMenuItem, - FieldActionsResolver, - EMPTY_ARRAY, - DocumentFieldActionNode, - FieldActionsProvider, } from 'sanity' /** @@ -683,7 +684,9 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { )} - {children} + + {children} + ) diff --git a/packages/sanity/src/desk/panes/document/constants.ts b/packages/sanity/src/desk/panes/document/constants.ts index 87e4ee92f9b6..a7c77188a09b 100644 --- a/packages/sanity/src/desk/panes/document/constants.ts +++ b/packages/sanity/src/desk/panes/document/constants.ts @@ -16,3 +16,4 @@ export const DEFAULT_MENU_ITEM_GROUPS: PaneMenuItemGroup[] = [{id: 'inspectors'} // inspectors export const HISTORY_INSPECTOR_NAME = 'sanity/desk/history' export const VALIDATION_INSPECTOR_NAME = 'sanity/desk/validation' +export const COMMENTS_INSPECTOR_NAME = 'sanity/desk/comments'