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
|
```js
|
||||||
function renderEditor(props, next) {
|
function renderEditor(props, next) {
|
||||||
const { children, editor } = props
|
const { editor } = props
|
||||||
const wordCount = countWords(editor.value.text)
|
const wordCount = countWords(editor.value.text)
|
||||||
|
const children = next()
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{props.children}
|
{children}
|
||||||
<span className="word-count">{wordCount}</span>
|
<span className="word-count">{wordCount}</span>
|
||||||
</React.Fragment>
|
</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:
|
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
|
```js
|
||||||
function normalizeNode(node) {
|
function normalizeNode(node, next) {
|
||||||
const { nodes } = node
|
const { nodes } = node
|
||||||
if (node.object !== 'block') return
|
if (node.object !== 'block') return next()
|
||||||
if (nodes.size !== 3) return
|
if (nodes.size !== 3) return next()
|
||||||
if (nodes.first().object !== 'text') return
|
if (nodes.first().object !== 'text') return next()
|
||||||
if (nodes.last().object !== 'text') return
|
if (nodes.last().object !== 'text') return next()
|
||||||
return change => change.removeNodeByKey(node.key)
|
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 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`
|
### `decorateNode`
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ This handler is called whenever the native DOM selection changes.
|
|||||||
|
|
||||||
### `renderEditor`
|
### `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.
|
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}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'check-list-item':
|
case 'check-list-item':
|
||||||
return <CheckListItem {...props} />
|
return <CheckListItem {...props} />
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,15 +154,15 @@ class CheckLists extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Value|Void}
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
|
|
||||||
if (event.key == 'Enter' && value.startBlock.type == 'check-list-item') {
|
if (event.key == 'Enter' && value.startBlock.type == 'check-list-item') {
|
||||||
change.splitBlock().setBlocks({ data: { checked: false } })
|
change.splitBlock().setBlocks({ data: { checked: false } })
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -170,8 +172,10 @@ class CheckLists extends React.Component {
|
|||||||
value.selection.startOffset == 0
|
value.selection.startOffset == 0
|
||||||
) {
|
) {
|
||||||
change.setBlocks('paragraph')
|
change.setBlocks('paragraph')
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -106,12 +106,14 @@ class CodeHighlighting extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'code':
|
case 'code':
|
||||||
return <CodeBlock {...props} />
|
return <CodeBlock {...props} />
|
||||||
case 'code_line':
|
case 'code_line':
|
||||||
return <CodeBlockLine {...props} />
|
return <CodeBlockLine {...props} />
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +124,7 @@ class CodeHighlighting extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -150,6 +152,8 @@ class CodeHighlighting extends React.Component {
|
|||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,17 +172,20 @@ class CodeHighlighting extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection, startBlock } = value
|
const { startBlock } = value
|
||||||
if (event.key != 'Enter') return
|
|
||||||
if (startBlock.type != 'code') return
|
if (event.key === 'Enter' && startBlock.type === 'code') {
|
||||||
if (selection.isExpanded) change.delete()
|
change.insertText('\n')
|
||||||
change.insertText('\n')
|
return
|
||||||
return true
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -58,13 +58,16 @@ class Embeds extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @return {Element}
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'video':
|
case 'video':
|
||||||
return <Video {...props} />
|
return <Video {...props} />
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -123,16 +123,19 @@ class Emojis extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node, isFocused } = props
|
const { attributes, children, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'paragraph': {
|
case 'paragraph': {
|
||||||
return <p {...attributes}>{children}</p>
|
return <p {...attributes}>{children}</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'emoji': {
|
case 'emoji': {
|
||||||
const code = node.data.get('code')
|
const code = node.data.get('code')
|
||||||
return (
|
return (
|
||||||
@@ -146,6 +149,10 @@ class Emojis extends React.Component {
|
|||||||
</Emoji>
|
</Emoji>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -70,10 +70,12 @@ class ForcedLayout extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -81,6 +83,8 @@ class ForcedLayout extends React.Component {
|
|||||||
return <h2 {...attributes}>{children}</h2>
|
return <h2 {...attributes}>{children}</h2>
|
||||||
case 'paragraph':
|
case 'paragraph':
|
||||||
return <p {...attributes}>{children}</p>
|
return <p {...attributes}>{children}</p>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -189,10 +189,12 @@ class HoveringMenu extends React.Component {
|
|||||||
* Render a Slate mark.
|
* Render a Slate mark.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -204,6 +206,8 @@ class HoveringMenu extends React.Component {
|
|||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -73,15 +73,19 @@ class HugeDocument extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'heading':
|
case 'heading':
|
||||||
return <h1 {...attributes}>{children}</h1>
|
return <h1 {...attributes}>{children}</h1>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,10 +93,12 @@ class HugeDocument extends React.Component {
|
|||||||
* Render a Slate mark.
|
* Render a Slate mark.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -104,6 +110,8 @@ class HugeDocument extends React.Component {
|
|||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -138,7 +138,7 @@ class Images extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, node, isFocused } = props
|
const { attributes, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -146,6 +146,10 @@ class Images extends React.Component {
|
|||||||
const src = node.data.get('src')
|
const src = node.data.get('src')
|
||||||
return <Image src={src} selected={isFocused} {...attributes} />
|
return <Image src={src} selected={isFocused} {...attributes} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,21 +181,22 @@ class Images extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDropOrPaste = (event, change) => {
|
onDropOrPaste = (event, change, next) => {
|
||||||
const { editor } = change
|
const { editor } = change
|
||||||
const target = getEventRange(event, editor)
|
const target = getEventRange(event, editor)
|
||||||
if (!target && event.type == 'drop') return
|
if (!target && event.type === 'drop') return next()
|
||||||
|
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, text, files } = transfer
|
const { type, text, files } = transfer
|
||||||
|
|
||||||
if (type == 'files') {
|
if (type === 'files') {
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const [mime] = file.type.split('/')
|
const [mime] = file.type.split('/')
|
||||||
if (mime != 'image') continue
|
if (mime !== 'image') continue
|
||||||
|
|
||||||
reader.addEventListener('load', () => {
|
reader.addEventListener('load', () => {
|
||||||
editor.change(c => {
|
editor.change(c => {
|
||||||
@@ -201,13 +206,17 @@ class Images extends React.Component {
|
|||||||
|
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type == 'text') {
|
if (type === 'text') {
|
||||||
if (!isUrl(text)) return
|
if (!isUrl(text)) return next()
|
||||||
if (!isImage(text)) return
|
if (!isImage(text)) return next()
|
||||||
change.call(insertImage, text, target)
|
change.call(insertImage, text, target)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -223,40 +223,52 @@ class InputTester extends React.Component {
|
|||||||
ref={this.ref}
|
ref={this.ref}
|
||||||
value={this.state.value}
|
value={this.state.value}
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
renderNode={({ attributes, children, node }) => {
|
renderNode={this.renderNode}
|
||||||
switch (node.type) {
|
renderMark={this.renderMark}
|
||||||
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>
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<EventsList />
|
<EventsList />
|
||||||
</Wrapper>
|
</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 => {
|
onRef = ref => {
|
||||||
this.el = ref
|
this.el = ref
|
||||||
}
|
}
|
||||||
|
@@ -100,10 +100,12 @@ class Links extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -116,6 +118,10 @@ class Links extends React.Component {
|
|||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,22 +185,22 @@ class Links extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onPaste = (event, change) => {
|
onPaste = (event, change, next) => {
|
||||||
if (change.value.selection.isCollapsed) return
|
if (change.value.selection.isCollapsed) return next()
|
||||||
|
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, text } = transfer
|
const { type, text } = transfer
|
||||||
if (type != 'text' && type != 'html') return
|
if (type != 'text' && type != 'html') return next()
|
||||||
if (!isUrl(text)) return
|
if (!isUrl(text)) return next()
|
||||||
|
|
||||||
if (this.hasLinks()) {
|
if (this.hasLinks()) {
|
||||||
change.call(unwrapLink)
|
change.call(unwrapLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
change.call(wrapLink, text)
|
change.call(wrapLink, text)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -53,21 +53,27 @@ class MarkdownPreview extends React.Component {
|
|||||||
* Render a Slate mark.
|
* Render a Slate mark.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
case 'bold':
|
case 'bold':
|
||||||
return <strong {...attributes}>{children}</strong>
|
return <strong {...attributes}>{children}</strong>
|
||||||
|
|
||||||
case 'code':
|
case 'code':
|
||||||
return <code {...attributes}>{children}</code>
|
return <code {...attributes}>{children}</code>
|
||||||
|
|
||||||
case 'italic':
|
case 'italic':
|
||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
|
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
|
||||||
case 'title': {
|
case 'title': {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -83,6 +89,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'punctuation': {
|
case 'punctuation': {
|
||||||
return (
|
return (
|
||||||
<span {...attributes} style={{ opacity: 0.2 }}>
|
<span {...attributes} style={{ opacity: 0.2 }}>
|
||||||
@@ -90,6 +97,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'list': {
|
case 'list': {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -104,6 +112,7 @@ class MarkdownPreview extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'hr': {
|
case 'hr': {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
@@ -118,6 +127,10 @@ class MarkdownPreview extends React.Component {
|
|||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -76,10 +76,12 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
* Render a Slate node.
|
* Render a Slate node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
* @param {Editor} editor
|
||||||
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -101,6 +103,8 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
return <h6 {...attributes}>{children}</h6>
|
return <h6 {...attributes}>{children}</h6>
|
||||||
case 'list-item':
|
case 'list-item':
|
||||||
return <li {...attributes}>{children}</li>
|
return <li {...attributes}>{children}</li>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,16 +123,19 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case ' ':
|
case ' ':
|
||||||
return this.onSpace(event, change)
|
return this.onSpace(event, change, next)
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
return this.onBackspace(event, change)
|
return this.onBackspace(event, change, next)
|
||||||
case 'Enter':
|
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 {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onSpace = (event, change) => {
|
onSpace = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection } = value
|
const { selection } = value
|
||||||
if (selection.isExpanded) return
|
if (selection.isExpanded) return next()
|
||||||
|
|
||||||
const { startBlock } = value
|
const { startBlock } = value
|
||||||
const { start } = selection
|
const { start } = selection
|
||||||
const chars = startBlock.text.slice(0, start.offset).replace(/\s*/g, '')
|
const chars = startBlock.text.slice(0, start.offset).replace(/\s*/g, '')
|
||||||
const type = this.getType(chars)
|
const type = this.getType(chars)
|
||||||
|
if (!type) return next()
|
||||||
if (!type) return
|
if (type == 'list-item' && startBlock.type == 'list-item') return next()
|
||||||
if (type == 'list-item' && startBlock.type == 'list-item') return
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
change.setBlocks(type)
|
change.setBlocks(type)
|
||||||
@@ -161,7 +168,6 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
change.moveFocusToStartOfNode(startBlock).delete()
|
change.moveFocusToStartOfNode(startBlock).delete()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -170,16 +176,17 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onBackspace = (event, change) => {
|
onBackspace = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection } = value
|
const { selection } = value
|
||||||
if (selection.isExpanded) return
|
if (selection.isExpanded) return next()
|
||||||
if (selection.start.offset != 0) return
|
if (selection.start.offset != 0) return next()
|
||||||
|
|
||||||
const { startBlock } = value
|
const { startBlock } = value
|
||||||
if (startBlock.type == 'paragraph') return
|
if (startBlock.type == 'paragraph') return next()
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.setBlocks('paragraph')
|
change.setBlocks('paragraph')
|
||||||
@@ -187,8 +194,6 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
if (startBlock.type == 'list-item') {
|
if (startBlock.type == 'list-item') {
|
||||||
change.unwrapBlock('bulleted-list')
|
change.unwrapBlock('bulleted-list')
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -197,18 +202,19 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onEnter = (event, change) => {
|
onEnter = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection } = value
|
const { selection } = value
|
||||||
const { start, end, isExpanded } = selection
|
const { start, end, isExpanded } = selection
|
||||||
if (isExpanded) return
|
if (isExpanded) return next()
|
||||||
|
|
||||||
const { startBlock } = value
|
const { startBlock } = value
|
||||||
if (start.offset == 0 && startBlock.text.length == 0)
|
if (start.offset == 0 && startBlock.text.length == 0)
|
||||||
return this.onBackspace(event, change)
|
return this.onBackspace(event, change, next)
|
||||||
if (end.offset != startBlock.text.length) return
|
if (end.offset != startBlock.text.length) return next()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
startBlock.type != 'heading-one' &&
|
startBlock.type != 'heading-one' &&
|
||||||
@@ -219,12 +225,11 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
startBlock.type != 'heading-six' &&
|
startBlock.type != 'heading-six' &&
|
||||||
startBlock.type != 'block-quote'
|
startBlock.type != 'block-quote'
|
||||||
) {
|
) {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.splitBlock().setBlocks('paragraph')
|
change.splitBlock().setBlocks('paragraph')
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -203,7 +203,7 @@ class PasteHtml extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node, isFocused } = props
|
const { attributes, children, node, isFocused } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -246,6 +246,10 @@ class PasteHtml extends React.Component {
|
|||||||
const src = node.data.get('src')
|
const src = node.data.get('src')
|
||||||
return <Image src={src} selected={isFocused} {...attributes} />
|
return <Image src={src} selected={isFocused} {...attributes} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,7 +260,7 @@ class PasteHtml extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -268,6 +272,8 @@ class PasteHtml extends React.Component {
|
|||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,12 +294,11 @@ class PasteHtml extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onPaste = (event, change) => {
|
onPaste = (event, change, next) => {
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
if (transfer.type != 'html') return
|
if (transfer.type != 'html') return next()
|
||||||
const { document } = serializer.deserialize(transfer.html)
|
const { document } = serializer.deserialize(transfer.html)
|
||||||
change.insertFragment(document)
|
change.insertFragment(document)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -28,10 +28,11 @@ const WordCounter = styled('span')`
|
|||||||
|
|
||||||
function WordCount(options) {
|
function WordCount(options) {
|
||||||
return {
|
return {
|
||||||
renderEditor(props) {
|
renderEditor(props, next) {
|
||||||
|
const children = next()
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>{props.children}</div>
|
<div>{children}</div>
|
||||||
<WordCounter>
|
<WordCounter>
|
||||||
Word Count: {props.value.document.text.split(' ').length}
|
Word Count: {props.value.document.text.split(' ').length}
|
||||||
</WordCounter>
|
</WordCounter>
|
||||||
|
@@ -166,7 +166,7 @@ class RichTextExample extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -182,6 +182,8 @@ class RichTextExample extends React.Component {
|
|||||||
return <li {...attributes}>{children}</li>
|
return <li {...attributes}>{children}</li>
|
||||||
case 'numbered-list':
|
case 'numbered-list':
|
||||||
return <ol {...attributes}>{children}</ol>
|
return <ol {...attributes}>{children}</ol>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,7 +194,7 @@ class RichTextExample extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -204,6 +206,8 @@ class RichTextExample extends React.Component {
|
|||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +229,7 @@ class RichTextExample extends React.Component {
|
|||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
let mark
|
let mark
|
||||||
|
|
||||||
if (isBoldHotkey(event)) {
|
if (isBoldHotkey(event)) {
|
||||||
@@ -237,12 +241,11 @@ class RichTextExample extends React.Component {
|
|||||||
} else if (isCodeHotkey(event)) {
|
} else if (isCodeHotkey(event)) {
|
||||||
mark = 'code'
|
mark = 'code'
|
||||||
} else {
|
} else {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.toggleMark(mark)
|
change.toggleMark(mark)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -46,12 +46,14 @@ class RTL extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
case 'block-quote':
|
case 'block-quote':
|
||||||
return <blockquote {...attributes}>{children}</blockquote>
|
return <blockquote {...attributes}>{children}</blockquote>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +74,14 @@ class RTL extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
if (event.key == 'Enter' && event.shiftKey) {
|
if (event.key == 'Enter' && event.shiftKey) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.insertText('\n')
|
change.insertText('\n')
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -108,7 +108,7 @@ class SearchHighlighting extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -118,6 +118,8 @@ class SearchHighlighting extends React.Component {
|
|||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -136,7 +136,7 @@ class SyncingEditor extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
@@ -148,6 +148,8 @@ class SyncingEditor extends React.Component {
|
|||||||
return <em {...attributes}>{children}</em>
|
return <em {...attributes}>{children}</em>
|
||||||
case 'underlined':
|
case 'underlined':
|
||||||
return <u {...attributes}>{children}</u>
|
return <u {...attributes}>{children}</u>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,7 +177,7 @@ class SyncingEditor extends React.Component {
|
|||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
let mark
|
let mark
|
||||||
|
|
||||||
if (isBoldHotkey(event)) {
|
if (isBoldHotkey(event)) {
|
||||||
@@ -187,12 +189,11 @@ class SyncingEditor extends React.Component {
|
|||||||
} else if (isCodeHotkey(event)) {
|
} else if (isCodeHotkey(event)) {
|
||||||
mark = 'code'
|
mark = 'code'
|
||||||
} else {
|
} else {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.toggleMark(mark)
|
change.toggleMark(mark)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -50,7 +50,7 @@ class Tables extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderNode = props => {
|
renderNode = (props, next) => {
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
|
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
@@ -64,6 +64,8 @@ class Tables extends React.Component {
|
|||||||
return <tr {...attributes}>{children}</tr>
|
return <tr {...attributes}>{children}</tr>
|
||||||
case 'table-cell':
|
case 'table-cell':
|
||||||
return <td {...attributes}>{children}</td>
|
return <td {...attributes}>{children}</td>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +76,14 @@ class Tables extends React.Component {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
renderMark = props => {
|
renderMark = (props, next) => {
|
||||||
const { children, mark, attributes } = props
|
const { children, mark, attributes } = props
|
||||||
|
|
||||||
switch (mark.type) {
|
switch (mark.type) {
|
||||||
case 'bold':
|
case 'bold':
|
||||||
return <strong {...attributes}>{children}</strong>
|
return <strong {...attributes}>{children}</strong>
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,12 +94,11 @@ class Tables extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onBackspace = (event, change) => {
|
onBackspace = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection } = value
|
const { selection } = value
|
||||||
if (selection.start.offset != 0) return
|
if (selection.start.offset != 0) return next()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,12 +118,11 @@ class Tables extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDelete = (event, change) => {
|
onDelete = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { selection } = value
|
const { selection } = value
|
||||||
if (selection.end.offset != value.startText.text.length) return
|
if (selection.end.offset != value.startText.text.length) return next()
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -130,23 +132,22 @@ class Tables extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDropOrPaste = (event, change) => {
|
onDropOrPaste = (event, change, next) => {
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { text = '' } = transfer
|
const { text = '' } = transfer
|
||||||
|
|
||||||
if (value.startBlock.type !== 'table-cell') {
|
if (value.startBlock.type !== 'table-cell') {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
const lines = text.split('\n')
|
const lines = text.split('\n')
|
||||||
const { document } = Plain.deserialize(lines[0] || '')
|
const { document } = Plain.deserialize(lines[0] || '')
|
||||||
change.insertFragment(document)
|
change.insertFragment(document)
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,9 +157,8 @@ class Tables extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onEnter = (event, change) => {
|
onEnter = (event, change, next) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,7 +168,7 @@ class Tables extends React.Component {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (event, change) => {
|
onKeyDown = (event, change, next) => {
|
||||||
const { value } = change
|
const { value } = change
|
||||||
const { document, selection } = value
|
const { document, selection } = value
|
||||||
const { start, isCollapsed } = selection
|
const { start, isCollapsed } = selection
|
||||||
@@ -181,24 +181,25 @@ class Tables extends React.Component {
|
|||||||
if (prevBlock.type === 'table-cell') {
|
if (prevBlock.type === 'table-cell') {
|
||||||
if (['Backspace', 'Delete', 'Enter'].includes(event.key)) {
|
if (['Backspace', 'Delete', 'Enter'].includes(event.key)) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
|
||||||
} else {
|
} else {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.startBlock.type !== 'table-cell') {
|
if (value.startBlock.type !== 'table-cell') {
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Backspace':
|
case 'Backspace':
|
||||||
return this.onBackspace(event, change)
|
return this.onBackspace(event, change, next)
|
||||||
case 'Delete':
|
case 'Delete':
|
||||||
return this.onDelete(event, change)
|
return this.onDelete(event, change, next)
|
||||||
case 'Enter':
|
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 { Editor as Controller } from 'slate'
|
||||||
|
|
||||||
import EVENT_HANDLERS from '../constants/event-handlers'
|
import EVENT_HANDLERS from '../constants/event-handlers'
|
||||||
import BrowserPlugin from '../plugins/browser'
|
|
||||||
import PropsPlugin from '../plugins/props'
|
|
||||||
import ReactPlugin from '../plugins/react'
|
import ReactPlugin from '../plugins/react'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,7 +141,7 @@ class Editor extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
debug('render', this)
|
debug('render', this)
|
||||||
const props = { ...this.props }
|
const props = { ...this.props, editor: this }
|
||||||
|
|
||||||
// Re-resolve the controller if needed based on memoized props.
|
// Re-resolve the controller if needed based on memoized props.
|
||||||
const { commands, plugins, queries, schema } = props
|
const { commands, plugins, queries, schema } = props
|
||||||
@@ -155,7 +153,7 @@ class Editor extends React.Component {
|
|||||||
this.controller.setValue(value, options)
|
this.controller.setValue(value, options)
|
||||||
|
|
||||||
// Render the editor's children with the controller.
|
// 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
|
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.'
|
'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 react = ReactPlugin(this.props)
|
||||||
const reactPlugin = ReactPlugin()
|
const attrs = { onChange: this.onControllerChange, plugins: [react] }
|
||||||
const browserPlugin = BrowserPlugin()
|
|
||||||
const propsPlugin = PropsPlugin(props)
|
|
||||||
const allPlugins = [reactPlugin, browserPlugin, propsPlugin, ...plugins]
|
|
||||||
const attrs = { onChange: onControllerChange, plugins: allPlugins }
|
|
||||||
this.controller = new Controller(attrs, { editor: this, normalize: false })
|
this.controller = new Controller(attrs, { editor: this, normalize: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -166,7 +166,7 @@ class Node extends React.Component {
|
|||||||
readOnly,
|
readOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
let placeholder = editor.run('renderPlaceholder', props, editor)
|
let placeholder = editor.run('renderPlaceholder', props)
|
||||||
|
|
||||||
if (placeholder) {
|
if (placeholder) {
|
||||||
placeholder = React.cloneElement(placeholder, {
|
placeholder = React.cloneElement(placeholder, {
|
||||||
@@ -176,15 +176,11 @@ class Node extends React.Component {
|
|||||||
children = [placeholder, ...children]
|
children = [placeholder, ...children]
|
||||||
}
|
}
|
||||||
|
|
||||||
const element = editor.run(
|
const element = editor.run('renderNode', {
|
||||||
'renderNode',
|
...props,
|
||||||
{
|
attributes,
|
||||||
...props,
|
children,
|
||||||
attributes,
|
})
|
||||||
children,
|
|
||||||
},
|
|
||||||
editor
|
|
||||||
)
|
|
||||||
|
|
||||||
return editor.query('isVoid', node) ? (
|
return editor.query('isVoid', node) ? (
|
||||||
<Void {...this.props}>{element}</Void>
|
<Void {...this.props}>{element}</Void>
|
||||||
|
@@ -2,14 +2,8 @@ import Base64 from 'slate-base64-serializer'
|
|||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
import Hotkeys from 'slate-hotkeys'
|
import Hotkeys from 'slate-hotkeys'
|
||||||
import Plain from 'slate-plain-serializer'
|
import Plain from 'slate-plain-serializer'
|
||||||
import ReactDOM from 'react-dom'
|
|
||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
import {
|
import { IS_IOS } from 'slate-dev-environment'
|
||||||
IS_FIREFOX,
|
|
||||||
IS_IE,
|
|
||||||
IS_IOS,
|
|
||||||
HAS_INPUT_EVENTS_LEVEL_2,
|
|
||||||
} from 'slate-dev-environment'
|
|
||||||
|
|
||||||
import cloneFragment from '../utils/clone-fragment'
|
import cloneFragment from '../utils/clone-fragment'
|
||||||
import findDOMNode from '../utils/find-dom-node'
|
import findDOMNode from '../utils/find-dom-node'
|
||||||
@@ -26,20 +20,16 @@ import setEventTransfer from '../utils/set-event-transfer'
|
|||||||
* @type {Function}
|
* @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}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function BrowserPlugin() {
|
function AfterPlugin(options = {}) {
|
||||||
let activeElement = null
|
|
||||||
let compositionCount = 0
|
|
||||||
let isComposing = false
|
|
||||||
let isCopying = false
|
|
||||||
let isDragging = false
|
|
||||||
let isDraggingInternally = null
|
let isDraggingInternally = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,24 +38,11 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBeforeInput(event, change, next) {
|
function onBeforeInput(event, change, next) {
|
||||||
const { editor, value } = change
|
const { editor, value } = change
|
||||||
const isSynthetic = !!event.nativeEvent
|
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
|
// If the event is synthetic, it's React's polyfill of `beforeinput` that
|
||||||
// isn't a true `beforeinput` event with meaningful information. It only
|
// isn't a true `beforeinput` event with meaningful information. It only
|
||||||
@@ -73,13 +50,15 @@ function BrowserPlugin() {
|
|||||||
if (isSynthetic) {
|
if (isSynthetic) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.insertText(event.data)
|
change.insertText(event.data)
|
||||||
return
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we can use the information in the `beforeinput` event to
|
// Otherwise, we can use the information in the `beforeinput` event to
|
||||||
// figure out the exact change that will occur, and prevent it.
|
// figure out the exact change that will occur, and prevent it.
|
||||||
const [targetRange] = event.getTargetRanges()
|
const [targetRange] = event.getTargetRanges()
|
||||||
if (!targetRange) return
|
if (!targetRange) return next()
|
||||||
|
|
||||||
|
debug('onBeforeInput', { event })
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
@@ -93,29 +72,29 @@ function BrowserPlugin() {
|
|||||||
case 'deleteContentBackward':
|
case 'deleteContentBackward':
|
||||||
case 'deleteContentForward': {
|
case 'deleteContentForward': {
|
||||||
change.deleteAtRange(range)
|
change.deleteAtRange(range)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deleteWordBackward': {
|
case 'deleteWordBackward': {
|
||||||
change.deleteWordBackwardAtRange(range)
|
change.deleteWordBackwardAtRange(range)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deleteWordForward': {
|
case 'deleteWordForward': {
|
||||||
change.deleteWordForwardAtRange(range)
|
change.deleteWordForwardAtRange(range)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deleteSoftLineBackward':
|
case 'deleteSoftLineBackward':
|
||||||
case 'deleteHardLineBackward': {
|
case 'deleteHardLineBackward': {
|
||||||
change.deleteLineBackwardAtRange(range)
|
change.deleteLineBackwardAtRange(range)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'deleteSoftLineForward':
|
case 'deleteSoftLineForward':
|
||||||
case 'deleteHardLineForward': {
|
case 'deleteHardLineForward': {
|
||||||
change.deleteLineForwardAtRange(range)
|
change.deleteLineForwardAtRange(range)
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'insertLineBreak':
|
case 'insertLineBreak':
|
||||||
@@ -131,7 +110,7 @@ function BrowserPlugin() {
|
|||||||
change.splitBlockAtRange(range)
|
change.splitBlockAtRange(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'insertFromYank':
|
case 'insertFromYank':
|
||||||
@@ -146,7 +125,7 @@ function BrowserPlugin() {
|
|||||||
? event.dataTransfer.getData('text/plain')
|
? event.dataTransfer.getData('text/plain')
|
||||||
: event.data
|
: event.data
|
||||||
|
|
||||||
if (text == null) return
|
if (text == null) break
|
||||||
|
|
||||||
change.insertTextAtRange(range, text, selection.marks)
|
change.insertTextAtRange(range, text, selection.marks)
|
||||||
|
|
||||||
@@ -156,9 +135,11 @@ function BrowserPlugin() {
|
|||||||
change.select({ marks: null })
|
change.select({ marks: null })
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -167,88 +148,12 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBlur(event, change, next) {
|
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 })
|
debug('onBlur', { event })
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
change.blur()
|
change.blur()
|
||||||
return true
|
next()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -257,23 +162,18 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onClick(event, change, next) {
|
function onClick(event, change, next) {
|
||||||
debug('onClick', { event })
|
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { editor } = change
|
const { editor } = change
|
||||||
if (editor.readOnly) return true
|
if (editor.readOnly) return next()
|
||||||
|
|
||||||
const { value } = editor
|
const { value } = editor
|
||||||
const { document } = value
|
const { document } = value
|
||||||
const node = findNode(event.target, editor)
|
const node = findNode(event.target, editor)
|
||||||
if (!node) return true
|
if (!node) return next()
|
||||||
|
|
||||||
|
debug('onClick', { event })
|
||||||
|
|
||||||
const ancestors = document.getAncestors(node.key)
|
const ancestors = document.getAncestors(node.key)
|
||||||
const isVoid =
|
const isVoid =
|
||||||
@@ -287,35 +187,7 @@ function BrowserPlugin() {
|
|||||||
change.focus().moveToEndOfNode(node)
|
change.focus().moveToEndOfNode(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
next()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,23 +196,13 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCopy(event, change, next) {
|
function onCopy(event, change, next) {
|
||||||
const window = getWindow(event.target)
|
|
||||||
isCopying = true
|
|
||||||
window.requestAnimationFrame(() => (isCopying = false))
|
|
||||||
|
|
||||||
debug('onCopy', { event })
|
debug('onCopy', { event })
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { editor } = change
|
const { editor } = change
|
||||||
cloneFragment(event, editor)
|
cloneFragment(event, editor)
|
||||||
return true
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -349,22 +211,11 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCut(event, change, next) {
|
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 })
|
debug('onCut', { event })
|
||||||
|
const { editor } = change
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
// Once the fake cut content has successfully been added to the clipboard,
|
// Once the fake cut content has successfully been added to the clipboard,
|
||||||
// delete the content in the current selection.
|
// delete the content in the current selection.
|
||||||
@@ -385,6 +236,8 @@ function BrowserPlugin() {
|
|||||||
editor.change(c => c.delete())
|
editor.change(c => c.delete())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -393,96 +246,12 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragEnd(event, change, next) {
|
function onDragEnd(event, change, next) {
|
||||||
debug('onDragEnd', { event })
|
debug('onDragEnd', { event })
|
||||||
isDragging = false
|
|
||||||
isDraggingInternally = null
|
isDraggingInternally = null
|
||||||
return next()
|
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -491,19 +260,13 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragStart(event, change, next) {
|
function onDragStart(event, change, next) {
|
||||||
debug('onDragStart', { event })
|
debug('onDragStart', { event })
|
||||||
|
|
||||||
isDragging = true
|
|
||||||
isDraggingInternally = true
|
isDraggingInternally = true
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { editor } = change
|
const { editor } = change
|
||||||
const { value } = editor
|
const { value } = editor
|
||||||
const { document } = value
|
const { document } = value
|
||||||
@@ -523,6 +286,7 @@ function BrowserPlugin() {
|
|||||||
const fragment = change.value.fragment
|
const fragment = change.value.fragment
|
||||||
const encoded = Base64.serializeNode(fragment)
|
const encoded = Base64.serializeNode(fragment)
|
||||||
setEventTransfer(event, 'fragment', encoded)
|
setEventTransfer(event, 'fragment', encoded)
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -531,26 +295,16 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDrop(event, change, next) {
|
function onDrop(event, change, next) {
|
||||||
const { editor, value } = change
|
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 { document, selection } = value
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
let target = getEventRange(event, editor)
|
let target = getEventRange(event, editor)
|
||||||
if (!target) return true
|
if (!target) return next()
|
||||||
|
|
||||||
|
debug('onDrop', { event })
|
||||||
|
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, fragment, text } = transfer
|
const { type, fragment, text } = transfer
|
||||||
@@ -611,49 +365,18 @@ function BrowserPlugin() {
|
|||||||
// DOM node, since that will make it go back to normal.
|
// DOM node, since that will make it go back to normal.
|
||||||
const focusNode = document.getNode(target.focus.key)
|
const focusNode = document.getNode(target.focus.key)
|
||||||
const el = findDOMNode(focusNode, window)
|
const el = findDOMNode(focusNode, window)
|
||||||
if (!el) return true
|
|
||||||
|
|
||||||
el.dispatchEvent(
|
if (el) {
|
||||||
new MouseEvent('mouseup', {
|
el.dispatchEvent(
|
||||||
view: window,
|
new MouseEvent('mouseup', {
|
||||||
bubbles: true,
|
view: window,
|
||||||
cancelable: true,
|
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('onFocus', { event })
|
next()
|
||||||
return next()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -662,19 +385,9 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onInput(event, change, next) {
|
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 window = getWindow(event.target)
|
||||||
const { editor, value } = change
|
const { editor, value } = change
|
||||||
|
|
||||||
@@ -682,7 +395,7 @@ function BrowserPlugin() {
|
|||||||
const native = window.getSelection()
|
const native = window.getSelection()
|
||||||
const { anchorNode } = native
|
const { anchorNode } = native
|
||||||
const point = findPoint(anchorNode, 0, editor)
|
const point = findPoint(anchorNode, 0, editor)
|
||||||
if (!point) return
|
if (!point) return next()
|
||||||
|
|
||||||
// Get the text node and leaf in question.
|
// Get the text node and leaf in question.
|
||||||
const { document, selection } = value
|
const { document, selection } = value
|
||||||
@@ -716,7 +429,9 @@ function BrowserPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the text is no different, abort.
|
// 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.
|
// Determine what the selection should be after changing the text.
|
||||||
const delta = textContent.length - text.length
|
const delta = textContent.length - text.length
|
||||||
@@ -729,6 +444,7 @@ function BrowserPlugin() {
|
|||||||
|
|
||||||
// Change the current value to have the leaf's text replaced.
|
// Change the current value to have the leaf's text replaced.
|
||||||
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
change.insertTextAtRange(entire, textContent, leaf.marks).select(corrected)
|
||||||
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -737,48 +453,12 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDown(event, change, next) {
|
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 })
|
debug('onKeyDown', { event })
|
||||||
|
|
||||||
// Certain hotkeys have native editing behaviors in `contenteditable`
|
const { editor, value } = change
|
||||||
// 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 { document, selection } = value
|
const { document, selection } = value
|
||||||
const hasVoidParent = document.hasVoidParent(selection.start.path, editor)
|
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 {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onPaste(event, change, next) {
|
function onPaste(event, change, next) {
|
||||||
const { editor, value } = change
|
|
||||||
if (editor.readOnly) return true
|
|
||||||
|
|
||||||
debug('onPaste', { event })
|
debug('onPaste', { event })
|
||||||
|
|
||||||
// Prevent defaults so the DOM state isn't corrupted.
|
const { value } = change
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, fragment, text } = transfer
|
const { type, fragment, text } = transfer
|
||||||
|
|
||||||
@@ -926,9 +596,9 @@ function BrowserPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type == 'text' || type == 'html') {
|
if (type == 'text' || type == 'html') {
|
||||||
if (!text) return true
|
if (!text) return next()
|
||||||
const { document, selection, startBlock } = value
|
const { document, selection, startBlock } = value
|
||||||
if (change.isVoid(startBlock)) return true
|
if (change.isVoid(startBlock)) return next()
|
||||||
|
|
||||||
const defaultBlock = startBlock
|
const defaultBlock = startBlock
|
||||||
const defaultMarks = document.getInsertMarksAtRange(selection)
|
const defaultMarks = document.getInsertMarksAtRange(selection)
|
||||||
@@ -937,7 +607,7 @@ function BrowserPlugin() {
|
|||||||
change.insertFragment(frag)
|
change.insertFragment(frag)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -946,38 +616,25 @@ function BrowserPlugin() {
|
|||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onSelect(event, change, next) {
|
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 })
|
debug('onSelect', { event })
|
||||||
|
|
||||||
// Save the new `activeElement`.
|
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
activeElement = window.document.activeElement
|
const { editor, value } = change
|
||||||
|
|
||||||
// Delegate to the plugins stack.
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { document } = value
|
const { document } = value
|
||||||
const native = window.getSelection()
|
const native = window.getSelection()
|
||||||
|
|
||||||
// If there are no ranges, the editor was blurred natively.
|
// If there are no ranges, the editor was blurred natively.
|
||||||
if (!native.rangeCount) {
|
if (!native.rangeCount) {
|
||||||
change.blur()
|
change.blur()
|
||||||
return true
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, determine the Slate selection from the native one.
|
// Otherwise, determine the Slate selection from the native one.
|
||||||
let range = findRange(native, editor)
|
let range = findRange(native, editor)
|
||||||
if (!range) return true
|
if (!range) return
|
||||||
|
|
||||||
const { anchor, focus } = range
|
const { anchor, focus } = range
|
||||||
const anchorText = document.getNode(anchor.key)
|
const anchorText = document.getNode(anchor.key)
|
||||||
@@ -1036,7 +693,7 @@ function BrowserPlugin() {
|
|||||||
selection = selection.set('marks', value.selection.marks)
|
selection = selection.set('marks', value.selection.marks)
|
||||||
|
|
||||||
change.select(selection)
|
change.select(selection)
|
||||||
return true
|
next()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1049,18 +706,11 @@ function BrowserPlugin() {
|
|||||||
onBeforeInput,
|
onBeforeInput,
|
||||||
onBlur,
|
onBlur,
|
||||||
onClick,
|
onClick,
|
||||||
onCompositionEnd,
|
|
||||||
onCompositionStart,
|
|
||||||
onCopy,
|
onCopy,
|
||||||
onCut,
|
onCut,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
onDragEnter,
|
|
||||||
onDragExit,
|
|
||||||
onDragLeave,
|
|
||||||
onDragOver,
|
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDrop,
|
onDrop,
|
||||||
onFocus,
|
|
||||||
onInput,
|
onInput,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onPaste,
|
onPaste,
|
||||||
@@ -1074,4 +724,4 @@ function BrowserPlugin() {
|
|||||||
* @type {Object}
|
* @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 React from 'react'
|
||||||
import { Text } from 'slate'
|
import { Text } from 'slate'
|
||||||
|
|
||||||
|
import DOMPlugin from './dom'
|
||||||
import Content from '../components/content'
|
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.
|
* A plugin that adds the React-specific rendering logic to the editor.
|
||||||
*
|
*
|
||||||
|
* @param {Object} options
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function ReactPlugin() {
|
function ReactPlugin(options = {}) {
|
||||||
|
const { plugins = [] } = options
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render editor.
|
* Render editor.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {Editor} editor
|
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function renderEditor(props, editor, next) {
|
function renderEditor(props, next) {
|
||||||
const children = (
|
const { editor } = props
|
||||||
|
return (
|
||||||
<Content
|
<Content
|
||||||
onEvent={editor.event}
|
onEvent={editor.event}
|
||||||
autoCorrect={props.autoCorrect}
|
autoCorrect={props.autoCorrect}
|
||||||
@@ -34,27 +57,22 @@ function ReactPlugin() {
|
|||||||
tagName={props.tagName}
|
tagName={props.tagName}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
const ret = next({ ...props, children }, editor)
|
|
||||||
return ret !== undefined ? ret : children
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render node.
|
* Render node.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {Editor} editor
|
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function renderNode(props, editor, next) {
|
function renderNode(props, next) {
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { attributes, children, node } = props
|
const { attributes, children, node } = props
|
||||||
if (node.object != 'block' && node.object != 'inline') return null
|
const { object } = node
|
||||||
const Tag = node.object == 'block' ? 'div' : 'span'
|
if (object != 'block' && object != 'inline') return null
|
||||||
|
|
||||||
|
const Tag = object == 'block' ? 'div' : 'span'
|
||||||
const style = { position: 'relative' }
|
const style = { position: 'relative' }
|
||||||
return (
|
return (
|
||||||
<Tag {...attributes} style={style}>
|
<Tag {...attributes} style={style}>
|
||||||
@@ -67,16 +85,12 @@ function ReactPlugin() {
|
|||||||
* Render placeholder.
|
* Render placeholder.
|
||||||
*
|
*
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {Editor} editor
|
|
||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function renderPlaceholder(props, editor, next) {
|
function renderPlaceholder(props, next) {
|
||||||
const ret = next()
|
const { editor, node } = props
|
||||||
if (ret !== undefined) return ret
|
|
||||||
|
|
||||||
const { node } = props
|
|
||||||
if (!editor.props.placeholder) return null
|
if (!editor.props.placeholder) return null
|
||||||
if (editor.state.isComposing) return null
|
if (editor.state.isComposing) return null
|
||||||
if (node.object != 'block') 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 {
|
const editorPlugin = PROPS.reduce((memo, prop) => {
|
||||||
renderEditor,
|
if (prop in options) memo[prop] = options[prop]
|
||||||
renderNode,
|
return memo
|
||||||
renderPlaceholder,
|
}, {})
|
||||||
}
|
|
||||||
|
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) {
|
switch (props.node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return Image(props)
|
return Image(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,12 @@ function Image(props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return Image(props)
|
return Image(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,12 @@ function Code(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'code':
|
case 'code':
|
||||||
return Code(props)
|
return Code(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,12 @@ function Image(props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return Image(props)
|
return Image(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,10 +10,12 @@ function Image(props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return Image(props)
|
return Image(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,10 +12,12 @@ function Code(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const props = {
|
export const props = {
|
||||||
renderNode(p) {
|
renderNode(p, editor, next) {
|
||||||
switch (p.node.type) {
|
switch (p.node.type) {
|
||||||
case 'code':
|
case 'code':
|
||||||
return Code(p)
|
return Code(p)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -26,10 +26,12 @@ function Bold(props) {
|
|||||||
return React.createElement('strong', { ...props.attributes }, props.children)
|
return React.createElement('strong', { ...props.attributes }, props.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMark(props) {
|
function renderMark(props, next) {
|
||||||
switch (props.mark.type) {
|
switch (props.mark.type) {
|
||||||
case 'bold':
|
case 'bold':
|
||||||
return Bold(props)
|
return Bold(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,12 @@ function Link(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'link':
|
case 'link':
|
||||||
return Link(props)
|
return Link(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,10 +7,12 @@ function Emoji(props) {
|
|||||||
return React.createElement('img', props.attributes)
|
return React.createElement('img', props.attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'emoji':
|
case 'emoji':
|
||||||
return Emoji(props)
|
return Emoji(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,10 +11,12 @@ function Link(props) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'link':
|
case 'link':
|
||||||
return Link(props)
|
return Link(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,10 +7,12 @@ function Bold(props) {
|
|||||||
return React.createElement('strong', { ...props.attributes }, props.children)
|
return React.createElement('strong', { ...props.attributes }, props.children)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMark(props) {
|
function renderMark(props, next) {
|
||||||
switch (props.mark.type) {
|
switch (props.mark.type) {
|
||||||
case 'bold':
|
case 'bold':
|
||||||
return Bold(props)
|
return Bold(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -10,10 +10,12 @@ function Image(props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'image':
|
case 'image':
|
||||||
return Image(props)
|
return Image(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,10 +7,12 @@ function Emoji(props) {
|
|||||||
return React.createElement('img', props.attributes)
|
return React.createElement('img', props.attributes)
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNode(props) {
|
function renderNode(props, next) {
|
||||||
switch (props.node.type) {
|
switch (props.node.type) {
|
||||||
case 'emoji':
|
case 'emoji':
|
||||||
return Emoji(props)
|
return Emoji(props)
|
||||||
|
default:
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,14 +16,6 @@ import Value from '../models/value'
|
|||||||
|
|
||||||
const debug = Debug('slate:editor')
|
const debug = Debug('slate:editor')
|
||||||
|
|
||||||
/**
|
|
||||||
* The core plugin.
|
|
||||||
*
|
|
||||||
* @type {Array|Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const corePlugin = CorePlugin()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Editor.
|
* Editor.
|
||||||
*
|
*
|
||||||
@@ -59,8 +51,8 @@ class Editor {
|
|||||||
isChanging: false,
|
isChanging: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPlugin(this, corePlugin)
|
const core = CorePlugin({ plugins })
|
||||||
plugins.forEach(p => registerPlugin(this, p))
|
registerPlugin(this, core)
|
||||||
|
|
||||||
this.run('onConstruct', this)
|
this.run('onConstruct', this)
|
||||||
|
|
||||||
@@ -304,12 +296,12 @@ function registerPlugin(editor, plugin) {
|
|||||||
const { commands, queries, schema, ...rest } = plugin
|
const { commands, queries, schema, ...rest } = plugin
|
||||||
|
|
||||||
if (commands) {
|
if (commands) {
|
||||||
const commandsPlugin = CommandsPlugin({ commands })
|
const commandsPlugin = CommandsPlugin(commands)
|
||||||
registerPlugin(editor, commandsPlugin)
|
registerPlugin(editor, commandsPlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (queries) {
|
if (queries) {
|
||||||
const queriesPlugin = QueriesPlugin({ queries })
|
const queriesPlugin = QueriesPlugin(queries)
|
||||||
registerPlugin(editor, queriesPlugin)
|
registerPlugin(editor, queriesPlugin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,19 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* A plugin that adds a set of commands to the editor.
|
* A plugin that adds a set of commands to the editor.
|
||||||
*
|
*
|
||||||
* @param {Object} options
|
* @param {Object} commands
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function CommandsPlugin(options = {}) {
|
function CommandsPlugin(commands = {}) {
|
||||||
const { commands, defer = false } = options
|
|
||||||
|
|
||||||
if (!commands) {
|
|
||||||
throw new Error(
|
|
||||||
'You must pass in the `commands` option to the Slate commands plugin.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On command, if it exists in our list of commands, call it.
|
* On command, if it exists in our list of commands, call it.
|
||||||
*
|
*
|
||||||
@@ -26,14 +18,7 @@ function CommandsPlugin(options = {}) {
|
|||||||
const { type, args } = command
|
const { type, args } = command
|
||||||
const fn = commands[type]
|
const fn = commands[type]
|
||||||
if (!fn) return next()
|
if (!fn) return next()
|
||||||
|
|
||||||
if (defer) {
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
change.call(fn, ...args)
|
change.call(fn, ...args)
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -12,10 +12,13 @@ import Text from '../models/text'
|
|||||||
/**
|
/**
|
||||||
* A plugin that defines the core Slate logic.
|
* A plugin that defines the core Slate logic.
|
||||||
*
|
*
|
||||||
|
* @param {Object} options
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function CorePlugin() {
|
function CorePlugin(options = {}) {
|
||||||
|
const { plugins = [] } = options
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The core Slate commands.
|
* The core Slate commands.
|
||||||
*
|
*
|
||||||
@@ -23,15 +26,12 @@ function CorePlugin() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const commands = Commands({
|
const commands = Commands({
|
||||||
defer: true,
|
...AtCurrentRange,
|
||||||
commands: {
|
...AtRange,
|
||||||
...AtCurrentRange,
|
...ByPath,
|
||||||
...AtRange,
|
...OnHistory,
|
||||||
...ByPath,
|
...OnSelection,
|
||||||
...OnHistory,
|
...OnValue,
|
||||||
...OnSelection,
|
|
||||||
...OnValue,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,13 +41,8 @@ function CorePlugin() {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const queries = Queries({
|
const queries = Queries({
|
||||||
defer: true,
|
isAtomic: () => false,
|
||||||
queries: {
|
isVoid: () => false,
|
||||||
isAtomic: () => false,
|
|
||||||
isVoid: () => false,
|
|
||||||
normalizeNode: () => {},
|
|
||||||
validateNode: () => {},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -180,7 +175,7 @@ function CorePlugin() {
|
|||||||
* @type {Array}
|
* @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.
|
* A plugin that adds a set of queries to the editor.
|
||||||
*
|
*
|
||||||
* @param {Object} options
|
* @param {Object} queries
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function QueriesPlugin(options = {}) {
|
function QueriesPlugin(queries = {}) {
|
||||||
const { queries, defer = false } = options
|
|
||||||
|
|
||||||
if (!queries) {
|
|
||||||
throw new Error(
|
|
||||||
'You must pass in the `queries` option to the Slate queries plugin.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On construct, register all the queries.
|
* On construct, register all the queries.
|
||||||
*
|
*
|
||||||
@@ -41,12 +33,6 @@ function QueriesPlugin(options = {}) {
|
|||||||
const { type, args } = query
|
const { type, args } = query
|
||||||
const fn = queries[type]
|
const fn = queries[type]
|
||||||
if (!fn) return next()
|
if (!fn) return next()
|
||||||
|
|
||||||
if (defer) {
|
|
||||||
const ret = next()
|
|
||||||
if (ret !== undefined) return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
const ret = fn(editor, ...args)
|
const ret = fn(editor, ...args)
|
||||||
return ret === undefined ? next() : ret
|
return ret === undefined ? next() : ret
|
||||||
}
|
}
|
||||||
|
@@ -136,12 +136,7 @@ function SchemaPlugin(schema) {
|
|||||||
* @param {Function} next
|
* @param {Function} next
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const queries = Queries({
|
const queries = Queries({ isAtomic, isVoid })
|
||||||
queries: {
|
|
||||||
isAtomic,
|
|
||||||
isVoid,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the plugins.
|
* Return the plugins.
|
||||||
|
Reference in New Issue
Block a user