mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-21 06:31:28 +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 Key from '../utils/key'
|
||||||
import Selection from '../models/selection'
|
|
||||||
import OffsetKey from '../utils/offset-key'
|
import OffsetKey from '../utils/offset-key'
|
||||||
import Raw from '../serializers/raw'
|
import Raw from '../serializers/raw'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import Selection from '../models/selection'
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
import includes from 'lodash/includes'
|
import includes from 'lodash/includes'
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
import Base64 from '../utils/base64'
|
|
||||||
import { IS_FIREFOX } from '../utils/environment'
|
import { IS_FIREFOX } from '../utils/environment'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Noop.
|
* Noop.
|
||||||
|
*
|
||||||
|
* @type {Function}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content types.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const TYPES = {
|
||||||
|
HTML: 'text/html',
|
||||||
|
SLATE: 'application/x-slate',
|
||||||
|
TEXT: 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content.
|
* Content.
|
||||||
|
*
|
||||||
|
* @type {Component}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class Content extends React.Component {
|
class Content extends React.Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Property types.
|
* Property types.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@@ -44,6 +62,8 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Default properties.
|
* Default properties.
|
||||||
|
*
|
||||||
|
* @type {Object}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@@ -215,9 +235,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
const { state } = this.props
|
const { state } = this.props
|
||||||
const { fragment } = state
|
const { fragment } = state
|
||||||
const raw = Raw.serializeNode(fragment)
|
const encoded = Fragment.serialize(fragment)
|
||||||
const string = JSON.stringify(raw)
|
|
||||||
const encoded = Base64.encode(string)
|
|
||||||
|
|
||||||
// Wrap the first character of the selection in a span that has the encoded
|
// 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.
|
// 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) => {
|
onDragStart = (e) => {
|
||||||
this.tmp.isDragging = true
|
this.tmp.isDragging = true
|
||||||
this.tmp.isInternalDrag = 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)
|
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
|
||||||
const types = Array.from(data.types)
|
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.
|
// Handle files.
|
||||||
if (data.files.length) {
|
else if (data.files.length) {
|
||||||
drop.type = 'files'
|
drop.type = 'files'
|
||||||
drop.files = data.files
|
drop.files = data.files
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle HTML.
|
// Handle HTML.
|
||||||
else if (includes(types, 'text/html')) {
|
else if (includes(types, TYPES.HTML)) {
|
||||||
drop.type = 'html'
|
drop.type = 'html'
|
||||||
drop.text = data.getData('text/plain')
|
drop.text = data.getData(TYPES.TEXT)
|
||||||
drop.html = data.getData('text/html')
|
drop.html = data.getData(TYPES.HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle plain text.
|
// Handle plain text.
|
||||||
else {
|
else {
|
||||||
drop.type = 'text'
|
drop.type = 'text'
|
||||||
drop.text = data.getData('text/plain')
|
drop.text = data.getData(TYPES.TEXT)
|
||||||
}
|
}
|
||||||
|
|
||||||
drop.data = data
|
drop.data = data
|
||||||
@@ -479,16 +510,16 @@ class Content extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Treat it as rich text if there is HTML content.
|
// 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.type = 'html'
|
||||||
paste.text = data.getData('text/plain')
|
paste.text = data.getData(TYPES.TEXT)
|
||||||
paste.html = data.getData('text/html')
|
paste.html = data.getData(TYPES.HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Treat everything else as plain text.
|
// Treat everything else as plain text.
|
||||||
else {
|
else {
|
||||||
paste.type = 'text'
|
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
|
// 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 regexp = /data-fragment="([^\s]+)"/
|
||||||
const matches = regexp.exec(paste.html)
|
const matches = regexp.exec(paste.html)
|
||||||
const [ full, encoded ] = matches
|
const [ full, encoded ] = matches
|
||||||
const string = Base64.decode(encoded)
|
const fragment = Fragment.deserialize(encoded)
|
||||||
const json = JSON.parse(string)
|
|
||||||
const fragment = Raw.deserialize(json)
|
|
||||||
let { state } = this.props
|
let { state } = this.props
|
||||||
|
|
||||||
state = state
|
state = state
|
||||||
.transform()
|
.transform()
|
||||||
.insertFragment(fragment.document)
|
.insertFragment(fragment)
|
||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
this.onChange(state)
|
this.onChange(state)
|
||||||
|
@@ -150,6 +150,13 @@ function Plugin(options = {}) {
|
|||||||
|
|
||||||
onDrop(e, drop, state, editor) {
|
onDrop(e, drop, state, editor) {
|
||||||
switch (drop.type) {
|
switch (drop.type) {
|
||||||
|
case 'fragment': {
|
||||||
|
return state
|
||||||
|
.transform()
|
||||||
|
.moveTo(drop.target)
|
||||||
|
.insertFragment(drop.fragment)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'html': {
|
case 'html': {
|
||||||
let transform = state
|
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