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:
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -21,6 +21,8 @@ function noop() {}
|
||||
const EVENT_HANDLERS = [
|
||||
'onBeforeInput',
|
||||
'onBlur',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDrop',
|
||||
'onKeyDown',
|
||||
'onPaste',
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user