1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-20 14:11:35 +02:00

add ability to drag/drop across slate instances

This commit is contained in:
Ian Storm Taylor
2016-07-22 21:16:42 -07:00
parent 3098c35bec
commit b65e218b25
4 changed files with 93 additions and 49 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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
View 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
}