diff --git a/.changeset/grumpy-planes-attack.md b/.changeset/grumpy-planes-attack.md new file mode 100644 index 0000000000..030a677ee5 --- /dev/null +++ b/.changeset/grumpy-planes-attack.md @@ -0,0 +1,16 @@ +--- +'@udecode/plate-comments': major +--- + +- Renamed the `comments` prop on CommentsProvider to `initialComments` to reflect the fact that updating its value after the initial render has no effect +- Removed the following props from CommentsProvider, since they represent the internal state of the comments plugin and should not be controlled externally: + - `activeCommentId` + - `addingCommentId` + - `newValue` + - `focusTextarea` +- The following props on CommentsProvider can now be updated after the initial render (whereas prior to this version, doing so had no effect): + - `myUserId` + - `users` + - `onCommentAdd` + - `onCommentUpdate` + - `onCommentDelete` diff --git a/apps/www/content/docs/comments.mdx b/apps/www/content/docs/comments.mdx index e28f7ccd3d..8e4cfc1192 100644 --- a/apps/www/content/docs/comments.mdx +++ b/apps/www/content/docs/comments.mdx @@ -276,21 +276,9 @@ An interface representing a user who can make comments. ### CommentsStoreState -An interface representing the state of the comments store. +An interface representing the internal state of the comments store. - - The unique ID of the currently logged in or active user. - - - An object where each key is a user's unique ID and the corresponding value - is an instance of the `CommentUser` type, representing the user's data. - - - An object where each key is a comment's unique ID and the corresponding - value is an instance of the `TComment` type, representing the comment's - data. - The unique ID of the currently active or focused comment. @@ -303,18 +291,6 @@ An interface representing the state of the comments store. Indicates whether the comment textarea is focused. - - A function that is invoked when a new comment is added. It accepts a partial - comment object, which must include the user's ID. - - - A function that is invoked when a comment is updated. It accepts a comment - object, which must include the comment's ID. - - - A function that is invoked when a comment is deleted. It accepts the unique - ID of the comment to be deleted. - ### TComment @@ -563,12 +539,33 @@ A div component for positioning comments in the editor. ### CommentsProvider -A jotai provider for comments data and users data. +A provider for comments data and users data. - -Extends `CommentsStoreState`. - + + The unique ID of the currently logged in or active user. + + + An object where each key is a user's unique ID and the corresponding value + is an instance of the `CommentUser` type, representing the user's data. + + + An object where each key is a comment's unique ID and the corresponding + value is an instance of the `TComment` type, representing the comment's + data. + + + A function that is invoked when a new comment is added. It accepts a partial + comment object, which must include the user's ID. + + + A function that is invoked when a comment is updated. It accepts a comment + object, which must include the comment's ID. + + + A function that is invoked when a comment is deleted. It accepts the unique + ID of the comment to be deleted. + ### useActiveCommentNode diff --git a/apps/www/src/lib/plate/demo/comments/CommentsProvider.tsx b/apps/www/src/lib/plate/demo/comments/CommentsProvider.tsx index 7cdeba5f88..de2b27bc3b 100644 --- a/apps/www/src/lib/plate/demo/comments/CommentsProvider.tsx +++ b/apps/www/src/lib/plate/demo/comments/CommentsProvider.tsx @@ -5,7 +5,7 @@ import { CommentsProvider as CommentsProviderPrimitive } from '@udecode/plate-co export function CommentsProvider({ children }: { children: ReactNode }) { return ( diff --git a/apps/www/src/lib/plate/demo/values/tableValue.tsx b/apps/www/src/lib/plate/demo/values/tableValue.tsx index ecf8252c97..e124c81caf 100644 --- a/apps/www/src/lib/plate/demo/values/tableValue.tsx +++ b/apps/www/src/lib/plate/demo/values/tableValue.tsx @@ -118,4 +118,4 @@ export const tableValue: any = ( {createSpanningTable()} -); \ No newline at end of file +); diff --git a/packages/comments/src/stores/comments/CommentsProvider.tsx b/packages/comments/src/stores/comments/CommentsProvider.tsx index 71dbd639e3..181a8ea75c 100644 --- a/packages/comments/src/stores/comments/CommentsProvider.tsx +++ b/packages/comments/src/stores/comments/CommentsProvider.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { createContext, ReactNode, useContext, useMemo } from 'react'; import { createAtomStore, getJotaiProviderInitialValues, @@ -11,9 +11,7 @@ import { import { CommentUser, TComment } from '../../types'; -export const SCOPE_COMMENTS = Symbol('comments'); - -export interface CommentsStoreState { +export interface CommentsContext { /** * Id of the current user. */ @@ -24,6 +22,16 @@ export interface CommentsStoreState { */ users: Record; + onCommentAdd: ((value: WithPartial) => void) | null; + onCommentUpdate: + | ((value: Pick & Partial>) => void) + | null; + onCommentDelete: ((id: string) => void) | null; +} + +export const SCOPE_COMMENTS = Symbol('comments'); + +export interface CommentsStoreState { /** * Comments data. */ @@ -39,26 +47,10 @@ export interface CommentsStoreState { newValue: Value; focusTextarea: boolean; - - onCommentAdd: ((value: WithPartial) => void) | null; - onCommentUpdate: - | ((value: Pick & Partial>) => void) - | null; - onCommentDelete: ((id: string) => void) | null; } export const { commentsStore, useCommentsStore } = createAtomStore( { - /** - * Id of the current user. - */ - myUserId: null, - - /** - * Users data. - */ - users: {}, - /** * Comments data. */ @@ -74,35 +66,83 @@ export const { commentsStore, useCommentsStore } = createAtomStore( newValue: [{ type: 'p', children: [{ text: '' }] }], focusTextarea: false, - - onCommentAdd: null, - onCommentUpdate: null, - onCommentDelete: null, - } as CommentsStoreState, + } satisfies CommentsStoreState as CommentsStoreState, { name: 'comments', scope: SCOPE_COMMENTS, } ); +export const CommentsContext = createContext({ + myUserId: null, + users: {}, + onCommentAdd: null, + onCommentUpdate: null, + onCommentDelete: null, +}); + +export interface CommentsProviderProps extends Partial { + initialComments?: CommentsStoreState['comments']; + children: ReactNode; +} + export function CommentsProvider({ children, - ...props -}: Partial & { children: ReactNode }) { + initialComments, + myUserId = null, + users = {}, + onCommentAdd = null, + onCommentUpdate = null, + onCommentDelete = null, +}: CommentsProviderProps) { return ( - {children} + + {children} + ); } export const useCommentsStates = () => useCommentsStore().use; -export const useCommentsSelectors = () => useCommentsStore().get; export const useCommentsActions = () => useCommentsStore().set; +export const useCommentsSelectors = () => { + const context = useContext(CommentsContext); + + const contextGetters = useMemo( + () => + Object.fromEntries( + Object.entries(context).map(([key, value]) => [key, () => value]) + ) as { [K in keyof CommentsContext]: () => CommentsContext[K] }, + [context] + ); + + const storeGetters = useCommentsStore().get; + + // Combine getters from context and store + return useMemo( + () => ({ + ...contextGetters, + ...storeGetters, + }), + [contextGetters, storeGetters] + ); +}; + export const useCommentById = (id?: string | null): TComment | null => { const comments = useCommentsSelectors().comments(); if (!id) return null;