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:
parent
3ac1b13ce3
commit
00d5785226
@ -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.
|
||||
|
||||
|
@ -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`
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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>
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)),
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 })
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user