mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
feat: add test annotations
This commit is contained in:
parent
ab3b8c4ee3
commit
2a4d64b074
@ -32,6 +32,8 @@ class Connection {
|
||||
private appendDoc = (path: string, value: ValueJSON) => {
|
||||
const sync = toSync(value)
|
||||
|
||||
sync.cursors = {}
|
||||
|
||||
const doc = Automerge.from(sync)
|
||||
|
||||
this.docSet.setDoc(path, doc)
|
||||
|
@ -1,14 +1,17 @@
|
||||
import { Operation, SyncDoc } from '../model'
|
||||
|
||||
export const addAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
console.log('addAnnotation!!!', op)
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
console.log('removeAnnotation!!!', op)
|
||||
return doc
|
||||
}
|
||||
|
||||
export const setAnnotation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
console.log('setAnnotation!!!', op)
|
||||
return doc
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,31 @@ import mark from './mark'
|
||||
import text from './text'
|
||||
import annotation from './annotation'
|
||||
|
||||
const setSelection = doc => doc
|
||||
const setSelection = (doc, op, { id, selection }) => {
|
||||
console.log('selection', selection.toJSON())
|
||||
if (!doc.cursors) {
|
||||
doc.cursors = {}
|
||||
}
|
||||
|
||||
// console.log('setSelection', op.toJSON(), id)
|
||||
const operation = op.toJSON()
|
||||
|
||||
if (!doc.cursors[id]) {
|
||||
doc.cursors[id] = {
|
||||
key: id,
|
||||
type: 'collaborative_selection'
|
||||
}
|
||||
}
|
||||
|
||||
const cursor = doc.cursors[id]
|
||||
const { focus, anchor } = operation.newProperties
|
||||
|
||||
if (focus) cursor.focus = focus
|
||||
if (anchor) cursor.anchor = anchor
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
const setValue = (doc, op) => doc
|
||||
|
||||
const opType: any = {
|
||||
@ -18,13 +42,16 @@ const opType: any = {
|
||||
set_value: setValue
|
||||
}
|
||||
|
||||
export const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
export const applyOperation = meta => (
|
||||
doc: SyncDoc,
|
||||
op: Operation
|
||||
): SyncDoc => {
|
||||
try {
|
||||
const applyOp = opType[op.type]
|
||||
|
||||
if (!applyOp) throw new TypeError('Invalid operation type!')
|
||||
|
||||
return applyOp(doc, op)
|
||||
return applyOp(doc, op, meta)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
||||
@ -32,5 +59,5 @@ export const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
}
|
||||
}
|
||||
|
||||
export const applySlateOps = (doc: SyncDoc, operations: Operations) =>
|
||||
operations.reduce(applyOperation, doc)
|
||||
export const applySlateOps = (doc: SyncDoc, operations: Operations, meta) =>
|
||||
operations.reduce(applyOperation(meta), doc)
|
||||
|
@ -15,18 +15,20 @@ const insertNodeOp = ({ value, obj, index, path }: Automerge.Diff) => map => {
|
||||
const inserate = ({ nodes, ...json }: any, path) => {
|
||||
const node = nodes ? { ...json, nodes: [] } : json
|
||||
|
||||
if (node.object === 'mark') {
|
||||
ops.push({
|
||||
type: 'add_mark',
|
||||
path: path.slice(0, -1),
|
||||
mark: node
|
||||
})
|
||||
} else {
|
||||
ops.push({
|
||||
type: 'insert_node',
|
||||
path,
|
||||
node
|
||||
})
|
||||
if (node.object) {
|
||||
if (node.object === 'mark') {
|
||||
ops.push({
|
||||
type: 'add_mark',
|
||||
path: path.slice(0, -1),
|
||||
mark: node
|
||||
})
|
||||
} else {
|
||||
ops.push({
|
||||
type: 'insert_node',
|
||||
path,
|
||||
node
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
nodes && nodes.forEach((n, i) => inserate(n, [...path, i]))
|
||||
|
@ -21,7 +21,7 @@ const opSet = (op: Automerge.Diff, [map, ops]) => {
|
||||
|
||||
if (set && path) {
|
||||
ops.push(set(op))
|
||||
} else {
|
||||
} else if (map[obj]) {
|
||||
map[obj][key] = link ? map[value] : value
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
import toSync from './toSync'
|
||||
import hexGen from './hexGen'
|
||||
|
||||
export const toJS = node => JSON.parse(JSON.stringify(node))
|
||||
export const toJS = node => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(node))
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export const cloneNode = node => toSync(toJS(node))
|
||||
|
||||
|
@ -5,7 +5,12 @@ import io from 'socket.io-client'
|
||||
import { Value, Operation } from 'slate'
|
||||
import { ConnectionModel } from './model'
|
||||
|
||||
import { applySlateOps, toSlateOp, toJS } from '@slate-collaborative/bridge'
|
||||
import {
|
||||
applySlateOps,
|
||||
toSlateOp,
|
||||
hexGen,
|
||||
toJS
|
||||
} from '@slate-collaborative/bridge'
|
||||
|
||||
class Connection {
|
||||
url: string
|
||||
@ -15,6 +20,7 @@ class Connection {
|
||||
socket: SocketIOClient.Socket
|
||||
editor: any
|
||||
connectOpts: any
|
||||
selection: any
|
||||
onConnect?: () => void
|
||||
onDisconnect?: () => void
|
||||
|
||||
@ -68,13 +74,47 @@ class Connection {
|
||||
})
|
||||
})
|
||||
|
||||
setTimeout(() => (this.editor.remote = false), 5)
|
||||
await Promise.resolve()
|
||||
|
||||
this.editor.remote = false
|
||||
|
||||
this.setCursors(docNew.cursors)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
setCursors = cursors => {
|
||||
if (!cursors) return
|
||||
// console.log('setCursors', cursors)
|
||||
const {
|
||||
value: { annotations }
|
||||
} = this.editor
|
||||
|
||||
const keyMap = {}
|
||||
|
||||
this.editor.withoutSaving(() => {
|
||||
annotations.forEach(anno => {
|
||||
// if (cursors[anno.key]) {
|
||||
// console.log('set cursor', anno, )
|
||||
// this.editor.setAnnotation(anno, cursors[anno.key])
|
||||
// keyMap[anno.key] = true
|
||||
// } else {
|
||||
// this.editor.removeAnnotation(anno)
|
||||
// }
|
||||
|
||||
this.editor.removeAnnotation(anno)
|
||||
})
|
||||
|
||||
Object.keys(cursors).forEach(key => {
|
||||
if (key !== this.socket.id && !keyMap[key]) {
|
||||
this.editor.addAnnotation(cursors[key])
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
receiveSlateOps = (operations: Immutable.List<Operation>) => {
|
||||
const doc = this.docSet.getDoc(this.docId)
|
||||
const message = `change from ${this.socket.id}`
|
||||
@ -82,7 +122,10 @@ class Connection {
|
||||
if (!doc) return
|
||||
|
||||
const changed = Automerge.change(doc, message, (d: any) =>
|
||||
applySlateOps(d, operations)
|
||||
applySlateOps(d, operations, {
|
||||
id: this.socket.id,
|
||||
selection: this.editor.value.selection
|
||||
})
|
||||
)
|
||||
|
||||
this.docSet.setDoc(this.docId, changed)
|
||||
|
@ -2,6 +2,7 @@ import { ReactNode } from 'react'
|
||||
|
||||
import onChange from './onChange'
|
||||
import renderEditor from './renderEditor'
|
||||
import renderAnnotation from './renderAnnotation'
|
||||
|
||||
import Connection from './Connection'
|
||||
|
||||
@ -22,7 +23,8 @@ const plugin = (opts: PluginOptions = {}) => {
|
||||
|
||||
return {
|
||||
onChange: onChange(options),
|
||||
renderEditor: renderEditor(options)
|
||||
renderEditor: renderEditor(options),
|
||||
renderAnnotation
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ExtendedEditor } from './model'
|
||||
|
||||
const onChange = opts => (editor: ExtendedEditor, next: () => void) => {
|
||||
if (!editor.remote) {
|
||||
if (editor.connection && !editor.remote) {
|
||||
const operations: any = editor.operations
|
||||
|
||||
editor.connection.receiveSlateOps(operations)
|
||||
|
43
packages/client/src/renderAnnotation.tsx
Normal file
43
packages/client/src/renderAnnotation.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
|
||||
const wrapStyles = { backgroundColor: '#e91e63', position: 'relative' }
|
||||
|
||||
const cursorStyleBase = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
pointerEvents: 'none',
|
||||
userSelect: 'none',
|
||||
transform: 'translateY(-100%)',
|
||||
fontSize: '10px',
|
||||
color: 'white',
|
||||
background: 'palevioletred',
|
||||
whiteSpace: 'nowrap'
|
||||
} as any
|
||||
|
||||
const renderAnnotation = (props, editor, next) => {
|
||||
const { children, annotation, attributes } = props
|
||||
|
||||
const isLeft = annotation.focus.offset >= annotation.anchor.offset
|
||||
|
||||
console.log('isLeft', isLeft)
|
||||
|
||||
const cursorStyles = { ...cursorStyleBase, left: isLeft ? '0%' : '100%' }
|
||||
|
||||
console.log('renderAnnotation', annotation.toJSON())
|
||||
|
||||
switch (annotation.type) {
|
||||
case 'collaborative_selection':
|
||||
return (
|
||||
<span {...attributes} style={wrapStyles}>
|
||||
<span contentEditable={false} style={cursorStyles}>
|
||||
{annotation.key}
|
||||
</span>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
export default renderAnnotation
|
Loading…
Reference in New Issue
Block a user