diff --git a/.changeset/lemon-numbers-look.md b/.changeset/lemon-numbers-look.md new file mode 100644 index 0000000000..d8c46a623f --- /dev/null +++ b/.changeset/lemon-numbers-look.md @@ -0,0 +1,21 @@ +--- +"@platejs/combobox": patch +--- + +Add `getUserId` option to `TriggerComboboxPluginOptions` to fix combobox popover opening for all users in Yjs collaboration mode + +When a user types a trigger character (e.g. `/` or `@`), the combobox input now stores the creator's `userId`. Only the creator will see the auto-focused combobox popover. + +```tsx +SlashPlugin.configure({ + options: { + getUserId: (editor) => editor.getOption(YjsPlugin, 'userId'), + }, +}) + +MentionPlugin.configure({ + options: { + getUserId: (editor) => editor.getOption(YjsPlugin, 'userId'), + }, +}) +``` diff --git a/.changeset/yjs-user-id.md b/.changeset/yjs-user-id.md new file mode 100644 index 0000000000..e9925fc219 --- /dev/null +++ b/.changeset/yjs-user-id.md @@ -0,0 +1,13 @@ +--- +"@platejs/yjs": patch +--- + +Add `userId` option to **YjsPlugin** for combobox collaboration support + +```tsx +YjsPlugin.configure({ + options: { + userId: user?.id, + }, +}) +``` diff --git a/apps/www/src/registry/ui/inline-combobox.tsx b/apps/www/src/registry/ui/inline-combobox.tsx index 8567f3c751..f32084fb92 100644 --- a/apps/www/src/registry/ui/inline-combobox.tsx +++ b/apps/www/src/registry/ui/inline-combobox.tsx @@ -27,6 +27,7 @@ import { cva } from 'class-variance-authority'; import { useComposedRef, useEditorRef } from 'platejs/react'; import { cn } from '@/lib/utils'; +import { YjsPlugin } from '@platejs/yjs/react'; type FilterFn = ( item: { value: string; group?: string; keywords?: string[]; label?: string }, @@ -89,6 +90,17 @@ const InlineCombobox = ({ const hasValueProp = valueProp !== undefined; const value = hasValueProp ? valueProp : valueState; + // Check if current user is the creator of this element (for Yjs collaboration) + const isCreator = React.useMemo(() => { + const elementUserId = (element as any).userId; + const currentUserId = editor.getOption(YjsPlugin, 'userId'); + + // If no userId (backwards compatibility or non-Yjs), allow + if (!elementUserId) return true; + + return elementUserId === currentUserId; + }, [editor, element]); + const setValue = React.useCallback( (newValue: string) => { setValueProp?.(newValue); @@ -126,6 +138,7 @@ const InlineCombobox = ({ const { props: inputProps, removeInput } = useComboboxInput({ cancelInputOnBlur: true, cursorState, + autoFocus: isCreator, ref: inputRef, onCancelInput: (cause) => { if (cause !== 'backspace') { diff --git a/docs/components/changelog.mdx b/docs/components/changelog.mdx index 4b24f33965..f5583cf733 100644 --- a/docs/components/changelog.mdx +++ b/docs/components/changelog.mdx @@ -8,6 +8,9 @@ Since Plate UI is not a component library, a changelog is maintained here. Use the [CLI](https://platejs.org/docs/components/cli) to install the latest version of the components. +### November 30 #26.10 +- `inline-combobox`: Added Yjs collaboration support - combobox popover now only shows for the user who triggered it, preventing the popover from opening for all users in collaborative editing + ### October 21 #26.9 - `suggestion-kit`: Remove `BlockSuggestion`use `SuggestionLineBreak` instead to fixes styles. - `use-chat`: Fix AI comment hiding when finished. diff --git a/packages/combobox/src/lib/types.ts b/packages/combobox/src/lib/types.ts index 9e4c9bf6d3..9c23c8a5d0 100644 --- a/packages/combobox/src/lib/types.ts +++ b/packages/combobox/src/lib/types.ts @@ -18,5 +18,7 @@ export type TriggerComboboxPluginOptions = { trigger?: RegExp | string[] | string; triggerPreviousCharPattern?: RegExp; createComboboxInput?: (trigger: string) => TElement; + /** Get current user ID for Yjs collaboration - used to set userId on combobox input */ + getUserId?: (editor: SlateEditor) => string | undefined; triggerQuery?: (editor: SlateEditor) => boolean; }; diff --git a/packages/combobox/src/lib/withTriggerCombobox.ts b/packages/combobox/src/lib/withTriggerCombobox.ts index 7e76c45169..b005ab4e08 100644 --- a/packages/combobox/src/lib/withTriggerCombobox.ts +++ b/packages/combobox/src/lib/withTriggerCombobox.ts @@ -28,6 +28,7 @@ export const withTriggerCombobox: OverrideEditor< insertText(text, options) { const { createComboboxInput, + getUserId, triggerPreviousCharPattern, triggerQuery, } = getOptions(); @@ -54,6 +55,13 @@ export const withTriggerCombobox: OverrideEditor< ? createComboboxInput(text) : { children: [{ text: '' }], type }; + // Add userId for Yjs collaboration - only the creator sees the combobox + const userId = getUserId?.(editor as SlateEditor); + + if (userId) { + (inputNode as any).userId = userId; + } + return editor.tf.insertNodes(inputNode, options); } diff --git a/packages/yjs/src/lib/BaseYjsPlugin.ts b/packages/yjs/src/lib/BaseYjsPlugin.ts index 61fdfd5b8b..3edb028c31 100644 --- a/packages/yjs/src/lib/BaseYjsPlugin.ts +++ b/packages/yjs/src/lib/BaseYjsPlugin.ts @@ -33,6 +33,7 @@ export const BaseYjsPlugin = createTSlatePlugin({ options: { _isConnected: false, _isSynced: false, + userId: null, _providers: [], awareness: null!, cursors: {}, diff --git a/packages/yjs/src/lib/providers/types.ts b/packages/yjs/src/lib/providers/types.ts index f28657f699..69bccc11c4 100644 --- a/packages/yjs/src/lib/providers/types.ts +++ b/packages/yjs/src/lib/providers/types.ts @@ -101,6 +101,8 @@ export type YjsConfig = PluginConfig< _isConnected: boolean; /** Whether the plugin is currently synced. */ _isSynced: boolean; + /** The current user ID. */ + userId: string | null; /** Array of all active, instantiated providers. */ _providers: UnifiedProvider[]; /** The shared Awareness instance used by the plugin. */