feat: update to slate 0.5x (#10)

Update Slate-Collaboration to be compatible with Slate 0.5x versions.
This commit is contained in:
George
2020-05-10 16:50:12 +03:00
committed by GitHub
parent fee0098c3d
commit 0fd9390a99
79 changed files with 2017 additions and 1596 deletions

View File

@@ -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
}

View 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))
})
})
})

View File

@@ -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 }

View File

@@ -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
}

View File

@@ -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
}

View 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
}

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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
}