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 system. It is developed to power our online editing platform [Substance](http://substance.io).
169 lines (153 loc) • 4.46 kB
JavaScript
import isString from '../util/isString'
import isArray from '../util/isArray'
import cloneDeep from '../util/cloneDeep'
import Data from './Data'
import ObjectOperation from './ObjectOperation'
import ArrayOperation from './ArrayOperation'
import TextOperation from './TextOperation'
import CoordinateOperation from './CoordinateOperation'
/**
Incremental data storage implemention.
@internal
*/
class IncrementalData extends Data {
/**
Create a new node.
@param {object} nodeData
@returns {ObjectOperation} The applied operation.
*/
create (nodeData) {
if (nodeData._isNode) {
nodeData = nodeData.toJSON()
}
const op = ObjectOperation.Create([nodeData.id], nodeData)
this.apply(op)
return op
}
/**
Delete a node.
@param {String} nodeId
@returns {ObjectOperation} The applied operation.
*/
delete (nodeId) {
var op = null
var node = this.get(nodeId)
if (node) {
var nodeData = node.toJSON()
op = ObjectOperation.Delete([nodeId], nodeData)
this.apply(op)
}
return op
}
/**
Update a property incrementally.
The diff can be of the following forms (depending on the updated property type):
- String:
- `{ insert: { offset: Number, value: Object } }`
- `{ delete: { start: Number, end: Number } }`
- Array:
- `{ insert: { offset: Number, value: Object } }`
- `{ delete: { offset: Number } }`
@param {array} path
@param {object} diff
@returns {ObjectOperation} The applied operation.
*/
update (path, diff) {
var diffOp = this._getDiffOp(path, diff)
var op = ObjectOperation.Update(path, diffOp)
this.apply(op)
return op
}
/**
Set a property to a new value
@param {Array} path
@param {Object} newValue
@returns {ObjectOperation} The applied operation.
*/
set (path, newValue) {
var oldValue = this.get(path)
var op = ObjectOperation.Set(path, oldValue, newValue)
this.apply(op)
return op
}
/**
Apply a given operation.
@param {ObjectOperation} op
*/
apply (op) {
if (op.type === ObjectOperation.NOP) return
else if (op.type === ObjectOperation.CREATE) {
// clone here as the operations value must not be changed
super.create(cloneDeep(op.val))
} else if (op.type === ObjectOperation.DELETE) {
super.delete(op.val.id)
} else if (op.type === ObjectOperation.UPDATE) {
const diff = op.diff
super.update(op.path, diff)
} else if (op.type === ObjectOperation.SET) {
super.set(op.path, op.val)
} else {
throw new Error('Illegal state.')
}
this.emit('operation:applied', op, this)
}
/**
Creates proper operation based on provided node path and diff.
@param {Array} path
@param {Object} diff
@returns {ObjectOperation} operation.
@private
*/
_getDiffOp (path, diff) {
var diffOp = null
if (diff._isOperation) {
diffOp = diff
} else {
var value = this.get(path)
diff = this._normalizeDiff(value, diff)
if (value === null || value === undefined) {
throw new Error('Property has not been initialized: ' + JSON.stringify(path))
} else if (isString(value)) {
switch (diff.type) {
case 'delete': {
diffOp = TextOperation.Delete(diff.start, value.substring(diff.start, diff.end))
break
}
case 'insert': {
diffOp = TextOperation.Insert(diff.start, diff.text)
break
}
default:
throw new Error('Unknown diff type')
}
} else if (isArray(value)) {
switch (diff.type) {
case 'delete': {
diffOp = ArrayOperation.Delete(diff.pos, value[diff.pos])
break
}
case 'insert': {
diffOp = ArrayOperation.Insert(diff.pos, diff.value)
break
}
default:
throw new Error('Unknown diff type')
}
} else if (value._isCoordinate) {
switch (diff.type) {
case 'shift': {
diffOp = CoordinateOperation.Shift(diff.value)
break
}
default:
throw new Error('Unknown diff type')
}
}
}
if (!diffOp) {
throw new Error('Unsupported diff: ' + JSON.stringify(diff))
}
return diffOp
}
}
export default IncrementalData