mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 06:31:28 +02:00
deprecate data, add getEventRange and getEventTransfer helpers (#1243)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { Editor } from 'slate-react'
|
import { Editor, getEventRange, getEventTransfer } from 'slate-react'
|
||||||
import { Block, State } from 'slate'
|
import { Block, State } from 'slate'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -199,30 +199,22 @@ class Images extends React.Component {
|
|||||||
* @param {Editor} editor
|
* @param {Editor} editor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
onDrop = (e, data, change, editor) => {
|
onDropOrPaste = (e, data, change, editor) => {
|
||||||
switch (data.type) {
|
const target = getEventRange(e)
|
||||||
case 'files': return this.onDropOrPasteFiles(e, data, change, editor)
|
if (!target) return
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const transfer = getEventTransfer(e)
|
||||||
* On drop or paste files, read and insert the image files.
|
const { type, text, files } = transfer
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
* @param {Editor} editor
|
|
||||||
*/
|
|
||||||
|
|
||||||
onDropOrPasteFiles = (e, data, change, editor) => {
|
if (type == 'files') {
|
||||||
for (const file of data.files) {
|
for (const file of files) {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
const [ type ] = file.type.split('/')
|
const [ mime ] = file.type.split('/')
|
||||||
if (type != 'image') continue
|
if (mime != 'image') continue
|
||||||
|
|
||||||
reader.addEventListener('load', () => {
|
reader.addEventListener('load', () => {
|
||||||
editor.change((t) => {
|
editor.change((c) => {
|
||||||
t.call(insertImage, reader.result, data.target)
|
c.call(insertImage, reader.result, target)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -230,36 +222,13 @@ class Images extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (type == 'text') {
|
||||||
* On paste, if the pasted content is an image URL, insert it.
|
if (!isUrl(text)) return
|
||||||
*
|
if (!isImage(text)) return
|
||||||
* @param {Event} e
|
change.call(insertImage, text, target)
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
* @param {Editor} editor
|
|
||||||
*/
|
|
||||||
|
|
||||||
onPaste = (e, data, change, editor) => {
|
|
||||||
switch (data.type) {
|
|
||||||
case 'files': return this.onDropOrPasteFiles(e, data, change, editor)
|
|
||||||
case 'text': return this.onPasteText(e, data, change)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* On paste text, if the pasted content is an image URL, insert it.
|
|
||||||
*
|
|
||||||
* @param {Event} e
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
onPasteText = (e, data, change) => {
|
|
||||||
if (!isUrl(data.text)) return
|
|
||||||
if (!isImage(data.text)) return
|
|
||||||
change.call(insertImage, data.text, data.target)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
import { Editor } from 'slate-react'
|
import { Editor, getEventTransfer } from 'slate-react'
|
||||||
import { State } from 'slate'
|
import { State } from 'slate'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -132,14 +132,17 @@ class Links extends React.Component {
|
|||||||
|
|
||||||
onPaste = (e, data, change) => {
|
onPaste = (e, data, change) => {
|
||||||
if (change.state.isCollapsed) return
|
if (change.state.isCollapsed) return
|
||||||
if (data.type != 'text' && data.type != 'html') return
|
|
||||||
if (!isUrl(data.text)) return
|
const transfer = getEventTransfer(e)
|
||||||
|
const { type, text } = transfer
|
||||||
|
if (type != 'text' && type != 'html') return
|
||||||
|
if (!isUrl(text)) return
|
||||||
|
|
||||||
if (this.hasLinks()) {
|
if (this.hasLinks()) {
|
||||||
change.call(unwrapLink)
|
change.call(unwrapLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
change.call(wrapLink, data.text)
|
change.call(wrapLink, text)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import Html from 'slate-html-serializer'
|
import Html from 'slate-html-serializer'
|
||||||
import { Editor } from 'slate-react'
|
import { Editor, getEventTransfer } from 'slate-react'
|
||||||
import { State } from 'slate'
|
import { State } from 'slate'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
@@ -180,9 +180,9 @@ class PasteHtml extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onPaste = (e, data, change) => {
|
onPaste = (e, data, change) => {
|
||||||
if (data.type != 'html') return
|
const transfer = getEventTransfer(e)
|
||||||
if (data.isShift) return
|
if (transfer.type != 'html') return
|
||||||
const { document } = serializer.deserialize(data.html)
|
const { document } = serializer.deserialize(transfer.html)
|
||||||
change.insertFragment(document)
|
change.insertFragment(document)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -78,7 +78,7 @@ function warn(message, ...args) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
function deprecate(version, message, ...args) {
|
function deprecate(version, message, ...args) {
|
||||||
log('warn', `Deprecation (v${version}): ${message}`, ...args)
|
log('warn', `Deprecation (${version}): ${message}`, ...args)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
|
|
||||||
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'
|
||||||
@@ -15,6 +17,8 @@ import findRange from './utils/find-range'
|
|||||||
export {
|
export {
|
||||||
Editor,
|
Editor,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
|
getEventRange,
|
||||||
|
getEventTransfer,
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
findDOMRange,
|
findDOMRange,
|
||||||
findNode,
|
findNode,
|
||||||
@@ -24,6 +28,8 @@ export {
|
|||||||
export default {
|
export default {
|
||||||
Editor,
|
Editor,
|
||||||
Placeholder,
|
Placeholder,
|
||||||
|
getEventRange,
|
||||||
|
getEventTransfer,
|
||||||
findDOMNode,
|
findDOMNode,
|
||||||
findDOMRange,
|
findDOMRange,
|
||||||
findNode,
|
findNode,
|
||||||
|
@@ -12,6 +12,9 @@ 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 findPoint from '../utils/find-point'
|
import findPoint from '../utils/find-point'
|
||||||
|
import findRange from '../utils/find-range'
|
||||||
|
import getEventRange from '../utils/get-event-range'
|
||||||
|
import getEventTransfer from '../utils/get-event-transfer'
|
||||||
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
import { IS_CHROME, IS_MAC, IS_SAFARI } from '../constants/environment'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,6 +42,8 @@ function AfterPlugin(options = {}) {
|
|||||||
placeholderStyle,
|
placeholderStyle,
|
||||||
} = options
|
} = options
|
||||||
|
|
||||||
|
let isDraggingInternally = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On before change, enforce the editor's schema.
|
* On before change, enforce the editor's schema.
|
||||||
*
|
*
|
||||||
@@ -143,7 +148,7 @@ function AfterPlugin(options = {}) {
|
|||||||
|
|
||||||
// Create a fake selection so that we can add a Base64-encoded copy of the
|
// Create a fake selection so that we can add a Base64-encoded copy of the
|
||||||
// fragment to the HTML, to decode on future pastes.
|
// fragment to the HTML, to decode on future pastes.
|
||||||
const { fragment } = data
|
const { fragment } = state
|
||||||
const encoded = Base64.serializeNode(fragment)
|
const encoded = Base64.serializeNode(fragment)
|
||||||
const range = native.getRangeAt(0)
|
const range = native.getRangeAt(0)
|
||||||
let contents = range.cloneContents()
|
let contents = range.cloneContents()
|
||||||
@@ -233,6 +238,51 @@ function AfterPlugin(options = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On drag end.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onDragEnd(event, data, change, editor) {
|
||||||
|
debug('onDragEnd', { event })
|
||||||
|
|
||||||
|
isDraggingInternally = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On drag over.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onDragOver(event, data, change, editor) {
|
||||||
|
debug('onDragOver', { event })
|
||||||
|
|
||||||
|
isDraggingInternally = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On drag start.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {Object} data
|
||||||
|
* @param {Change} change
|
||||||
|
* @param {Editor} editor
|
||||||
|
*/
|
||||||
|
|
||||||
|
function onDragStart(event, data, change, editor) {
|
||||||
|
debug('onDragStart', { event })
|
||||||
|
|
||||||
|
isDraggingInternally = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On drop.
|
* On drop.
|
||||||
*
|
*
|
||||||
@@ -241,39 +291,23 @@ function AfterPlugin(options = {}) {
|
|||||||
* @param {Change} change
|
* @param {Change} change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onDrop(event, data, change) {
|
function onDrop(event, data, change, editor) {
|
||||||
debug('onDrop', { data })
|
debug('onDrop', { event })
|
||||||
|
|
||||||
switch (data.type) {
|
|
||||||
case 'text':
|
|
||||||
case 'html':
|
|
||||||
return onDropText(event, data, change)
|
|
||||||
case 'fragment':
|
|
||||||
return onDropFragment(event, data, change)
|
|
||||||
case 'node':
|
|
||||||
return onDropNode(event, data, change)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On drop node.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onDropNode(event, data, change) {
|
|
||||||
debug('onDropNode', { data })
|
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { selection } = state
|
const { selection } = state
|
||||||
let { node, target, isInternal } = data
|
let target = getEventRange(event, state)
|
||||||
|
if (!target) return
|
||||||
|
|
||||||
|
const transfer = getEventTransfer(event)
|
||||||
|
const { type, fragment, node, text } = transfer
|
||||||
|
|
||||||
|
change.focus()
|
||||||
|
|
||||||
// If the drag is internal and the target is after the selection, it
|
// If the drag is internal and the target is after the selection, it
|
||||||
// needs to account for the selection's content being deleted.
|
// needs to account for the selection's content being deleted.
|
||||||
if (
|
if (
|
||||||
isInternal &&
|
isDraggingInternally &&
|
||||||
selection.endKey == target.endKey &&
|
selection.endKey == target.endKey &&
|
||||||
selection.endOffset < target.endOffset
|
selection.endOffset < target.endOffset
|
||||||
) {
|
) {
|
||||||
@@ -282,95 +316,26 @@ function AfterPlugin(options = {}) {
|
|||||||
: 0 - selection.endOffset)
|
: 0 - selection.endOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternal) {
|
if (isDraggingInternally) {
|
||||||
change.delete()
|
change.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Block.isBlock(node)) {
|
change.select(target)
|
||||||
change
|
|
||||||
.select(target)
|
|
||||||
.focus()
|
|
||||||
.insertBlock(node)
|
|
||||||
.removeNodeByKey(node.key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Inline.isInline(node)) {
|
if (type == 'text' || type == 'html') {
|
||||||
change
|
|
||||||
.select(target)
|
|
||||||
.focus()
|
|
||||||
.insertInline(node)
|
|
||||||
.removeNodeByKey(node.key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On drop fragment.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onDropFragment(event, data, change) {
|
|
||||||
debug('onDropFragment', { data })
|
|
||||||
|
|
||||||
const { state } = change
|
|
||||||
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.move(selection.startKey == selection.endKey
|
|
||||||
? 0 - selection.endOffset + selection.startOffset
|
|
||||||
: 0 - selection.endOffset)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInternal) {
|
|
||||||
change.delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
change
|
|
||||||
.select(target)
|
|
||||||
.focus()
|
|
||||||
.insertFragment(fragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On drop text.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onDropText(event, data, change) {
|
|
||||||
debug('onDropText', { data })
|
|
||||||
|
|
||||||
const { state } = change
|
|
||||||
const { document } = state
|
|
||||||
const { text, target } = data
|
|
||||||
const { anchorKey } = target
|
const { anchorKey } = target
|
||||||
|
|
||||||
change.select(target).focus()
|
|
||||||
|
|
||||||
let hasVoidParent = document.hasVoidParent(anchorKey)
|
let hasVoidParent = document.hasVoidParent(anchorKey)
|
||||||
|
|
||||||
// Insert text into nearest text node
|
|
||||||
if (hasVoidParent) {
|
if (hasVoidParent) {
|
||||||
let node = document.getNode(anchorKey)
|
let n = document.getNode(anchorKey)
|
||||||
|
|
||||||
while (hasVoidParent) {
|
while (hasVoidParent) {
|
||||||
node = document.getNextText(node.key)
|
n = document.getNextText(n.key)
|
||||||
if (!node) break
|
if (!n) break
|
||||||
hasVoidParent = document.hasVoidParent(node.key)
|
hasVoidParent = document.hasVoidParent(n.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node) change.collapseToStartOf(node)
|
if (n) change.collapseToStartOf(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
text
|
text
|
||||||
@@ -381,6 +346,19 @@ function AfterPlugin(options = {}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (type == 'fragment') {
|
||||||
|
change.insertFragment(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 'node' && Block.isBlock(node)) {
|
||||||
|
change.insertBlock(node).removeNodeByKey(node.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == 'node' && Inline.isInline(node)) {
|
||||||
|
change.insertInline(node).removeNodeByKey(node.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On input.
|
* On input.
|
||||||
*
|
*
|
||||||
@@ -733,48 +711,23 @@ function AfterPlugin(options = {}) {
|
|||||||
function onPaste(event, data, change) {
|
function onPaste(event, data, change) {
|
||||||
debug('onPaste', { data })
|
debug('onPaste', { data })
|
||||||
|
|
||||||
switch (data.type) {
|
const transfer = getEventTransfer(event)
|
||||||
case 'fragment':
|
const { type, fragment, text } = transfer
|
||||||
return onPasteFragment(event, data, change)
|
|
||||||
case 'text':
|
if (type == 'fragment') {
|
||||||
case 'html':
|
change.insertFragment(fragment)
|
||||||
return onPasteText(event, data, change)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (type == 'text' || type == 'html') {
|
||||||
* On paste fragment.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onPasteFragment(event, data, change) {
|
|
||||||
debug('onPasteFragment', { data })
|
|
||||||
change.insertFragment(data.fragment)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On paste text, split blocks at new lines.
|
|
||||||
*
|
|
||||||
* @param {Event} event
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Change} change
|
|
||||||
*/
|
|
||||||
|
|
||||||
function onPasteText(event, data, change) {
|
|
||||||
debug('onPasteText', { data })
|
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { document, selection, startBlock } = state
|
const { document, selection, startBlock } = state
|
||||||
if (startBlock.isVoid) return
|
if (startBlock.isVoid) return
|
||||||
|
|
||||||
const { text } = data
|
|
||||||
const defaultBlock = startBlock
|
const defaultBlock = startBlock
|
||||||
const defaultMarks = document.getMarksAtRange(selection.collapseToStart())
|
const defaultMarks = document.getMarksAtRange(selection.collapseToStart())
|
||||||
const fragment = Plain.deserialize(text, { defaultBlock, defaultMarks }).document
|
const frag = Plain.deserialize(text, { defaultBlock, defaultMarks }).document
|
||||||
change.insertFragment(fragment)
|
change.insertFragment(frag)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -787,7 +740,73 @@ function AfterPlugin(options = {}) {
|
|||||||
|
|
||||||
function onSelect(event, data, change) {
|
function onSelect(event, data, change) {
|
||||||
debug('onSelect', { data })
|
debug('onSelect', { data })
|
||||||
change.select(data.selection)
|
|
||||||
|
const window = getWindow(event.target)
|
||||||
|
const { state } = change
|
||||||
|
const { document } = state
|
||||||
|
const native = window.getSelection()
|
||||||
|
|
||||||
|
// If there are no ranges, the editor was blurred natively.
|
||||||
|
if (!native.rangeCount) {
|
||||||
|
change.blur()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, determine the Slate selection from the native one.
|
||||||
|
let range = findRange(native, state)
|
||||||
|
if (!range) return
|
||||||
|
|
||||||
|
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)
|
||||||
|
change.select(range)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -899,6 +918,9 @@ function AfterPlugin(options = {}) {
|
|||||||
onBlur,
|
onBlur,
|
||||||
onCopy,
|
onCopy,
|
||||||
onCut,
|
onCut,
|
||||||
|
onDragEnd,
|
||||||
|
onDragOver,
|
||||||
|
onDragStart,
|
||||||
onDrop,
|
onDrop,
|
||||||
onInput,
|
onInput,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
|
@@ -9,7 +9,8 @@ import { findDOMNode } from 'react-dom'
|
|||||||
import HOTKEYS from '../constants/hotkeys'
|
import HOTKEYS from '../constants/hotkeys'
|
||||||
import TRANSFER_TYPES from '../constants/transfer-types'
|
import TRANSFER_TYPES from '../constants/transfer-types'
|
||||||
import findRange from '../utils/find-range'
|
import findRange from '../utils/find-range'
|
||||||
import getTransferData from '../utils/get-transfer-data'
|
import getEventRange from '../utils/get-event-range'
|
||||||
|
import getEventTransfer from '../utils/get-event-transfer'
|
||||||
import setTransferData from '../utils/set-transfer-data'
|
import setTransferData from '../utils/set-transfer-data'
|
||||||
import { IS_FIREFOX, IS_MAC, SUPPORTED_EVENTS } from '../constants/environment'
|
import { IS_FIREFOX, IS_MAC, SUPPORTED_EVENTS } from '../constants/environment'
|
||||||
|
|
||||||
@@ -135,8 +136,8 @@ function BeforePlugin() {
|
|||||||
window.requestAnimationFrame(() => isCopying = false)
|
window.requestAnimationFrame(() => isCopying = false)
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
data.type = 'fragment'
|
defineDeprecatedData(data, 'type', 'fragment')
|
||||||
data.fragment = state.fragment
|
defineDeprecatedData(data, 'fragment', state.fragment)
|
||||||
|
|
||||||
debug('onCopy', { event })
|
debug('onCopy', { event })
|
||||||
}
|
}
|
||||||
@@ -158,8 +159,8 @@ function BeforePlugin() {
|
|||||||
window.requestAnimationFrame(() => isCopying = false)
|
window.requestAnimationFrame(() => isCopying = false)
|
||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
data.type = 'fragment'
|
defineDeprecatedData(data, 'type', 'fragment')
|
||||||
data.fragment = state.fragment
|
defineDeprecatedData(data, 'fragment', state.fragment)
|
||||||
|
|
||||||
debug('onCut', { event })
|
debug('onCut', { event })
|
||||||
}
|
}
|
||||||
@@ -212,12 +213,16 @@ function BeforePlugin() {
|
|||||||
isDragging = true
|
isDragging = true
|
||||||
isInternalDrag = true
|
isInternalDrag = true
|
||||||
|
|
||||||
const { dataTransfer } = event.nativeEvent
|
const d = getEventTransfer(event)
|
||||||
const d = getTransferData(dataTransfer)
|
const { nativeEvent } = event
|
||||||
Object.assign(data, d)
|
const { dataTransfer } = nativeEvent
|
||||||
|
|
||||||
if (data.type != 'node') {
|
Object.keys(d).forEach((key) => {
|
||||||
const { state } = this.props
|
defineDeprecatedData(data, key, d[key])
|
||||||
|
})
|
||||||
|
|
||||||
|
if (d.type != 'node') {
|
||||||
|
const { state } = change
|
||||||
const { fragment } = state
|
const { fragment } = state
|
||||||
const encoded = Base64.serializeNode(fragment)
|
const encoded = Base64.serializeNode(fragment)
|
||||||
setTransferData(dataTransfer, TRANSFER_TYPES.FRAGMENT, encoded)
|
setTransferData(dataTransfer, TRANSFER_TYPES.FRAGMENT, encoded)
|
||||||
@@ -243,60 +248,29 @@ function BeforePlugin() {
|
|||||||
|
|
||||||
const { state } = change
|
const { state } = change
|
||||||
const { nativeEvent } = event
|
const { nativeEvent } = event
|
||||||
const { dataTransfer, x, y } = nativeEvent
|
const { dataTransfer } = nativeEvent
|
||||||
const d = getTransferData(dataTransfer)
|
const d = getEventTransfer(event)
|
||||||
Object.assign(data, d)
|
|
||||||
|
|
||||||
// Resolve a range from the caret position where the drop occured.
|
Object.keys(d).forEach((key) => {
|
||||||
const window = getWindow(event.target)
|
defineDeprecatedData(data, key, d[key])
|
||||||
let range
|
})
|
||||||
|
|
||||||
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
|
const range = getEventRange(event, state)
|
||||||
if (window.document.caretRangeFromPoint) {
|
if (!range) return true
|
||||||
range = window.document.caretRangeFromPoint(x, y)
|
|
||||||
} else {
|
|
||||||
const position = window.document.caretPositionFromPoint(x, y)
|
|
||||||
range = window.document.createRange()
|
|
||||||
range.setStart(position.offsetNode, position.offset)
|
|
||||||
range.setEnd(position.offsetNode, position.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve a Slate range from the DOM range.
|
|
||||||
let selection = findRange(range, state)
|
|
||||||
if (!selection) return true
|
|
||||||
|
|
||||||
const { document } = state
|
|
||||||
const node = document.getNode(selection.anchorKey)
|
|
||||||
const parent = document.getParent(node.key)
|
|
||||||
const el = findDOMNode(parent)
|
|
||||||
|
|
||||||
// If the drop target is inside a void node, move it into either the next or
|
|
||||||
// previous node, depending on which side the `x` and `y` coordinates are
|
|
||||||
// closest to.
|
|
||||||
if (parent.isVoid) {
|
|
||||||
const rect = el.getBoundingClientRect()
|
|
||||||
const isPrevious = parent.kind == 'inline'
|
|
||||||
? x - rect.left < rect.left + rect.width - x
|
|
||||||
: y - rect.top < rect.top + rect.height - y
|
|
||||||
|
|
||||||
selection = isPrevious
|
|
||||||
? selection.moveToEndOf(document.getPreviousText(node.key))
|
|
||||||
: selection.moveToStartOf(document.getNextText(node.key))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add drop-specific information to the data.
|
// Add drop-specific information to the data.
|
||||||
data.target = selection
|
defineDeprecatedData(data, 'target', range)
|
||||||
|
|
||||||
// COMPAT: Edge throws "Permission denied" errors when
|
// COMPAT: Edge throws "Permission denied" errors when
|
||||||
// accessing `dropEffect` or `effectAllowed` (2017/7/12)
|
// accessing `dropEffect` or `effectAllowed` (2017/7/12)
|
||||||
try {
|
try {
|
||||||
data.effect = dataTransfer.dropEffect
|
defineDeprecatedData(data, 'effect', dataTransfer.dropEffect)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
data.effect = null
|
defineDeprecatedData(data, 'effect', null)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (d.type == 'fragment' || d.type == 'node') {
|
if (d.type == 'fragment' || d.type == 'node') {
|
||||||
data.isInternal = isInternalDrag
|
defineDeprecatedData(data, 'isInternal', isInternalDrag)
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('onDrop', { event })
|
debug('onDrop', { event })
|
||||||
@@ -421,19 +395,14 @@ function BeforePlugin() {
|
|||||||
if (editor.props.readOnly) return
|
if (editor.props.readOnly) return
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const d = getTransferData(event.clipboardData)
|
const d = getEventTransfer(event)
|
||||||
Object.assign(data, d)
|
|
||||||
|
|
||||||
// COMPAT: Attach the `isShift` flag, so that people can use it to trigger
|
Object.keys(d).forEach((key) => {
|
||||||
// "Paste and Match Style" logic.
|
defineDeprecatedData(data, key, d[key])
|
||||||
Object.defineProperty(data, 'isShift', {
|
|
||||||
enumerable: true,
|
|
||||||
get() {
|
|
||||||
logger.deprecate('0.28.0', 'The `data.isShift` property of paste events has been deprecated. If you need this functionality, you\'ll need to keep track of that state with `onKeyDown` and `onKeyUp` events instead')
|
|
||||||
return isShifting
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
defineDeprecatedData(data, 'isShift', isShifting)
|
||||||
|
|
||||||
debug('onPaste', { event })
|
debug('onPaste', { event })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +427,7 @@ function BeforePlugin() {
|
|||||||
|
|
||||||
// If there are no ranges, the editor was blurred natively.
|
// If there are no ranges, the editor was blurred natively.
|
||||||
if (!native.rangeCount) {
|
if (!native.rangeCount) {
|
||||||
data.selection = selection.blur()
|
defineDeprecatedData(data, 'selection', selection.blur())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, determine the Slate selection from the native one.
|
// Otherwise, determine the Slate selection from the native one.
|
||||||
@@ -516,7 +485,7 @@ function BeforePlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
range = range.normalize(document)
|
range = range.normalize(document)
|
||||||
data.selection = range
|
defineDeprecatedData(data, 'selection', range)
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('onSelect', { event })
|
debug('onSelect', { event })
|
||||||
@@ -549,37 +518,33 @@ function BeforePlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add deprecated `data` fields from a key `event`.
|
* Deprecated.
|
||||||
*
|
|
||||||
* @param {Object} data
|
|
||||||
* @param {Object} event
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function addDeprecatedKeyProperties(data, event) {
|
function defineDeprecatedData(data, key, value) {
|
||||||
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
|
||||||
const name = keycode(which)
|
|
||||||
|
|
||||||
function define(key, value) {
|
|
||||||
Object.defineProperty(data, key, {
|
Object.defineProperty(data, key, {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
get() {
|
get() {
|
||||||
logger.deprecate('0.28.0', `The \`data.${key}\` property of keyboard events is deprecated, please use the native \`event\` properties instead.`)
|
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
|
return value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
define('code', which)
|
function addDeprecatedKeyProperties(data, event) {
|
||||||
define('key', name)
|
const { altKey, ctrlKey, metaKey, shiftKey, which } = event
|
||||||
define('isAlt', altKey)
|
const name = keycode(which)
|
||||||
define('isCmd', IS_MAC ? metaKey && !altKey : false)
|
defineDeprecatedData(data, 'code', which)
|
||||||
define('isCtrl', ctrlKey && !altKey)
|
defineDeprecatedData(data, 'key', name)
|
||||||
define('isLine', IS_MAC ? metaKey : false)
|
defineDeprecatedData(data, 'isAlt', altKey)
|
||||||
define('isMeta', metaKey)
|
defineDeprecatedData(data, 'isCmd', IS_MAC ? metaKey && !altKey : false)
|
||||||
define('isMod', IS_MAC ? metaKey && !altKey : ctrlKey && !altKey)
|
defineDeprecatedData(data, 'isCtrl', ctrlKey && !altKey)
|
||||||
define('isModAlt', IS_MAC ? metaKey && altKey : ctrlKey && altKey)
|
defineDeprecatedData(data, 'isLine', IS_MAC ? metaKey : false)
|
||||||
define('isShift', shiftKey)
|
defineDeprecatedData(data, 'isMeta', metaKey)
|
||||||
define('isWord', IS_MAC ? altKey : ctrlKey)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
69
packages/slate-react/src/utils/get-event-range.js
Normal file
69
packages/slate-react/src/utils/get-event-range.js
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
import getWindow from 'get-window'
|
||||||
|
|
||||||
|
import findDOMNode from './find-dom-node'
|
||||||
|
import findRange from './find-range'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the target range from a DOM `event`.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
* @param {State} state
|
||||||
|
* @return {Range}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getEventRange(event, state) {
|
||||||
|
if (event.nativeEvent) {
|
||||||
|
event = event.nativeEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
const { x, y } = event
|
||||||
|
if (x == null || y == null) return null
|
||||||
|
|
||||||
|
// Resolve a range from the caret position where the drop occured.
|
||||||
|
const window = getWindow(event.target)
|
||||||
|
let r
|
||||||
|
|
||||||
|
// COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
|
||||||
|
if (window.document.caretRangeFromPoint) {
|
||||||
|
r = window.document.caretRangeFromPoint(x, y)
|
||||||
|
} else {
|
||||||
|
const position = window.document.caretPositionFromPoint(x, y)
|
||||||
|
r = window.document.createRange()
|
||||||
|
r.setStart(position.offsetNode, position.offset)
|
||||||
|
r.setEnd(position.offsetNode, position.offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve a Slate range from the DOM range.
|
||||||
|
let range = findRange(r, state)
|
||||||
|
if (!range) return null
|
||||||
|
|
||||||
|
const { document } = state
|
||||||
|
const node = document.getNode(range.anchorKey)
|
||||||
|
const parent = document.getParent(node.key)
|
||||||
|
const el = findDOMNode(parent)
|
||||||
|
|
||||||
|
// If the drop target is inside a void node, move it into either the next or
|
||||||
|
// previous node, depending on which side the `x` and `y` coordinates are
|
||||||
|
// closest to.
|
||||||
|
if (parent.isVoid) {
|
||||||
|
const rect = el.getBoundingClientRect()
|
||||||
|
const isPrevious = parent.kind == 'inline'
|
||||||
|
? x - rect.left < rect.left + rect.width - x
|
||||||
|
: y - rect.top < rect.top + rect.height - y
|
||||||
|
|
||||||
|
range = isPrevious
|
||||||
|
? range.moveToEndOf(document.getPreviousText(node.key))
|
||||||
|
: range.moveToStartOf(document.getNextText(node.key))
|
||||||
|
}
|
||||||
|
|
||||||
|
return range
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default getEventRange
|
@@ -12,13 +12,18 @@ import TRANSFER_TYPES from '../constants/transfer-types'
|
|||||||
const FRAGMENT_MATCHER = / data-slate-fragment="([^\s"]+)"/
|
const FRAGMENT_MATCHER = / data-slate-fragment="([^\s"]+)"/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the data and type from a native data `transfer`.
|
* Get the transfer data from an `event`.
|
||||||
*
|
*
|
||||||
* @param {DataTransfer} transfer
|
* @param {Event} event
|
||||||
* @return {Object}
|
* @return {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function getTransferData(transfer) {
|
function getEventTransfer(event) {
|
||||||
|
if (event.nativeEvent) {
|
||||||
|
event = event.nativeEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
const transfer = event.dataTransfer || event.clipboardData
|
||||||
let fragment = getType(transfer, TRANSFER_TYPES.FRAGMENT)
|
let fragment = getType(transfer, TRANSFER_TYPES.FRAGMENT)
|
||||||
let node = getType(transfer, TRANSFER_TYPES.NODE)
|
let node = getType(transfer, TRANSFER_TYPES.NODE)
|
||||||
const html = getType(transfer, 'text/html')
|
const html = getType(transfer, 'text/html')
|
||||||
@@ -148,4 +153,4 @@ function getType(transfer, type) {
|
|||||||
* @type {Function}
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default getTransferData
|
export default getEventTransfer
|
Reference in New Issue
Block a user