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.
216 lines (185 loc) • 5.35 kB
JavaScript
import { isNumber, isString } from '../util'
import ContainerAddress from './ContainerAddress'
import documentHelpers from './documentHelpers'
export default function (DocumentNode) {
class AbstractContainer extends DocumentNode {
contains(nodeId) {
return this.getPosition(nodeId) >= 0
}
getPosition(node, strict) {
if (isString(node)) {
node = this.document.get(node)
}
if (!node) return -1
let pos = this._getPosition(node)
if (strict && pos < 0) {
throw new Error('Node is not within this container: ' + node.id)
}
return pos
}
getNodeAt(idx) {
const nodeId = this.getNodeIdAt(idx)
if (nodeId) {
return this.getDocument().get(nodeId)
}
}
getNodeIdAt(idx) {
let content = this.getContent()
if (idx < 0 || idx >= content.length) {
// throw new Error('Array index out of bounds: ' + idx + ", " + content.length)
return undefined
} else {
return content[idx]
}
}
getNodes() {
const doc = this.getDocument()
return this.getContent().map(id => doc.get(id)).filter(Boolean)
}
show(nodeId, pos) {
// allow to provide a node instance instead of nodeId
const arg1 = arguments[0]
if (!isString(arg1)) {
if (arg1._isNode) {
nodeId = arg1.id
}
}
if (arguments.length > 1) {
console.error('DEPRECATED: use container.showAt(pos, nodeId) instead')
} else {
pos = this.getLength()
}
return this.showAt(pos, nodeId)
}
showAt(pos, nodeId) {
const doc = this.getDocument()
const length = this.getLength()
if (!isNumber(pos) || pos < 0 || pos > length) {
throw new Error('Index out of bounds')
}
if (!isString(nodeId)) {
if (nodeId._isNode) {
nodeId = nodeId.id
} else {
throw new Error('Invalid argument.')
}
}
doc.update(this.getContentPath(), { type: 'insert', pos: pos, value: nodeId })
}
hide(nodeId) {
const pos = this.getPosition(nodeId)
this.hideAt(pos)
}
hideAt(pos) {
const length = this.getLength()
if (pos >= 0 && pos < length) {
const doc = this.getDocument()
doc.update(this.getContentPath(), { type: 'delete', pos: pos })
} else {
throw new Error('Index out of bounds.')
}
}
getAddress(coor) {
if (!coor._isCoordinate) {
// we have broken with an earlier version of this API
throw new Error('Illegal argument: Container.getAddress(coor) expects a Coordinate instance.')
}
var nodeId = coor.path[0]
var nodePos = this.getPosition(nodeId)
var offset
if (coor.isNodeCoordinate()) {
if (coor.offset > 0) {
offset = Number.MAX_VALUE
} else {
offset = 0
}
} else {
offset = coor.offset
}
return new ContainerAddress(nodePos, offset)
}
getLength() {
return this.getContent().length
}
get length() {
return this.getLength()
}
_getPosition(node) {
if (this._isCaching) {
return this._getCachedPosition(node)
} else {
return this._lookupPosition(node)
}
}
_getCachedPosition(node) {
let cache = this._cachedPositions || this._fillCache()
let nodeId = node.id
let pos = -1
if (cache.hasOwnProperty(nodeId)) {
pos = cache[nodeId]
} else {
pos = this._lookupPosition(node)
cache[nodeId] = pos
}
return pos
}
_fillCache() {
let positions = {}
this.nodes.forEach((id, pos) => {
positions[id] = pos
})
this._cachedPositions = positions
return positions
}
_invalidateCache() {
this._cachedPositions = null
}
_lookupPosition(node) {
if (node.hasParent()) {
node = node.getContainerRoot()
}
return this.getContent().indexOf(node.id)
}
_enableCaching() {
// this hook is used to invalidate cached positions
if (this.document) {
this.document.data.on('operation:applied', this._onOperationApplied, this)
this._isCaching = true
}
}
_onOperationApplied(op) {
if (op.type === 'set' || op.type === 'update') {
if (op.path[0] === this.id) {
this._invalidateCache()
}
}
}
_onDocumentChange(change) {
if (change.hasUpdated(this.getContentPath())) {
this._invalidateCache()
}
}
// NOTE: this has been in ParentNodeMixin before
// TODO: try to get rid of this
hasChildren() {
return this.getContent().length > 0
}
getChildIndex(child) {
return this.getContent().indexOf(child.id)
}
getChildren() {
return documentHelpers.getNodes(this.getDocument(), this.getContent())
}
getChildAt(idx) {
var childrenIds = this.getContent()
if (idx < 0 || idx >= childrenIds.length) {
throw new Error('Array index out of bounds: ' + idx + ", " + childrenIds.length)
}
return this.getDocument().get(childrenIds[idx], 'strict')
}
getChildCount() {
return this.getContent().length
}
}
return AbstractContainer
}