mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-17 20:51:20 +02:00
fix plugins stack ordering and defaulting
This commit is contained in:
@@ -139,11 +139,12 @@ This sounds weird, but it can be pretty useful if you want to render additional
|
||||
|
||||
```js
|
||||
function renderEditor(props, next) {
|
||||
const { children, editor } = props
|
||||
const { editor } = props
|
||||
const wordCount = countWords(editor.value.text)
|
||||
const children = next()
|
||||
return (
|
||||
<React.Fragment>
|
||||
{props.children}
|
||||
{children}
|
||||
<span className="word-count">{wordCount}</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
|
@@ -100,12 +100,12 @@ Sometimes though, the declarative validation syntax isn't fine-grained enough to
|
||||
When you define a `normalizeNode` function, you either return nothing if the node's already valid, or you return a normalizer function that will make the node valid if it isn't. Here's an example:
|
||||
|
||||
```js
|
||||
function normalizeNode(node) {
|
||||
function normalizeNode(node, next) {
|
||||
const { nodes } = node
|
||||
if (node.object !== 'block') return
|
||||
if (nodes.size !== 3) return
|
||||
if (nodes.first().object !== 'text') return
|
||||
if (nodes.last().object !== 'text') return
|
||||
if (node.object !== 'block') return next()
|
||||
if (nodes.size !== 3) return next()
|
||||
if (nodes.first().object !== 'text') return next()
|
||||
if (nodes.last().object !== 'text') return next()
|
||||
return change => change.removeNodeByKey(node.key)
|
||||
}
|
||||
```
|
||||
|
@@ -31,7 +31,7 @@ In addition to the [core plugin hooks](../slate/plugins.md), when using `slate-r
|
||||
|
||||
The event hooks have a signature of `(event, change, next)`—the `event` is a React object that you are used to from React's event handlers.
|
||||
|
||||
The rendering hooks are just like render props common to other React API's, and receive `(props, editor, next)`. For more information, see the [Rendering](./rendering.md) reference.
|
||||
The rendering hooks are just like render props common to other React API's, and receive `(props, next)`. For more information, see the [Rendering](./rendering.md) reference.
|
||||
|
||||
### `decorateNode`
|
||||
|
||||
@@ -107,7 +107,7 @@ This handler is called whenever the native DOM selection changes.
|
||||
|
||||
### `renderEditor`
|
||||
|
||||
`Function renderEditor(props: Object, editor: Editor) => ReactNode|Void`
|
||||
`Function renderEditor(props: Object, next: Function) => ReactNode|Void`
|
||||
|
||||
The `renderEditor` property allows you to define higher-order-component-like behavior. It is passed all of the properties of the editor, including `props.children`. You can then choose to wrap the existing `children` in any custom elements or proxy the properties however you choose. This can be useful for rendering toolbars, styling the editor, rendering validation, etc. Remember that the `renderEditor` function has to render `props.children` for editor's content to render.
|
||||
|
||||
|
@@ -124,10 +124,12 @@ class CheckLists extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
switch (props.node.type) {
|
||||
case 'check-list-item':
|
||||
return <CheckListItem {...props} />
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,15 +154,15 @@ class CheckLists extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @return {Value|Void}
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
const { value } = change
|
||||
|
||||
if (event.key == 'Enter' && value.startBlock.type == 'check-list-item') {
|
||||
change.splitBlock().setBlocks({ data: { checked: false } })
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -170,8 +172,10 @@ class CheckLists extends React.Component {
|
||||
value.selection.startOffset == 0
|
||||
) {
|
||||
change.setBlocks('paragraph')
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -106,12 +106,14 @@ class CodeHighlighting extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
switch (props.node.type) {
|
||||
case 'code':
|
||||
return <CodeBlock {...props} />
|
||||
case 'code_line':
|
||||
return <CodeBlockLine {...props} />
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +124,7 @@ class CodeHighlighting extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -150,6 +152,8 @@ class CodeHighlighting extends React.Component {
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,17 +172,20 @@ class CodeHighlighting extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection, startBlock } = value
|
||||
if (event.key != 'Enter') return
|
||||
if (startBlock.type != 'code') return
|
||||
if (selection.isExpanded) change.delete()
|
||||
change.insertText('\n')
|
||||
return true
|
||||
const { startBlock } = value
|
||||
|
||||
if (event.key === 'Enter' && startBlock.type === 'code') {
|
||||
change.insertText('\n')
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -58,13 +58,16 @@ class Embeds extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Element}
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
switch (props.node.type) {
|
||||
case 'video':
|
||||
return <Video {...props} />
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -123,16 +123,19 @@ class Emojis extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node, isFocused } = props
|
||||
|
||||
switch (node.type) {
|
||||
case 'paragraph': {
|
||||
return <p {...attributes}>{children}</p>
|
||||
}
|
||||
|
||||
case 'emoji': {
|
||||
const code = node.data.get('code')
|
||||
return (
|
||||
@@ -146,6 +149,10 @@ class Emojis extends React.Component {
|
||||
</Emoji>
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -70,10 +70,12 @@ class ForcedLayout extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -81,6 +83,8 @@ class ForcedLayout extends React.Component {
|
||||
return <h2 {...attributes}>{children}</h2>
|
||||
case 'paragraph':
|
||||
return <p {...attributes}>{children}</p>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -189,10 +189,12 @@ class HoveringMenu extends React.Component {
|
||||
* Render a Slate mark.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -204,6 +206,8 @@ class HoveringMenu extends React.Component {
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -73,15 +73,19 @@ class HugeDocument extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
case 'heading':
|
||||
return <h1 {...attributes}>{children}</h1>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,10 +93,12 @@ class HugeDocument extends React.Component {
|
||||
* Render a Slate mark.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -104,6 +110,8 @@ class HugeDocument extends React.Component {
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -138,7 +138,7 @@ class Images extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, node, isFocused } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -146,6 +146,10 @@ class Images extends React.Component {
|
||||
const src = node.data.get('src')
|
||||
return <Image src={src} selected={isFocused} {...attributes} />
|
||||
}
|
||||
|
||||
default: {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,21 +181,22 @@ class Images extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onDropOrPaste = (event, change) => {
|
||||
onDropOrPaste = (event, change, next) => {
|
||||
const { editor } = change
|
||||
const target = getEventRange(event, editor)
|
||||
if (!target && event.type == 'drop') return
|
||||
if (!target && event.type === 'drop') return next()
|
||||
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, text, files } = transfer
|
||||
|
||||
if (type == 'files') {
|
||||
if (type === 'files') {
|
||||
for (const file of files) {
|
||||
const reader = new FileReader()
|
||||
const [mime] = file.type.split('/')
|
||||
if (mime != 'image') continue
|
||||
if (mime !== 'image') continue
|
||||
|
||||
reader.addEventListener('load', () => {
|
||||
editor.change(c => {
|
||||
@@ -201,13 +206,17 @@ class Images extends React.Component {
|
||||
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (type == 'text') {
|
||||
if (!isUrl(text)) return
|
||||
if (!isImage(text)) return
|
||||
if (type === 'text') {
|
||||
if (!isUrl(text)) return next()
|
||||
if (!isImage(text)) return next()
|
||||
change.call(insertImage, text, target)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -223,40 +223,52 @@ class InputTester extends React.Component {
|
||||
ref={this.ref}
|
||||
value={this.state.value}
|
||||
onChange={this.onChange}
|
||||
renderNode={({ attributes, children, node }) => {
|
||||
switch (node.type) {
|
||||
case 'block-quote':
|
||||
return <blockquote {...attributes}>{children}</blockquote>
|
||||
case 'bulleted-list':
|
||||
return <ul {...attributes}>{children}</ul>
|
||||
case 'heading-one':
|
||||
return <h1 {...attributes}>{children}</h1>
|
||||
case 'heading-two':
|
||||
return <h2 {...attributes}>{children}</h2>
|
||||
case 'list-item':
|
||||
return <li {...attributes}>{children}</li>
|
||||
case 'numbered-list':
|
||||
return <ol {...attributes}>{children}</ol>
|
||||
}
|
||||
}}
|
||||
renderMark={({ attributes, children, mark }) => {
|
||||
switch (mark.type) {
|
||||
case 'bold':
|
||||
return <strong {...attributes}>{children}</strong>
|
||||
case 'code':
|
||||
return <code {...attributes}>{children}</code>
|
||||
case 'italic':
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
}
|
||||
}}
|
||||
renderNode={this.renderNode}
|
||||
renderMark={this.renderMark}
|
||||
/>
|
||||
<EventsList />
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
case 'block-quote':
|
||||
return <blockquote {...attributes}>{children}</blockquote>
|
||||
case 'bulleted-list':
|
||||
return <ul {...attributes}>{children}</ul>
|
||||
case 'heading-one':
|
||||
return <h1 {...attributes}>{children}</h1>
|
||||
case 'heading-two':
|
||||
return <h2 {...attributes}>{children}</h2>
|
||||
case 'list-item':
|
||||
return <li {...attributes}>{children}</li>
|
||||
case 'numbered-list':
|
||||
return <ol {...attributes}>{children}</ol>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
renderMark = (props, next) => {
|
||||
const { attributes, children, mark } = props
|
||||
|
||||
switch (mark.type) {
|
||||
case 'bold':
|
||||
return <strong {...attributes}>{children}</strong>
|
||||
case 'code':
|
||||
return <code {...attributes}>{children}</code>
|
||||
case 'italic':
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
onRef = ref => {
|
||||
this.el = ref
|
||||
}
|
||||
|
@@ -100,10 +100,12 @@ class Links extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -116,6 +118,10 @@ class Links extends React.Component {
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,22 +185,22 @@ class Links extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onPaste = (event, change) => {
|
||||
if (change.value.selection.isCollapsed) return
|
||||
onPaste = (event, change, next) => {
|
||||
if (change.value.selection.isCollapsed) return next()
|
||||
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, text } = transfer
|
||||
if (type != 'text' && type != 'html') return
|
||||
if (!isUrl(text)) return
|
||||
if (type != 'text' && type != 'html') return next()
|
||||
if (!isUrl(text)) return next()
|
||||
|
||||
if (this.hasLinks()) {
|
||||
change.call(unwrapLink)
|
||||
}
|
||||
|
||||
change.call(wrapLink, text)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -53,21 +53,27 @@ class MarkdownPreview extends React.Component {
|
||||
* Render a Slate mark.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
case 'bold':
|
||||
return <strong {...attributes}>{children}</strong>
|
||||
|
||||
case 'code':
|
||||
return <code {...attributes}>{children}</code>
|
||||
|
||||
case 'italic':
|
||||
return <em {...attributes}>{children}</em>
|
||||
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
|
||||
case 'title': {
|
||||
return (
|
||||
<span
|
||||
@@ -83,6 +89,7 @@ class MarkdownPreview extends React.Component {
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
case 'punctuation': {
|
||||
return (
|
||||
<span {...attributes} style={{ opacity: 0.2 }}>
|
||||
@@ -90,6 +97,7 @@ class MarkdownPreview extends React.Component {
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
case 'list': {
|
||||
return (
|
||||
<span
|
||||
@@ -104,6 +112,7 @@ class MarkdownPreview extends React.Component {
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
case 'hr': {
|
||||
return (
|
||||
<span
|
||||
@@ -118,6 +127,10 @@ class MarkdownPreview extends React.Component {
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
default: {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -76,10 +76,12 @@ class MarkdownShortcuts extends React.Component {
|
||||
* Render a Slate node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -101,6 +103,8 @@ class MarkdownShortcuts extends React.Component {
|
||||
return <h6 {...attributes}>{children}</h6>
|
||||
case 'list-item':
|
||||
return <li {...attributes}>{children}</li>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,16 +123,19 @@ class MarkdownShortcuts extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
switch (event.key) {
|
||||
case ' ':
|
||||
return this.onSpace(event, change)
|
||||
return this.onSpace(event, change, next)
|
||||
case 'Backspace':
|
||||
return this.onBackspace(event, change)
|
||||
return this.onBackspace(event, change, next)
|
||||
case 'Enter':
|
||||
return this.onEnter(event, change)
|
||||
return this.onEnter(event, change, next)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,20 +145,20 @@ class MarkdownShortcuts extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onSpace = (event, change) => {
|
||||
onSpace = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection } = value
|
||||
if (selection.isExpanded) return
|
||||
if (selection.isExpanded) return next()
|
||||
|
||||
const { startBlock } = value
|
||||
const { start } = selection
|
||||
const chars = startBlock.text.slice(0, start.offset).replace(/\s*/g, '')
|
||||
const type = this.getType(chars)
|
||||
|
||||
if (!type) return
|
||||
if (type == 'list-item' && startBlock.type == 'list-item') return
|
||||
if (!type) return next()
|
||||
if (type == 'list-item' && startBlock.type == 'list-item') return next()
|
||||
event.preventDefault()
|
||||
|
||||
change.setBlocks(type)
|
||||
@@ -161,7 +168,6 @@ class MarkdownShortcuts extends React.Component {
|
||||
}
|
||||
|
||||
change.moveFocusToStartOfNode(startBlock).delete()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,16 +176,17 @@ class MarkdownShortcuts extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onBackspace = (event, change) => {
|
||||
onBackspace = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection } = value
|
||||
if (selection.isExpanded) return
|
||||
if (selection.start.offset != 0) return
|
||||
if (selection.isExpanded) return next()
|
||||
if (selection.start.offset != 0) return next()
|
||||
|
||||
const { startBlock } = value
|
||||
if (startBlock.type == 'paragraph') return
|
||||
if (startBlock.type == 'paragraph') return next()
|
||||
|
||||
event.preventDefault()
|
||||
change.setBlocks('paragraph')
|
||||
@@ -187,8 +194,6 @@ class MarkdownShortcuts extends React.Component {
|
||||
if (startBlock.type == 'list-item') {
|
||||
change.unwrapBlock('bulleted-list')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,18 +202,19 @@ class MarkdownShortcuts extends React.Component {
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
onEnter = (event, change) => {
|
||||
onEnter = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection } = value
|
||||
const { start, end, isExpanded } = selection
|
||||
if (isExpanded) return
|
||||
if (isExpanded) return next()
|
||||
|
||||
const { startBlock } = value
|
||||
if (start.offset == 0 && startBlock.text.length == 0)
|
||||
return this.onBackspace(event, change)
|
||||
if (end.offset != startBlock.text.length) return
|
||||
return this.onBackspace(event, change, next)
|
||||
if (end.offset != startBlock.text.length) return next()
|
||||
|
||||
if (
|
||||
startBlock.type != 'heading-one' &&
|
||||
@@ -219,12 +225,11 @@ class MarkdownShortcuts extends React.Component {
|
||||
startBlock.type != 'heading-six' &&
|
||||
startBlock.type != 'block-quote'
|
||||
) {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
change.splitBlock().setBlocks('paragraph')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -203,7 +203,7 @@ class PasteHtml extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node, isFocused } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -246,6 +246,10 @@ class PasteHtml extends React.Component {
|
||||
const src = node.data.get('src')
|
||||
return <Image src={src} selected={isFocused} {...attributes} />
|
||||
}
|
||||
|
||||
default: {
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,7 +260,7 @@ class PasteHtml extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -268,6 +272,8 @@ class PasteHtml extends React.Component {
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,12 +294,11 @@ class PasteHtml extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onPaste = (event, change) => {
|
||||
onPaste = (event, change, next) => {
|
||||
const transfer = getEventTransfer(event)
|
||||
if (transfer.type != 'html') return
|
||||
if (transfer.type != 'html') return next()
|
||||
const { document } = serializer.deserialize(transfer.html)
|
||||
change.insertFragment(document)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,10 +28,11 @@ const WordCounter = styled('span')`
|
||||
|
||||
function WordCount(options) {
|
||||
return {
|
||||
renderEditor(props) {
|
||||
renderEditor(props, next) {
|
||||
const children = next()
|
||||
return (
|
||||
<div>
|
||||
<div>{props.children}</div>
|
||||
<div>{children}</div>
|
||||
<WordCounter>
|
||||
Word Count: {props.value.document.text.split(' ').length}
|
||||
</WordCounter>
|
||||
|
@@ -166,7 +166,7 @@ class RichTextExample extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -182,6 +182,8 @@ class RichTextExample extends React.Component {
|
||||
return <li {...attributes}>{children}</li>
|
||||
case 'numbered-list':
|
||||
return <ol {...attributes}>{children}</ol>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ class RichTextExample extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -204,6 +206,8 @@ class RichTextExample extends React.Component {
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +229,7 @@ class RichTextExample extends React.Component {
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
let mark
|
||||
|
||||
if (isBoldHotkey(event)) {
|
||||
@@ -237,12 +241,11 @@ class RichTextExample extends React.Component {
|
||||
} else if (isCodeHotkey(event)) {
|
||||
mark = 'code'
|
||||
} else {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
change.toggleMark(mark)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -46,12 +46,14 @@ class RTL extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
case 'block-quote':
|
||||
return <blockquote {...attributes}>{children}</blockquote>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +74,14 @@ class RTL extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
if (event.key == 'Enter' && event.shiftKey) {
|
||||
event.preventDefault()
|
||||
change.insertText('\n')
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -108,7 +108,7 @@ class SearchHighlighting extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -118,6 +118,8 @@ class SearchHighlighting extends React.Component {
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -136,7 +136,7 @@ class SyncingEditor extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
@@ -148,6 +148,8 @@ class SyncingEditor extends React.Component {
|
||||
return <em {...attributes}>{children}</em>
|
||||
case 'underlined':
|
||||
return <u {...attributes}>{children}</u>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +177,7 @@ class SyncingEditor extends React.Component {
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
let mark
|
||||
|
||||
if (isBoldHotkey(event)) {
|
||||
@@ -187,12 +189,11 @@ class SyncingEditor extends React.Component {
|
||||
} else if (isCodeHotkey(event)) {
|
||||
mark = 'code'
|
||||
} else {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
change.toggleMark(mark)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -50,7 +50,7 @@ class Tables extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderNode = props => {
|
||||
renderNode = (props, next) => {
|
||||
const { attributes, children, node } = props
|
||||
|
||||
switch (node.type) {
|
||||
@@ -64,6 +64,8 @@ class Tables extends React.Component {
|
||||
return <tr {...attributes}>{children}</tr>
|
||||
case 'table-cell':
|
||||
return <td {...attributes}>{children}</td>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,12 +76,14 @@ class Tables extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderMark = props => {
|
||||
renderMark = (props, next) => {
|
||||
const { children, mark, attributes } = props
|
||||
|
||||
switch (mark.type) {
|
||||
case 'bold':
|
||||
return <strong {...attributes}>{children}</strong>
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,12 +94,11 @@ class Tables extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onBackspace = (event, change) => {
|
||||
onBackspace = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection } = value
|
||||
if (selection.start.offset != 0) return
|
||||
if (selection.start.offset != 0) return next()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,12 +118,11 @@ class Tables extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onDelete = (event, change) => {
|
||||
onDelete = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { selection } = value
|
||||
if (selection.end.offset != value.startText.text.length) return
|
||||
if (selection.end.offset != value.startText.text.length) return next()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -130,23 +132,22 @@ class Tables extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onDropOrPaste = (event, change) => {
|
||||
onDropOrPaste = (event, change, next) => {
|
||||
const transfer = getEventTransfer(event)
|
||||
const { value } = change
|
||||
const { text = '' } = transfer
|
||||
|
||||
if (value.startBlock.type !== 'table-cell') {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
if (!text) {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
const lines = text.split('\n')
|
||||
const { document } = Plain.deserialize(lines[0] || '')
|
||||
change.insertFragment(document)
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,9 +157,8 @@ class Tables extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onEnter = (event, change) => {
|
||||
onEnter = (event, change, next) => {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +168,7 @@ class Tables extends React.Component {
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (event, change) => {
|
||||
onKeyDown = (event, change, next) => {
|
||||
const { value } = change
|
||||
const { document, selection } = value
|
||||
const { start, isCollapsed } = selection
|
||||
@@ -181,24 +181,25 @@ class Tables extends React.Component {
|
||||
if (prevBlock.type === 'table-cell') {
|
||||
if (['Backspace', 'Delete', 'Enter'].includes(event.key)) {
|
||||
event.preventDefault()
|
||||
return true
|
||||
} else {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value.startBlock.type !== 'table-cell') {
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case 'Backspace':
|
||||
return this.onBackspace(event, change)
|
||||
return this.onBackspace(event, change, next)
|
||||
case 'Delete':
|
||||
return this.onDelete(event, change)
|
||||
return this.onDelete(event, change, next)
|
||||
case 'Enter':
|
||||
return this.onEnter(event, change)
|
||||
return this.onEnter(event, change, next)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -8,8 +8,6 @@ import warning from 'tiny-warning'
|
||||
import { Editor as Controller } from 'slate'
|
||||
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
import BrowserPlugin from '../plugins/browser'
|
||||
import PropsPlugin from '../plugins/props'
|
||||
import ReactPlugin from '../plugins/react'
|
||||
|
||||
/**
|
||||
@@ -143,7 +141,7 @@ class Editor extends React.Component {
|
||||
|
||||
render() {
|
||||
debug('render', this)
|
||||
const props = { ...this.props }
|
||||
const props = { ...this.props, editor: this }
|
||||
|
||||
// Re-resolve the controller if needed based on memoized props.
|
||||
const { commands, plugins, queries, schema } = props
|
||||
@@ -155,7 +153,7 @@ class Editor extends React.Component {
|
||||
this.controller.setValue(value, options)
|
||||
|
||||
// Render the editor's children with the controller.
|
||||
const children = this.controller.run('renderEditor', props, this)
|
||||
const children = this.controller.run('renderEditor', props)
|
||||
return children
|
||||
}
|
||||
|
||||
@@ -182,12 +180,8 @@ class Editor extends React.Component {
|
||||
'A Slate <Editor> component is re-resolving the `plugins`, `schema`, `commands` or `queries` on each update, which leads to poor performance. This is often due to passing in a new references for these props with each render by declaring them inline in your render function. Do not do this! Declare them outside your render function, or memoize them instead.'
|
||||
)
|
||||
|
||||
const { props, onControllerChange } = this
|
||||
const reactPlugin = ReactPlugin()
|
||||
const browserPlugin = BrowserPlugin()
|
||||
const propsPlugin = PropsPlugin(props)
|
||||
const allPlugins = [reactPlugin, browserPlugin, propsPlugin, ...plugins]
|
||||
const attrs = { onChange: onControllerChange, plugins: allPlugins }
|
||||
const react = ReactPlugin(this.props)
|
||||
const attrs = { onChange: this.onControllerChange, plugins: [react] }
|
||||
this.controller = new Controller(attrs, { editor: this, normalize: false })
|
||||
}
|
||||
|
||||
|
@@ -166,7 +166,7 @@ class Node extends React.Component {
|
||||
readOnly,
|
||||
}
|
||||
|
||||
let placeholder = editor.run('renderPlaceholder', props, editor)
|
||||
let placeholder = editor.run('renderPlaceholder', props)
|
||||
|
||||
if (placeholder) {
|
||||
placeholder = React.cloneElement(placeholder, {
|
||||
@@ -176,15 +176,11 @@ class Node extends React.Component {
|
||||
children = [placeholder, ...children]
|
||||
}
|
||||
|
||||
const element = editor.run(
|
||||
'renderNode',
|
||||
{
|
||||
...props,
|
||||
attributes,
|
||||
children,
|
||||
},
|
||||
editor
|
||||
)
|
||||
const element = editor.run('renderNode', {
|
||||
...props,
|
||||
attributes,
|
||||
children,
|
||||
})
|
||||
|
||||
return editor.query('isVoid', node) ? (
|
||||
<Void {...this.props}>{element}</Void>
|
||||
|
@@ -2,14 +2,8 @@ import Base64 from 'slate-base64-serializer'
|
||||
import Debug from 'debug'
|
||||
import Hotkeys from 'slate-hotkeys'
|
||||
import Plain from 'slate-plain-serializer'
|
||||
import ReactDOM from 'react-dom'
|
||||
import getWindow from 'get-window'
|
||||
import {
|
||||
IS_FIREFOX,
|
||||
IS_IE,
|
||||
IS_IOS,
|
||||
HAS_INPUT_EVENTS_LEVEL_2,
|
||||
} from 'slate-dev-environment'
|
||||
import { IS_IOS } from 'slate-dev-environment'
|
||||
|
||||
import cloneFragment from '../utils/clone-fragment'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
@@ -26,20 +20,16 @@ import setEventTransfer from '../utils/set-event-transfer'
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const debug = Debug('slate:browser')
|
||||
const debug = Debug('slate:after')
|
||||
|
||||
/**
|
||||
* A plugin that adds the browser-specific logic to the editor.
|
||||
* A plugin that adds the "after" browser-specific logic to the editor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function BrowserPlugin() {
|
||||
let activeElement = null
|
||||
let compositionCount = 0
|
||||
let isComposing = false
|
||||
let isCopying = false
|
||||
let isDragging = false
|
||||
function AfterPlugin(options = {}) {
|
||||
let isDraggingInternally = null
|
||||
|
||||
/**
|
||||
@@ -48,24 +38,11 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onBeforeInput(event, change, next) {
|
||||
const { editor, value } = change
|
||||
const isSynthetic = !!event.nativeEvent
|
||||
if (editor.readOnly) return true
|
||||
|
||||
// COMPAT: If the browser supports Input Events Level 2, we will have
|
||||
// attached a custom handler for the real `beforeinput` events, instead of
|
||||
// allowing React's synthetic polyfill, so we need to ignore synthetics.
|
||||
if (isSynthetic && HAS_INPUT_EVENTS_LEVEL_2) return true
|
||||
|
||||
debug('onBeforeInput', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
// If the event is synthetic, it's React's polyfill of `beforeinput` that
|
||||
// isn't a true `beforeinput` event with meaningful information. It only
|
||||
@@ -73,13 +50,15 @@ function BrowserPlugin() {
|
||||
if (isSynthetic) {
|
||||
event.preventDefault()
|
||||
change.insertText(event.data)
|
||||
return
|
||||
return next()
|
||||
}
|
||||
|
||||
// Otherwise, we can use the information in the `beforeinput` event to
|
||||
// figure out the exact change that will occur, and prevent it.
|
||||
const [targetRange] = event.getTargetRanges()
|
||||
if (!targetRange) return
|
||||
if (!targetRange) return next()
|
||||
|
||||
debug('onBeforeInput', { event })
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
@@ -93,29 +72,29 @@ function BrowserPlugin() {
|
||||
case 'deleteContentBackward':
|
||||
case 'deleteContentForward': {
|
||||
change.deleteAtRange(range)
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteWordBackward': {
|
||||
change.deleteWordBackwardAtRange(range)
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteWordForward': {
|
||||
change.deleteWordForwardAtRange(range)
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteSoftLineBackward':
|
||||
case 'deleteHardLineBackward': {
|
||||
change.deleteLineBackwardAtRange(range)
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteSoftLineForward':
|
||||
case 'deleteHardLineForward': {
|
||||
change.deleteLineForwardAtRange(range)
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'insertLineBreak':
|
||||
@@ -131,7 +110,7 @@ function BrowserPlugin() {
|
||||
change.splitBlockAtRange(range)
|
||||
}
|
||||
|
||||
return
|
||||
break
|
||||
}
|
||||
|
||||
case 'insertFromYank':
|
||||
@@ -146,7 +125,7 @@ function BrowserPlugin() {
|
||||
? event.dataTransfer.getData('text/plain')
|
||||
: event.data
|
||||
|
||||
if (text == null) return
|
||||
if (text == null) break
|
||||
|
||||
change.insertTextAtRange(range, text, selection.marks)
|
||||
|
||||
@@ -156,9 +135,11 @@ function BrowserPlugin() {
|
||||
change.select({ marks: null })
|
||||
}
|
||||
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,88 +148,12 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onBlur(event, change, next) {
|
||||
const { editor } = change
|
||||
if (isCopying) return true
|
||||
if (editor.readOnly) return true
|
||||
|
||||
const { relatedTarget, target } = event
|
||||
const window = getWindow(target)
|
||||
|
||||
// COMPAT: If the current `activeElement` is still the previous one, this is
|
||||
// due to the window being blurred when the tab itself becomes unfocused, so
|
||||
// we want to abort early to allow to editor to stay focused when the tab
|
||||
// becomes focused again.
|
||||
if (activeElement === window.document.activeElement) return true
|
||||
|
||||
// COMPAT: The `relatedTarget` can be null when the new focus target is not
|
||||
// a "focusable" element (eg. a `<div>` without `tabindex` set).
|
||||
if (relatedTarget) {
|
||||
const el = ReactDOM.findDOMNode(editor)
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is returning to the
|
||||
// editor from an embedded editable element (eg. an <input> element inside
|
||||
// a void node).
|
||||
if (relatedTarget === el) return true
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is moving from the
|
||||
// editor to inside a void node's spacer element.
|
||||
if (relatedTarget.hasAttribute('data-slate-spacer')) return true
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is moving to a non-
|
||||
// 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 true
|
||||
}
|
||||
|
||||
debug('onBlur', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
change.blur()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* On composition end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onCompositionEnd(event, change, next) {
|
||||
const { editor } = change
|
||||
const n = compositionCount
|
||||
|
||||
// The `count` check here ensures that if another composition starts
|
||||
// before the timeout has closed out this one, we will abort unsetting the
|
||||
// `isComposing` flag, since a composition is still in affect.
|
||||
window.requestAnimationFrame(() => {
|
||||
if (compositionCount > n) return
|
||||
isComposing = false
|
||||
|
||||
// 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
|
||||
// differently ideally, in a less invasive way?
|
||||
// (apply force re-render if isComposing changes)
|
||||
if (editor.state.isComposing) {
|
||||
editor.setState({ isComposing: false })
|
||||
}
|
||||
})
|
||||
|
||||
debug('onCompositionEnd', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
return next()
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -257,23 +162,18 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onClick(event, change, next) {
|
||||
debug('onClick', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return true
|
||||
if (editor.readOnly) return next()
|
||||
|
||||
const { value } = editor
|
||||
const { document } = value
|
||||
const node = findNode(event.target, editor)
|
||||
if (!node) return true
|
||||
if (!node) return next()
|
||||
|
||||
debug('onClick', { event })
|
||||
|
||||
const ancestors = document.getAncestors(node.key)
|
||||
const isVoid =
|
||||
@@ -287,35 +187,7 @@ function BrowserPlugin() {
|
||||
change.focus().moveToEndOfNode(node)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* On composition start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onCompositionStart(event, change, 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
|
||||
// differently ideally, in a less invasive way?
|
||||
// (apply force re-render if isComposing changes)
|
||||
if (!editor.state.isComposing) {
|
||||
editor.setState({ isComposing: true })
|
||||
}
|
||||
|
||||
debug('onCompositionStart', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
return next()
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -324,23 +196,13 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onCopy(event, change, next) {
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => (isCopying = false))
|
||||
|
||||
debug('onCopy', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { editor } = change
|
||||
cloneFragment(event, editor)
|
||||
return true
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,22 +211,11 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onCut(event, change, next) {
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return true
|
||||
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => (isCopying = false))
|
||||
|
||||
debug('onCut', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
const { editor } = change
|
||||
|
||||
// Once the fake cut content has successfully been added to the clipboard,
|
||||
// delete the content in the current selection.
|
||||
@@ -385,6 +236,8 @@ function BrowserPlugin() {
|
||||
editor.change(c => c.delete())
|
||||
}
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,96 +246,12 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragEnd(event, change, next) {
|
||||
debug('onDragEnd', { event })
|
||||
isDragging = false
|
||||
isDraggingInternally = null
|
||||
return next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag enter.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragEnter(event, change, next) {
|
||||
debug('onDragEnter', { event })
|
||||
return next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag exit.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragExit(event, change, next) {
|
||||
debug('onDragExit', { event })
|
||||
return next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag leave.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragLeave(event, change, next) {
|
||||
debug('onDragLeave', { event })
|
||||
return next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag over.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragOver(event, change, next) {
|
||||
debug('onDragOver', { event })
|
||||
|
||||
// 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()
|
||||
|
||||
// COMPAT: IE won't call onDrop on contentEditables unless the
|
||||
// default dragOver is prevented:
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/913982/
|
||||
// (2018/07/11)
|
||||
if (IS_IE) event.preventDefault()
|
||||
|
||||
// If a drag is already in progress, don't do this again.
|
||||
if (!isDragging) {
|
||||
isDragging = true
|
||||
|
||||
// COMPAT: IE will raise an `unspecified error` if dropEffect is
|
||||
// set. (2018/07/11)
|
||||
if (!IS_IE) {
|
||||
event.nativeEvent.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
return next()
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -491,19 +260,13 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDragStart(event, change, next) {
|
||||
debug('onDragStart', { event })
|
||||
|
||||
isDragging = true
|
||||
isDraggingInternally = true
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { editor } = change
|
||||
const { value } = editor
|
||||
const { document } = value
|
||||
@@ -523,6 +286,7 @@ function BrowserPlugin() {
|
||||
const fragment = change.value.fragment
|
||||
const encoded = Base64.serializeNode(fragment)
|
||||
setEventTransfer(event, 'fragment', encoded)
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -531,26 +295,16 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onDrop(event, change, next) {
|
||||
const { editor, value } = change
|
||||
if (editor.readOnly) return true
|
||||
|
||||
debug('onDrop', { event })
|
||||
|
||||
// Prevent default so the DOM's value isn't corrupted.
|
||||
event.preventDefault()
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { document, selection } = value
|
||||
const window = getWindow(event.target)
|
||||
let target = getEventRange(event, editor)
|
||||
if (!target) return true
|
||||
if (!target) return next()
|
||||
|
||||
debug('onDrop', { event })
|
||||
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, fragment, text } = transfer
|
||||
@@ -611,49 +365,18 @@ function BrowserPlugin() {
|
||||
// DOM node, since that will make it go back to normal.
|
||||
const focusNode = document.getNode(target.focus.key)
|
||||
const el = findDOMNode(focusNode, window)
|
||||
if (!el) return true
|
||||
|
||||
el.dispatchEvent(
|
||||
new MouseEvent('mouseup', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* On focus.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onFocus(event, change, next) {
|
||||
const { editor } = change
|
||||
if (isCopying) return true
|
||||
if (editor.readOnly) return true
|
||||
|
||||
const el = ReactDOM.findDOMNode(editor)
|
||||
|
||||
// Save the new `activeElement`.
|
||||
const window = getWindow(event.target)
|
||||
activeElement = window.document.activeElement
|
||||
|
||||
// COMPAT: If the editor has nested editable elements, the focus can go to
|
||||
// those elements. In Firefox, this must be prevented because it results in
|
||||
// issues with keyboard navigation. (2017/03/30)
|
||||
if (IS_FIREFOX && event.target != el) {
|
||||
el.focus()
|
||||
return true
|
||||
if (el) {
|
||||
el.dispatchEvent(
|
||||
new MouseEvent('mouseup', {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
debug('onFocus', { event })
|
||||
return next()
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -662,19 +385,9 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onInput(event, change, next) {
|
||||
if (isComposing) return true
|
||||
if (change.value.selection.isBlurred) return true
|
||||
|
||||
debug('onInput', { event })
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { editor, value } = change
|
||||
|
||||
@@ -682,7 +395,7 @@ function BrowserPlugin() {
|
||||
const native = window.getSelection()
|
||||
const { anchorNode } = native
|
||||
const point = findPoint(anchorNode, 0, editor)
|
||||
if (!point) return
|
||||
if (!point) return next()
|
||||
|
||||
// Get the text node and leaf in question.
|
||||
const { document, selection } = value
|
||||
@@ -716,7 +429,9 @@ function BrowserPlugin() {
|
||||
}
|
||||
|
||||
// If the text is no different, abort.
|
||||
if (textContent == text) return
|
||||
if (textContent == text) return next()
|
||||
|
||||
debug('onInput', { event })
|
||||
|
||||
// Determine what the selection should be after changing the text.
|
||||
const delta = textContent.length - text.length
|
||||
@@ -729,6 +444,7 @@ function BrowserPlugin() {
|
||||
|
||||
// Change the current value to have the leaf's text replaced.
|
||||
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -737,48 +453,12 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onKeyDown(event, change, next) {
|
||||
const { editor, value } = change
|
||||
if (editor.readOnly) return true
|
||||
|
||||
// When composing, we need to prevent all hotkeys from executing while
|
||||
// typing. However, certain characters also move the selection before
|
||||
// we're able to handle it, so prevent their default behavior.
|
||||
if (isComposing) {
|
||||
if (Hotkeys.isCompose(event)) event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
debug('onKeyDown', { event })
|
||||
|
||||
// Certain hotkeys have native editing behaviors in `contenteditable`
|
||||
// elements which will change the DOM and cause our value to be out of sync,
|
||||
// so they need to always be prevented.
|
||||
if (
|
||||
!IS_IOS &&
|
||||
(Hotkeys.isBold(event) ||
|
||||
Hotkeys.isDeleteBackward(event) ||
|
||||
Hotkeys.isDeleteForward(event) ||
|
||||
Hotkeys.isDeleteLineBackward(event) ||
|
||||
Hotkeys.isDeleteLineForward(event) ||
|
||||
Hotkeys.isDeleteWordBackward(event) ||
|
||||
Hotkeys.isDeleteWordForward(event) ||
|
||||
Hotkeys.isItalic(event) ||
|
||||
Hotkeys.isRedo(event) ||
|
||||
Hotkeys.isSplitBlock(event) ||
|
||||
Hotkeys.isTransposeCharacter(event) ||
|
||||
Hotkeys.isUndo(event))
|
||||
) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { editor, value } = change
|
||||
const { document, selection } = value
|
||||
const hasVoidParent = document.hasVoidParent(selection.start.path, editor)
|
||||
|
||||
@@ -893,7 +573,7 @@ function BrowserPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -902,22 +582,12 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onPaste(event, change, next) {
|
||||
const { editor, value } = change
|
||||
if (editor.readOnly) return true
|
||||
|
||||
debug('onPaste', { event })
|
||||
|
||||
// Prevent defaults so the DOM state isn't corrupted.
|
||||
event.preventDefault()
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { value } = change
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, fragment, text } = transfer
|
||||
|
||||
@@ -926,9 +596,9 @@ function BrowserPlugin() {
|
||||
}
|
||||
|
||||
if (type == 'text' || type == 'html') {
|
||||
if (!text) return true
|
||||
if (!text) return next()
|
||||
const { document, selection, startBlock } = value
|
||||
if (change.isVoid(startBlock)) return true
|
||||
if (change.isVoid(startBlock)) return next()
|
||||
|
||||
const defaultBlock = startBlock
|
||||
const defaultMarks = document.getInsertMarksAtRange(selection)
|
||||
@@ -937,7 +607,7 @@ function BrowserPlugin() {
|
||||
change.insertFragment(frag)
|
||||
}
|
||||
|
||||
return true
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -946,38 +616,25 @@ function BrowserPlugin() {
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function onSelect(event, change, next) {
|
||||
if (isCopying) return true
|
||||
if (isComposing) return true
|
||||
|
||||
const { editor, value } = change
|
||||
if (editor.readOnly) return true
|
||||
|
||||
debug('onSelect', { event })
|
||||
|
||||
// Save the new `activeElement`.
|
||||
const window = getWindow(event.target)
|
||||
activeElement = window.document.activeElement
|
||||
|
||||
// Delegate to the plugins stack.
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { editor, value } = change
|
||||
const { document } = value
|
||||
const native = window.getSelection()
|
||||
|
||||
// If there are no ranges, the editor was blurred natively.
|
||||
if (!native.rangeCount) {
|
||||
change.blur()
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
let range = findRange(native, editor)
|
||||
if (!range) return true
|
||||
if (!range) return
|
||||
|
||||
const { anchor, focus } = range
|
||||
const anchorText = document.getNode(anchor.key)
|
||||
@@ -1036,7 +693,7 @@ function BrowserPlugin() {
|
||||
selection = selection.set('marks', value.selection.marks)
|
||||
|
||||
change.select(selection)
|
||||
return true
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1049,18 +706,11 @@ function BrowserPlugin() {
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onClick,
|
||||
onCompositionEnd,
|
||||
onCompositionStart,
|
||||
onCopy,
|
||||
onCut,
|
||||
onDragEnd,
|
||||
onDragEnter,
|
||||
onDragExit,
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDragStart,
|
||||
onDrop,
|
||||
onFocus,
|
||||
onInput,
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
@@ -1074,4 +724,4 @@ function BrowserPlugin() {
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default BrowserPlugin
|
||||
export default AfterPlugin
|
505
packages/slate-react/src/plugins/before.js
Normal file
505
packages/slate-react/src/plugins/before.js
Normal file
@@ -0,0 +1,505 @@
|
||||
import Debug from 'debug'
|
||||
import Hotkeys from 'slate-hotkeys'
|
||||
import ReactDOM from 'react-dom'
|
||||
import getWindow from 'get-window'
|
||||
import {
|
||||
IS_FIREFOX,
|
||||
IS_IE,
|
||||
IS_IOS,
|
||||
HAS_INPUT_EVENTS_LEVEL_2,
|
||||
} from 'slate-dev-environment'
|
||||
|
||||
import findNode from '../utils/find-node'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
const debug = Debug('slate:before')
|
||||
|
||||
/**
|
||||
* A plugin that adds the "before" browser-specific logic to the editor.
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function BeforePlugin() {
|
||||
let activeElement = null
|
||||
let compositionCount = 0
|
||||
let isComposing = false
|
||||
let isCopying = false
|
||||
let isDragging = false
|
||||
|
||||
/**
|
||||
* On before input.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onBeforeInput(event, change, next) {
|
||||
const { editor } = change
|
||||
const isSynthetic = !!event.nativeEvent
|
||||
if (editor.readOnly) return
|
||||
|
||||
// COMPAT: If the browser supports Input Events Level 2, we will have
|
||||
// attached a custom handler for the real `beforeinput` events, instead of
|
||||
// allowing React's synthetic polyfill, so we need to ignore synthetics.
|
||||
if (isSynthetic && HAS_INPUT_EVENTS_LEVEL_2) return
|
||||
|
||||
debug('onBeforeInput', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On blur.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onBlur(event, change, next) {
|
||||
const { editor } = change
|
||||
if (isCopying) return
|
||||
if (editor.readOnly) return
|
||||
|
||||
const { relatedTarget, target } = event
|
||||
const window = getWindow(target)
|
||||
|
||||
// COMPAT: If the current `activeElement` is still the previous one, this is
|
||||
// due to the window being blurred when the tab itself becomes unfocused, so
|
||||
// we want to abort early to allow to editor to stay focused when the tab
|
||||
// becomes focused again.
|
||||
if (activeElement === window.document.activeElement) return
|
||||
|
||||
// COMPAT: The `relatedTarget` can be null when the new focus target is not
|
||||
// a "focusable" element (eg. a `<div>` without `tabindex` set).
|
||||
if (relatedTarget) {
|
||||
const el = ReactDOM.findDOMNode(editor)
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is returning to the
|
||||
// editor from an embedded editable element (eg. an <input> element inside
|
||||
// a void node).
|
||||
if (relatedTarget === el) return
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is moving from the
|
||||
// editor to inside a void node's spacer element.
|
||||
if (relatedTarget.hasAttribute('data-slate-spacer')) return
|
||||
|
||||
// COMPAT: The event should be ignored if the focus is moving to a non-
|
||||
// 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
|
||||
}
|
||||
|
||||
debug('onBlur', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On composition end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onCompositionEnd(event, change, next) {
|
||||
const { editor } = change
|
||||
const n = compositionCount
|
||||
|
||||
// The `count` check here ensures that if another composition starts
|
||||
// before the timeout has closed out this one, we will abort unsetting the
|
||||
// `isComposing` flag, since a composition is still in affect.
|
||||
window.requestAnimationFrame(() => {
|
||||
if (compositionCount > n) return
|
||||
isComposing = false
|
||||
|
||||
// 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
|
||||
// differently ideally, in a less invasive way?
|
||||
// (apply force re-render if isComposing changes)
|
||||
if (editor.state.isComposing) {
|
||||
editor.setState({ isComposing: false })
|
||||
}
|
||||
})
|
||||
|
||||
debug('onCompositionEnd', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On click.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onClick(event, change, next) {
|
||||
debug('onClick', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On composition start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onCompositionStart(event, change, 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
|
||||
// differently ideally, in a less invasive way?
|
||||
// (apply force re-render if isComposing changes)
|
||||
if (!editor.state.isComposing) {
|
||||
editor.setState({ isComposing: true })
|
||||
}
|
||||
|
||||
debug('onCompositionStart', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On copy.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onCopy(event, change, next) {
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => (isCopying = false))
|
||||
|
||||
debug('onCopy', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On cut.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onCut(event, change, next) {
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return
|
||||
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => (isCopying = false))
|
||||
|
||||
debug('onCut', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragEnd(event, change, next) {
|
||||
isDragging = false
|
||||
debug('onDragEnd', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag enter.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragEnter(event, change, next) {
|
||||
debug('onDragEnter', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag exit.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragExit(event, change, next) {
|
||||
debug('onDragExit', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag leave.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragLeave(event, change, next) {
|
||||
debug('onDragLeave', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag over.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragOver(event, change, 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()
|
||||
|
||||
// COMPAT: IE won't call onDrop on contentEditables unless the
|
||||
// default dragOver is prevented:
|
||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/913982/
|
||||
// (2018/07/11)
|
||||
if (IS_IE) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// If a drag is already in progress, don't do this again.
|
||||
if (!isDragging) {
|
||||
isDragging = true
|
||||
|
||||
// COMPAT: IE will raise an `unspecified error` if dropEffect is
|
||||
// set. (2018/07/11)
|
||||
if (!IS_IE) {
|
||||
event.nativeEvent.dataTransfer.dropEffect = 'move'
|
||||
}
|
||||
}
|
||||
|
||||
debug('onDragOver', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDragStart(event, change, next) {
|
||||
isDragging = true
|
||||
debug('onDragStart', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drop.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onDrop(event, change, next) {
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return
|
||||
|
||||
// Prevent default so the DOM's value isn't corrupted.
|
||||
event.preventDefault()
|
||||
|
||||
debug('onDrop', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On focus.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onFocus(event, change, next) {
|
||||
const { editor } = change
|
||||
if (isCopying) return
|
||||
if (editor.readOnly) return
|
||||
|
||||
const el = ReactDOM.findDOMNode(editor)
|
||||
|
||||
// Save the new `activeElement`.
|
||||
const window = getWindow(event.target)
|
||||
activeElement = window.document.activeElement
|
||||
|
||||
// COMPAT: If the editor has nested editable elements, the focus can go to
|
||||
// those elements. In Firefox, this must be prevented because it results in
|
||||
// issues with keyboard navigation. (2017/03/30)
|
||||
if (IS_FIREFOX && event.target != el) {
|
||||
el.focus()
|
||||
return
|
||||
}
|
||||
|
||||
debug('onFocus', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On input.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onInput(event, change, next) {
|
||||
if (isComposing) return
|
||||
if (change.value.selection.isBlurred) return
|
||||
debug('onInput', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On key down.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onKeyDown(event, change, next) {
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return
|
||||
|
||||
// When composing, we need to prevent all hotkeys from executing while
|
||||
// typing. However, certain characters also move the selection before
|
||||
// we're able to handle it, so prevent their default behavior.
|
||||
if (isComposing) {
|
||||
if (Hotkeys.isCompose(event)) event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
// Certain hotkeys have native editing behaviors in `contenteditable`
|
||||
// elements which will change the DOM and cause our value to be out of sync,
|
||||
// so they need to always be prevented.
|
||||
if (
|
||||
!IS_IOS &&
|
||||
(Hotkeys.isBold(event) ||
|
||||
Hotkeys.isDeleteBackward(event) ||
|
||||
Hotkeys.isDeleteForward(event) ||
|
||||
Hotkeys.isDeleteLineBackward(event) ||
|
||||
Hotkeys.isDeleteLineForward(event) ||
|
||||
Hotkeys.isDeleteWordBackward(event) ||
|
||||
Hotkeys.isDeleteWordForward(event) ||
|
||||
Hotkeys.isItalic(event) ||
|
||||
Hotkeys.isRedo(event) ||
|
||||
Hotkeys.isSplitBlock(event) ||
|
||||
Hotkeys.isTransposeCharacter(event) ||
|
||||
Hotkeys.isUndo(event))
|
||||
) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
debug('onKeyDown', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On paste.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onPaste(event, change, next) {
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return
|
||||
|
||||
// Prevent defaults so the DOM state isn't corrupted.
|
||||
event.preventDefault()
|
||||
|
||||
debug('onPaste', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* On select.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
function onSelect(event, change, next) {
|
||||
if (isCopying) return
|
||||
if (isComposing) return
|
||||
|
||||
const { editor } = change
|
||||
if (editor.readOnly) return
|
||||
|
||||
// Save the new `activeElement`.
|
||||
const window = getWindow(event.target)
|
||||
activeElement = window.document.activeElement
|
||||
|
||||
debug('onSelect', { event })
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the plugin.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
return {
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onClick,
|
||||
onCompositionEnd,
|
||||
onCompositionStart,
|
||||
onCopy,
|
||||
onCut,
|
||||
onDragEnd,
|
||||
onDragEnter,
|
||||
onDragExit,
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDragStart,
|
||||
onDrop,
|
||||
onFocus,
|
||||
onInput,
|
||||
onKeyDown,
|
||||
onPaste,
|
||||
onSelect,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default BeforePlugin
|
24
packages/slate-react/src/plugins/dom.js
Normal file
24
packages/slate-react/src/plugins/dom.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import AfterPlugin from './after'
|
||||
import BeforePlugin from './before'
|
||||
|
||||
/**
|
||||
* A plugin that adds the browser-specific logic to the editor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function DOMPlugin(options = {}) {
|
||||
const { plugins = [] } = options
|
||||
const beforePlugin = BeforePlugin()
|
||||
const afterPlugin = AfterPlugin()
|
||||
return [beforePlugin, ...plugins, afterPlugin]
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default DOMPlugin
|
@@ -1,46 +0,0 @@
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
|
||||
/**
|
||||
* Props that can be defined by plugins.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const PROPS = [
|
||||
...EVENT_HANDLERS,
|
||||
'commands',
|
||||
'decorateNode',
|
||||
'queries',
|
||||
'renderEditor',
|
||||
'renderMark',
|
||||
'renderNode',
|
||||
'renderPlaceholder',
|
||||
'schema',
|
||||
]
|
||||
|
||||
/**
|
||||
* A plugin that is defined from the props on the `<Editor>` component.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function PropsPlugin(props) {
|
||||
const plugin = {}
|
||||
|
||||
for (const prop of PROPS) {
|
||||
if (prop in props) {
|
||||
plugin[prop] = props[prop]
|
||||
}
|
||||
}
|
||||
|
||||
return plugin
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default PropsPlugin
|
74
packages/slate-react/src/plugins/react.js
vendored
74
packages/slate-react/src/plugins/react.js
vendored
@@ -1,26 +1,49 @@
|
||||
import React from 'react'
|
||||
import { Text } from 'slate'
|
||||
|
||||
import DOMPlugin from './dom'
|
||||
import Content from '../components/content'
|
||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||
|
||||
/**
|
||||
* Props that can be defined by plugins.
|
||||
*
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
const PROPS = [
|
||||
...EVENT_HANDLERS,
|
||||
'commands',
|
||||
'decorateNode',
|
||||
'queries',
|
||||
'renderEditor',
|
||||
'renderMark',
|
||||
'renderNode',
|
||||
'renderPlaceholder',
|
||||
'schema',
|
||||
]
|
||||
|
||||
/**
|
||||
* A plugin that adds the React-specific rendering logic to the editor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function ReactPlugin() {
|
||||
function ReactPlugin(options = {}) {
|
||||
const { plugins = [] } = options
|
||||
|
||||
/**
|
||||
* Render editor.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function renderEditor(props, editor, next) {
|
||||
const children = (
|
||||
function renderEditor(props, next) {
|
||||
const { editor } = props
|
||||
return (
|
||||
<Content
|
||||
onEvent={editor.event}
|
||||
autoCorrect={props.autoCorrect}
|
||||
@@ -34,27 +57,22 @@ function ReactPlugin() {
|
||||
tagName={props.tagName}
|
||||
/>
|
||||
)
|
||||
|
||||
const ret = next({ ...props, children }, editor)
|
||||
return ret !== undefined ? ret : children
|
||||
}
|
||||
|
||||
/**
|
||||
* Render node.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function renderNode(props, editor, next) {
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
function renderNode(props, next) {
|
||||
const { attributes, children, node } = props
|
||||
if (node.object != 'block' && node.object != 'inline') return null
|
||||
const Tag = node.object == 'block' ? 'div' : 'span'
|
||||
const { object } = node
|
||||
if (object != 'block' && object != 'inline') return null
|
||||
|
||||
const Tag = object == 'block' ? 'div' : 'span'
|
||||
const style = { position: 'relative' }
|
||||
return (
|
||||
<Tag {...attributes} style={style}>
|
||||
@@ -67,16 +85,12 @@ function ReactPlugin() {
|
||||
* Render placeholder.
|
||||
*
|
||||
* @param {Object} props
|
||||
* @param {Editor} editor
|
||||
* @param {Function} next
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function renderPlaceholder(props, editor, next) {
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
|
||||
const { node } = props
|
||||
function renderPlaceholder(props, next) {
|
||||
const { editor, node } = props
|
||||
if (!editor.props.placeholder) return null
|
||||
if (editor.state.isComposing) return null
|
||||
if (node.object != 'block') return null
|
||||
@@ -101,16 +115,22 @@ function ReactPlugin() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the plugin.
|
||||
* Return the plugins.
|
||||
*
|
||||
* @type {Object}
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
return {
|
||||
renderEditor,
|
||||
renderNode,
|
||||
renderPlaceholder,
|
||||
}
|
||||
const editorPlugin = PROPS.reduce((memo, prop) => {
|
||||
if (prop in options) memo[prop] = options[prop]
|
||||
return memo
|
||||
}, {})
|
||||
|
||||
const domPlugin = DOMPlugin({
|
||||
plugins: [editorPlugin, ...plugins],
|
||||
})
|
||||
|
||||
const defaultsPlugin = { renderEditor, renderNode, renderPlaceholder }
|
||||
return [domPlugin, defaultsPlugin]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -11,10 +11,12 @@ function Image(props) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'image':
|
||||
return Image(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,12 @@ function Image(props) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'image':
|
||||
return Image(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,12 @@ function Code(props) {
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'code':
|
||||
return Code(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,12 @@ function Image(props) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'image':
|
||||
return Image(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,10 +10,12 @@ function Image(props) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'image':
|
||||
return Image(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -12,10 +12,12 @@ function Code(props) {
|
||||
}
|
||||
|
||||
export const props = {
|
||||
renderNode(p) {
|
||||
renderNode(p, editor, next) {
|
||||
switch (p.node.type) {
|
||||
case 'code':
|
||||
return Code(p)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -26,10 +26,12 @@ function Bold(props) {
|
||||
return React.createElement('strong', { ...props.attributes }, props.children)
|
||||
}
|
||||
|
||||
function renderMark(props) {
|
||||
function renderMark(props, next) {
|
||||
switch (props.mark.type) {
|
||||
case 'bold':
|
||||
return Bold(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,12 @@ function Link(props) {
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'link':
|
||||
return Link(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,10 +7,12 @@ function Emoji(props) {
|
||||
return React.createElement('img', props.attributes)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'emoji':
|
||||
return Emoji(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -11,10 +11,12 @@ function Link(props) {
|
||||
)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'link':
|
||||
return Link(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,10 +7,12 @@ function Bold(props) {
|
||||
return React.createElement('strong', { ...props.attributes }, props.children)
|
||||
}
|
||||
|
||||
function renderMark(props) {
|
||||
function renderMark(props, next) {
|
||||
switch (props.mark.type) {
|
||||
case 'bold':
|
||||
return Bold(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -10,10 +10,12 @@ function Image(props) {
|
||||
})
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'image':
|
||||
return Image(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,10 +7,12 @@ function Emoji(props) {
|
||||
return React.createElement('img', props.attributes)
|
||||
}
|
||||
|
||||
function renderNode(props) {
|
||||
function renderNode(props, next) {
|
||||
switch (props.node.type) {
|
||||
case 'emoji':
|
||||
return Emoji(props)
|
||||
default:
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -16,14 +16,6 @@ import Value from '../models/value'
|
||||
|
||||
const debug = Debug('slate:editor')
|
||||
|
||||
/**
|
||||
* The core plugin.
|
||||
*
|
||||
* @type {Array|Object}
|
||||
*/
|
||||
|
||||
const corePlugin = CorePlugin()
|
||||
|
||||
/**
|
||||
* Editor.
|
||||
*
|
||||
@@ -59,8 +51,8 @@ class Editor {
|
||||
isChanging: false,
|
||||
}
|
||||
|
||||
registerPlugin(this, corePlugin)
|
||||
plugins.forEach(p => registerPlugin(this, p))
|
||||
const core = CorePlugin({ plugins })
|
||||
registerPlugin(this, core)
|
||||
|
||||
this.run('onConstruct', this)
|
||||
|
||||
@@ -304,12 +296,12 @@ function registerPlugin(editor, plugin) {
|
||||
const { commands, queries, schema, ...rest } = plugin
|
||||
|
||||
if (commands) {
|
||||
const commandsPlugin = CommandsPlugin({ commands })
|
||||
const commandsPlugin = CommandsPlugin(commands)
|
||||
registerPlugin(editor, commandsPlugin)
|
||||
}
|
||||
|
||||
if (queries) {
|
||||
const queriesPlugin = QueriesPlugin({ queries })
|
||||
const queriesPlugin = QueriesPlugin(queries)
|
||||
registerPlugin(editor, queriesPlugin)
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,11 @@
|
||||
/**
|
||||
* A plugin that adds a set of commands to the editor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} commands
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function CommandsPlugin(options = {}) {
|
||||
const { commands, defer = false } = options
|
||||
|
||||
if (!commands) {
|
||||
throw new Error(
|
||||
'You must pass in the `commands` option to the Slate commands plugin.'
|
||||
)
|
||||
}
|
||||
|
||||
function CommandsPlugin(commands = {}) {
|
||||
/**
|
||||
* On command, if it exists in our list of commands, call it.
|
||||
*
|
||||
@@ -26,14 +18,7 @@ function CommandsPlugin(options = {}) {
|
||||
const { type, args } = command
|
||||
const fn = commands[type]
|
||||
if (!fn) return next()
|
||||
|
||||
if (defer) {
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
}
|
||||
|
||||
change.call(fn, ...args)
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -12,10 +12,13 @@ import Text from '../models/text'
|
||||
/**
|
||||
* A plugin that defines the core Slate logic.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function CorePlugin() {
|
||||
function CorePlugin(options = {}) {
|
||||
const { plugins = [] } = options
|
||||
|
||||
/**
|
||||
* The core Slate commands.
|
||||
*
|
||||
@@ -23,15 +26,12 @@ function CorePlugin() {
|
||||
*/
|
||||
|
||||
const commands = Commands({
|
||||
defer: true,
|
||||
commands: {
|
||||
...AtCurrentRange,
|
||||
...AtRange,
|
||||
...ByPath,
|
||||
...OnHistory,
|
||||
...OnSelection,
|
||||
...OnValue,
|
||||
},
|
||||
...AtCurrentRange,
|
||||
...AtRange,
|
||||
...ByPath,
|
||||
...OnHistory,
|
||||
...OnSelection,
|
||||
...OnValue,
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -41,13 +41,8 @@ function CorePlugin() {
|
||||
*/
|
||||
|
||||
const queries = Queries({
|
||||
defer: true,
|
||||
queries: {
|
||||
isAtomic: () => false,
|
||||
isVoid: () => false,
|
||||
normalizeNode: () => {},
|
||||
validateNode: () => {},
|
||||
},
|
||||
isAtomic: () => false,
|
||||
isVoid: () => false,
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -180,7 +175,7 @@ function CorePlugin() {
|
||||
* @type {Array}
|
||||
*/
|
||||
|
||||
return [commands, queries, schema]
|
||||
return [schema, ...plugins, commands, queries]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -1,19 +1,11 @@
|
||||
/**
|
||||
* A plugin that adds a set of queries to the editor.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @param {Object} queries
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
function QueriesPlugin(options = {}) {
|
||||
const { queries, defer = false } = options
|
||||
|
||||
if (!queries) {
|
||||
throw new Error(
|
||||
'You must pass in the `queries` option to the Slate queries plugin.'
|
||||
)
|
||||
}
|
||||
|
||||
function QueriesPlugin(queries = {}) {
|
||||
/**
|
||||
* On construct, register all the queries.
|
||||
*
|
||||
@@ -41,12 +33,6 @@ function QueriesPlugin(options = {}) {
|
||||
const { type, args } = query
|
||||
const fn = queries[type]
|
||||
if (!fn) return next()
|
||||
|
||||
if (defer) {
|
||||
const ret = next()
|
||||
if (ret !== undefined) return ret
|
||||
}
|
||||
|
||||
const ret = fn(editor, ...args)
|
||||
return ret === undefined ? next() : ret
|
||||
}
|
||||
|
@@ -136,12 +136,7 @@ function SchemaPlugin(schema) {
|
||||
* @param {Function} next
|
||||
*/
|
||||
|
||||
const queries = Queries({
|
||||
queries: {
|
||||
isAtomic,
|
||||
isVoid,
|
||||
},
|
||||
})
|
||||
const queries = Queries({ isAtomic, isVoid })
|
||||
|
||||
/**
|
||||
* Return the plugins.
|
||||
|
Reference in New Issue
Block a user