mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-02-25 01:33:37 +01:00
311 lines
5.7 KiB
JavaScript
311 lines
5.7 KiB
JavaScript
|
|
import Text from './text'
|
|
import uid from 'uid'
|
|
import { Map, OrderedMap, Record } from 'immutable'
|
|
|
|
/**
|
|
* Record.
|
|
*/
|
|
|
|
const NodeRecord = new Record({
|
|
data: new Map(),
|
|
key: null,
|
|
nodes: new OrderedMap(),
|
|
type: null
|
|
})
|
|
|
|
/**
|
|
* Node.
|
|
*/
|
|
|
|
class Node extends NodeRecord {
|
|
|
|
/**
|
|
* Create a new `Node` with `properties`.
|
|
*
|
|
* @param {Object} properties
|
|
* @return {Node} node
|
|
*/
|
|
|
|
static create(properties = {}) {
|
|
if (!properties.type) throw new Error('You must pass a node `type`.')
|
|
properties.key = uid(4)
|
|
return new Node(properties)
|
|
}
|
|
|
|
/**
|
|
* Create an ordered map of `Nodes` from an array of `Nodes`.
|
|
*
|
|
* @param {Array} nodes
|
|
* @return {OrderedMap} map
|
|
*/
|
|
|
|
static createMap(nodes = []) {
|
|
return nodes.reduce((map, node) => {
|
|
map = map.set(node.key, node)
|
|
return map
|
|
}, new OrderedMap())
|
|
}
|
|
|
|
/**
|
|
* Get the length of the concatenated text of the node.
|
|
*
|
|
* @return {Number} length
|
|
*/
|
|
|
|
get length() {
|
|
return this.text.length
|
|
}
|
|
|
|
/**
|
|
* Get the concatenated text `string` of all child nodes.
|
|
*
|
|
* @return {String} text
|
|
*/
|
|
|
|
get text() {
|
|
return this
|
|
.filterNodes(node => node.type == 'text')
|
|
.map(node => node.characters)
|
|
.flatten()
|
|
.map(character => character.text)
|
|
.join('')
|
|
}
|
|
|
|
/**
|
|
* Recursively find nodes nodes by `iterator`.
|
|
*
|
|
* @param {Function} iterator
|
|
* @return {Node} node
|
|
*/
|
|
|
|
findNode(iterator) {
|
|
const shallow = this.nodes.find(iterator)
|
|
if (shallow != null) return shallow
|
|
|
|
return this.nodes
|
|
.map(node => node instanceof Node ? node.findNode(iterator) : null)
|
|
.filter(node => node)
|
|
.first()
|
|
}
|
|
|
|
/**
|
|
* Recursively filter nodes nodes with `iterator`.
|
|
*
|
|
* @param {Function} iterator
|
|
* @return {OrderedMap} matches
|
|
*/
|
|
|
|
filterNodes(iterator) {
|
|
const shallow = this.nodes.filter(iterator)
|
|
const deep = this.nodes
|
|
.map(node => node instanceof Node ? node.filterNodes(iterator) : null)
|
|
.filter(node => node)
|
|
.reduce((all, map) => {
|
|
return all.concat(map)
|
|
}, shallow)
|
|
|
|
return deep
|
|
}
|
|
|
|
/**
|
|
* Get a child node by `key`.
|
|
*
|
|
* @param {String} key
|
|
* @return {Node or Null}
|
|
*/
|
|
|
|
getNode(key) {
|
|
return this.findNode(node => node.key == key) || null
|
|
}
|
|
|
|
/**
|
|
* Get the child node after the one by `key`.
|
|
*
|
|
* @param {String or Node} key
|
|
* @return {Node or Null}
|
|
*/
|
|
|
|
getNextNode(key) {
|
|
if (typeof key != 'string') {
|
|
key = key.key
|
|
}
|
|
|
|
const shallow = this.nodes
|
|
.skipUntil(node => node.key == key)
|
|
.rest()
|
|
.first()
|
|
|
|
if (shallow != null) return shallow
|
|
|
|
return this.nodes
|
|
.map(node => node instanceof Node ? node.getNextNode(key) : null)
|
|
.filter(node => node)
|
|
.first()
|
|
}
|
|
|
|
/**
|
|
* Get the child node before the one by `key`.
|
|
*
|
|
* @param {String or Node} key
|
|
* @return {Node or Null}
|
|
*/
|
|
|
|
getPreviousNode(key) {
|
|
if (typeof key != 'string') {
|
|
key = key.key
|
|
}
|
|
|
|
const matches = this.nodes.get(key)
|
|
|
|
if (matches) {
|
|
return this.nodes
|
|
.takeUntil(node => node.key == key)
|
|
.last()
|
|
}
|
|
|
|
return this.nodes
|
|
.map(node => node instanceof Node ? node.getPreviousNode(key) : null)
|
|
.filter(node => node)
|
|
.first()
|
|
}
|
|
|
|
/**
|
|
* Get the parent of a child node by `key`.
|
|
*
|
|
* @param {String or Node} key
|
|
* @return {Node or Null}
|
|
*/
|
|
|
|
getParentNode(key) {
|
|
if (typeof key != 'string') {
|
|
key = key.key
|
|
}
|
|
|
|
if (this.nodes.get(key)) return this
|
|
let node = null
|
|
|
|
this.nodes.forEach((child) => {
|
|
if (!(child instanceof Node)) return
|
|
const match = child.getParentNode(key)
|
|
if (match) node = match
|
|
})
|
|
|
|
return node
|
|
}
|
|
|
|
/**
|
|
* Get the child text node at `offset`.
|
|
*
|
|
* @param {String} offset
|
|
* @return {Node or Null}
|
|
*/
|
|
|
|
getNodeAtOffset(offset) {
|
|
let match = null
|
|
let i
|
|
|
|
this.nodes.forEach((node) => {
|
|
if (!node.length > offset + i) return
|
|
match = node.type == 'text'
|
|
? node
|
|
: node.getNodeAtOffset(offset - i)
|
|
i += node.length
|
|
})
|
|
|
|
return match
|
|
}
|
|
|
|
/**
|
|
* Recursively check if a child node exists by `key`.
|
|
*
|
|
* @param {String or Node} key
|
|
* @return {Boolean} true
|
|
*/
|
|
|
|
hasNode(key) {
|
|
if (typeof key != 'string') {
|
|
key = key.key
|
|
}
|
|
|
|
const shallow = this.nodes.has(key)
|
|
if (shallow) return true
|
|
|
|
const deep = this.nodes
|
|
.map(node => node instanceof Node ? node.hasNode(key) : false)
|
|
.some(has => has)
|
|
if (deep) return true
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* Push a new `node` onto the map of nodes.
|
|
*
|
|
* @param {String or Node} key
|
|
* @param {Node} node
|
|
* @return {Node} node
|
|
*/
|
|
|
|
pushNode(key, node) {
|
|
if (typeof key != 'string') {
|
|
node = key
|
|
key = node.key
|
|
}
|
|
|
|
let nodes = this.nodes.set(key, node)
|
|
return this.merge({ nodes })
|
|
}
|
|
|
|
/**
|
|
* Remove a `node` from the children node map.
|
|
*
|
|
* @param {String or Node} key
|
|
* @return {Node} node
|
|
*/
|
|
|
|
removeNode(key) {
|
|
if (typeof key != 'string') {
|
|
key = key.key
|
|
}
|
|
|
|
let nodes = this.nodes.remove(key)
|
|
return this.merge({ nodes })
|
|
}
|
|
|
|
/**
|
|
* Set a new value for a child node by `key`.
|
|
*
|
|
* @param {String or Node} key
|
|
* @param {Node} node
|
|
* @return {Node} node
|
|
*/
|
|
|
|
updateNode(key, node) {
|
|
if (typeof key != 'string') {
|
|
node = key
|
|
key = node.key
|
|
}
|
|
|
|
if (this.nodes.get(key)) {
|
|
const nodes = this.nodes.set(key, node)
|
|
return this.set('nodes', nodes)
|
|
}
|
|
|
|
const nodes = this.nodes.map((child) => {
|
|
return child instanceof Node
|
|
? child.updateNode(key, node)
|
|
: child
|
|
})
|
|
|
|
return this.merge({ nodes })
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Export.
|
|
*/
|
|
|
|
export default Node
|