import * as Y from 'yjs' import { Extension } from '@tiptap/core' import { AnnotationPlugin, AnnotationPluginKey } from './AnnotationPlugin' export interface AddAnnotationAction { type: 'addAnnotation', data: any, from: number, to: number, } export interface UpdateAnnotationAction { type: 'updateAnnotation', id: string, data: any, } export interface DeleteAnnotationAction { type: 'deleteAnnotation', id: string, } export interface AnnotationOptions { HTMLAttributes: { [key: string]: any }, /** * An event listener which receives annotations for the current selection. */ onUpdate: (items: [any?]) => {}, /** * An initialized Y.js document. */ document: Y.Doc | null, /** * Name of a Y.js map, can be changed to sync multiple fields with one Y.js document. */ field: string, /** * A raw Y.js map, can be used instead of `document` and `field`. */ map: Y.Map | null, instance: string, } function getMapFromOptions(options: AnnotationOptions): Y.Map { return options.map ? options.map : options.document?.getMap(options.field) as Y.Map } declare module '@tiptap/core' { interface Commands { annotation: { addAnnotation: (data: any) => ReturnType, updateAnnotation: (id: string, data: any) => ReturnType, deleteAnnotation: (id: string) => ReturnType, } } } export const CollaborationAnnotation = Extension.create({ name: 'annotation', priority: 1000, defaultOptions: { HTMLAttributes: { class: 'annotation', }, onUpdate: decorations => decorations, document: null, field: 'annotations', map: null, instance: '', }, onCreate() { const map = getMapFromOptions(this.options) map.observe(() => { console.log(`[${this.options.instance}] map updated → createDecorations`) const transaction = this.editor.state.tr.setMeta(AnnotationPluginKey, { type: 'createDecorations', }) this.editor.view.dispatch(transaction) }) }, addCommands() { return { addAnnotation: (data: any) => ({ dispatch, state }) => { const { selection } = state if (selection.empty) { return false } if (dispatch && data) { state.tr.setMeta(AnnotationPluginKey, { type: 'addAnnotation', from: selection.from, to: selection.to, data, }) } return true }, updateAnnotation: (id: string, data: any) => ({ dispatch, state }) => { if (dispatch) { state.tr.setMeta(AnnotationPluginKey, { type: 'updateAnnotation', id, data, }) } return true }, deleteAnnotation: id => ({ dispatch, state }) => { if (dispatch) { state.tr.setMeta(AnnotationPluginKey, { type: 'deleteAnnotation', id, }) } return true }, } }, addProseMirrorPlugins() { return [ AnnotationPlugin({ HTMLAttributes: this.options.HTMLAttributes, onUpdate: this.options.onUpdate, map: getMapFromOptions(this.options), instance: this.options.instance, }), ] }, })