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).

173 lines (153 loc) 4.25 kB
import { EventEmitter, getKeyForPath, deleteFromArray } from '../util' export default class MarkersManager extends EventEmitter { constructor (editorState) { super() this._editorState = editorState this._markers = new MarkersIndex() editorState.addObserver(['document'], this._onDocumentChange, this, { stage: 'update' }) } dispose () { this._editorState.removeObserver(this) } addMarker (marker) { const path = marker.getPath() this._markers.add(path, marker) this._setDirty(path) } removeMarker (marker) { const path = marker.getPath() this._markers.remove(path, marker) this._setDirty(path) } clearMarkers (path, filter) { this._markers.clearMarkers(path, filter) this._setDirty(path) } getMarkers (path) { return this._markers.get(path) } _getDocumentObserver () { return this._editorState._getDocumentObserver() } _setDirty (path) { this._editorState._setDirty('document') this._getDocumentObserver().setDirty(path) } // updating markers to reflect changes on the text they are bound to _onDocumentChange (change) { for (const op of change.primitiveOps) { if (op.type === 'update' && op.diff._isTextOperation) { const markers = this._markers.get(op.path) if (!markers || markers.length === 0) continue const diff = op.diff switch (diff.type) { case 'insert': markers.forEach(m => this._transformInsert(m, diff)) break case 'delete': markers.forEach(m => this._transformDelete(m, diff)) break default: // } } } } _transformInsert (marker, op) { const pos = op.pos const length = op.str.length if (length === 0) return // console.log('Transforming marker after insert') const start = marker.start.offset const end = marker.end.offset let newStart = start let newEnd = end if (pos >= end) return if (pos <= start) { newStart += length newEnd += length marker.start.offset = newStart marker.end.offset = newEnd return } if (pos < end) { newEnd += length marker.end.offset = newEnd // NOTE: right now, any change inside a marker // removes the marker, as opposed to changes before // which shift the marker this._remove(marker) } } _transformDelete (marker, op) { const pos1 = op.pos const length = op.str.length const pos2 = pos1 + length if (pos1 === pos2) return var start = marker.start.offset var end = marker.end.offset var newStart = start var newEnd = end if (pos2 <= start) { newStart -= length newEnd -= length marker.start.offset = newStart marker.end.offset = newEnd } else if (pos1 >= end) { // the marker needs to be changed // now, there might be cases where the marker gets invalid, such as a spell-correction } else { if (pos1 <= start) { newStart = start - Math.min(pos2 - pos1, start - pos1) } if (pos1 <= end) { newEnd = end - Math.min(pos2 - pos1, end - pos1) } // TODO: we should do something special when the change occurred inside the marker if (start !== end && newStart === newEnd) { this._remove(marker) return } if (start !== newStart) { marker.start.offset = newStart } if (end !== newEnd) { marker.end.offset = newEnd } this._remove(marker) } } _remove (marker) { this.removeMarker(marker) } } class MarkersIndex { add (path, val) { const key = getKeyForPath(path) if (!this[key]) { this[key] = [] } this[key].push(val) } remove (path, val) { const key = getKeyForPath(path) if (this[key]) { deleteFromArray(this[key], val) } } get (path) { const key = getKeyForPath(path) return this[key] || [] } clearMarkers (path, filter) { const key = getKeyForPath(path) const arr = this[key] if (arr) { for (let i = arr.length - 1; i >= 0; i--) { if (filter(arr[i])) { arr.splice(i, 1) } } } } }