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,
|
findRange,
|
||||||
getEventRange,
|
getEventRange,
|
||||||
getEventTransfer,
|
getEventTransfer,
|
||||||
|
setEventTransfer,
|
||||||
} from 'slate-react'
|
} from 'slate-react'
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -41,9 +42,33 @@ Find the Slate range from a DOM `range` or `selection` and a Slate `state`.
|
|||||||
### `getEventRange`
|
### `getEventRange`
|
||||||
`getEventRange(event: DOMEvent, state: State) => Range`
|
`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`
|
||||||
`getEventTransfer(event: DOMEvent) => Object`
|
`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.
|
* On change, set the new checked value on the block.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onChange = (e) => {
|
onChange = (event) => {
|
||||||
const checked = e.target.checked
|
const checked = event.target.checked
|
||||||
const { editor, node } = this.props
|
const { editor, node } = this.props
|
||||||
editor.change(c => c.setNodeByKey(node.key, { data: { checked }}))
|
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,
|
* If backspace is pressed when collapsed at the start of a check list item,
|
||||||
* then turn it back into a paragraph.
|
* then turn it back into a paragraph.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {State|Void}
|
* @return {State|Void}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
|
|
||||||
if (
|
if (
|
||||||
e.key == 'Enter' &&
|
event.key == 'Enter' &&
|
||||||
state.startBlock.type == 'check-list-item'
|
state.startBlock.type == 'check-list-item'
|
||||||
) {
|
) {
|
||||||
return change
|
change.splitBlock().setBlock({ data: { checked: false }})
|
||||||
.splitBlock()
|
return true
|
||||||
.setBlock({ data: { checked: false }})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
e.key == 'Backspace' &&
|
event.key == 'Backspace' &&
|
||||||
state.isCollapsed &&
|
state.isCollapsed &&
|
||||||
state.startBlock.type == 'check-list-item' &&
|
state.startBlock.type == 'check-list-item' &&
|
||||||
state.selection.startOffset == 0
|
state.selection.startOffset == 0
|
||||||
) {
|
) {
|
||||||
return change
|
change.setBlock('paragraph')
|
||||||
.setBlock('paragraph')
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ function CodeBlock(props) {
|
|||||||
const { editor, node } = props
|
const { editor, node } = props
|
||||||
const language = node.data.get('language')
|
const language = node.data.get('language')
|
||||||
|
|
||||||
function onChange(e) {
|
function onChange(event) {
|
||||||
editor.change(c => c.setNodeByKey(node.key, { data: { language: e.target.value }}))
|
editor.change(c => c.setNodeByKey(node.key, { data: { language: event.target.value }}))
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -165,19 +165,19 @@ class CodeHighlighting extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On key down inside code blocks, insert soft new lines.
|
* On key down inside code blocks, insert soft new lines.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { startBlock } = state
|
const { startBlock } = state
|
||||||
if (e.key != 'Enter') return
|
if (event.key != 'Enter') return
|
||||||
if (startBlock.type != 'code') return
|
if (startBlock.type != 'code') return
|
||||||
if (state.isExpanded) change.delete()
|
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),
|
state: State.fromJSON(initialState),
|
||||||
schema: {
|
schema: {
|
||||||
nodes: {
|
nodes: {
|
||||||
'title': props => <h2 {...props.attrs}>{props.children}</h2>,
|
title: props => <h2 {...props.attrs}>{props.children}</h2>,
|
||||||
'paragraph': props => <p {...props.attrs}>{props.children}</p>
|
paragraph: props => <p {...props.attrs}>{props.children}</p>,
|
||||||
},
|
},
|
||||||
rules: [
|
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 */
|
/* 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',
|
match: (object) => {
|
||||||
validate: document => !document.nodes.size || document.nodes.first().type !== 'title' ? document.nodes : null,
|
return object.kind == 'document'
|
||||||
|
},
|
||||||
|
validate: (document) => {
|
||||||
|
return !document.nodes.size || document.nodes.first().type != 'title' ? document.nodes : null
|
||||||
|
},
|
||||||
normalize: (change, document, nodes) => {
|
normalize: (change, document, nodes) => {
|
||||||
if (!nodes.size) {
|
if (!nodes.size) {
|
||||||
const title = Block.create({ type: 'title', data: {}})
|
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 */
|
/* 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) => {
|
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
|
return invalidChildren.size ? invalidChildren : null
|
||||||
},
|
},
|
||||||
normalize: (change, document, invalidChildren) => {
|
normalize: (change, document, invalidChildren) => {
|
||||||
let updatedTransform = change
|
|
||||||
invalidChildren.forEach((child) => {
|
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 */
|
/* Rule that forces at least one paragraph, normalizes by inserting an empty paragraph */
|
||||||
|
|
||||||
{
|
{
|
||||||
match: node => node.kind === 'document',
|
match: (object) => {
|
||||||
validate: document => document.nodes.size < 2 ? true : null,
|
return object.kind == 'document'
|
||||||
|
},
|
||||||
|
validate: (document) => {
|
||||||
|
return document.nodes.size < 2 ? true : null
|
||||||
|
},
|
||||||
normalize: (change, document) => {
|
normalize: (change, document) => {
|
||||||
const paragraph = Block.create({ type: 'paragraph', data: {}})
|
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.
|
* Check if the current selection has a mark with `type` in it.
|
||||||
*
|
*
|
||||||
@ -31,22 +38,22 @@ function Menu({ menuRef, onChange, state }) {
|
|||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function hasMark(type) {
|
hasMark(type) {
|
||||||
|
const { state } = this.props
|
||||||
return state.activeMarks.some(mark => mark.type == type)
|
return state.activeMarks.some(mark => mark.type == type)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When a mark button is clicked, toggle the current mark.
|
* When a mark button is clicked, toggle the current mark.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {String} type
|
* @param {String} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onClickMark(e, type) {
|
onClickMark(event, type) {
|
||||||
e.preventDefault()
|
const { state, onChange } = this.props
|
||||||
const change = state
|
event.preventDefault()
|
||||||
.change()
|
const change = state.change().toggleMark(type)
|
||||||
.toggleMark(type)
|
|
||||||
onChange(change)
|
onChange(change)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,11 +65,9 @@ function Menu({ menuRef, onChange, state }) {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function renderMarkButton(type, icon) {
|
renderMarkButton(type, icon) {
|
||||||
const isActive = hasMark(type)
|
const isActive = this.hasMark(type)
|
||||||
function onMouseDown(e) {
|
const onMouseDown = event => this.onClickMark(event, type)
|
||||||
onClickMark(e, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||||
@ -71,16 +76,26 @@ function Menu({ menuRef, onChange, state }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
/**
|
||||||
ReactDOM.createPortal(
|
* Render.
|
||||||
<div className="menu hover-menu" ref={menuRef}>
|
*
|
||||||
{renderMarkButton('bold', 'format_bold')}
|
* @return {Element}
|
||||||
{renderMarkButton('italic', 'format_italic')}
|
*/
|
||||||
{renderMarkButton('underlined', 'format_underlined')}
|
|
||||||
{renderMarkButton('code', 'code')}
|
render() {
|
||||||
</div>, root
|
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.
|
* Render.
|
||||||
|
@ -23,14 +23,14 @@ for (let h = 0; h < HEADINGS; h++) {
|
|||||||
nodes.push({
|
nodes.push({
|
||||||
kind: 'block',
|
kind: 'block',
|
||||||
type: 'heading',
|
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++) {
|
for (let p = 0; p < PARAGRAPHS; p++) {
|
||||||
nodes.push({
|
nodes.push({
|
||||||
kind: 'block',
|
kind: 'block',
|
||||||
type: 'paragraph',
|
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.
|
* On clicking the image button, prompt for an image and insert it.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onClickImage = (e) => {
|
onClickImage = (event) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
const src = window.prompt('Enter the URL of the image:')
|
const src = window.prompt('Enter the URL of the image:')
|
||||||
if (!src) return
|
if (!src) return
|
||||||
|
|
||||||
@ -193,17 +193,16 @@ class Images extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On drop, insert the image wherever it is dropped.
|
* On drop, insert the image wherever it is dropped.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDropOrPaste = (e, data, change, editor) => {
|
onDropOrPaste = (event, change, editor) => {
|
||||||
const target = getEventRange(e)
|
const target = getEventRange(event)
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
|
||||||
const transfer = getEventTransfer(e)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, text, files } = transfer
|
const { type, text, files } = transfer
|
||||||
|
|
||||||
if (type == 'files') {
|
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.
|
* 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.
|
* Otherwise, add a new link with an href and text.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onClickLink = (e) => {
|
onClickLink = (event) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
const { state } = this.state
|
const { state } = this.state
|
||||||
const hasLinks = this.hasLinks()
|
const hasLinks = this.hasLinks()
|
||||||
const change = state.change()
|
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.
|
* On paste, if the text is a link, wrap the selection in a link.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onPaste = (e, data, change) => {
|
onPaste = (event, change) => {
|
||||||
if (change.state.isCollapsed) return
|
if (change.state.isCollapsed) return
|
||||||
|
|
||||||
const transfer = getEventTransfer(e)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, text } = transfer
|
const { type, text } = transfer
|
||||||
if (type != 'text' && type != 'html') return
|
if (type != 'text' && type != 'html') return
|
||||||
if (!isUrl(text)) return
|
if (!isUrl(text)) return
|
||||||
|
@ -99,16 +99,15 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On key down, check for our specific key shortcuts.
|
* On key down, check for our specific key shortcuts.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Data} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
switch (e.key) {
|
switch (event.key) {
|
||||||
case ' ': return this.onSpace(e, change)
|
case ' ': return this.onSpace(event, change)
|
||||||
case 'Backspace': return this.onBackspace(e, change)
|
case 'Backspace': return this.onBackspace(event, change)
|
||||||
case 'Enter': return this.onEnter(e, 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
|
* On space, if it was after an auto-markdown shortcut, convert the current
|
||||||
* node into the shortcut's corresponding type.
|
* node into the shortcut's corresponding type.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {State} change
|
* @param {State} change
|
||||||
* @return {State or Null} state
|
* @return {State or Null} state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onSpace = (e, change) => {
|
onSpace = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
if (state.isExpanded) return
|
if (state.isExpanded) return
|
||||||
|
|
||||||
@ -131,7 +130,7 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
|
|
||||||
if (!type) return
|
if (!type) return
|
||||||
if (type == 'list-item' && startBlock.type == 'list-item') return
|
if (type == 'list-item' && startBlock.type == 'list-item') return
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
change.setBlock(type)
|
change.setBlock(type)
|
||||||
|
|
||||||
@ -139,10 +138,7 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
change.wrapBlock('bulleted-list')
|
change.wrapBlock('bulleted-list')
|
||||||
}
|
}
|
||||||
|
|
||||||
change
|
change.extendToStartOf(startBlock).delete()
|
||||||
.extendToStartOf(startBlock)
|
|
||||||
.delete()
|
|
||||||
|
|
||||||
return true
|
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
|
* On backspace, if at the start of a non-paragraph, convert it back into a
|
||||||
* paragraph node.
|
* paragraph node.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {State} change
|
* @param {State} change
|
||||||
* @return {State or Null} state
|
* @return {State or Null} state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onBackspace = (e, change) => {
|
onBackspace = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
if (state.isExpanded) return
|
if (state.isExpanded) return
|
||||||
if (state.startOffset != 0) return
|
if (state.startOffset != 0) return
|
||||||
@ -163,7 +159,7 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
const { startBlock } = state
|
const { startBlock } = state
|
||||||
if (startBlock.type == 'paragraph') return
|
if (startBlock.type == 'paragraph') return
|
||||||
|
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
change.setBlock('paragraph')
|
change.setBlock('paragraph')
|
||||||
|
|
||||||
if (startBlock.type == 'list-item') {
|
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,
|
* On return, if at the end of a node type that should not be extended,
|
||||||
* create a new paragraph below it.
|
* create a new paragraph below it.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {State} change
|
* @param {State} change
|
||||||
* @return {State or Null} state
|
* @return {State or Null} state
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onEnter = (e, change) => {
|
onEnter = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
if (state.isExpanded) return
|
if (state.isExpanded) return
|
||||||
|
|
||||||
const { startBlock, startOffset, endOffset } = state
|
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 (endOffset != startBlock.text.length) return
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -202,12 +198,8 @@ class MarkdownShortcuts extends React.Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
|
change.splitBlock().setBlock('paragraph')
|
||||||
change
|
|
||||||
.splitBlock()
|
|
||||||
.setBlock('paragraph')
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,13 +174,12 @@ class PasteHtml extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On paste, deserialize the HTML and then insert the fragment.
|
* On paste, deserialize the HTML and then insert the fragment.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onPaste = (e, data, change) => {
|
onPaste = (event, change) => {
|
||||||
const transfer = getEventTransfer(e)
|
const transfer = getEventTransfer(event)
|
||||||
if (transfer.type != 'html') return
|
if (transfer.type != 'html') return
|
||||||
const { document } = serializer.deserialize(transfer.html)
|
const { document } = serializer.deserialize(transfer.html)
|
||||||
change.insertFragment(document)
|
change.insertFragment(document)
|
||||||
|
@ -114,28 +114,27 @@ class RichTextExample extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On key down, if it's a formatting command toggle a mark.
|
* On key down, if it's a formatting command toggle a mark.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
let mark
|
let mark
|
||||||
|
|
||||||
if (isBoldHotkey(e)) {
|
if (isBoldHotkey(event)) {
|
||||||
mark = 'bold'
|
mark = 'bold'
|
||||||
} else if (isItalicHotkey(e)) {
|
} else if (isItalicHotkey(event)) {
|
||||||
mark = 'italic'
|
mark = 'italic'
|
||||||
} else if (isUnderlinedHotkey(e)) {
|
} else if (isUnderlinedHotkey(event)) {
|
||||||
mark = 'underlined'
|
mark = 'underlined'
|
||||||
} else if (isCodeHotkey(e)) {
|
} else if (isCodeHotkey(event)) {
|
||||||
mark = 'code'
|
mark = 'code'
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
change.toggleMark(mark)
|
change.toggleMark(mark)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -143,12 +142,12 @@ class RichTextExample extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* When a mark button is clicked, toggle the current mark.
|
* When a mark button is clicked, toggle the current mark.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {String} type
|
* @param {String} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onClickMark = (e, type) => {
|
onClickMark = (event, type) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
const { state } = this.state
|
const { state } = this.state
|
||||||
const change = state.change().toggleMark(type)
|
const change = state.change().toggleMark(type)
|
||||||
this.onChange(change)
|
this.onChange(change)
|
||||||
@ -157,12 +156,12 @@ class RichTextExample extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* When a block button is clicked, toggle the block type.
|
* When a block button is clicked, toggle the block type.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {String} type
|
* @param {String} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onClickBlock = (e, type) => {
|
onClickBlock = (event, type) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
const { state } = this.state
|
const { state } = this.state
|
||||||
const change = state.change()
|
const change = state.change()
|
||||||
const { document } = state
|
const { document } = state
|
||||||
@ -258,7 +257,7 @@ class RichTextExample extends React.Component {
|
|||||||
|
|
||||||
renderMarkButton = (type, icon) => {
|
renderMarkButton = (type, icon) => {
|
||||||
const isActive = this.hasMark(type)
|
const isActive = this.hasMark(type)
|
||||||
const onMouseDown = e => this.onClickMark(e, type)
|
const onMouseDown = event => this.onClickMark(event, type)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
||||||
@ -277,7 +276,7 @@ class RichTextExample extends React.Component {
|
|||||||
|
|
||||||
renderBlockButton = (type, icon) => {
|
renderBlockButton = (type, icon) => {
|
||||||
const isActive = this.hasBlock(type)
|
const isActive = this.hasBlock(type)
|
||||||
const onMouseDown = e => this.onClickBlock(e, type)
|
const onMouseDown = event => this.onClickBlock(event, type)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
<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.
|
* On key down, if it's <shift-enter> add a soft break.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
if (e.key == 'Enter' && e.shiftKey) {
|
if (event.key == 'Enter' && event.shiftKey) {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
change.insertText('\n')
|
change.insertText('\n')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -50,12 +50,12 @@ class SearchHighlighting extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On input change, update the decorations.
|
* On input change, update the decorations.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onInputChange = (e) => {
|
onInputChange = (event) => {
|
||||||
const { state } = this.state
|
const { state } = this.state
|
||||||
const string = e.target.value
|
const string = event.target.value
|
||||||
const texts = state.document.getTexts()
|
const texts = state.document.getTexts()
|
||||||
const decorations = []
|
const decorations = []
|
||||||
|
|
||||||
|
@ -105,28 +105,27 @@ class SyncingEditor extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On key down, if it's a formatting command toggle a mark.
|
* On key down, if it's a formatting command toggle a mark.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @return {Change}
|
* @return {Change}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
let mark
|
let mark
|
||||||
|
|
||||||
if (isBoldHotkey(e)) {
|
if (isBoldHotkey(event)) {
|
||||||
mark = 'bold'
|
mark = 'bold'
|
||||||
} else if (isItalicHotkey(e)) {
|
} else if (isItalicHotkey(event)) {
|
||||||
mark = 'italic'
|
mark = 'italic'
|
||||||
} else if (isUnderlinedHotkey(e)) {
|
} else if (isUnderlinedHotkey(event)) {
|
||||||
mark = 'underlined'
|
mark = 'underlined'
|
||||||
} else if (isCodeHotkey(e)) {
|
} else if (isCodeHotkey(event)) {
|
||||||
mark = 'code'
|
mark = 'code'
|
||||||
} else {
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
change.toggleMark(mark)
|
change.toggleMark(mark)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -134,12 +133,12 @@ class SyncingEditor extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* When a mark button is clicked, toggle the current mark.
|
* When a mark button is clicked, toggle the current mark.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {String} type
|
* @param {String} type
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onClickMark = (e, type) => {
|
onClickMark = (event, type) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
const { state } = this.state
|
const { state } = this.state
|
||||||
const change = state.change().toggleMark(type)
|
const change = state.change().toggleMark(type)
|
||||||
this.onChange(change)
|
this.onChange(change)
|
||||||
@ -187,7 +186,7 @@ class SyncingEditor extends React.Component {
|
|||||||
|
|
||||||
renderButton = (type, icon) => {
|
renderButton = (type, icon) => {
|
||||||
const isActive = this.hasMark(type)
|
const isActive = this.hasMark(type)
|
||||||
const onMouseDown = e => this.onClickMark(e, type)
|
const onMouseDown = event => this.onClickMark(event, type)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="button" onMouseDown={onMouseDown} data-active={isActive}>
|
<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.
|
* On backspace, do nothing if at the start of a table cell.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onBackspace = (e, change) => {
|
onBackspace = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
if (state.startOffset != 0) return
|
if (state.startOffset != 0) return
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,38 +67,37 @@ class Tables extends React.Component {
|
|||||||
/**
|
/**
|
||||||
* On delete, do nothing if at the end of a table cell.
|
* On delete, do nothing if at the end of a table cell.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDelete = (e, change) => {
|
onDelete = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
if (state.endOffset != state.startText.text.length) return
|
if (state.endOffset != state.startText.text.length) return
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On return, do nothing if inside a table cell.
|
* On return, do nothing if inside a table cell.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onEnter = (e, change) => {
|
onEnter = (event, change) => {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On key down, check for our specific key shortcuts.
|
* On key down, check for our specific key shortcuts.
|
||||||
*
|
*
|
||||||
* @param {Event} e
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onKeyDown = (e, data, change) => {
|
onKeyDown = (event, change) => {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { document, selection } = state
|
const { document, selection } = state
|
||||||
const { startKey } = selection
|
const { startKey } = selection
|
||||||
@ -109,7 +108,7 @@ class Tables extends React.Component {
|
|||||||
const prevBlock = document.getClosestBlock(previous.key)
|
const prevBlock = document.getClosestBlock(previous.key)
|
||||||
|
|
||||||
if (prevBlock.type == 'table-cell') {
|
if (prevBlock.type == 'table-cell') {
|
||||||
e.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,10 +117,10 @@ class Tables extends React.Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (e.key) {
|
switch (event.key) {
|
||||||
case 'Backspace': return this.onBackspace(e, state)
|
case 'Backspace': return this.onBackspace(event, state)
|
||||||
case 'Delete': return this.onDelete(e, state)
|
case 'Delete': return this.onDelete(event, state)
|
||||||
case 'Enter': return this.onEnter(e, state)
|
case 'Enter': return this.onEnter(event, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,8 @@ import Node from './node'
|
|||||||
import findClosestNode from '../utils/find-closest-node'
|
import findClosestNode from '../utils/find-closest-node'
|
||||||
import findDOMRange from '../utils/find-dom-range'
|
import findDOMRange from '../utils/find-dom-range'
|
||||||
import findRange from '../utils/find-range'
|
import findRange from '../utils/find-range'
|
||||||
import getHtmlFromNativePaste from '../utils/get-html-from-native-paste'
|
|
||||||
import scrollToSelection from '../utils/scroll-to-selection'
|
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.
|
* Debug.
|
||||||
@ -229,18 +228,6 @@ class Content extends React.Component {
|
|||||||
this.tmp.key++
|
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
|
// If the `onSelect` handler fires while the `isUpdatingSelection` flag is
|
||||||
// set it's a result of updating the selection manually, so skip it.
|
// set it's a result of updating the selection manually, so skip it.
|
||||||
if (handler == 'onSelect' && this.tmp.isUpdatingSelection) {
|
if (handler == 'onSelect' && this.tmp.isUpdatingSelection) {
|
||||||
@ -274,7 +261,6 @@ class Content extends React.Component {
|
|||||||
handler == 'onCompositionStart' ||
|
handler == 'onCompositionStart' ||
|
||||||
handler == 'onCopy' ||
|
handler == 'onCopy' ||
|
||||||
handler == 'onCut' ||
|
handler == 'onCut' ||
|
||||||
handler == 'onDragStart' ||
|
|
||||||
handler == 'onFocus' ||
|
handler == 'onFocus' ||
|
||||||
handler == 'onInput' ||
|
handler == 'onInput' ||
|
||||||
handler == 'onKeyDown' ||
|
handler == 'onKeyDown' ||
|
||||||
@ -285,7 +271,7 @@ class Content extends React.Component {
|
|||||||
if (!this.isInEditor(event.target)) return
|
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
|
if (text == null) return
|
||||||
|
|
||||||
debug('onNativeBeforeInput', { event, text })
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
const { editor, state } = this.props
|
const { editor, state } = this.props
|
||||||
|
@ -1,13 +1,10 @@
|
|||||||
|
|
||||||
import Base64 from 'slate-base64-serializer'
|
|
||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import SlateTypes from 'slate-prop-types'
|
import SlateTypes from 'slate-prop-types'
|
||||||
import Types from 'prop-types'
|
import Types from 'prop-types'
|
||||||
|
|
||||||
import setTransferData from '../utils/set-transfer-data'
|
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug.
|
* Debug.
|
||||||
@ -56,68 +53,6 @@ class Void extends React.Component {
|
|||||||
debug(message, `${id}`, ...args)
|
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.
|
* Render.
|
||||||
*
|
*
|
||||||
@ -136,14 +71,7 @@ class Void extends React.Component {
|
|||||||
this.debug('render', { props })
|
this.debug('render', { props })
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tag
|
<Tag data-slate-void data-key={node.key} draggable={readOnly ? null : true}>
|
||||||
data-slate-void
|
|
||||||
data-key={node.key}
|
|
||||||
onClick={this.onClick}
|
|
||||||
onDragOver={this.onDragOver}
|
|
||||||
onDragEnter={this.onDragEnter}
|
|
||||||
onDragStart={this.onDragStart}
|
|
||||||
>
|
|
||||||
{!readOnly && <Tag style={style}>
|
{!readOnly && <Tag style={style}>
|
||||||
{this.renderText()}
|
{this.renderText()}
|
||||||
</Tag>}
|
</Tag>}
|
||||||
|
@ -8,11 +8,15 @@
|
|||||||
const EVENT_HANDLERS = [
|
const EVENT_HANDLERS = [
|
||||||
'onBeforeInput',
|
'onBeforeInput',
|
||||||
'onBlur',
|
'onBlur',
|
||||||
|
'onClick',
|
||||||
'onCompositionEnd',
|
'onCompositionEnd',
|
||||||
'onCompositionStart',
|
'onCompositionStart',
|
||||||
'onCopy',
|
'onCopy',
|
||||||
'onCut',
|
'onCut',
|
||||||
'onDragEnd',
|
'onDragEnd',
|
||||||
|
'onDragEnter',
|
||||||
|
'onDragExit',
|
||||||
|
'onDragLeave',
|
||||||
'onDragOver',
|
'onDragOver',
|
||||||
'onDragStart',
|
'onDragStart',
|
||||||
'onDrop',
|
'onDrop',
|
||||||
|
@ -38,6 +38,13 @@ const CONTENTEDITABLE = e => (
|
|||||||
UNDO(e)
|
UNDO(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const COMPOSING = e => (
|
||||||
|
e.key == 'ArrowDown' ||
|
||||||
|
e.key == 'ArrowLeft' ||
|
||||||
|
e.key == 'ArrowRight' ||
|
||||||
|
e.key == 'ArrowUp'
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export.
|
* Export.
|
||||||
*
|
*
|
||||||
@ -46,6 +53,7 @@ const CONTENTEDITABLE = e => (
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
BOLD,
|
BOLD,
|
||||||
|
COMPOSING,
|
||||||
CONTENTEDITABLE,
|
CONTENTEDITABLE,
|
||||||
DELETE_CHAR_BACKWARD,
|
DELETE_CHAR_BACKWARD,
|
||||||
DELETE_CHAR_FORWARD,
|
DELETE_CHAR_FORWARD,
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Slate-specific data transfer types.
|
* The transfer types that Slate recognizes.
|
||||||
*
|
*
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const TYPES = {
|
const TRANSFER_TYPES = {
|
||||||
FRAGMENT: 'application/x-slate-fragment',
|
FRAGMENT: 'application/x-slate-fragment',
|
||||||
|
HTML: 'text/html',
|
||||||
NODE: 'application/x-slate-node',
|
NODE: 'application/x-slate-node',
|
||||||
|
RICH: 'text/rtf',
|
||||||
|
TEXT: 'text/plain',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,4 +19,4 @@ const TYPES = {
|
|||||||
* @type {Object}
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default TYPES
|
export default TRANSFER_TYPES
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
|
|
||||||
import Editor from './components/editor'
|
import Editor from './components/editor'
|
||||||
import Placeholder from './components/placeholder'
|
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 findDOMNode from './utils/find-dom-node'
|
||||||
import findDOMRange from './utils/find-dom-range'
|
import findDOMRange from './utils/find-dom-range'
|
||||||
import findNode from './utils/find-node'
|
import findNode from './utils/find-node'
|
||||||
import findRange from './utils/find-range'
|
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.
|
* Export.
|
||||||
@ -17,21 +18,23 @@ import findRange from './utils/find-range'
|
|||||||
export {
|
export {
|
||||||
Editor,
|
Editor,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
getEventRange,
|
|
||||||
getEventTransfer,
|
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
findDOMRange,
|
findDOMRange,
|
||||||
findNode,
|
findNode,
|
||||||
findRange,
|
findRange,
|
||||||
|
getEventRange,
|
||||||
|
getEventTransfer,
|
||||||
|
setEventTransfer,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Editor,
|
Editor,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
getEventRange,
|
|
||||||
getEventTransfer,
|
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
findDOMRange,
|
findDOMRange,
|
||||||
findNode,
|
findNode,
|
||||||
findRange,
|
findRange,
|
||||||
|
getEventRange,
|
||||||
|
getEventTransfer,
|
||||||
|
setEventTransfer,
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ import HOTKEYS from '../constants/hotkeys'
|
|||||||
import Content from '../components/content'
|
import Content from '../components/content'
|
||||||
import Placeholder from '../components/placeholder'
|
import Placeholder from '../components/placeholder'
|
||||||
import findDOMNode from '../utils/find-dom-node'
|
import findDOMNode from '../utils/find-dom-node'
|
||||||
|
import findNode from '../utils/find-node'
|
||||||
import findPoint from '../utils/find-point'
|
import findPoint from '../utils/find-point'
|
||||||
import findRange from '../utils/find-range'
|
import findRange from '../utils/find-range'
|
||||||
import getEventRange from '../utils/get-event-range'
|
import getEventRange from '../utils/get-event-range'
|
||||||
import getEventTransfer from '../utils/get-event-transfer'
|
import getEventTransfer from '../utils/get-event-transfer'
|
||||||
|
import setEventTransfer from '../utils/set-event-transfer'
|
||||||
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,21 +62,23 @@ function AfterPlugin(options = {}) {
|
|||||||
// normalize changes to the document, not selection.
|
// normalize changes to the document, not selection.
|
||||||
if (prevState && state.document == prevState.document) return
|
if (prevState && state.document == prevState.document) return
|
||||||
|
|
||||||
|
debug('onBeforeChange')
|
||||||
|
|
||||||
change.normalize(coreSchema)
|
change.normalize(coreSchema)
|
||||||
change.normalize(schema)
|
change.normalize(schema)
|
||||||
debug('onBeforeChange')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On before input, correct any browser inconsistencies.
|
* On before input, correct any browser inconsistencies.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBeforeInput(event, data, change) {
|
function onBeforeInput(event, change, editor) {
|
||||||
debug('onBeforeInput', { data })
|
debug('onBeforeInput', { event })
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
change.insertText(event.data)
|
change.insertText(event.data)
|
||||||
}
|
}
|
||||||
@ -83,40 +87,69 @@ function AfterPlugin(options = {}) {
|
|||||||
* On blur.
|
* On blur.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBlur(event, data, change) {
|
function onBlur(event, change, editor) {
|
||||||
debug('onBlur', { data })
|
debug('onBlur', { event })
|
||||||
|
|
||||||
change.blur()
|
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.
|
* On copy.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCopy(event, data, change) {
|
function onCopy(event, change, editor) {
|
||||||
debug('onCopy', data)
|
debug('onCopy', { event })
|
||||||
onCutOrCopy(event, data, change)
|
|
||||||
|
onCutOrCopy(event, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On cut.
|
* On cut.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCut(event, data, change, editor) {
|
function onCut(event, change, editor) {
|
||||||
debug('onCut', data)
|
debug('onCut', { event })
|
||||||
onCutOrCopy(event, data, change)
|
|
||||||
|
onCutOrCopy(event, change)
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
|
|
||||||
// Once the fake cut content has successfully been added to the clipboard,
|
// Once the fake cut content has successfully been added to the clipboard,
|
||||||
@ -130,11 +163,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On cut or copy.
|
* On cut or copy.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCutOrCopy(event, data, change) {
|
function onCutOrCopy(event, change, editor) {
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
const native = window.getSelection()
|
const native = window.getSelection()
|
||||||
const { state } = change
|
const { state } = change
|
||||||
@ -242,12 +275,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On drag end.
|
* On drag end.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragEnd(event, data, change, editor) {
|
function onDragEnd(event, change, editor) {
|
||||||
debug('onDragEnd', { event })
|
debug('onDragEnd', { event })
|
||||||
|
|
||||||
isDraggingInternally = null
|
isDraggingInternally = null
|
||||||
@ -257,12 +289,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On drag over.
|
* On drag over.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragOver(event, data, change, editor) {
|
function onDragOver(event, change, editor) {
|
||||||
debug('onDragOver', { event })
|
debug('onDragOver', { event })
|
||||||
|
|
||||||
isDraggingInternally = false
|
isDraggingInternally = false
|
||||||
@ -272,26 +303,39 @@ function AfterPlugin(options = {}) {
|
|||||||
* On drag start.
|
* On drag start.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragStart(event, data, change, editor) {
|
function onDragStart(event, change, editor) {
|
||||||
debug('onDragStart', { event })
|
debug('onDragStart', { event })
|
||||||
|
|
||||||
isDraggingInternally = true
|
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.
|
* On drop.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDrop(event, data, change, editor) {
|
function onDrop(event, change, editor) {
|
||||||
debug('onDrop', { event })
|
debug('onDrop', { event })
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
@ -363,12 +407,12 @@ function AfterPlugin(options = {}) {
|
|||||||
* On input.
|
* On input.
|
||||||
*
|
*
|
||||||
* @param {Event} eventvent
|
* @param {Event} eventvent
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @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 window = getWindow(event.target)
|
||||||
const { state } = change
|
const { state } = change
|
||||||
|
|
||||||
@ -428,21 +472,21 @@ function AfterPlugin(options = {}) {
|
|||||||
* On key down.
|
* On key down.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDown(event, data, change) {
|
function onKeyDown(event, change, editor) {
|
||||||
debug('onKeyDown', { data })
|
debug('onKeyDown', { event })
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Enter': return onKeyDownEnter(event, data, change)
|
case 'Enter': return onKeyDownEnter(event, change)
|
||||||
case 'Backspace': return onKeyDownBackspace(event, data, change)
|
case 'Backspace': return onKeyDownBackspace(event, change)
|
||||||
case 'Delete': return onKeyDownDelete(event, data, change)
|
case 'Delete': return onKeyDownDelete(event, change)
|
||||||
case 'ArrowLeft': return onKeyDownLeft(event, data, change)
|
case 'ArrowLeft': return onKeyDownLeft(event, change)
|
||||||
case 'ArrowRight': return onKeyDownRight(event, data, change)
|
case 'ArrowRight': return onKeyDownRight(event, change)
|
||||||
case 'ArrowUp': return onKeyDownUp(event, data, change)
|
case 'ArrowUp': return onKeyDownUp(event, change)
|
||||||
case 'ArrowDown': return onKeyDownDown(event, data, change)
|
case 'ArrowDown': return onKeyDownDown(event, change)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HOTKEYS.DELETE_CHAR_BACKWARD(event)) {
|
if (HOTKEYS.DELETE_CHAR_BACKWARD(event)) {
|
||||||
@ -470,11 +514,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On `enter` key down, split the current block in half.
|
* On `enter` key down, split the current block in half.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDownEnter(event, data, change) {
|
function onKeyDownEnter(event, change, editor) {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { document, startKey } = state
|
const { document, startKey } = state
|
||||||
const hasVoidParent = document.hasVoidParent(startKey)
|
const hasVoidParent = document.hasVoidParent(startKey)
|
||||||
@ -495,11 +539,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On `backspace` key down, delete backwards.
|
* On `backspace` key down, delete backwards.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @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 isWord = IS_MAC ? event.altKey : event.ctrlKey
|
||||||
const isLine = IS_MAC ? event.metaKey : false
|
const isLine = IS_MAC ? event.metaKey : false
|
||||||
|
|
||||||
@ -514,11 +558,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* On `delete` key down, delete forwards.
|
* On `delete` key down, delete forwards.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @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 isWord = IS_MAC ? event.altKey : event.ctrlKey
|
||||||
const isLine = IS_MAC ? event.metaKey : false
|
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.
|
* the zero-width spaces will cause two arrow keys to jump to the next text.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDownLeft(event, data, change) {
|
function onKeyDownLeft(event, change, editor) {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
|
|
||||||
if (event.ctrlKey) return
|
if (event.ctrlKey) return
|
||||||
@ -596,11 +640,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* selection to the very start of an inline node here. (2016/11/29)
|
* selection to the very start of an inline node here. (2016/11/29)
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDownRight(event, data, change) {
|
function onKeyDownRight(event, change, editor) {
|
||||||
const { state } = change
|
const { state } = change
|
||||||
|
|
||||||
if (event.ctrlKey) return
|
if (event.ctrlKey) return
|
||||||
@ -650,11 +694,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* Firefox, option-up doesn't properly move the selection.
|
* Firefox, option-up doesn't properly move the selection.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDownUp(event, data, change) {
|
function onKeyDownUp(event, change, editor) {
|
||||||
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
@ -679,11 +723,11 @@ function AfterPlugin(options = {}) {
|
|||||||
* Firefox, option-down doesn't properly move the selection.
|
* Firefox, option-down doesn't properly move the selection.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDownDown(event, data, change) {
|
function onKeyDownDown(event, change, editor) {
|
||||||
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
if (!IS_MAC || event.ctrlKey || !event.altKey) return
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
@ -704,12 +748,12 @@ function AfterPlugin(options = {}) {
|
|||||||
* On paste.
|
* On paste.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onPaste(event, data, change) {
|
function onPaste(event, change, editor) {
|
||||||
debug('onPaste', { data })
|
debug('onPaste', { event })
|
||||||
|
|
||||||
const transfer = getEventTransfer(event)
|
const transfer = getEventTransfer(event)
|
||||||
const { type, fragment, text } = transfer
|
const { type, fragment, text } = transfer
|
||||||
@ -734,12 +778,12 @@ function AfterPlugin(options = {}) {
|
|||||||
* On select.
|
* On select.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onSelect(event, data, change) {
|
function onSelect(event, change, editor) {
|
||||||
debug('onSelect', { data })
|
debug('onSelect', { event })
|
||||||
|
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
const { state } = change
|
const { state } = change
|
||||||
@ -916,6 +960,7 @@ function AfterPlugin(options = {}) {
|
|||||||
onBeforeChange,
|
onBeforeChange,
|
||||||
onBeforeInput,
|
onBeforeInput,
|
||||||
onBlur,
|
onBlur,
|
||||||
|
onClick,
|
||||||
onCopy,
|
onCopy,
|
||||||
onCut,
|
onCut,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
|
@ -1,18 +1,10 @@
|
|||||||
|
|
||||||
import Base64 from 'slate-base64-serializer'
|
|
||||||
import Debug from 'debug'
|
import Debug from 'debug'
|
||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
import keycode from 'keycode'
|
|
||||||
import logger from 'slate-dev-logger'
|
|
||||||
import { findDOMNode } from 'react-dom'
|
import { findDOMNode } from 'react-dom'
|
||||||
|
|
||||||
import HOTKEYS from '../constants/hotkeys'
|
import HOTKEYS from '../constants/hotkeys'
|
||||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
import { IS_FIREFOX, SUPPORTED_EVENTS } from '../constants/environment'
|
||||||
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'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Debug.
|
* Debug.
|
||||||
@ -33,19 +25,16 @@ function BeforePlugin() {
|
|||||||
let isComposing = false
|
let isComposing = false
|
||||||
let isCopying = false
|
let isCopying = false
|
||||||
let isDragging = false
|
let isDragging = false
|
||||||
let isShifting = false
|
|
||||||
let isInternalDrag = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On before input.
|
* On before input.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBeforeInput(event, data, change, editor) {
|
function onBeforeInput(event, change, editor) {
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
// COMPAT: React's `onBeforeInput` synthetic event is based on the native
|
// COMPAT: React's `onBeforeInput` synthetic event is based on the native
|
||||||
@ -63,12 +52,11 @@ function BeforePlugin() {
|
|||||||
* On blur.
|
* On blur.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onBlur(event, data, change, editor) {
|
function onBlur(event, change, editor) {
|
||||||
if (isCopying) return true
|
if (isCopying) return true
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
@ -86,12 +74,11 @@ function BeforePlugin() {
|
|||||||
* On composition end.
|
* On composition end.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCompositionEnd(event, data, change, editor) {
|
function onCompositionEnd(event, change, editor) {
|
||||||
const n = compositionCount
|
const n = compositionCount
|
||||||
|
|
||||||
// The `count` check here ensures that if another composition starts
|
// The `count` check here ensures that if another composition starts
|
||||||
@ -109,12 +96,11 @@ function BeforePlugin() {
|
|||||||
* On composition start.
|
* On composition start.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCompositionStart(event, data, change, editor) {
|
function onCompositionStart(event, change, editor) {
|
||||||
isComposing = true
|
isComposing = true
|
||||||
compositionCount++
|
compositionCount++
|
||||||
|
|
||||||
@ -125,20 +111,15 @@ function BeforePlugin() {
|
|||||||
* On copy.
|
* On copy.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCopy(event, data, change, editor) {
|
function onCopy(event, change, editor) {
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
isCopying = true
|
isCopying = true
|
||||||
window.requestAnimationFrame(() => isCopying = false)
|
window.requestAnimationFrame(() => isCopying = false)
|
||||||
|
|
||||||
const { state } = change
|
|
||||||
defineDeprecatedData(data, 'type', 'fragment')
|
|
||||||
defineDeprecatedData(data, 'fragment', state.fragment)
|
|
||||||
|
|
||||||
debug('onCopy', { event })
|
debug('onCopy', { event })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,22 +127,17 @@ function BeforePlugin() {
|
|||||||
* On cut.
|
* On cut.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onCut(event, data, change, editor) {
|
function onCut(event, change, editor) {
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
const window = getWindow(event.target)
|
const window = getWindow(event.target)
|
||||||
isCopying = true
|
isCopying = true
|
||||||
window.requestAnimationFrame(() => isCopying = false)
|
window.requestAnimationFrame(() => isCopying = false)
|
||||||
|
|
||||||
const { state } = change
|
|
||||||
defineDeprecatedData(data, 'type', 'fragment')
|
|
||||||
defineDeprecatedData(data, 'fragment', state.fragment)
|
|
||||||
|
|
||||||
debug('onCut', { event })
|
debug('onCut', { event })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,33 +145,84 @@ function BeforePlugin() {
|
|||||||
* On drag end.
|
* On drag end.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @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()
|
event.stopPropagation()
|
||||||
|
|
||||||
isDragging = false
|
isDragging = false
|
||||||
isInternalDrag = null
|
|
||||||
|
|
||||||
debug('onDragEnd', { event })
|
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.
|
* On drag over.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDragOver(event, data, change, editor) {
|
function onDragOver(event, change, editor) {
|
||||||
if (isDragging) return true
|
// Stop propagation so the event isn't visible to parent editors.
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
|
// If a drag is already in progress, don't do this again.
|
||||||
|
if (!isDragging) return true
|
||||||
|
|
||||||
isDragging = 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 })
|
debug('onDragOver', { event })
|
||||||
}
|
}
|
||||||
@ -204,29 +231,15 @@ function BeforePlugin() {
|
|||||||
* On drag start.
|
* On drag start.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @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
|
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 })
|
debug('onDragStart', { event })
|
||||||
}
|
}
|
||||||
@ -235,43 +248,19 @@ function BeforePlugin() {
|
|||||||
* On drop.
|
* On drop.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @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.stopPropagation()
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
|
// Nothing happens in read-only mode.
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
const { state } = change
|
// Prevent default so the DOM's state isn't corrupted.
|
||||||
const { nativeEvent } = event
|
event.preventDefault()
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
debug('onDrop', { event })
|
debug('onDrop', { event })
|
||||||
}
|
}
|
||||||
@ -280,12 +269,11 @@ function BeforePlugin() {
|
|||||||
* On focus.
|
* On focus.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onFocus(event, data, change, editor) {
|
function onFocus(event, change, editor) {
|
||||||
if (isCopying) return true
|
if (isCopying) return true
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
@ -306,12 +294,11 @@ function BeforePlugin() {
|
|||||||
* On input.
|
* On input.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onInput(event, data, change, editor) {
|
function onInput(event, change, editor) {
|
||||||
if (isComposing) return true
|
if (isComposing) return true
|
||||||
if (change.state.isBlurred) return true
|
if (change.state.isBlurred) return true
|
||||||
|
|
||||||
@ -322,23 +309,17 @@ function BeforePlugin() {
|
|||||||
* On key down.
|
* On key down.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onKeyDown(event, data, change, editor) {
|
function onKeyDown(event, change, editor) {
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
const { key } = event
|
|
||||||
|
|
||||||
// When composing, these characters commit the composition but also move the
|
// When composing, these characters commit the composition but also move the
|
||||||
// selection before we're able to handle it, so prevent their default,
|
// selection before we're able to handle it, so prevent their default,
|
||||||
// selection-moving behavior.
|
// selection-moving behavior.
|
||||||
if (
|
if (isComposing && HOTKEYS.COMPOSING(event)) {
|
||||||
isComposing &&
|
|
||||||
(key == 'ArrowLeft' || key == 'ArrowRight' || key == 'ArrowUp' || key == 'ArrowDown')
|
|
||||||
) {
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -349,59 +330,22 @@ function BeforePlugin() {
|
|||||||
event.preventDefault()
|
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 })
|
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.
|
* On paste.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onPaste(event, data, change, editor) {
|
function onPaste(event, change, editor) {
|
||||||
if (editor.props.readOnly) return true
|
if (editor.props.readOnly) return true
|
||||||
|
|
||||||
|
// Prevent defaults so the DOM state isn't corrupted.
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const d = getEventTransfer(event)
|
|
||||||
|
|
||||||
Object.keys(d).forEach((key) => {
|
|
||||||
defineDeprecatedData(data, key, d[key])
|
|
||||||
})
|
|
||||||
|
|
||||||
defineDeprecatedData(data, 'isShift', isShifting)
|
|
||||||
|
|
||||||
debug('onPaste', { event })
|
debug('onPaste', { event })
|
||||||
}
|
}
|
||||||
@ -410,84 +354,15 @@ function BeforePlugin() {
|
|||||||
* On select.
|
* On select.
|
||||||
*
|
*
|
||||||
* @param {Event} event
|
* @param {Event} event
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onSelect(event, data, change, editor) {
|
function onSelect(event, change, editor) {
|
||||||
if (isCopying) return true
|
if (isCopying) return true
|
||||||
if (isComposing) return true
|
if (isComposing) return true
|
||||||
if (editor.props.readOnly) 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 })
|
debug('onSelect', { event })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,48 +380,20 @@ function BeforePlugin() {
|
|||||||
onCopy,
|
onCopy,
|
||||||
onCut,
|
onCut,
|
||||||
onDragEnd,
|
onDragEnd,
|
||||||
|
onDragEnter,
|
||||||
|
onDragExit,
|
||||||
|
onDragLeave,
|
||||||
onDragOver,
|
onDragOver,
|
||||||
onDragStart,
|
onDragStart,
|
||||||
onDrop,
|
onDrop,
|
||||||
onFocus,
|
onFocus,
|
||||||
onInput,
|
onInput,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
onKeyUp,
|
|
||||||
onPaste,
|
onPaste,
|
||||||
onSelect,
|
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.
|
* Export.
|
||||||
*
|
*
|
||||||
|
@ -15,6 +15,8 @@ import findPoint from './find-point'
|
|||||||
|
|
||||||
function findRange(native, state) {
|
function findRange(native, state) {
|
||||||
const el = native.anchorNode || native.startContainer
|
const el = native.anchorNode || native.startContainer
|
||||||
|
if (!el) return null
|
||||||
|
|
||||||
const window = getWindow(el)
|
const window = getWindow(el)
|
||||||
|
|
||||||
// If the `native` object is a DOM `Range` or `StaticRange` object, change it
|
// 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'
|
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.
|
* Fragment matching regexp for HTML nodes.
|
||||||
*
|
*
|
||||||
@ -24,11 +38,11 @@ function getEventTransfer(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transfer = event.dataTransfer || event.clipboardData
|
const transfer = event.dataTransfer || event.clipboardData
|
||||||
let fragment = getType(transfer, TRANSFER_TYPES.FRAGMENT)
|
let fragment = getType(transfer, FRAGMENT)
|
||||||
let node = getType(transfer, TRANSFER_TYPES.NODE)
|
let node = getType(transfer, NODE)
|
||||||
const html = getType(transfer, 'text/html')
|
const html = getType(transfer, HTML)
|
||||||
const rich = getType(transfer, 'text/rtf')
|
const rich = getType(transfer, RICH)
|
||||||
let text = getType(transfer, 'text/plain')
|
let text = getType(transfer, TEXT)
|
||||||
let files
|
let files
|
||||||
|
|
||||||
// If there isn't a fragment, but there is HTML, check to see if the HTML is
|
// 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) {
|
if (text) {
|
||||||
const embeddedTypes = getEmbeddedTypes(text)
|
const embeddedTypes = getEmbeddedTypes(text)
|
||||||
|
|
||||||
if (embeddedTypes[TRANSFER_TYPES.FRAGMENT]) fragment = embeddedTypes[TRANSFER_TYPES.FRAGMENT]
|
if (embeddedTypes[FRAGMENT]) fragment = embeddedTypes[FRAGMENT]
|
||||||
if (embeddedTypes[TRANSFER_TYPES.NODE]) node = embeddedTypes[TRANSFER_TYPES.NODE]
|
if (embeddedTypes[NODE]) node = embeddedTypes[NODE]
|
||||||
if (embeddedTypes['text/plain']) text = embeddedTypes['text/plain']
|
if (embeddedTypes[TEXT]) text = embeddedTypes[TEXT]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode a fragment or node if they exist.
|
// Decode a fragment or node if they exist.
|
||||||
@ -91,8 +105,8 @@ function getEventTransfer(event) {
|
|||||||
function getEmbeddedTypes(text) {
|
function getEmbeddedTypes(text) {
|
||||||
const prefix = 'SLATE-DATA-EMBED::'
|
const prefix = 'SLATE-DATA-EMBED::'
|
||||||
|
|
||||||
if (text.substring(0, prefix.length) !== prefix) {
|
if (text.substring(0, prefix.length) != prefix) {
|
||||||
return { 'text/plain': text }
|
return { TEXT: text }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to parse, if fails then just standard text/plain
|
// Attempt to parse, if fails then just standard text/plain
|
||||||
@ -141,7 +155,7 @@ function getType(transfer, type) {
|
|||||||
if (!transfer.types || !transfer.types.length) {
|
if (!transfer.types || !transfer.types.length) {
|
||||||
// COMPAT: In IE 11, there is no `types` field but `getData('Text')`
|
// COMPAT: In IE 11, there is no `types` field but `getData('Text')`
|
||||||
// is supported`. (2017/06/23)
|
// 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
|
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
|
* COMPAT: In Edge, custom types throw errors, so embed all non-standard
|
||||||
* types in text/plain compound object. (2017/7/12)
|
* types in text/plain compound object. (2017/7/12)
|
||||||
*
|
*
|
||||||
* @param {DataTransfer} dataTransfer
|
* @param {Event} event
|
||||||
* @param {String} type
|
* @param {String} type
|
||||||
* @param {String} content
|
* @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 {
|
try {
|
||||||
dataTransfer.setData(type, content)
|
transfer.setData(mime, content)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const prefix = 'SLATE-DATA-EMBED::'
|
const prefix = 'SLATE-DATA-EMBED::'
|
||||||
const text = dataTransfer.getData('text/plain')
|
const text = transfer.getData(TEXT)
|
||||||
let obj = {}
|
let obj = {}
|
||||||
|
|
||||||
// If the existing plain text data is prefixed, it's Slate JSON data.
|
// 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.
|
// Otherwise, it's just set it as is.
|
||||||
else {
|
else {
|
||||||
obj['text/plain'] = text
|
obj[TEXT] = text
|
||||||
}
|
}
|
||||||
|
|
||||||
obj[type] = content
|
obj[mime] = content
|
||||||
const string = `${prefix}${JSON.stringify(obj)}`
|
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}
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default setTransferData
|
export default setEventTransfer
|
@ -23,7 +23,7 @@ export const state = (
|
|||||||
|
|
||||||
export const output = `
|
export const output = `
|
||||||
<div data-slate-editor="true" contenteditable="true" role="textbox">
|
<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">
|
<div style="height:0;color:transparent">
|
||||||
<span>
|
<span>
|
||||||
<span></span>
|
<span></span>
|
||||||
|
@ -31,7 +31,7 @@ export const output = `
|
|||||||
<span data-slate-zero-width="true"> </span>
|
<span data-slate-zero-width="true"> </span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span data-slate-void="true">
|
<span data-slate-void="true" draggable="true">
|
||||||
<span style="height:0;color:transparent">
|
<span style="height:0;color:transparent">
|
||||||
<span>
|
<span>
|
||||||
<span></span>
|
<span></span>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user