mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-03-06 05:49:47 +01:00
Remove data (#1244)
* remove data from event handler signatures * standardize known transfer types * add setEventTransfer to docs * update examples, fix drag/drop * fix tests and draggable attribute setting
This commit is contained in:
parent
ba5263e0f6
commit
11b2003f53
@ -9,6 +9,7 @@ import {
|
||||
findRange,
|
||||
getEventRange,
|
||||
getEventTransfer,
|
||||
setEventTransfer,
|
||||
} from 'slate-react'
|
||||
```
|
||||
|
||||
@ -41,9 +42,33 @@ Find the Slate range from a DOM `range` or `selection` and a Slate `state`.
|
||||
### `getEventRange`
|
||||
`getEventRange(event: DOMEvent, state: State) => Range`
|
||||
|
||||
Find the affected Slate range from a DOM `event` and Slate `state`.
|
||||
Get the affected Slate range from a DOM `event` and Slate `state`.
|
||||
|
||||
### `getEventTransfer`
|
||||
`getEventTransfer(event: DOMEvent) => Object`
|
||||
|
||||
Find the Slate-related data from a DOM `event` and Slate `state`.
|
||||
Get the Slate-related data from a DOM `event` and Slate `state`.
|
||||
|
||||
```js
|
||||
function onDrop(event, change, editor) {
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, node } = transfer
|
||||
|
||||
if (type == 'node') {
|
||||
// Do something with `node`...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `setEventTransfer`
|
||||
`setEventTransfer(event: DOMEvent, type: String, data: Any)`
|
||||
|
||||
Sets the Slate-related `data` with `type` on an `event`. The `type` must be one of the types Slate recognizes: `'fragment'`, `'html'`, `'node'`, `'rich'`, or `'text'`.
|
||||
|
||||
```js
|
||||
function onDragStart(event, change, editor) {
|
||||
const { state } = change
|
||||
const { startNode } = state
|
||||
setEventTransfer(event, 'node', startNode)
|
||||
}
|
||||
```
|
||||
|
@ -16,11 +16,11 @@ class CheckListItem extends React.Component {
|
||||
/**
|
||||
* On change, set the new checked value on the block.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onChange = (e) => {
|
||||
const checked = e.target.checked
|
||||
onChange = (event) => {
|
||||
const checked = event.target.checked
|
||||
const { editor, node } = this.props
|
||||
editor.change(c => c.setNodeByKey(node.key, { data: { checked }}))
|
||||
}
|
||||
@ -106,32 +106,30 @@ class CheckLists extends React.Component {
|
||||
* If backspace is pressed when collapsed at the start of a check list item,
|
||||
* then turn it back into a paragraph.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @return {State|Void}
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
onKeyDown = (event, change) => {
|
||||
const { state } = change
|
||||
|
||||
if (
|
||||
e.key == 'Enter' &&
|
||||
event.key == 'Enter' &&
|
||||
state.startBlock.type == 'check-list-item'
|
||||
) {
|
||||
return change
|
||||
.splitBlock()
|
||||
.setBlock({ data: { checked: false }})
|
||||
change.splitBlock().setBlock({ data: { checked: false }})
|
||||
return true
|
||||
}
|
||||
|
||||
if (
|
||||
e.key == 'Backspace' &&
|
||||
event.key == 'Backspace' &&
|
||||
state.isCollapsed &&
|
||||
state.startBlock.type == 'check-list-item' &&
|
||||
state.selection.startOffset == 0
|
||||
) {
|
||||
return change
|
||||
.setBlock('paragraph')
|
||||
change.setBlock('paragraph')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,8 @@ function CodeBlock(props) {
|
||||
const { editor, node } = props
|
||||
const language = node.data.get('language')
|
||||
|
||||
function onChange(e) {
|
||||
editor.change(c => c.setNodeByKey(node.key, { data: { language: e.target.value }}))
|
||||
function onChange(event) {
|
||||
editor.change(c => c.setNodeByKey(node.key, { data: { language: event.target.value }}))
|
||||
}
|
||||
|
||||
return (
|
||||
@ -165,19 +165,19 @@ class CodeHighlighting extends React.Component {
|
||||
/**
|
||||
* On key down inside code blocks, insert soft new lines.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
onKeyDown = (event, change) => {
|
||||
const { state } = change
|
||||
const { startBlock } = state
|
||||
if (e.key != 'Enter') return
|
||||
if (event.key != 'Enter') return
|
||||
if (startBlock.type != 'code') return
|
||||
if (state.isExpanded) change.delete()
|
||||
return change.insertText('\n')
|
||||
change.insertText('\n')
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,51 +23,59 @@ class ForcedLayout extends React.Component {
|
||||
state: State.fromJSON(initialState),
|
||||
schema: {
|
||||
nodes: {
|
||||
'title': props => <h2 {...props.attrs}>{props.children}</h2>,
|
||||
'paragraph': props => <p {...props.attrs}>{props.children}</p>
|
||||
title: props => <h2 {...props.attrs}>{props.children}</h2>,
|
||||
paragraph: props => <p {...props.attrs}>{props.children}</p>,
|
||||
},
|
||||
rules: [
|
||||
/* Rule that always makes the first block a title, normalizes by inserting one if no children, or setting the top to be a title */
|
||||
|
||||
{
|
||||
match: node => node.kind === 'document',
|
||||
validate: document => !document.nodes.size || document.nodes.first().type !== 'title' ? document.nodes : null,
|
||||
match: (object) => {
|
||||
return object.kind == 'document'
|
||||
},
|
||||
validate: (document) => {
|
||||
return !document.nodes.size || document.nodes.first().type != 'title' ? document.nodes : null
|
||||
},
|
||||
normalize: (change, document, nodes) => {
|
||||
if (!nodes.size) {
|
||||
const title = Block.create({ type: 'title', data: {}})
|
||||
return change.insertNodeByKey(document.key, 0, title)
|
||||
change.insertNodeByKey(document.key, 0, title)
|
||||
return
|
||||
}
|
||||
|
||||
return change.setNodeByKey(nodes.first().key, 'title')
|
||||
change.setNodeByKey(nodes.first().key, 'title')
|
||||
}
|
||||
},
|
||||
|
||||
/* Rule that only allows for one title, normalizes by making titles paragraphs */
|
||||
|
||||
{
|
||||
match: node => node.kind === 'document',
|
||||
match: (object) => {
|
||||
return object.kind == 'document'
|
||||
},
|
||||
validate: (document) => {
|
||||
const invalidChildren = document.nodes.filter((child, index) => child.type === 'title' && index !== 0)
|
||||
const invalidChildren = document.nodes.filter((child, index) => child.type == 'title' && index != 0)
|
||||
return invalidChildren.size ? invalidChildren : null
|
||||
},
|
||||
normalize: (change, document, invalidChildren) => {
|
||||
let updatedTransform = change
|
||||
invalidChildren.forEach((child) => {
|
||||
updatedTransform = change.setNodeByKey(child.key, 'paragraph')
|
||||
change.setNodeByKey(child.key, 'paragraph')
|
||||
})
|
||||
|
||||
return updatedTransform
|
||||
}
|
||||
},
|
||||
|
||||
/* Rule that forces at least one paragraph, normalizes by inserting an empty paragraph */
|
||||
|
||||
{
|
||||
match: node => node.kind === 'document',
|
||||
validate: document => document.nodes.size < 2 ? true : null,
|
||||
match: (object) => {
|
||||
return object.kind == 'document'
|
||||
},
|
||||
validate: (document) => {
|
||||
return document.nodes.size < 2 ? true : null
|
||||
},
|
||||
normalize: (change, document) => {
|
||||
const paragraph = Block.create({ type: 'paragraph', data: {}})
|
||||
return change.insertNodeByKey(document.key, 1, paragraph)
|
||||
change.insertNodeByKey(document.key, 1, paragraph)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -23,7 +23,14 @@ const schema = {
|
||||
}
|
||||
}
|
||||
|
||||
function Menu({ menuRef, onChange, state }) {
|
||||
/**
|
||||
* The menu.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class Menu extends React.Component {
|
||||
|
||||
/**
|
||||
* Check if the current selection has a mark with `type` in it.
|
||||
*
|
||||
@ -31,22 +38,22 @@ function Menu({ menuRef, onChange, state }) {
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function hasMark(type) {
|
||||
hasMark(type) {
|
||||
const { state } = this.props
|
||||
return state.activeMarks.some(mark => mark.type == type)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a mark button is clicked, toggle the current mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
function onClickMark(e, type) {
|
||||
e.preventDefault()
|
||||
const change = state
|
||||
.change()
|
||||
.toggleMark(type)
|
||||
onClickMark(event, type) {
|
||||
const { state, onChange } = this.props
|
||||
event.preventDefault()
|
||||
const change = state.change().toggleMark(type)
|
||||
onChange(change)
|
||||
}
|
||||
|
||||
@ -58,11 +65,9 @@ function Menu({ menuRef, onChange, state }) {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
function renderMarkButton(type, icon) {
|
||||
const isActive = hasMark(type)
|
||||
function onMouseDown(e) {
|
||||
onClickMark(e, type)
|
||||
}
|
||||
renderMarkButton(type, icon) {
|
||||
const isActive = this.hasMark(type)
|
||||
const onMouseDown = event => this.onClickMark(event, type)
|
||||
|
||||
return (
|
||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||
@ -71,16 +76,26 @@ function Menu({ menuRef, onChange, state }) {
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
ReactDOM.createPortal(
|
||||
<div className="menu hover-menu" ref={menuRef}>
|
||||
{renderMarkButton('bold', 'format_bold')}
|
||||
{renderMarkButton('italic', 'format_italic')}
|
||||
{renderMarkButton('underlined', 'format_underlined')}
|
||||
{renderMarkButton('code', 'code')}
|
||||
</div>, root
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
render() {
|
||||
return (
|
||||
ReactDOM.createPortal(
|
||||
<div className="menu hover-menu" ref={this.props.menuRef}>
|
||||
{this.renderMarkButton('bold', 'format_bold')}
|
||||
{this.renderMarkButton('italic', 'format_italic')}
|
||||
{this.renderMarkButton('underlined', 'format_underlined')}
|
||||
{this.renderMarkButton('code', 'code')}
|
||||
</div>,
|
||||
root
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -125,11 +140,14 @@ class HoveringMenu extends React.Component {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set menu ref
|
||||
* Save the `menu` ref.
|
||||
*
|
||||
* @param {Menu} menu
|
||||
*/
|
||||
|
||||
menuRef = el => this.menu = el
|
||||
menuRef = (menu) => {
|
||||
this.menu = menu
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
|
@ -23,14 +23,14 @@ for (let h = 0; h < HEADINGS; h++) {
|
||||
nodes.push({
|
||||
kind: 'block',
|
||||
type: 'heading',
|
||||
nodes: [{ kind: 'text', ranges: [{ text: faker.lorem.sentence() }] }]
|
||||
nodes: [{ kind: 'text', leaves: [{ text: faker.lorem.sentence() }] }]
|
||||
})
|
||||
|
||||
for (let p = 0; p < PARAGRAPHS; p++) {
|
||||
nodes.push({
|
||||
kind: 'block',
|
||||
type: 'paragraph',
|
||||
nodes: [{ kind: 'text', ranges: [{ text: faker.lorem.paragraph() }] }]
|
||||
nodes: [{ kind: 'text', leaves: [{ text: faker.lorem.paragraph() }] }]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -175,11 +175,11 @@ class Images extends React.Component {
|
||||
/**
|
||||
* On clicking the image button, prompt for an image and insert it.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onClickImage = (e) => {
|
||||
e.preventDefault()
|
||||
onClickImage = (event) => {
|
||||
event.preventDefault()
|
||||
const src = window.prompt('Enter the URL of the image:')
|
||||
if (!src) return
|
||||
|
||||
@ -193,17 +193,16 @@ class Images extends React.Component {
|
||||
/**
|
||||
* On drop, insert the image wherever it is dropped.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
onDropOrPaste = (e, data, change, editor) => {
|
||||
const target = getEventRange(e)
|
||||
onDropOrPaste = (event, change, editor) => {
|
||||
const target = getEventRange(event)
|
||||
if (!target) return
|
||||
|
||||
const transfer = getEventTransfer(e)
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, text, files } = transfer
|
||||
|
||||
if (type == 'files') {
|
||||
|
@ -92,11 +92,11 @@ class Links extends React.Component {
|
||||
* When clicking a link, if the selection has a link in it, remove the link.
|
||||
* Otherwise, add a new link with an href and text.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onClickLink = (e) => {
|
||||
e.preventDefault()
|
||||
onClickLink = (event) => {
|
||||
event.preventDefault()
|
||||
const { state } = this.state
|
||||
const hasLinks = this.hasLinks()
|
||||
const change = state.change()
|
||||
@ -125,15 +125,14 @@ class Links extends React.Component {
|
||||
/**
|
||||
* On paste, if the text is a link, wrap the selection in a link.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onPaste = (e, data, change) => {
|
||||
onPaste = (event, change) => {
|
||||
if (change.state.isCollapsed) return
|
||||
|
||||
const transfer = getEventTransfer(e)
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, text } = transfer
|
||||
if (type != 'text' && type != 'html') return
|
||||
if (!isUrl(text)) return
|
||||
|
@ -99,16 +99,15 @@ class MarkdownShortcuts extends React.Component {
|
||||
/**
|
||||
* On key down, check for our specific key shortcuts.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Data} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
switch (e.key) {
|
||||
case ' ': return this.onSpace(e, change)
|
||||
case 'Backspace': return this.onBackspace(e, change)
|
||||
case 'Enter': return this.onEnter(e, change)
|
||||
onKeyDown = (event, change) => {
|
||||
switch (event.key) {
|
||||
case ' ': return this.onSpace(event, change)
|
||||
case 'Backspace': return this.onBackspace(event, change)
|
||||
case 'Enter': return this.onEnter(event, change)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,12 +115,12 @@ class MarkdownShortcuts extends React.Component {
|
||||
* On space, if it was after an auto-markdown shortcut, convert the current
|
||||
* node into the shortcut's corresponding type.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {State} change
|
||||
* @return {State or Null} state
|
||||
*/
|
||||
|
||||
onSpace = (e, change) => {
|
||||
onSpace = (event, change) => {
|
||||
const { state } = change
|
||||
if (state.isExpanded) return
|
||||
|
||||
@ -131,7 +130,7 @@ class MarkdownShortcuts extends React.Component {
|
||||
|
||||
if (!type) return
|
||||
if (type == 'list-item' && startBlock.type == 'list-item') return
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
|
||||
change.setBlock(type)
|
||||
|
||||
@ -139,10 +138,7 @@ class MarkdownShortcuts extends React.Component {
|
||||
change.wrapBlock('bulleted-list')
|
||||
}
|
||||
|
||||
change
|
||||
.extendToStartOf(startBlock)
|
||||
.delete()
|
||||
|
||||
change.extendToStartOf(startBlock).delete()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -150,12 +146,12 @@ class MarkdownShortcuts extends React.Component {
|
||||
* On backspace, if at the start of a non-paragraph, convert it back into a
|
||||
* paragraph node.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {State} change
|
||||
* @return {State or Null} state
|
||||
*/
|
||||
|
||||
onBackspace = (e, change) => {
|
||||
onBackspace = (event, change) => {
|
||||
const { state } = change
|
||||
if (state.isExpanded) return
|
||||
if (state.startOffset != 0) return
|
||||
@ -163,7 +159,7 @@ class MarkdownShortcuts extends React.Component {
|
||||
const { startBlock } = state
|
||||
if (startBlock.type == 'paragraph') return
|
||||
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
change.setBlock('paragraph')
|
||||
|
||||
if (startBlock.type == 'list-item') {
|
||||
@ -177,17 +173,17 @@ class MarkdownShortcuts extends React.Component {
|
||||
* On return, if at the end of a node type that should not be extended,
|
||||
* create a new paragraph below it.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {State} change
|
||||
* @return {State or Null} state
|
||||
*/
|
||||
|
||||
onEnter = (e, change) => {
|
||||
onEnter = (event, change) => {
|
||||
const { state } = change
|
||||
if (state.isExpanded) return
|
||||
|
||||
const { startBlock, startOffset, endOffset } = state
|
||||
if (startOffset == 0 && startBlock.text.length == 0) return this.onBackspace(e, change)
|
||||
if (startOffset == 0 && startBlock.text.length == 0) return this.onBackspace(event, change)
|
||||
if (endOffset != startBlock.text.length) return
|
||||
|
||||
if (
|
||||
@ -202,12 +198,8 @@ class MarkdownShortcuts extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
|
||||
change
|
||||
.splitBlock()
|
||||
.setBlock('paragraph')
|
||||
|
||||
event.preventDefault()
|
||||
change.splitBlock().setBlock('paragraph')
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -174,13 +174,12 @@ class PasteHtml extends React.Component {
|
||||
/**
|
||||
* On paste, deserialize the HTML and then insert the fragment.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onPaste = (e, data, change) => {
|
||||
const transfer = getEventTransfer(e)
|
||||
onPaste = (event, change) => {
|
||||
const transfer = getEventTransfer(event)
|
||||
if (transfer.type != 'html') return
|
||||
const { document } = serializer.deserialize(transfer.html)
|
||||
change.insertFragment(document)
|
||||
|
@ -114,28 +114,27 @@ class RichTextExample extends React.Component {
|
||||
/**
|
||||
* On key down, if it's a formatting command toggle a mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
onKeyDown = (event, change) => {
|
||||
let mark
|
||||
|
||||
if (isBoldHotkey(e)) {
|
||||
if (isBoldHotkey(event)) {
|
||||
mark = 'bold'
|
||||
} else if (isItalicHotkey(e)) {
|
||||
} else if (isItalicHotkey(event)) {
|
||||
mark = 'italic'
|
||||
} else if (isUnderlinedHotkey(e)) {
|
||||
} else if (isUnderlinedHotkey(event)) {
|
||||
mark = 'underlined'
|
||||
} else if (isCodeHotkey(e)) {
|
||||
} else if (isCodeHotkey(event)) {
|
||||
mark = 'code'
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
change.toggleMark(mark)
|
||||
return true
|
||||
}
|
||||
@ -143,12 +142,12 @@ class RichTextExample extends React.Component {
|
||||
/**
|
||||
* When a mark button is clicked, toggle the current mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
onClickMark = (e, type) => {
|
||||
e.preventDefault()
|
||||
onClickMark = (event, type) => {
|
||||
event.preventDefault()
|
||||
const { state } = this.state
|
||||
const change = state.change().toggleMark(type)
|
||||
this.onChange(change)
|
||||
@ -157,12 +156,12 @@ class RichTextExample extends React.Component {
|
||||
/**
|
||||
* When a block button is clicked, toggle the block type.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
onClickBlock = (e, type) => {
|
||||
e.preventDefault()
|
||||
onClickBlock = (event, type) => {
|
||||
event.preventDefault()
|
||||
const { state } = this.state
|
||||
const change = state.change()
|
||||
const { document } = state
|
||||
@ -258,7 +257,7 @@ class RichTextExample extends React.Component {
|
||||
|
||||
renderMarkButton = (type, icon) => {
|
||||
const isActive = this.hasMark(type)
|
||||
const onMouseDown = e => this.onClickMark(e, type)
|
||||
const onMouseDown = event => this.onClickMark(event, type)
|
||||
|
||||
return (
|
||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||
@ -277,7 +276,7 @@ class RichTextExample extends React.Component {
|
||||
|
||||
renderBlockButton = (type, icon) => {
|
||||
const isActive = this.hasBlock(type)
|
||||
const onMouseDown = e => this.onClickBlock(e, type)
|
||||
const onMouseDown = event => this.onClickBlock(event, type)
|
||||
|
||||
return (
|
||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||
|
@ -48,14 +48,13 @@ class PlainText extends React.Component {
|
||||
/**
|
||||
* On key down, if it's <shift-enter> add a soft break.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
if (e.key == 'Enter' && e.shiftKey) {
|
||||
e.preventDefault()
|
||||
onKeyDown = (event, change) => {
|
||||
if (event.key == 'Enter' && event.shiftKey) {
|
||||
event.preventDefault()
|
||||
change.insertText('\n')
|
||||
return true
|
||||
}
|
||||
|
@ -50,12 +50,12 @@ class SearchHighlighting extends React.Component {
|
||||
/**
|
||||
* On input change, update the decorations.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onInputChange = (e) => {
|
||||
onInputChange = (event) => {
|
||||
const { state } = this.state
|
||||
const string = e.target.value
|
||||
const string = event.target.value
|
||||
const texts = state.document.getTexts()
|
||||
const decorations = []
|
||||
|
||||
|
@ -105,28 +105,27 @@ class SyncingEditor extends React.Component {
|
||||
/**
|
||||
* On key down, if it's a formatting command toggle a mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @return {Change}
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
onKeyDown = (event, change) => {
|
||||
let mark
|
||||
|
||||
if (isBoldHotkey(e)) {
|
||||
if (isBoldHotkey(event)) {
|
||||
mark = 'bold'
|
||||
} else if (isItalicHotkey(e)) {
|
||||
} else if (isItalicHotkey(event)) {
|
||||
mark = 'italic'
|
||||
} else if (isUnderlinedHotkey(e)) {
|
||||
} else if (isUnderlinedHotkey(event)) {
|
||||
mark = 'underlined'
|
||||
} else if (isCodeHotkey(e)) {
|
||||
} else if (isCodeHotkey(event)) {
|
||||
mark = 'code'
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
change.toggleMark(mark)
|
||||
return true
|
||||
}
|
||||
@ -134,12 +133,12 @@ class SyncingEditor extends React.Component {
|
||||
/**
|
||||
* When a mark button is clicked, toggle the current mark.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {String} type
|
||||
*/
|
||||
|
||||
onClickMark = (e, type) => {
|
||||
e.preventDefault()
|
||||
onClickMark = (event, type) => {
|
||||
event.preventDefault()
|
||||
const { state } = this.state
|
||||
const change = state.change().toggleMark(type)
|
||||
this.onChange(change)
|
||||
@ -187,7 +186,7 @@ class SyncingEditor extends React.Component {
|
||||
|
||||
renderButton = (type, icon) => {
|
||||
const isActive = this.hasMark(type)
|
||||
const onMouseDown = e => this.onClickMark(e, type)
|
||||
const onMouseDown = event => this.onClickMark(event, type)
|
||||
|
||||
return (
|
||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||
|
@ -43,14 +43,14 @@ class Tables extends React.Component {
|
||||
/**
|
||||
* On backspace, do nothing if at the start of a table cell.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onBackspace = (e, change) => {
|
||||
onBackspace = (event, change) => {
|
||||
const { state } = change
|
||||
if (state.startOffset != 0) return
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -67,38 +67,37 @@ class Tables extends React.Component {
|
||||
/**
|
||||
* On delete, do nothing if at the end of a table cell.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onDelete = (e, change) => {
|
||||
onDelete = (event, change) => {
|
||||
const { state } = change
|
||||
if (state.endOffset != state.startText.text.length) return
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* On return, do nothing if inside a table cell.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onEnter = (e, change) => {
|
||||
e.preventDefault()
|
||||
onEnter = (event, change) => {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* On key down, check for our specific key shortcuts.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
*/
|
||||
|
||||
onKeyDown = (e, data, change) => {
|
||||
onKeyDown = (event, change) => {
|
||||
const { state } = change
|
||||
const { document, selection } = state
|
||||
const { startKey } = selection
|
||||
@ -109,7 +108,7 @@ class Tables extends React.Component {
|
||||
const prevBlock = document.getClosestBlock(previous.key)
|
||||
|
||||
if (prevBlock.type == 'table-cell') {
|
||||
e.preventDefault()
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -118,10 +117,10 @@ class Tables extends React.Component {
|
||||
return
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case 'Backspace': return this.onBackspace(e, state)
|
||||
case 'Delete': return this.onDelete(e, state)
|
||||
case 'Enter': return this.onEnter(e, state)
|
||||
switch (event.key) {
|
||||
case 'Backspace': return this.onBackspace(event, state)
|
||||
case 'Delete': return this.onDelete(event, state)
|
||||
case 'Enter': return this.onEnter(event, state)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,9 +11,8 @@ import Node from './node'
|
||||
import findClosestNode from '../utils/find-closest-node'
|
||||
import findDOMRange from '../utils/find-dom-range'
|
||||
import findRange from '../utils/find-range'
|
||||
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
|
||||
import scrollToSelection from '../utils/scroll-to-selection'
|
||||
import { IS_FIREFOX, IS_IE, SUPPORTED_EVENTS } from '../constants/environment'
|
||||
import { IS_FIREFOX, SUPPORTED_EVENTS } from '../constants/environment'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
@ -229,18 +228,6 @@ class Content extends React.Component {
|
||||
this.tmp.key++
|
||||
}
|
||||
|
||||
// COMPAT: In IE 11, only plain text can be retrieved from the event's
|
||||
// `clipboardData`. To get HTML, use the browser's native paste action which
|
||||
// can only be handled synchronously. (2017/06/23)
|
||||
if (handler == 'onPaste' && IS_IE) {
|
||||
getHtmlFromNativePaste(event.target, (html) => {
|
||||
const data = html ? { html, type: 'html' } : {}
|
||||
this.props.onPaste(event, data)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If the `onSelect` handler fires while the `isUpdatingSelection` flag is
|
||||
// set it's a result of updating the selection manually, so skip it.
|
||||
if (handler == 'onSelect' && this.tmp.isUpdatingSelection) {
|
||||
@ -274,7 +261,6 @@ class Content extends React.Component {
|
||||
handler == 'onCompositionStart' ||
|
||||
handler == 'onCopy' ||
|
||||
handler == 'onCut' ||
|
||||
handler == 'onDragStart' ||
|
||||
handler == 'onFocus' ||
|
||||
handler == 'onInput' ||
|
||||
handler == 'onKeyDown' ||
|
||||
@ -285,7 +271,7 @@ class Content extends React.Component {
|
||||
if (!this.isInEditor(event.target)) return
|
||||
}
|
||||
|
||||
this.props[handler](event, {})
|
||||
this.props[handler](event)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,7 +301,6 @@ class Content extends React.Component {
|
||||
|
||||
if (text == null) return
|
||||
|
||||
debug('onNativeBeforeInput', { event, text })
|
||||
event.preventDefault()
|
||||
|
||||
const { editor, state } = this.props
|
||||
|
@ -1,13 +1,10 @@
|
||||
|
||||
import Base64 from 'slate-base64-serializer'
|
||||
import Debug from 'debug'
|
||||
import React from 'react'
|
||||
import SlateTypes from 'slate-prop-types'
|
||||
import Types from 'prop-types'
|
||||
|
||||
import setTransferData from '../utils/set-transfer-data'
|
||||
import Text from './text'
|
||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
@ -56,68 +53,6 @@ class Void extends React.Component {
|
||||
debug(message, `${id}`, ...args)
|
||||
}
|
||||
|
||||
/**
|
||||
* When one of the wrapper elements it clicked, select the void node.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onClick = (event) => {
|
||||
if (this.props.readOnly) return
|
||||
|
||||
this.debug('onClick', { event })
|
||||
|
||||
const { node, editor } = this.props
|
||||
|
||||
editor.change((change) => {
|
||||
change
|
||||
// COMPAT: In Chrome & Safari, selections that are at the zero offset of
|
||||
// an inline node will be automatically replaced to be at the last
|
||||
// offset of a previous inline node, which screws us up, so we always
|
||||
// want to set it to the end of the node. (2016/11/29)
|
||||
.collapseToEndOf(node)
|
||||
.focus()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag enter, prevent default to allow drops.
|
||||
*
|
||||
* @type {Event} event
|
||||
*/
|
||||
|
||||
onDragEnter = (event) => {
|
||||
if (this.props.readOnly) return
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag over, prevent default to allow drops.
|
||||
*
|
||||
* @type {Event} event
|
||||
*/
|
||||
|
||||
onDragOver = (event) => {
|
||||
if (this.props.readOnly) return
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag start, add a serialized representation of the node to the data.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
|
||||
onDragStart = (event) => {
|
||||
const { node } = this.props
|
||||
const encoded = Base64.serializeNode(node, { preserveKeys: true })
|
||||
const { dataTransfer } = event.nativeEvent
|
||||
|
||||
setTransferData(dataTransfer, TRANSFER_TYPES.NODE, encoded)
|
||||
|
||||
this.debug('onDragStart', event)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render.
|
||||
*
|
||||
@ -136,14 +71,7 @@ class Void extends React.Component {
|
||||
this.debug('render', { props })
|
||||
|
||||
return (
|
||||
<Tag
|
||||
data-slate-void
|
||||
data-key={node.key}
|
||||
onClick={this.onClick}
|
||||
onDragOver={this.onDragOver}
|
||||
onDragEnter={this.onDragEnter}
|
||||
onDragStart={this.onDragStart}
|
||||
>
|
||||
<Tag data-slate-void data-key={node.key} draggable={readOnly ? null : true}>
|
||||
{!readOnly && <Tag style={style}>
|
||||
{this.renderText()}
|
||||
</Tag>}
|
||||
|
@ -8,11 +8,15 @@
|
||||
const EVENT_HANDLERS = [
|
||||
'onBeforeInput',
|
||||
'onBlur',
|
||||
'onClick',
|
||||
'onCompositionEnd',
|
||||
'onCompositionStart',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDragEnd',
|
||||
'onDragEnter',
|
||||
'onDragExit',
|
||||
'onDragLeave',
|
||||
'onDragOver',
|
||||
'onDragStart',
|
||||
'onDrop',
|
||||
|
@ -38,6 +38,13 @@ const CONTENTEDITABLE = e => (
|
||||
UNDO(e)
|
||||
)
|
||||
|
||||
const COMPOSING = e => (
|
||||
e.key == 'ArrowDown' ||
|
||||
e.key == 'ArrowLeft' ||
|
||||
e.key == 'ArrowRight' ||
|
||||
e.key == 'ArrowUp'
|
||||
)
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
@ -46,6 +53,7 @@ const CONTENTEDITABLE = e => (
|
||||
|
||||
export default {
|
||||
BOLD,
|
||||
COMPOSING,
|
||||
CONTENTEDITABLE,
|
||||
DELETE_CHAR_BACKWARD,
|
||||
DELETE_CHAR_FORWARD,
|
||||
|
@ -1,12 +1,16 @@
|
||||
|
||||
/**
|
||||
* Slate-specific data transfer types.
|
||||
* The transfer types that Slate recognizes.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
const TRANSFER_TYPES = {
|
||||
FRAGMENT: 'application/x-slate-fragment',
|
||||
HTML: 'text/html',
|
||||
NODE: 'application/x-slate-node',
|
||||
RICH: 'text/rtf',
|
||||
TEXT: 'text/plain',
|
||||
}
|
||||
|
||||
/**
|
||||
@ -15,4 +19,4 @@ const TYPES = {
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
export default TYPES
|
||||
export default TRANSFER_TYPES
|
||||
|
@ -1,12 +1,13 @@
|
||||
|
||||
import Editor from './components/editor'
|
||||
import Placeholder from './components/placeholder'
|
||||
import getEventRange from './utils/get-event-range'
|
||||
import getEventTransfer from './utils/get-event-transfer'
|
||||
import findDOMNode from './utils/find-dom-node'
|
||||
import findDOMRange from './utils/find-dom-range'
|
||||
import findNode from './utils/find-node'
|
||||
import findRange from './utils/find-range'
|
||||
import getEventRange from './utils/get-event-range'
|
||||
import getEventTransfer from './utils/get-event-transfer'
|
||||
import setEventTransfer from './utils/set-event-transfer'
|
||||
|
||||
/**
|
||||
* Export.
|
||||
@ -17,21 +18,23 @@ import findRange from './utils/find-range'
|
||||
export {
|
||||
Editor,
|
||||
Placeholder,
|
||||
getEventRange,
|
||||
getEventTransfer,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
findRange,
|
||||
getEventRange,
|
||||
getEventTransfer,
|
||||
setEventTransfer,
|
||||
}
|
||||
|
||||
export default {
|
||||
Editor,
|
||||
Placeholder,
|
||||
getEventRange,
|
||||
getEventTransfer,
|
||||
findDOMNode,
|
||||
findDOMRange,
|
||||
findNode,
|
||||
findRange,
|
||||
getEventRange,
|
||||
getEventTransfer,
|
||||
setEventTransfer,
|
||||
}
|
||||
|
@ -11,10 +11,12 @@ import HOTKEYS from '../constants/hotkeys'
|
||||
import Content from '../components/content'
|
||||
import Placeholder from '../components/placeholder'
|
||||
import findDOMNode from '../utils/find-dom-node'
|
||||
import findNode from '../utils/find-node'
|
||||
import findPoint from '../utils/find-point'
|
||||
import findRange from '../utils/find-range'
|
||||
import getEventRange from '../utils/get-event-range'
|
||||
import getEventTransfer from '../utils/get-event-transfer'
|
||||
import setEventTransfer from '../utils/set-event-transfer'
|
||||
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
||||
|
||||
/**
|
||||
@ -60,21 +62,23 @@ function AfterPlugin(options = {}) {
|
||||
// normalize changes to the document, not selection.
|
||||
if (prevState && state.document == prevState.document) return
|
||||
|
||||
debug('onBeforeChange')
|
||||
|
||||
change.normalize(coreSchema)
|
||||
change.normalize(schema)
|
||||
debug('onBeforeChange')
|
||||
}
|
||||
|
||||
/**
|
||||
* On before input, correct any browser inconsistencies.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBeforeInput(event, data, change) {
|
||||
debug('onBeforeInput', { data })
|
||||
function onBeforeInput(event, change, editor) {
|
||||
debug('onBeforeInput', { event })
|
||||
|
||||
event.preventDefault()
|
||||
change.insertText(event.data)
|
||||
}
|
||||
@ -83,40 +87,69 @@ function AfterPlugin(options = {}) {
|
||||
* On blur.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBlur(event, data, change) {
|
||||
debug('onBlur', { data })
|
||||
function onBlur(event, change, editor) {
|
||||
debug('onBlur', { event })
|
||||
|
||||
change.blur()
|
||||
}
|
||||
|
||||
/**
|
||||
* On click.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onClick(event, change, editor) {
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
const { state } = change
|
||||
const { document } = state
|
||||
const node = findNode(event.target, state)
|
||||
const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
|
||||
|
||||
if (isVoid) {
|
||||
// COMPAT: In Chrome & Safari, selections that are at the zero offset of
|
||||
// an inline node will be automatically replaced to be at the last offset
|
||||
// of a previous inline node, which screws us up, so we always want to set
|
||||
// it to the end of the node. (2016/11/29)
|
||||
change.focus().collapseToEndOf(node)
|
||||
}
|
||||
|
||||
debug('onClick', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On copy.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCopy(event, data, change) {
|
||||
debug('onCopy', data)
|
||||
onCutOrCopy(event, data, change)
|
||||
function onCopy(event, change, editor) {
|
||||
debug('onCopy', { event })
|
||||
|
||||
onCutOrCopy(event, change)
|
||||
}
|
||||
|
||||
/**
|
||||
* On cut.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCut(event, data, change, editor) {
|
||||
debug('onCut', data)
|
||||
onCutOrCopy(event, data, change)
|
||||
function onCut(event, change, editor) {
|
||||
debug('onCut', { event })
|
||||
|
||||
onCutOrCopy(event, change)
|
||||
const window = getWindow(event.target)
|
||||
|
||||
// Once the fake cut content has successfully been added to the clipboard,
|
||||
@ -130,11 +163,11 @@ function AfterPlugin(options = {}) {
|
||||
* On cut or copy.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCutOrCopy(event, data, change) {
|
||||
function onCutOrCopy(event, change, editor) {
|
||||
const window = getWindow(event.target)
|
||||
const native = window.getSelection()
|
||||
const { state } = change
|
||||
@ -242,12 +275,11 @@ function AfterPlugin(options = {}) {
|
||||
* On drag end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragEnd(event, data, change, editor) {
|
||||
function onDragEnd(event, change, editor) {
|
||||
debug('onDragEnd', { event })
|
||||
|
||||
isDraggingInternally = null
|
||||
@ -257,12 +289,11 @@ function AfterPlugin(options = {}) {
|
||||
* On drag over.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragOver(event, data, change, editor) {
|
||||
function onDragOver(event, change, editor) {
|
||||
debug('onDragOver', { event })
|
||||
|
||||
isDraggingInternally = false
|
||||
@ -272,26 +303,39 @@ function AfterPlugin(options = {}) {
|
||||
* On drag start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragStart(event, data, change, editor) {
|
||||
function onDragStart(event, change, editor) {
|
||||
debug('onDragStart', { event })
|
||||
|
||||
isDraggingInternally = true
|
||||
|
||||
const { state } = change
|
||||
const { document } = state
|
||||
const node = findNode(event.target, state)
|
||||
const isVoid = node && (node.isVoid || document.hasVoidParent(node.key))
|
||||
|
||||
if (isVoid) {
|
||||
const encoded = Base64.serializeNode(node, { preserveKeys: true })
|
||||
setEventTransfer(event, 'node', encoded)
|
||||
} else {
|
||||
const { fragment } = state
|
||||
const encoded = Base64.serializeNode(fragment)
|
||||
setEventTransfer(event, 'fragment', encoded)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On drop.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDrop(event, data, change, editor) {
|
||||
function onDrop(event, change, editor) {
|
||||
debug('onDrop', { event })
|
||||
|
||||
const { state } = change
|
||||
@ -363,12 +407,12 @@ function AfterPlugin(options = {}) {
|
||||
* On input.
|
||||
*
|
||||
* @param {Event} eventvent
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onInput(event, data, change, editor) {
|
||||
function onInput(event, change, editor) {
|
||||
debug('onInput', { event })
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { state } = change
|
||||
|
||||
@ -428,21 +472,21 @@ function AfterPlugin(options = {}) {
|
||||
* On key down.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDown(event, data, change) {
|
||||
debug('onKeyDown', { data })
|
||||
function onKeyDown(event, change, editor) {
|
||||
debug('onKeyDown', { event })
|
||||
|
||||
switch (event.key) {
|
||||
case 'Enter': return onKeyDownEnter(event, data, change)
|
||||
case 'Backspace': return onKeyDownBackspace(event, data, change)
|
||||
case 'Delete': return onKeyDownDelete(event, data, change)
|
||||
case 'ArrowLeft': return onKeyDownLeft(event, data, change)
|
||||
case 'ArrowRight': return onKeyDownRight(event, data, change)
|
||||
case 'ArrowUp': return onKeyDownUp(event, data, change)
|
||||
case 'ArrowDown': return onKeyDownDown(event, data, change)
|
||||
case 'Enter': return onKeyDownEnter(event, change)
|
||||
case 'Backspace': return onKeyDownBackspace(event, change)
|
||||
case 'Delete': return onKeyDownDelete(event, change)
|
||||
case 'ArrowLeft': return onKeyDownLeft(event, change)
|
||||
case 'ArrowRight': return onKeyDownRight(event, change)
|
||||
case 'ArrowUp': return onKeyDownUp(event, change)
|
||||
case 'ArrowDown': return onKeyDownDown(event, change)
|
||||
}
|
||||
|
||||
if (HOTKEYS.DELETE_CHAR_BACKWARD(event)) {
|
||||
@ -470,11 +514,11 @@ function AfterPlugin(options = {}) {
|
||||
* On `enter` key down, split the current block in half.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownEnter(event, data, change) {
|
||||
function onKeyDownEnter(event, change, editor) {
|
||||
const { state } = change
|
||||
const { document, startKey } = state
|
||||
const hasVoidParent = document.hasVoidParent(startKey)
|
||||
@ -495,11 +539,11 @@ function AfterPlugin(options = {}) {
|
||||
* On `backspace` key down, delete backwards.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownBackspace(event, data, change) {
|
||||
function onKeyDownBackspace(event, change, editor) {
|
||||
const isWord = IS_MAC ? event.altKey : event.ctrlKey
|
||||
const isLine = IS_MAC ? event.metaKey : false
|
||||
|
||||
@ -514,11 +558,11 @@ function AfterPlugin(options = {}) {
|
||||
* On `delete` key down, delete forwards.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownDelete(event, data, change) {
|
||||
function onKeyDownDelete(event, change, editor) {
|
||||
const isWord = IS_MAC ? event.altKey : event.ctrlKey
|
||||
const isLine = IS_MAC ? event.metaKey : false
|
||||
|
||||
@ -540,11 +584,11 @@ function AfterPlugin(options = {}) {
|
||||
* the zero-width spaces will cause two arrow keys to jump to the next text.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownLeft(event, data, change) {
|
||||
function onKeyDownLeft(event, change, editor) {
|
||||
const { state } = change
|
||||
|
||||
if (event.ctrlKey) return
|
||||
@ -596,11 +640,11 @@ function AfterPlugin(options = {}) {
|
||||
* selection to the very start of an inline node here. (2016/11/29)
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownRight(event, data, change) {
|
||||
function onKeyDownRight(event, change, editor) {
|
||||
const { state } = change
|
||||
|
||||
if (event.ctrlKey) return
|
||||
@ -650,11 +694,11 @@ function AfterPlugin(options = {}) {
|
||||
* Firefox, option-up doesn't properly move the selection.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownUp(event, data, change) {
|
||||
function onKeyDownUp(event, change, editor) {
|
||||
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
||||
|
||||
const { state } = change
|
||||
@ -679,11 +723,11 @@ function AfterPlugin(options = {}) {
|
||||
* Firefox, option-down doesn't properly move the selection.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDownDown(event, data, change) {
|
||||
function onKeyDownDown(event, change, editor) {
|
||||
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
||||
|
||||
const { state } = change
|
||||
@ -704,12 +748,12 @@ function AfterPlugin(options = {}) {
|
||||
* On paste.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onPaste(event, data, change) {
|
||||
debug('onPaste', { data })
|
||||
function onPaste(event, change, editor) {
|
||||
debug('onPaste', { event })
|
||||
|
||||
const transfer = getEventTransfer(event)
|
||||
const { type, fragment, text } = transfer
|
||||
@ -734,12 +778,12 @@ function AfterPlugin(options = {}) {
|
||||
* On select.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onSelect(event, data, change) {
|
||||
debug('onSelect', { data })
|
||||
function onSelect(event, change, editor) {
|
||||
debug('onSelect', { event })
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { state } = change
|
||||
@ -916,6 +960,7 @@ function AfterPlugin(options = {}) {
|
||||
onBeforeChange,
|
||||
onBeforeInput,
|
||||
onBlur,
|
||||
onClick,
|
||||
onCopy,
|
||||
onCut,
|
||||
onDragEnd,
|
||||
|
@ -1,18 +1,10 @@
|
||||
|
||||
import Base64 from 'slate-base64-serializer'
|
||||
import Debug from 'debug'
|
||||
import getWindow from 'get-window'
|
||||
import keycode from 'keycode'
|
||||
import logger from 'slate-dev-logger'
|
||||
import { findDOMNode } from 'react-dom'
|
||||
|
||||
import HOTKEYS from '../constants/hotkeys'
|
||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
import findRange from '../utils/find-range'
|
||||
import getEventRange from '../utils/get-event-range'
|
||||
import getEventTransfer from '../utils/get-event-transfer'
|
||||
import setTransferData from '../utils/set-transfer-data'
|
||||
import { IS_FIREFOX, IS_MAC, SUPPORTED_EVENTS } from '../constants/environment'
|
||||
import { IS_FIREFOX, SUPPORTED_EVENTS } from '../constants/environment'
|
||||
|
||||
/**
|
||||
* Debug.
|
||||
@ -33,19 +25,16 @@ function BeforePlugin() {
|
||||
let isComposing = false
|
||||
let isCopying = false
|
||||
let isDragging = false
|
||||
let isShifting = false
|
||||
let isInternalDrag = null
|
||||
|
||||
/**
|
||||
* On before input.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBeforeInput(event, data, change, editor) {
|
||||
function onBeforeInput(event, change, editor) {
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
// COMPAT: React's `onBeforeInput` synthetic event is based on the native
|
||||
@ -63,12 +52,11 @@ function BeforePlugin() {
|
||||
* On blur.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onBlur(event, data, change, editor) {
|
||||
function onBlur(event, change, editor) {
|
||||
if (isCopying) return true
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
@ -86,12 +74,11 @@ function BeforePlugin() {
|
||||
* On composition end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCompositionEnd(event, data, change, editor) {
|
||||
function onCompositionEnd(event, change, editor) {
|
||||
const n = compositionCount
|
||||
|
||||
// The `count` check here ensures that if another composition starts
|
||||
@ -109,12 +96,11 @@ function BeforePlugin() {
|
||||
* On composition start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCompositionStart(event, data, change, editor) {
|
||||
function onCompositionStart(event, change, editor) {
|
||||
isComposing = true
|
||||
compositionCount++
|
||||
|
||||
@ -125,20 +111,15 @@ function BeforePlugin() {
|
||||
* On copy.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCopy(event, data, change, editor) {
|
||||
function onCopy(event, change, editor) {
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => isCopying = false)
|
||||
|
||||
const { state } = change
|
||||
defineDeprecatedData(data, 'type', 'fragment')
|
||||
defineDeprecatedData(data, 'fragment', state.fragment)
|
||||
|
||||
debug('onCopy', { event })
|
||||
}
|
||||
|
||||
@ -146,22 +127,17 @@ function BeforePlugin() {
|
||||
* On cut.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onCut(event, data, change, editor) {
|
||||
function onCut(event, change, editor) {
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
const window = getWindow(event.target)
|
||||
isCopying = true
|
||||
window.requestAnimationFrame(() => isCopying = false)
|
||||
|
||||
const { state } = change
|
||||
defineDeprecatedData(data, 'type', 'fragment')
|
||||
defineDeprecatedData(data, 'fragment', state.fragment)
|
||||
|
||||
debug('onCut', { event })
|
||||
}
|
||||
|
||||
@ -169,33 +145,84 @@ function BeforePlugin() {
|
||||
* On drag end.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragEnd(event, data, change, editor) {
|
||||
function onDragEnd(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
isDragging = false
|
||||
isInternalDrag = null
|
||||
|
||||
debug('onDragEnd', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag enter.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragEnter(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
debug('onDragEnter', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag exit.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragExit(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
debug('onDragExit', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag leave.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragLeave(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
debug('onDragLeave', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On drag over.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragOver(event, data, change, editor) {
|
||||
if (isDragging) return true
|
||||
function onDragOver(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
// If a drag is already in progress, don't do this again.
|
||||
if (!isDragging) return true
|
||||
|
||||
isDragging = true
|
||||
isInternalDrag = false
|
||||
event.nativeEvent.dataTransfer.dropEffect = 'move'
|
||||
|
||||
// You must call `preventDefault` to signal that drops are allowed.
|
||||
event.preventDefault()
|
||||
|
||||
debug('onDragOver', { event })
|
||||
}
|
||||
@ -204,29 +231,15 @@ function BeforePlugin() {
|
||||
* On drag start.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDragStart(event, data, change, editor) {
|
||||
function onDragStart(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
|
||||
isDragging = true
|
||||
isInternalDrag = true
|
||||
|
||||
const d = getEventTransfer(event)
|
||||
const { nativeEvent } = event
|
||||
const { dataTransfer } = nativeEvent
|
||||
|
||||
Object.keys(d).forEach((key) => {
|
||||
defineDeprecatedData(data, key, d[key])
|
||||
})
|
||||
|
||||
if (d.type != 'node') {
|
||||
const { state } = change
|
||||
const { fragment } = state
|
||||
const encoded = Base64.serializeNode(fragment)
|
||||
setTransferData(dataTransfer, TRANSFER_TYPES.FRAGMENT, encoded)
|
||||
}
|
||||
|
||||
debug('onDragStart', { event })
|
||||
}
|
||||
@ -235,43 +248,19 @@ function BeforePlugin() {
|
||||
* On drop.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onDrop(event, data, change, editor) {
|
||||
function onDrop(event, change, editor) {
|
||||
// Stop propagation so the event isn't visible to parent editors.
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
// Nothing happens in read-only mode.
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
const { state } = change
|
||||
const { nativeEvent } = event
|
||||
const { dataTransfer } = nativeEvent
|
||||
const d = getEventTransfer(event)
|
||||
|
||||
Object.keys(d).forEach((key) => {
|
||||
defineDeprecatedData(data, key, d[key])
|
||||
})
|
||||
|
||||
const range = getEventRange(event, state)
|
||||
if (!range) return true
|
||||
|
||||
// Add drop-specific information to the data.
|
||||
defineDeprecatedData(data, 'target', range)
|
||||
|
||||
// COMPAT: Edge throws "Permission denied" errors when
|
||||
// accessing `dropEffect` or `effectAllowed` (2017/7/12)
|
||||
try {
|
||||
defineDeprecatedData(data, 'effect', dataTransfer.dropEffect)
|
||||
} catch (err) {
|
||||
defineDeprecatedData(data, 'effect', null)
|
||||
}
|
||||
|
||||
if (d.type == 'fragment' || d.type == 'node') {
|
||||
defineDeprecatedData(data, 'isInternal', isInternalDrag)
|
||||
}
|
||||
// Prevent default so the DOM's state isn't corrupted.
|
||||
event.preventDefault()
|
||||
|
||||
debug('onDrop', { event })
|
||||
}
|
||||
@ -280,12 +269,11 @@ function BeforePlugin() {
|
||||
* On focus.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onFocus(event, data, change, editor) {
|
||||
function onFocus(event, change, editor) {
|
||||
if (isCopying) return true
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
@ -306,12 +294,11 @@ function BeforePlugin() {
|
||||
* On input.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onInput(event, data, change, editor) {
|
||||
function onInput(event, change, editor) {
|
||||
if (isComposing) return true
|
||||
if (change.state.isBlurred) return true
|
||||
|
||||
@ -322,23 +309,17 @@ function BeforePlugin() {
|
||||
* On key down.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyDown(event, data, change, editor) {
|
||||
function onKeyDown(event, change, editor) {
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
const { key } = event
|
||||
|
||||
// When composing, these characters commit the composition but also move the
|
||||
// selection before we're able to handle it, so prevent their default,
|
||||
// selection-moving behavior.
|
||||
if (
|
||||
isComposing &&
|
||||
(key == 'ArrowLeft' || key == 'ArrowRight' || key == 'ArrowUp' || key == 'ArrowDown')
|
||||
) {
|
||||
if (isComposing && HOTKEYS.COMPOSING(event)) {
|
||||
event.preventDefault()
|
||||
return true
|
||||
}
|
||||
@ -349,59 +330,22 @@ function BeforePlugin() {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// Keep track of an `isShifting` flag, because it's often used to trigger
|
||||
// "Paste and Match Style" commands, but isn't available on the event in a
|
||||
// normal paste event.
|
||||
if (key == 'Shift') {
|
||||
isShifting = true
|
||||
}
|
||||
|
||||
// COMPAT: add the deprecated keyboard event properties.
|
||||
addDeprecatedKeyProperties(data, event)
|
||||
|
||||
debug('onKeyDown', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On key up.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onKeyUp(event, data, change, editor) {
|
||||
// COMPAT: add the deprecated keyboard event properties.
|
||||
addDeprecatedKeyProperties(data, event)
|
||||
|
||||
if (event.key == 'Shift') {
|
||||
isShifting = false
|
||||
}
|
||||
|
||||
debug('onKeyUp', { event })
|
||||
}
|
||||
|
||||
/**
|
||||
* On paste.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onPaste(event, data, change, editor) {
|
||||
function onPaste(event, change, editor) {
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
// Prevent defaults so the DOM state isn't corrupted.
|
||||
event.preventDefault()
|
||||
const d = getEventTransfer(event)
|
||||
|
||||
Object.keys(d).forEach((key) => {
|
||||
defineDeprecatedData(data, key, d[key])
|
||||
})
|
||||
|
||||
defineDeprecatedData(data, 'isShift', isShifting)
|
||||
|
||||
debug('onPaste', { event })
|
||||
}
|
||||
@ -410,84 +354,15 @@ function BeforePlugin() {
|
||||
* On select.
|
||||
*
|
||||
* @param {Event} event
|
||||
* @param {Object} data
|
||||
* @param {Change} change
|
||||
* @param {Editor} editor
|
||||
*/
|
||||
|
||||
function onSelect(event, data, change, editor) {
|
||||
function onSelect(event, change, editor) {
|
||||
if (isCopying) return true
|
||||
if (isComposing) return true
|
||||
if (editor.props.readOnly) return true
|
||||
|
||||
const window = getWindow(event.target)
|
||||
const { state } = change
|
||||
const { document, selection } = state
|
||||
const native = window.getSelection()
|
||||
|
||||
// If there are no ranges, the editor was blurred natively.
|
||||
if (!native.rangeCount) {
|
||||
defineDeprecatedData(data, 'selection', selection.blur())
|
||||
}
|
||||
|
||||
// Otherwise, determine the Slate selection from the native one.
|
||||
else {
|
||||
let range = findRange(native, state)
|
||||
if (!range) return true
|
||||
|
||||
const { anchorKey, anchorOffset, focusKey, focusOffset } = range
|
||||
const anchorText = document.getNode(anchorKey)
|
||||
const focusText = document.getNode(focusKey)
|
||||
const anchorInline = document.getClosestInline(anchorKey)
|
||||
const focusInline = document.getClosestInline(focusKey)
|
||||
const focusBlock = document.getClosestBlock(focusKey)
|
||||
const anchorBlock = document.getClosestBlock(anchorKey)
|
||||
|
||||
// COMPAT: If the anchor point is at the start of a non-void, and the
|
||||
// focus point is inside a void node with an offset that isn't `0`, set
|
||||
// the focus offset to `0`. This is due to void nodes <span>'s being
|
||||
// positioned off screen, resulting in the offset always being greater
|
||||
// than `0`. Since we can't know what it really should be, and since an
|
||||
// offset of `0` is less destructive because it creates a hanging
|
||||
// selection, go with `0`. (2017/09/07)
|
||||
if (
|
||||
anchorBlock &&
|
||||
!anchorBlock.isVoid &&
|
||||
anchorOffset == 0 &&
|
||||
focusBlock &&
|
||||
focusBlock.isVoid &&
|
||||
focusOffset != 0
|
||||
) {
|
||||
range = range.set('focusOffset', 0)
|
||||
}
|
||||
|
||||
// COMPAT: If the selection is at the end of a non-void inline node, and
|
||||
// there is a node after it, put it in the node after instead. This
|
||||
// standardizes the behavior, since it's indistinguishable to the user.
|
||||
if (
|
||||
anchorInline &&
|
||||
!anchorInline.isVoid &&
|
||||
anchorOffset == anchorText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(anchorKey)
|
||||
const next = block.getNextText(anchorKey)
|
||||
if (next) range = range.moveAnchorTo(next.key, 0)
|
||||
}
|
||||
|
||||
if (
|
||||
focusInline &&
|
||||
!focusInline.isVoid &&
|
||||
focusOffset == focusText.text.length
|
||||
) {
|
||||
const block = document.getClosestBlock(focusKey)
|
||||
const next = block.getNextText(focusKey)
|
||||
if (next) range = range.moveFocusTo(next.key, 0)
|
||||
}
|
||||
|
||||
range = range.normalize(document)
|
||||
defineDeprecatedData(data, 'selection', range)
|
||||
}
|
||||
|
||||
debug('onSelect', { event })
|
||||
}
|
||||
|
||||
@ -505,48 +380,20 @@ function BeforePlugin() {
|
||||
onCopy,
|
||||
onCut,
|
||||
onDragEnd,
|
||||
onDragEnter,
|
||||
onDragExit,
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDragStart,
|
||||
onDrop,
|
||||
onFocus,
|
||||
onInput,
|
||||
onKeyDown,
|
||||
onKeyUp,
|
||||
onPaste,
|
||||
onSelect,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated.
|
||||
*/
|
||||
|
||||
function defineDeprecatedData(data, key, value) {
|
||||
Object.defineProperty(data, key, {
|
||||
enumerable: true,
|
||||
get() {
|
||||
logger.deprecate('slate-react@0.5.0', `Accessing the \`data.${key}\` property is deprecated, please use the native \`event\` properties instead, or one of the newly exposed helper utilities.`)
|
||||
return value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function addDeprecatedKeyProperties(data, event) {
|
||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||
const name = keycode(which)
|
||||
defineDeprecatedData(data, 'code', which)
|
||||
defineDeprecatedData(data, 'key', name)
|
||||
defineDeprecatedData(data, 'isAlt', altKey)
|
||||
defineDeprecatedData(data, 'isCmd', IS_MAC ? metaKey && !altKey : false)
|
||||
defineDeprecatedData(data, 'isCtrl', ctrlKey && !altKey)
|
||||
defineDeprecatedData(data, 'isLine', IS_MAC ? metaKey : false)
|
||||
defineDeprecatedData(data, 'isMeta', metaKey)
|
||||
defineDeprecatedData(data, 'isMod', IS_MAC ? metaKey && !altKey : ctrlKey && !altKey)
|
||||
defineDeprecatedData(data, 'isModAlt', IS_MAC ? metaKey && altKey : ctrlKey && altKey)
|
||||
defineDeprecatedData(data, 'isShift', shiftKey)
|
||||
defineDeprecatedData(data, 'isWord', IS_MAC ? altKey : ctrlKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*
|
||||
|
@ -15,6 +15,8 @@ import findPoint from './find-point'
|
||||
|
||||
function findRange(native, state) {
|
||||
const el = native.anchorNode || native.startContainer
|
||||
if (!el) return null
|
||||
|
||||
const window = getWindow(el)
|
||||
|
||||
// If the `native` object is a DOM `Range` or `StaticRange` object, change it
|
||||
|
@ -3,6 +3,20 @@ import Base64 from 'slate-base64-serializer'
|
||||
|
||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
|
||||
/**
|
||||
* Trasnfer types.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const {
|
||||
FRAGMENT,
|
||||
HTML,
|
||||
NODE,
|
||||
RICH,
|
||||
TEXT
|
||||
} = TRANSFER_TYPES
|
||||
|
||||
/**
|
||||
* Fragment matching regexp for HTML nodes.
|
||||
*
|
||||
@ -24,11 +38,11 @@ function getEventTransfer(event) {
|
||||
}
|
||||
|
||||
const transfer = event.dataTransfer || event.clipboardData
|
||||
let fragment = getType(transfer, TRANSFER_TYPES.FRAGMENT)
|
||||
let node = getType(transfer, TRANSFER_TYPES.NODE)
|
||||
const html = getType(transfer, 'text/html')
|
||||
const rich = getType(transfer, 'text/rtf')
|
||||
let text = getType(transfer, 'text/plain')
|
||||
let fragment = getType(transfer, FRAGMENT)
|
||||
let node = getType(transfer, NODE)
|
||||
const html = getType(transfer, HTML)
|
||||
const rich = getType(transfer, RICH)
|
||||
let text = getType(transfer, TEXT)
|
||||
let files
|
||||
|
||||
// If there isn't a fragment, but there is HTML, check to see if the HTML is
|
||||
@ -48,9 +62,9 @@ function getEventTransfer(event) {
|
||||
if (text) {
|
||||
const embeddedTypes = getEmbeddedTypes(text)
|
||||
|
||||
if (embeddedTypes[TRANSFER_TYPES.FRAGMENT]) fragment = embeddedTypes[TRANSFER_TYPES.FRAGMENT]
|
||||
if (embeddedTypes[TRANSFER_TYPES.NODE]) node = embeddedTypes[TRANSFER_TYPES.NODE]
|
||||
if (embeddedTypes['text/plain']) text = embeddedTypes['text/plain']
|
||||
if (embeddedTypes[FRAGMENT]) fragment = embeddedTypes[FRAGMENT]
|
||||
if (embeddedTypes[NODE]) node = embeddedTypes[NODE]
|
||||
if (embeddedTypes[TEXT]) text = embeddedTypes[TEXT]
|
||||
}
|
||||
|
||||
// Decode a fragment or node if they exist.
|
||||
@ -91,8 +105,8 @@ function getEventTransfer(event) {
|
||||
function getEmbeddedTypes(text) {
|
||||
const prefix = 'SLATE-DATA-EMBED::'
|
||||
|
||||
if (text.substring(0, prefix.length) !== prefix) {
|
||||
return { 'text/plain': text }
|
||||
if (text.substring(0, prefix.length) != prefix) {
|
||||
return { TEXT: text }
|
||||
}
|
||||
|
||||
// Attempt to parse, if fails then just standard text/plain
|
||||
@ -141,7 +155,7 @@ function getType(transfer, type) {
|
||||
if (!transfer.types || !transfer.types.length) {
|
||||
// COMPAT: In IE 11, there is no `types` field but `getData('Text')`
|
||||
// is supported`. (2017/06/23)
|
||||
return type === 'text/plain' ? transfer.getData('Text') || null : null
|
||||
return type == TEXT ? transfer.getData('Text') || null : null
|
||||
}
|
||||
|
||||
return transfer.types.indexOf(type) !== -1 ? transfer.getData(type) || null : null
|
||||
|
@ -1,21 +1,43 @@
|
||||
|
||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||
|
||||
/**
|
||||
* Set data with `type` and `content` on a `dataTransfer` object.
|
||||
* The default plain text transfer type.
|
||||
*
|
||||
* @type {String}
|
||||
*/
|
||||
|
||||
const { TEXT } = TRANSFER_TYPES
|
||||
|
||||
/**
|
||||
* Set data with `type` and `content` on an `event`.
|
||||
*
|
||||
* COMPAT: In Edge, custom types throw errors, so embed all non-standard
|
||||
* types in text/plain compound object. (2017/7/12)
|
||||
*
|
||||
* @param {DataTransfer} dataTransfer
|
||||
* @param {Event} event
|
||||
* @param {String} type
|
||||
* @param {String} content
|
||||
*/
|
||||
|
||||
function setTransferData(dataTransfer, type, content) {
|
||||
function setEventTransfer(event, type, content) {
|
||||
const mime = TRANSFER_TYPES[type.toUpperCase()]
|
||||
|
||||
if (!mime) {
|
||||
throw new Error(`Cannot set unknown transfer type "${mime}"`)
|
||||
}
|
||||
|
||||
if (event.nativeEvent) {
|
||||
event = event.nativeEvent
|
||||
}
|
||||
|
||||
const transfer = event.dataTransfer || event.clipboardData
|
||||
|
||||
try {
|
||||
dataTransfer.setData(type, content)
|
||||
transfer.setData(mime, content)
|
||||
} catch (err) {
|
||||
const prefix = 'SLATE-DATA-EMBED::'
|
||||
const text = dataTransfer.getData('text/plain')
|
||||
const text = transfer.getData(TEXT)
|
||||
let obj = {}
|
||||
|
||||
// If the existing plain text data is prefixed, it's Slate JSON data.
|
||||
@ -29,12 +51,12 @@ function setTransferData(dataTransfer, type, content) {
|
||||
|
||||
// Otherwise, it's just set it as is.
|
||||
else {
|
||||
obj['text/plain'] = text
|
||||
obj[TEXT] = text
|
||||
}
|
||||
|
||||
obj[type] = content
|
||||
obj[mime] = content
|
||||
const string = `${prefix}${JSON.stringify(obj)}`
|
||||
dataTransfer.setData('text/plain', string)
|
||||
transfer.setData(TEXT, string)
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,4 +66,4 @@ function setTransferData(dataTransfer, type, content) {
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
export default setTransferData
|
||||
export default setEventTransfer
|
@ -23,7 +23,7 @@ export const state = (
|
||||
|
||||
export const output = `
|
||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
||||
<div data-slate-void="true">
|
||||
<div data-slate-void="true" draggable="true">
|
||||
<div style="height:0;color:transparent">
|
||||
<span>
|
||||
<span></span>
|
||||
|
@ -31,7 +31,7 @@ export const output = `
|
||||
<span data-slate-zero-width="true"> </span>
|
||||
</span>
|
||||
</span>
|
||||
<span data-slate-void="true">
|
||||
<span data-slate-void="true" draggable="true">
|
||||
<span style="height:0;color:transparent">
|
||||
<span>
|
||||
<span></span>
|
||||
|
Loading…
x
Reference in New Issue
Block a user