Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .changeset/lemon-numbers-look.md
Original file line number Diff line number Diff line change
@@ -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'),
},
})
```
13 changes: 13 additions & 0 deletions .changeset/yjs-user-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@platejs/yjs": patch
---

Add `userId` option to **YjsPlugin** for combobox collaboration support

```tsx
YjsPlugin.configure({
options: {
userId: user?.id,
},
})
```
13 changes: 13 additions & 0 deletions apps/www/src/registry/ui/inline-combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -126,6 +138,7 @@ const InlineCombobox = ({
const { props: inputProps, removeInput } = useComboboxInput({
cancelInputOnBlur: true,
cursorState,
autoFocus: isCreator,
ref: inputRef,
onCancelInput: (cause) => {
if (cause !== 'backspace') {
Expand Down
3 changes: 3 additions & 0 deletions docs/components/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions packages/combobox/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
8 changes: 8 additions & 0 deletions packages/combobox/src/lib/withTriggerCombobox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const withTriggerCombobox: OverrideEditor<
insertText(text, options) {
const {
createComboboxInput,
getUserId,
triggerPreviousCharPattern,
triggerQuery,
} = getOptions();
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions packages/yjs/src/lib/BaseYjsPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const BaseYjsPlugin = createTSlatePlugin<YjsConfig>({
options: {
_isConnected: false,
_isSynced: false,
userId: null,
_providers: [],
awareness: null!,
cursors: {},
Expand Down
2 changes: 2 additions & 0 deletions packages/yjs/src/lib/providers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down