mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-01 21:10:14 +02:00
@@ -6,7 +6,7 @@ import OffsetKey from '../utils/offset-key'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import Selection from '../models/selection'
|
import Selection from '../models/selection'
|
||||||
import Transfer from '../utils/transfer'
|
import getTransferData from '../utils/get-transfer-data'
|
||||||
import TYPES from '../constants/types'
|
import TYPES from '../constants/types'
|
||||||
import getWindow from 'get-window'
|
import getWindow from 'get-window'
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
@@ -328,10 +328,10 @@ class Content extends React.Component {
|
|||||||
if (!this.isInContentEditable(event)) return
|
if (!this.isInContentEditable(event)) return
|
||||||
|
|
||||||
const { dataTransfer } = event.nativeEvent
|
const { dataTransfer } = event.nativeEvent
|
||||||
const transfer = new Transfer(dataTransfer)
|
const data = getTransferData(dataTransfer)
|
||||||
|
|
||||||
// Prevent default when nodes are dragged to allow dropping.
|
// Prevent default when nodes are dragged to allow dropping.
|
||||||
if (transfer.getType() == 'node') {
|
if (data.type == 'node') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,10 +354,10 @@ class Content extends React.Component {
|
|||||||
this.tmp.isDragging = true
|
this.tmp.isDragging = true
|
||||||
this.tmp.isInternalDrag = true
|
this.tmp.isInternalDrag = true
|
||||||
const { dataTransfer } = event.nativeEvent
|
const { dataTransfer } = event.nativeEvent
|
||||||
const transfer = new Transfer(dataTransfer)
|
const data = getTransferData(dataTransfer)
|
||||||
|
|
||||||
// If it's a node being dragged, the data type is already set.
|
// If it's a node being dragged, the data type is already set.
|
||||||
if (transfer.getType() == 'node') return
|
if (data.type == 'node') return
|
||||||
|
|
||||||
const { state } = this.props
|
const { state } = this.props
|
||||||
const { fragment } = state
|
const { fragment } = state
|
||||||
@@ -383,8 +383,7 @@ class Content extends React.Component {
|
|||||||
const { state } = this.props
|
const { state } = this.props
|
||||||
const { nativeEvent } = event
|
const { nativeEvent } = event
|
||||||
const { dataTransfer, x, y } = nativeEvent
|
const { dataTransfer, x, y } = nativeEvent
|
||||||
const transfer = new Transfer(dataTransfer)
|
const data = getTransferData(dataTransfer)
|
||||||
const data = transfer.getData()
|
|
||||||
|
|
||||||
// Resolve the point where the drop occured.
|
// Resolve the point where the drop occured.
|
||||||
let range
|
let range
|
||||||
@@ -584,8 +583,7 @@ class Content extends React.Component {
|
|||||||
if (!this.isInContentEditable(event)) return
|
if (!this.isInContentEditable(event)) return
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const transfer = new Transfer(event.clipboardData)
|
const data = getTransferData(event.clipboardData)
|
||||||
const data = transfer.getData()
|
|
||||||
|
|
||||||
// Attach the `isShift` flag, so that people can use it to trigger "Paste
|
// Attach the `isShift` flag, so that people can use it to trigger "Paste
|
||||||
// and Match Style" logic.
|
// and Match Style" logic.
|
||||||
|
93
src/utils/get-transfer-data.js
Normal file
93
src/utils/get-transfer-data.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
import Base64 from '../serializers/base-64'
|
||||||
|
import TYPES from '../constants/types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment matching regexp for HTML nodes.
|
||||||
|
*
|
||||||
|
* @type {RegExp}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const FRAGMENT_MATCHER = /data-slate-fragment="([^\s]+)"/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data and type from a native data `transfer`.
|
||||||
|
*
|
||||||
|
* @param {DataTransfer} transfer
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getTransferData(transfer) {
|
||||||
|
let fragment = transfer.getData(TYPES.FRAGMENT) || null
|
||||||
|
let node = transfer.getData(TYPES.NODE) || null
|
||||||
|
let html = transfer.getData('text/html') || null
|
||||||
|
let rich = transfer.getData('text/rtf') || null
|
||||||
|
let text = transfer.getData('text/plain') || null
|
||||||
|
let files
|
||||||
|
|
||||||
|
// If there isn't a fragment, but there is HTML, check to see if the HTML is
|
||||||
|
// actually an encoded fragment.
|
||||||
|
if (
|
||||||
|
!fragment &&
|
||||||
|
html &&
|
||||||
|
~html.indexOf('<span data-slate-fragment="')
|
||||||
|
) {
|
||||||
|
const matches = FRAGMENT_MATCHER.exec(html)
|
||||||
|
const [ full, encoded ] = matches // eslint-disable-line no-unused-vars
|
||||||
|
if (encoded) fragment = encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a fragment or node if they exist.
|
||||||
|
if (fragment) fragment = Base64.deserializeNode(fragment)
|
||||||
|
if (node) node = Base64.deserializeNode(node)
|
||||||
|
|
||||||
|
// Get and normalize files if they exist.
|
||||||
|
if (transfer.items && transfer.items.length) {
|
||||||
|
const fileItems = Array.from(transfer.items)
|
||||||
|
.map(item => item.kind == 'file' ? item.getAsFile() : null)
|
||||||
|
.filter(exists => exists)
|
||||||
|
|
||||||
|
if (fileItems.length) files = fileItems
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transfer.files && transfer.files.length) {
|
||||||
|
files = Array.from(files)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the type of the data.
|
||||||
|
const data = { files, fragment, html, node, rich, text }
|
||||||
|
data.type = getTransferType(data)
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the type of a transfer from its `data`.
|
||||||
|
*
|
||||||
|
* @param {Object} data
|
||||||
|
* @return {String}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function getTransferType(data) {
|
||||||
|
if (data.fragment) return 'fragment'
|
||||||
|
if (data.node) return 'node'
|
||||||
|
|
||||||
|
// COMPAT: Microsoft Word adds an image of the selected text to the data.
|
||||||
|
// Since files are preferred over HTML or text, this would cause the type to
|
||||||
|
// be considered `files`. But it also adds rich text data so we can check
|
||||||
|
// for that and properly set the type to `html` or `text`. (2016/11/21)
|
||||||
|
if (data.rich && data.html) return 'html'
|
||||||
|
if (data.rich && data.text) return 'text'
|
||||||
|
|
||||||
|
if (data.files) return 'files'
|
||||||
|
if (data.html) return 'html'
|
||||||
|
if (data.text) return 'text'
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export.
|
||||||
|
*
|
||||||
|
* @type {Function}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default getTransferData
|
@@ -1,287 +0,0 @@
|
|||||||
|
|
||||||
import Base64 from '../serializers/base-64'
|
|
||||||
import TYPES from '../constants/types'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fragment matching regexp for HTML nodes.
|
|
||||||
*
|
|
||||||
* @type {RegExp}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const FRAGMENT_MATCHER = /data-slate-fragment="([^\s]+)"/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Data transfer helper.
|
|
||||||
*
|
|
||||||
* @type {Transfer}
|
|
||||||
*/
|
|
||||||
|
|
||||||
class Transfer {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param {DataTransfer} data
|
|
||||||
*/
|
|
||||||
|
|
||||||
constructor(data) {
|
|
||||||
this.data = data
|
|
||||||
this.cache = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a data object representing the transfer's primary content type.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getData() {
|
|
||||||
const type = this.getType()
|
|
||||||
const data = {}
|
|
||||||
data.type = type
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'files':
|
|
||||||
data.files = this.getFiles()
|
|
||||||
break
|
|
||||||
case 'fragment':
|
|
||||||
data.fragment = this.getFragment()
|
|
||||||
break
|
|
||||||
case 'html':
|
|
||||||
data.html = this.getHtml()
|
|
||||||
data.text = this.getText()
|
|
||||||
break
|
|
||||||
case 'node':
|
|
||||||
data.node = this.getNode()
|
|
||||||
break
|
|
||||||
case 'text':
|
|
||||||
data.text = this.getText()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the Files content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {Array|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getFiles() {
|
|
||||||
if ('files' in this.cache) return this.cache.files
|
|
||||||
|
|
||||||
const { data } = this
|
|
||||||
let files
|
|
||||||
|
|
||||||
if (data.items && data.items.length) {
|
|
||||||
const fileItems = Array.from(data.items)
|
|
||||||
.map(item => item.kind == 'file' ? item.getAsFile() : null)
|
|
||||||
.filter(exists => exists)
|
|
||||||
|
|
||||||
if (fileItems.length) files = fileItems
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.files && data.files.length) {
|
|
||||||
files = Array.from(data.files)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache.files = files
|
|
||||||
return files
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the Slate document fragment content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {Document || Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getFragment() {
|
|
||||||
if ('fragment' in this.cache) return this.cache.fragment
|
|
||||||
|
|
||||||
const html = this.getHtml()
|
|
||||||
let encoded = this.data.getData(TYPES.FRAGMENT)
|
|
||||||
let fragment
|
|
||||||
|
|
||||||
// If there's html content, and the html includes a `data-fragment`
|
|
||||||
// attribute, it's actually a Base64-serialized fragment from a cut/copy.
|
|
||||||
if (!encoded && html && ~html.indexOf('<span data-slate-fragment="')) {
|
|
||||||
const matches = FRAGMENT_MATCHER.exec(html)
|
|
||||||
const [ full, attribute ] = matches // eslint-disable-line no-unused-vars
|
|
||||||
encoded = attribute
|
|
||||||
}
|
|
||||||
|
|
||||||
if (encoded) {
|
|
||||||
fragment = Base64.deserializeNode(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache.fragment = fragment
|
|
||||||
return fragment
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the HTML content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {String|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getHtml() {
|
|
||||||
if ('html' in this.cache) return this.cache.html
|
|
||||||
|
|
||||||
let html
|
|
||||||
const string = this.data.getData('text/html')
|
|
||||||
|
|
||||||
if (string != '') html = string
|
|
||||||
|
|
||||||
this.cache.html = html
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the Slate node content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {Node|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getNode() {
|
|
||||||
if ('node' in this.cache) return this.cache.node
|
|
||||||
|
|
||||||
const encoded = this.data.getData(TYPES.NODE)
|
|
||||||
let node
|
|
||||||
|
|
||||||
if (encoded) {
|
|
||||||
node = Base64.deserializeNode(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cache.node = node
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the rich text content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {String|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getRichText() {
|
|
||||||
if ('richtext' in this.cache) return this.cache.richtext
|
|
||||||
|
|
||||||
let richtext
|
|
||||||
const string = this.data.getData('text/rtf')
|
|
||||||
|
|
||||||
if (string != '') richtext = string
|
|
||||||
|
|
||||||
this.cache.richtext = richtext
|
|
||||||
return richtext
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the text content of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {String|Void}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getText() {
|
|
||||||
if ('text' in this.cache) return this.cache.text
|
|
||||||
|
|
||||||
let text
|
|
||||||
const string = this.data.getData('text/plain')
|
|
||||||
|
|
||||||
if (string != '') text = string
|
|
||||||
|
|
||||||
this.cache.text = text
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the primary type of the data transfer.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
*/
|
|
||||||
|
|
||||||
getType() {
|
|
||||||
if (this.hasFragment()) return 'fragment'
|
|
||||||
if (this.hasNode()) return 'node'
|
|
||||||
|
|
||||||
// COMPAT: Microsoft Word adds an image of the selected text to the data.
|
|
||||||
// Since files are preferred over HTML or text, this would cause the type to
|
|
||||||
// be considered `files`. But it also adds rich text data so we can check
|
|
||||||
// for that and properly set the type to `html` or `text`. (2016/11/21)
|
|
||||||
if (this.hasRichText() && this.hasHtml()) return 'html'
|
|
||||||
if (this.hasRichText() && this.hasText()) return 'text'
|
|
||||||
|
|
||||||
if (this.hasFiles()) return 'files'
|
|
||||||
if (this.hasHtml()) return 'html'
|
|
||||||
if (this.hasText()) return 'text'
|
|
||||||
return 'unknown'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has File content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasFiles() {
|
|
||||||
return this.getFiles() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has HTML content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasHtml() {
|
|
||||||
return this.getHtml() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has rich text content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasRichText() {
|
|
||||||
return this.getRichText() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has text content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasText() {
|
|
||||||
return this.getText() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has a Slate document fragment as content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasFragment() {
|
|
||||||
return this.getFragment() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the data transfer has a Slate node as content.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
|
|
||||||
hasNode() {
|
|
||||||
return this.getNode() != null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export.
|
|
||||||
*
|
|
||||||
* @type {Transfer}
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default Transfer
|
|
Reference in New Issue
Block a user