Skip to content

Commit

Permalink
fix(comments): conditional rendering of CommentsProvider (#6412)
Browse files Browse the repository at this point in the history
  • Loading branch information
hermanwikner authored Apr 22, 2024
1 parent b8f3666 commit 379396e
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ interface CommentsEnabledProviderProps {
documentType: string
}

/**
* @beta
* @hidden
*/
export const CommentsEnabledProvider = memo(function CommentsEnabledProvider(
props: CommentsEnabledProviderProps,
) {
Expand Down
6 changes: 2 additions & 4 deletions packages/sanity/src/core/comments/hooks/useCommentsEnabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import {CommentsEnabledContext} from '../context/enabled'
import {type CommentsEnabledContextValue} from '../context/enabled/types'

/**
* @internal
* This hook returns a boolean indicating whether comments are enabled or not.
* It checks if the project has the `studioComments` feature flag enabled and
* if comments is enabled for the current document in the config API.
* @beta
* @hidden
*/
export function useCommentsEnabled(): CommentsEnabledContextValue {
return useContext(CommentsEnabledContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ import {useCommentsEnabled} from '../../hooks'

export function CommentsDocumentLayout(props: DocumentLayoutProps) {
const {documentId, documentType} = props
const parentContext = useCommentsEnabled()

// If there is a parent context and the mode is not null, a parent provider is
// already checking if comments are enabled. In such cases, additional wrapping
// of the document layout in the `CommentsEnabledProvider` is unnecessary.
// The `DocumentPane` component within the `structureTool` handles this wrapping.
// However, as this plugin may be used in contexts outside of the `structureTool`,
// we must check for a parent context that checks if comments are enabled and
// conditionally apply the `CommentsEnabledProvider` wrapping if it is not present.
if (parentContext.mode !== null) {
return <CommentsDocumentLayoutInner {...props} />
}

return (
<CommentsEnabledProvider documentId={documentId} documentType={documentType}>
Expand Down
2 changes: 2 additions & 0 deletions packages/sanity/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ export {
CommentInput,
type CommentIntentGetter,
COMMENTS_INSPECTOR_NAME,
CommentsEnabledProvider,
CommentsIntentProvider,
type CommentsIntentProviderProps,
CommentsProvider,
useCommentsEnabled,
} from './comments'
export * from './components'
export * from './components/collapseMenu'
Expand Down
66 changes: 4 additions & 62 deletions packages/sanity/src/structure/panes/document/DocumentPane.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import {type Path} from '@sanity/types'
import {Stack, Text} from '@sanity/ui'
import {fromString as pathFromString} from '@sanity/util/paths'
import {memo, useCallback, useLayoutEffect, useMemo, useRef} from 'react'
import {memo, useMemo} from 'react'
import {
COMMENTS_INSPECTOR_NAME,
CommentsProvider,
ReferenceInputOptionsProvider,
SourceProvider,
Translate,
Expand All @@ -20,10 +18,10 @@ import {structureLocaleNamespace} from '../../i18n'
import {type DocumentPaneNode} from '../../types'
import {ErrorPane} from '../error'
import {LoadingPane} from '../loading'
import {CommentsWrapper} from './comments'
import {useDocumentLayoutComponent} from './document-layout'
import {DocumentPaneProvider} from './DocumentPaneProvider'
import {type DocumentPaneProviderProps} from './types'
import {useDocumentPane} from './useDocumentPane'

type DocumentPaneOptions = DocumentPaneNode['options']

Expand Down Expand Up @@ -140,70 +138,14 @@ function DocumentPaneInner(props: DocumentPaneProviderProps) {
initialValueTemplateItems={templatePermissions}
activePath={activePath}
>
<CommentsProviderWrapper>
<CommentsWrapper documentId={options.id} documentType={options.type}>
<DocumentLayout documentId={options.id} documentType={options.type} />
</CommentsProviderWrapper>
</CommentsWrapper>
</ReferenceInputOptionsProvider>
</DocumentPaneProvider>
)
}

function CommentsProviderWrapper({children}: {children: React.ReactNode}) {
const {documentId, documentType, connectionState, onPathOpen, inspector, openInspector} =
useDocumentPane()
const {params, setParams, createPathWithParams} = usePaneRouter()

const selectedCommentId = params?.comment
const paramsRef = useRef(params)
useLayoutEffect(() => {
paramsRef.current = params
}, [params])

const getCommentLink = useCallback(
(commentId: string) => {
// Generate a path based on the current pane params.
// We force a value for `inspect` to ensure that this is included in URLs when comments
// are created outside of the inspector context (i.e. directly on the field)
// @todo: consider filtering pane router params and culling all non-active RHS panes prior to generating this link
const path = createPathWithParams({
...paramsRef.current,
comment: commentId,
inspect: COMMENTS_INSPECTOR_NAME,
})
return `${window.location.origin}${path}`
},
[createPathWithParams],
)

const handleClearSelectedComment = useCallback(() => {
setParams({...paramsRef.current, comment: undefined})
}, [setParams])

const handleOpenCommentsInspector = useCallback(() => {
if (inspector?.name === COMMENTS_INSPECTOR_NAME) return

openInspector(COMMENTS_INSPECTOR_NAME)
}, [inspector?.name, openInspector])

return (
<CommentsProvider
documentId={documentId}
documentType={documentType}
getCommentLink={getCommentLink}
isConnecting={connectionState === 'connecting'}
onClearSelectedComment={handleClearSelectedComment}
onPathOpen={onPathOpen}
selectedCommentId={selectedCommentId}
isCommentsOpen={inspector?.name === COMMENTS_INSPECTOR_NAME}
onCommentsOpen={handleOpenCommentsInspector}
sortOrder="desc"
type="field"
>
{children}
</CommentsProvider>
)
}

function usePaneOptions(
options: DocumentPaneOptions,
params: Record<string, string | undefined> = {},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {useCallback, useLayoutEffect, useRef} from 'react'
import {
COMMENTS_INSPECTOR_NAME,
CommentsEnabledProvider,
CommentsProvider,
useCommentsEnabled,
} from 'sanity'
import {usePaneRouter} from 'sanity/structure'

import {useDocumentPane} from '../useDocumentPane'

interface CommentsWrapperProps {
children: React.ReactNode
documentId: string
documentType: string
}

/**
* @internal
* A wrapper that conditionally wraps the document layout in a comments provider
* if the feature is enabled for the project and the current document.
*/
export function CommentsWrapper(props: CommentsWrapperProps) {
const {children, documentId, documentType} = props

return (
<CommentsEnabledProvider documentId={documentId} documentType={documentType}>
<CommentsProviderWrapper documentId={documentId} documentType={documentType}>
{children}
</CommentsProviderWrapper>
</CommentsEnabledProvider>
)
}

function CommentsProviderWrapper(props: CommentsWrapperProps) {
const {children, documentId, documentType} = props

const {enabled} = useCommentsEnabled()
const {connectionState, onPathOpen, inspector, openInspector} = useDocumentPane()
const {params, setParams, createPathWithParams} = usePaneRouter()

const selectedCommentId = params?.comment
const paramsRef = useRef(params)

useLayoutEffect(() => {
paramsRef.current = params
}, [params])

const getCommentLink = useCallback(
(commentId: string) => {
// Generate a path based on the current pane params.
// We force a value for `inspect` to ensure that this is included in URLs when comments
// are created outside of the inspector context (i.e. directly on the field)
// @todo: consider filtering pane router params and culling all non-active RHS panes prior to generating this link
const path = createPathWithParams({
...paramsRef.current,
comment: commentId,
inspect: COMMENTS_INSPECTOR_NAME,
})
return `${window.location.origin}${path}`
},
[createPathWithParams],
)

const handleClearSelectedComment = useCallback(() => {
setParams({...paramsRef.current, comment: undefined})
}, [setParams])

const handleOpenCommentsInspector = useCallback(() => {
if (inspector?.name === COMMENTS_INSPECTOR_NAME) return

openInspector(COMMENTS_INSPECTOR_NAME)
}, [inspector?.name, openInspector])

// If comments are not enabled, render the default document layout
if (!enabled) {
return <>{children}</>
}

return (
<CommentsProvider
documentId={documentId}
documentType={documentType}
getCommentLink={getCommentLink}
isCommentsOpen={inspector?.name === COMMENTS_INSPECTOR_NAME}
isConnecting={connectionState === 'connecting'}
onClearSelectedComment={handleClearSelectedComment}
onCommentsOpen={handleOpenCommentsInspector}
onPathOpen={onPathOpen}
selectedCommentId={selectedCommentId}
sortOrder="desc"
type="field"
>
{children}
</CommentsProvider>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './CommentsWrapper'

0 comments on commit 379396e

Please sign in to comment.