mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-31 02:49:56 +02:00
refactor decorations to use selections (#1221)
* refactor decorations to use selections * update docs * cleanup * add Selection.createList * fix tests * fix for nested blocks * fix lint * actually merge * revert small change * add state.decorations, with search example
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
"keycode": "^2.1.2",
|
||||
"prop-types": "^15.5.8",
|
||||
"react-portal": "^3.1.0",
|
||||
"react-immutable-proptypes": "^2.1.0",
|
||||
"selection-is-backward": "^1.0.0",
|
||||
"slate-base64-serializer": "^0.1.11",
|
||||
"slate-dev-logger": "^0.1.12",
|
||||
|
@@ -12,13 +12,13 @@ import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
import Node from './node'
|
||||
import extendSelection from '../utils/extend-selection'
|
||||
import findClosestNode from '../utils/find-closest-node'
|
||||
import getCaretPosition from '../utils/get-caret-position'
|
||||
import findDropPoint from '../utils/find-drop-point'
|
||||
import findNativePoint from '../utils/find-native-point'
|
||||
import findPoint from '../utils/find-point'
|
||||
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
|
||||
import getPoint from '../utils/get-point'
|
||||
import getDropPoint from '../utils/get-drop-point'
|
||||
import getTransferData from '../utils/get-transfer-data'
|
||||
import setTransferData from '../utils/set-transfer-data'
|
||||
import scrollToSelection from '../utils/scroll-to-selection'
|
||||
import setTransferData from '../utils/set-transfer-data'
|
||||
import { IS_FIREFOX, IS_MAC, IS_IE } from '../constants/environment'
|
||||
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
updateSelection = () => {
|
||||
const { editor, state } = this.props
|
||||
const { state } = this.props
|
||||
const { selection } = state
|
||||
const window = getWindow(this.element)
|
||||
const native = window.getSelection()
|
||||
@@ -144,10 +144,8 @@ class Content extends React.Component {
|
||||
|
||||
// Otherwise, figure out which DOM nodes should be selected...
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset, isCollapsed } = selection
|
||||
const anchor = getCaretPosition(anchorKey, anchorOffset, state, editor, this.element)
|
||||
const focus = isCollapsed
|
||||
? anchor
|
||||
: getCaretPosition(focusKey, focusOffset, state, editor, this.element)
|
||||
const anchor = findNativePoint(anchorKey, anchorOffset)
|
||||
const focus = isCollapsed ? anchor : findNativePoint(focusKey, focusOffset)
|
||||
|
||||
// If they are already selected, do nothing.
|
||||
if (
|
||||
@@ -432,12 +430,11 @@ class Content extends React.Component {
|
||||
|
||||
if (this.props.readOnly) return
|
||||
|
||||
const { editor, state } = this.props
|
||||
const { state } = this.props
|
||||
const { nativeEvent } = event
|
||||
const { dataTransfer } = nativeEvent
|
||||
const data = getTransferData(dataTransfer)
|
||||
const point = getDropPoint(event, state, editor)
|
||||
|
||||
const point = findDropPoint(event, state)
|
||||
if (!point) return
|
||||
|
||||
// Add drop-specific information to the data.
|
||||
@@ -484,26 +481,33 @@ class Content extends React.Component {
|
||||
// Get the selection point.
|
||||
const native = window.getSelection()
|
||||
const { anchorNode, anchorOffset } = native
|
||||
const point = getPoint(anchorNode, anchorOffset, state, editor)
|
||||
const point = findPoint(anchorNode, anchorOffset, state)
|
||||
if (!point) return
|
||||
|
||||
// Get the range in question.
|
||||
const { key, index, start, end } = point
|
||||
// Get the text node and range in question.
|
||||
const { document, selection } = state
|
||||
const schema = editor.getSchema()
|
||||
const decorators = document.getDescendantDecorators(key, schema)
|
||||
const node = document.getDescendant(key)
|
||||
const block = document.getClosestBlock(node.key)
|
||||
const ranges = node.getRanges(decorators)
|
||||
const lastText = block.getLastText()
|
||||
const node = document.getDescendant(point.key)
|
||||
const ranges = node.getRanges()
|
||||
let start = 0
|
||||
let end = 0
|
||||
|
||||
const range = ranges.find((r) => {
|
||||
end += r.text.length
|
||||
if (end >= point.offset) return true
|
||||
start = end
|
||||
})
|
||||
|
||||
// Get the text information.
|
||||
const { text } = range
|
||||
let { textContent } = anchorNode
|
||||
const block = document.getClosestBlock(node.key)
|
||||
const lastText = block.getLastText()
|
||||
const lastRange = ranges.last()
|
||||
const lastChar = textContent.charAt(textContent.length - 1)
|
||||
const isLastText = node == lastText
|
||||
const isLastRange = index == ranges.size - 1
|
||||
const isLastRange = range == lastRange
|
||||
|
||||
// If we're dealing with the last leaf, and the DOM text ends in a new line,
|
||||
// COMPAT: If this is the last range, and the DOM text ends in a new line,
|
||||
// we will have added another new line in <Leaf>'s render method to account
|
||||
// for browsers collapsing a single trailing new lines, so remove it.
|
||||
if (isLastText && isLastRange && lastChar == '\n') {
|
||||
@@ -511,26 +515,20 @@ class Content extends React.Component {
|
||||
}
|
||||
|
||||
// If the text is no different, abort.
|
||||
const range = ranges.get(index)
|
||||
const { text, marks } = range
|
||||
if (textContent == text) return
|
||||
|
||||
// Determine what the selection should be after changing the text.
|
||||
const delta = textContent.length - text.length
|
||||
const after = selection.collapseToEnd().move(delta)
|
||||
const corrected = selection.collapseToEnd().move(delta)
|
||||
const entire = selection.moveAnchorTo(point.key, start).moveFocusTo(point.key, end)
|
||||
|
||||
// Change the current state to have the text replaced.
|
||||
// Change the current state to have the range's text replaced.
|
||||
editor.change((change) => {
|
||||
change
|
||||
.select({
|
||||
anchorKey: key,
|
||||
anchorOffset: start,
|
||||
focusKey: key,
|
||||
focusOffset: end
|
||||
})
|
||||
.select(entire)
|
||||
.delete()
|
||||
.insertText(textContent, marks)
|
||||
.select(after)
|
||||
.insertText(textContent, range.marks)
|
||||
.select(corrected)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -677,7 +675,7 @@ class Content extends React.Component {
|
||||
if (!this.isInEditor(event.target)) return
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { state, editor } = this.props
|
||||
const { state } = this.props
|
||||
const { document, selection } = state
|
||||
const native = window.getSelection()
|
||||
const data = {}
|
||||
@@ -690,8 +688,8 @@ class Content extends React.Component {
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
else {
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset } = native
|
||||
const anchor = getPoint(anchorNode, anchorOffset, state, editor)
|
||||
const focus = getPoint(focusNode, focusOffset, state, editor)
|
||||
const anchor = findPoint(anchorNode, anchorOffset, state)
|
||||
const focus = findPoint(focusNode, focusOffset, state)
|
||||
if (!anchor || !focus) return
|
||||
|
||||
// There are situations where a select event will fire with a new native
|
||||
@@ -872,11 +870,14 @@ class Content extends React.Component {
|
||||
|
||||
renderNode = (child, isSelected) => {
|
||||
const { editor, readOnly, schema, state } = this.props
|
||||
const { document } = state
|
||||
const { document, decorations } = state
|
||||
let decs = document.getDecorations(schema)
|
||||
if (decorations) decs = decorations.concat(decs)
|
||||
return (
|
||||
<Node
|
||||
block={null}
|
||||
editor={editor}
|
||||
decorations={decs}
|
||||
isSelected={isSelected}
|
||||
key={child.key}
|
||||
node={child}
|
||||
|
@@ -100,6 +100,37 @@ class Leaf extends React.Component {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all of the leaf's mark components.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMarks(props) {
|
||||
const { marks, schema, node, offset, text, state, editor } = props
|
||||
const children = this.renderText(props)
|
||||
|
||||
return marks.reduce((memo, mark) => {
|
||||
const Component = mark.getComponent(schema)
|
||||
if (!Component) return memo
|
||||
return (
|
||||
<Component
|
||||
editor={editor}
|
||||
mark={mark}
|
||||
marks={marks}
|
||||
node={node}
|
||||
offset={offset}
|
||||
schema={schema}
|
||||
state={state}
|
||||
text={text}
|
||||
>
|
||||
{memo}
|
||||
</Component>
|
||||
)
|
||||
}, children)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the text content of the leaf, accounting for browsers.
|
||||
*
|
||||
@@ -136,37 +167,6 @@ class Leaf extends React.Component {
|
||||
return text
|
||||
}
|
||||
|
||||
/**
|
||||
* Render all of the leaf's mark components.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMarks(props) {
|
||||
const { marks, schema, node, offset, text, state, editor } = props
|
||||
const children = this.renderText(props)
|
||||
|
||||
return marks.reduce((memo, mark) => {
|
||||
const Component = mark.getComponent(schema)
|
||||
if (!Component) return memo
|
||||
return (
|
||||
<Component
|
||||
editor={editor}
|
||||
mark={mark}
|
||||
marks={marks}
|
||||
node={node}
|
||||
offset={offset}
|
||||
schema={schema}
|
||||
state={state}
|
||||
text={text}
|
||||
>
|
||||
{memo}
|
||||
</Component>
|
||||
)
|
||||
}, children)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,7 @@
|
||||
|
||||
import Base64 from 'slate-base64-serializer'
|
||||
import Debug from 'debug'
|
||||
import ImmutableTypes from 'react-immutable-proptypes'
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import logger from 'slate-dev-logger'
|
||||
@@ -35,6 +36,7 @@ class Node extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
block: SlateTypes.block,
|
||||
decorations: ImmutableTypes.list.isRequired,
|
||||
editor: Types.object.isRequired,
|
||||
isSelected: Types.bool.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
@@ -136,6 +138,9 @@ class Node extends React.Component {
|
||||
// need to be rendered again.
|
||||
if (n.isSelected || p.isSelected) return true
|
||||
|
||||
// If the decorations have changed, update.
|
||||
if (!n.decorations.equals(p.decorations)) return true
|
||||
|
||||
// Otherwise, don't update.
|
||||
return false
|
||||
}
|
||||
@@ -225,11 +230,13 @@ class Node extends React.Component {
|
||||
*/
|
||||
|
||||
renderNode = (child, isSelected) => {
|
||||
const { block, editor, node, readOnly, schema, state } = this.props
|
||||
const { block, decorations, editor, node, readOnly, schema, state } = this.props
|
||||
const Component = child.kind === 'text' ? Text : Node
|
||||
const decs = decorations.concat(node.getDecorations(schema))
|
||||
return (
|
||||
<Component
|
||||
block={node.kind == 'block' ? node : block}
|
||||
decorations={decs}
|
||||
editor={editor}
|
||||
isSelected={isSelected}
|
||||
key={child.key}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
|
||||
import Debug from 'debug'
|
||||
import ImmutableTypes from 'react-immutable-proptypes'
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
@@ -24,6 +25,7 @@ class Text extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
block: SlateTypes.block,
|
||||
decorations: ImmutableTypes.list.isRequired,
|
||||
editor: Types.object.isRequired,
|
||||
node: SlateTypes.node.isRequired,
|
||||
parent: SlateTypes.node.isRequired,
|
||||
@@ -63,16 +65,6 @@ class Text extends React.Component {
|
||||
// for simplicity we just let them through.
|
||||
if (n.node != p.node) return true
|
||||
|
||||
// Re-render if the current decorations have changed, even if the content of
|
||||
// the text node itself hasn't.
|
||||
if (n.schema.hasDecorators) {
|
||||
const nDecorators = n.state.document.getDescendantDecorators(n.node.key, n.schema)
|
||||
const pDecorators = p.state.document.getDescendantDecorators(p.node.key, p.schema)
|
||||
const nRanges = n.node.getRanges(nDecorators)
|
||||
const pRanges = p.node.getRanges(pDecorators)
|
||||
if (!nRanges.equals(pRanges)) return true
|
||||
}
|
||||
|
||||
// If the node parent is a block node, and it was the last child of the
|
||||
// block, re-render to cleanup extra `<br/>` or `\n`.
|
||||
if (n.parent.kind == 'block') {
|
||||
@@ -81,6 +73,9 @@ class Text extends React.Component {
|
||||
if (p.node == pLast && n.node != nLast) return true
|
||||
}
|
||||
|
||||
// Re-render if the current decorations have changed.
|
||||
if (!n.decorations.equals(p.decorations)) return true
|
||||
|
||||
// Otherwise, don't update.
|
||||
return false
|
||||
}
|
||||
@@ -95,10 +90,19 @@ class Text extends React.Component {
|
||||
const { props } = this
|
||||
this.debug('render', { props })
|
||||
|
||||
const { node, schema, state } = props
|
||||
const { decorations, node, state } = props
|
||||
const { document } = state
|
||||
const decorators = schema.hasDecorators ? document.getDescendantDecorators(node.key, schema) : []
|
||||
const ranges = node.getRanges(decorators)
|
||||
const { key } = node
|
||||
|
||||
const decs = decorations.filter((d) => {
|
||||
const { startKey, endKey } = d
|
||||
if (startKey == key || endKey == key) return true
|
||||
const startsBefore = document.areDescendantsSorted(startKey, key)
|
||||
const endsAfter = document.areDescendantsSorted(key, endKey)
|
||||
return startsBefore && endsAfter
|
||||
})
|
||||
|
||||
const ranges = node.getRanges(decs)
|
||||
let offset = 0
|
||||
|
||||
const leaves = ranges.map((range, i) => {
|
||||
@@ -108,7 +112,7 @@ class Text extends React.Component {
|
||||
})
|
||||
|
||||
return (
|
||||
<span data-key={node.key}>
|
||||
<span data-key={key}>
|
||||
{leaves}
|
||||
</span>
|
||||
)
|
||||
|
@@ -8,8 +8,8 @@ import { Block, Inline, coreSchema } from 'slate'
|
||||
|
||||
import Content from '../components/content'
|
||||
import Placeholder from '../components/placeholder'
|
||||
import getPoint from '../utils/get-point'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
||||
|
||||
/**
|
||||
@@ -64,10 +64,9 @@ function Plugin(options = {}) {
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBeforeInput(e, data, change, editor) {
|
||||
function onBeforeInput(e, data, change) {
|
||||
debug('onBeforeInput', { data })
|
||||
e.preventDefault()
|
||||
|
||||
@@ -82,8 +81,8 @@ function Plugin(options = {}) {
|
||||
// the selection has gotten out of sync, and adjust it if so. (03/18/2017)
|
||||
const window = getWindow(e.target)
|
||||
const native = window.getSelection()
|
||||
const a = getPoint(native.anchorNode, native.anchorOffset, state, editor)
|
||||
const f = getPoint(native.focusNode, native.focusOffset, state, editor)
|
||||
const a = findPoint(native.anchorNode, native.anchorOffset, state)
|
||||
const f = findPoint(native.focusNode, native.focusOffset, state)
|
||||
const hasMismatch = a && f && (
|
||||
anchorKey != a.key ||
|
||||
anchorOffset != a.offset ||
|
||||
|
@@ -1,16 +1,22 @@
|
||||
|
||||
import { Node } from 'slate'
|
||||
|
||||
/**
|
||||
* Find the DOM node for a `node`.
|
||||
* Find the DOM node for a `key`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @param {String|Node} key
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function findDOMNode(node) {
|
||||
const el = window.document.querySelector(`[data-key="${node.key}"]`)
|
||||
function findDOMNode(key) {
|
||||
if (Node.isNode(key)) {
|
||||
key = key.key
|
||||
}
|
||||
|
||||
const el = window.document.querySelector(`[data-key="${key}"]`)
|
||||
|
||||
if (!el) {
|
||||
throw new Error(`Unable to find a DOM node for "${node.key}". This is often because of forgetting to add \`props.attributes\` to a component returned from \`renderNode\`.`)
|
||||
throw new Error(`Unable to find a DOM node for "${key}". This is often because of forgetting to add \`props.attributes\` to a component returned from \`renderNode\`.`)
|
||||
}
|
||||
|
||||
return el
|
||||
|
@@ -2,18 +2,17 @@
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import findClosestNode from './find-closest-node'
|
||||
import getPoint from './get-point'
|
||||
import findPoint from './find-point'
|
||||
|
||||
/**
|
||||
* Get the target point for a drop event.
|
||||
* Find the target point for a drop `event`.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function getDropPoint(event, state, editor) {
|
||||
function findDropPoint(event, state) {
|
||||
const { document } = state
|
||||
const { nativeEvent, target } = event
|
||||
const { x, y } = nativeEvent
|
||||
@@ -48,7 +47,6 @@ function getDropPoint(event, state, editor) {
|
||||
document.getNextSibling(nodeKey)
|
||||
const key = text.key
|
||||
const offset = previous ? text.characters.size : 0
|
||||
|
||||
return { key, offset }
|
||||
}
|
||||
|
||||
@@ -71,12 +69,10 @@ function getDropPoint(event, state, editor) {
|
||||
const text = block.getLastText()
|
||||
const { key } = text
|
||||
const offset = 0
|
||||
|
||||
return { key, offset }
|
||||
}
|
||||
|
||||
const point = getPoint(n, o, state, editor)
|
||||
|
||||
const point = findPoint(n, o, state)
|
||||
return point
|
||||
}
|
||||
|
||||
@@ -86,4 +82,4 @@ function getDropPoint(event, state, editor) {
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default getDropPoint
|
||||
export default findDropPoint
|
45
packages/slate-react/src/utils/find-native-point.js
Normal file
45
packages/slate-react/src/utils/find-native-point.js
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import findDOMNode from './find-dom-node'
|
||||
|
||||
/**
|
||||
* Find a native DOM selection point from a Slate `key` and `offset`.
|
||||
*
|
||||
* @param {Element} root
|
||||
* @param {String} key
|
||||
* @param {Number} offset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findNativePoint(key, offset) {
|
||||
const el = findDOMNode(key)
|
||||
if (!el) return null
|
||||
|
||||
const window = getWindow(el)
|
||||
const iterator = window.document.createNodeIterator(el, NodeFilter.SHOW_TEXT)
|
||||
let start = 0
|
||||
let n
|
||||
|
||||
while (n = iterator.nextNode()) {
|
||||
const { length } = n.textContent
|
||||
const end = start + length
|
||||
|
||||
if (offset <= end) {
|
||||
const o = offset - start
|
||||
return { node: n, offset: o }
|
||||
}
|
||||
|
||||
start = end
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findNativePoint
|
84
packages/slate-react/src/utils/find-point.js
Normal file
84
packages/slate-react/src/utils/find-point.js
Normal file
@@ -0,0 +1,84 @@
|
||||
|
||||
import getWindow from 'get-window'
|
||||
|
||||
import OffsetKey from './offset-key'
|
||||
import normalizeNodeAndOffset from './normalize-node-and-offset'
|
||||
import findClosestNode from './find-closest-node'
|
||||
|
||||
/**
|
||||
* Constants.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const OFFSET_KEY_ATTRIBUTE = 'data-offset-key'
|
||||
const RANGE_SELECTOR = `[${OFFSET_KEY_ATTRIBUTE}]`
|
||||
const TEXT_SELECTOR = `[data-key]`
|
||||
const VOID_SELECTOR = '[data-slate-void]'
|
||||
|
||||
/**
|
||||
* Find a Slate point from a DOM selection's `nativeNode` and `nativeOffset`.
|
||||
*
|
||||
* @param {Element} nativeNode
|
||||
* @param {Number} nativeOffset
|
||||
* @param {State} state
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findPoint(nativeNode, nativeOffset, state) {
|
||||
const {
|
||||
node: nearestNode,
|
||||
offset: nearestOffset,
|
||||
} = normalizeNodeAndOffset(nativeNode, nativeOffset)
|
||||
|
||||
const window = getWindow(nativeNode)
|
||||
const { parentNode } = nearestNode
|
||||
let rangeNode = findClosestNode(parentNode, RANGE_SELECTOR)
|
||||
let offset
|
||||
let node
|
||||
|
||||
// Calculate how far into the text node the `nearestNode` is, so that we can
|
||||
// determine what the offset relative to the text node is.
|
||||
if (rangeNode) {
|
||||
const range = window.document.createRange()
|
||||
const textNode = findClosestNode(rangeNode, TEXT_SELECTOR)
|
||||
range.setStart(textNode, 0)
|
||||
range.setEnd(nearestNode, nearestOffset)
|
||||
node = textNode
|
||||
offset = range.toString().length
|
||||
}
|
||||
|
||||
// For void nodes, the element with the offset key will be a cousin, not an
|
||||
// ancestor, so find it by going down from the nearest void parent.
|
||||
else {
|
||||
const voidNode = findClosestNode(parentNode, VOID_SELECTOR)
|
||||
if (!voidNode) return null
|
||||
rangeNode = voidNode.querySelector(RANGE_SELECTOR)
|
||||
node = rangeNode
|
||||
offset = node.textContent.length
|
||||
}
|
||||
|
||||
// Get the string value of the offset key attribute.
|
||||
const offsetKey = rangeNode.getAttribute(OFFSET_KEY_ATTRIBUTE)
|
||||
if (!offsetKey) return null
|
||||
|
||||
const { key } = OffsetKey.parse(offsetKey)
|
||||
|
||||
// COMPAT: If someone is clicking from one Slate editor into another, the
|
||||
// select event fires twice, once for the old editor's `element` first, and
|
||||
// then afterwards for the correct `element`. (2017/03/03)
|
||||
if (!state.document.hasDescendant(key)) return null
|
||||
|
||||
return {
|
||||
key,
|
||||
offset,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default findPoint
|
@@ -1,46 +0,0 @@
|
||||
|
||||
import findDeepestNode from './find-deepest-node'
|
||||
|
||||
/**
|
||||
* Get caret position from selection point.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Number} offset
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @param {Element} el
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function getCaretPosition(key, offset, state, editor, el) {
|
||||
const { document } = state
|
||||
const text = document.getDescendant(key)
|
||||
const schema = editor.getSchema()
|
||||
const decorators = document.getDescendantDecorators(key, schema)
|
||||
const ranges = text.getRanges(decorators)
|
||||
|
||||
let a = 0
|
||||
let index
|
||||
let off
|
||||
|
||||
ranges.forEach((range, i) => {
|
||||
const { length } = range.text
|
||||
a += length
|
||||
if (a < offset) return
|
||||
index = i
|
||||
off = offset - (a - length)
|
||||
return false
|
||||
})
|
||||
|
||||
const span = el.querySelector(`[data-offset-key="${key}-${index}"]`)
|
||||
const node = findDeepestNode(span)
|
||||
return { node, offset: off }
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default getCaretPosition
|
@@ -1,41 +0,0 @@
|
||||
|
||||
import OffsetKey from './offset-key'
|
||||
|
||||
/**
|
||||
* Get a point from a native selection's DOM `element` and `offset`.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {Number} offset
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function getPoint(element, offset, state, editor) {
|
||||
const { document } = state
|
||||
const schema = editor.getSchema()
|
||||
|
||||
// If we can't find an offset key, we can't get a point.
|
||||
const offsetKey = OffsetKey.findKey(element, offset)
|
||||
if (!offsetKey) return null
|
||||
|
||||
// COMPAT: If someone is clicking from one Slate editor into another, the
|
||||
// select event fires two, once for the old editor's `element` first, and
|
||||
// then afterwards for the correct `element`. (2017/03/03)
|
||||
const { key } = offsetKey
|
||||
const node = document.getDescendant(key)
|
||||
if (!node) return null
|
||||
|
||||
const decorators = document.getDescendantDecorators(key, schema)
|
||||
const ranges = node.getRanges(decorators)
|
||||
const point = OffsetKey.findPoint(offsetKey, ranges)
|
||||
return point
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default getPoint
|
@@ -1,7 +1,4 @@
|
||||
|
||||
import normalizeNodeAndOffset from './normalize-node-and-offset'
|
||||
import findClosestNode from './find-closest-node'
|
||||
|
||||
/**
|
||||
* Offset key parser regex.
|
||||
*
|
||||
@@ -10,117 +7,6 @@ import findClosestNode from './find-closest-node'
|
||||
|
||||
const PARSER = /^(\w+)(?:-(\d+))?$/
|
||||
|
||||
/**
|
||||
* Offset key attribute name.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const ATTRIBUTE = 'data-offset-key'
|
||||
|
||||
/**
|
||||
* Offset key attribute selector.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const SELECTOR = `[${ATTRIBUTE}]`
|
||||
|
||||
/**
|
||||
* Void node selection.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const VOID_SELECTOR = '[data-slate-void]'
|
||||
|
||||
/**
|
||||
* Find the start and end bounds from an `offsetKey` and `ranges`.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {List<Range>} ranges
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findBounds(index, ranges) {
|
||||
const range = ranges.get(index)
|
||||
const start = ranges
|
||||
.slice(0, index)
|
||||
.reduce((memo, r) => {
|
||||
return memo += r.text.length
|
||||
}, 0)
|
||||
|
||||
return {
|
||||
start,
|
||||
end: start + range.text.length
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From a DOM node, find the closest parent's offset key.
|
||||
*
|
||||
* @param {Element} rawNode
|
||||
* @param {Number} rawOffset
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findKey(rawNode, rawOffset) {
|
||||
let { node, offset } = normalizeNodeAndOffset(rawNode, rawOffset)
|
||||
const { parentNode } = node
|
||||
|
||||
// Find the closest parent with an offset key attribute.
|
||||
let closest = findClosestNode(parentNode, SELECTOR)
|
||||
|
||||
// For void nodes, the element with the offset key will be a cousin, not an
|
||||
// ancestor, so find it by going down from the nearest void parent.
|
||||
if (!closest) {
|
||||
const closestVoid = findClosestNode(parentNode, VOID_SELECTOR)
|
||||
if (!closestVoid) return null
|
||||
closest = closestVoid.querySelector(SELECTOR)
|
||||
offset = closest.textContent.length
|
||||
}
|
||||
|
||||
// Get the string value of the offset key attribute.
|
||||
const offsetKey = closest.getAttribute(ATTRIBUTE)
|
||||
|
||||
// If we still didn't find an offset key, abort.
|
||||
if (!offsetKey) return null
|
||||
|
||||
// Return the parsed the offset key.
|
||||
const parsed = parse(offsetKey)
|
||||
return {
|
||||
key: parsed.key,
|
||||
index: parsed.index,
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the selection point from an `offsetKey` and `ranges`.
|
||||
*
|
||||
* @param {Object} offsetKey
|
||||
* @param {List<Range>} ranges
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function findPoint(offsetKey, ranges) {
|
||||
let { key, index, offset } = offsetKey
|
||||
const { start, end } = findBounds(index, ranges)
|
||||
|
||||
// Don't let the offset be outside of the start and end bounds.
|
||||
offset = start + offset
|
||||
offset = Math.max(offset, start)
|
||||
offset = Math.min(offset, end)
|
||||
|
||||
return {
|
||||
key,
|
||||
index,
|
||||
start,
|
||||
end,
|
||||
offset
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an offset key `string`.
|
||||
*
|
||||
@@ -158,9 +44,6 @@ function stringify(object) {
|
||||
*/
|
||||
|
||||
export default {
|
||||
findBounds,
|
||||
findKey,
|
||||
findPoint,
|
||||
parse,
|
||||
stringify
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
/** @jsx h */
|
||||
|
||||
import h from '../../helpers/h'
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
paragraph: {
|
||||
decorate(text, block) {
|
||||
let { characters } = text
|
||||
let second = characters.get(1)
|
||||
const mark = Mark.create({ type: 'bold' })
|
||||
const marks = second.marks.add(mark)
|
||||
second = second.merge({ marks })
|
||||
characters = characters.set(1, second)
|
||||
return characters
|
||||
decorate(block) {
|
||||
const text = block.getFirstText()
|
||||
return [{
|
||||
anchorKey: text.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: text.key,
|
||||
focusOffset: 2,
|
||||
marks: [{ type: 'bold' }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@@ -1,4 +1,6 @@
|
||||
|
||||
import State from '../models/state'
|
||||
|
||||
/**
|
||||
* Changes.
|
||||
*
|
||||
@@ -8,20 +10,20 @@
|
||||
const Changes = {}
|
||||
|
||||
/**
|
||||
* Set `properties` on the top-level state's data.
|
||||
* Set `properties` on the state.
|
||||
*
|
||||
* @param {Change} change
|
||||
* @param {Object} properties
|
||||
* @param {Object|State} properties
|
||||
*/
|
||||
|
||||
Changes.setData = (change, properties) => {
|
||||
Changes.setState = (change, properties) => {
|
||||
properties = State.createProperties(properties)
|
||||
const { state } = change
|
||||
const { data } = state
|
||||
|
||||
change.applyOperation({
|
||||
type: 'set_data',
|
||||
type: 'set_state',
|
||||
properties,
|
||||
data,
|
||||
state,
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -165,19 +165,12 @@ class Node {
|
||||
first = normalizeKey(first)
|
||||
second = normalizeKey(second)
|
||||
|
||||
let sorted
|
||||
const keys = this.getKeysAsArray()
|
||||
const firstIndex = keys.indexOf(first)
|
||||
const secondIndex = keys.indexOf(second)
|
||||
if (firstIndex == -1 || secondIndex == -1) return null
|
||||
|
||||
this.forEachDescendant((n) => {
|
||||
if (n.key === first) {
|
||||
sorted = true
|
||||
return false
|
||||
} else if (n.key === second) {
|
||||
sorted = false
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return sorted
|
||||
return firstIndex < secondIndex
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -609,8 +602,8 @@ class Node {
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
getDecorators(schema) {
|
||||
return schema.__getDecorators(this)
|
||||
getDecorations(schema) {
|
||||
return schema.__getDecorations(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -674,32 +667,6 @@ class Node {
|
||||
return descendant
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the decorators for a descendant by `key` given a `schema`.
|
||||
*
|
||||
* @param {String} key
|
||||
* @param {Schema} schema
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
getDescendantDecorators(key, schema) {
|
||||
if (!schema.hasDecorators) {
|
||||
return []
|
||||
}
|
||||
|
||||
const descendant = this.assertDescendant(key)
|
||||
let child = this.getFurthestAncestor(key)
|
||||
let decorators = []
|
||||
|
||||
while (child != descendant) {
|
||||
decorators = decorators.concat(child.getDecorators(schema))
|
||||
child = child.getFurthestAncestor(key)
|
||||
}
|
||||
|
||||
decorators = decorators.concat(descendant.getDecorators(schema))
|
||||
return decorators
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first child text node.
|
||||
*
|
||||
@@ -958,18 +925,29 @@ class Node {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a set of all keys in the node.
|
||||
* Return a set of all keys in the node as an array.
|
||||
*
|
||||
* @return {Set<String>}
|
||||
* @return {Array<String>}
|
||||
*/
|
||||
|
||||
getKeys() {
|
||||
getKeysAsArray() {
|
||||
const keys = []
|
||||
|
||||
this.forEachDescendant((desc) => {
|
||||
keys.push(desc.key)
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a set of all keys in the node.
|
||||
*
|
||||
* @return {Set<String>}
|
||||
*/
|
||||
|
||||
getKeys() {
|
||||
const keys = this.getKeysAsArray()
|
||||
return new Set(keys)
|
||||
}
|
||||
|
||||
@@ -2102,6 +2080,7 @@ memoize(Node.prototype, [
|
||||
'getInlines',
|
||||
'getInlinesAsArray',
|
||||
'getKeys',
|
||||
'getKeysAsArray',
|
||||
'getLastText',
|
||||
'getMarks',
|
||||
'getOrderedMarks',
|
||||
@@ -2135,11 +2114,10 @@ memoize(Node.prototype, [
|
||||
'getClosestVoid',
|
||||
'getCommonAncestor',
|
||||
'getComponent',
|
||||
'getDecorators',
|
||||
'getDecorations',
|
||||
'getDepth',
|
||||
'getDescendant',
|
||||
'getDescendantAtPath',
|
||||
'getDescendantDecorators',
|
||||
'getFragmentAtRange',
|
||||
'getFurthestBlock',
|
||||
'getFurthestInline',
|
||||
|
@@ -7,6 +7,7 @@ import typeOf from 'type-of'
|
||||
import { Record } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Selection from '../models/selection'
|
||||
import isReactComponent from '../utils/is-react-component'
|
||||
|
||||
/**
|
||||
@@ -126,24 +127,33 @@ class Schema extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the decorators for an `object`.
|
||||
* Return the decorations for an `object`.
|
||||
*
|
||||
* This method is private, because it should always be called on one of the
|
||||
* often-changing immutable objects instead, since it will be memoized for
|
||||
* much better performance.
|
||||
*
|
||||
* @param {Mixed} object
|
||||
* @return {Array}
|
||||
* @return {List<Selection>}
|
||||
*/
|
||||
|
||||
__getDecorators(object) {
|
||||
return this.rules
|
||||
.filter(rule => rule.decorate && rule.match(object))
|
||||
.map((rule) => {
|
||||
return (text) => {
|
||||
return rule.decorate(text, object)
|
||||
}
|
||||
__getDecorations(object) {
|
||||
const array = []
|
||||
|
||||
this.rules.forEach((rule) => {
|
||||
if (!rule.decorate) return
|
||||
if (!rule.match(object)) return
|
||||
|
||||
const decorations = rule.decorate(object)
|
||||
if (!decorations.length) return
|
||||
|
||||
decorations.forEach((dec) => {
|
||||
array.push(dec)
|
||||
})
|
||||
})
|
||||
|
||||
const list = Selection.createList(array)
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,9 +1,10 @@
|
||||
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { Record } from 'immutable'
|
||||
import { List, Record, Set } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import Mark from './mark'
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
@@ -48,6 +49,22 @@ class Selection extends Record(DEFAULTS) {
|
||||
throw new Error(`\`Selection.create\` only accepts objects or selections, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of `Selections` from a `value`.
|
||||
*
|
||||
* @param {Array<Selection|Object>|List<Selection|Object>} value
|
||||
* @return {List<Selection>}
|
||||
*/
|
||||
|
||||
static createList(value = []) {
|
||||
if (List.isList(value) || Array.isArray(value)) {
|
||||
const list = new List(value.map(Selection.create))
|
||||
return list
|
||||
}
|
||||
|
||||
throw new Error(`\`Selection.createList\` only accepts arrays or lists, but you passed it: ${value}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dictionary of settable selection properties from `attrs`.
|
||||
*
|
||||
@@ -108,7 +125,7 @@ class Selection extends Record(DEFAULTS) {
|
||||
focusOffset,
|
||||
isBackward,
|
||||
isFocused,
|
||||
marks,
|
||||
marks: marks == null ? null : new Set(marks.map(Mark.fromJSON)),
|
||||
})
|
||||
|
||||
return selection
|
||||
|
@@ -5,7 +5,7 @@ import { Record, Set, List, Map } from 'immutable'
|
||||
|
||||
import MODEL_TYPES from '../constants/model-types'
|
||||
import SCHEMA from '../schemas/core'
|
||||
import Change from './change'
|
||||
import Data from './data'
|
||||
import Document from './document'
|
||||
import History from './history'
|
||||
import Selection from './selection'
|
||||
@@ -21,6 +21,7 @@ const DEFAULTS = {
|
||||
selection: Selection.create(),
|
||||
history: History.create(),
|
||||
data: new Map(),
|
||||
decorations: null,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +52,31 @@ class State extends Record(DEFAULTS) {
|
||||
throw new Error(`\`State.create\` only accepts objects or states, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a dictionary of settable state properties from `attrs`.
|
||||
*
|
||||
* @param {Object|State} attrs
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
static createProperties(attrs = {}) {
|
||||
if (State.isState(attrs)) {
|
||||
return {
|
||||
data: attrs.data,
|
||||
decorations: attrs.decorations,
|
||||
}
|
||||
}
|
||||
|
||||
if (isPlainObject(attrs)) {
|
||||
const props = {}
|
||||
if ('data' in attrs) props.data = Data.create(attrs.data)
|
||||
if ('decorations' in attrs) props.decorations = Selection.createList(attrs.decorations)
|
||||
return props
|
||||
}
|
||||
|
||||
throw new Error(`\`State.createProperties\` only accepts objects or states, but you passed it: ${attrs}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `State` from a JSON `object`.
|
||||
*
|
||||
@@ -549,6 +575,7 @@ class State extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
change(attrs = {}) {
|
||||
const Change = require('./change').default
|
||||
return new Change({ ...attrs, state: this })
|
||||
}
|
||||
|
||||
@@ -572,11 +599,20 @@ class State extends Record(DEFAULTS) {
|
||||
|
||||
toJSON(options = {}) {
|
||||
const object = {
|
||||
kind: this.kind,
|
||||
data: this.data.toJSON(),
|
||||
document: this.document.toJSON(options),
|
||||
kind: this.kind,
|
||||
history: this.history.toJSON(),
|
||||
selection: this.selection.toJSON(),
|
||||
decorations: this.decorations ? this.decorations.toArray().map(d => d.toJSON()) : null,
|
||||
history: this.history.toJSON(),
|
||||
}
|
||||
|
||||
if (!options.preserveData) {
|
||||
delete object.data
|
||||
}
|
||||
|
||||
if (!options.preserveDecorations) {
|
||||
delete object.decorations
|
||||
}
|
||||
|
||||
if (!options.preserveHistory) {
|
||||
@@ -587,10 +623,6 @@ class State extends Record(DEFAULTS) {
|
||||
delete object.selection
|
||||
}
|
||||
|
||||
if (!options.preserveStateData) {
|
||||
delete object.data
|
||||
}
|
||||
|
||||
if (options.preserveSelection && !options.preserveKeys) {
|
||||
const { document, selection } = this
|
||||
object.selection.anchorPath = selection.isSet ? document.getPath(selection.anchorKey) : null
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { List, Record, OrderedSet, is } from 'immutable'
|
||||
import { List, OrderedSet, Record, Set, is } from 'immutable'
|
||||
|
||||
import Character from './character'
|
||||
import Mark from './mark'
|
||||
@@ -193,11 +193,25 @@ class Text extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
addMark(index, length, mark) {
|
||||
const marks = new Set([mark])
|
||||
return this.addMarks(index, length, marks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a `set` of marks at `index` and `length`.
|
||||
*
|
||||
* @param {Number} index
|
||||
* @param {Number} length
|
||||
* @param {Set<Mark>} set
|
||||
* @return {Text}
|
||||
*/
|
||||
|
||||
addMarks(index, length, set) {
|
||||
const characters = this.characters.map((char, i) => {
|
||||
if (i < index) return char
|
||||
if (i >= index + length) return char
|
||||
let { marks } = char
|
||||
marks = marks.add(mark)
|
||||
marks = marks.union(set)
|
||||
char = char.set('marks', marks)
|
||||
return char
|
||||
})
|
||||
@@ -206,24 +220,29 @@ class Text extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive a set of decorated characters with `decorators`.
|
||||
* Derive a set of decorated characters with `decorations`.
|
||||
*
|
||||
* @param {Array} decorators
|
||||
* @param {List<Decoration>} decorations
|
||||
* @return {List<Character>}
|
||||
*/
|
||||
|
||||
getDecorations(decorators) {
|
||||
const node = this
|
||||
let { characters } = node
|
||||
getDecoratedCharacters(decorations) {
|
||||
let node = this
|
||||
const { key, characters } = node
|
||||
|
||||
// PERF: Exit early if there are no characters to be decorated.
|
||||
if (characters.size == 0) return characters
|
||||
|
||||
for (let i = 0; i < decorators.length; i++) {
|
||||
const decorator = decorators[i]
|
||||
const decorateds = decorator(node)
|
||||
characters = characters.merge(decorateds)
|
||||
}
|
||||
decorations.forEach((range) => {
|
||||
const { startKey, endKey, startOffset, endOffset, marks } = range
|
||||
const hasStart = startKey == key
|
||||
const hasEnd = endKey == key
|
||||
const index = hasStart ? startOffset : 0
|
||||
const length = hasEnd ? endOffset - index : characters.size
|
||||
node = node.addMarks(index, length, marks)
|
||||
})
|
||||
|
||||
return characters
|
||||
return node.characters
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -233,8 +252,8 @@ class Text extends Record(DEFAULTS) {
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
getDecorators(schema) {
|
||||
return schema.__getDecorators(this)
|
||||
getDecorations(schema) {
|
||||
return schema.__getDecorations(this)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -291,12 +310,12 @@ class Text extends Record(DEFAULTS) {
|
||||
/**
|
||||
* Derive the ranges for a list of `characters`.
|
||||
*
|
||||
* @param {Array|Void} decorators (optional)
|
||||
* @param {Array|Void} decorations (optional)
|
||||
* @return {List<Range>}
|
||||
*/
|
||||
|
||||
getRanges(decorators = []) {
|
||||
const characters = this.getDecorations(decorators)
|
||||
getRanges(decorations = []) {
|
||||
const characters = this.getDecoratedCharacters(decorations)
|
||||
let ranges = []
|
||||
|
||||
// PERF: cache previous values for faster lookup.
|
||||
@@ -513,8 +532,8 @@ memoize(Text.prototype, [
|
||||
})
|
||||
|
||||
memoize(Text.prototype, [
|
||||
'getDecoratedCharacters',
|
||||
'getDecorations',
|
||||
'getDecorators',
|
||||
'getMarksAtIndex',
|
||||
'getRanges',
|
||||
'validate'
|
||||
|
@@ -310,23 +310,6 @@ const APPLIERS = {
|
||||
return state
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `data` on `state`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
set_data(state, operation) {
|
||||
const { properties } = operation
|
||||
let { data } = state
|
||||
|
||||
data = data.merge(properties)
|
||||
state = state.set('data', data)
|
||||
return state
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on mark on text at `offset` and `length` in node by `path`.
|
||||
*
|
||||
@@ -359,15 +342,13 @@ const APPLIERS = {
|
||||
let { document } = state
|
||||
let node = document.assertPath(path)
|
||||
|
||||
// Warn when trying to overwite a node's children.
|
||||
if (properties.nodes && properties.nodes != node.nodes) {
|
||||
logger.warn('Updating a Node\'s `nodes` property via `setNode()` is not allowed. Use the appropriate insertion and removal operations instead. The opeartion in question was:', operation)
|
||||
if ('nodes' in properties) {
|
||||
logger.warn('Updating a Node\'s `nodes` property via `setNode()` is not allowed. Use the appropriate insertion and removal methods instead. The operation in question was:', operation)
|
||||
delete properties.nodes
|
||||
}
|
||||
|
||||
// Warn when trying to change a node's key.
|
||||
if (properties.key && properties.key != node.key) {
|
||||
logger.warn('Updating a Node\'s `key` property via `setNode()` is not allowed. There should be no reason to do this. The opeartion in question was:', operation)
|
||||
if ('key' in properties) {
|
||||
logger.warn('Updating a Node\'s `key` property via `setNode()` is not allowed. There should be no reason to do this. The operation in question was:', operation)
|
||||
delete properties.key
|
||||
}
|
||||
|
||||
@@ -413,6 +394,36 @@ const APPLIERS = {
|
||||
return state
|
||||
},
|
||||
|
||||
/**
|
||||
* Set `properties` on `state`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @param {Object} operation
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
set_state(state, operation) {
|
||||
const { properties } = operation
|
||||
|
||||
if ('document' in properties) {
|
||||
logger.warn('Updating `state.document` property via `setState()` is not allowed. Use the appropriate document updating methods instead. The operation in question was:', operation)
|
||||
delete properties.document
|
||||
}
|
||||
|
||||
if ('selection' in properties) {
|
||||
logger.warn('Updating `state.selection` property via `setState()` is not allowed. Use the appropriate selection updating methods instead. The operation in question was:', operation)
|
||||
delete properties.selection
|
||||
}
|
||||
|
||||
if ('history' in properties) {
|
||||
logger.warn('Updating `state.history` property via `setState()` is not allowed. Use the appropriate history updating methods instead. The operation in question was:', operation)
|
||||
delete properties.history
|
||||
}
|
||||
|
||||
state = state.merge(properties)
|
||||
return state
|
||||
},
|
||||
|
||||
/**
|
||||
* Split a node by `path` at `offset`.
|
||||
*
|
||||
|
@@ -3,7 +3,7 @@
|
||||
import h from '../../../helpers/h'
|
||||
|
||||
export default function (change) {
|
||||
change.setData({ thing: 'value' })
|
||||
change.setState({ data: { thing: 'value' }})
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@@ -42,5 +42,5 @@ export const output = {
|
||||
}
|
||||
|
||||
export const options = {
|
||||
preserveStateData: true,
|
||||
preserveData: true,
|
||||
}
|
||||
|
Reference in New Issue
Block a user