mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
fix: index error and set of root children error
This commit is contained in:
parent
0b1e4b66cb
commit
e0b342585a
@ -1,4 +1,5 @@
|
|||||||
import * as Automerge from 'automerge'
|
import * as Automerge from 'automerge'
|
||||||
|
import { Operation, Node } from 'slate'
|
||||||
|
|
||||||
import { toSlatePath, toJS } from '../utils'
|
import { toSlatePath, toJS } from '../utils'
|
||||||
import { rootKey } from './constants'
|
import { rootKey } from './constants'
|
||||||
@ -19,44 +20,57 @@ const setDataOp = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setChildren = (op: Automerge.Diff, doc: any) => (map: any) => {
|
||||||
|
const { value } = op
|
||||||
|
|
||||||
|
const ops: Operation[] = []
|
||||||
|
|
||||||
|
let newValue = map[value]
|
||||||
|
if (!newValue) {
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
|
||||||
|
// First remove all existing child nodes
|
||||||
|
for (let i = doc.children.length - 1; i >= 0; i--) {
|
||||||
|
ops.push({
|
||||||
|
type: 'remove_node',
|
||||||
|
path: [i],
|
||||||
|
node: doc.children[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then add all the newly defined nodes
|
||||||
|
const newChildren: Node[] = newValue
|
||||||
|
newChildren.forEach((child, index) => {
|
||||||
|
ops.push({
|
||||||
|
type: 'insert_node',
|
||||||
|
path: [index],
|
||||||
|
node: child
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return ops
|
||||||
|
}
|
||||||
|
|
||||||
const opSet = (op: Automerge.Diff, [map, ops]: any, doc: any) => {
|
const opSet = (op: Automerge.Diff, [map, ops]: any, doc: any) => {
|
||||||
const { link, value, path, obj, key } = op
|
const { link, value, path, obj, key } = op
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// We can ignore any root level cursor updates since those
|
if (map[obj]) {
|
||||||
// will not correspond to any slate operations
|
map[obj][key as any] = link ? map[value] : value
|
||||||
if (obj === rootKey && key === 'cursors') {
|
}
|
||||||
|
|
||||||
|
// ignore all cursor updates since those do not need to translate into
|
||||||
|
// slate operations
|
||||||
|
if (path && path.includes('cursors')) {
|
||||||
return [map, ops]
|
return [map, ops]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle updates received for the root children array
|
// Handle updates received for the root children array
|
||||||
if (obj === rootKey && key === 'children' && map[value]) {
|
if (obj === rootKey && key === 'children') {
|
||||||
// First remove all existing child nodes
|
ops.push(setChildren(op, doc))
|
||||||
for (let i = doc.children.length - 1; i >= 0; i--) {
|
} else if (path && obj !== rootKey && !path.includes('cursors')) {
|
||||||
ops.push((map: any) => ({
|
|
||||||
type: 'remove_node',
|
|
||||||
path: [i],
|
|
||||||
node: doc.children[i]
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Then add all the newly defined nodes
|
|
||||||
const newChildren: Node[] = map[value]
|
|
||||||
newChildren.forEach((child, index) => {
|
|
||||||
ops.push((map: any) => ({
|
|
||||||
type: 'insert_node',
|
|
||||||
path: [index],
|
|
||||||
node: child
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
return [map, ops]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (path && path[0] !== 'cursors') {
|
|
||||||
ops.push(setDataOp(op, doc))
|
ops.push(setDataOp(op, doc))
|
||||||
} else if (map[obj]) {
|
|
||||||
map[obj][key as any] = link ? map[value] : value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [map, ops]
|
return [map, ops]
|
||||||
|
@ -6,7 +6,11 @@ export const isTree = (node: Node): boolean => Boolean(node?.children)
|
|||||||
|
|
||||||
export const getTarget = (doc: SyncValue | Element, path: Path) => {
|
export const getTarget = (doc: SyncValue | Element, path: Path) => {
|
||||||
const iterate = (current: any, idx: number) => {
|
const iterate = (current: any, idx: number) => {
|
||||||
if (current === null || !(isTree(current) || current[idx])) {
|
if (current === null || current === undefined) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(isTree(current) || current[idx])) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import Automerge from 'automerge'
|
|||||||
import { createServer } from 'http'
|
import { createServer } from 'http'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import isEqual from 'lodash/isEqual'
|
import isEqual from 'lodash/isEqual'
|
||||||
import { createEditor, Node, Transforms } from 'slate'
|
import { createEditor, Editor, Node, Transforms } from 'slate'
|
||||||
import { SyncDoc, toJS, toSlateOp } from '@hiveteams/collab-bridge'
|
import { createDoc, SyncDoc, toJS, toSlateOp } from '@hiveteams/collab-bridge'
|
||||||
import AutomergeCollaboration from '@hiveteams/collab-backend/lib/AutomergeCollaboration'
|
import AutomergeCollaboration from '@hiveteams/collab-backend/lib/AutomergeCollaboration'
|
||||||
import withIOCollaboration from './withIOCollaboration'
|
import withIOCollaboration from './withIOCollaboration'
|
||||||
import { AutomergeOptions, SocketIOPluginOptions } from './interfaces'
|
import { AutomergeOptions, SocketIOPluginOptions } from './interfaces'
|
||||||
@ -213,6 +213,23 @@ describe('automerge editor client tests', () => {
|
|||||||
toSlateOp(operations, currentDoc)
|
toSlateOp(operations, currentDoc)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not throw index error', () => {
|
||||||
|
// Read from our test json file for the deep tree error
|
||||||
|
// This allows us to easily reproduce real production errors
|
||||||
|
// and create test cases that resolve those errors
|
||||||
|
const rawData = fs.readFileSync(
|
||||||
|
`${__dirname}/test-json/index-error.json`,
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
const parsedData = JSON.parse(rawData)
|
||||||
|
const { current, operations } = parsedData
|
||||||
|
const currentDoc = Automerge.load<SyncDoc>(current)
|
||||||
|
|
||||||
|
// ensure no errors throw when removing a deep tree node
|
||||||
|
// that has already been removed
|
||||||
|
toSlateOp(operations, currentDoc)
|
||||||
|
})
|
||||||
|
|
||||||
it('should update children for a root level children operation', async () => {
|
it('should update children for a root level children operation', async () => {
|
||||||
const editor = await createCollabEditor()
|
const editor = await createCollabEditor()
|
||||||
|
|
||||||
@ -233,6 +250,30 @@ describe('automerge editor client tests', () => {
|
|||||||
expect(Node.string(editor.children[1])).toEqual('nodes')
|
expect(Node.string(editor.children[1])).toEqual('nodes')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('set node for children with missing value should not throw error', () => {
|
||||||
|
const operations: Automerge.Diff[] = [
|
||||||
|
{
|
||||||
|
action: 'set',
|
||||||
|
type: 'map',
|
||||||
|
obj: '00000000-0000-0000-0000-000000000000',
|
||||||
|
key: 'children',
|
||||||
|
path: [],
|
||||||
|
value: '6c7bf8a5-d0e0-4b08-a4a2-32df65b807e5',
|
||||||
|
link: true,
|
||||||
|
conflicts: [
|
||||||
|
{
|
||||||
|
actor: '8c5d5ada-3db9-4189-9e04-2e7c101d057d',
|
||||||
|
value: 'e198d171-a00a-4d5c-a597-c0ff35a7f639',
|
||||||
|
link: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const slateOps = toSlateOp(operations, createDoc())
|
||||||
|
expect(slateOps.length).toEqual(0)
|
||||||
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
collabBackend.destroy()
|
collabBackend.destroy()
|
||||||
server.close()
|
server.close()
|
||||||
|
Loading…
Reference in New Issue
Block a user