1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-25 01:33:37 +01:00
slate/lib/models/state.js

375 lines
7.4 KiB
JavaScript
Raw Normal View History

2016-06-15 12:07:12 -07:00
import Document from './document'
2016-06-17 18:52:23 -07:00
import Selection from './selection'
import Transform from './transform'
import { Record, Stack } from 'immutable'
2016-06-15 12:07:12 -07:00
/**
* History.
2016-06-15 12:07:12 -07:00
*/
const History = new Record({
undos: new Stack(),
redos: new Stack()
2016-06-15 12:07:12 -07:00
})
2016-06-17 00:09:54 -07:00
/**
* Default properties.
2016-06-17 00:09:54 -07:00
*/
2016-06-20 12:57:31 -07:00
const DEFAULTS = {
document: new Document(),
selection: new Selection(),
history: new History(),
2016-06-18 23:07:55 -07:00
isNative: true
}
2016-06-17 00:09:54 -07:00
/**
2016-06-20 12:57:31 -07:00
* Node-like methods that should be mixed into the `State` prototype.
2016-06-17 00:09:54 -07:00
*/
2016-06-20 12:57:31 -07:00
const NODE_LIKE_METHODS = [
'deleteAtRange',
'deleteBackwardAtRange',
'deleteForwardAtRange',
'insertAtRange',
'splitAtRange'
2016-06-17 00:09:54 -07:00
]
2016-06-15 12:07:12 -07:00
/**
* State.
*/
2016-06-20 12:57:31 -07:00
class State extends Record(DEFAULTS) {
2016-06-15 12:07:12 -07:00
/**
2016-06-17 18:20:26 -07:00
* Create a new `State` with `properties`.
2016-06-15 12:07:12 -07:00
*
2016-06-20 12:57:31 -07:00
* @param {Object} properties
2016-06-15 12:07:12 -07:00
* @return {State} state
*/
2016-06-17 18:20:26 -07:00
static create(properties = {}) {
return new State(properties)
2016-06-15 12:07:12 -07:00
}
/**
* Is the current selection collapsed?
*
* @return {Boolean} isCollapsed
*/
get isCurrentlyCollapsed() {
return this.selection.isCollapsed
}
/**
* Is the current selection expanded?
*
* @return {Boolean} isExpanded
*/
get isCurrentlyExpanded() {
return this.selection.isExpanded
}
2016-06-21 10:43:04 -07:00
/**
* Get the current start key.
*
* @return {String} startKey
*/
get currentStartKey() {
return this.selection.startKey
}
/**
* Get the current end key.
*
* @return {String} endKey
*/
get currentEndKey() {
return this.selection.endKey
}
/**
* Get the current start offset.
*
* @return {String} startOffset
*/
get currentStartOffset() {
return this.selection.startOffset
}
/**
* Get the current end offset.
*
* @return {String} endOffset
*/
get currentEndOffset() {
return this.selection.endOffset
}
2016-06-20 12:57:31 -07:00
/**
* Get the characters in the current selection.
*
* @return {List} characters
*/
2016-06-20 17:38:56 -07:00
get currentCharacters() {
return this.document.getCharactersAtRange(this.selection)
2016-06-20 12:57:31 -07:00
}
/**
* Get the marks of the current selection.
*
* @return {Set} marks
*/
2016-06-20 17:38:56 -07:00
get currentMarks() {
return this.document.getMarksAtRange(this.selection)
2016-06-20 12:57:31 -07:00
}
2016-06-20 17:38:56 -07:00
/**
* Get the block nodes in the current selection.
2016-06-20 17:38:56 -07:00
*
* @return {OrderedMap} nodes
*/
get currentBlockNodes() {
return this.document.getBlockNodesAtRange(this.selection)
2016-06-20 17:38:56 -07:00
}
2016-06-20 12:57:31 -07:00
/**
* Get the text nodes in the current selection.
*
* @return {OrderedMap} nodes
*/
2016-06-20 17:38:56 -07:00
get currentTextNodes() {
return this.document.getTextNodesAtRange(this.selection)
2016-06-20 12:57:31 -07:00
}
/**
* Return a new `Transform` with the current state as a starting point.
*
* @return {Transform} transform
*/
transform() {
2016-06-20 12:57:31 -07:00
const state = this
return new Transform({ state })
}
/**
2016-06-20 12:57:31 -07:00
* Delete at the current selection.
2016-06-17 13:34:29 -07:00
*
* @return {State} state
*/
delete() {
let state = this
let { document, selection } = state
2016-06-17 13:34:29 -07:00
// When collapsed, there's nothing to do.
if (selection.isCollapsed) return state
2016-06-17 13:34:29 -07:00
// Otherwise, delete and update the selection.
document = document.deleteAtRange(selection)
selection = selection.moveToStart()
state = state.merge({ document, selection })
2016-06-17 13:34:29 -07:00
return state
}
/**
* Delete backward `n` characters at the current selection.
*
* @param {Number} n (optional)
* @return {State} state
*/
2016-06-17 00:09:54 -07:00
2016-06-17 13:34:29 -07:00
deleteBackward(n = 1) {
let state = this
let { document, selection } = state
let after = selection
2016-06-17 13:34:29 -07:00
// Determine what the selection should be after deleting.
const { startKey } = selection
const startNode = document.getNode(startKey)
2016-06-17 13:34:29 -07:00
2016-06-21 17:08:15 -07:00
if (selection.isExpanded) {
after = selection.moveToStart()
2016-06-20 13:21:24 -07:00
}
2016-06-21 17:08:15 -07:00
else if (selection.isAtStartOf(document)) {
after = selection
2016-06-17 18:52:23 -07:00
}
else if (selection.isAtStartOf(startNode)) {
const parent = document.getParentNode(startNode)
const previous = document.getPreviousNode(parent).nodes.first()
after = selection.moveToEndOf(previous)
2016-06-17 13:34:29 -07:00
}
2016-06-17 00:34:27 -07:00
2016-06-20 17:38:56 -07:00
else {
after = selection.moveBackward(n)
2016-06-17 00:09:54 -07:00
}
2016-06-17 13:34:29 -07:00
// Delete backward and then update the selection.
document = document.deleteBackwardAtRange(selection)
selection = after
state = state.merge({ document, selection })
2016-06-17 13:34:29 -07:00
return state
}
/**
* Delete forward `n` characters at the current selection.
*
* @param {Number} n (optional)
* @return {State} state
*/
2016-06-15 12:07:12 -07:00
2016-06-17 13:34:29 -07:00
deleteForward(n = 1) {
let state = this
let { document, selection } = state
let after = selection
2016-06-17 18:52:23 -07:00
// Determine what the selection should be after deleting.
if (selection.isExpanded) {
after = selection.moveToStart()
2016-06-17 18:52:23 -07:00
}
// Delete forward and then update the selection.
document = document.deleteForwardAtRange(selection)
selection = after
state = state.merge({ document, selection })
2016-06-17 12:00:15 -07:00
return state
}
/**
2016-06-20 12:57:31 -07:00
* Insert a `text` string at the current selection.
2016-06-17 12:00:15 -07:00
*
* @param {String} text
2016-06-17 13:34:29 -07:00
* @return {State} state
*/
insertText(text) {
2016-06-17 13:34:29 -07:00
let state = this
let { document, selection } = state
2016-06-17 13:34:29 -07:00
// Insert the text and update the selection.
document = document.insertTextAtRange(selection, text)
selection = selection.moveForward(text.length)
state = state.merge({ document, selection })
2016-06-17 13:34:29 -07:00
return state
}
/**
2016-06-20 12:57:31 -07:00
* Add a `mark` to the characters in the current selection.
*
* @param {Mark} mark
* @return {State} state
*/
mark(mark) {
let state = this
let { document, selection } = state
document = document.markAtRange(selection, mark)
state = state.merge({ document })
return state
}
2016-06-20 17:38:56 -07:00
/**
* Set the nodes in the current selection to `type`.
*
* @param {String} type
* @return {State} state
*/
setType(type) {
let state = this
let { document, selection } = state
document = document.setTypeAtRange(selection, type)
state = state.merge({ document })
return state
}
2016-06-20 12:57:31 -07:00
/**
* Split the node at the current selection.
2016-06-16 16:43:02 -07:00
*
* @return {State} state
*/
split() {
2016-06-17 00:09:54 -07:00
let state = this
let { document, selection } = state
let after
2016-06-17 00:34:27 -07:00
// Split the document.
document = document.splitAtRange(selection)
2016-06-17 13:34:29 -07:00
// Determine what the selection should be after splitting.
const { startKey } = selection
const startNode = document.getNode(startKey)
const parent = document.getParentNode(startNode)
const next = document.getNextNode(parent)
const text = next.nodes.first()
selection = selection.moveToStartOf(text)
2016-06-17 16:10:44 -07:00
state = state.merge({ document, selection })
2016-06-17 16:10:44 -07:00
return state
}
2016-06-20 12:57:31 -07:00
/**
* Remove a `mark` to the characters in the current selection.
*
* @param {Mark} mark
* @return {State} state
*/
unmark(mark) {
let state = this
let { document, selection } = state
document = document.unmarkAtRange(selection, mark)
state = state.merge({ document })
return state
}
2016-06-21 18:00:18 -07:00
/**
* Wrap the block nodes in the current selection in new nodes of `type`.
*
* @param {String} type
* @return {State} state
*/
wrap(type) {
let state = this
let { document, selection } = state
document = document.wrapAtRange(selection, type)
state = state.merge({ document })
return state
}
2016-06-15 12:07:12 -07:00
}
2016-06-17 00:09:54 -07:00
/**
* Mix in node-like methods.
*/
2016-06-20 12:57:31 -07:00
NODE_LIKE_METHODS.forEach((method) => {
2016-06-17 00:09:54 -07:00
State.prototype[method] = function (...args) {
let { document } = this
document = document[method](...args)
return this.merge({ document })
2016-06-17 00:09:54 -07:00
}
})
2016-06-15 12:07:12 -07:00
/**
* Export.
*/
export default State