mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-01 04:50:27 +02:00
add node component, cleanup draggable/void interactions
This commit is contained in:
@@ -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'
|
||||
|
@@ -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 <img draggable src={src} />
|
||||
})
|
||||
return (
|
||||
<Void {...props} className="image-block">
|
||||
<img src={src} {...props.attributes} />
|
||||
</Void>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +87,7 @@ class Images extends React.Component {
|
||||
renderNode={this.renderNode}
|
||||
onChange={this.onChange}
|
||||
onDocumentChange={this.onDocumentChange}
|
||||
onDrop={this.onDrop}
|
||||
onPaste={this.onPaste}
|
||||
/>
|
||||
</div>
|
||||
@@ -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.
|
||||
*
|
||||
|
@@ -112,7 +112,19 @@ class App extends React.Component {
|
||||
const router = (
|
||||
<Router history={hashHistory}>
|
||||
<Route path="/" component={App}>
|
||||
<IndexRedirect to="rich-text" />
|
||||
<Route path="auto-markdown" component={AutoMarkdown} />
|
||||
<Route path="code-highlighting" component={CodeHighlighting} />
|
||||
<Route path="hovering-menu" component={HoveringMenu} />
|
||||
<Route path="images" component={Images} />
|
||||
<Route path="links" component={Links} />
|
||||
<Route path="paste-html" component={PasteHtml} />
|
||||
<Route path="plain-text" component={PlainText} />
|
||||
<Route path="read-only" component={ReadOnly} />
|
||||
<Route path="rich-text" component={RichText} />
|
||||
<Route path="tables" component={Tables} />
|
||||
<Route path="dev-performance-plain" component={DevPerformancePlain} />
|
||||
<Route path="dev-performance-rich" component={DevPerformanceRich} />
|
||||
</Route>
|
||||
</Router>
|
||||
)
|
||||
|
@@ -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 (
|
||||
<Component
|
||||
attributes={attributes}
|
||||
<Node
|
||||
key={node.key}
|
||||
editor={editor}
|
||||
node={node}
|
||||
state={state}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a text `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
renderText = (node) => {
|
||||
const { editor, renderMark, state } = this.props
|
||||
return (
|
||||
<Text
|
||||
key={node.key}
|
||||
editor={editor}
|
||||
node={node}
|
||||
renderNode={renderNode}
|
||||
renderMark={renderMark}
|
||||
state={state}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@@ -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 (
|
||||
<Tag
|
||||
draggable
|
||||
onDragStart={this.onDragStart}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default Draggable
|
139
lib/components/node.js
Normal file
139
lib/components/node.js
Normal file
@@ -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 (
|
||||
<Node
|
||||
key={node.key}
|
||||
node={node}
|
||||
state={state}
|
||||
editor={editor}
|
||||
renderNode={renderNode}
|
||||
renderMark={renderMark}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<Component
|
||||
attributes={attributes}
|
||||
key={node.key}
|
||||
editor={editor}
|
||||
node={node}
|
||||
state={state}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a text node.
|
||||
*
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
renderText = () => {
|
||||
const { node, editor, renderMark, state } = this.props
|
||||
return (
|
||||
<Text
|
||||
key={node.key}
|
||||
editor={editor}
|
||||
node={node}
|
||||
renderMark={renderMark}
|
||||
state={state}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default Node
|
@@ -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'
|
||||
}
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div contenteditable="true">
|
||||
<div contenteditable="false">
|
||||
<div contenteditable="true" style="position:relative;">
|
||||
<span style="position:absolute;top:0px;right:0px;bottom:0px;left:0px;text-indent:-9999px;">
|
||||
<span style="position:absolute;top:0px;left:-9999px;text-indent:-9999px;">
|
||||
<span>
|
||||
<br>
|
||||
</span>
|
||||
|
Reference in New Issue
Block a user