| import { Extension } from "@tiptap/core"; |
| import { yCursorPlugin, defaultCursorBuilder, defaultSelectionBuilder } from "@tiptap/y-tiptap"; |
|
|
| export interface CursorUser { |
| name: string; |
| color: string; |
| avatarUrl?: string; |
| } |
|
|
| export interface CollaborationCursorOptions { |
| provider: any; |
| user: CursorUser; |
| render?: (user: CursorUser) => HTMLElement; |
| selectionRender?: typeof defaultSelectionBuilder; |
| } |
|
|
| const awarenessStatesToArray = (states: Map<number, any>) => { |
| return Array.from(states.entries()).map(([key, value]) => ({ |
| clientId: key, |
| ...value.user, |
| })); |
| }; |
|
|
| export const CollaborationCursorV3 = Extension.create<CollaborationCursorOptions>({ |
| name: "collaborationCursor", |
| priority: 999, |
|
|
| addOptions() { |
| return { |
| provider: null, |
| user: { name: "Anonymous", color: "#aaaaaa" } as CursorUser, |
| render: (user: CursorUser) => { |
| const cursor = document.createElement("span"); |
| cursor.classList.add("collaboration-cursor__caret"); |
| cursor.setAttribute("style", `border-color: ${user.color}`); |
| const label = document.createElement("div"); |
| label.classList.add("collaboration-cursor__label"); |
| label.setAttribute("style", `background-color: ${user.color}`); |
|
|
| if (user.avatarUrl) { |
| const avatar = document.createElement("img"); |
| avatar.src = user.avatarUrl; |
| avatar.classList.add("collaboration-cursor__avatar"); |
| label.appendChild(avatar); |
| } |
|
|
| label.appendChild(document.createTextNode(user.name)); |
| cursor.appendChild(label); |
| return cursor; |
| }, |
| selectionRender: defaultSelectionBuilder, |
| }; |
| }, |
|
|
| addStorage() { |
| return { users: [] as any[] }; |
| }, |
|
|
| onCreate() { |
| if (!this.options.provider) { |
| throw new Error('The "provider" option is required for CollaborationCursorV3'); |
| } |
| }, |
|
|
| addCommands() { |
| |
| |
| |
| |
| return { |
| updateUser: |
| (attributes: Record<string, any>) => |
| () => { |
| this.options.user = attributes as any; |
| this.options.provider.awareness.setLocalStateField("user", this.options.user); |
| return true; |
| }, |
| } as any; |
| }, |
|
|
| addProseMirrorPlugins() { |
| const awareness = this.options.provider.awareness; |
| awareness.setLocalStateField("user", this.options.user); |
| this.storage.users = awarenessStatesToArray(awareness.states); |
| awareness.on("update", () => { |
| this.storage.users = awarenessStatesToArray(awareness.states); |
| }); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const userBuilder = this.options.render ?? defaultCursorBuilder; |
| const cursorBuilder = (user: CursorUser, clientId: number): HTMLElement => { |
| const el = userBuilder(user); |
| const peerState = awareness.getStates().get(clientId); |
| if (peerState?.agentFocus) { |
| el.classList.add("collaboration-cursor--silenced"); |
| } |
| return el; |
| }; |
|
|
| return [ |
| yCursorPlugin( |
| awareness, |
| { |
| cursorBuilder, |
| selectionBuilder: this.options.selectionRender ?? defaultSelectionBuilder, |
| }, |
| ), |
| ]; |
| }, |
| }); |
|
|