1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-09-02 11:42:53 +02:00

move onCopy and onCut logic to core plugin, refactor onDrop

This commit is contained in:
Ian Storm Taylor
2016-07-27 13:33:36 -07:00
parent 6503ee2d80
commit fba3fe7a13
3 changed files with 138 additions and 83 deletions

View File

@@ -222,7 +222,16 @@ class Content extends React.Component {
*/
onCopy = (e) => {
this.onCutCopy(e)
this.tmp.isCopying = true
window.requestAnimationFrame(() => {
this.tmp.isCopying = false
})
const { state } = this.props
const data = {}
data.type = 'fragment'
data.fragment = state.fragment
this.props.onCopy(e, data)
}
/**
@@ -233,69 +242,17 @@ class Content extends React.Component {
onCut = (e) => {
if (this.props.readOnly) return
this.onCutCopy(e)
// Once the cut has successfully executed, delete the current selection.
window.requestAnimationFrame(() => {
const state = this.props.state.transform().delete().apply()
this.onChange(state)
})
}
/**
* On cut and copy, add the currently selected fragment to the currently
* selected DOM, so that it will show up when pasted.
*
* @param {Event} e
*/
onCutCopy = (e) => {
const native = window.getSelection()
if (!native.rangeCount) return
const { state } = this.props
const { fragment } = state
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)
// Set the `isCopying` flag, so our `onSelect` logic doesn't fire.
this.tmp.isCopying = true
const r = window.document.createRange()
// COMPAT: In Firefox, trying to use the terser `native.selectAllChildren`
// throws an error, so we use the older `range` equivalent. (2016/06/21)
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)
this.tmp.isCopying = false
})
const { state } = this.props
const data = {}
data.type = 'fragment'
data.fragment = state.fragment
this.props.onCut(e, data)
}
/**
@@ -364,14 +321,13 @@ class Content extends React.Component {
const { state, renderDecorations } = this.props
const { selection } = state
const data = e.nativeEvent.dataTransfer
const drop = {}
const { dataTransfer, x, y } = e.nativeEvent
const data = {}
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
const types = Array.from(data.types)
const types = Array.from(dataTransfer.types)
// Resolve the point where the drop occured.
const { x, y } = e.nativeEvent
let range
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
@@ -398,45 +354,44 @@ class Content extends React.Component {
// Handle Slate fragments.
if (includes(types, TYPES.FRAGMENT)) {
const encoded = data.getData(TYPES.FRAGMENT)
const encoded = dataTransfer.getData(TYPES.FRAGMENT)
const fragment = Base64.deserializeNode(encoded)
drop.type = 'fragment'
drop.fragment = fragment
drop.isInternal = this.tmp.isInternalDrag
data.type = 'fragment'
data.fragment = fragment
data.isInternal = this.tmp.isInternalDrag
}
// Handle Slate nodes.
else if (includes(types, TYPES.NODE)) {
const encoded = data.getData(TYPES.NODE)
const encoded = dataTransfer.getData(TYPES.NODE)
const node = Base64.deserializeNode(encoded)
drop.type = 'node'
drop.node = node
drop.isInternal = this.tmp.isInternalDrag
data.type = 'node'
data.node = node
data.isInternal = this.tmp.isInternalDrag
}
// Handle files.
else if (data.files.length) {
drop.type = 'files'
drop.files = data.files
else if (dataTransfer.files.length) {
data.type = 'files'
data.files = dataTransfer.files
}
// Handle HTML.
else if (includes(types, TYPES.HTML)) {
drop.type = 'html'
drop.text = data.getData(TYPES.TEXT)
drop.html = data.getData(TYPES.HTML)
data.type = 'html'
data.text = dataTransfer.getData(TYPES.TEXT)
data.html = dataTransfer.getData(TYPES.HTML)
}
// Handle plain text.
else {
drop.type = 'text'
drop.text = data.getData(TYPES.TEXT)
data.type = 'text'
data.text = dataTransfer.getData(TYPES.TEXT)
}
drop.data = data
drop.target = target
drop.effect = data.dropEffect
this.props.onDrop(e, drop)
data.target = target
data.effect = dataTransfer.dropEffect
this.props.onDrop(e, data)
}
/**

View File

@@ -21,6 +21,8 @@ function noop() {}
const EVENT_HANDLERS = [
'onBeforeInput',
'onBlur',
'onCopy',
'onCut',
'onDrop',
'onKeyDown',
'onPaste',

View File

@@ -1,4 +1,5 @@
import Base64 from '../serializers/base-64'
import Character from '../models/character'
import Key from '../utils/key'
import Placeholder from '../components/placeholder'
@@ -136,6 +137,100 @@ function Plugin(options = {}) {
.apply({ isNative: true })
}
/**
* 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)
})
}
/**
* On drop.
*
@@ -440,6 +535,9 @@ function Plugin(options = {}) {
return {
onBeforeInput,
onBlur,
onCopy,
onCut,
onDrop,
onKeyDown,
onPaste,