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 (162 loc) 4.04 kB
import { get, setWith } from '../vendor/lodash-es' import isString from './isString' import isArray from './isArray' import deleteFromArray from './deleteFromArray' import hasOwnProperty from './hasOwnProperty' class TreeNode {} /* * A tree-structure for indexes. */ export default class TreeIndex { /** * Get value at path. * * @return {object} The value stored for a given path * * @example * * obj.get(['b', 'b1']); * => b1Val */ get (path) { if (arguments.length > 1) { path = Array.prototype.slice(arguments, 0) } path = _pathify(path) return get(this, path) } getAll (path) { if (arguments.length > 1) { path = Array.prototype.slice(arguments, 0) } path = _pathify(path) if (!isArray(path)) { throw new Error('Illegal argument for TreeIndex.get()') } const node = get(this, path) return this._collectValues(node) } set (path, value) { path = _pathify(path) setWith(this, path, value, function (val) { if (!val) return new TreeNode() }) } delete (path) { path = _pathify(path) if (path.length === 1) { delete this[path[0]] } else { const key = path[path.length - 1] path = path.slice(0, -1) const parent = get(this, path) if (parent) { delete parent[key] } } } clear () { const root = this for (const key in root) { if (hasOwnProperty(root, key)) { delete root[key] } } } traverse (fn) { this._traverse(this, [], fn) } forEach (...args) { this.traverse(...args) } _traverse (root, path, fn) { let id for (id in root) { if (!hasOwnProperty(root, id)) continue const child = root[id] const childPath = path.concat([id]) if (child instanceof TreeNode) { this._traverse(child, childPath, fn) } else { fn(child, childPath) } } } _collectValues (root) { // TODO: don't know if this is the best solution // We use this only for indexes, e.g., to get all annotation on one node const vals = {} this._traverse(root, [], function (val, path) { const key = path[path.length - 1] vals[key] = val }) return vals } } function _pathify (path) { if (isString(path)) { return [path] } else { return path } } class TreeIndexArrays extends TreeIndex { contains (path) { const val = super.get(path) return Boolean(val) } get (path) { let val = super.get(path) if (val instanceof TreeNode) { val = val.__values__ || [] } return val } set (path, arr) { const val = super.get(path) val.__values__ = arr } add (path, value) { path = _pathify(path) if (!isArray(path)) { throw new Error('Illegal arguments.') } let arr // We are using setWith, as it allows us to create nodes on the way down // setWith can be controlled via a hook called for each key in the path // If val is not defined, a new node must be created and returned. // If val is defined, then we must return undefined to keep the original tree node // __dummy__ is necessary as setWith is used to set a value, but we want // to append to the array setWith(this, path.concat(['__values__', '__dummy__']), undefined, function (val, key) { if (key === '__values__') { if (!val) val = [] arr = val } else if (!val) { val = new TreeNode() } return val }) delete arr.__dummy__ arr.push(value) } remove (path, value) { const arr = get(this, path) if (arr instanceof TreeNode) { if (arguments.length === 1) { delete arr.__values__ } else { deleteFromArray(arr.__values__, value) } } } _collectValues (root) { let vals = [] this._traverse(root, [], function (val) { vals.push(val) }) vals = Array.prototype.concat.apply([], vals) return vals } } TreeIndex.Arrays = TreeIndexArrays