mirror of
https://github.com/cudr/slate-collaborative.git
synced 2024-10-27 20:34:06 +00:00
Preserve external history option (#22)
* feat: handle node text in remove_text op * feat: add preserveExternalHistory option
This commit is contained in:
parent
b49cf33464
commit
55b1b2ca5a
@ -31,6 +31,7 @@ Check [detailed example](https://github.com/cudr/slate-collaborative/blob/master
|
|||||||
onConnect?: () => void // connect callback
|
onConnect?: () => void // connect callback
|
||||||
onDisconnect?: () => void // disconnect callback
|
onDisconnect?: () => void // disconnect callback
|
||||||
onError?: (reason: string) => void // error callback
|
onError?: (reason: string) => void // error callback
|
||||||
|
preserveExternalHistory?: boolean // preserve slate-history operations form other clients
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -9,7 +9,9 @@ export const insertText = (
|
|||||||
): SyncValue => {
|
): SyncValue => {
|
||||||
const node = getTarget(doc, op.path)
|
const node = getTarget(doc, op.path)
|
||||||
|
|
||||||
node.text.insertAt(op.offset, ...op.text.split(''))
|
const offset = Math.min(node.text.length, op.offset)
|
||||||
|
|
||||||
|
node.text.insertAt(offset, ...op.text.split(''))
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
@ -20,7 +22,9 @@ export const removeText = (
|
|||||||
): SyncValue => {
|
): SyncValue => {
|
||||||
const node = getTarget(doc, op.path)
|
const node = getTarget(doc, op.path)
|
||||||
|
|
||||||
node.text.deleteAt(op.offset, op.text.length)
|
const offset = Math.min(node.text.length, op.offset)
|
||||||
|
|
||||||
|
node.text.deleteAt(offset, op.text.length)
|
||||||
|
|
||||||
return doc
|
return doc
|
||||||
}
|
}
|
||||||
|
@ -54,15 +54,13 @@ describe('convert operations to slatejs model', () => {
|
|||||||
{
|
{
|
||||||
type: 'remove_node',
|
type: 'remove_node',
|
||||||
path: [1],
|
path: [1],
|
||||||
node: {
|
node: createNode('paragraph', 'hello twice!')
|
||||||
text: '*'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'remove_node',
|
type: 'remove_node',
|
||||||
path: [0, 0],
|
path: [0, 0],
|
||||||
node: {
|
node: {
|
||||||
text: '*'
|
children: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as Automerge from 'automerge'
|
import * as Automerge from 'automerge'
|
||||||
|
|
||||||
const createByType = (type: any) =>
|
const createByType = (type: Automerge.CollectionType) =>
|
||||||
type === 'map' ? {} : type === 'list' ? [] : ''
|
type === 'map' ? {} : type === 'list' ? [] : ''
|
||||||
|
|
||||||
const opCreate = ({ obj, type }: Automerge.Diff, [map, ops]: any) => {
|
const opCreate = ({ obj, type }: Automerge.Diff, [map, ops]: any) => {
|
||||||
|
@ -5,6 +5,8 @@ import opRemove from './remove'
|
|||||||
import opSet from './set'
|
import opSet from './set'
|
||||||
import opCreate from './create'
|
import opCreate from './create'
|
||||||
|
|
||||||
|
import { toJS } from '../utils'
|
||||||
|
|
||||||
import { SyncDoc } from '../model'
|
import { SyncDoc } from '../model'
|
||||||
|
|
||||||
const byAction = {
|
const byAction = {
|
||||||
@ -32,7 +34,9 @@ const toSlateOp = (ops: Automerge.Diff[], doc: SyncDoc) => {
|
|||||||
[]
|
[]
|
||||||
])
|
])
|
||||||
|
|
||||||
return defer.flatMap(op => op(tempTree, doc)).filter(op => op)
|
const tempDoc = toJS(doc)
|
||||||
|
|
||||||
|
return defer.flatMap(op => op(tempTree, tempDoc)).filter(op => op)
|
||||||
}
|
}
|
||||||
|
|
||||||
export { toSlateOp }
|
export { toSlateOp }
|
||||||
|
@ -1,33 +1,56 @@
|
|||||||
import * as Automerge from 'automerge'
|
import * as Automerge from 'automerge'
|
||||||
|
import { Element } from 'slate'
|
||||||
|
|
||||||
import { toSlatePath, toJS } from '../utils'
|
import { toSlatePath, toJS } from '../utils'
|
||||||
import { getTarget } from '../path'
|
import { getTarget } from '../path'
|
||||||
|
|
||||||
const removeTextOp = ({ index, path }: Automerge.Diff) => () => ({
|
const removeTextOp = (op: Automerge.Diff) => (map: any, doc: Element) => {
|
||||||
type: 'remove_text',
|
const { index, path, obj } = op
|
||||||
path: toSlatePath(path).slice(0, path?.length),
|
|
||||||
offset: index,
|
const slatePath = toSlatePath(path).slice(0, path?.length)
|
||||||
text: '*',
|
|
||||||
marks: []
|
let node
|
||||||
})
|
|
||||||
|
try {
|
||||||
|
node = getTarget(doc, slatePath) || map[obj]
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e, op, doc)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof index !== 'number') return
|
||||||
|
|
||||||
|
const text = node?.text[index] || '*'
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
node.text = node.text?.slice(0, index) + node.text?.slice(index + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'remove_text',
|
||||||
|
path: slatePath,
|
||||||
|
offset: index,
|
||||||
|
text,
|
||||||
|
marks: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const removeNodeOp = ({ index, obj, path }: Automerge.Diff) => (
|
const removeNodeOp = ({ index, obj, path }: Automerge.Diff) => (
|
||||||
map: any,
|
map: any,
|
||||||
doc: any
|
doc: Element
|
||||||
) => {
|
) => {
|
||||||
const slatePath = toSlatePath(path)
|
const slatePath = toSlatePath(path)
|
||||||
if (!map.hasOwnProperty(obj)) {
|
|
||||||
const target = getTarget(doc, [...slatePath, index] as any)
|
|
||||||
|
|
||||||
|
const parent = getTarget(doc, slatePath)
|
||||||
|
const target = parent?.children[index as number] || { children: [] }
|
||||||
|
|
||||||
|
if (!map.hasOwnProperty(obj)) {
|
||||||
map[obj] = target
|
map[obj] = target
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'remove_node',
|
type: 'remove_node',
|
||||||
path: slatePath.length ? slatePath.concat(index) : [index],
|
path: slatePath.length ? slatePath.concat(index) : [index],
|
||||||
node: {
|
node: target
|
||||||
text: '*'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Node, Path } from 'slate'
|
import { Element, Node, Path } from 'slate'
|
||||||
|
|
||||||
import { SyncValue } from '../model'
|
import { SyncValue } from '../model'
|
||||||
|
|
||||||
export const isTree = (node: Node): boolean => Boolean(node?.children)
|
export const isTree = (node: Node): boolean => Boolean(node?.children)
|
||||||
|
|
||||||
export const getTarget = (doc: SyncValue, path: Path) => {
|
export const getTarget = (doc: SyncValue | Element, path: Path) => {
|
||||||
const iterate = (current: any, idx: number) => {
|
const iterate = (current: any, idx: number) => {
|
||||||
if (!(isTree(current) || current[idx])) {
|
if (!(isTree(current) || current[idx])) {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
@ -30,7 +30,7 @@ export const getParentPath = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getParent = (
|
export const getParent = (
|
||||||
doc: SyncValue,
|
doc: SyncValue | Element,
|
||||||
path: Path,
|
path: Path,
|
||||||
level = 1
|
level = 1
|
||||||
): [any, number] => {
|
): [any, number] => {
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
"@slate-collaborative/bridge": "^0.6.7",
|
"@slate-collaborative/bridge": "^0.6.7",
|
||||||
"automerge": "0.14.0",
|
"automerge": "0.14.0",
|
||||||
"slate": "0.58.3",
|
"slate": "0.58.3",
|
||||||
|
"slate-history": "0.58.3",
|
||||||
"socket.io-client": "^2.3.0",
|
"socket.io-client": "^2.3.0",
|
||||||
"typescript": "^3.8.3"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Automerge from 'automerge'
|
import Automerge from 'automerge'
|
||||||
|
|
||||||
import { Editor, Operation } from 'slate'
|
import { Editor, Operation } from 'slate'
|
||||||
|
import { HistoryEditor } from 'slate-history'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
toJS,
|
toJS,
|
||||||
@ -111,7 +112,8 @@ export const AutomergeEditor = {
|
|||||||
applyOperation: (
|
applyOperation: (
|
||||||
e: AutomergeEditor,
|
e: AutomergeEditor,
|
||||||
docId: string,
|
docId: string,
|
||||||
data: Automerge.Message
|
data: Automerge.Message,
|
||||||
|
preserveExternalHistory?: boolean
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const current: any = e.docSet.getDoc(docId)
|
const current: any = e.docSet.getDoc(docId)
|
||||||
@ -126,12 +128,16 @@ export const AutomergeEditor = {
|
|||||||
e.isRemote = true
|
e.isRemote = true
|
||||||
|
|
||||||
Editor.withoutNormalizing(e, () => {
|
Editor.withoutNormalizing(e, () => {
|
||||||
slateOps.forEach((o: Operation) => {
|
if (HistoryEditor.isHistoryEditor(e) && !preserveExternalHistory) {
|
||||||
e.apply(o)
|
HistoryEditor.withoutSaving(e, () => {
|
||||||
})
|
slateOps.forEach((o: Operation) => e.apply(o))
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
slateOps.forEach((o: Operation) => e.apply(o))
|
||||||
|
}
|
||||||
|
|
||||||
e.onCursor && e.onCursor(updated.cursors)
|
e.onCursor && e.onCursor(updated.cursors)
|
||||||
|
})
|
||||||
|
|
||||||
Promise.resolve().then(_ => (e.isRemote = false))
|
Promise.resolve().then(_ => (e.isRemote = false))
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import { CursorData, CollabAction } from '@slate-collaborative/bridge'
|
|||||||
export interface AutomergeOptions {
|
export interface AutomergeOptions {
|
||||||
docId: string
|
docId: string
|
||||||
cursorData?: CursorData
|
cursorData?: CursorData
|
||||||
|
preserveExternalHistory?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +24,7 @@ const withAutomerge = <T extends Editor>(
|
|||||||
|
|
||||||
const { onChange } = e
|
const { onChange } = e
|
||||||
|
|
||||||
const { docId, cursorData } = options || {}
|
const { docId, cursorData, preserveExternalHistory } = options || {}
|
||||||
|
|
||||||
e.docSet = new Automerge.DocSet()
|
e.docSet = new Automerge.DocSet()
|
||||||
|
|
||||||
@ -73,11 +74,9 @@ const withAutomerge = <T extends Editor>(
|
|||||||
|
|
||||||
if (!e.isRemote) {
|
if (!e.isRemote) {
|
||||||
AutomergeEditor.applySlateOps(e, docId, operations, cursorData)
|
AutomergeEditor.applySlateOps(e, docId, operations, cursorData)
|
||||||
|
|
||||||
|
onChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange()
|
|
||||||
|
|
||||||
// console.log('e', e.children)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,7 +96,7 @@ const withAutomerge = <T extends Editor>(
|
|||||||
e.receiveOperation = data => {
|
e.receiveOperation = data => {
|
||||||
if (docId !== data.docId) return
|
if (docId !== data.docId) return
|
||||||
|
|
||||||
AutomergeEditor.applyOperation(e, docId, data)
|
AutomergeEditor.applyOperation(e, docId, data, preserveExternalHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
Loading…
Reference in New Issue
Block a user