1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-16 12:14:14 +02: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
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 ```js
function normalizeNode(node) { function normalizeNode(node) {
if (node.object != 'block') return
if (node.isVoid) return
const { nodes } = node const { nodes } = node
if (nodes.size != 3) return if (node.object !== 'block') return
if (nodes.first().object != 'text') return if (nodes.size !== 3) return
if (nodes.last().object != 'text') return if (nodes.first().object !== 'text') return
if (nodes.last().object !== 'text') return
return change => { return change => change.removeNodeByKey(node.key)
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. 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 = () => { updateMenu = () => {
const { value } = this.state
const menu = this.menu const menu = this.menu
if (!menu) return 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') menu.removeAttribute('style')
return return
} }
const selection = window.getSelection() const native = window.getSelection()
const range = selection.getRangeAt(0) const range = native.getRangeAt(0)
const rect = range.getBoundingClientRect() const rect = range.getBoundingClientRect()
menu.style.opacity = 1 menu.style.opacity = 1
menu.style.top = `${rect.top + window.pageYOffset - menu.offsetHeight}px` 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", "release": "yarn build:production && yarn test && yarn lint && lerna publish && yarn gh-pages",
"server": "webpack-dev-server --config ./support/webpack/config.js", "server": "webpack-dev-server --config ./support/webpack/config.js",
"start": "npm-run-all --parallel --print-label watch server", "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", "test:coverage": "cross-env NODE_ENV=test nyc yarn test",
"watch": "rollup --config ./support/rollup/config.js --watch" "watch": "rollup --config ./support/rollup/config.js --watch"
} }

View File

@@ -126,11 +126,13 @@ class Leaf extends React.Component {
*/ */
renderText() { 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. // COMPAT: Render text inside void nodes with a zero-width space.
// So the node can contain selection but the text is not visible. // 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> return <span data-slate-zero-width="z">{'\u200B'}</span>
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -152,6 +152,15 @@ class Block extends Record(DEFAULTS) {
return this.object 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. * Check if the block is empty.
* Returns true if block is not void and all it's children nodes are 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() { 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 = { const object = {
object: this.object, object: this.object,
type: this.type, type: this.type,
isVoid: this.isVoid, isVoid: this.get('isVoid'),
data: this.data.toJSON(), data: this.data.toJSON(),
nodes: this.nodes.toArray().map(n => n.toJSON(options)), nodes: this.nodes.toArray().map(n => n.toJSON(options)),
} }

View File

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

View File

@@ -152,6 +152,15 @@ class Inline extends Record(DEFAULTS) {
return this.object 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. * Check if the inline is empty.
* Returns true if inline is not void and all it's children nodes are 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() { 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 = { const object = {
object: this.object, object: this.object,
type: this.type, type: this.type,
isVoid: this.isVoid, isVoid: this.get('isVoid'),
data: this.data.toJSON(), data: this.data.toJSON(),
nodes: this.nodes.toArray().map(n => n.toJSON(options)), 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`. * Get the closest void parent of a node by `path`.
* *
* @param {List|String} path * @param {List|String} path
* @param {Schema} schema
* @return {Node|Null} * @return {Node|Null}
*/ */
getClosestVoid(path) { getClosestVoid(path, schema) {
const closest = this.getClosest(path, p => p.isVoid) if (!schema) {
return closest 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. * Check if a node has a void parent.
* *
* @param {List|String} path * @param {List|String} path
* @param {Schema} schema
* @return {Boolean} * @return {Boolean}
*/ */
hasVoidParent(path) { hasVoidParent(path, schema) {
const closest = this.getClosestVoid(path) 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 return !!closest
} }

View File

@@ -282,7 +282,7 @@ class Operation extends Record(DEFAULTS) {
if (key == 'properties' && type == 'set_node') { if (key == 'properties' && type == 'set_node') {
const v = {} const v = {}
if ('data' in value) v.data = value.data.toJS() 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 if ('type' in value) v.type = value.type
value = v 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. * Return a JSON representation of the schema.
* *
@@ -432,7 +445,7 @@ function defaultNormalize(change, error) {
case NODE_IS_VOID_INVALID: { case NODE_IS_VOID_INVALID: {
return change.setNodeByKey( return change.setNodeByKey(
node.key, node.key,
{ isVoid: !node.isVoid }, { isVoid: !node.get('isVoid') },
{ normalize: false } { normalize: false }
) )
} }
@@ -530,7 +543,7 @@ function validateType(node, rule) {
function validateIsVoid(node, rule) { function validateIsVoid(node, rule) {
if (rule.isVoid == null) return 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 }) return fail(NODE_IS_VOID_INVALID, { rule, node })
} }

View File

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