mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-20 06:01:24 +02:00
add ability to drag/drop across slate instances
This commit is contained in:
@@ -1,29 +1,47 @@
|
||||
|
||||
import Fragment from '../utils/fragment'
|
||||
import Key from '../utils/key'
|
||||
import Selection from '../models/selection'
|
||||
import OffsetKey from '../utils/offset-key'
|
||||
import Raw from '../serializers/raw'
|
||||
import React from 'react'
|
||||
import Selection from '../models/selection'
|
||||
import Text from './text'
|
||||
import includes from 'lodash/includes'
|
||||
import keycode from 'keycode'
|
||||
import Base64 from '../utils/base64'
|
||||
import { IS_FIREFOX } from '../utils/environment'
|
||||
|
||||
/**
|
||||
* Noop.
|
||||
*
|
||||
* @type {Function}
|
||||
*/
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* Content types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
HTML: 'text/html',
|
||||
SLATE: 'application/x-slate',
|
||||
TEXT: 'text/plain'
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class Content extends React.Component {
|
||||
|
||||
/**
|
||||
* Property types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static propTypes = {
|
||||
@@ -44,6 +62,8 @@ class Content extends React.Component {
|
||||
|
||||
/**
|
||||
* Default properties.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
static defaultProps = {
|
||||
@@ -215,9 +235,7 @@ class Content extends React.Component {
|
||||
|
||||
const { state } = this.props
|
||||
const { fragment } = state
|
||||
const raw = Raw.serializeNode(fragment)
|
||||
const string = JSON.stringify(raw)
|
||||
const encoded = Base64.encode(string)
|
||||
const encoded = Fragment.serialize(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.
|
||||
@@ -291,6 +309,11 @@ class Content extends React.Component {
|
||||
onDragStart = (e) => {
|
||||
this.tmp.isDragging = true
|
||||
this.tmp.isInternalDrag = true
|
||||
const { state } = this.props
|
||||
const { fragment } = state
|
||||
const encoded = Fragment.serialize(fragment)
|
||||
const data = e.nativeEvent.dataTransfer
|
||||
data.setData(TYPES.SLATE, encoded)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,23 +377,31 @@ class Content extends React.Component {
|
||||
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
|
||||
const types = Array.from(data.types)
|
||||
|
||||
// Handle external Slate drags.
|
||||
if (includes(types, TYPES.SLATE)) {
|
||||
const encoded = data.getData(TYPES.SLATE)
|
||||
const fragment = Fragment.deserialize(encoded)
|
||||
drop.type = 'fragment'
|
||||
drop.fragment = fragment
|
||||
}
|
||||
|
||||
// Handle files.
|
||||
if (data.files.length) {
|
||||
else if (data.files.length) {
|
||||
drop.type = 'files'
|
||||
drop.files = data.files
|
||||
}
|
||||
|
||||
// Handle HTML.
|
||||
else if (includes(types, 'text/html')) {
|
||||
else if (includes(types, TYPES.HTML)) {
|
||||
drop.type = 'html'
|
||||
drop.text = data.getData('text/plain')
|
||||
drop.html = data.getData('text/html')
|
||||
drop.text = data.getData(TYPES.TEXT)
|
||||
drop.html = data.getData(TYPES.HTML)
|
||||
}
|
||||
|
||||
// Handle plain text.
|
||||
else {
|
||||
drop.type = 'text'
|
||||
drop.text = data.getData('text/plain')
|
||||
drop.text = data.getData(TYPES.TEXT)
|
||||
}
|
||||
|
||||
drop.data = data
|
||||
@@ -479,16 +510,16 @@ class Content extends React.Component {
|
||||
}
|
||||
|
||||
// Treat it as rich text if there is HTML content.
|
||||
else if (includes(types, 'text/html')) {
|
||||
else if (includes(types, TYPES.HTML)) {
|
||||
paste.type = 'html'
|
||||
paste.text = data.getData('text/plain')
|
||||
paste.html = data.getData('text/html')
|
||||
paste.text = data.getData(TYPES.TEXT)
|
||||
paste.html = data.getData(TYPES.HTML)
|
||||
}
|
||||
|
||||
// Treat everything else as plain text.
|
||||
else {
|
||||
paste.type = 'text'
|
||||
paste.text = data.getData('text/plain')
|
||||
paste.text = data.getData(TYPES.TEXT)
|
||||
}
|
||||
|
||||
// If html, and the html includes a `data-fragment` attribute, it's actually
|
||||
@@ -498,14 +529,12 @@ class Content extends React.Component {
|
||||
const regexp = /data-fragment="([^\s]+)"/
|
||||
const matches = regexp.exec(paste.html)
|
||||
const [ full, encoded ] = matches
|
||||
const string = Base64.decode(encoded)
|
||||
const json = JSON.parse(string)
|
||||
const fragment = Raw.deserialize(json)
|
||||
const fragment = Fragment.deserialize(encoded)
|
||||
let { state } = this.props
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.insertFragment(fragment.document)
|
||||
.insertFragment(fragment)
|
||||
.apply()
|
||||
|
||||
this.onChange(state)
|
||||
|
@@ -150,6 +150,13 @@ function Plugin(options = {}) {
|
||||
|
||||
onDrop(e, drop, state, editor) {
|
||||
switch (drop.type) {
|
||||
case 'fragment': {
|
||||
return state
|
||||
.transform()
|
||||
.moveTo(drop.target)
|
||||
.insertFragment(drop.fragment)
|
||||
.apply()
|
||||
}
|
||||
case 'text':
|
||||
case 'html': {
|
||||
let transform = state
|
||||
|
@@ -1,31 +0,0 @@
|
||||
|
||||
/**
|
||||
* Encode a `string` as Base64.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function encode(string) {
|
||||
return window.btoa(window.unescape(window.encodeURIComponent(string)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a `string` as Base64.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {String}
|
||||
*/
|
||||
|
||||
function decode(string) {
|
||||
return window.decodeURIComponent(window.escape(window.atob(string)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default {
|
||||
encode,
|
||||
decode
|
||||
}
|
39
lib/utils/fragment.js
Normal file
39
lib/utils/fragment.js
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
import Raw from '../serializers/raw'
|
||||
|
||||
/**
|
||||
* Serialize a `string` as Base64.
|
||||
*
|
||||
* @param {Document} fragment
|
||||
* @return {String} encoded
|
||||
*/
|
||||
|
||||
function serialize(fragment) {
|
||||
const raw = Raw.serializeNode(fragment)
|
||||
const string = JSON.stringify(raw)
|
||||
const encoded = window.btoa(window.unescape(window.encodeURIComponent(string)))
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a `fragment` as Base64.
|
||||
*
|
||||
* @param {String} encoded
|
||||
* @return {Document} fragment
|
||||
*/
|
||||
|
||||
function deserialize(encoded) {
|
||||
const string = window.decodeURIComponent(window.escape(window.atob(encoded)))
|
||||
const json = JSON.parse(string)
|
||||
const state = Raw.deserialize(json)
|
||||
return state.document
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default {
|
||||
serialize,
|
||||
deserialize
|
||||
}
|
Reference in New Issue
Block a user