mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-02 19:52:32 +02:00
add draggable nodes, first steps
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
|
||||
import { Editor, Mark, Raw, Void } from '../..'
|
||||
import { Editor, Draggable, Raw, Void } from '../..'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import initialState from './state.json'
|
||||
import isImage from 'is-image'
|
||||
import isUrl from 'is-url'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
/**
|
||||
* Define a set of node renderers.
|
||||
@@ -18,9 +17,11 @@ const NODES = {
|
||||
const { node, state } = props
|
||||
const src = node.data.get('src')
|
||||
return (
|
||||
<Void {...props} className="image-block">
|
||||
<img {...props.attributes} src={src} />
|
||||
</Void>
|
||||
<Draggable {...props}>
|
||||
<Void {...props} className="image-block">
|
||||
<img {...props.attributes} src={src} />
|
||||
</Void>
|
||||
</Draggable>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -138,6 +138,6 @@ td {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.image-block:focus > * > img {
|
||||
.image-block:focus img {
|
||||
box-shadow: 0 0 0 2px blue;
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
|
||||
import Fragment from '../utils/fragment'
|
||||
import Base64 from '../serializers/base-64'
|
||||
import Key from '../utils/key'
|
||||
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'
|
||||
import { IS_FIREFOX } from '../utils/environment'
|
||||
@@ -18,18 +19,6 @@ import { IS_FIREFOX } from '../utils/environment'
|
||||
|
||||
function noop() {}
|
||||
|
||||
/**
|
||||
* Content types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
HTML: 'text/html',
|
||||
SLATE: 'application/x-slate',
|
||||
TEXT: 'text/plain'
|
||||
}
|
||||
|
||||
/**
|
||||
* Content.
|
||||
*
|
||||
@@ -235,7 +224,7 @@ class Content extends React.Component {
|
||||
|
||||
const { state } = this.props
|
||||
const { fragment } = state
|
||||
const encoded = Fragment.serialize(fragment)
|
||||
const encoded = Base64.serializeNode(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.
|
||||
@@ -286,6 +275,7 @@ class Content extends React.Component {
|
||||
|
||||
onDragEnd = (e) => {
|
||||
this.tmp.isDragging = false
|
||||
this.tmp.isInternalDrag = null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,6 +285,15 @@ class Content extends React.Component {
|
||||
*/
|
||||
|
||||
onDragOver = (e) => {
|
||||
const data = e.nativeEvent.dataTransfer
|
||||
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
|
||||
const types = Array.from(data.types)
|
||||
|
||||
// Prevent default when nodes are dragged to allow dropping.
|
||||
if (includes(types, TYPES.NODE)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
if (this.tmp.isDragging) return
|
||||
this.tmp.isDragging = true
|
||||
this.tmp.isInternalDrag = false
|
||||
@@ -309,11 +308,17 @@ class Content extends React.Component {
|
||||
onDragStart = (e) => {
|
||||
this.tmp.isDragging = true
|
||||
this.tmp.isInternalDrag = true
|
||||
const data = e.nativeEvent.dataTransfer
|
||||
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
|
||||
const types = Array.from(data.types)
|
||||
|
||||
// If it's a node being dragged, the data type is already set.
|
||||
if (includes(types, TYPES.NODE)) return
|
||||
|
||||
const { state } = this.props
|
||||
const { fragment } = state
|
||||
const encoded = Fragment.serialize(fragment)
|
||||
const data = e.nativeEvent.dataTransfer
|
||||
data.setData(TYPES.SLATE, encoded)
|
||||
const encoded = Base64.serializeNode(fragment)
|
||||
data.setData(TYPES.FRAGMENT, encoded)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -331,13 +336,16 @@ class Content extends React.Component {
|
||||
const data = e.nativeEvent.dataTransfer
|
||||
const drop = {}
|
||||
|
||||
// COMPAT: In Firefox, `types` is array-like. (2016/06/21)
|
||||
const types = Array.from(data.types)
|
||||
|
||||
// Resolve the point where the drop occured.
|
||||
const { x, y } = e.nativeEvent
|
||||
const range = window.document.caretRangeFromPoint(x, y)
|
||||
const startNode = range.startContainer
|
||||
const startOffset = range.startOffset
|
||||
const point = OffsetKey.findPoint(startNode, startOffset, state)
|
||||
let target = Selection.create({
|
||||
const target = Selection.create({
|
||||
anchorKey: point.key,
|
||||
anchorOffset: point.offset,
|
||||
focusKey: point.key,
|
||||
@@ -348,41 +356,22 @@ class Content extends React.Component {
|
||||
// If the target is inside a void node, abort.
|
||||
if (state.document.hasVoidParent(point.key)) return
|
||||
|
||||
// If the drag is internal, handle it now. And it the target is after the
|
||||
// selection, it needs to account for the selection's content being deleted.
|
||||
if (this.tmp.isInternalDrag) {
|
||||
if (
|
||||
selection.endKey == target.endKey &&
|
||||
selection.endOffset < target.endOffset
|
||||
) {
|
||||
const width = selection.startKey == selection.endKey
|
||||
? selection.endOffset - selection.startOffset
|
||||
: selection.endOffset
|
||||
|
||||
target = target.moveBackward(width)
|
||||
}
|
||||
|
||||
const fragment = state.fragment
|
||||
const next = state
|
||||
.transform()
|
||||
.delete()
|
||||
.moveTo(target)
|
||||
.insertFragment(fragment)
|
||||
.apply()
|
||||
|
||||
this.onChange(next)
|
||||
return
|
||||
}
|
||||
|
||||
// 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)
|
||||
// Handle Slate fragments.
|
||||
if (includes(types, TYPES.FRAGMENT)) {
|
||||
const encoded = data.getData(TYPES.FRAGMENT)
|
||||
const fragment = Base64.deserializeDocument(encoded)
|
||||
drop.type = 'fragment'
|
||||
drop.fragment = fragment
|
||||
drop.isInternal = this.tmp.isInternalDrag
|
||||
}
|
||||
|
||||
// Handle Slate nodes.
|
||||
else if (includes(types, TYPES.NODE)) {
|
||||
const encoded = data.getData(TYPES.NODE)
|
||||
const node = Base64.deserializeNode(encoded)
|
||||
drop.type = 'node'
|
||||
drop.node = node
|
||||
drop.isInternal = this.tmp.isInternalDrag
|
||||
}
|
||||
|
||||
// Handle files.
|
||||
@@ -529,7 +518,7 @@ class Content extends React.Component {
|
||||
const regexp = /data-fragment="([^\s]+)"/
|
||||
const matches = regexp.exec(paste.html)
|
||||
const [ full, encoded ] = matches
|
||||
const fragment = Fragment.deserialize(encoded)
|
||||
const fragment = Base64.deserializeDocument(encoded)
|
||||
let { state } = this.props
|
||||
|
||||
state = state
|
||||
|
63
lib/components/draggable.js
Normal file
63
lib/components/draggable.js
Normal file
@@ -0,0 +1,63 @@
|
||||
|
||||
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
|
@@ -3,6 +3,7 @@
|
||||
* Components.
|
||||
*/
|
||||
|
||||
import Draggable from './components/draggable'
|
||||
import Editor from './components/editor'
|
||||
import Placeholder from './components/placeholder'
|
||||
import Void from './components/void'
|
||||
@@ -50,6 +51,7 @@ export {
|
||||
Character,
|
||||
Data,
|
||||
Document,
|
||||
Draggable,
|
||||
Editor,
|
||||
Html,
|
||||
Inline,
|
||||
@@ -69,6 +71,7 @@ export default {
|
||||
Character,
|
||||
Data,
|
||||
Document,
|
||||
Draggable,
|
||||
Editor,
|
||||
Html,
|
||||
Inline,
|
||||
|
@@ -78,6 +78,20 @@ class Block extends new Record(DEFAULTS) {
|
||||
return 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node empty?
|
||||
*
|
||||
* @return {Boolean} isEmpty
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return (
|
||||
this.nodes.size == 1 &&
|
||||
this.nodes.first().kind == 'text' &&
|
||||
this.length == 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
|
@@ -78,6 +78,20 @@ class Inline extends new Record(DEFAULTS) {
|
||||
return 'inline'
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node empty?
|
||||
*
|
||||
* @return {Boolean} isEmpty
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return (
|
||||
this.nodes.size == 1 &&
|
||||
this.nodes.first().kind == 'text' &&
|
||||
this.length == 0
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
|
@@ -654,6 +654,31 @@ class State extends new Record(DEFAULTS) {
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `block` at the current selection.
|
||||
*
|
||||
* @param {String || Object || Block} block
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
insertBlock(block) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
let after = selection
|
||||
|
||||
// Insert the block
|
||||
document = document.insertBlockAtRange(selection, block)
|
||||
|
||||
// Determine what the selection should be after inserting.
|
||||
const keys = state.document.getTexts().map(text => text.key)
|
||||
const text = document.getTexts().find(n => !keys.includes(n.key))
|
||||
selection = selection.collapseToEndOf(text)
|
||||
|
||||
// Update the document and selection.
|
||||
state = state.merge({ document, selection })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a `fragment` at the current selection.
|
||||
*
|
||||
|
@@ -70,6 +70,16 @@ class Text extends new Record(DEFAULTS) {
|
||||
return 'text'
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the node empty?
|
||||
*
|
||||
* @return {Boolean} isEmpty
|
||||
*/
|
||||
|
||||
get isEmpty() {
|
||||
return this.length == 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the concatenated text of the node.
|
||||
*
|
||||
|
@@ -30,7 +30,9 @@ const DOCUMENT_RANGE_TRANSFORMS = [
|
||||
'deleteAtRange',
|
||||
'deleteBackwardAtRange',
|
||||
'deleteForwardAtRange',
|
||||
'insertBlockAtRange',
|
||||
'insertFragmentAtRange',
|
||||
'insertInlineAtRange',
|
||||
'insertTextAtRange',
|
||||
'addMarkAtRange',
|
||||
'setBlockAtRange',
|
||||
@@ -84,7 +86,9 @@ const STATE_DOCUMENT_TRANSFORMS = [
|
||||
'delete',
|
||||
'deleteBackward',
|
||||
'deleteForward',
|
||||
'insertBlock',
|
||||
'insertFragment',
|
||||
'insertInline',
|
||||
'insertText',
|
||||
'addMark',
|
||||
'setBlock',
|
||||
|
@@ -18,6 +18,45 @@ import { List, Map, Set } from 'immutable'
|
||||
|
||||
const Transforms = {
|
||||
|
||||
/**
|
||||
* Add a new `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark or String} mark
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
addMarkAtRange(range, mark) {
|
||||
mark = normalizeMark(mark)
|
||||
let node = this
|
||||
|
||||
// When the range is collapsed, do nothing.
|
||||
if (range.isCollapsed) return node
|
||||
|
||||
// Otherwise, find each of the text nodes within the range.
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
let texts = node.getTextsAtRange(range)
|
||||
|
||||
// Apply the mark to each of the text nodes's matching characters.
|
||||
texts = texts.map((text) => {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = marks.add(mark)
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
return text.merge({ characters })
|
||||
})
|
||||
|
||||
// Update each of the text nodes.
|
||||
texts.forEach((text) => {
|
||||
node = node.updateDescendant(text)
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete everything in a `range`.
|
||||
*
|
||||
@@ -176,6 +215,76 @@ const Transforms = {
|
||||
return node.deleteAtRange(range)
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a block `node` at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
insertBlockAtRange(range, node) {
|
||||
let doc = this
|
||||
|
||||
// If expanded, delete the range first.
|
||||
if (range.isExpanded) {
|
||||
doc = doc.deleteAtRange(range)
|
||||
range = range.collapseToStart()
|
||||
}
|
||||
|
||||
// Allow for passing just a type string.
|
||||
if (typeof node == 'string') node = { type: node }
|
||||
|
||||
// Allow for passing a plain object of properties.
|
||||
node = Block.create(node)
|
||||
|
||||
const { startKey, startOffset } = range
|
||||
let startBlock = doc.getClosestBlock(startKey)
|
||||
let parent = doc.getParent(startBlock)
|
||||
let nodes = Block.createList([node])
|
||||
const isParent = parent == doc
|
||||
|
||||
// If the start block is void, insert after it.
|
||||
if (startBlock.isVoid) {
|
||||
parent = parent.insertChildrenAfter(startBlock, nodes)
|
||||
}
|
||||
|
||||
// If the block is empty, replace it.
|
||||
else if (startBlock.isEmpty) {
|
||||
parent = parent.merge({ nodes })
|
||||
}
|
||||
|
||||
// If the range is at the start of the block, insert before.
|
||||
else if (range.isAtStartOf(startBlock)) {
|
||||
nodes = nodes.concat(parent.nodes)
|
||||
parent = parent.merge({ nodes })
|
||||
}
|
||||
|
||||
// If the range is at the end of the block, insert after.
|
||||
else if (range.isAtEndOf(startBlock)) {
|
||||
nodes = parent.nodes.concat(nodes)
|
||||
parent = parent.merge({ nodes })
|
||||
}
|
||||
|
||||
// Otherwise, split the block and insert between.
|
||||
else {
|
||||
doc = doc.splitBlockAtRange(range)
|
||||
parent = doc.getParent(startBlock)
|
||||
startBlock = doc.getClosestBlock(startKey)
|
||||
nodes = parent.nodes.takeUntil(n => n == startBlock)
|
||||
.push(startBlock)
|
||||
.push(node)
|
||||
.concat(parent.nodes.skipUntil(n => n == startBlock).rest())
|
||||
parent = parent.merge({ nodes })
|
||||
}
|
||||
|
||||
doc = isParent
|
||||
? parent
|
||||
: doc.updateDescendant(parent)
|
||||
|
||||
return doc.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert a `fragment` at a `range`.
|
||||
*
|
||||
@@ -275,6 +384,46 @@ const Transforms = {
|
||||
return node.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert an inline `node` at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Node} node
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
insertInlineAtRange(range, node) {
|
||||
let doc = this
|
||||
|
||||
// If expanded, delete the range first.
|
||||
if (range.isExpanded) {
|
||||
doc = doc.deleteAtRange(range)
|
||||
range = range.collapseToStart()
|
||||
}
|
||||
|
||||
// Allow for passing a type string.
|
||||
if (typeof node == 'string') node = { type: node }
|
||||
|
||||
// Allow for passing a plain object of properties.
|
||||
node = Inline.create(node)
|
||||
|
||||
// Split the text nodes at the cursor.
|
||||
doc = doc.splitTextAtRange(range)
|
||||
|
||||
// Insert the node between the split text nodes.
|
||||
const { startKey, endKey, startOffset, endOffset } = range
|
||||
const startText = doc.getDescendant(startKey)
|
||||
let parent = doc.getParent(startKey)
|
||||
const nodes = parent.nodes.takeUntil(n => n == startText)
|
||||
.push(startText)
|
||||
.push(node)
|
||||
.concat(parent.nodes.skipUntil(n => n == startText).rest())
|
||||
|
||||
parent = parent.merge({ nodes })
|
||||
doc = doc.updateDescendant(parent)
|
||||
return doc.normalize()
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert text `string` at a `range`, with optional `marks`.
|
||||
*
|
||||
@@ -303,14 +452,14 @@ const Transforms = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new `mark` to the characters at `range`.
|
||||
* Remove an existing `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark or String} mark
|
||||
* @param {Mark or String} mark (optional)
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
addMarkAtRange(range, mark) {
|
||||
removeMarkAtRange(range, mark) {
|
||||
mark = normalizeMark(mark)
|
||||
let node = this
|
||||
|
||||
@@ -318,7 +467,6 @@ const Transforms = {
|
||||
if (range.isCollapsed) return node
|
||||
|
||||
// Otherwise, find each of the text nodes within the range.
|
||||
const { startKey, startOffset, endKey, endOffset } = range
|
||||
let texts = node.getTextsAtRange(range)
|
||||
|
||||
// Apply the mark to each of the text nodes's matching characters.
|
||||
@@ -326,7 +474,9 @@ const Transforms = {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = marks.add(mark)
|
||||
marks = mark
|
||||
? marks.remove(mark)
|
||||
: marks.clear()
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
@@ -552,46 +702,6 @@ const Transforms = {
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an existing `mark` to the characters at `range`.
|
||||
*
|
||||
* @param {Selection} range
|
||||
* @param {Mark or String} mark (optional)
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
removeMarkAtRange(range, mark) {
|
||||
mark = normalizeMark(mark)
|
||||
let node = this
|
||||
|
||||
// When the range is collapsed, do nothing.
|
||||
if (range.isCollapsed) return node
|
||||
|
||||
// Otherwise, find each of the text nodes within the range.
|
||||
let texts = node.getTextsAtRange(range)
|
||||
|
||||
// Apply the mark to each of the text nodes's matching characters.
|
||||
texts = texts.map((text) => {
|
||||
let characters = text.characters.map((char, i) => {
|
||||
if (!isInRange(i, text, range)) return char
|
||||
let { marks } = char
|
||||
marks = mark
|
||||
? marks.remove(mark)
|
||||
: marks.clear()
|
||||
return char.merge({ marks })
|
||||
})
|
||||
|
||||
return text.merge({ characters })
|
||||
})
|
||||
|
||||
// Update each of the text nodes.
|
||||
texts.forEach((text) => {
|
||||
node = node.updateDescendant(text)
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
/**
|
||||
* Add or remove a `mark` from the characters at `range`, depending on whether
|
||||
* it's already there.
|
||||
|
@@ -151,12 +151,39 @@ function Plugin(options = {}) {
|
||||
onDrop(e, drop, state, editor) {
|
||||
switch (drop.type) {
|
||||
case 'fragment': {
|
||||
const { selection } = state
|
||||
let { fragment, target, isInternal } = drop
|
||||
|
||||
// If the drag is internal and the target is after the selection, it
|
||||
// needs to account for the selection's content being deleted.
|
||||
if (
|
||||
isInternal &&
|
||||
selection.endKey == target.endKey &&
|
||||
selection.endOffset < target.endOffset
|
||||
) {
|
||||
target = target.moveBackward(selection.startKey == selection.endKey
|
||||
? selection.endOffset - selection.startOffset
|
||||
: selection.endOffset)
|
||||
}
|
||||
|
||||
let transform = state.transform()
|
||||
|
||||
if (isInternal) transform = transform.delete()
|
||||
|
||||
return transform
|
||||
.moveTo(target)
|
||||
.insertFragment(fragment)
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'node': {
|
||||
return state
|
||||
.transform()
|
||||
.moveTo(drop.target)
|
||||
.insertFragment(drop.fragment)
|
||||
[drop.node.kind == 'block' ? 'insertBlock' : 'insertInline'](drop.node)
|
||||
.apply()
|
||||
}
|
||||
|
||||
case 'text':
|
||||
case 'html': {
|
||||
let transform = state
|
||||
|
105
lib/serializers/base-64.js
Normal file
105
lib/serializers/base-64.js
Normal file
@@ -0,0 +1,105 @@
|
||||
|
||||
import Raw from './raw'
|
||||
|
||||
/**
|
||||
* Encode a JSON `object` as base-64 `string`.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @return {String} encoded
|
||||
*/
|
||||
|
||||
function encode(object) {
|
||||
const string = JSON.stringify(object)
|
||||
const encoded = window.btoa(window.unescape(window.encodeURIComponent(string)))
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a base-64 `string` to a JSON `object`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Object} object
|
||||
*/
|
||||
|
||||
function decode(string) {
|
||||
const decoded = window.decodeURIComponent(window.escape(window.atob(string)))
|
||||
const object = JSON.parse(decoded)
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a State `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
function deserialize(string) {
|
||||
const raw = decode(string)
|
||||
const state = Raw.deserialize(raw)
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Document `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Document} document
|
||||
*/
|
||||
|
||||
function deserializeDocument(string) {
|
||||
const raw = decode(string)
|
||||
const state = Raw.deserialize(raw)
|
||||
return state.document
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a Node `string`.
|
||||
*
|
||||
* @param {String} string
|
||||
* @return {Node} node
|
||||
*/
|
||||
|
||||
function deserializeNode(string) {
|
||||
const raw = decode(string)
|
||||
const node = Raw.deserializeNode(raw)
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `state`.
|
||||
*
|
||||
* @param {State} state
|
||||
* @return {String} encoded
|
||||
*/
|
||||
|
||||
function serialize(state) {
|
||||
const raw = Raw.serialize(state)
|
||||
const encoded = encode(raw)
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {String} encoded
|
||||
*/
|
||||
|
||||
function serializeNode(node) {
|
||||
const raw = Raw.serializeNode(node)
|
||||
const encoded = encode(raw)
|
||||
return encoded
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default {
|
||||
deserialize,
|
||||
deserializeDocument,
|
||||
deserializeNode,
|
||||
serialize,
|
||||
serializeNode
|
||||
}
|
@@ -1,39 +0,0 @@
|
||||
|
||||
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
|
||||
}
|
19
lib/utils/types.js
Normal file
19
lib/utils/types.js
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
/**
|
||||
* Content types.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const TYPES = {
|
||||
FRAGMENT: 'application/x-slate-fragment',
|
||||
HTML: 'text/html',
|
||||
NODE: 'application/x-slate-node',
|
||||
TEXT: 'text/plain'
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default TYPES
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,20 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wo
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: rd
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,15 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,19 @@
|
||||
|
||||
import { Block } from '../../../../..'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, Block.create({ type: 'image' }))
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, { type: 'image' })
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
29
test/transforms/fixtures/insert-block/block-end/index.js
Normal file
29
test/transforms/fixtures/insert-block/block-end/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock('image')
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().last()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
14
test/transforms/fixtures/insert-block/block-end/output.yaml
Normal file
14
test/transforms/fixtures/insert-block/block-end/output.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
29
test/transforms/fixtures/insert-block/block-middle/index.js
Normal file
29
test/transforms/fixtures/insert-block/block-middle/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock('image')
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,20 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wo
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: rd
|
29
test/transforms/fixtures/insert-block/block-start/index.js
Normal file
29
test/transforms/fixtures/insert-block/block-start/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock('image')
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
29
test/transforms/fixtures/insert-block/is-empty/index.js
Normal file
29
test/transforms/fixtures/insert-block/is-empty/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock('image')
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
29
test/transforms/fixtures/insert-block/is-void/index.js
Normal file
29
test/transforms/fixtures/insert-block/is-void/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock('image')
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().last()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
9
test/transforms/fixtures/insert-block/is-void/input.yaml
Normal file
9
test/transforms/fixtures/insert-block/is-void/input.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
15
test/transforms/fixtures/insert-block/is-void/output.yaml
Normal file
15
test/transforms/fixtures/insert-block/is-void/output.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
30
test/transforms/fixtures/insert-block/with-block/index.js
Normal file
30
test/transforms/fixtures/insert-block/with-block/index.js
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
import { Block } from '../../../../..'
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock(Block.create({ type: 'image' }))
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
14
test/transforms/fixtures/insert-block/with-block/output.yaml
Normal file
14
test/transforms/fixtures/insert-block/with-block/output.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
29
test/transforms/fixtures/insert-block/with-object/index.js
Normal file
29
test/transforms/fixtures/insert-block/with-object/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
const next = state
|
||||
.transform()
|
||||
.moveTo(range)
|
||||
.insertBlock({ type: 'image' })
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToStartOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertInlineAtRange(range, 'hashtag')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,20 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wo
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: rd
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, 'image')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,15 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
isVoid: true
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,19 @@
|
||||
|
||||
import { Block } from '../../../../..'
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, Block.create({ type: 'image' }))
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTexts()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertBlockAtRange(range, { type: 'image' })
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: image
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
Reference in New Issue
Block a user