mirror of
https://github.com/cudr/slate-collaborative.git
synced 2026-03-02 03:40:18 +00:00
feat: update to slate 0.5x (#10)
Update Slate-Collaboration to be compatible with Slate 0.5x versions.
This commit is contained in:
@@ -1,60 +0,0 @@
|
||||
import { SyncDoc } from '../model/index'
|
||||
import { toSync } from '../utils'
|
||||
import {
|
||||
AddAnnotationOperation,
|
||||
RemoveAnnotationOperation,
|
||||
SetAnnotationOperation
|
||||
} from 'slate'
|
||||
|
||||
export const addAnnotation = (
|
||||
doc: SyncDoc,
|
||||
op: AddAnnotationOperation
|
||||
): SyncDoc => {
|
||||
if (!doc.annotations) {
|
||||
doc['annotations'] = {}
|
||||
}
|
||||
|
||||
const annotation = op.annotation.toJSON()
|
||||
|
||||
doc.annotations[annotation.key] = toSync(annotation)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeAnnotation = (
|
||||
doc: SyncDoc,
|
||||
op: RemoveAnnotationOperation
|
||||
): SyncDoc => {
|
||||
if (doc.annotations) {
|
||||
delete doc.annotations[op.annotation.key]
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const setAnnotation = (
|
||||
doc: SyncDoc,
|
||||
op: SetAnnotationOperation
|
||||
): SyncDoc => {
|
||||
/**
|
||||
* Looks like set_annotation option is broken, temporary disabled
|
||||
*/
|
||||
|
||||
// const { newProperties }: any = op.toJSON()
|
||||
|
||||
// if (!doc.annotations || !newProperties) return doc
|
||||
|
||||
// if (!doc.annotations[newProperties.key]) {
|
||||
// return addAnnotation(doc, newProperties)
|
||||
// } else {
|
||||
// doc.annotations[newProperties.key] = { ...doc.annotations[newProperties.key], ...newProperties }
|
||||
// }
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default {
|
||||
add_annotation: addAnnotation,
|
||||
remove_annotation: removeAnnotation,
|
||||
set_annotation: setAnnotation
|
||||
}
|
||||
233
packages/bridge/src/apply/apply.spec.ts
Normal file
233
packages/bridge/src/apply/apply.spec.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import * as Automerge from 'automerge'
|
||||
|
||||
import { createDoc, toJS, createNode, createText } from '../utils'
|
||||
|
||||
import { applySlateOps } from './'
|
||||
|
||||
const transforms = [
|
||||
[
|
||||
'insert_text',
|
||||
[createNode('paragraph', '')],
|
||||
[
|
||||
{
|
||||
marks: [],
|
||||
offset: 0,
|
||||
path: [0, 0],
|
||||
text: 'Hello ',
|
||||
type: 'insert_text'
|
||||
},
|
||||
{
|
||||
marks: [],
|
||||
offset: 6,
|
||||
path: [0, 0],
|
||||
text: 'collaborator',
|
||||
type: 'insert_text'
|
||||
},
|
||||
{
|
||||
marks: [],
|
||||
offset: 18,
|
||||
path: [0, 0],
|
||||
text: '!',
|
||||
type: 'insert_text'
|
||||
}
|
||||
],
|
||||
[createNode('paragraph', 'Hello collaborator!')]
|
||||
],
|
||||
[
|
||||
'remove_text',
|
||||
[createNode('paragraph', 'Hello collaborator!')],
|
||||
[
|
||||
{
|
||||
offset: 11,
|
||||
path: [0, 0],
|
||||
text: 'borator',
|
||||
type: 'remove_text'
|
||||
},
|
||||
{
|
||||
offset: 5,
|
||||
path: [0, 0],
|
||||
text: ' colla',
|
||||
type: 'remove_text'
|
||||
}
|
||||
],
|
||||
[createNode('paragraph', 'Hello!')]
|
||||
],
|
||||
[
|
||||
'insert_node',
|
||||
null,
|
||||
[
|
||||
{
|
||||
type: 'insert_node',
|
||||
path: [1],
|
||||
node: { type: 'paragraph', children: [] }
|
||||
},
|
||||
{
|
||||
type: 'insert_node',
|
||||
path: [1, 0],
|
||||
node: { text: 'Hello collaborator!' }
|
||||
}
|
||||
],
|
||||
[createNode(), createNode('paragraph', 'Hello collaborator!')]
|
||||
],
|
||||
[
|
||||
'merge_node',
|
||||
[
|
||||
createNode('paragraph', 'Hello '),
|
||||
createNode('paragraph', 'collaborator!')
|
||||
],
|
||||
[
|
||||
{
|
||||
path: [1],
|
||||
position: 1,
|
||||
properties: { type: 'paragraph' },
|
||||
target: null,
|
||||
type: 'merge_node'
|
||||
},
|
||||
{
|
||||
path: [0, 1],
|
||||
position: 6,
|
||||
properties: {},
|
||||
target: null,
|
||||
type: 'merge_node'
|
||||
}
|
||||
],
|
||||
[createNode('paragraph', 'Hello collaborator!')]
|
||||
],
|
||||
[
|
||||
'move_node',
|
||||
[
|
||||
createNode('paragraph', 'first'),
|
||||
createNode('paragraph', 'second'),
|
||||
createNode('paragraph', 'third'),
|
||||
createNode('paragraph', 'fourth')
|
||||
],
|
||||
[
|
||||
{
|
||||
newPath: [0],
|
||||
path: [1],
|
||||
type: 'move_node'
|
||||
},
|
||||
{
|
||||
newPath: [3, 0],
|
||||
path: [2, 0],
|
||||
type: 'move_node'
|
||||
}
|
||||
],
|
||||
[
|
||||
createNode('paragraph', 'second'),
|
||||
createNode('paragraph', 'first'),
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [createText('third'), createText('fourth')]
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
'remove_node',
|
||||
[
|
||||
createNode('paragraph', 'first'),
|
||||
createNode('paragraph', 'second'),
|
||||
createNode('paragraph', 'third')
|
||||
],
|
||||
[
|
||||
{
|
||||
path: [1, 0],
|
||||
type: 'remove_node'
|
||||
},
|
||||
{
|
||||
path: [0],
|
||||
type: 'remove_node'
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: []
|
||||
},
|
||||
createNode('paragraph', 'third')
|
||||
]
|
||||
],
|
||||
[
|
||||
'set_node',
|
||||
[
|
||||
createNode('paragraph', 'first', { test: '1234' }),
|
||||
createNode('paragraph', 'second')
|
||||
],
|
||||
[
|
||||
{
|
||||
path: [0],
|
||||
type: 'set_node',
|
||||
properties: {
|
||||
test: '1234'
|
||||
},
|
||||
newProperties: {
|
||||
test: '4567'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: [1, 0],
|
||||
type: 'set_node',
|
||||
newProperties: {
|
||||
data: '4567'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
createNode('paragraph', 'first', { test: '4567' }),
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{
|
||||
data: '4567',
|
||||
text: 'second'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
'split_node',
|
||||
[createNode('paragraph', 'Hello collaborator!')],
|
||||
[
|
||||
{
|
||||
path: [0, 0],
|
||||
position: 6,
|
||||
target: null,
|
||||
type: 'split_node'
|
||||
},
|
||||
{
|
||||
path: [0],
|
||||
position: 1,
|
||||
properties: {
|
||||
type: 'paragraph'
|
||||
},
|
||||
target: 6,
|
||||
type: 'split_node'
|
||||
}
|
||||
],
|
||||
[
|
||||
createNode('paragraph', 'Hello '),
|
||||
createNode('paragraph', 'collaborator!')
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
describe('apply slate operations to Automerge document', () => {
|
||||
transforms.forEach(([op, input, operations, output]) => {
|
||||
it(`apply ${op} operations`, () => {
|
||||
const doc = createDoc(input)
|
||||
|
||||
const updated = Automerge.change(doc, (d: any) => {
|
||||
applySlateOps(d.children, operations as any)
|
||||
})
|
||||
|
||||
const expected = createDoc(output)
|
||||
|
||||
expect(toJS(expected)).toStrictEqual(toJS(updated))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,20 +1,17 @@
|
||||
import { Operation, Operations, SyncDoc } from '../model'
|
||||
import { Operation } from 'slate'
|
||||
|
||||
import node from './node'
|
||||
import mark from './mark'
|
||||
import text from './text'
|
||||
import annotation from './annotation'
|
||||
|
||||
const setSelection = doc => doc
|
||||
const setValue = doc => doc
|
||||
import { SyncDoc } from '../model'
|
||||
import { toJS } from '../utils'
|
||||
|
||||
const setSelection = (doc: any) => doc
|
||||
|
||||
const opType = {
|
||||
...text,
|
||||
...annotation,
|
||||
...node,
|
||||
...mark,
|
||||
set_selection: setSelection
|
||||
// set_value: setValue
|
||||
}
|
||||
|
||||
const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
@@ -22,19 +19,19 @@ const applyOperation = (doc: SyncDoc, op: Operation): SyncDoc => {
|
||||
const applyOp = opType[op.type]
|
||||
|
||||
if (!applyOp) {
|
||||
console.log('operation', op.toJS())
|
||||
throw new TypeError(`Unsupported operation type: ${op.type}!`)
|
||||
}
|
||||
|
||||
return applyOp(doc, op)
|
||||
return applyOp(doc, op as any)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
console.error(e, op, toJS(doc))
|
||||
|
||||
return doc
|
||||
}
|
||||
}
|
||||
|
||||
const applySlateOps = (doc: SyncDoc, operations: Operations) =>
|
||||
operations.reduce(applyOperation, doc)
|
||||
const applySlateOps = (doc: SyncDoc, operations: Operation[]): SyncDoc => {
|
||||
return operations.reduce(applyOperation, doc)
|
||||
}
|
||||
|
||||
export { applyOperation, applySlateOps }
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import { getTarget } from '../path'
|
||||
import { toSync } from '../utils'
|
||||
import { SyncDoc } from '../model'
|
||||
|
||||
import { AddMarkOperation, RemoveMarkOperation, SetMarkOperation } from 'slate'
|
||||
|
||||
const findIndex = (node, mark) =>
|
||||
node.marks.findIndex(m => m.type === mark.type)
|
||||
|
||||
export const addMark = (doc: SyncDoc, op: AddMarkOperation) => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
if (node.object !== 'text') {
|
||||
throw new TypeError('cannot set marks on non-text node')
|
||||
}
|
||||
|
||||
if (findIndex(node, op.mark) < 0) node.marks.push(toSync(op.mark.toJS()))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeMark = (doc: SyncDoc, op: RemoveMarkOperation) => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
if (node.object !== 'text') {
|
||||
throw new TypeError('cannot set marks on non-text node')
|
||||
}
|
||||
|
||||
const index = findIndex(node, op.mark)
|
||||
|
||||
if (index >= 0) node.marks.splice(index, 1)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const setMark = (doc: SyncDoc, op: SetMarkOperation) => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
if (node.object !== 'text') {
|
||||
throw new TypeError('cannot set marks on non-text node')
|
||||
}
|
||||
|
||||
const index = findIndex(node, op.properties)
|
||||
|
||||
if (index === -1) {
|
||||
console.warn('did not find old mark with properties', op.properties)
|
||||
|
||||
if (!op.newProperties.type) {
|
||||
throw new TypeError('no old mark, and new mark missing type')
|
||||
}
|
||||
|
||||
node.marks.push({
|
||||
object: 'mark',
|
||||
type: op.newProperties.type,
|
||||
...op.newProperties
|
||||
})
|
||||
} else {
|
||||
node.marks[index] = {
|
||||
object: 'mark',
|
||||
...node.marks[index],
|
||||
...op.newProperties
|
||||
}
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default {
|
||||
add_mark: addMark,
|
||||
remove_mark: removeMark,
|
||||
set_mark: setMark
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import { SyncDoc } from '../model'
|
||||
|
||||
import {
|
||||
SplitNodeOperation,
|
||||
InsertNodeOperation,
|
||||
MoveNodeOperation,
|
||||
RemoveNodeOperation,
|
||||
MergeNodeOperation,
|
||||
SetNodeOperation
|
||||
} from 'slate'
|
||||
|
||||
import { getTarget, getParent } from '../path'
|
||||
import { toJS, cloneNode, toSync } from '../utils'
|
||||
|
||||
export const insertNode = (doc: SyncDoc, op: InsertNodeOperation): SyncDoc => {
|
||||
const [parent, index] = getParent(doc, op.path)
|
||||
|
||||
if (parent.object === 'text') {
|
||||
throw new TypeError('cannot insert node into text node')
|
||||
}
|
||||
|
||||
parent.nodes.splice(index, 0, toSync(op.node.toJS()))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const moveNode = (doc: SyncDoc, op: MoveNodeOperation): SyncDoc => {
|
||||
const [from, fromIndex] = getParent(doc, op.path)
|
||||
const [to, toIndex] = getParent(doc, op.newPath)
|
||||
|
||||
if (from.object === 'text' || to.object === 'text') {
|
||||
throw new TypeError('cannot move node as child of a text node')
|
||||
}
|
||||
|
||||
to.nodes.splice(toIndex, 0, ...from.nodes.splice(fromIndex, 1))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeNode = (doc: SyncDoc, op: RemoveNodeOperation): SyncDoc => {
|
||||
const [parent, index] = getParent(doc, op.path)
|
||||
|
||||
if (parent.object === 'text') {
|
||||
throw new TypeError('cannot remove node from text node')
|
||||
}
|
||||
|
||||
parent.nodes.splice(index, 1)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const splitNode = (doc: SyncDoc, op: SplitNodeOperation): SyncDoc => {
|
||||
const [parent, index]: [any, number] = getParent(doc, op.path)
|
||||
|
||||
const target = parent.nodes[index]
|
||||
const inject = cloneNode(target)
|
||||
|
||||
if (target.object === 'text') {
|
||||
target.text.length > op.position &&
|
||||
target.text.deleteAt(op.position, target.text.length - op.position)
|
||||
op.position && inject.text.deleteAt(0, op.position)
|
||||
} else {
|
||||
target.nodes.splice(op.position, target.nodes.length - op.position)
|
||||
op.position && inject.nodes.splice(0, op.position)
|
||||
}
|
||||
|
||||
parent.nodes.insertAt(index + 1, inject)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const mergeNode = (doc: SyncDoc, op: MergeNodeOperation) => {
|
||||
const [parent, index]: [any, number] = getParent(doc, op.path)
|
||||
|
||||
const prev = parent.nodes[index - 1]
|
||||
const next = parent.nodes[index]
|
||||
|
||||
if (prev.object === 'text') {
|
||||
prev.text.insertAt(prev.text.length, ...toJS(next.text).split(''))
|
||||
} else {
|
||||
next.nodes.forEach(n => prev.nodes.push(cloneNode(n)))
|
||||
}
|
||||
|
||||
parent.nodes.deleteAt(index, 1)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const setNode = (doc: SyncDoc, op: SetNodeOperation) => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
const { type, data }: any = op.newProperties
|
||||
|
||||
if (type) node.type = type
|
||||
if (node.object !== 'text' && data) node.data = data.toJSON()
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default {
|
||||
insert_node: insertNode,
|
||||
move_node: moveNode,
|
||||
remove_node: removeNode,
|
||||
split_node: splitNode,
|
||||
merge_node: mergeNode,
|
||||
set_node: setNode
|
||||
}
|
||||
15
packages/bridge/src/apply/node/index.ts
Normal file
15
packages/bridge/src/apply/node/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import insertNode from './insertNode'
|
||||
import mergeNode from './mergeNode'
|
||||
import moveNode from './moveNode'
|
||||
import removeNode from './removeNode'
|
||||
import setNode from './setNode'
|
||||
import splitNode from './splitNode'
|
||||
|
||||
export default {
|
||||
insert_node: insertNode,
|
||||
merge_node: mergeNode,
|
||||
move_node: moveNode,
|
||||
remove_node: removeNode,
|
||||
set_node: setNode,
|
||||
split_node: splitNode
|
||||
}
|
||||
19
packages/bridge/src/apply/node/insertNode.ts
Normal file
19
packages/bridge/src/apply/node/insertNode.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { InsertNodeOperation } from 'slate'
|
||||
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getParent, getChildren } from '../../path'
|
||||
import { toSync } from '../../utils'
|
||||
|
||||
const insertNode = (doc: SyncDoc, op: InsertNodeOperation): SyncDoc => {
|
||||
const [parent, index] = getParent(doc, op.path)
|
||||
|
||||
if (parent.text) {
|
||||
throw new TypeError("Can't insert node into text node")
|
||||
}
|
||||
|
||||
getChildren(parent).splice(index, 0, toSync(op.node))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default insertNode
|
||||
24
packages/bridge/src/apply/node/mergeNode.ts
Normal file
24
packages/bridge/src/apply/node/mergeNode.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { MergeNodeOperation, Node } from 'slate'
|
||||
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getParent, getChildren } from '../../path'
|
||||
import { toJS, cloneNode } from '../../utils'
|
||||
|
||||
const mergeNode = (doc: SyncDoc, op: MergeNodeOperation): SyncDoc => {
|
||||
const [parent, index]: [any, number] = getParent(doc, op.path)
|
||||
|
||||
const prev = parent[index - 1] || parent.children[index - 1]
|
||||
const next = parent[index] || parent.children[index]
|
||||
|
||||
if (prev.text) {
|
||||
prev.text.insertAt(prev.text.length, ...toJS(next.text).split(''))
|
||||
} else {
|
||||
getChildren(next).forEach((n: Node) => getChildren(prev).push(cloneNode(n)))
|
||||
}
|
||||
|
||||
getChildren(parent).deleteAt(index, 1)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default mergeNode
|
||||
26
packages/bridge/src/apply/node/moveNode.ts
Normal file
26
packages/bridge/src/apply/node/moveNode.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { MoveNodeOperation } from 'slate'
|
||||
|
||||
import { cloneNode } from '../../utils'
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getParent, getChildren } from '../../path'
|
||||
|
||||
const moveNode = (doc: SyncDoc, op: MoveNodeOperation): SyncDoc => {
|
||||
const [from, fromIndex] = getParent(doc, op.path)
|
||||
const [to, toIndex] = getParent(doc, op.newPath)
|
||||
|
||||
if (from.text || to.text) {
|
||||
throw new TypeError("Can't move node as child of a text node")
|
||||
}
|
||||
|
||||
getChildren(to).splice(
|
||||
toIndex,
|
||||
0,
|
||||
...getChildren(from)
|
||||
.splice(fromIndex, 1)
|
||||
.map(cloneNode)
|
||||
)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default moveNode
|
||||
18
packages/bridge/src/apply/node/removeNode.ts
Normal file
18
packages/bridge/src/apply/node/removeNode.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { RemoveNodeOperation } from 'slate'
|
||||
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getParent, getChildren } from '../../path'
|
||||
|
||||
export const removeNode = (doc: SyncDoc, op: RemoveNodeOperation): SyncDoc => {
|
||||
const [parent, index] = getParent(doc, op.path)
|
||||
|
||||
if (parent.text) {
|
||||
throw new TypeError("Can't remove node from text node")
|
||||
}
|
||||
|
||||
getChildren(parent).splice(index, 1)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default removeNode
|
||||
18
packages/bridge/src/apply/node/setNode.ts
Normal file
18
packages/bridge/src/apply/node/setNode.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { SetNodeOperation } from 'slate'
|
||||
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getTarget } from '../../path'
|
||||
|
||||
const setNode = (doc: SyncDoc, op: SetNodeOperation): SyncDoc => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
const { newProperties } = op
|
||||
|
||||
for (let key in newProperties) {
|
||||
node[key] = newProperties[key]
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default setNode
|
||||
27
packages/bridge/src/apply/node/splitNode.ts
Normal file
27
packages/bridge/src/apply/node/splitNode.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { SplitNodeOperation } from 'slate'
|
||||
|
||||
import { SyncDoc } from '../../model'
|
||||
import { getParent, getChildren } from '../../path'
|
||||
import { cloneNode } from '../../utils'
|
||||
|
||||
const splitNode = (doc: SyncDoc, op: SplitNodeOperation): SyncDoc => {
|
||||
const [parent, index]: [any, number] = getParent(doc, op.path)
|
||||
|
||||
const target = getChildren(parent)[index]
|
||||
const inject = cloneNode(target)
|
||||
|
||||
if (target.text) {
|
||||
target.text.length > op.position &&
|
||||
target.text.deleteAt(op.position, target.text.length - op.position)
|
||||
op.position && inject.text.deleteAt(0, op.position)
|
||||
} else {
|
||||
target.children.splice(op.position, target.children.length - op.position)
|
||||
op.position && inject.children.splice(0, op.position)
|
||||
}
|
||||
|
||||
getChildren(parent).insertAt(index + 1, inject)
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export default splitNode
|
||||
@@ -1,11 +1,12 @@
|
||||
import { SyncDoc } from '../model'
|
||||
import { InsertTextOperation, RemoveTextOperation } from 'slate'
|
||||
|
||||
import { getTarget } from '../path'
|
||||
import { SyncDoc } from '../model'
|
||||
|
||||
export const insertText = (doc: SyncDoc, op: InsertTextOperation): SyncDoc => {
|
||||
const node = getTarget(doc, op.path)
|
||||
|
||||
node.text.insertAt(op.offset, op.text)
|
||||
node.text.insertAt(op.offset, ...op.text.split(''))
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { toSlateOp } from './index'
|
||||
import { createDoc, cloneDoc, createBlockJSON } from '../utils'
|
||||
import { createDoc, cloneDoc, createNode } from '../utils'
|
||||
|
||||
describe('convert operations to slatejs model', () => {
|
||||
it('convert insert operations', () => {
|
||||
const doc1 = createDoc()
|
||||
const doc2 = cloneDoc(doc1)
|
||||
|
||||
const change = Automerge.change(doc1, 'change', d => {
|
||||
d.document.nodes.push(createBlockJSON('paragraph', 'hello!'))
|
||||
d.document.nodes[1].nodes[0].text = 'hello!'
|
||||
const change = Automerge.change(doc1, d => {
|
||||
d.children.push(createNode('paragraph', 'hello!'))
|
||||
d.children[1].children[0].text = 'hello!'
|
||||
})
|
||||
|
||||
const operations = Automerge.diff(doc2, change)
|
||||
@@ -20,12 +20,12 @@ describe('convert operations to slatejs model', () => {
|
||||
{
|
||||
type: 'insert_node',
|
||||
path: [1],
|
||||
node: { object: 'block', type: 'paragraph', nodes: [] }
|
||||
node: { type: 'paragraph', children: [] }
|
||||
},
|
||||
{
|
||||
type: 'insert_node',
|
||||
path: [1, 0],
|
||||
node: { object: 'text', marks: [], text: 'hello!' }
|
||||
node: { text: 'hello!' }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -33,17 +33,17 @@ describe('convert operations to slatejs model', () => {
|
||||
})
|
||||
|
||||
it('convert remove operations', () => {
|
||||
const doc1 = Automerge.change(createDoc(), 'change', d => {
|
||||
d.document.nodes.push(createBlockJSON('paragraph', 'hello!'))
|
||||
d.document.nodes.push(createBlockJSON('paragraph', 'hello twice!'))
|
||||
d.document.nodes[1].nodes[0].text = 'hello!'
|
||||
const doc1 = Automerge.change(createDoc(), d => {
|
||||
d.children.push(createNode('paragraph', 'hello!'))
|
||||
d.children.push(createNode('paragraph', 'hello twice!'))
|
||||
d.children[1].children[0].text = 'hello!'
|
||||
})
|
||||
|
||||
const doc2 = cloneDoc(doc1)
|
||||
|
||||
const change = Automerge.change(doc1, 'change', d => {
|
||||
delete d.document.nodes[1]
|
||||
delete d.document.nodes[0].nodes[0]
|
||||
const change = Automerge.change(doc1, d => {
|
||||
delete d.children[1]
|
||||
delete d.children[0].children[0]
|
||||
})
|
||||
|
||||
const operations = Automerge.diff(doc2, change)
|
||||
@@ -55,14 +55,14 @@ describe('convert operations to slatejs model', () => {
|
||||
type: 'remove_node',
|
||||
path: [1],
|
||||
node: {
|
||||
object: 'text'
|
||||
text: '*'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'remove_node',
|
||||
path: [0, 0],
|
||||
node: {
|
||||
object: 'text'
|
||||
text: '*'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as Automerge from 'automerge'
|
||||
|
||||
const createByType = type => (type === 'map' ? {} : type === 'list' ? [] : '')
|
||||
const createByType = (type: any) =>
|
||||
type === 'map' ? {} : type === 'list' ? [] : ''
|
||||
|
||||
const opCreate = ({ obj, type }: Automerge.Diff, [map, ops]) => {
|
||||
const opCreate = ({ obj, type }: Automerge.Diff, [map, ops]: any) => {
|
||||
map[obj] = createByType(type)
|
||||
|
||||
return [map, ops]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { Node } from 'slate'
|
||||
|
||||
import opInsert from './insert'
|
||||
import opRemove from './remove'
|
||||
@@ -14,11 +15,11 @@ const byAction = {
|
||||
|
||||
const rootKey = '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
const toSlateOp = (ops: Automerge.Diff[], doc) => {
|
||||
const iterate = (acc, op) => {
|
||||
const toSlateOp = (ops: Automerge.Diff[], doc: Automerge.Doc<Node>) => {
|
||||
const iterate = (acc: [any, any[]], op: Automerge.Diff): any => {
|
||||
const action = byAction[op.action]
|
||||
|
||||
const result = action ? action(op, acc) : acc
|
||||
const result = action ? action(op, acc, doc) : acc
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { toSlatePath, toJS } from '../utils/index'
|
||||
|
||||
import { toSlatePath, toJS } from '../utils'
|
||||
|
||||
import { SyncDoc } from '../model'
|
||||
|
||||
const insertTextOp = ({ index, path, value }: Automerge.Diff) => () => ({
|
||||
type: 'insert_text',
|
||||
@@ -9,32 +12,31 @@ const insertTextOp = ({ index, path, value }: Automerge.Diff) => () => ({
|
||||
marks: []
|
||||
})
|
||||
|
||||
const insertNodeOp = ({ value, obj, index, path }: Automerge.Diff) => map => {
|
||||
const ops = []
|
||||
const insertNodeOp = (
|
||||
{ value, obj, index, path }: Automerge.Diff,
|
||||
doc: any
|
||||
) => (map: any) => {
|
||||
const ops: any = []
|
||||
|
||||
const iterate = ({ nodes, ...json }, path) => {
|
||||
const node = nodes ? { ...json, nodes: [] } : json
|
||||
const iterate = ({ children, ...json }: any, path: any) => {
|
||||
const node = children ? { ...json, children: [] } : json
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
}
|
||||
ops.push({
|
||||
type: 'insert_node',
|
||||
path,
|
||||
node
|
||||
})
|
||||
|
||||
nodes && nodes.forEach((n, i) => iterate(n, [...path, i]))
|
||||
children &&
|
||||
children.forEach((n: any, i: any) => {
|
||||
const node = map[n] || Automerge.getObjectById(doc, n)
|
||||
|
||||
iterate((node && toJS(node)) || n, [...path, i])
|
||||
})
|
||||
}
|
||||
|
||||
const source = map[value] || (map[obj] && toJS(map[obj]))
|
||||
const source =
|
||||
map[value] || toJS(map[obj] || Automerge.getObjectById(doc, value))
|
||||
|
||||
source && iterate(source, [...toSlatePath(path), index])
|
||||
|
||||
@@ -46,11 +48,11 @@ const insertByType = {
|
||||
list: insertNodeOp
|
||||
}
|
||||
|
||||
const opInsert = (op: Automerge.Diff, [map, ops]) => {
|
||||
const opInsert = (op: Automerge.Diff, [map, ops]: any, doc: SyncDoc) => {
|
||||
try {
|
||||
const { link, obj, path, index, type, value } = op
|
||||
|
||||
if (link && map[obj]) {
|
||||
if (link && map.hasOwnProperty(obj)) {
|
||||
map[obj].splice(index, 0, map[value] || value)
|
||||
} else if ((type === 'text' || type === 'list') && !path) {
|
||||
map[obj] = map[obj]
|
||||
@@ -62,7 +64,7 @@ const opInsert = (op: Automerge.Diff, [map, ops]) => {
|
||||
} else {
|
||||
const insert = insertByType[type]
|
||||
|
||||
const operation = insert && insert(op, map)
|
||||
const operation = insert && insert(op, doc)
|
||||
|
||||
ops.push(operation)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { toSlatePath, toJS } from '../utils/index'
|
||||
|
||||
import { toSlatePath, toJS } from '../utils'
|
||||
import { getTarget } from '../path'
|
||||
|
||||
const removeTextOp = ({ index, path }: Automerge.Diff) => () => ({
|
||||
type: 'remove_text',
|
||||
path: toSlatePath(path).slice(0, path.length),
|
||||
path: toSlatePath(path).slice(0, path?.length),
|
||||
offset: index,
|
||||
text: '*',
|
||||
marks: []
|
||||
})
|
||||
|
||||
const removeMarkOp = ({ path, index }: Automerge.Diff) => (map, doc) => {
|
||||
const slatePath = toSlatePath(path)
|
||||
const target = getTarget(doc, slatePath)
|
||||
|
||||
return {
|
||||
type: 'remove_mark',
|
||||
path: slatePath,
|
||||
mark: {
|
||||
type: target.marks[index].type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeNodesOp = ({ index, obj, path }: Automerge.Diff) => (map, doc) => {
|
||||
const removeNodeOp = ({ index, obj, path }: Automerge.Diff) => (
|
||||
map: any,
|
||||
doc: any
|
||||
) => {
|
||||
const slatePath = toSlatePath(path)
|
||||
if (!map.hasOwnProperty(obj)) {
|
||||
const target = getTarget(doc, [...slatePath, index] as any)
|
||||
@@ -35,34 +26,20 @@ const removeNodesOp = ({ index, obj, path }: Automerge.Diff) => (map, doc) => {
|
||||
type: 'remove_node',
|
||||
path: slatePath.length ? slatePath.concat(index) : [index],
|
||||
node: {
|
||||
object: 'text'
|
||||
text: '*'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeAnnotationOp = ({ key }: Automerge.Diff) => (map, doc) => {
|
||||
const annotation = toJS(doc.annotations[key])
|
||||
|
||||
if (annotation) {
|
||||
return {
|
||||
type: 'remove_annotation',
|
||||
annotation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const removeByType = {
|
||||
text: removeTextOp,
|
||||
nodes: removeNodesOp,
|
||||
marks: removeMarkOp,
|
||||
annotations: removeAnnotationOp
|
||||
}
|
||||
|
||||
const opRemove = (op: Automerge.Diff, [map, ops]) => {
|
||||
const opRemove = (op: Automerge.Diff, [map, ops]: any) => {
|
||||
try {
|
||||
const { index, path, obj } = op
|
||||
const { index, path, obj, type } = op
|
||||
|
||||
if (map.hasOwnProperty(obj) && op.type !== 'text') {
|
||||
if (
|
||||
map.hasOwnProperty(obj) &&
|
||||
typeof map[obj] !== 'string' &&
|
||||
type !== 'text'
|
||||
) {
|
||||
map[obj].splice(index, 1)
|
||||
|
||||
return [map, ops]
|
||||
@@ -70,7 +47,11 @@ const opRemove = (op: Automerge.Diff, [map, ops]) => {
|
||||
|
||||
if (!path) return [map, ops]
|
||||
|
||||
const fn = removeByType[path[path.length - 1]]
|
||||
const key = path[path.length - 1]
|
||||
|
||||
if (key === 'cursors') return [map, ops]
|
||||
|
||||
const fn = key === 'text' ? removeTextOp : removeNodeOp
|
||||
|
||||
return [map, [...ops, fn(op)]]
|
||||
} catch (e) {
|
||||
|
||||
@@ -1,62 +1,31 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { toSlatePath, toJS } from '../utils/index'
|
||||
|
||||
const setDataOp = ({ path, value }: Automerge.Diff) => map => ({
|
||||
type: 'set_node',
|
||||
path: toSlatePath(path),
|
||||
properties: {},
|
||||
newProperties: {
|
||||
data: map[value]
|
||||
}
|
||||
})
|
||||
import { toSlatePath, toJS } from '../utils'
|
||||
|
||||
const AnnotationSetOp = ({ key, value }: Automerge.Diff) => (map, doc) => {
|
||||
if (!doc.annotations) {
|
||||
doc.annotations = {}
|
||||
}
|
||||
|
||||
let op
|
||||
|
||||
/**
|
||||
* Looks like set_annotation option is broken, temporary disabled
|
||||
*/
|
||||
|
||||
// if (!doc.annotations[key]) {
|
||||
op = {
|
||||
type: 'add_annotation',
|
||||
annotation: map[value]
|
||||
}
|
||||
// } else {
|
||||
// op = {
|
||||
// type: 'set_annotation',
|
||||
// properties: toJS(doc.annotations[key]),
|
||||
// newProperties: map[value]
|
||||
// }
|
||||
// }
|
||||
|
||||
return op
|
||||
}
|
||||
|
||||
const setByType = {
|
||||
data: setDataOp
|
||||
}
|
||||
|
||||
const opSet = (op: Automerge.Diff, [map, ops]) => {
|
||||
const { link, value, path, obj, key } = op
|
||||
try {
|
||||
const set = setByType[key]
|
||||
|
||||
if (set && path) {
|
||||
ops.push(set(op))
|
||||
} else if (map[obj]) {
|
||||
map[obj][key] = link ? map[value] : value
|
||||
const setDataOp = (
|
||||
{ key = '', obj, path, value }: Automerge.Diff,
|
||||
doc: any
|
||||
) => (map: any) => {
|
||||
return {
|
||||
type: 'set_node',
|
||||
path: toSlatePath(path),
|
||||
properties: {
|
||||
[key]: Automerge.getObjectById(doc, obj)?.[key]
|
||||
},
|
||||
newProperties: {
|
||||
[key]: value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotation
|
||||
*/
|
||||
if (path && path.length === 1 && path[0] === 'annotations') {
|
||||
ops.push(AnnotationSetOp(op))
|
||||
const opSet = (op: Automerge.Diff, [map, ops]: any, doc: any) => {
|
||||
const { link, value, path, obj, key } = op
|
||||
|
||||
try {
|
||||
if (path && path[0] !== 'cursors') {
|
||||
ops.push(setDataOp(op, doc))
|
||||
} else if (map[obj]) {
|
||||
map[obj][key as any] = link ? map[value] : value
|
||||
}
|
||||
|
||||
return [map, ops]
|
||||
|
||||
@@ -1,68 +1,35 @@
|
||||
import { Selection } from 'slate'
|
||||
import merge from 'lodash/merge'
|
||||
import { Operation, Range } from 'slate'
|
||||
|
||||
import { toJS } from '../utils'
|
||||
import { SyncDoc, CursorKey } from '../model'
|
||||
import { CursorData } from '../model'
|
||||
|
||||
export const setCursor = (
|
||||
doc: SyncDoc,
|
||||
key: CursorKey,
|
||||
selection: Selection,
|
||||
type,
|
||||
data
|
||||
id: string,
|
||||
selection: Range | null,
|
||||
doc: any,
|
||||
operations: Operation[],
|
||||
cursorData: CursorData
|
||||
) => {
|
||||
if (!doc) return
|
||||
const cursorOps = operations.filter(op => op.type === 'set_selection')
|
||||
|
||||
if (!doc.annotations) {
|
||||
doc.annotations = {}
|
||||
}
|
||||
if (!doc.cursors) doc.cursors = {}
|
||||
|
||||
if (!doc.annotations[key]) {
|
||||
doc.annotations[key] = {
|
||||
key,
|
||||
type,
|
||||
data: {}
|
||||
}
|
||||
}
|
||||
const newCursor = cursorOps[cursorOps.length - 1]?.newProperties || {}
|
||||
|
||||
const annotation = toJS(doc.annotations[key])
|
||||
|
||||
annotation.focus = selection.end.toJSON()
|
||||
annotation.anchor = selection.start.toJSON()
|
||||
|
||||
annotation.data = merge(annotation.data, data, {
|
||||
isBackward: selection.isBackward,
|
||||
targetPath: selection.isBackward
|
||||
? annotation.anchor.path
|
||||
: annotation.focus.path
|
||||
})
|
||||
|
||||
doc.annotations[key] = annotation
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const removeCursor = (doc: SyncDoc, key: CursorKey) => {
|
||||
if (doc.annotations && doc.annotations[key]) {
|
||||
delete doc.annotations[key]
|
||||
}
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const cursorOpFilter = (ops, type: string) =>
|
||||
ops.filter(op => {
|
||||
if (op.type === 'set_annotation') {
|
||||
return !(
|
||||
(op.properties && op.properties.type === type) ||
|
||||
(op.newProperties && op.newProperties.type === type)
|
||||
if (selection) {
|
||||
doc.cursors[id] = JSON.stringify(
|
||||
Object.assign(
|
||||
(doc.cursors[id] && JSON.parse(doc.cursors[id])) || {},
|
||||
newCursor,
|
||||
selection,
|
||||
{
|
||||
...cursorData,
|
||||
isForward: Boolean(newCursor.focus)
|
||||
}
|
||||
)
|
||||
} else if (
|
||||
op.type === 'add_annotation' ||
|
||||
op.type === 'remove_annotation'
|
||||
) {
|
||||
return op.annotation.type !== type
|
||||
}
|
||||
)
|
||||
} else {
|
||||
delete doc.cursors[id]
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
return doc
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { ValueJSON } from 'slate'
|
||||
|
||||
export type CursorKey = string
|
||||
|
||||
export interface SyncDoc extends ValueJSON {}
|
||||
@@ -1,2 +1,23 @@
|
||||
export * from './automerge'
|
||||
export * from './slate'
|
||||
import Automerge from 'automerge'
|
||||
import { Node, Range } from 'slate'
|
||||
|
||||
export type SyncDoc = Automerge.Doc<Node & Cursors>
|
||||
|
||||
export type CollabActionType = 'operation' | 'document'
|
||||
|
||||
export interface CollabAction {
|
||||
type: CollabActionType
|
||||
payload: any
|
||||
}
|
||||
|
||||
export interface CursorData {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface Cursor extends Range, CursorData {
|
||||
isForward: boolean
|
||||
}
|
||||
|
||||
export interface Cursors {
|
||||
[key: string]: Cursor
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { Operation, NodeJSON } from 'slate'
|
||||
import { List } from 'immutable'
|
||||
import { Operation, Path, NodeEntry } from 'slate'
|
||||
|
||||
export type Operations = List<Operation>
|
||||
export type SyncNode = NodeJSON
|
||||
export type Path = List<number>
|
||||
export type SyncNode = NodeEntry
|
||||
|
||||
export { Operation }
|
||||
export { Operation, Path }
|
||||
|
||||
@@ -1,38 +1,42 @@
|
||||
import { SyncDoc, Path } from '../model'
|
||||
import { NodeJSON } from 'slate'
|
||||
import { Node, Path } from 'slate'
|
||||
|
||||
export const isTree = (node: NodeJSON): any => node && node.object !== 'text'
|
||||
import { SyncDoc } from '../model'
|
||||
|
||||
export const isTree = (node: Node): boolean => Boolean(node?.children)
|
||||
|
||||
export const getTarget = (doc: SyncDoc, path: Path) => {
|
||||
const iterate = (current: any, idx: number) => {
|
||||
if (!isTree(current) || !current.nodes) {
|
||||
if (!(isTree(current) || current[idx])) {
|
||||
throw new TypeError(
|
||||
`path ${path.toString()} does not match tree ${JSON.stringify(current)}`
|
||||
)
|
||||
}
|
||||
|
||||
return current.nodes[idx]
|
||||
return current[idx] || current?.children[idx]
|
||||
}
|
||||
|
||||
return path.reduce(iterate, doc.document)
|
||||
return path.reduce(iterate, doc)
|
||||
}
|
||||
|
||||
export const getParentPath = (
|
||||
path: Path,
|
||||
level: number = 1
|
||||
): [number, Path] => {
|
||||
if (level > path.size) {
|
||||
if (level > path.length) {
|
||||
throw new TypeError('requested ancestor is higher than root')
|
||||
}
|
||||
|
||||
return [path.get(path.size - level), path.slice(0, path.size - level) as Path]
|
||||
return [path[path.length - level], path.slice(0, path.length - level)]
|
||||
}
|
||||
|
||||
export const getParent = (
|
||||
doc: SyncDoc,
|
||||
path: Path,
|
||||
level = 1
|
||||
): [NodeJSON, number] => {
|
||||
): [any, number] => {
|
||||
const [idx, parentPath] = getParentPath(path, level)
|
||||
|
||||
return [getTarget(doc, parentPath), idx]
|
||||
}
|
||||
|
||||
export const getChildren = (node: Node) => node.children || node
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import toSync from './toSync'
|
||||
import hexGen from './hexGen'
|
||||
|
||||
import { CollabAction } from '../model'
|
||||
|
||||
export * from './testUtils'
|
||||
|
||||
const toJS = node => {
|
||||
const toJS = (node: any) => {
|
||||
try {
|
||||
return JSON.parse(JSON.stringify(node))
|
||||
} catch (e) {
|
||||
@@ -12,8 +14,13 @@ const toJS = node => {
|
||||
}
|
||||
}
|
||||
|
||||
const cloneNode = node => toSync(toJS(node))
|
||||
const cloneNode = (node: any) => toSync(toJS(node))
|
||||
|
||||
const toSlatePath = path => (path ? path.filter(d => Number.isInteger(d)) : [])
|
||||
const toSlatePath = (path: any) =>
|
||||
path ? path.filter((d: any) => Number.isInteger(d)) : []
|
||||
|
||||
export { toSync, toJS, toSlatePath, hexGen, cloneNode }
|
||||
const toCollabAction = (type: any, fn: (action: CollabAction) => void) => (
|
||||
payload: any
|
||||
) => fn({ type, payload })
|
||||
|
||||
export { toSync, toJS, toSlatePath, hexGen, cloneNode, toCollabAction }
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
import * as Automerge from 'automerge'
|
||||
import { TextJSON } from 'slate'
|
||||
|
||||
export const createTextJSON = (text: string = ''): TextJSON => ({
|
||||
object: 'text',
|
||||
marks: [],
|
||||
import { toSync } from '../'
|
||||
|
||||
import { Node } from 'slate'
|
||||
|
||||
export const createText = (text: string = '') => ({
|
||||
text
|
||||
})
|
||||
|
||||
export const createBlockJSON = (
|
||||
export const createNode = (
|
||||
type: string = 'paragraph',
|
||||
text: string = ''
|
||||
text: string = '',
|
||||
data?: { [key: string]: any }
|
||||
) => ({
|
||||
object: 'block',
|
||||
type,
|
||||
nodes: [createTextJSON(text)]
|
||||
children: [createText(text)],
|
||||
...data
|
||||
})
|
||||
|
||||
export const createValueJSON = () => ({
|
||||
document: {
|
||||
nodes: [createBlockJSON()]
|
||||
}
|
||||
export const createValue = (children?: any): { children: Node[] } => ({
|
||||
children: children || [createNode()]
|
||||
})
|
||||
|
||||
export const createDoc = () => Automerge.from(createValueJSON())
|
||||
export const createDoc = (children?: any) =>
|
||||
Automerge.from(toSync(createValue(children)))
|
||||
|
||||
export const cloneDoc = doc => Automerge.change(doc, '', d => d)
|
||||
export const cloneDoc = (doc: any) => Automerge.change(doc, '', d => d)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as Automerge from 'automerge'
|
||||
|
||||
const toSync = node => {
|
||||
const toSync = (node: any) => {
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
@@ -10,20 +10,10 @@ const toSync = node => {
|
||||
...node,
|
||||
text: new Automerge.Text(node.text)
|
||||
}
|
||||
} else if (node.nodes) {
|
||||
} else if (node.children) {
|
||||
return {
|
||||
...node,
|
||||
nodes: node.nodes.map(toSync)
|
||||
}
|
||||
} else if (node.leaves) {
|
||||
return {
|
||||
...node,
|
||||
leaves: node.leaves.map(toSync)
|
||||
}
|
||||
} else if (node.document) {
|
||||
return {
|
||||
...node,
|
||||
document: toSync(node.document)
|
||||
children: node.children.map(toSync)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user