diff --git a/packages/bridge/src/apply/index.ts b/packages/bridge/src/apply/index.ts index fdd3983..c7d366c 100644 --- a/packages/bridge/src/apply/index.ts +++ b/packages/bridge/src/apply/index.ts @@ -3,7 +3,6 @@ import { Operation, Operations, SyncDoc } from '../model' import node from './node' import mark from './mark' import text from './text' -import selection from './selection' import annotation from './annotation' const setSelection = doc => doc @@ -14,20 +13,17 @@ const opType: any = { ...annotation, ...node, ...mark, - ...selection + set_selection: setSelection // set_value: setValue } -export const applyOperation = meta => ( - doc: SyncDoc, - op: Operation -): SyncDoc => { +export const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => { try { const applyOp = opType[op.type] if (!applyOp) throw new TypeError('Unsupported operation type!') - return applyOp(doc, op, meta) + return applyOp(doc, op) } catch (e) { console.error(e) @@ -35,5 +31,5 @@ export const applyOperation = meta => ( } } -export const applySlateOps = (doc: SyncDoc, operations: Operations, meta) => - operations.reduce(applyOperation(meta), doc) +export const applySlateOps = (doc: SyncDoc, operations: Operations) => + operations.reduce(applyOperation, doc) diff --git a/packages/bridge/src/apply/selection.ts b/packages/bridge/src/apply/selection.ts deleted file mode 100644 index 55283bd..0000000 --- a/packages/bridge/src/apply/selection.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { toJS } from '../utils' - -const setSelection = (doc, op, { id, selection, annotationType }) => { - if (!doc.cursors) { - doc.cursors = {} - } - - const operation = op.toJS() - - if (!doc.cursors[id]) { - doc.cursors[id] = { - key: id, - type: annotationType, - data: {} - } - } - - const cursor = doc.cursors[id] - const { focus, anchor } = operation.newProperties - - if (focus) cursor.focus = focus - if (anchor) cursor.anchor = anchor - - const cursorPath = cursor.data.isBackward - ? anchor && anchor.path - : focus && focus.path - - if (cursorPath) cursor.data.cursorPath = toJS(cursorPath) - - cursor.data.isBackward = selection.isBackward - - return doc -} - -export default { - set_selection: setSelection -} diff --git a/packages/bridge/src/convert/index.ts b/packages/bridge/src/convert/index.ts index 7dc0321..31bd8a9 100644 --- a/packages/bridge/src/convert/index.ts +++ b/packages/bridge/src/convert/index.ts @@ -30,7 +30,11 @@ const toSlateOp = (ops: Automerge.Diff[], currentTree) => { [] ]) - return defer.flatMap(op => op(tempTree, currentTree)) + const res = defer.flatMap(op => op(tempTree, currentTree)) + + console.log('toSlate@@@', ops, res) + + return res } export { toSlateOp } diff --git a/packages/bridge/src/convert/insert.ts b/packages/bridge/src/convert/insert.ts index dc7a300..2f923d8 100644 --- a/packages/bridge/src/convert/insert.ts +++ b/packages/bridge/src/convert/insert.ts @@ -52,7 +52,7 @@ const opInsert = (op: Automerge.Diff, [map, ops]) => { if (link && map[obj]) { map[obj].splice(index, 0, map[value] || value) - } else if (type === 'text' && !path) { + } else if ((type === 'text' || type === 'list') && !path) { map[obj] = map[obj] ? map[obj] .slice(0, index) diff --git a/packages/bridge/src/convert/remove.ts b/packages/bridge/src/convert/remove.ts index a768cdf..80cd113 100644 --- a/packages/bridge/src/convert/remove.ts +++ b/packages/bridge/src/convert/remove.ts @@ -40,10 +40,18 @@ const removeNodesOp = ({ index, obj, path }: Automerge.Diff) => (map, doc) => { } } +const removeAnnotationOp = ({ key }: Automerge.Diff) => (map, doc) => { + return { + type: 'remove_annotation', + annotation: toJS(doc.annotations[key]) + } +} + const removeByType = { text: removeTextOp, nodes: removeNodesOp, - marks: removeMarkOp + marks: removeMarkOp, + annotations: removeAnnotationOp } const opRemove = (op: Automerge.Diff, [map, ops]) => { diff --git a/packages/bridge/src/convert/set.ts b/packages/bridge/src/convert/set.ts index 26d4c98..9aa3d85 100644 --- a/packages/bridge/src/convert/set.ts +++ b/packages/bridge/src/convert/set.ts @@ -10,6 +10,31 @@ const setDataOp = ({ path, value }: Automerge.Diff) => map => ({ } }) +const AnnotationSetOp = ({ key, value }: Automerge.Diff) => (map, doc) => { + if (!doc.annotations) { + doc.annotations = {} + } + + let op + + if (!doc.annotations[key]) { + op = { + type: 'add_annotation', + annotation: map[value] + } + } else { + op = { + type: 'set_annotation', + properties: doc.annotations[key], + newProperties: map[value] + } + } + + console.log('opSET!!', key, map[value], op) + + return op +} + const setByType = { data: setDataOp } @@ -25,6 +50,13 @@ const opSet = (op: Automerge.Diff, [map, ops]) => { map[obj][key] = link ? map[value] : value } + /** + * Annotation + */ + if (path && path.length === 1 && path[0] === 'annotations') { + ops.push(AnnotationSetOp(op)) + } + return [map, ops] } catch (e) { console.error(e, op, toJS(map)) diff --git a/packages/bridge/src/cursor/index.ts b/packages/bridge/src/cursor/index.ts new file mode 100644 index 0000000..c0eeee5 --- /dev/null +++ b/packages/bridge/src/cursor/index.ts @@ -0,0 +1,88 @@ +import { toJS } from '../utils' + +import { Operation } from 'slate' +import { List } from 'immutable' + +export const setCursor = ( + doc, + { id, selection, selectionOps, annotationType } +) => { + if (!doc.annotations) { + doc.annotations = {} + } + + if (!doc.annotations[id]) { + doc.annotations[id] = { + key: id, + type: annotationType, + data: {} + } + } + + const annotation = toJS(doc.annotations[id]) + + if (selectionOps.size) { + selectionOps.forEach(op => { + const { newProperties } = op.toJSON() + + if (newProperties.focus) annotation.focus = newProperties.focus + if (newProperties.anchor) annotation.anchor = newProperties.anchor + if (newProperties.data) annotation.data = newProperties.data + }) + } + + const cursorStart = annotation.anchor && annotation.anchor.offset + const cursorEnd = annotation.focus && annotation.focus.offset + + console.log('cursor!!', cursorStart, cursorEnd) + console.log( + 'selection!!', + annotation, + selection.start.offset, + selection.end.offset + ) + + if (selection.start.offset !== cursorStart) { + annotation.focus = selection.end.toJS() || {} + } + + if (selection.end.offset !== cursorEnd) { + annotation.anchor = selection.start.toJS() || {} + } + + annotation.data.isBackward = selection.isBackward + + console.log('setted cursor', annotation, toJS(doc)) + + doc.annotations[id] = annotation + + return doc +} + +export const removeCursor = (doc, { id }) => { + console.log('!!!removeCursor', doc, id) + if (doc.annotations && doc.annotations[id]) { + delete doc.annotations[id] + } + + return doc +} + +export const cursorOpFilter = (doc, ops: List) => + ops.filter(op => { + const { annotations } = doc + + if (op.type === 'set_annotation') { + return !( + (op.properties && annotations[op.properties.key]) || + (op.newProperties && annotations[op.newProperties.key]) + ) + } else if ( + op.type === 'add_annotation' || + op.type === 'remove_annotation' + ) { + return !annotations[op.annotation.key] + } + + return true + }) diff --git a/packages/bridge/src/index.ts b/packages/bridge/src/index.ts index 26d4a2e..5b54441 100644 --- a/packages/bridge/src/index.ts +++ b/packages/bridge/src/index.ts @@ -1,3 +1,4 @@ export * from './apply' export * from './convert' export * from './utils' +export * from './cursor' diff --git a/packages/client/src/Connection.ts b/packages/client/src/Connection.ts index 63b7496..79312d7 100644 --- a/packages/client/src/Connection.ts +++ b/packages/client/src/Connection.ts @@ -3,12 +3,14 @@ import Immutable from 'immutable' import io from 'socket.io-client' import { Value, Operation } from 'slate' -import { ConnectionModel } from './model' +import { ConnectionModel, ExtendedEditor } from './model' import { + setCursor, + removeCursor, + cursorOpFilter, applySlateOps, toSlateOp, - hexGen, toJS } from '@slate-collaborative/bridge' @@ -18,7 +20,7 @@ class Connection { docSet: Automerge.DocSet connection: Automerge.Connection socket: SocketIOClient.Socket - editor: any + editor: ExtendedEditor connectOpts: any selection: any onConnect?: () => void @@ -56,6 +58,9 @@ class Connection { const currentDoc = this.docSet.getDoc(this.docId) const docNew = this.connection.receiveMsg(data) + console.log('current doc before updates', toJS(currentDoc)) + console.log('new doc with remote updates!!', toJS(docNew)) + if (!docNew) { return } @@ -88,25 +93,31 @@ class Connection { setCursors = cursors => { if (!cursors) return - const { - value: { annotations } - } = this.editor + // const { + // value: { annotations } + // } = this.editor - const keyMap = {} + // const keyMap = {} - console.log('cursors', cursors) + // console.log('cursors', cursors) - this.editor.withoutSaving(() => { - annotations.forEach(anno => { - this.editor.removeAnnotation(anno) - }) + // this.editor.withoutSaving(() => { + // annotations.forEach(anno => { + // this.editor.removeAnnotation(anno) + // }) - Object.keys(cursors).forEach(key => { - if (key !== this.socket.id && !keyMap[key]) { - this.editor.addAnnotation(toJS(cursors[key])) - } - }) - }) + // Object.keys(cursors).forEach(key => { + // if (key !== this.socket.id && !keyMap[key]) { + // this.editor.addAnnotation(toJS(cursors[key])) + // } + // }) + // }) + + console.log( + '!!!!VAL', + this.connectOpts.query.name, + this.editor.value.toJSON({ preserveAnnotations: true }) + ) } receiveSlateOps = (operations: Immutable.List) => { @@ -115,52 +126,25 @@ class Connection { if (!doc) return - operations = operations.filter(op => op.type !== 'set_selection') - - const { value } = this.editor - - const { selection } = value + const { + value: { selection } + } = this.editor - const meta = { + const cursorData = { id: this.socket.id, selection, + selectionOps: operations.filter(op => op.type === 'set_selection'), annotationType: 'collaborative_selection' } - const cursor = doc.cursors[meta.id] - const cursorStart = cursor && cursor.anchor && cursor.anchor.offset - const cursorEnd = cursor && cursor.focus && cursor.focus.offset - - console.log('cursor!!', cursorStart, cursorEnd) - console.log('selection!!', selection.start.offset, selection.end.offset) - - if ( - selection.start.offset !== cursorStart || - selection.end.offset !== cursorEnd - ) { - console.log( - '!!!!!! append selection op!!!', - selection.start.toJS(), - selection.end.toJS() - ) - const opData = { - type: 'set_selection', - properties: cursor || {}, - newProperties: { - anchor: selection.start, - focus: selection.end - } - } - - const op = Operation.fromJSON(opData) - - operations = operations.push(op) - } + const withCursor = selection.isFocused ? setCursor : removeCursor const changed = Automerge.change(doc, message, (d: any) => - applySlateOps(d, operations, meta) + withCursor(applySlateOps(d, cursorOpFilter(d, operations)), cursorData) ) + console.log('doc with annotations!!', toJS(changed)) + this.docSet.setDoc(this.docId, changed) } diff --git a/packages/client/src/model.ts b/packages/client/src/model.ts index cc284c1..52b615c 100644 --- a/packages/client/src/model.ts +++ b/packages/client/src/model.ts @@ -1,16 +1,22 @@ -import { Editor } from 'slate' +import { Editor, Controller, Value } from 'slate' import { PluginOptions } from './index' import Connection from './Connection' -export interface ConnectionModel extends PluginOptions { - editor: Editor - onConnect: () => void - onDisconnect: () => void +interface FixedController extends Controller { + setValue: (value: Value) => void } export interface ExtendedEditor extends Editor { - remote: boolean - connection: Connection + remote?: boolean + connection?: Connection + controller: FixedController + setFocus: () => void +} + +export interface ConnectionModel extends PluginOptions { + editor: ExtendedEditor + onConnect: () => void + onDisconnect: () => void } export interface ControllerProps extends PluginOptions { diff --git a/packages/client/src/renderAnnotation.tsx b/packages/client/src/renderAnnotation.tsx index ce7cf97..67fd31d 100644 --- a/packages/client/src/renderAnnotation.tsx +++ b/packages/client/src/renderAnnotation.tsx @@ -18,7 +18,9 @@ const renderAnnotation = (props, editor, next) => { const { children, annotation, attributes, node } = props const isBackward = annotation.data.get('isBackward') - const cursorPath = annotation.data.get('cursorPath') + const cursorPath = isBackward ? annotation.anchor.path : annotation.focus.path + + console.log('renderAnnotation', props, isBackward, cursorPath) const cursorStyles = { ...cursorStyleBase, left: isBackward ? '0%' : '100%' } @@ -28,6 +30,7 @@ const renderAnnotation = (props, editor, next) => { const isTarget = targetNode && targetNode.key === node.key + console.log('isTarget', isTarget, targetNode, node) const showCursor = isTarget switch (annotation.type) {