mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-02-24 17:23:07 +01:00
210 lines
4.4 KiB
JavaScript
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
|