UNPKG

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.

149 lines (125 loc) 3.69 kB
import { isFunction } from '../util' import DocumentChange from './DocumentChange' /* A transaction for editing a document in an EditorSession. Wherever you see `tx`, it is mostly an instance of this class. The transaction is used to manipulate the document in a 'turtle-graphics' style. For that it maintains an internal state consisting of an array of operations, a selection, and the current surface. Usually, at the beginning of a transaction, one Surface is focused. This is used to initialize the transaction state. Depending on the type of Surface or the type of the current selection, some manipulations are allowed or others are not: it is not possible to create a ContainerAnnotation without a ContainerSelection, or pasting a list of nodes into a TextPropertyEditor will strip the structure and just the text content. */ export default class Transaction { /* @param {Document} doc */ constructor(master) { // using a different name internally this.master = master this.stage = master.newInstance().createFromDocument(master) // HACK: some code is relying on this this.stage._isTransactionDocument = true this.tx = this.stage.createEditingInterface() // internal state this._isTransacting = false this._surface = null // HACK: need to wipe the ops from master as otherwise the next // sync would fail master._ops.length = 0 } dispose() { this.stage.dispose() } // internal API get ops() { return this.stage._ops } set ops(ops) { this.stage._ops = ops } getSelection() { return this.tx.getSelection() } setSelection(sel) { this.tx.setSelection(sel) } _reset() { this._before = {} this._after = {} this.stage._ops.length = 0 this._info = {} this.setSelection(null) } /** Start a transaction to manipulate the document @param {function} transformation a function(tx) that performs actions on the transaction document tx @example ```js doc.transaction(function(tx, args) { tx.update(...) ... return { selection: newSelection } }) ``` */ _recordChange(transformation, selection) { if (this._isTransacting) throw new Error('Nested transactions are not supported.') if (!isFunction(transformation)) throw new Error('Document.transaction() requires a transformation function.') let hasFinished = false this._isTransacting = true this._reset() let change try { const tx = this.tx tx.setSelection(selection) let selBefore = tx.getSelection() transformation(tx, { selection: selBefore }) let ops = this.ops if (ops.length > 0) { change = new DocumentChange(ops, tx._before, tx._after) change.before = { selection: selBefore } change.after = { selection: tx.getSelection() } } hasFinished = true } finally { if (!hasFinished) { this._rollback() } this._isTransacting = false } return change } _sync() { const master = this.master const stage = this.stage let ops = master._ops for (let i = 0; i < ops.length; i++) { stage._applyOp(ops[i]) } ops.length = 0 } // HACK: we are not doing well with updating the stage __applyChange__(change) { const stage = this.stage const ops = change.ops for (let i = 0; i < ops.length; i++) { stage._applyOp(ops[i]) } } _rollback() { const stage = this.stage let ops = stage._ops for (let i = ops.length - 1; i >= 0; i--) { stage._applyOp(ops[i].invert()) } ops.length = 0 } }