mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-04-21 13:51:59 +02:00
parent
a5dc3b7c8d
commit
4e177e4092
@ -6,7 +6,7 @@ import OffsetKey from '../utils/offset-key'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import Selection from '../models/selection'
|
||||
import Transfer from '../utils/transfer'
|
||||
import getTransferData from '../utils/get-transfer-data'
|
||||
import TYPES from '../constants/types'
|
||||
import getWindow from 'get-window'
|
||||
import keycode from 'keycode'
|
||||
@ -328,10 +328,10 @@ class Content extends React.Component {
|
||||
if (!this.isInContentEditable(event)) return
|
||||
|
||||
const { dataTransfer } = event.nativeEvent
|
||||
const transfer = new Transfer(dataTransfer)
|
||||
const data = getTransferData(dataTransfer)
|
||||
|
||||
// Prevent default when nodes are dragged to allow dropping.
|
||||
if (transfer.getType() == 'node') {
|
||||
if (data.type == 'node') {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
@ -354,10 +354,10 @@ class Content extends React.Component {
|
||||
this.tmp.isDragging = true
|
||||
this.tmp.isInternalDrag = true
|
||||
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 (transfer.getType() == 'node') return
|
||||
if (data.type == 'node') return
|
||||
|
||||
const { state } = this.props
|
||||
const { fragment } = state
|
||||
@ -383,8 +383,7 @@ class Content extends React.Component {
|
||||
const { state } = this.props
|
||||
const { nativeEvent } = event
|
||||
const { dataTransfer, x, y } = nativeEvent
|
||||
const transfer = new Transfer(dataTransfer)
|
||||
const data = transfer.getData()
|
||||
const data = getTransferData(dataTransfer)
|
||||
|
||||
// Resolve the point where the drop occured.
|
||||
let range
|
||||
@ -584,8 +583,7 @@ class Content extends React.Component {
|
||||
if (!this.isInContentEditable(event)) return
|
||||
|
||||
event.preventDefault()
|
||||
const transfer = new Transfer(event.clipboardData)
|
||||
const data = transfer.getData()
|
||||
const data = getTransferData(event.clipboardData)
|
||||
|
||||
// Attach the `isShift` flag, so that people can use it to trigger "Paste
|
||||
// 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
|
Loading…
x
Reference in New Issue
Block a user