1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-15 11:44:05 +02:00

Remove commands (#3351)

* remove commands in favor of editor-level functions

* update examples

* fix lint
This commit is contained in:
Ian Storm Taylor
2019-12-18 15:00:42 -05:00
committed by GitHub
parent c2d7905e19
commit 0bbe121d76
578 changed files with 3532 additions and 3370 deletions

View File

@@ -1,5 +1,13 @@
import React, { useEffect, useRef, useMemo, useCallback } from 'react'
import { Editor, Element, NodeEntry, Node, Range, Text } from 'slate'
import {
Editor,
Element,
NodeEntry,
Node,
Range,
Text,
Transforms,
} from 'slate'
import debounce from 'debounce'
import scrollIntoView from 'scroll-into-view-if-needed'
@@ -252,7 +260,7 @@ export const Editable = (props: EditableProps) => {
const range = ReactEditor.toSlateRange(editor, targetRange)
if (!selection || !Range.equals(selection, range)) {
Editor.select(editor, range)
Transforms.select(editor, range)
}
}
}
@@ -264,7 +272,7 @@ export const Editable = (props: EditableProps) => {
Range.isExpanded(selection) &&
type.startsWith('delete')
) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
return
}
@@ -272,60 +280,60 @@ export const Editable = (props: EditableProps) => {
case 'deleteByComposition':
case 'deleteByCut':
case 'deleteByDrag': {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
break
}
case 'deleteContent':
case 'deleteContentForward': {
editor.exec({ type: 'delete_forward', unit: 'character' })
Editor.deleteForward(editor)
break
}
case 'deleteContentBackward': {
editor.exec({ type: 'delete_backward', unit: 'character' })
Editor.deleteBackward(editor)
break
}
case 'deleteEntireSoftLine': {
editor.exec({ type: 'delete_backward', unit: 'line' })
editor.exec({ type: 'delete_forward', unit: 'line' })
Editor.deleteBackward(editor, { unit: 'line' })
Editor.deleteForward(editor, { unit: 'line' })
break
}
case 'deleteHardLineBackward': {
editor.exec({ type: 'delete_backward', unit: 'block' })
Editor.deleteBackward(editor, { unit: 'block' })
break
}
case 'deleteSoftLineBackward': {
editor.exec({ type: 'delete_backward', unit: 'line' })
Editor.deleteBackward(editor, { unit: 'line' })
break
}
case 'deleteHardLineForward': {
editor.exec({ type: 'delete_forward', unit: 'block' })
Editor.deleteForward(editor, { unit: 'block' })
break
}
case 'deleteSoftLineForward': {
editor.exec({ type: 'delete_forward', unit: 'line' })
Editor.deleteForward(editor, { unit: 'line' })
break
}
case 'deleteWordBackward': {
editor.exec({ type: 'delete_backward', unit: 'word' })
Editor.deleteBackward(editor, { unit: 'word' })
break
}
case 'deleteWordForward': {
editor.exec({ type: 'delete_forward', unit: 'word' })
Editor.deleteForward(editor, { unit: 'word' })
break
}
case 'insertLineBreak':
case 'insertParagraph': {
editor.exec({ type: 'insert_break' })
Editor.insertBreak(editor)
break
}
@@ -336,9 +344,9 @@ export const Editable = (props: EditableProps) => {
case 'insertReplacementText':
case 'insertText': {
if (data instanceof DataTransfer) {
editor.exec({ type: 'insert_data', data })
ReactEditor.insertData(editor, data)
} else if (typeof data === 'string') {
editor.exec({ type: 'insert_text', text: data })
Editor.insertText(editor, data)
}
break
@@ -378,9 +386,9 @@ export const Editable = (props: EditableProps) => {
hasEditableTarget(editor, domRange.endContainer)
) {
const range = ReactEditor.toSlateRange(editor, domRange)
Editor.select(editor, range)
Transforms.select(editor, range)
} else {
Editor.deselect(editor)
Transforms.deselect(editor)
}
}
}, 100),
@@ -441,7 +449,7 @@ export const Editable = (props: EditableProps) => {
if (IS_FIREFOX && !readOnly) {
event.preventDefault()
const text = (event as any).data as string
editor.exec({ type: 'insert_text', text })
Editor.insertText(editor, text)
}
},
[readOnly]
@@ -517,7 +525,7 @@ export const Editable = (props: EditableProps) => {
if (Editor.void(editor, { at: start })) {
const range = Editor.range(editor, start)
Editor.select(editor, range)
Transforms.select(editor, range)
}
}
},
@@ -536,7 +544,7 @@ export const Editable = (props: EditableProps) => {
// type that we need. So instead, insert whenever a composition
// ends since it will already have been committed to the DOM.
if (!IS_SAFARI && !IS_FIREFOX && event.data) {
editor.exec({ type: 'insert_text', text: event.data })
Editor.insertText(editor, event.data)
}
}
},
@@ -577,7 +585,7 @@ export const Editable = (props: EditableProps) => {
const { selection } = editor
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
}
}
},
@@ -615,7 +623,7 @@ export const Editable = (props: EditableProps) => {
// so that it shows up in the selection's fragment.
if (voidMatch) {
const range = Editor.range(editor, path)
Editor.select(editor, range)
Transforms.select(editor, range)
}
setFragmentData(event.dataTransfer, editor)
@@ -641,8 +649,8 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
const range = ReactEditor.findEventRange(editor, event)
const data = event.dataTransfer
Editor.select(editor, range)
editor.exec({ type: 'insert_data', data })
Transforms.select(editor, range)
ReactEditor.insertData(editor, data)
}
}
},
@@ -688,13 +696,21 @@ export const Editable = (props: EditableProps) => {
// hotkeys ourselves. (2019/11/06)
if (Hotkeys.isRedo(nativeEvent)) {
event.preventDefault()
editor.exec({ type: 'redo' })
if (editor.undo) {
editor.undo()
}
return
}
if (Hotkeys.isUndo(nativeEvent)) {
event.preventDefault()
editor.exec({ type: 'undo' })
if (editor.redo) {
editor.redo()
}
return
}
@@ -704,19 +720,19 @@ export const Editable = (props: EditableProps) => {
// (2017/10/17)
if (Hotkeys.isMoveLineBackward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, { unit: 'line', reverse: true })
Transforms.move(editor, { unit: 'line', reverse: true })
return
}
if (Hotkeys.isMoveLineForward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, { unit: 'line' })
Transforms.move(editor, { unit: 'line' })
return
}
if (Hotkeys.isExtendLineBackward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, {
Transforms.move(editor, {
unit: 'line',
edge: 'focus',
reverse: true,
@@ -726,7 +742,7 @@ export const Editable = (props: EditableProps) => {
if (Hotkeys.isExtendLineForward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, { unit: 'line', edge: 'focus' })
Transforms.move(editor, { unit: 'line', edge: 'focus' })
return
}
@@ -739,9 +755,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isCollapsed(selection)) {
Editor.move(editor, { reverse: true })
Transforms.move(editor, { reverse: true })
} else {
Editor.collapse(editor, { edge: 'start' })
Transforms.collapse(editor, { edge: 'start' })
}
return
@@ -751,9 +767,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isCollapsed(selection)) {
Editor.move(editor)
Transforms.move(editor)
} else {
Editor.collapse(editor, { edge: 'end' })
Transforms.collapse(editor, { edge: 'end' })
}
return
@@ -761,13 +777,13 @@ export const Editable = (props: EditableProps) => {
if (Hotkeys.isMoveWordBackward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, { unit: 'word', reverse: true })
Transforms.move(editor, { unit: 'word', reverse: true })
return
}
if (Hotkeys.isMoveWordForward(nativeEvent)) {
event.preventDefault()
Editor.move(editor, { unit: 'word' })
Transforms.move(editor, { unit: 'word' })
return
}
@@ -788,7 +804,7 @@ export const Editable = (props: EditableProps) => {
if (Hotkeys.isSplitBlock(nativeEvent)) {
event.preventDefault()
editor.exec({ type: 'insert_break' })
Editor.insertBreak(editor)
return
}
@@ -796,9 +812,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_backward', unit: 'character' })
Editor.deleteBackward(editor)
}
return
@@ -808,9 +824,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_forward', unit: 'character' })
Editor.deleteForward(editor)
}
return
@@ -820,9 +836,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_backward', unit: 'line' })
Editor.deleteBackward(editor, { unit: 'line' })
}
return
@@ -832,9 +848,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_forward', unit: 'line' })
Editor.deleteForward(editor, { unit: 'line' })
}
return
@@ -844,9 +860,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_backward', unit: 'word' })
Editor.deleteBackward(editor, { unit: 'word' })
}
return
@@ -856,9 +872,9 @@ export const Editable = (props: EditableProps) => {
event.preventDefault()
if (selection && Range.isExpanded(selection)) {
editor.exec({ type: 'delete_fragment' })
Editor.deleteFragment(editor)
} else {
editor.exec({ type: 'delete_forward', unit: 'word' })
Editor.deleteForward(editor, { unit: 'word' })
}
return
@@ -879,10 +895,7 @@ export const Editable = (props: EditableProps) => {
!isEventHandled(event, attributes.onPaste)
) {
event.preventDefault()
editor.exec({
type: 'insert_data',
data: event.clipboardData,
})
ReactEditor.insertData(editor, event.clipboardData)
}
},
[readOnly, attributes.onPaste]
@@ -984,7 +997,10 @@ const isDOMEventHandled = (event: Event, handler?: (event: Event) => void) => {
* Set the currently selected fragment to the clipboard.
*/
const setFragmentData = (dataTransfer: DataTransfer, editor: Editor): void => {
const setFragmentData = (
dataTransfer: DataTransfer,
editor: ReactEditor
): void => {
const { selection } = editor
if (!selection) {

View File

@@ -1,5 +1,5 @@
import React, { useMemo, useState, useCallback } from 'react'
import { Editor, Node, Range } from 'slate'
import { Node } from 'slate'
import { ReactEditor } from '../plugin/react-editor'
import { FocusedContext } from '../hooks/use-focused'
@@ -13,7 +13,7 @@ import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
*/
export const Slate = (props: {
editor: Editor
editor: ReactEditor
value: Node[]
children: React.ReactNode
onChange: (value: Node[]) => void
@@ -21,7 +21,7 @@ export const Slate = (props: {
}) => {
const { editor, children, onChange, value, ...rest } = props
const [key, setKey] = useState(0)
const context: [Editor] = useMemo(() => {
const context: [ReactEditor] = useMemo(() => {
editor.children = value
Object.assign(editor, rest)
return [editor]

View File

@@ -1,14 +1,15 @@
import { Editor } from 'slate'
import { createContext, useContext } from 'react'
import { ReactEditor } from '../plugin/react-editor'
/**
* A React context for sharing the `Editor` class.
* A React context for sharing the editor object.
*/
export const EditorContext = createContext<Editor | null>(null)
export const EditorContext = createContext<ReactEditor | null>(null)
/**
* Get the current `Editor` class that the component lives under.
* Get the current editor object from the React context.
*/
export const useEditor = () => {

View File

@@ -1,15 +1,16 @@
import { Editor } from 'slate'
import { createContext, useContext } from 'react'
import { ReactEditor } from '../plugin/react-editor'
/**
* A React context for sharing the `Editor` class, in a way that re-renders the
* A React context for sharing the editor object, in a way that re-renders the
* context whenever changes occur.
*/
export const SlateContext = createContext<[Editor] | null>(null)
export const SlateContext = createContext<[ReactEditor] | null>(null)
/**
* Get the current `Editor` class that the component lives under.
* Get the current editor object from the React context.
*/
export const useSlate = () => {

View File

@@ -16,6 +16,5 @@ export { useSelected } from './hooks/use-selected'
export { useSlate } from './hooks/use-slate'
// Plugin
export { InsertDataCommand, ReactCommand } from './plugin/react-command'
export { ReactEditor } from './plugin/react-editor'
export { withReact } from './plugin/with-react'

View File

@@ -1,33 +0,0 @@
import { Command } from 'slate'
/**
* The `InsertDataCommand` inserts content from a `DataTransfer` object.
*/
export interface InsertDataCommand {
type: 'insert_data'
data: DataTransfer
}
/**
* The `ReactCommand` union for all commands that the React plugins defines.
*/
export type ReactCommand = InsertDataCommand
export const ReactCommand = {
/**
* Check if a value is a `ReactCommand` object.
*/
isReactCommand(value: any): value is InsertDataCommand {
if (Command.isCommand(value)) {
switch (value.type) {
case 'insert_data':
return value.data instanceof DataTransfer
}
}
return false
},
}

View File

@@ -1,4 +1,4 @@
import { Editor, Element, Node, Path, Point, Range } from 'slate'
import { Editor, Node, Path, Point, Range, Transforms } from 'slate'
import { Key } from '../utils/key'
import {
@@ -22,7 +22,13 @@ import {
normalizeDOMPoint,
} from '../utils/dom'
export interface ReactEditor extends Editor {}
/**
* A React and DOM-specific version of the `Editor` interface.
*/
export interface ReactEditor extends Editor {
insertData: (data: DataTransfer) => void
}
export const ReactEditor = {
/**
@@ -129,7 +135,7 @@ export const ReactEditor = {
}
if (selection) {
Editor.deselect(editor)
Transforms.deselect(editor)
}
},
@@ -170,6 +176,14 @@ export const ReactEditor = {
)
},
/**
* Insert data from a `DataTransfer` into the editor.
*/
insertData(editor: ReactEditor, data: DataTransfer): void {
editor.insertData(data)
},
/**
* Find the native DOM element from a Slate node.
*/

View File

@@ -1,8 +1,7 @@
import ReactDOM from 'react-dom'
import { Editor, Node, Path, Operation, Command } from 'slate'
import { Editor, Node, Path, Operation, Transforms } from 'slate'
import { ReactEditor } from './react-editor'
import { ReactCommand } from './react-command'
import { Key } from '../utils/key'
import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
@@ -10,18 +9,19 @@ import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
* `withReact` adds React and DOM specific behaviors to the editor.
*/
export const withReact = (editor: Editor): Editor => {
const { apply, exec, onChange } = editor
export const withReact = (editor: Editor): ReactEditor => {
const e = editor as ReactEditor
const { apply, onChange } = e
editor.apply = (op: Operation) => {
e.apply = (op: Operation) => {
const matches: [Path, Key][] = []
switch (op.type) {
case 'insert_text':
case 'remove_text':
case 'set_node': {
for (const [node, path] of Editor.levels(editor, { at: op.path })) {
const key = ReactEditor.findKey(editor, node)
for (const [node, path] of Editor.levels(e, { at: op.path })) {
const key = ReactEditor.findKey(e, node)
matches.push([path, key])
}
@@ -32,10 +32,10 @@ export const withReact = (editor: Editor): Editor => {
case 'remove_node':
case 'merge_node':
case 'split_node': {
for (const [node, path] of Editor.levels(editor, {
for (const [node, path] of Editor.levels(e, {
at: Path.parent(op.path),
})) {
const key = ReactEditor.findKey(editor, node)
const key = ReactEditor.findKey(e, node)
matches.push([path, key])
}
@@ -51,53 +51,45 @@ export const withReact = (editor: Editor): Editor => {
apply(op)
for (const [path, key] of matches) {
const [node] = Editor.node(editor, path)
const [node] = Editor.node(e, path)
NODE_TO_KEY.set(node, key)
}
}
editor.exec = (command: Command) => {
if (
ReactCommand.isReactCommand(command) &&
command.type === 'insert_data'
) {
const { data } = command
const fragment = data.getData('application/x-slate-fragment')
e.insertData = (data: DataTransfer) => {
const fragment = data.getData('application/x-slate-fragment')
if (fragment) {
const decoded = decodeURIComponent(window.atob(fragment))
const parsed = JSON.parse(decoded) as Node[]
Editor.insertFragment(editor, parsed)
return
}
if (fragment) {
const decoded = decodeURIComponent(window.atob(fragment))
const parsed = JSON.parse(decoded) as Node[]
Transforms.insertFragment(e, parsed)
return
}
const text = data.getData('text/plain')
const text = data.getData('text/plain')
if (text) {
const lines = text.split('\n')
let split = false
if (text) {
const lines = text.split('\n')
let split = false
for (const line of lines) {
if (split) {
Editor.splitNodes(editor)
}
Editor.insertText(editor, line)
split = true
for (const line of lines) {
if (split) {
Transforms.splitNodes(e)
}
Transforms.insertText(e, line)
split = true
}
} else {
exec(command)
}
}
editor.onChange = () => {
e.onChange = () => {
// COMPAT: React doesn't batch `setState` hook calls, which means that the
// children and selection can get out of sync for one render pass. So we
// have to use this unstable API to ensure it batches them. (2019/12/03)
// https://github.com/facebook/react/issues/14259#issuecomment-439702367
ReactDOM.unstable_batchedUpdates(() => {
const onContextChange = EDITOR_TO_ON_CHANGE.get(editor)
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
if (onContextChange) {
onContextChange()
@@ -107,5 +99,5 @@ export const withReact = (editor: Editor): Editor => {
})
}
return editor
return e
}