diff --git a/examples/auto-markdown/index.js b/examples/auto-markdown/index.js
index afca9c65b..d50dae896 100644
--- a/examples/auto-markdown/index.js
+++ b/examples/auto-markdown/index.js
@@ -1,5 +1,5 @@
-import { Editor, Raw, wrap } from '../..'
+import { Editor, Raw } from '../..'
import React from 'react'
import keycode from 'keycode'
import initialState from './state.json'
diff --git a/examples/images/index.js b/examples/images/index.js
index 0045032b0..e11372364 100644
--- a/examples/images/index.js
+++ b/examples/images/index.js
@@ -1,5 +1,5 @@
-import { Editor, Raw, wrap } from '../..'
+import { Editor, Raw, Void } from '../..'
import React from 'react'
import ReactDOM from 'react-dom'
import initialState from './state.json'
@@ -13,11 +13,15 @@ import isUrl from 'is-url'
*/
const NODES = {
- image: wrap()((props) => {
+ image: (props) => {
const { node, state } = props
const src = node.data.get('src')
- return
- })
+ return (
+
+
+
+ )
+ }
}
/**
@@ -83,6 +87,7 @@ class Images extends React.Component {
renderNode={this.renderNode}
onChange={this.onChange}
onDocumentChange={this.onDocumentChange}
+ onDrop={this.onDrop}
onPaste={this.onPaste}
/>
@@ -153,6 +158,25 @@ class Images extends React.Component {
this.onChange(state)
}
+ /**
+ * On drop, insert the image wherever it is dropped.
+ *
+ * @param {Event} e
+ * @param {Object} drop
+ * @param {State} state
+ * @return {State}
+ */
+
+ onDrop = (e, drop, state) => {
+ if (drop.type != 'node') return
+ return state
+ .transform()
+ .removeNodeByKey(drop.node.key)
+ .moveTo(drop.target)
+ .insertBlock(drop.node)
+ .apply()
+ }
+
/**
* On paste, if the pasted content is an image URL, insert it.
*
diff --git a/examples/index.js b/examples/index.js
index d08b70a74..b8144bb57 100644
--- a/examples/index.js
+++ b/examples/index.js
@@ -112,7 +112,19 @@ class App extends React.Component {
const router = (
+
+
+
+
+
+
+
+
+
+
+
+
)
diff --git a/lib/components/content.js b/lib/components/content.js
index 4db502178..8c6a21bb8 100644
--- a/lib/components/content.js
+++ b/lib/components/content.js
@@ -1,11 +1,11 @@
import Base64 from '../serializers/base-64'
import Key from '../utils/key'
+import Node from './node'
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 TYPES from '../utils/types'
import includes from 'lodash/includes'
import keycode from 'keycode'
@@ -341,7 +341,16 @@ class Content extends React.Component {
// Resolve the point where the drop occured.
const { x, y } = e.nativeEvent
- const range = window.document.caretRangeFromPoint(x, y)
+ let range
+
+ // COMPAT: In Firefox, `caretRangeFromPoint` doesn't exist. (2016/07/25)
+ if (window.document.caretRangeFromPoint) {
+ range = window.document.caretRangeFromPoint(x, y)
+ } else {
+ range = window.document.createRange()
+ range.setStart(e.nativeEvent.rangeParent, e.nativeEvent.rangeOffset)
+ }
+
const startNode = range.startContainer
const startOffset = range.startOffset
const point = OffsetKey.findPoint(startNode, startOffset, state)
@@ -359,7 +368,7 @@ class Content extends React.Component {
// Handle Slate fragments.
if (includes(types, TYPES.FRAGMENT)) {
const encoded = data.getData(TYPES.FRAGMENT)
- const fragment = Base64.deserializeDocument(encoded)
+ const fragment = Base64.deserializeNode(encoded)
drop.type = 'fragment'
drop.fragment = fragment
drop.isInternal = this.tmp.isInternalDrag
@@ -395,6 +404,7 @@ class Content extends React.Component {
drop.data = data
drop.target = target
+ drop.effect = data.dropEffect
this.props.onDrop(e, drop)
}
@@ -518,7 +528,7 @@ class Content extends React.Component {
const regexp = /data-fragment="([^\s]+)"/
const matches = regexp.exec(paste.html)
const [ full, encoded ] = matches
- const fragment = Base64.deserializeDocument(encoded)
+ const fragment = Base64.deserializeNode(encoded)
let { state } = this.props
state = state
@@ -653,62 +663,15 @@ class Content extends React.Component {
*/
renderNode = (node) => {
- switch (node.kind) {
- case 'block':
- case 'inline':
- return this.renderElement(node)
- case 'text':
- return this.renderText(node)
- }
- }
-
- /**
- * Render an element `node`.
- *
- * @param {Node} node
- * @return {Element} element
- */
-
- renderElement = (node) => {
- const { editor, renderNode, state } = this.props
- const Component = renderNode(node)
- const children = node.nodes
- .map(child => this.renderNode(child))
- .toArray()
-
- const attributes = {
- 'data-key': node.key
- }
-
+ const { editor, renderMark, renderNode, state } = this.props
return (
-
- {children}
-
- )
- }
-
- /**
- * Render a text `node`.
- *
- * @param {Node} node
- * @return {Element} element
- */
-
- renderText = (node) => {
- const { editor, renderMark, state } = this.props
- return (
-
)
}
diff --git a/lib/components/draggable.js b/lib/components/draggable.js
deleted file mode 100644
index 836d4ef62..000000000
--- a/lib/components/draggable.js
+++ /dev/null
@@ -1,63 +0,0 @@
-
-import Base64 from '../serializers/base-64'
-import React from 'react'
-import TYPES from '../utils/types'
-
-/**
- * Draggable.
- *
- * @type {Component}
- */
-
-class Draggable extends React.Component {
-
- static propTypes = {
- children: React.PropTypes.any.isRequired,
- className: React.PropTypes.string,
- editor: React.PropTypes.object.isRequired,
- node: React.PropTypes.object.isRequired,
- state: React.PropTypes.object.isRequired,
- style: React.PropTypes.object
- };
-
- static defaultProps = {
- style: {}
- }
-
- shouldComponentUpdate = (props) => {
- return (
- props.node != this.props.node ||
- props.state.selection.hasEdgeIn(props.node) ||
- this.props.state.selection.hasEdgeIn(this.props.node)
- )
- }
-
- onDragStart = (e) => {
- const { node } = this.props
- const encoded = Base64.serializeNode(node)
- const data = e.nativeEvent.dataTransfer
- data.setData(TYPES.NODE, encoded)
- }
-
- render = () => {
- const { children, node, className, style } = this.props
- const Tag = node.kind == 'block' ? 'div' : 'span'
- return (
-
- {children}
-
- )
- }
-
-}
-
-/**
- * Export.
- */
-
-export default Draggable
diff --git a/lib/components/node.js b/lib/components/node.js
new file mode 100644
index 000000000..6b05e9740
--- /dev/null
+++ b/lib/components/node.js
@@ -0,0 +1,139 @@
+
+import Base64 from '../serializers/base-64'
+import React from 'react'
+import TYPES from '../utils/types'
+import Text from './text'
+
+/**
+ * Node.
+ *
+ * @type {Component}
+ */
+
+class Node extends React.Component {
+
+ static propTypes = {
+ editor: React.PropTypes.object.isRequired,
+ node: React.PropTypes.object.isRequired,
+ renderMark: React.PropTypes.func.isRequired,
+ renderNode: React.PropTypes.func.isRequired,
+ state: React.PropTypes.object.isRequired
+ };
+
+ static defaultProps = {
+ style: {}
+ }
+
+ shouldComponentUpdate = (props) => {
+ return (
+ props.node != this.props.node ||
+ props.state.selection.hasEdgeIn(props.node)
+ )
+ }
+
+ /**
+ * On drag start, add a serialized representation of the node to the data.
+ *
+ * @param {Event} e
+ */
+
+ onDragStart = (e) => {
+ const { node } = this.props
+ const encoded = Base64.serializeNode(node)
+ const data = e.nativeEvent.dataTransfer
+ data.setData(TYPES.NODE, encoded)
+ }
+
+ /**
+ * Render.
+ *
+ * @return {Element} element
+ */
+
+ render = () => {
+ const { node } = this.props
+ return node.kind == 'text'
+ ? this.renderText()
+ : this.renderElement()
+ }
+
+ /**
+ * Render a `node`.
+ *
+ * @param {Node} node
+ * @return {Element} element
+ */
+
+ renderNode = (node) => {
+ const { editor, renderMark, renderNode, state } = this.props
+ return (
+
+ )
+ }
+
+ /**
+ * Render an element `node`.
+ *
+ * @return {Element} element
+ */
+
+ renderElement = () => {
+ const { editor, node, renderNode, state } = this.props
+ const Component = renderNode(node)
+ const children = node.nodes
+ .map(child => this.renderNode(child))
+ .toArray()
+
+ // Attributes that the developer has to mix into the element in their custom
+ // renderer component.
+ const attributes = {
+ 'data-key': node.key,
+ 'onDragStart': this.onDragStart
+ }
+
+ return (
+
+ {children}
+
+ )
+ }
+
+ /**
+ * Render a text node.
+ *
+ * @return {Element} element
+ */
+
+ renderText = () => {
+ const { node, editor, renderMark, state } = this.props
+ return (
+
+ )
+ }
+
+}
+
+/**
+ * Export.
+ */
+
+export default Node
diff --git a/lib/components/void.js b/lib/components/void.js
index f595e00b2..06c6a6b7e 100644
--- a/lib/components/void.js
+++ b/lib/components/void.js
@@ -28,8 +28,7 @@ class Void extends React.Component {
shouldComponentUpdate = (props) => {
return (
props.node != this.props.node ||
- props.state.selection.hasEdgeIn(props.node) ||
- this.props.state.selection.hasEdgeIn(this.props.node)
+ props.state.selection.hasEdgeIn(props.node)
)
}
@@ -59,15 +58,10 @@ class Void extends React.Component {
}
renderSpacer = () => {
- // Styles that will cause the spacer to be overlaid exactly on top of the
- // void content, so it capture clicks and emulates the same scrolling
- // behavior, but with a negative text indent to hide the cursor.
const style = {
position: 'absolute',
top: '0px',
- right: '0px',
- bottom: '0px',
- left: '0px',
+ left: '-9999px',
textIndent: '-9999px'
}
diff --git a/lib/index.js b/lib/index.js
index 51488e4b9..e17c7a970 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,7 +3,6 @@
* Components.
*/
-import Draggable from './components/draggable'
import Editor from './components/editor'
import Placeholder from './components/placeholder'
import Void from './components/void'
@@ -27,7 +26,6 @@ import Text from './models/text'
*/
import Html from './serializers/html'
-import Json from './serializers/json'
import Plain from './serializers/plain'
import Raw from './serializers/raw'
@@ -52,11 +50,9 @@ export {
Character,
Data,
Document,
- Draggable,
Editor,
Html,
Inline,
- Json,
Mark,
Placeholder,
Plain,
@@ -73,11 +69,9 @@ export default {
Character,
Data,
Document,
- Draggable,
Editor,
Html,
Inline,
- Json,
Mark,
Placeholder,
Plain,
diff --git a/lib/plugins/core.js b/lib/plugins/core.js
index 7a0de5d41..a7abe9a87 100644
--- a/lib/plugins/core.js
+++ b/lib/plugins/core.js
@@ -176,18 +176,6 @@ function Plugin(options = {}) {
.apply()
}
- case 'node': {
- const { node, target, isInternal } = drop
- let transform = state.transform()
-
- if (isInternal) transform = transform.removeNodeByKey(node.key)
-
- return transform
- .moveTo(target)
- [node.kind == 'block' ? 'insertBlock' : 'insertInline'](node)
- .apply()
- }
-
case 'text':
case 'html': {
const { text, target } = drop
diff --git a/test/rendering/fixtures/custom-block-void/output.html b/test/rendering/fixtures/custom-block-void/output.html
index f31b16fd0..5a81ebf40 100644
--- a/test/rendering/fixtures/custom-block-void/output.html
+++ b/test/rendering/fixtures/custom-block-void/output.html
@@ -2,7 +2,7 @@