substance
Version:
Substance is a JavaScript library for web-based content editing. It provides building blocks for realizing custom text editors and web-based publishing systems.
109 lines (101 loc) • 3 kB
JavaScript
import { isArray, isEqual } from '../util'
import ObjectOperation from './ObjectOperation'
/*
Transforms change A with B, as if A was done before B.
A' and B' can be used to update two clients to get to the
same document content.
/ A - B' \
v_n v_n+1
\ B - A' /
*/
export function transformDocumentChange(A, B) {
_transformInplaceBatch(A, B)
}
export function transformSelection(sel, a) {
let newSel = sel.clone()
let hasChanged = _transformSelectionInplace(newSel, a)
if (hasChanged) {
return newSel
} else {
return sel
}
}
function _transformInplaceSingle(a, b) {
for (let i = 0; i < a.ops.length; i++) {
let a_op = a.ops[i]
for (let j = 0; j < b.ops.length; j++) {
let b_op = b.ops[j]
// ATTENTION: order of arguments is important.
// First argument is the dominant one, i.e. it is treated as if it was applied before
ObjectOperation.transform(a_op, b_op, {inplace: true})
}
}
if (a.before) {
_transformSelectionInplace(a.before.selection, b)
}
if (a.after) {
_transformSelectionInplace(a.after.selection, b)
}
if (b.before) {
_transformSelectionInplace(b.before.selection, a)
}
if (b.after) {
_transformSelectionInplace(b.after.selection, a)
}
}
function _transformInplaceBatch(A, B) {
if (!isArray(A)) {
A = [A]
}
if (!isArray(B)) {
B = [B]
}
for (let i = 0; i < A.length; i++) {
let a = A[i]
for (let j = 0; j < B.length; j++) {
let b = B[j]
_transformInplaceSingle(a,b)
}
}
}
function _transformSelectionInplace(sel, a) {
if (!sel || (!sel.isPropertySelection() && !sel.isContainerSelection()) ) {
return false
}
let ops = a.ops
let hasChanged = false
let isCollapsed = sel.isCollapsed()
for(let i=0; i<ops.length; i++) {
let op = ops[i]
hasChanged |= _transformCoordinateInplace(sel.start, op)
if (!isCollapsed) {
hasChanged |= _transformCoordinateInplace(sel.end, op)
} else {
if (sel.isContainerSelection()) {
sel.end.path = sel.start.path
}
sel.end.offset = sel.start.offset
}
}
return hasChanged
}
function _transformCoordinateInplace(coor, op) {
if (!isEqual(op.path, coor.path)) return false
let hasChanged = false
if (op.type === 'update' && op.propertyType === 'string') {
let diff = op.diff
let newOffset
if (diff.isInsert() && diff.pos <= coor.offset) {
newOffset = coor.offset + diff.str.length
// console.log('Transforming coordinate after inserting %s chars:', diff.str.length, coor.toString(), '->', newOffset)
coor.offset = newOffset
hasChanged = true
} else if (diff.isDelete() && diff.pos <= coor.offset) {
newOffset = Math.max(diff.pos, coor.offset - diff.str.length)
// console.log('Transforming coordinate after deleting %s chars:', diff.str.length, coor.toString(), '->', newOffset)
coor.offset = newOffset
hasChanged = true
}
}
return hasChanged
}