147 lines
3.3 KiB
TypeScript
147 lines
3.3 KiB
TypeScript
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<any> | null,
|
|
instance: string,
|
|
}
|
|
|
|
function getMapFromOptions(options: AnnotationOptions): Y.Map<any> {
|
|
return options.map
|
|
? options.map
|
|
: options.document?.getMap(options.field) as Y.Map<any>
|
|
}
|
|
|
|
declare module '@tiptap/core' {
|
|
interface Commands<ReturnType> {
|
|
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: <AnnotationOptions>{
|
|
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, <AddAnnotationAction>{
|
|
type: 'addAnnotation',
|
|
from: selection.from,
|
|
to: selection.to,
|
|
data,
|
|
})
|
|
}
|
|
|
|
return true
|
|
},
|
|
updateAnnotation: (id: string, data: any) => ({ dispatch, state }) => {
|
|
if (dispatch) {
|
|
state.tr.setMeta(AnnotationPluginKey, <UpdateAnnotationAction>{
|
|
type: 'updateAnnotation',
|
|
id,
|
|
data,
|
|
})
|
|
}
|
|
|
|
return true
|
|
},
|
|
deleteAnnotation: id => ({ dispatch, state }) => {
|
|
if (dispatch) {
|
|
state.tr.setMeta(AnnotationPluginKey, <DeleteAnnotationAction>{
|
|
type: 'deleteAnnotation',
|
|
id,
|
|
})
|
|
}
|
|
|
|
return true
|
|
},
|
|
}
|
|
},
|
|
|
|
addProseMirrorPlugins() {
|
|
return [
|
|
AnnotationPlugin({
|
|
HTMLAttributes: this.options.HTMLAttributes,
|
|
onUpdate: this.options.onUpdate,
|
|
map: getMapFromOptions(this.options),
|
|
instance: this.options.instance,
|
|
}),
|
|
]
|
|
},
|
|
})
|