1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-24 17:23:07 +01:00
slate/lib/models/state.js
2016-06-18 23:07:55 -07:00

210 lines
4.4 KiB
JavaScript

import Document from './document'
import Selection from './selection'
import Transform from './transform'
import { Record, Stack } from 'immutable'
/**
* History.
*/
const History = new Record({
undos: new Stack(),
redos: new Stack()
})
/**
* Default properties.
*/
const DEFAULT_PROPERTIES = {
document: new Document(),
selection: new Selection(),
history: new History(),
isNative: true
}
/**
* Document-like methods, that should be mixed into the `State` prototype.
*/
const DOCUMENT_LIKE_METHODS = [
'deleteAtRange',
'deleteBackwardAtRange',
'deleteForwardAtRange',
'insertAtRange',
'splitAtRange'
]
/**
* State.
*/
class State extends Record(DEFAULT_PROPERTIES) {
/**
* Create a new `State` with `properties`.
*
* @param {Objetc} properties
* @return {State} state
*/
static create(properties = {}) {
return new State(properties)
}
/**
* Return a new `Transform` with the current state as a starting point.
*
* @return {Transform} transform
*/
transform() {
return new Transform({ state: this })
}
/**
* Delete a single character.
*
* @return {State} state
*/
delete() {
let state = this
let { document, selection } = state
// When collapsed, there's nothing to do.
if (selection.isCollapsed) return state
// Otherwise, delete and update the selection.
document = document.deleteAtRange(selection)
selection = selection.moveToStart()
state = state.merge({ document, selection })
return state
}
/**
* Delete backward `n` characters at the current selection.
*
* @param {Number} n (optional)
* @return {State} state
*/
deleteBackward(n = 1) {
let state = this
let { document, selection } = state
let after = selection
// Determine what the selection should be after deleting.
const { startKey } = selection
const startNode = document.getNode(startKey)
if (selection.isExpanded) {
after = selection.moveToStart()
}
else if (selection.isAtStartOf(startNode)) {
const parent = document.getParentNode(startNode)
const previous = document.getPreviousNode(parent).nodes.first()
after = selection.moveToEndOf(previous)
}
else if (!selection.isAtEndOf(document)) {
after = selection.moveBackward(n)
}
// Delete backward and then update the selection.
document = document.deleteBackwardAtRange(selection)
selection = after
state = state.merge({ document, selection })
return state
}
/**
* Delete forward `n` characters at the current selection.
*
* @param {Number} n (optional)
* @return {State} state
*/
deleteForward(n = 1) {
let state = this
let { document, selection } = state
let after = selection
// Determine what the selection should be after deleting.
if (selection.isExpanded) {
after = selection.moveToStart()
}
// Delete forward and then update the selection.
document = document.deleteForwardAtRange(selection)
selection = after
state = state.merge({ document, selection })
return state
}
/**
* Insert a `text` string at the current cursor position.
*
* @param {String} text
* @return {State} state
*/
insertText(text) {
let state = this
let { document, selection } = state
// Insert the text and update the selection.
document = document.insertTextAtRange(selection, text)
selection = selection.moveForward(text.length)
state = state.merge({ document, selection })
return state
}
/**
* Split at a the current cursor position.
*
* @return {State} state
*/
split() {
let state = this
let { document, selection } = state
let after
// Split the document.
document = document.splitAtRange(selection)
// 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)
state = state.merge({ document, selection })
return state
}
}
/**
* Mix in node-like methods.
*/
DOCUMENT_LIKE_METHODS.forEach((method) => {
State.prototype[method] = function (...args) {
let { document } = this
document = document[method](...args)
return this.merge({ document })
}
})
/**
* Export.
*/
export default State