2016-06-15 12:07:12 -07:00
|
|
|
|
2016-07-27 13:33:36 -07:00
|
|
|
import Base64 from '../serializers/base-64'
|
2016-07-27 11:38:57 -07:00
|
|
|
import Character from '../models/character'
|
2016-07-11 18:36:45 -07:00
|
|
|
import Placeholder from '../components/placeholder'
|
2016-06-17 19:57:37 -07:00
|
|
|
import React from 'react'
|
2016-07-14 13:42:10 -07:00
|
|
|
import String from '../utils/string'
|
2016-06-15 12:07:12 -07:00
|
|
|
|
2016-07-06 14:48:40 -07:00
|
|
|
/**
|
2016-07-11 18:36:45 -07:00
|
|
|
* The default plugin.
|
2016-07-06 20:19:19 -07:00
|
|
|
*
|
2016-07-11 18:36:45 -07:00
|
|
|
* @param {Object} options
|
2016-07-11 19:24:10 -07:00
|
|
|
* @property {Element} placeholder
|
|
|
|
* @property {String} placeholderClassName
|
|
|
|
* @property {Object} placeholderStyle
|
2016-07-11 18:36:45 -07:00
|
|
|
* @return {Object}
|
2016-07-06 14:48:40 -07:00
|
|
|
*/
|
|
|
|
|
2016-07-11 18:36:45 -07:00
|
|
|
function Plugin(options = {}) {
|
2016-07-11 19:24:10 -07:00
|
|
|
const {
|
|
|
|
placeholder,
|
|
|
|
placeholderClassName,
|
|
|
|
placeholderStyle
|
|
|
|
} = options
|
2016-06-24 10:46:01 -07:00
|
|
|
|
2016-06-15 12:07:12 -07:00
|
|
|
/**
|
2016-07-27 12:54:04 -07:00
|
|
|
* The default block renderer.
|
2016-06-15 12:07:12 -07:00
|
|
|
*
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {Object} props
|
|
|
|
* @return {Element}
|
2016-06-15 12:07:12 -07:00
|
|
|
*/
|
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
function DEFAULT_BLOCK(props) {
|
|
|
|
return (
|
|
|
|
<div {...props.attributes} style={{ position: 'relative' }}>
|
|
|
|
{props.children}
|
|
|
|
{placeholder
|
|
|
|
? <Placeholder
|
|
|
|
className={placeholderClassName}
|
|
|
|
node={props.node}
|
|
|
|
parent={props.state.document}
|
|
|
|
state={props.state}
|
|
|
|
style={placeholderStyle}
|
|
|
|
>
|
|
|
|
{placeholder}
|
|
|
|
</Placeholder>
|
|
|
|
: null}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The default inline renderer.
|
|
|
|
*
|
|
|
|
* @param {Object} props
|
|
|
|
* @return {Element}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function DEFAULT_INLINE(props) {
|
|
|
|
return (
|
|
|
|
<span {...props.attributes} style={{ position: 'relative' }}>
|
|
|
|
{props.children}
|
|
|
|
</span>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On before input, see if we can let the browser continue with it's native
|
|
|
|
* input behavior, to avoid a re-render for performance.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 13:12:19 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @param {Editor} editor
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 13:12:19 -07:00
|
|
|
function onBeforeInput(e, data, state, editor) {
|
2016-07-27 12:54:04 -07:00
|
|
|
const { renderDecorations } = editor
|
|
|
|
const { startOffset, startText, startBlock } = state
|
|
|
|
|
|
|
|
// Determine what the characters would be if natively inserted.
|
|
|
|
const prev = startText.getDecoratedCharacters(startBlock, renderDecorations)
|
|
|
|
const char = prev.get(startOffset)
|
|
|
|
const chars = prev
|
|
|
|
.slice(0, startOffset)
|
|
|
|
.push(Character.create({ text: e.data, marks: char && char.marks }))
|
|
|
|
.concat(prev.slice(startOffset))
|
|
|
|
|
|
|
|
// Determine what the characters should be, if not natively inserted.
|
|
|
|
let next = state
|
|
|
|
.transform()
|
|
|
|
.insertText(e.data)
|
|
|
|
.apply()
|
|
|
|
|
|
|
|
const nextText = next.startText
|
|
|
|
const nextBlock = next.startBlock
|
|
|
|
const nextChars = nextText.getDecoratedCharacters(nextBlock, renderDecorations)
|
|
|
|
|
|
|
|
// We do not have to re-render if the current selection is collapsed, the
|
|
|
|
// current node is not empty, there are no marks on the cursor, and the
|
|
|
|
// natively inserted characters would be the same as the non-native.
|
|
|
|
const isNative = (
|
|
|
|
state.isCollapsed &&
|
|
|
|
state.startText.text != '' &&
|
|
|
|
state.cursorMarks == null &&
|
|
|
|
chars.equals(nextChars)
|
|
|
|
)
|
|
|
|
|
|
|
|
// Add the `isNative` flag directly, so we don't have to re-transform.
|
|
|
|
if (isNative) {
|
|
|
|
next = next.merge({ isNative })
|
2016-07-11 18:36:45 -07:00
|
|
|
}
|
2016-06-30 14:37:29 -07:00
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
// If not native, prevent default so that the DOM remains untouched.
|
|
|
|
if (!isNative) e.preventDefault()
|
|
|
|
|
|
|
|
// Return the new state.
|
|
|
|
return next
|
|
|
|
}
|
|
|
|
|
2016-07-27 13:07:52 -07:00
|
|
|
/**
|
|
|
|
* On blur.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onBlur(e, data, state) {
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.blur()
|
|
|
|
.apply({ isNative: true })
|
|
|
|
}
|
|
|
|
|
2016-07-27 13:33:36 -07:00
|
|
|
/**
|
|
|
|
* On copy.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onCopy(e, data, state) {
|
|
|
|
onCutOrCopy(e, data, state)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On cut.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @param {Editor} editor
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onCut(e, data, state, editor) {
|
|
|
|
onCutOrCopy(e, data, state)
|
|
|
|
|
|
|
|
// Once the fake cut content has successfully been added to the clipboard,
|
|
|
|
// delete the content in the current selection.
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
const next = editor
|
|
|
|
.getState()
|
|
|
|
.transform()
|
|
|
|
.delete()
|
|
|
|
.apply()
|
|
|
|
|
|
|
|
editor.onChange(next)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On cut or copy, create a fake selection so that we can add a Base 64
|
|
|
|
* encoded copy of the fragment to the HTML, to decode on future pastes.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onCutOrCopy(e, data, state) {
|
|
|
|
const native = window.getSelection()
|
|
|
|
if (!native.rangeCount) return
|
|
|
|
|
|
|
|
const { fragment } = data
|
|
|
|
const encoded = Base64.serializeNode(fragment)
|
|
|
|
|
|
|
|
// Wrap the first character of the selection in a span that has the encoded
|
|
|
|
// fragment attached as an attribute, so it will show up in the copied HTML.
|
|
|
|
const range = native.getRangeAt(0)
|
|
|
|
const contents = range.cloneContents()
|
|
|
|
const wrapper = window.document.createElement('span')
|
|
|
|
const text = contents.childNodes[0]
|
|
|
|
const char = text.textContent.slice(0, 1)
|
|
|
|
const first = window.document.createTextNode(char)
|
|
|
|
const rest = text.textContent.slice(1)
|
|
|
|
text.textContent = rest
|
|
|
|
wrapper.appendChild(first)
|
|
|
|
wrapper.setAttribute('data-fragment', encoded)
|
|
|
|
contents.insertBefore(wrapper, text)
|
|
|
|
|
|
|
|
// Add the phony content to the DOM, and select it, so it will be copied.
|
|
|
|
const body = window.document.querySelector('body')
|
|
|
|
const div = window.document.createElement('div')
|
|
|
|
div.setAttribute('contenteditable', true)
|
|
|
|
div.style.position = 'absolute'
|
|
|
|
div.style.left = '-9999px'
|
|
|
|
div.appendChild(contents)
|
|
|
|
body.appendChild(div)
|
|
|
|
|
|
|
|
// COMPAT: In Firefox, trying to use the terser `native.selectAllChildren`
|
|
|
|
// throws an error, so we use the older `range` equivalent. (2016/06/21)
|
|
|
|
const r = window.document.createRange()
|
|
|
|
r.selectNodeContents(div)
|
|
|
|
native.removeAllRanges()
|
|
|
|
native.addRange(r)
|
|
|
|
|
|
|
|
// Revert to the previous selection right after copying.
|
|
|
|
window.requestAnimationFrame(() => {
|
|
|
|
body.removeChild(div)
|
|
|
|
native.removeAllRanges()
|
|
|
|
native.addRange(range)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
/**
|
|
|
|
* On drop.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onDrop(e, data, state) {
|
|
|
|
switch (data.type) {
|
|
|
|
case 'text':
|
|
|
|
case 'html':
|
|
|
|
return onDropText(e, data, state)
|
|
|
|
case 'fragment':
|
|
|
|
return onDropFragment(e, data, state)
|
2016-06-15 12:07:12 -07:00
|
|
|
}
|
2016-07-11 18:36:45 -07:00
|
|
|
}
|
2016-06-17 19:57:37 -07:00
|
|
|
|
2016-06-24 12:06:59 -07:00
|
|
|
/**
|
2016-07-27 12:54:04 -07:00
|
|
|
* On drop fragment.
|
2016-06-24 12:06:59 -07:00
|
|
|
*
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
2016-06-24 12:06:59 -07:00
|
|
|
*/
|
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
function onDropFragment(e, data, state) {
|
|
|
|
const { selection } = state
|
|
|
|
let { fragment, target, isInternal } = data
|
|
|
|
|
|
|
|
// If the drag is internal and the target is after the selection, it
|
|
|
|
// needs to account for the selection's content being deleted.
|
|
|
|
if (
|
|
|
|
isInternal &&
|
|
|
|
selection.endKey == target.endKey &&
|
|
|
|
selection.endOffset < target.endOffset
|
|
|
|
) {
|
|
|
|
target = target.moveBackward(selection.startKey == selection.endKey
|
|
|
|
? selection.endOffset - selection.startOffset
|
|
|
|
: selection.endOffset)
|
2016-07-11 18:36:45 -07:00
|
|
|
}
|
2016-07-27 12:54:04 -07:00
|
|
|
|
|
|
|
let transform = state.transform()
|
|
|
|
|
|
|
|
if (isInternal) transform = transform.delete()
|
|
|
|
|
|
|
|
return transform
|
|
|
|
.moveTo(target)
|
|
|
|
.insertFragment(fragment)
|
|
|
|
.apply()
|
2016-07-11 18:36:45 -07:00
|
|
|
}
|
2016-06-24 12:06:59 -07:00
|
|
|
|
2016-06-17 19:57:37 -07:00
|
|
|
/**
|
2016-07-27 12:54:04 -07:00
|
|
|
* On drop text, split the blocks at new lines.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
2016-06-17 19:57:37 -07:00
|
|
|
*/
|
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
function onDropText(e, data, state) {
|
|
|
|
const { text, target } = data
|
|
|
|
let transform = state
|
|
|
|
.transform()
|
|
|
|
.moveTo(target)
|
|
|
|
|
|
|
|
text
|
|
|
|
.split('\n')
|
|
|
|
.forEach((line, i) => {
|
|
|
|
if (i > 0) transform = transform.splitBlock()
|
|
|
|
transform = transform.insertText(line)
|
|
|
|
})
|
2016-07-11 18:36:45 -07:00
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
return transform.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On key down.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDown(e, data, state) {
|
|
|
|
switch (data.key) {
|
|
|
|
case 'enter': return onKeyDownEnter(e, data, state)
|
|
|
|
case 'backspace': return onKeyDownBackspace(e, data, state)
|
|
|
|
case 'delete': return onKeyDownDelete(e, data, state)
|
|
|
|
case 'y': return onKeyDownY(e, data, state)
|
|
|
|
case 'z': return onKeyDownZ(e, data, state)
|
2016-07-27 12:54:04 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On `enter` key down, split the current block in half.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDownEnter(e, data, state) {
|
2016-07-27 12:54:04 -07:00
|
|
|
const { document, startKey, startBlock } = state
|
|
|
|
|
|
|
|
// For void blocks, we don't want to split. Instead we just move to the
|
|
|
|
// start of the next text node if one exists.
|
|
|
|
if (startBlock && startBlock.isVoid) {
|
|
|
|
const text = document.getNextText(startKey)
|
|
|
|
if (!text) return
|
|
|
|
return state
|
2016-07-26 16:58:42 -07:00
|
|
|
.transform()
|
2016-07-27 12:54:04 -07:00
|
|
|
.collapseToStartOf(text)
|
2016-07-26 16:58:42 -07:00
|
|
|
.apply()
|
2016-07-27 12:54:04 -07:00
|
|
|
}
|
2016-07-26 16:58:42 -07:00
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.splitBlock()
|
|
|
|
.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On `backspace` key down, delete backwards.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDownBackspace(e, data, state) {
|
2016-07-27 12:54:04 -07:00
|
|
|
// If expanded, delete regularly.
|
|
|
|
if (state.isExpanded) {
|
2016-07-26 11:07:31 -07:00
|
|
|
return state
|
2016-07-25 16:59:12 -07:00
|
|
|
.transform()
|
2016-07-27 12:54:04 -07:00
|
|
|
.delete()
|
|
|
|
.apply()
|
2016-07-11 18:36:45 -07:00
|
|
|
}
|
2016-07-27 12:54:04 -07:00
|
|
|
|
|
|
|
const { startOffset, startBlock } = state
|
|
|
|
const text = startBlock.text
|
|
|
|
let n
|
|
|
|
|
|
|
|
// Determine how far backwards to delete.
|
2016-07-27 14:30:09 -07:00
|
|
|
if (data.isWord) {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = String.getWordOffsetBackward(text, startOffset)
|
2016-07-27 14:30:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else if (data.isLine) {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = startOffset
|
2016-07-27 14:30:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = String.getCharOffsetBackward(text, startOffset)
|
|
|
|
}
|
|
|
|
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.deleteBackward(n)
|
|
|
|
.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On `delete` key down, delete forwards.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDownDelete(e, data, state) {
|
2016-07-27 12:54:04 -07:00
|
|
|
// If expanded, delete regularly.
|
|
|
|
if (state.isExpanded) {
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.delete()
|
|
|
|
.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
const { startOffset, startBlock } = state
|
|
|
|
const text = startBlock.text
|
|
|
|
let n
|
|
|
|
|
|
|
|
// Determine how far forwards to delete.
|
2016-07-27 14:30:09 -07:00
|
|
|
if (data.isWord) {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = String.getWordOffsetForward(text, startOffset)
|
2016-07-27 14:30:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else if (data.isLine) {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = text.length - startOffset
|
2016-07-27 14:30:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
2016-07-27 12:54:04 -07:00
|
|
|
n = String.getCharOffsetForward(text, startOffset)
|
|
|
|
}
|
|
|
|
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.deleteForward(n)
|
|
|
|
.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On `y` key down, redo.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDownY(e, data, state) {
|
|
|
|
if (!data.isMod) return
|
2016-07-27 12:54:04 -07:00
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.redo()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On `z` key down, undo or redo.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
2016-07-27 14:30:09 -07:00
|
|
|
* @param {Object} data
|
2016-07-27 12:54:04 -07:00
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
2016-07-27 14:30:09 -07:00
|
|
|
function onKeyDownZ(e, data, state) {
|
|
|
|
if (!data.isMod) return
|
2016-07-27 12:54:04 -07:00
|
|
|
return state
|
|
|
|
.transform()
|
2016-07-27 14:30:09 -07:00
|
|
|
[data.isShift ? 'redo' : 'undo']()
|
2016-07-27 12:54:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On paste.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onPaste(e, data, state) {
|
|
|
|
switch (data.type) {
|
2016-07-27 14:34:11 -07:00
|
|
|
case 'fragment':
|
|
|
|
return onPasteFragment(e, data, state)
|
2016-07-27 12:54:04 -07:00
|
|
|
case 'text':
|
|
|
|
case 'html':
|
|
|
|
return onPasteText(e, data, state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 14:34:11 -07:00
|
|
|
/**
|
|
|
|
* On paste fragment.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onPasteFragment(e, data, state) {
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.insertFragment(data.fragment)
|
|
|
|
.apply()
|
|
|
|
}
|
|
|
|
|
2016-07-27 12:54:04 -07:00
|
|
|
/**
|
|
|
|
* On paste text, split blocks at new lines.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onPasteText(e, data, state) {
|
|
|
|
let transform = state.transform()
|
|
|
|
|
|
|
|
data.text
|
|
|
|
.split('\n')
|
|
|
|
.forEach((line, i) => {
|
|
|
|
if (i > 0) transform = transform.splitBlock()
|
|
|
|
transform = transform.insertText(line)
|
|
|
|
})
|
|
|
|
|
|
|
|
return transform.apply()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* On select.
|
|
|
|
*
|
|
|
|
* @param {Event} e
|
|
|
|
* @param {Object} data
|
|
|
|
* @param {State} state
|
|
|
|
* @return {State}
|
|
|
|
*/
|
|
|
|
|
|
|
|
function onSelect(e, data, state) {
|
|
|
|
const { selection, isNative } = data
|
|
|
|
return state
|
|
|
|
.transform()
|
|
|
|
.moveTo(selection)
|
|
|
|
.focus()
|
|
|
|
.apply({ isNative })
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The core `node` renderer, which uses plain `<div>` or `<span>` depending on
|
|
|
|
* what kind of node it is.
|
|
|
|
*
|
|
|
|
* @param {Node} node
|
|
|
|
* @return {Component} component
|
|
|
|
*/
|
|
|
|
|
|
|
|
function renderNode(node) {
|
|
|
|
return node.kind == 'block'
|
|
|
|
? DEFAULT_BLOCK
|
|
|
|
: DEFAULT_INLINE
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the core plugin.
|
|
|
|
*/
|
|
|
|
|
|
|
|
return {
|
|
|
|
onBeforeInput,
|
2016-07-27 13:33:36 -07:00
|
|
|
onBlur,
|
|
|
|
onCopy,
|
|
|
|
onCut,
|
2016-07-27 12:54:04 -07:00
|
|
|
onDrop,
|
|
|
|
onKeyDown,
|
|
|
|
onPaste,
|
|
|
|
onSelect,
|
|
|
|
renderNode
|
2016-06-15 12:07:12 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-11 18:36:45 -07:00
|
|
|
/**
|
|
|
|
* Export.
|
|
|
|
*/
|
|
|
|
|
|
|
|
export default Plugin
|