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 system. It is developed to power our online editing platform [Substance](http://substance.io).

182 lines (156 loc) 4.34 kB
import NodeIndex from '../model/NodeIndex' import { getKeyForPath } from '../util' // kind of an index that is used to dispatch updates export default class DocumentObserver { constructor (doc, editorState) { // console.log('Creating DocumentObserver for', editorState.getId()) this.editorState = editorState this.doc = doc this.dirty = new Set() this.init() } init () { const doc = this.doc this.dirty.clear() if (!doc.getIndex('relationships')) { doc.addIndex('relationships', new RelationshipIndex()) } doc.on('document:changed', this._onDocumentChanged, this) } dispose () { this.doc.off(this) } // called by EditorState when updates have been propagated reset () { this.dirty = new Set() } setDirty (path) { this.dirty.add(getKeyForPath(path)) } // TODO: this is built on top of the current implementation of // DocumentChange. We could try to consolidate and have just // one place where this information is derived _onDocumentChanged (change, info = {}) { // ATTENTION: the change is not carrying reflection until change._extracted = true if (!change._extracted) change._extractInformation(this.doc) // console.log('DocumentObserver._onDocumentChanged()', this.editorState.getId(), change, change.updated) const dirty = this.dirty Object.keys(change.updated).forEach(id => { dirty.add(id) }) } } const ONE = Symbol('ONE') const MANY = Symbol('MANY') class RelationshipIndex extends NodeIndex { constructor () { super() // a mapping from type to relational properties this._relationsByType = {} // the inverse index this._byTarget = new ValuesById() } get (targetId) { return this._byTarget.get(targetId) } select (node) { // eslint-disable-line no-unused-vars return true } clear () { this._byTarget.clear() } create (node) { // eslint-disable-line no-unused-vars const relations = this._getRelations(node) if (!relations) return for (const [name, type] of relations) { const val = node.get(name) if (!val) continue if (type === ONE) { this._add(val, node.id) } else { val.forEach(targetId => this._add(targetId, node.id)) } } } delete (node) { const relations = this._getRelations(node) if (!relations) return for (const [name, type] of relations) { const val = node.get(name) if (!val) continue if (type === ONE) { this._remove(val, node.id) } else { val.forEach(targetId => this._remove(targetId, node.id)) } } } update (node, path, newValue, oldValue) { const relations = this._getRelations(node) if (!relations) return const type = relations.get(path[1]) if (!type) return if (type === ONE) { this._remove(oldValue, node.id) this._add(newValue, node.id) } else { oldValue.forEach(targetId => this._remove(targetId, node.id)) newValue.forEach(targetId => this._add(targetId, node.id)) } } _getRelations (node) { let relations = this._relationsByType[node.type] if (relations === undefined) { relations = getRelations(node) if (relations.size === 0) relations = false this._relationsByType[node.type] = relations } return relations } _add (targetId, sourceId) { this._byTarget.add(targetId, sourceId) } _remove (targetId, sourceId) { this._byTarget.remove(targetId, sourceId) } } function getRelations (node) { const relations = new Map() const nodeSchema = node.getSchema() for (const property of nodeSchema) { if (property.isReference()) { const name = property.name const type = property.isArray() ? MANY : ONE relations.set(name, type) } } return relations } class ValuesById { constructor () { this._index = new Map() } get (key) { return this._index.get(key) } add (key, val) { let vals = this._index.get(key) if (!vals) { vals = new Set() this._index.set(key, vals) } vals.add(val) } remove (key, val) { const vals = this._index.get(key) if (vals) { vals.delete(val) if (vals.size === 0) { this._index.delete(key) } } } clear () { this._index = new Map() } }