1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-15 03:33:59 +02:00

remove change, fold into editor (#2337)

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

Improvement / debt.

#### What's the new behavior?

This pull request removes the `Change` object as we know it, and folds all of its behaviors into the new `Editor` controller instead, simplifying a lot of the confusion around what is a "change vs. editor" and when to use which. It makes the standard API a **lot** nicer to use I think.

---

###### NEW

**The `editor.command` and `editor.query` methods can take functions.** Previously they only accepted a `type` string and would look up the command or query by type. Now, they also accept a custom function. This is helpful for plugin authors, who want to accept a "command option", since it gives users more flexibility to write one-off commands or queries. For example a plugin could be passed either:

```js
Hotkey({
  hotkey: 'cmd+b',
  command: 'addBoldMark',
})
```

Or a custom command function:

```js
Hotkey({
  hotkey: 'cmd+b',
  command: editor => editor.addBoldMark().moveToEnd()
})
```

###### BREAKING

**The `Change` object has been removed.** The `Change` object as we know it previously has been removed, and all of its behaviors have been folded into the `Editor` controller. This includes the top-level commands and queries methods, as well as methods like `applyOperation` and `normalize`. _All places that used to receive `change` now receive `editor`, which is API equivalent._

**Changes are now flushed to `onChange` asynchronously.** Previously this was done synchronously, which resulted in some strange race conditions in React environments. Now they will always be flushed asynchronously, just like `setState`.

**The `render*` and `decorate*` middleware signatures have changed!** Previously the `render*` and `decorate*` middleware was passed `(props, next)`. However now, for consistency with the other middleware they are all passed `(props, editor, next)`. This way, all middleware always receive `editor` and `next` as their final two arguments.

**The `normalize*` and `validate*` middleware signatures have changed!** Previously the `normalize*` and `validate*` middleware was passed `(node, next)`. However now, for consistency with the other middleware they are all passed `(node, editor, next)`. This way, all middleware always receive `editor` and `next` as their final two arguments.

**The `editor.event` method has been removed.** Previously this is what you'd use when writing tests to simulate events being fired—which were slightly different to other running other middleware. With the simplification to the editor and to the newly-consistent middleware signatures, you can now use `editor.run` directly to simulate events:

```js
editor.run('onKeyDown', { key: 'Tab', ... })
```

###### DEPRECATED

**The `editor.change` method is deprecated.** With the removal of the `Change` object, there's no need anymore to create the small closures with `editor.change()`. Instead you can directly invoke commands on the editor in series, and all of the changes will be emitted asynchronously on the next tick.

```js
editor
  .insertText('word')
  .moveFocusForward(10)
  .addMark('bold')
```

**The `applyOperations` method is deprecated.** Instead you can loop a set of operations and apply each one using `applyOperation`. This is to reduce the number of methods exposed on the `Editor` to keep it simpler.

**The `change.call` method is deprecated.** Previously this was used to call a one-off function as a change method. Now this behavior is equivalent to calling `editor.command(fn)` instead.

---

Fixes: https://github.com/ianstormtaylor/slate/issues/2334
Fixes: https://github.com/ianstormtaylor/slate/issues/2282
This commit is contained in:
Ian Storm Taylor
2018-10-27 12:18:23 -07:00
committed by GitHub
parent 633fc9eebc
commit 8dd919dc34
583 changed files with 3281 additions and 3489 deletions

View File

@@ -14,13 +14,13 @@ This document maintains a list of changes to the `slate-react` package with each
```js
// Previously, you'd return `undefined` to continue.
function onKeyDown(event, change, editor) {
function onKeyDown(event, editor, next) {
if (event.key !== 'Enter') return
...
}
// Now, you call `next()` to continue...
function onKeyDown(event, change, next) {
function onKeyDown(event, editor, next) {
if (event.key !== 'Enter') return next()
...
}
@@ -29,7 +29,7 @@ function onKeyDown(event, change, next) {
While that may seem inconvenient, it opens up an entire new behavior, which is deferring to the plugins later in the stack to see if they "handle" a specific case, and if not, handling it yourself:
```js
function onKeyDown(event, change, next) {
function onKeyDown(event, editor, next) {
if (event.key === 'Enter') {
const handled = next()
if (handled) return handled
@@ -45,7 +45,7 @@ Under the covers, the `schema`, `commands` and `queries` concept are all impleme
```js
const plugin = {
onCommand(command, change, next) {
onCommand(command, editor, next) {
...
}
}
@@ -62,7 +62,7 @@ This allows you to actually listen in to all commands, and override individual b
**The `editor` object is no longer passed to event handlers.** Previously, the third argument to event handlers would be the React `editor` instance. However, now that `Change` objects contain a direct reference to the editor, you can access this on `change.editor` instead.
```js
function onKeyDown(event, change, next) {
function onKeyDown(event, editor, next) {
const { editor } = change
...
}
@@ -206,7 +206,7 @@ In its place is the new `next` argument, which allows you to choose to defer to
###### BREAKING
**The `data` argument to event handlers has been removed.** Previously event handlers had a signature of `(event, data, change, editor)`, but now they have a signature of just `(event, change, editor)`. This leads to simpler internal Slate logic, and less complex relationship dependencies between plugins. All of the information inside the old `data` argument can be accessed via the similar properties on the `event` argument, or via the `getEventRange`, `getEventTransfer` and `setEventTransfer` helpers.
**The `data` argument to event handlers has been removed.** Previously event handlers had a signature of `(event, data, change, editor)`, but now they have a signature of just `(event, editor, next)`. This leads to simpler internal Slate logic, and less complex relationship dependencies between plugins. All of the information inside the old `data` argument can be accessed via the similar properties on the `event` argument, or via the `getEventRange`, `getEventTransfer` and `setEventTransfer` helpers.
###### NEW

View File

@@ -99,7 +99,7 @@ class Editor extends React.Component {
this.tmp.updates++
if (this.props.autoFocus) {
this.change(c => c.focus())
this.focus()
}
if (this.tmp.change) {
@@ -121,20 +121,6 @@ class Editor extends React.Component {
}
}
/**
* On controller change, call `onChange` or queue the change for flushing.
*
* @param {Change} change
*/
onControllerChange = change => {
if (this.tmp.mounted) {
this.props.onChange(change)
} else {
this.tmp.change = change
}
}
/**
* Render the editor.
*
@@ -181,8 +167,21 @@ class Editor extends React.Component {
this.tmp.resolves++
const react = ReactPlugin(this.props)
const attrs = { onChange: this.onControllerChange, plugins: [react] }
this.controller = new Controller(attrs, { editor: this, normalize: false })
const onChange = change => {
if (this.tmp.mounted) {
this.props.onChange(change)
} else {
this.tmp.change = change
}
}
this.controller = new Controller(
{ plugins: [react], onChange },
{ controller: this, construct: false }
)
this.controller.run('onConstruct')
})
/**
@@ -190,6 +189,10 @@ class Editor extends React.Component {
* can be passed in its place to plugins.
*/
get operations() {
return this.controller.operations
}
get readOnly() {
return this.controller.readOnly
}
@@ -198,59 +201,91 @@ class Editor extends React.Component {
return this.controller.value
}
change = (...args) => {
return this.controller.change(...args)
applyOperation(...args) {
return this.controller.applyOperation(...args)
}
command = (...args) => {
command(...args) {
return this.controller.command(...args)
}
event = (...args) => {
return this.controller.event(...args)
normalize(...args) {
return this.controller.normalize(...args)
}
onChange = (...args) => {
return this.controller.onChange(...args)
}
query = (...args) => {
query(...args) {
return this.controller.query(...args)
}
run = (...args) => {
registerCommand(...args) {
return this.controller.registerCommand(...args)
}
registerQuery(...args) {
return this.controller.registerQuery(...args)
}
run(...args) {
return this.controller.run(...args)
}
/**
* Mimic the API of a DOM input/textarea, to maintain a React-like interface.
*/
blur = () => {
this.change(c => c.blur())
}
focus = () => {
this.change(c => c.focus())
withoutNormalizing(...args) {
return this.controller.withoutNormalizing(...args)
}
/**
* Deprecated.
*/
get editor() {
return this.controller.editor
}
get schema() {
invariant(
false,
'As of Slate 0.42.0, the `editor.schema` property no longer exists, and its functionality has been folded into the editor itself. Use the `editor` instead.'
'As of Slate 0.42, the `editor.schema` property no longer exists, and its functionality has been folded into the editor itself. Use the `editor` instead.'
)
}
get stack() {
invariant(
false,
'As of Slate 0.42.0, the `editor.stack` property no longer exists, and its functionality has been folded into the editor itself. Use the `editor` instead.'
'As of Slate 0.42, the `editor.stack` property no longer exists, and its functionality has been folded into the editor itself. Use the `editor` instead.'
)
}
call(...args) {
return this.controller.call(...args)
}
change(...args) {
return this.controller.change(...args)
}
onChange(...args) {
return this.controller.onChange(...args)
}
applyOperations(...args) {
return this.controller.applyOperations(...args)
}
setOperationFlag(...args) {
return this.controller.setOperationFlag(...args)
}
getFlag(...args) {
return this.controller.getFlag(...args)
}
unsetOperationFlag(...args) {
return this.controller.unsetOperationFlag(...args)
}
withoutNormalization(...args) {
return this.controller.withoutNormalization(...args)
}
}
/**

View File

@@ -36,12 +36,12 @@ function AfterPlugin(options = {}) {
* On before input.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onBeforeInput(event, change, next) {
const { editor, value } = change
function onBeforeInput(event, editor, next) {
const { value } = editor
const isSynthetic = !!event.nativeEvent
// If the event is synthetic, it's React's polyfill of `beforeinput` that
@@ -49,7 +49,7 @@ function AfterPlugin(options = {}) {
// gets triggered for character insertions, so we can just insert directly.
if (isSynthetic) {
event.preventDefault()
change.insertText(event.data)
editor.insertText(event.data)
return next()
}
@@ -71,29 +71,29 @@ function AfterPlugin(options = {}) {
case 'deleteContent':
case 'deleteContentBackward':
case 'deleteContentForward': {
change.deleteAtRange(range)
editor.deleteAtRange(range)
break
}
case 'deleteWordBackward': {
change.deleteWordBackwardAtRange(range)
editor.deleteWordBackwardAtRange(range)
break
}
case 'deleteWordForward': {
change.deleteWordForwardAtRange(range)
editor.deleteWordForwardAtRange(range)
break
}
case 'deleteSoftLineBackward':
case 'deleteHardLineBackward': {
change.deleteLineBackwardAtRange(range)
editor.deleteLineBackwardAtRange(range)
break
}
case 'deleteSoftLineForward':
case 'deleteHardLineForward': {
change.deleteLineForwardAtRange(range)
editor.deleteLineForwardAtRange(range)
break
}
@@ -105,9 +105,9 @@ function AfterPlugin(options = {}) {
)
if (hasVoidParent) {
change.moveToStartOfNextText()
editor.moveToStartOfNextText()
} else {
change.splitBlockAtRange(range)
editor.splitBlockAtRange(range)
}
break
@@ -127,12 +127,12 @@ function AfterPlugin(options = {}) {
if (text == null) break
change.insertTextAtRange(range, text, selection.marks)
editor.insertTextAtRange(range, text, selection.marks)
// If the text was successfully inserted, and the selection had marks
// on it, unset the selection's marks.
if (selection.marks && value.document != change.value.document) {
change.select({ marks: null })
if (selection.marks && value.document != editor.value.document) {
editor.select({ marks: null })
}
break
@@ -146,13 +146,13 @@ function AfterPlugin(options = {}) {
* On blur.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onBlur(event, change, next) {
function onBlur(event, editor, next) {
debug('onBlur', { event })
change.blur()
editor.blur()
next()
}
@@ -160,12 +160,11 @@ function AfterPlugin(options = {}) {
* On click.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onClick(event, change, next) {
const { editor } = change
function onClick(event, editor, next) {
if (editor.readOnly) return next()
const { value } = editor
@@ -177,14 +176,14 @@ function AfterPlugin(options = {}) {
const ancestors = document.getAncestors(node.key)
const isVoid =
node && (change.isVoid(node) || ancestors.some(a => change.isVoid(a)))
node && (editor.isVoid(node) || ancestors.some(a => editor.isVoid(a)))
if (isVoid) {
// COMPAT: In Chrome & Safari, selections that are at the zero offset of
// an inline node will be automatically replaced to be at the last offset
// of a previous inline node, which screws us up, so we always want to set
// it to the end of the node. (2016/11/29)
change.focus().moveToEndOfNode(node)
editor.focus().moveToEndOfNode(node)
}
next()
@@ -194,13 +193,12 @@ function AfterPlugin(options = {}) {
* On copy.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCopy(event, change, next) {
function onCopy(event, editor, next) {
debug('onCopy', { event })
const { editor } = change
cloneFragment(event, editor)
next()
}
@@ -209,31 +207,30 @@ function AfterPlugin(options = {}) {
* On cut.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCut(event, change, next) {
function onCut(event, editor, next) {
debug('onCut', { event })
const { editor } = change
// Once the fake cut content has successfully been added to the clipboard,
// delete the content in the current selection.
cloneFragment(event, editor, () => {
// 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 { value } = editor
const { endBlock, endInline, selection } = value
const { isCollapsed } = selection
const isVoidBlock = endBlock && change.isVoid(endBlock) && isCollapsed
const isVoidInline = endInline && change.isVoid(endInline) && isCollapsed
const isVoidBlock = endBlock && editor.isVoid(endBlock) && isCollapsed
const isVoidInline = endInline && editor.isVoid(endInline) && isCollapsed
if (isVoidBlock) {
editor.change(c => c.removeNodeByKey(endBlock.key))
editor.editor(c => c.removeNodeByKey(endBlock.key))
} else if (isVoidInline) {
editor.change(c => c.removeNodeByKey(endInline.key))
editor.editor(c => c.removeNodeByKey(endInline.key))
} else {
editor.change(c => c.delete())
editor.editor(c => c.delete())
}
})
@@ -244,11 +241,11 @@ function AfterPlugin(options = {}) {
* On drag end.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragEnd(event, change, next) {
function onDragEnd(event, editor, next) {
debug('onDragEnd', { event })
isDraggingInternally = null
next()
@@ -258,32 +255,31 @@ function AfterPlugin(options = {}) {
* On drag start.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragStart(event, change, next) {
function onDragStart(event, editor, next) {
debug('onDragStart', { event })
isDraggingInternally = true
const { editor } = change
const { value } = editor
const { document } = value
const node = findNode(event.target, editor)
const ancestors = document.getAncestors(node.key)
const isVoid =
node && (change.isVoid(node) || ancestors.some(a => change.isVoid(a)))
node && (editor.isVoid(node) || ancestors.some(a => editor.isVoid(a)))
const selectionIncludesNode = value.blocks.some(
block => block.key === node.key
)
// If a void block is dragged and is not selected, select it (necessary for local drags).
if (isVoid && !selectionIncludesNode) {
change.moveToRangeOfNode(node)
editor.moveToRangeOfNode(node)
}
const fragment = change.value.fragment
const fragment = editor.value.fragment
const encoded = Base64.serializeNode(fragment)
setEventTransfer(event, 'fragment', encoded)
next()
@@ -293,12 +289,12 @@ function AfterPlugin(options = {}) {
* On drop.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDrop(event, change, next) {
const { editor, value } = change
function onDrop(event, editor, next) {
const { value } = editor
const { document, selection } = value
const window = getWindow(event.target)
let target = getEventRange(event, editor)
@@ -309,7 +305,7 @@ function AfterPlugin(options = {}) {
const transfer = getEventTransfer(event)
const { type, fragment, text } = transfer
change.focus()
editor.focus()
// If the drag is internal and the target is after the selection, it
// needs to account for the selection's content being deleted.
@@ -326,10 +322,10 @@ function AfterPlugin(options = {}) {
}
if (isDraggingInternally) {
change.delete()
editor.delete()
}
change.select(target)
editor.select(target)
if (type == 'text' || type == 'html') {
const { anchor } = target
@@ -344,19 +340,19 @@ function AfterPlugin(options = {}) {
hasVoidParent = document.hasVoidParent(n.key, editor)
}
if (n) change.moveToStartOfNode(n)
if (n) editor.moveToStartOfNode(n)
}
if (text) {
text.split('\n').forEach((line, i) => {
if (i > 0) change.splitBlock()
change.insertText(line)
if (i > 0) editor.splitBlock()
editor.insertText(line)
})
}
}
if (type == 'fragment') {
change.insertFragment(fragment)
editor.insertFragment(fragment)
}
// COMPAT: React's onSelect event breaks after an onDrop event
@@ -383,13 +379,13 @@ function AfterPlugin(options = {}) {
* On input.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onInput(event, change, next) {
function onInput(event, editor, next) {
const window = getWindow(event.target)
const { editor, value } = change
const { value } = editor
// Get the selection point.
const native = window.getSelection()
@@ -443,7 +439,7 @@ function AfterPlugin(options = {}) {
entire = document.resolveRange(entire)
// Change the current value to have the leaf's text replaced.
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
editor.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
next()
}
@@ -451,14 +447,14 @@ function AfterPlugin(options = {}) {
* On key down.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onKeyDown(event, change, next) {
function onKeyDown(event, editor, next) {
debug('onKeyDown', { event })
const { editor, value } = change
const { value } = editor
const { document, selection } = value
const hasVoidParent = document.hasVoidParent(selection.start.path, editor)
@@ -467,40 +463,40 @@ function AfterPlugin(options = {}) {
// preserve native autocorrect behavior, so they shouldn't be handled here.
if (Hotkeys.isSplitBlock(event) && !IS_IOS) {
return hasVoidParent
? change.moveToStartOfNextText()
: change.splitBlock()
? editor.moveToStartOfNextText()
: editor.splitBlock()
}
if (Hotkeys.isDeleteBackward(event) && !IS_IOS) {
return change.deleteCharBackward()
return editor.deleteCharBackward()
}
if (Hotkeys.isDeleteForward(event) && !IS_IOS) {
return change.deleteCharForward()
return editor.deleteCharForward()
}
if (Hotkeys.isDeleteLineBackward(event)) {
return change.deleteLineBackward()
return editor.deleteLineBackward()
}
if (Hotkeys.isDeleteLineForward(event)) {
return change.deleteLineForward()
return editor.deleteLineForward()
}
if (Hotkeys.isDeleteWordBackward(event)) {
return change.deleteWordBackward()
return editor.deleteWordBackward()
}
if (Hotkeys.isDeleteWordForward(event)) {
return change.deleteWordForward()
return editor.deleteWordForward()
}
if (Hotkeys.isRedo(event)) {
return change.redo()
return editor.redo()
}
if (Hotkeys.isUndo(event)) {
return change.undo()
return editor.undo()
}
// COMPAT: Certain browsers don't handle the selection updates properly. In
@@ -508,22 +504,22 @@ function AfterPlugin(options = {}) {
// selection isn't properly collapsed. (2017/10/17)
if (Hotkeys.isMoveLineBackward(event)) {
event.preventDefault()
return change.moveToStartOfBlock()
return editor.moveToStartOfBlock()
}
if (Hotkeys.isMoveLineForward(event)) {
event.preventDefault()
return change.moveToEndOfBlock()
return editor.moveToEndOfBlock()
}
if (Hotkeys.isExtendLineBackward(event)) {
event.preventDefault()
return change.moveFocusToStartOfBlock()
return editor.moveFocusToStartOfBlock()
}
if (Hotkeys.isExtendLineForward(event)) {
event.preventDefault()
return change.moveFocusToEndOfBlock()
return editor.moveFocusToEndOfBlock()
}
// COMPAT: If a void node is selected, or a zero-width text node adjacent to
@@ -533,30 +529,30 @@ function AfterPlugin(options = {}) {
event.preventDefault()
if (!selection.isCollapsed) {
return change.moveToStart()
return editor.moveToStart()
}
return change.moveBackward()
return editor.moveBackward()
}
if (Hotkeys.isMoveForward(event)) {
event.preventDefault()
if (!selection.isCollapsed) {
return change.moveToEnd()
return editor.moveToEnd()
}
return change.moveForward()
return editor.moveForward()
}
if (Hotkeys.isMoveWordBackward(event)) {
event.preventDefault()
return change.moveWordBackward()
return editor.moveWordBackward()
}
if (Hotkeys.isMoveWordForward(event)) {
event.preventDefault()
return change.moveWordForward()
return editor.moveWordForward()
}
if (Hotkeys.isExtendBackward(event)) {
@@ -566,7 +562,7 @@ function AfterPlugin(options = {}) {
if (hasVoidParent || isPreviousInVoid || startText.text == '') {
event.preventDefault()
return change.moveFocusBackward()
return editor.moveFocusBackward()
}
}
@@ -577,7 +573,7 @@ function AfterPlugin(options = {}) {
if (hasVoidParent || isNextInVoid || startText.text == '') {
event.preventDefault()
return change.moveFocusForward()
return editor.moveFocusForward()
}
}
@@ -588,31 +584,31 @@ function AfterPlugin(options = {}) {
* On paste.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onPaste(event, change, next) {
function onPaste(event, editor, next) {
debug('onPaste', { event })
const { value } = change
const { value } = editor
const transfer = getEventTransfer(event)
const { type, fragment, text } = transfer
if (type == 'fragment') {
change.insertFragment(fragment)
editor.insertFragment(fragment)
}
if (type == 'text' || type == 'html') {
if (!text) return next()
const { document, selection, startBlock } = value
if (change.isVoid(startBlock)) return next()
if (editor.isVoid(startBlock)) return next()
const defaultBlock = startBlock
const defaultMarks = document.getInsertMarksAtRange(selection)
const frag = Plain.deserialize(text, { defaultBlock, defaultMarks })
.document
change.insertFragment(frag)
editor.insertFragment(frag)
}
next()
@@ -622,21 +618,21 @@ function AfterPlugin(options = {}) {
* On select.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onSelect(event, change, next) {
function onSelect(event, editor, next) {
debug('onSelect', { event })
const window = getWindow(event.target)
const { editor, value } = change
const { value } = editor
const { document } = value
const native = window.getSelection()
// If there are no ranges, the editor was blurred natively.
if (!native.rangeCount) {
change.blur()
editor.blur()
return
}
@@ -661,10 +657,10 @@ function AfterPlugin(options = {}) {
// selection, go with `0`. (2017/09/07)
if (
anchorBlock &&
!change.isVoid(anchorBlock) &&
!editor.isVoid(anchorBlock) &&
anchor.offset == 0 &&
focusBlock &&
change.isVoid(focusBlock) &&
editor.isVoid(focusBlock) &&
focus.offset != 0
) {
range = range.setFocus(focus.setOffset(0))
@@ -675,7 +671,7 @@ function AfterPlugin(options = {}) {
// standardizes the behavior, since it's indistinguishable to the user.
if (
anchorInline &&
!change.isVoid(anchorInline) &&
!editor.isVoid(anchorInline) &&
anchor.offset == anchorText.text.length
) {
const block = document.getClosestBlock(anchor.key)
@@ -685,7 +681,7 @@ function AfterPlugin(options = {}) {
if (
focusInline &&
!change.isVoid(focusInline) &&
!editor.isVoid(focusInline) &&
focus.offset == focusText.text.length
) {
const block = document.getClosestBlock(focus.key)
@@ -697,10 +693,10 @@ function AfterPlugin(options = {}) {
selection = selection.setIsFocused(true)
// Preserve active marks from the current selection.
// They will be cleared by `change.select` if the selection actually moved.
// They will be cleared by `editor.select` if the selection actually moved.
selection = selection.set('marks', value.selection.marks)
change.select(selection)
editor.select(selection)
next()
}

View File

@@ -36,12 +36,11 @@ function BeforePlugin() {
* On before input.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onBeforeInput(event, change, next) {
const { editor } = change
function onBeforeInput(event, editor, next) {
const isSynthetic = !!event.nativeEvent
if (editor.readOnly) return
@@ -58,12 +57,11 @@ function BeforePlugin() {
* On blur.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onBlur(event, change, next) {
const { editor } = change
function onBlur(event, editor, next) {
if (isCopying) return
if (editor.readOnly) return
@@ -94,7 +92,7 @@ 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, editor)
if (el.contains(relatedTarget) && node && !change.isVoid(node)) return
if (el.contains(relatedTarget) && node && !editor.isVoid(node)) return
}
debug('onBlur', { event })
@@ -105,12 +103,11 @@ function BeforePlugin() {
* On composition end.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCompositionEnd(event, change, next) {
const { editor } = change
function onCompositionEnd(event, editor, next) {
const n = compositionCount
// The `count` check here ensures that if another composition starts
@@ -137,11 +134,11 @@ function BeforePlugin() {
* On click.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onClick(event, change, next) {
function onClick(event, editor, next) {
debug('onClick', { event })
next()
}
@@ -150,14 +147,13 @@ function BeforePlugin() {
* On composition start.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCompositionStart(event, change, next) {
function onCompositionStart(event, editor, next) {
isComposing = true
compositionCount++
const { editor } = change
// HACK: we need to re-render the editor here so that it will update its
// placeholder in case one is currently rendered. This should be handled
@@ -167,7 +163,7 @@ function BeforePlugin() {
editor.setState({ isComposing: true })
}
const { value } = change
const { value } = editor
const { selection } = value
if (!selection.isCollapsed) {
@@ -178,7 +174,7 @@ function BeforePlugin() {
// (because it cannot find <span> nodes in DOM). This is a workaround that
// erases selection as soon as composition starts and preventing <spans>
// to be dropped.
change.delete()
editor.delete()
}
debug('onCompositionStart', { event })
@@ -189,11 +185,11 @@ function BeforePlugin() {
* On copy.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCopy(event, change, next) {
function onCopy(event, editor, next) {
const window = getWindow(event.target)
isCopying = true
window.requestAnimationFrame(() => (isCopying = false))
@@ -206,12 +202,11 @@ function BeforePlugin() {
* On cut.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onCut(event, change, next) {
const { editor } = change
function onCut(event, editor, next) {
if (editor.readOnly) return
const window = getWindow(event.target)
@@ -226,11 +221,11 @@ function BeforePlugin() {
* On drag end.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragEnd(event, change, next) {
function onDragEnd(event, editor, next) {
isDragging = false
debug('onDragEnd', { event })
next()
@@ -240,11 +235,11 @@ function BeforePlugin() {
* On drag enter.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragEnter(event, change, next) {
function onDragEnter(event, editor, next) {
debug('onDragEnter', { event })
next()
}
@@ -253,11 +248,11 @@ function BeforePlugin() {
* On drag exit.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragExit(event, change, next) {
function onDragExit(event, editor, next) {
debug('onDragExit', { event })
next()
}
@@ -266,11 +261,11 @@ function BeforePlugin() {
* On drag leave.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragLeave(event, change, next) {
function onDragLeave(event, editor, next) {
debug('onDragLeave', { event })
next()
}
@@ -279,18 +274,17 @@ function BeforePlugin() {
* On drag over.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragOver(event, change, next) {
function onDragOver(event, editor, next) {
// If the target is inside a void node, and only in this case,
// 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 { editor } = change
const node = findNode(event.target, editor)
if (change.isVoid(node)) event.preventDefault()
if (editor.isVoid(node)) event.preventDefault()
// COMPAT: IE won't call onDrop on contentEditables unless the
// default dragOver is prevented:
@@ -319,11 +313,11 @@ function BeforePlugin() {
* On drag start.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDragStart(event, change, next) {
function onDragStart(event, editor, next) {
isDragging = true
debug('onDragStart', { event })
next()
@@ -333,12 +327,11 @@ function BeforePlugin() {
* On drop.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onDrop(event, change, next) {
const { editor } = change
function onDrop(event, editor, next) {
if (editor.readOnly) return
// Prevent default so the DOM's value isn't corrupted.
@@ -352,12 +345,11 @@ function BeforePlugin() {
* On focus.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onFocus(event, change, next) {
const { editor } = change
function onFocus(event, editor, next) {
if (isCopying) return
if (editor.readOnly) return
@@ -383,13 +375,13 @@ function BeforePlugin() {
* On input.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onInput(event, change, next) {
function onInput(event, editor, next) {
if (isComposing) return
if (change.value.selection.isBlurred) return
if (editor.value.selection.isBlurred) return
debug('onInput', { event })
next()
}
@@ -398,12 +390,11 @@ function BeforePlugin() {
* On key down.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onKeyDown(event, change, next) {
const { editor } = change
function onKeyDown(event, editor, next) {
if (editor.readOnly) return
// When composing, we need to prevent all hotkeys from executing while
@@ -415,7 +406,7 @@ function BeforePlugin() {
}
// Certain hotkeys have native editing behaviors in `contenteditable`
// elements which will change the DOM and cause our value to be out of sync,
// elements which will editor the DOM and cause our value to be out of sync,
// so they need to always be prevented.
if (
!IS_IOS &&
@@ -443,12 +434,11 @@ function BeforePlugin() {
* On paste.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onPaste(event, change, next) {
const { editor } = change
function onPaste(event, editor, next) {
if (editor.readOnly) return
// Prevent defaults so the DOM state isn't corrupted.
@@ -462,15 +452,14 @@ function BeforePlugin() {
* On select.
*
* @param {Event} event
* @param {Change} change
* @param {Editor} editor
* @param {Function} next
*/
function onSelect(event, change, next) {
function onSelect(event, editor, next) {
if (isCopying) return
if (isComposing) return
const { editor } = change
if (editor.readOnly) return
// Save the new `activeElement`.

View File

@@ -41,14 +41,13 @@ function ReactPlugin(options = {}) {
* @return {Object}
*/
function renderEditor(props, next) {
const { editor } = props
function renderEditor(props, editor, next) {
return (
<Content
onEvent={editor.event}
autoCorrect={props.autoCorrect}
className={props.className}
editor={editor}
onEvent={(handler, event) => editor.run(handler, event)}
readOnly={props.readOnly}
role={props.role}
spellCheck={props.spellCheck}
@@ -63,11 +62,12 @@ function ReactPlugin(options = {}) {
* Render node.
*
* @param {Object} props
* @param {Editor} editor
* @param {Function} next
* @return {Element}
*/
function renderNode(props, next) {
function renderNode(props, editor, next) {
const { attributes, children, node } = props
const { object } = node
if (object != 'block' && object != 'inline') return null
@@ -89,8 +89,8 @@ function ReactPlugin(options = {}) {
* @return {Element}
*/
function renderPlaceholder(props, next) {
const { editor, node } = props
function renderPlaceholder(props, editor, next) {
const { node } = props
if (!editor.props.placeholder) return null
if (editor.state.isComposing) return null
if (node.object != 'block') return null

View File

@@ -11,7 +11,7 @@ function Image(props) {
})
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'image':
return Image(props)

View File

@@ -11,7 +11,7 @@ function Image(props) {
})
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'image':
return Image(props)

View File

@@ -11,7 +11,7 @@ function Code(props) {
)
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'code':
return Code(props)

View File

@@ -11,7 +11,7 @@ function Image(props) {
})
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'image':
return Image(props)

View File

@@ -10,7 +10,7 @@ function Image(props) {
})
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'image':
return Image(props)

View File

@@ -26,7 +26,7 @@ function Bold(props) {
return React.createElement('strong', { ...props.attributes }, props.children)
}
function renderMark(props, next) {
function renderMark(props, editor, next) {
switch (props.mark.type) {
case 'bold':
return Bold(props)

View File

@@ -11,7 +11,7 @@ function Link(props) {
)
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'link':
return Link(props)

View File

@@ -7,7 +7,7 @@ function Emoji(props) {
return React.createElement('img', props.attributes)
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'emoji':
return Emoji(props)

View File

@@ -11,7 +11,7 @@ function Link(props) {
)
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'link':
return Link(props)

View File

@@ -7,7 +7,7 @@ function Bold(props) {
return React.createElement('strong', { ...props.attributes }, props.children)
}
function renderMark(props, next) {
function renderMark(props, editor, next) {
switch (props.mark.type) {
case 'bold':
return Bold(props)

View File

@@ -10,7 +10,7 @@ function Image(props) {
})
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'image':
return Image(props)

View File

@@ -7,7 +7,7 @@ function Emoji(props) {
return React.createElement('img', props.attributes)
}
function renderNode(props, next) {
function renderNode(props, editor, next) {
switch (props.node.type) {
case 'emoji':
return Emoji(props)