1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-02-01 05:16:10 +01:00

deprecate isVoid and related properties/methods (#2102)

#### Is this adding or improving a _feature_ or fixing a _bug_?

Improvement.

#### What's the new behavior?

This deprecates the `node.isVoid` property in favor of using `schema.isVoid(node)`, which will allow us to remove the hardcoded "void" concept from the data model, and have it depend on the schema instead. 

This allows you to build different kinds of editors, with different void semantics, depending on your use case, without having this information hardcoded in the data itself. Even switching the `isVoid` semantics on the fly based on a user toggling a setting for example.

#### How does this change work?

This is the first step, which just deprecates `node.isVoid`, and provides the new alternative of `schema.isVoid(node)`, while still using the `isVoid` value of nodes under the covers.

The next step is to change the logic to search the schema for real, and completely remove the `isVoid` value from nodes.

#### Have you checked that...?

<!-- 
Please run through this checklist for your pull request: 
-->

* [x] The new code matches the existing patterns and styles.
* [x] The tests pass with `yarn test`.
* [x] The linter passes with `yarn lint`. (Fix errors with `yarn prettier`.)
* [x] The relevant examples still work. (Run examples with `yarn watch`.)
This commit is contained in:
Ian Storm Taylor 2018-08-21 15:52:44 -07:00 committed by GitHub
parent 3ac1b13ce3
commit 00d5785226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 204 additions and 102 deletions

View File

@ -93,21 +93,16 @@ When you define a `normalizeNode` function, you either return nothing if the nod
```js
function normalizeNode(node) {
if (node.object != 'block') return
if (node.isVoid) return
const { nodes } = node
if (nodes.size != 3) return
if (nodes.first().object != 'text') return
if (nodes.last().object != 'text') return
return change => {
change.removeNodeByKey(node.key)
}
if (node.object !== 'block') return
if (nodes.size !== 3) return
if (nodes.first().object !== 'text') return
if (nodes.last().object !== 'text') return
return change => change.removeNodeByKey(node.key)
}
```
This validation defines a very specific (honestly, useless) behavior, where if a node is block, non-void and has three children, the first and last of which are text nodes, it is removed. I don't know why you'd ever do that, but the point is that you can get very specific with your validations this way. Any property of the node can be examined.
This validation defines a very specific (honestly, useless) behavior, where if a node is a block and has three children, the first and last of which are text nodes, it is removed. I don't know why you'd ever do that, but the point is that you can get very specific with your validations this way. Any property of the node can be examined.
When you need this level of specificity, using the `normalizeNode` property of the editor or plugins is handy.

View File

@ -125,17 +125,19 @@ class HoveringMenu extends React.Component {
*/
updateMenu = () => {
const { value } = this.state
const menu = this.menu
if (!menu) return
if (value.isBlurred || value.isEmpty) {
const { value } = this.state
const { fragment, selection } = value
if (selection.isBlurred || selection.isCollapsed || fragment.text === '') {
menu.removeAttribute('style')
return
}
const selection = window.getSelection()
const range = selection.getRangeAt(0)
const native = window.getSelection()
const range = native.getRangeAt(0)
const rect = range.getBoundingClientRect()
menu.style.opacity = 1
menu.style.top = `${rect.top + window.pageYOffset - menu.offsetHeight}px`

View File

@ -109,7 +109,7 @@
"release": "yarn build:production && yarn test && yarn lint && lerna publish && yarn gh-pages",
"server": "webpack-dev-server --config ./support/webpack/config.js",
"start": "npm-run-all --parallel --print-label watch server",
"test": "cross-env BABEL_ENV=test FORBID_DEPRECATIONS=true FORBID_WARNINGS=true mocha --require babel-core/register ./packages/*/test/index.js",
"test": "cross-env BABEL_ENV=test FORBID_WARNINGS=true mocha --require babel-core/register ./packages/*/test/index.js",
"test:coverage": "cross-env NODE_ENV=test nyc yarn test",
"watch": "rollup --config ./support/rollup/config.js --watch"
}

View File

@ -126,11 +126,13 @@ class Leaf extends React.Component {
*/
renderText() {
const { block, node, parent, text, index, leaves } = this.props
const { block, node, editor, parent, text, index, leaves } = this.props
const { value } = editor
const { schema } = value
// COMPAT: Render text inside void nodes with a zero-width space.
// So the node can contain selection but the text is not visible.
if (parent.isVoid) {
if (schema.isVoid(parent)) {
return <span data-slate-zero-width="z">{'\u200B'}</span>
}

View File

@ -131,7 +131,7 @@ class Node extends React.Component {
readOnly,
} = this.props
const { value } = editor
const { selection } = value
const { selection, schema } = value
const { stack } = editor
const indexes = node.getSelectionIndexes(selection, isSelected)
const decs = decorations.concat(node.getDecorations(stack))
@ -184,7 +184,11 @@ class Node extends React.Component {
children,
})
return node.isVoid ? <Void {...this.props}>{element}</Void> : element
return schema.isVoid(node) ? (
<Void {...this.props}>{element}</Void>
) : (
element
)
}
/**

View File

@ -163,9 +163,11 @@ function AfterPlugin() {
if (editor.props.readOnly) return true
const { value } = change
const { document } = value
const { document, schema } = value
const node = findNode(event.target, value)
const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
const ancestors = document.getAncestors(node.key)
const isVoid =
node && (schema.isVoid(node) || ancestors.some(a => schema.isVoid(a)))
if (isVoid) {
// COMPAT: In Chrome & Safari, selections that are at the zero offset of
@ -209,10 +211,10 @@ function AfterPlugin() {
// If user cuts a void block node or a void inline node,
// manually removes it since selection is collapsed in this case.
const { value } = change
const { endBlock, endInline, selection } = value
const { endBlock, endInline, selection, schema } = value
const { isCollapsed } = selection
const isVoidBlock = endBlock && endBlock.isVoid && isCollapsed
const isVoidInline = endInline && endInline.isVoid && isCollapsed
const isVoidBlock = endBlock && schema.isVoid(endBlock) && isCollapsed
const isVoidInline = endInline && schema.isVoid(endInline) && isCollapsed
if (isVoidBlock) {
editor.change(c => c.removeNodeByKey(endBlock.key))
@ -264,9 +266,11 @@ function AfterPlugin() {
isDraggingInternally = true
const { value } = change
const { document } = value
const { document, schema } = value
const node = findNode(event.target, value)
const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
const ancestors = document.getAncestors(node.key)
const isVoid =
node && (schema.isVoid(node) || ancestors.some(a => schema.isVoid(a)))
if (isVoid) {
const encoded = Base64.serializeNode(node, { preserveKeys: true })
@ -290,7 +294,7 @@ function AfterPlugin() {
debug('onDrop', { event })
const { value } = change
const { document, selection } = value
const { document, selection, schema } = value
const window = getWindow(event.target)
let target = getEventRange(event, value)
if (!target) return
@ -322,7 +326,7 @@ function AfterPlugin() {
if (type == 'text' || type == 'html') {
const { anchor } = target
let hasVoidParent = document.hasVoidParent(anchor.key)
let hasVoidParent = document.hasVoidParent(anchor.key, schema)
if (hasVoidParent) {
let n = document.getNode(anchor.key)
@ -330,7 +334,7 @@ function AfterPlugin() {
while (hasVoidParent) {
n = document.getNextText(n.key)
if (!n) break
hasVoidParent = document.hasVoidParent(n.key)
hasVoidParent = document.hasVoidParent(n.key, schema)
}
if (n) change.moveToStartOfNode(n)
@ -451,6 +455,7 @@ function AfterPlugin() {
debug('onKeyDown', { event })
const { value } = change
const { schema } = value
// COMPAT: In iOS, some of these hotkeys are handled in the
// `onNativeBeforeInput` handler of the `<Content>` component in order to
@ -522,7 +527,7 @@ function AfterPlugin() {
if (Hotkeys.isMoveBackward(event)) {
const { document, isInVoid, previousText, startText } = value
const isPreviousInVoid =
previousText && document.hasVoidParent(previousText.key)
previousText && document.hasVoidParent(previousText.key, schema)
if (isInVoid || isPreviousInVoid || startText.text == '') {
event.preventDefault()
@ -532,7 +537,8 @@ function AfterPlugin() {
if (Hotkeys.isMoveForward(event)) {
const { document, isInVoid, nextText, startText } = value
const isNextInVoid = nextText && document.hasVoidParent(nextText.key)
const isNextInVoid =
nextText && document.hasVoidParent(nextText.key, schema)
if (isInVoid || isNextInVoid || startText.text == '') {
event.preventDefault()
@ -543,7 +549,7 @@ function AfterPlugin() {
if (Hotkeys.isExtendBackward(event)) {
const { document, isInVoid, previousText, startText } = value
const isPreviousInVoid =
previousText && document.hasVoidParent(previousText.key)
previousText && document.hasVoidParent(previousText.key, schema)
if (isInVoid || isPreviousInVoid || startText.text == '') {
event.preventDefault()
@ -553,7 +559,8 @@ function AfterPlugin() {
if (Hotkeys.isExtendForward(event)) {
const { document, isInVoid, nextText, startText } = value
const isNextInVoid = nextText && document.hasVoidParent(nextText.key)
const isNextInVoid =
nextText && document.hasVoidParent(nextText.key, schema)
if (isInVoid || isNextInVoid || startText.text == '') {
event.preventDefault()
@ -583,8 +590,8 @@ function AfterPlugin() {
if (type == 'text' || type == 'html') {
if (!text) return
const { value } = change
const { document, selection, startBlock } = value
if (startBlock.isVoid) return
const { document, selection, startBlock, schema } = value
if (schema.isVoid(startBlock)) return
const defaultBlock = startBlock
const defaultMarks = document.getInsertMarksAtRange(selection)
@ -607,7 +614,7 @@ function AfterPlugin() {
const window = getWindow(event.target)
const { value } = change
const { document } = value
const { document, schema } = value
const native = window.getSelection()
// If there are no ranges, the editor was blurred natively.
@ -637,10 +644,10 @@ function AfterPlugin() {
// selection, go with `0`. (2017/09/07)
if (
anchorBlock &&
!anchorBlock.isVoid &&
!schema.isVoid(anchorBlock) &&
anchor.offset == 0 &&
focusBlock &&
focusBlock.isVoid &&
schema.isVoid(focusBlock) &&
focus.offset != 0
) {
range = range.setFocus(focus.setOffset(0))
@ -651,7 +658,7 @@ function AfterPlugin() {
// standardizes the behavior, since it's indistinguishable to the user.
if (
anchorInline &&
!anchorInline.isVoid &&
!schema.isVoid(anchorInline) &&
anchor.offset == anchorText.text.length
) {
const block = document.getClosestBlock(anchor.key)
@ -661,7 +668,7 @@ function AfterPlugin() {
if (
focusInline &&
!focusInline.isVoid &&
!schema.isVoid(focusInline) &&
focus.offset == focusText.text.length
) {
const block = document.getClosestBlock(focus.key)

View File

@ -66,6 +66,7 @@ function BeforePlugin() {
if (editor.props.readOnly) return true
const { value } = change
const { schema } = value
const { relatedTarget, target } = event
const window = getWindow(target)
@ -93,7 +94,8 @@ function BeforePlugin() {
// editable section of an element that isn't a void node (eg. a list item
// of the check list example).
const node = findNode(relatedTarget, value)
if (el.contains(relatedTarget) && node && !node.isVoid) return true
if (el.contains(relatedTarget) && node && !schema.isVoid(node))
return true
}
debug('onBlur', { event })
@ -269,8 +271,10 @@ function BeforePlugin() {
// call `preventDefault` to signal that drops are allowed.
// When the target is editable, dropping is already allowed by
// default, and calling `preventDefault` hides the cursor.
const { value } = editor
const { schema } = value
const node = findNode(event.target, editor.value)
if (node.isVoid) event.preventDefault()
if (schema.isVoid(node)) event.preventDefault()
// COMPAT: IE won't call onDrop on contentEditables unless the
// default dragOver is prevented:

View File

@ -26,9 +26,10 @@ function cloneFragment(
) {
const window = getWindow(event.target)
const native = window.getSelection()
const { schema } = value
const { start, end } = value.selection
const startVoid = value.document.getClosestVoid(start.key)
const endVoid = value.document.getClosestVoid(end.key)
const startVoid = value.document.getClosestVoid(start.key, schema)
const endVoid = value.document.getClosestVoid(end.key, schema)
// If the selection is collapsed, and it isn't inside a void node, abort.
if (native.isCollapsed && !startVoid) return

View File

@ -19,14 +19,14 @@ function getEventRange(event, value) {
const { x, y, target } = event
if (x == null || y == null) return null
const { document } = value
const { document, schema } = value
const node = findNode(target, value)
if (!node) return null
// If the drop target is inside a void node, move it into either the next or
// previous node, depending on which side the `x` and `y` coordinates are
// closest to.
if (node.isVoid) {
if (schema.isVoid(node)) {
const rect = target.getBoundingClientRect()
const isPrevious =
node.object == 'inline'

View File

@ -83,11 +83,11 @@ Changes.deleteAtRange = (change, range, options = {}) => {
let startOffset = start.offset
let endKey = end.key
let endOffset = end.offset
let { document } = value
let isStartVoid = document.hasVoidParent(startKey)
let isEndVoid = document.hasVoidParent(endKey)
let startBlock = document.getClosestBlock(startKey)
let endBlock = document.getClosestBlock(endKey)
let { document, schema } = value
let isStartVoid = document.hasVoidParent(startKey, schema)
let isEndVoid = document.hasVoidParent(endKey, schema)
let startBlock = document.getClosestBlock(startKey, schema)
let endBlock = document.getClosestBlock(endKey, schema)
// Check if we have a "hanging" selection case where the even though the
// selection extends into the start of the end node, we actually want to
@ -104,14 +104,14 @@ Changes.deleteAtRange = (change, range, options = {}) => {
const prevText = document.getPreviousText(endKey)
endKey = prevText.key
endOffset = prevText.text.length
isEndVoid = document.hasVoidParent(endKey)
isEndVoid = document.hasVoidParent(endKey, schema)
}
// If the start node is inside a void node, remove the void node and update
// the starting point to be right after it, continuously until the start point
// is not a void, or until the entire range is handled.
while (isStartVoid) {
const startVoid = document.getClosestVoid(startKey)
const startVoid = document.getClosestVoid(startKey, schema)
const nextText = document.getNextText(startKey)
change.removeNodeByKey(startVoid.key, { normalize: false })
@ -125,14 +125,14 @@ Changes.deleteAtRange = (change, range, options = {}) => {
document = change.value.document
startKey = nextText.key
startOffset = 0
isStartVoid = document.hasVoidParent(startKey)
isStartVoid = document.hasVoidParent(startKey, schema)
}
// If the end node is inside a void node, do the same thing but backwards. But
// we don't need any aborting checks because if we've gotten this far there
// must be a non-void node that will exit the loop.
while (isEndVoid) {
const endVoid = document.getClosestVoid(endKey)
const endVoid = document.getClosestVoid(endKey, schema)
const prevText = document.getPreviousText(endKey)
change.removeNodeByKey(endVoid.key, { normalize: false })
@ -140,7 +140,7 @@ Changes.deleteAtRange = (change, range, options = {}) => {
document = change.value.document
endKey = prevText.key
endOffset = prevText.text.length
isEndVoid = document.hasVoidParent(endKey)
isEndVoid = document.hasVoidParent(endKey, schema)
}
// If the start and end key are the same, and it was a hanging selection, we
@ -340,7 +340,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
if (n === 0) return
const normalize = change.getFlag('normalize', options)
const { value } = change
const { document } = value
const { document, schema } = value
const { start, focus } = range
// If the range is expanded, perform a regular delete instead.
@ -349,7 +349,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
return
}
const voidParent = document.getClosestVoid(start.key)
const voidParent = document.getClosestVoid(start.key, schema)
// If there is a void parent, delete it.
if (voidParent) {
@ -360,7 +360,12 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
const block = document.getClosestBlock(start.key)
// If the closest is not void, but empty, remove it
if (block && block.isEmpty && document.nodes.size !== 1) {
if (
block &&
!schema.isVoid(block) &&
block.text === '' &&
document.nodes.size !== 1
) {
change.removeNodeByKey(block.key, { normalize })
return
}
@ -377,7 +382,7 @@ Changes.deleteBackwardAtRange = (change, range, n = 1, options = {}) => {
if (start.isAtStartOfNode(text)) {
const prev = document.getPreviousText(text.key)
const prevBlock = document.getClosestBlock(prev.key)
const prevVoid = document.getClosestVoid(prev.key)
const prevVoid = document.getClosestVoid(prev.key, schema)
// If the previous text node has a void parent, remove it.
if (prevVoid) {
@ -498,7 +503,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
if (n === 0) return
const normalize = change.getFlag('normalize', options)
const { value } = change
const { document } = value
const { document, schema } = value
const { start, focus } = range
// If the range is expanded, perform a regular delete instead.
@ -507,7 +512,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
return
}
const voidParent = document.getClosestVoid(start.key)
const voidParent = document.getClosestVoid(start.key, schema)
// If the node has a void parent, delete it.
if (voidParent) {
@ -518,7 +523,12 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
const block = document.getClosestBlock(start.key)
// If the closest is not void, but empty, remove it
if (block && block.isEmpty && document.nodes.size !== 1) {
if (
block &&
!schema.isVoid(block) &&
block.text === '' &&
document.nodes.size !== 1
) {
const nextBlock = document.getNextBlock(block.key)
change.removeNodeByKey(block.key, { normalize })
@ -540,7 +550,7 @@ Changes.deleteForwardAtRange = (change, range, n = 1, options = {}) => {
if (start.isAtEndOfNode(text)) {
const next = document.getNextText(text.key)
const nextBlock = document.getClosestBlock(next.key)
const nextVoid = document.getClosestVoid(next.key)
const nextVoid = document.getClosestVoid(next.key, schema)
// If the next text node has a void parent, remove it.
if (nextVoid) {
@ -607,7 +617,7 @@ Changes.insertBlockAtRange = (change, range, block, options = {}) => {
}
const { value } = change
const { document } = value
const { document, schema } = value
const { start } = range
let startKey = start.key
let startOffset = start.offset
@ -616,17 +626,17 @@ Changes.insertBlockAtRange = (change, range, block, options = {}) => {
const parent = document.getParent(startBlock.key)
const index = parent.nodes.indexOf(startBlock)
if (startBlock.isVoid) {
if (schema.isVoid(startBlock)) {
const extra = start.isAtEndOfNode(startBlock) ? 1 : 0
change.insertNodeByKey(parent.key, index + extra, block, { normalize })
} else if (startBlock.isEmpty) {
} else if (!startInline && startBlock.text === '') {
change.insertNodeByKey(parent.key, index + 1, block, { normalize })
} else if (start.isAtStartOfNode(startBlock)) {
change.insertNodeByKey(parent.key, index, block, { normalize })
} else if (start.isAtEndOfNode(startBlock)) {
change.insertNodeByKey(parent.key, index + 1, block, { normalize })
} else {
if (startInline && startInline.isVoid) {
if (startInline && schema.isVoid(startInline)) {
const atEnd = start.isAtEndOfNode(startInline)
const siblingText = atEnd
? document.getNextText(startKey)
@ -688,6 +698,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
// Calculate a few things...
const { start } = range
const { value } = change
const { schema } = value
let { document } = value
let startText = document.getDescendant(start.key)
let startBlock = document.getClosestBlock(startText.key)
@ -702,7 +713,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
const lastBlock = blocks.last()
// If the fragment only contains a void block, use `insertBlock` instead.
if (firstBlock == lastBlock && firstBlock.isVoid) {
if (firstBlock === lastBlock && schema.isVoid(firstBlock)) {
change.insertBlockAtRange(range, firstBlock, options)
return
}
@ -769,7 +780,7 @@ Changes.insertFragmentAtRange = (change, range, fragment, options = {}) => {
// If the starting block is empty, we replace it entirely with the first block
// of the fragment, since this leads to a more expected behavior for the user.
if (startBlock.isEmpty) {
if (!schema.isVoid(startBlock) && startBlock.text === '') {
change.removeNodeByKey(startBlock.key, { normalize: false })
change.insertNodeByKey(parent.key, index, firstBlock, { normalize: false })
} else {
@ -814,13 +825,13 @@ Changes.insertInlineAtRange = (change, range, inline, options = {}) => {
}
const { value } = change
const { document } = value
const { document, schema } = value
const { start } = range
const parent = document.getParent(start.key)
const startText = document.assertDescendant(start.key)
const index = parent.nodes.indexOf(startText)
if (parent.isVoid) return
if (schema.isVoid(parent)) return
change.splitNodeByKey(start.key, start.offset, { normalize: false })
change.insertNodeByKey(parent.key, index + 1, inline, { normalize: false })
@ -844,12 +855,12 @@ Changes.insertInlineAtRange = (change, range, inline, options = {}) => {
Changes.insertTextAtRange = (change, range, text, marks, options = {}) => {
let { normalize } = options
const { value } = change
const { document } = value
const { document, schema } = value
const { start } = range
let key = start.key
let offset = start.offset
const parent = document.getParent(start.key)
if (parent.isVoid) return
if (schema.isVoid(parent)) return
if (range.isExpanded) {
change.deleteAtRange(range, { normalize: false })
@ -929,11 +940,11 @@ Changes.removeMarkAtRange = (change, range, mark, options = {}) => {
Changes.setBlocksAtRange = (change, range, properties, options = {}) => {
const normalize = change.getFlag('normalize', options)
const { value } = change
const { document } = value
const { document, schema } = value
const blocks = document.getBlocksAtRange(range)
const { start, end, isCollapsed } = range
const isStartVoid = document.hasVoidParent(start.key)
const isStartVoid = document.hasVoidParent(start.key, schema)
const startBlock = document.getClosestBlock(start.key)
const endBlock = document.getClosestBlock(end.key)
@ -1121,7 +1132,7 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
const normalize = change.getFlag('normalize', options)
const { value } = change
let { document } = value
let { document, schema } = value
const blocks = document.getBlocksAtRange(range)
const wrappers = blocks
.map(block => {
@ -1129,7 +1140,10 @@ Changes.unwrapBlockAtRange = (change, range, properties, options = {}) => {
if (parent.object != 'block') return false
if (properties.type != null && parent.type != properties.type)
return false
if (properties.isVoid != null && parent.isVoid != properties.isVoid)
if (
properties.isVoid != null &&
schema.isVoid(parent) != properties.isVoid
)
return false
if (properties.data != null && !parent.data.isSuperset(properties.data))
return false
@ -1220,7 +1234,7 @@ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
const normalize = change.getFlag('normalize', options)
const { value } = change
const { document } = value
const { document, schema } = value
const texts = document.getTextsAtRange(range)
const inlines = texts
.map(text => {
@ -1228,7 +1242,10 @@ Changes.unwrapInlineAtRange = (change, range, properties, options = {}) => {
if (parent.object != 'inline') return false
if (properties.type != null && parent.type != properties.type)
return false
if (properties.isVoid != null && parent.isVoid != properties.isVoid)
if (
properties.isVoid != null &&
schema.isVoid(parent) != properties.isVoid
)
return false
if (properties.data != null && !parent.data.isSuperset(properties.data))
return false
@ -1337,7 +1354,7 @@ Changes.wrapBlockAtRange = (change, range, block, options = {}) => {
Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
const { value } = change
let { document } = value
let { document, schema } = value
const normalize = change.getFlag('normalize', options)
const { start, end } = range
@ -1345,7 +1362,7 @@ Changes.wrapInlineAtRange = (change, range, inline, options = {}) => {
// Wrapping an inline void
const inlineParent = document.getClosestInline(start.key)
if (!inlineParent.isVoid) {
if (!schema.isVoid(inlineParent)) {
return
}

View File

@ -658,9 +658,9 @@ function pointBackward(change, point, n = 1) {
const Point = point.slice(0, 1).toUpperCase() + point.slice(1)
const { value } = change
const { document, selection } = value
const { document, selection, schema } = value
const p = selection[point]
const isInVoid = document.hasVoidParent(p.path)
const isInVoid = document.hasVoidParent(p.path, schema)
// what is this?
if (!isInVoid && p.offset - n >= 0) {
@ -674,7 +674,8 @@ function pointBackward(change, point, n = 1) {
const block = document.getClosestBlock(p.path)
const isInBlock = block.hasNode(previous.key)
const isPreviousInVoid = previous && document.hasVoidParent(previous.key)
const isPreviousInVoid =
previous && document.hasVoidParent(previous.key, schema)
change[`move${Point}ToEndOfNode`](previous)
// when is this called?
@ -690,10 +691,10 @@ function pointForward(change, point, n = 1) {
const Point = point.slice(0, 1).toUpperCase() + point.slice(1)
const { value } = change
const { document, selection } = value
const { document, selection, schema } = value
const p = selection[point]
const text = document.getNode(p.path)
const isInVoid = document.hasVoidParent(p.path)
const isInVoid = document.hasVoidParent(p.path, schema)
// what is this?
if (!isInVoid && p.offset + n <= text.text.length) {
@ -707,7 +708,7 @@ function pointForward(change, point, n = 1) {
const block = document.getClosestBlock(p.path)
const isInBlock = block.hasNode(next.key)
const isNextInVoid = document.hasVoidParent(next.key)
const isNextInVoid = document.hasVoidParent(next.key, schema)
change[`move${Point}ToStartOfNode`](next)
// when is this called?

View File

@ -152,6 +152,15 @@ class Block extends Record(DEFAULTS) {
return this.object
}
get isVoid() {
logger.deprecate(
'0.38.0',
'The `Node.isVoid` property is deprecated, please use the `Schema.isVoid()` checking method instead.'
)
return this.get('isVoid')
}
/**
* Check if the block is empty.
* Returns true if block is not void and all it's children nodes are empty.
@ -161,7 +170,9 @@ class Block extends Record(DEFAULTS) {
*/
get isEmpty() {
return !this.isVoid && !this.nodes.some(child => !child.isEmpty)
logger.deprecate('0.38.0', 'The `Node.isEmpty` property is deprecated.')
return !this.get('isVoid') && !this.nodes.some(child => !child.isEmpty)
}
/**
@ -185,7 +196,7 @@ class Block extends Record(DEFAULTS) {
const object = {
object: this.object,
type: this.type,
isVoid: this.isVoid,
isVoid: this.get('isVoid'),
data: this.data.toJSON(),
nodes: this.nodes.toArray().map(n => n.toJSON(options)),
}

View File

@ -105,6 +105,15 @@ class Document extends Record(DEFAULTS) {
return this.object
}
get isVoid() {
logger.deprecate(
'0.38.0',
'The `Node.isVoid` property is deprecated, please use the `Schema.isVoid()` checking method instead.'
)
return this.get('isVoid')
}
/**
* Check if the document is empty.
* Returns true if all it's children nodes are empty.
@ -113,6 +122,7 @@ class Document extends Record(DEFAULTS) {
*/
get isEmpty() {
logger.deprecate('0.38.0', 'The `Node.isEmpty` property is deprecated.')
return !this.nodes.some(child => !child.isEmpty)
}

View File

@ -152,6 +152,15 @@ class Inline extends Record(DEFAULTS) {
return this.object
}
get isVoid() {
logger.deprecate(
'0.38.0',
'The `Node.isVoid` property is deprecated, please use the `Schema.isVoid()` checking method instead.'
)
return this.get('isVoid')
}
/**
* Check if the inline is empty.
* Returns true if inline is not void and all it's children nodes are empty.
@ -161,7 +170,8 @@ class Inline extends Record(DEFAULTS) {
*/
get isEmpty() {
return !this.isVoid && !this.nodes.some(child => !child.isEmpty)
logger.deprecate('0.38.0', 'The `Node.isEmpty` property is deprecated.')
return !this.get('isVoid') && !this.nodes.some(child => !child.isEmpty)
}
/**
@ -185,7 +195,7 @@ class Inline extends Record(DEFAULTS) {
const object = {
object: this.object,
type: this.type,
isVoid: this.isVoid,
isVoid: this.get('isVoid'),
data: this.data.toJSON(),
nodes: this.nodes.toArray().map(n => n.toJSON(options)),
}

View File

@ -540,12 +540,24 @@ class Node {
* Get the closest void parent of a node by `path`.
*
* @param {List|String} path
* @param {Schema} schema
* @return {Node|Null}
*/
getClosestVoid(path) {
const closest = this.getClosest(path, p => p.isVoid)
return closest
getClosestVoid(path, schema) {
if (!schema) {
logger.deprecate(
'0.38.0',
'Calling the `Node.getClosestVoid` method without passing a second `schema` argument is deprecated.'
)
const closest = this.getClosest(path, p => p.get('isVoid'))
return closest
}
const ancestors = this.getAncestors(path)
const ancestor = ancestors.findLast(a => schema.isVoid(a))
return ancestor
}
/**
@ -1636,11 +1648,22 @@ class Node {
* Check if a node has a void parent.
*
* @param {List|String} path
* @param {Schema} schema
* @return {Boolean}
*/
hasVoidParent(path) {
const closest = this.getClosestVoid(path)
hasVoidParent(path, schema) {
if (!schema) {
logger.deprecate(
'0.38.0',
'Calling the `Node.hasVoidParent` method without the second `schema` argument is deprecated.'
)
const closest = this.getClosestVoid(path)
return !!closest
}
const closest = this.getClosestVoid(path, schema)
return !!closest
}

View File

@ -282,7 +282,7 @@ class Operation extends Record(DEFAULTS) {
if (key == 'properties' && type == 'set_node') {
const v = {}
if ('data' in value) v.data = value.data.toJS()
if ('isVoid' in value) v.isVoid = value.isVoid
if ('isVoid' in value) v.isVoid = value.get('isVoid')
if ('type' in value) v.type = value.type
value = v
}

View File

@ -359,6 +359,19 @@ class Schema extends Record(DEFAULTS) {
}
}
/**
* Check if a node is void.
*
* @param {Node}
* @return {Boolean}
*/
isVoid(node) {
// COMPAT: Right now this just provides a way to get around the
// deprecation warnings, but in the future it will check the rules.
return node.get('isVoid')
}
/**
* Return a JSON representation of the schema.
*
@ -432,7 +445,7 @@ function defaultNormalize(change, error) {
case NODE_IS_VOID_INVALID: {
return change.setNodeByKey(
node.key,
{ isVoid: !node.isVoid },
{ isVoid: !node.get('isVoid') },
{ normalize: false }
)
}
@ -530,7 +543,7 @@ function validateType(node, rule) {
function validateIsVoid(node, rule) {
if (rule.isVoid == null) return
if (rule.isVoid === node.isVoid) return
if (rule.isVoid === node.get('isVoid')) return
return fail(NODE_IS_VOID_INVALID, { rule, node })
}

View File

@ -528,6 +528,7 @@ class Value extends Record(DEFAULTS) {
*/
get isEmpty() {
logger.deprecate('0.38.0', 'The `Value.isEmpty` property is deprecated.')
if (this.selection.isCollapsed) return true
if (this.selection.end.offset != 0 && this.selection.start.offset != 0)
return false
@ -541,8 +542,9 @@ class Value extends Record(DEFAULTS) {
*/
get isInVoid() {
logger.deprecate('0.38.0', 'The `Value.isInVoid` property is deprecated.')
if (this.selection.isExpanded) return false
return this.document.hasVoidParent(this.selection.start.key)
return this.document.hasVoidParent(this.selection.start.key, this.schema)
}
/**