mirror of
				https://github.com/cudr/slate-collaborative.git
				synced 2025-06-13 12:54:04 +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) => { | ||||||
|  |   const { index, path, obj } = op | ||||||
|  | 
 | ||||||
|  |   const slatePath = toSlatePath(path).slice(0, path?.length) | ||||||
|  | 
 | ||||||
|  |   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', |     type: 'remove_text', | ||||||
|   path: toSlatePath(path).slice(0, path?.length), |     path: slatePath, | ||||||
|     offset: index, |     offset: index, | ||||||
|   text: '*', |     text, | ||||||
|     marks: [] |     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