mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-29 09:59:48 +02:00
refactor core plugin for readability
This commit is contained in:
@@ -6,7 +6,6 @@ import React from 'react'
|
||||
import String from '../utils/string'
|
||||
import keycode from 'keycode'
|
||||
import { IS_WINDOWS, IS_MAC } from '../utils/environment'
|
||||
import OffsetKey from '../utils/offset-key'
|
||||
|
||||
/**
|
||||
* The default plugin.
|
||||
@@ -26,68 +25,57 @@ function Plugin(options = {}) {
|
||||
} = options
|
||||
|
||||
/**
|
||||
* Define a default block renderer.
|
||||
* The default block renderer.
|
||||
*
|
||||
* @type {Component}
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
class DEFAULT_BLOCK extends React.Component {
|
||||
render = () => {
|
||||
const { attributes, children } = this.props
|
||||
function DEFAULT_BLOCK(props) {
|
||||
return (
|
||||
<div {...attributes} style={{ position: 'relative' }}>
|
||||
{this.renderPlaceholder()}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderPlaceholder = () => {
|
||||
if (!placeholder) return null
|
||||
const { node, state } = this.props
|
||||
return (
|
||||
<Placeholder
|
||||
<div {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
{placeholder
|
||||
? <Placeholder
|
||||
className={placeholderClassName}
|
||||
node={node}
|
||||
parent={state.document}
|
||||
state={state}
|
||||
node={props.node}
|
||||
parent={props.state.document}
|
||||
state={props.state}
|
||||
style={placeholderStyle}
|
||||
>
|
||||
{placeholder}
|
||||
</Placeholder>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a default inline renderer.
|
||||
* The default inline renderer.
|
||||
*
|
||||
* @type {Component}
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
class DEFAULT_INLINE extends React.Component {
|
||||
render = () => {
|
||||
const { attributes, children } = this.props
|
||||
return <span {...attributes} style={{ position: 'relative' }}>{children}</span>
|
||||
}
|
||||
function DEFAULT_INLINE(props) {
|
||||
return (
|
||||
<span {...props.attributes} style={{ position: 'relative' }}>
|
||||
{props.children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the plugin.
|
||||
*/
|
||||
|
||||
return {
|
||||
|
||||
/**
|
||||
* The core `onBeforeInput` handler.
|
||||
* On before input, see if we can let the browser continue with it's native
|
||||
* input behavior, to avoid a re-render for performance.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {State or Null}
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
onBeforeInput(e, state, editor) {
|
||||
function onBeforeInput(e, state, editor) {
|
||||
const { renderDecorations } = editor
|
||||
const { startOffset, startText, startBlock } = state
|
||||
|
||||
@@ -129,23 +117,39 @@ function Plugin(options = {}) {
|
||||
|
||||
// Return the new state.
|
||||
return next
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The core `onDrop` handler.
|
||||
* On drop.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} drop
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {State or Null}
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
onDrop(e, drop, state, editor) {
|
||||
switch (drop.type) {
|
||||
case 'fragment': {
|
||||
function onDrop(e, data, state) {
|
||||
switch (data.type) {
|
||||
case 'text':
|
||||
case 'html':
|
||||
return onDropText(e, data, state)
|
||||
case 'fragment':
|
||||
return onDropFragment(e, data, state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On drop fragment.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onDropFragment(e, data, state) {
|
||||
const { selection } = state
|
||||
let { fragment, target, isInternal } = drop
|
||||
let { fragment, target, isInternal } = data
|
||||
|
||||
// If the drag is internal and the target is after the selection, it
|
||||
// needs to account for the selection's content being deleted.
|
||||
@@ -169,9 +173,17 @@ function Plugin(options = {}) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'html': {
|
||||
const { text, target } = drop
|
||||
/**
|
||||
* On drop text, split the blocks at new lines.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onDropText(e, data, state) {
|
||||
const { text, target } = data
|
||||
let transform = state
|
||||
.transform()
|
||||
.moveTo(target)
|
||||
@@ -185,40 +197,75 @@ function Plugin(options = {}) {
|
||||
|
||||
return transform.apply()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The core `onKeyDown` handler.
|
||||
* On key down.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {State or Null}
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
onKeyDown(e, state, editor) {
|
||||
const key = keycode(e.which)
|
||||
let transform = state.transform()
|
||||
|
||||
switch (key) {
|
||||
case 'enter': {
|
||||
const { startBlock } = state
|
||||
if (startBlock && !startBlock.isVoid) return transform.splitBlock().apply()
|
||||
|
||||
const { document, startKey } = state
|
||||
const text = document.getNextText(startKey)
|
||||
if (!text) return
|
||||
|
||||
return transform.collapseToStartOf(text).apply()
|
||||
function onKeyDown(e, state) {
|
||||
switch (keycode(e.which)) {
|
||||
case 'enter': return onKeyDownEnter(e, state)
|
||||
case 'backspace': return onKeyDownBackspace(e, state)
|
||||
case 'delete': return onKeyDownDelete(e, state)
|
||||
case 'y': return onKeyDownY(e, state)
|
||||
case 'z': return onKeyDownZ(e, state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On `enter` key down, split the current block in half.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownEnter(e, state) {
|
||||
const { document, startKey, startBlock } = state
|
||||
|
||||
// For void blocks, we don't want to split. Instead we just move to the
|
||||
// start of the next text node if one exists.
|
||||
if (startBlock && startBlock.isVoid) {
|
||||
const text = document.getNextText(startKey)
|
||||
if (!text) return
|
||||
return state
|
||||
.transform()
|
||||
.collapseToStartOf(text)
|
||||
.apply()
|
||||
}
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.splitBlock()
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* On `backspace` key down, delete backwards.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownBackspace(e, state) {
|
||||
// If expanded, delete regularly.
|
||||
if (state.isExpanded) {
|
||||
return state
|
||||
.transform()
|
||||
.delete()
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'backspace': {
|
||||
if (state.isExpanded) return transform.delete().apply()
|
||||
const { startOffset, startBlock } = state
|
||||
const text = startBlock.text
|
||||
let n
|
||||
|
||||
// Determine how far backwards to delete.
|
||||
if (Key.isWord(e)) {
|
||||
n = String.getWordOffsetBackward(text, startOffset)
|
||||
} else if (Key.isLine(e)) {
|
||||
@@ -227,15 +274,34 @@ function Plugin(options = {}) {
|
||||
n = String.getCharOffsetBackward(text, startOffset)
|
||||
}
|
||||
|
||||
return transform.deleteBackward(n).apply()
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackward(n)
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* On `delete` key down, delete forwards.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownDelete(e, state) {
|
||||
// If expanded, delete regularly.
|
||||
if (state.isExpanded) {
|
||||
return state
|
||||
.transform()
|
||||
.delete()
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'delete': {
|
||||
if (state.isExpanded) return transform.delete().apply()
|
||||
const { startOffset, startBlock } = state
|
||||
const text = startBlock.text
|
||||
let n
|
||||
|
||||
// Determine how far forwards to delete.
|
||||
if (Key.isWord(e)) {
|
||||
n = String.getWordOffsetForward(text, startOffset)
|
||||
} else if (Key.isLine(e)) {
|
||||
@@ -244,72 +310,72 @@ function Plugin(options = {}) {
|
||||
n = String.getCharOffsetForward(text, startOffset)
|
||||
}
|
||||
|
||||
return transform.deleteForward(n).apply()
|
||||
return state
|
||||
.transform()
|
||||
.deleteForward(n)
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'up': {
|
||||
if (state.isExpanded) return
|
||||
const first = state.blocks.first()
|
||||
if (!first || !first.isVoid) return
|
||||
e.preventDefault()
|
||||
return transform.collapseToEndOfPreviousBlock().apply()
|
||||
}
|
||||
|
||||
case 'down': {
|
||||
if (state.isExpanded) return
|
||||
const first = state.blocks.first()
|
||||
if (!first || !first.isVoid) return
|
||||
e.preventDefault()
|
||||
return transform.collapseToStartOfNextBlock().apply()
|
||||
}
|
||||
|
||||
case 'left': {
|
||||
if (state.isExpanded) return
|
||||
const node = state.blocks.first() || state.inlines.first()
|
||||
if (!node || !node.isVoid) return
|
||||
e.preventDefault()
|
||||
return transform.collapseToEndOfPreviousText().apply()
|
||||
}
|
||||
|
||||
case 'right': {
|
||||
if (state.isExpanded) return
|
||||
const node = state.blocks.first() || state.inlines.first()
|
||||
if (!node || !node.isVoid) return
|
||||
e.preventDefault()
|
||||
return transform.collapseToStartOfNextText().apply()
|
||||
}
|
||||
|
||||
case 'y': {
|
||||
if (!Key.isWindowsCommand(e)) return
|
||||
return transform.redo()
|
||||
}
|
||||
|
||||
case 'z': {
|
||||
if (!Key.isCommand(e)) return
|
||||
return IS_MAC && Key.isShift(e)
|
||||
? transform.redo()
|
||||
: transform.undo()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The core `onPaste` handler, which treats everything as plain text.
|
||||
* On `y` key down, redo.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} paste
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {State or Null}
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
onPaste(e, paste, state, editor) {
|
||||
switch (paste.type) {
|
||||
function onKeyDownY(e, state) {
|
||||
if (!Key.isWindowsCommand(e)) return
|
||||
return state
|
||||
.transform()
|
||||
.redo()
|
||||
}
|
||||
|
||||
/**
|
||||
* On `z` key down, undo or redo.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownZ(e, state) {
|
||||
if (!Key.isCommand(e)) return
|
||||
return state
|
||||
.transform()
|
||||
[IS_MAC && Key.isShift(e) ? 'redo' : 'undo']()
|
||||
}
|
||||
|
||||
/**
|
||||
* On paste.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onPaste(e, data, state) {
|
||||
switch (data.type) {
|
||||
case 'text':
|
||||
case 'html': {
|
||||
case 'html':
|
||||
return onPasteText(e, data, state)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On paste text, split blocks at new lines.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onPasteText(e, data, state) {
|
||||
let transform = state.transform()
|
||||
|
||||
paste.text
|
||||
data.text
|
||||
.split('\n')
|
||||
.forEach((line, i) => {
|
||||
if (i > 0) transform = transform.splitBlock()
|
||||
@@ -318,27 +384,24 @@ function Plugin(options = {}) {
|
||||
|
||||
return transform.apply()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The core `onSelect` handler.
|
||||
* On select.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} select
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @param {Editor} editor
|
||||
* @return {State or Null}
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
onSelect(e, select, state, editor) {
|
||||
const { selection, isNative } = select
|
||||
function onSelect(e, data, state) {
|
||||
const { selection, isNative } = data
|
||||
return state
|
||||
.transform()
|
||||
.moveTo(selection)
|
||||
.focus()
|
||||
.apply({ isNative })
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The core `node` renderer, which uses plain `<div>` or `<span>` depending on
|
||||
@@ -348,11 +411,23 @@ function Plugin(options = {}) {
|
||||
* @return {Component} component
|
||||
*/
|
||||
|
||||
renderNode(node) {
|
||||
function renderNode(node) {
|
||||
return node.kind == 'block'
|
||||
? DEFAULT_BLOCK
|
||||
: DEFAULT_INLINE
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the core plugin.
|
||||
*/
|
||||
|
||||
return {
|
||||
onBeforeInput,
|
||||
onDrop,
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
onSelect,
|
||||
renderNode
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user