Skip to content

Commit

Permalink
feat(comments): add optional intent link to comments (#5576)
Browse files Browse the repository at this point in the history
* feat(comments): add optional intent link to comments

* refactor(comments): remove unused prop

* feat(comments): only show intent link if intent preview param differs
  • Loading branch information
rdunk authored Feb 13, 2024
1 parent 51ed18d commit 27bf5f9
Show file tree
Hide file tree
Showing 17 changed files with 264 additions and 26 deletions.
2 changes: 2 additions & 0 deletions packages/sanity/src/structure/comments/i18n/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ const commentsLocaleStrings = defineLocalesResources('comments', {
'list-item.edit-comment-upsell': 'Upgrade to edit comment',
/** Aria label for the button that takes you to the field, which wraps a thread/comment */
'list-item.go-to-field-button.aria-label': 'Go to field',
/** The text showing the comment context */
'list-item.layout-context': 'on <IntentLink>{{title}}</IntentLink>',
/** The marker to indicate that a comment has been edited in brackets */
'list-item.layout-edited': 'edited',
/** The error text when sending a comment has failed */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,91 @@ const BASE: CommentDocument = {
],
}

const INTENT: CommentDocument = {
...BASE,
_id: '2',
threadId: '2',
message: [
{
_type: 'block',
_key: '36a3f0d3832d',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '89014dd684cc',
text: 'A comment with context',
marks: [],
},
],
},
],
context: {
payload: {
workspace: 'default',
},
intent: {
title: 'Page One',
name: 'edit',
params: {
id: 'd73bb3d8-b1b7-4ca3-8f55-969bba902cd3',
path: 'string',
type: 'commentsDebug',
inspect: 'sanity/structure/comments',
mode: 'structure',
preview: '/page-one',
},
},
tool: 'structure',
},
}

const INTENT_RESPONSE_SAME = {
...INTENT,
_id: '3',
parentCommentId: '2',
message: [
{
_type: 'block',
_key: '36a3f0d3832d',
style: 'normal',
markDefs: [],
children: [
{
_type: 'span',
_key: '89014dd684cc',
text: 'A response with context',
marks: [],
},
],
},
],
}

const INTENT_RESPONSE_DIFF = {
...INTENT_RESPONSE_SAME,
_id: '4',
context: {
payload: {
workspace: 'default',
},
intent: {
title: 'Page Two',
name: 'edit',
params: {
id: 'd73bb3d8-b1b7-4ca3-8f55-969bba902cd3',
path: 'string',
type: 'commentsDebug',
inspect: 'sanity/structure/comments',
mode: 'structure',
preview: '/page-two',
},
},
tool: 'structure',
},
}

const MENTION_HOOK_OPTIONS = {
documentValue: {
_type: 'author',
Expand All @@ -92,7 +177,12 @@ const MENTION_HOOK_OPTIONS = {
const STATUS_OPTIONS: Record<CommentStatus, CommentStatus> = {open: 'open', resolved: 'resolved'}

export default function CommentsListStory() {
const [state, setState] = useState<CommentDocument[]>([BASE])
const [state, setState] = useState<CommentDocument[]>([
BASE,
INTENT,
INTENT_RESPONSE_DIFF,
INTENT_RESPONSE_SAME,
])

const error = useBoolean('Error', false, 'Props') || null
const loading = useBoolean('Loading', false, 'Props') || false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styled, {css} from 'styled-components'
import {Button} from '../../../../../ui-components'
import {commentsLocaleNamespace} from '../../../i18n'
import {type CommentsSelectedPath} from '../../context'
import {hasCommentMessageValue} from '../../helpers'
import {commentIntentIfDiffers, hasCommentMessageValue} from '../../helpers'
import {
type CommentCreatePayload,
type CommentDocument,
Expand Down Expand Up @@ -257,6 +257,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onInputKeyDown={handleInputKeyDown}
onReactionSelect={onReactionSelect}
readOnly={readOnly}
intent={commentIntentIfDiffers(parentComment, reply)}
/>
</Stack>
)),
Expand All @@ -269,6 +270,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onDelete,
onEdit,
onReactionSelect,
parentComment,
readOnly,
splicedReplies,
mode,
Expand Down Expand Up @@ -316,6 +318,7 @@ export const CommentsListItem = React.memo(function CommentsListItem(props: Comm
onReactionSelect={onReactionSelect}
onStatusChange={onStatusChange}
readOnly={readOnly}
intent={parentComment.context?.intent}
/>
</Stack>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import {Box, Card, Flex, Stack, Text, TextSkeleton, useClickOutside} from '@sani
import {useCallback, useMemo, useRef, useState} from 'react'
import {
type RelativeTimeOptions,
Translate,
useDateTimeFormat,
useDidUpdate,
useRelativeTime,
useTranslation,
useUser,
} from 'sanity'
import {IntentLink} from 'sanity/router'
import styled, {css} from 'styled-components'

import {commentsLocaleNamespace} from '../../../i18n'
import {hasCommentMessageValue, useCommentHasChanged} from '../../helpers'
import {
type CommentContext,
type CommentDocument,
type CommentEditPayload,
type CommentMessage,
Expand Down Expand Up @@ -49,6 +52,16 @@ const TimeText = styled(Text)(({theme}) => {
`
})

const IntentText = styled(Text)(({theme}) => {
const isDark = theme.sanity.color.dark
const fg = hues.gray[isDark ? 200 : 800].hex

return css`
--card-fg-color: ${fg};
color: var(--card-fg-color);
`
})

const InnerStack = styled(Stack)`
transition: opacity 200ms ease;
Expand Down Expand Up @@ -125,6 +138,7 @@ interface CommentsListItemLayoutProps {
onReactionSelect?: (id: string, reaction: CommentReactionOption) => void
onStatusChange?: (id: string, status: CommentStatus) => void
readOnly?: boolean
intent?: CommentContext['intent']
}

const RELATIVE_TIME_OPTIONS: RelativeTimeOptions = {useTemporalPhrase: true}
Expand All @@ -136,6 +150,7 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
comment,
currentUser,
hasError,
intent,
isParent,
isRetrying,
mentionOptions,
Expand Down Expand Up @@ -298,28 +313,55 @@ export function CommentsListItemLayout(props: CommentsListItemLayoutProps) {
<Flex align="center" gap={FLEX_GAP} flex={1}>
<CommentsAvatar user={user} />

<Flex align="center" paddingBottom={1} sizing="border" flex={1}>
<Flex align="flex-end" gap={2}>
<Box flex={1}>{name}</Box>

{!displayError && (
<Flex align="center" gap={1}>
<TimeText muted size={0}>
<time dateTime={createdDate.toISOString()} title={formattedCreatedAt}>
{createdTimeAgo}
</time>
</TimeText>

{formattedLastEditAt && editedDate && (
<TimeText muted size={0} title={formattedLastEditAt}>
<time dateTime={editedDate.toISOString()} title={formattedLastEditAt}>
({t('list-item.layout-edited')})
<Flex direction="column" gap={2} paddingY={intent ? 2 : 0}>
<Flex
align="center"
paddingBottom={comment.context?.intent ? 0 : 1}
sizing="border"
flex={1}
>
<Flex align="flex-end" gap={2}>
<Box flex={1}>{name}</Box>

{!displayError && (
<Flex align="center" gap={1}>
<TimeText muted size={0}>
<time dateTime={createdDate.toISOString()} title={formattedCreatedAt}>
{createdTimeAgo}
</time>
</TimeText>
)}
</Flex>
)}

{formattedLastEditAt && editedDate && (
<TimeText muted size={0} title={formattedLastEditAt}>
<time dateTime={editedDate.toISOString()} title={formattedLastEditAt}>
({t('list-item.layout-edited')})
</time>
</TimeText>
)}
</Flex>
)}
</Flex>
</Flex>

{intent && (
<Box flex={1}>
<IntentText muted size={0} textOverflow="ellipsis">
<Translate
t={t}
i18nKey="list-item.layout-context"
values={{title: intent.title, intent: 'edit'}}
components={{
IntentLink: ({children}) =>
intent ? (
<IntentLink params={intent.params} intent={intent.name}>
{children}
</IntentLink>
) : undefined,
}}
/>
</IntentText>
</Box>
)}
</Flex>

{!isEditing && !displayError && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './comments'
export * from './enabled'
export * from './intent'
export * from './onboarding'
export * from './selected-path'
export * from './setup'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {createContext} from 'react'

import {type CommentsIntentContextValue} from './types'

export const CommentsIntentContext = createContext<CommentsIntentContextValue | undefined>(
undefined,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {memo, type ReactNode} from 'react'

import {type CommentIntentGetter} from '../../types'
import {CommentsIntentContext} from './CommentsIntentContext'

interface CommentsIntentProviderProps {
children: ReactNode
getIntent: CommentIntentGetter
}

/**
* @beta
* @hidden
*/
export const CommentsIntentProvider = memo(function CommentsIntentProvider(
props: CommentsIntentProviderProps,
) {
const {children, getIntent} = props

return (
<CommentsIntentContext.Provider value={getIntent}>{children}</CommentsIntentContext.Provider>
)
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './CommentsIntentContext'
export * from './CommentsIntentProvider'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import {type CommentIntentGetter} from '../../types'

/**
* @beta
* @hidden
*/
export type CommentsIntentContextValue = CommentIntentGetter
23 changes: 22 additions & 1 deletion packages/sanity/src/structure/comments/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {isEqual} from 'lodash'
import {useMemo, useRef} from 'react'
import {isPortableTextSpan, isPortableTextTextBlock} from 'sanity'

import {type CommentMessage} from './types'
import {type CommentContext, type CommentDocument, type CommentMessage} from './types'

export function useCommentHasChanged(message: CommentMessage): boolean {
const prevMessage = useRef<CommentMessage>(message)
Expand All @@ -19,3 +19,24 @@ export function hasCommentMessageValue(value: CommentMessage): boolean {
(block?.children || [])?.some((c) => (isPortableTextSpan(c) ? c.text : c.userId)),
)
}

export function commentIntentIfDiffers(
parent?: CommentDocument,
comment?: CommentDocument,
): CommentContext['intent'] | undefined {
const parentIntent = parent?.context?.intent
const intent = comment?.context?.intent
// If no intent, nothing to return
if (!intent) return undefined
// If no parent intent, no comparison necessary
if (!parentIntent) return intent
// If the preview param differs, return the intent
if (
'preview' in intent.params &&
'preview' in parentIntent.params &&
intent.params.preview !== parentIntent.params.preview
) {
return intent
}
return undefined
}
1 change: 1 addition & 0 deletions packages/sanity/src/structure/comments/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './use-comment-operations'
export * from './useComments'
export * from './useCommentsEnabled'
export * from './useCommentsIntent'
export * from './useCommentsOnboarding'
export * from './useCommentsSelectedPath'
export * from './useCommentsSetup'
Expand Down
Loading

0 comments on commit 27bf5f9

Please sign in to comment.