sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
107 lines (88 loc) • 3.85 kB
text/typescript
import {type SanityDocument} from '@sanity/client'
import {type CurrentUser, type SchemaType} from '@sanity/types'
import {isTextSelectionComment} from '../helpers'
import {type CommentDocument, type CommentsType, type CommentThreadItem} from '../types'
import {buildCommentBreadcrumbs} from './buildCommentBreadcrumbs'
const EMPTY_ARRAY: [] = []
interface BuildCommentThreadItemsProps {
comments: CommentDocument[]
currentUser: CurrentUser
documentValue: Partial<SanityDocument> | null
schemaType: SchemaType
type: CommentsType
}
/**
* This function formats comments into a structure that is easier to work with in the UI.
* It also validates each comment against the document value and schema type to ensure
* that the comment is valid. If the comment is invalid, it will be omitted from the
* returned array.
*/
export function buildCommentThreadItems(props: BuildCommentThreadItemsProps): CommentThreadItem[] {
const {comments, currentUser, documentValue, schemaType, type} = props
const parentComments = comments?.filter((c) => !c.parentCommentId)
// If the comments are "task" comments, just group them together as thread items
// without any validation of the comments.
if (type === 'task') {
const taskCommentItems = parentComments.map((parentComment) => {
const replies = comments?.filter((r) => r.parentCommentId === parentComment._id)
const commentsCount = [parentComment, ...replies].length
const hasReferencedValue = false
const item: CommentThreadItem = {
commentsCount,
parentComment,
replies,
threadId: parentComment.threadId,
hasReferencedValue,
breadcrumbs: EMPTY_ARRAY,
fieldPath: '',
}
return item
})
return taskCommentItems
}
// If the comments are "field" comments, we want to validate them against
// the document value and schema type.
if (type === 'field') {
const fieldCommentItems = parentComments.map((parentComment) => {
const crumbs = buildCommentBreadcrumbs({
currentUser,
documentValue,
fieldPath: parentComment.target.path?.field || '',
schemaType,
})
// NOTE: Keep this code commented out for now as we might want to use it later.
let hasTextSelection = false
// If the comment is a text selection comment, we need to make sure that
// we can successfully build a range decoration selection from it.
if (isTextSelectionComment(parentComment)) {
hasTextSelection = Boolean(
parentComment.target.path?.selection &&
parentComment.target.path.selection.value.some((v) => v.text),
)
}
// Check if the comment has an invalid breadcrumb. The breadcrumbs can be invalid if:
// - The field is hidden by conditional fields
// - The field is not found in the schema type
// - The field is not found in the document value (array items only)
const hasInvalidBreadcrumb = crumbs.some((bc) => bc.invalid)
// If the comment has an invalid breadcrumb or selection, we will omit it from the list.
if (hasInvalidBreadcrumb) return undefined
const replies = comments?.filter((r) => r.parentCommentId === parentComment._id)
const commentsCount = [parentComment, ...replies].length
const hasReferencedValue = hasTextSelection
const item: CommentThreadItem = {
breadcrumbs: crumbs,
commentsCount,
fieldPath: parentComment.target.path?.field || '',
parentComment,
replies,
threadId: parentComment.threadId,
hasReferencedValue,
}
return item
})
// We use the `Boolean` function to filter out any `undefined` items from the array.
return fieldCommentItems.filter(Boolean) as CommentThreadItem[]
}
return EMPTY_ARRAY
}