all files / model/ TransactionDocument.js

95.74% Statements 45/47
68.75% Branches 11/16
100% Functions 14/14
95.74% Lines 45/47
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146                                                                218×   218× 218× 218×   218×     218× 218×     218× 654×       218×   218×     218× 218×             1774× 55×   1774×     1774× 1774× 1774× 1774×         17×               54× 54× 54×         12× 12× 12×         402× 402× 402× 402×         168× 157×       11×         11× 11×         168× 168×                            
import { forEach, uuid } from '../util'
import Document from './Document'
import IncrementalData from './IncrementalData'
import DocumentNodeFactory from './DocumentNodeFactory'
import ParentNodeHook from './ParentNodeHook'
 
/**
  A {@link Document} instance that is used during transaction.
 
  During editing a TransactionDocument is kept up-to-date with the real one.
  Whenever a transaction is started on the document, a TransactionDocument is used to
  record changes, which are applied en-bloc when the transaction is saved.
 
  The transaction document is the common way to manipulate the document.
  It provides a 'turtle-graphics' style API, i.e., it has a state
 
  @example
 
  To start a transaction run
 
  ```
  doc.transaction(function(tx) {
    // use tx to record changes
  })
  ```
*/
class TransactionDocument extends Document {
 
  /**
    @param {model/Document} document a document instance
  */
  constructor(document) {
    super('SKIP')
 
    this.schema = document.schema
    this.nodeFactory = new DocumentNodeFactory(this)
    this.data = new IncrementalData(this.schema, this.nodeFactory)
 
    this.document = document
 
    // ops recorded since transaction start
    this.ops = []
    this.lastOp = null
 
    // copy all indexes
    forEach(document.data.indexes, function(index, name) {
      this.data.addIndex(name, index.clone())
    }.bind(this))
 
    // ATTENTION: this must before loading the seed
    ParentNodeHook.register(this)
 
    this.createFromDocument(document)
 
    // make sure that we mirror all changes that are done outside of transactions
    document.on('document:changed', this._onDocumentChanged, this)
    this._skipNextDocumentChange = false
  }
 
  dispose() {
    this.document.off(this)
    this.data.off()
  }
 
  create(nodeData) {
    if (!nodeData.id) {
      nodeData.id = uuid(nodeData.type)
    }
    Iif (!nodeData.type) {
      throw new Error('No node type provided')
    }
    this.lastOp = this.data.create(nodeData)
    Eif (this.lastOp) {
      this.ops.push(this.lastOp)
      return this.data.get(nodeData.id)
    }
  }
 
  createDefaultTextNode(text, dir) {
    return this.create({
      type: this.getSchema().getDefaultTextType(),
      content: text || '',
      direction: dir
    })
  }
 
  delete(nodeId) {
    this.lastOp = this.data.delete(nodeId)
    Eif (this.lastOp) {
      this.ops.push(this.lastOp)
    }
  }
 
  set(path, value) {
    this.lastOp = this.data.set(path, value)
    Eif (this.lastOp) {
      this.ops.push(this.lastOp)
    }
  }
 
  update(path, diffOp) {
    let op = this.lastOp = this.data.update(path, diffOp)
    Eif (op) {
      this.ops.push(op)
      return op
    }
  }
 
  _onDocumentChanged(change) {
    if (this._skipNextDocumentChange) {
      this._skipNextDocumentChange = false
    } else {
      // NOTE: this is hooked to document:changed (low-level), to make sure that we
      // update the transaction document too when the document is manipulated directly, e.g. using `document.create(...)`
      this._apply(change)
    }
  }
 
  _apply(documentChange) {
    documentChange.ops.forEach(function(op) {
      this.data.apply(op)
    }.bind(this))
  }
 
  _reset() {
    this.ops = []
    this.lastOp = null
  }
 
  _rollback() {
    for (var i = this.ops.length - 1; i >= 0; i--) {
      this.data.apply(this.ops[i].invert())
    }
    this.ops = []
    this.lastOp = null
  }
 
  newInstance() {
    return this.document.newInstance()
  }
}
 
TransactionDocument.prototype._isTransactionDocument = true
 
export default TransactionDocument