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 React from 'react'
|
||||||
import ReactDOM from 'react-dom'
|
import ReactDOM from 'react-dom'
|
||||||
import initialState from './state.json'
|
import initialState from './state.json'
|
||||||
import isImage from 'is-image'
|
import isImage from 'is-image'
|
||||||
import isUrl from 'is-url'
|
import isUrl from 'is-url'
|
||||||
import { Map } from 'immutable'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define a set of node renderers.
|
* Define a set of node renderers.
|
||||||
@@ -18,9 +17,11 @@ const NODES = {
|
|||||||
const { node, state } = props
|
const { node, state } = props
|
||||||
const src = node.data.get('src')
|
const src = node.data.get('src')
|
||||||
return (
|
return (
|
||||||
<Void {...props} className="image-block">
|
<Draggable {...props}>
|
||||||
<img {...props.attributes} src={src} />
|
<Void {...props} className="image-block">
|
||||||
</Void>
|
<img {...props.attributes} src={src} />
|
||||||
|
</Void>
|
||||||
|
</Draggable>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -138,6 +138,6 @@ td {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.image-block:focus > * > img {
|
.image-block:focus img {
|
||||||
box-shadow: 0 0 0 2px blue;
|
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 Key from '../utils/key'
|
||||||
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 Selection from '../models/selection'
|
||||||
import Text from './text'
|
import Text from './text'
|
||||||
|
import TYPES from '../utils/types'
|
||||||
import includes from 'lodash/includes'
|
import includes from 'lodash/includes'
|
||||||
import keycode from 'keycode'
|
import keycode from 'keycode'
|
||||||
import { IS_FIREFOX } from '../utils/environment'
|
import { IS_FIREFOX } from '../utils/environment'
|
||||||
@@ -18,18 +19,6 @@ import { IS_FIREFOX } from '../utils/environment'
|
|||||||
|
|
||||||
function noop() {}
|
function noop() {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Content types.
|
|
||||||
*
|
|
||||||
* @type {Object}
|
|
||||||
*/
|
|
||||||
|
|
||||||
const TYPES = {
|
|
||||||
HTML: 'text/html',
|
|
||||||
SLATE: 'application/x-slate',
|
|
||||||
TEXT: 'text/plain'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content.
|
* Content.
|
||||||
*
|
*
|
||||||
@@ -235,7 +224,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
const { state } = this.props
|
const { state } = this.props
|
||||||
const { fragment } = state
|
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
|
// 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.
|
||||||
@@ -286,6 +275,7 @@ class Content extends React.Component {
|
|||||||
|
|
||||||
onDragEnd = (e) => {
|
onDragEnd = (e) => {
|
||||||
this.tmp.isDragging = false
|
this.tmp.isDragging = false
|
||||||
|
this.tmp.isInternalDrag = null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -295,6 +285,15 @@ class Content extends React.Component {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
onDragOver = (e) => {
|
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
|
if (this.tmp.isDragging) return
|
||||||
this.tmp.isDragging = true
|
this.tmp.isDragging = true
|
||||||
this.tmp.isInternalDrag = false
|
this.tmp.isInternalDrag = false
|
||||||
@@ -309,11 +308,17 @@ 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 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 { state } = this.props
|
||||||
const { fragment } = state
|
const { fragment } = state
|
||||||
const encoded = Fragment.serialize(fragment)
|
const encoded = Base64.serializeNode(fragment)
|
||||||
const data = e.nativeEvent.dataTransfer
|
data.setData(TYPES.FRAGMENT, encoded)
|
||||||
data.setData(TYPES.SLATE, encoded)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -331,13 +336,16 @@ class Content extends React.Component {
|
|||||||
const data = e.nativeEvent.dataTransfer
|
const data = e.nativeEvent.dataTransfer
|
||||||
const drop = {}
|
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.
|
// Resolve the point where the drop occured.
|
||||||
const { x, y } = e.nativeEvent
|
const { x, y } = e.nativeEvent
|
||||||
const range = window.document.caretRangeFromPoint(x, y)
|
const range = window.document.caretRangeFromPoint(x, y)
|
||||||
const startNode = range.startContainer
|
const startNode = range.startContainer
|
||||||
const startOffset = range.startOffset
|
const startOffset = range.startOffset
|
||||||
const point = OffsetKey.findPoint(startNode, startOffset, state)
|
const point = OffsetKey.findPoint(startNode, startOffset, state)
|
||||||
let target = Selection.create({
|
const target = Selection.create({
|
||||||
anchorKey: point.key,
|
anchorKey: point.key,
|
||||||
anchorOffset: point.offset,
|
anchorOffset: point.offset,
|
||||||
focusKey: point.key,
|
focusKey: point.key,
|
||||||
@@ -348,41 +356,22 @@ class Content extends React.Component {
|
|||||||
// If the target is inside a void node, abort.
|
// If the target is inside a void node, abort.
|
||||||
if (state.document.hasVoidParent(point.key)) return
|
if (state.document.hasVoidParent(point.key)) return
|
||||||
|
|
||||||
// If the drag is internal, handle it now. And it the target is after the
|
// Handle Slate fragments.
|
||||||
// selection, it needs to account for the selection's content being deleted.
|
if (includes(types, TYPES.FRAGMENT)) {
|
||||||
if (this.tmp.isInternalDrag) {
|
const encoded = data.getData(TYPES.FRAGMENT)
|
||||||
if (
|
const fragment = Base64.deserializeDocument(encoded)
|
||||||
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)
|
|
||||||
drop.type = 'fragment'
|
drop.type = 'fragment'
|
||||||
drop.fragment = 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.
|
// Handle files.
|
||||||
@@ -529,7 +518,7 @@ 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 fragment = Fragment.deserialize(encoded)
|
const fragment = Base64.deserializeDocument(encoded)
|
||||||
let { state } = this.props
|
let { state } = this.props
|
||||||
|
|
||||||
state = state
|
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.
|
* Components.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import Draggable from './components/draggable'
|
||||||
import Editor from './components/editor'
|
import Editor from './components/editor'
|
||||||
import Placeholder from './components/placeholder'
|
import Placeholder from './components/placeholder'
|
||||||
import Void from './components/void'
|
import Void from './components/void'
|
||||||
@@ -50,6 +51,7 @@ export {
|
|||||||
Character,
|
Character,
|
||||||
Data,
|
Data,
|
||||||
Document,
|
Document,
|
||||||
|
Draggable,
|
||||||
Editor,
|
Editor,
|
||||||
Html,
|
Html,
|
||||||
Inline,
|
Inline,
|
||||||
@@ -69,6 +71,7 @@ export default {
|
|||||||
Character,
|
Character,
|
||||||
Data,
|
Data,
|
||||||
Document,
|
Document,
|
||||||
|
Draggable,
|
||||||
Editor,
|
Editor,
|
||||||
Html,
|
Html,
|
||||||
Inline,
|
Inline,
|
||||||
|
@@ -78,6 +78,20 @@ class Block extends new Record(DEFAULTS) {
|
|||||||
return 'block'
|
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.
|
* Get the length of the concatenated text of the node.
|
||||||
*
|
*
|
||||||
|
@@ -78,6 +78,20 @@ class Inline extends new Record(DEFAULTS) {
|
|||||||
return 'inline'
|
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.
|
* Get the length of the concatenated text of the node.
|
||||||
*
|
*
|
||||||
|
@@ -654,6 +654,31 @@ class State extends new Record(DEFAULTS) {
|
|||||||
return state
|
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.
|
* Insert a `fragment` at the current selection.
|
||||||
*
|
*
|
||||||
|
@@ -70,6 +70,16 @@ class Text extends new Record(DEFAULTS) {
|
|||||||
return 'text'
|
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.
|
* Get the length of the concatenated text of the node.
|
||||||
*
|
*
|
||||||
|
@@ -30,7 +30,9 @@ const DOCUMENT_RANGE_TRANSFORMS = [
|
|||||||
'deleteAtRange',
|
'deleteAtRange',
|
||||||
'deleteBackwardAtRange',
|
'deleteBackwardAtRange',
|
||||||
'deleteForwardAtRange',
|
'deleteForwardAtRange',
|
||||||
|
'insertBlockAtRange',
|
||||||
'insertFragmentAtRange',
|
'insertFragmentAtRange',
|
||||||
|
'insertInlineAtRange',
|
||||||
'insertTextAtRange',
|
'insertTextAtRange',
|
||||||
'addMarkAtRange',
|
'addMarkAtRange',
|
||||||
'setBlockAtRange',
|
'setBlockAtRange',
|
||||||
@@ -84,7 +86,9 @@ const STATE_DOCUMENT_TRANSFORMS = [
|
|||||||
'delete',
|
'delete',
|
||||||
'deleteBackward',
|
'deleteBackward',
|
||||||
'deleteForward',
|
'deleteForward',
|
||||||
|
'insertBlock',
|
||||||
'insertFragment',
|
'insertFragment',
|
||||||
|
'insertInline',
|
||||||
'insertText',
|
'insertText',
|
||||||
'addMark',
|
'addMark',
|
||||||
'setBlock',
|
'setBlock',
|
||||||
|
@@ -18,6 +18,45 @@ import { List, Map, Set } from 'immutable'
|
|||||||
|
|
||||||
const Transforms = {
|
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`.
|
* Delete everything in a `range`.
|
||||||
*
|
*
|
||||||
@@ -176,6 +215,76 @@ const Transforms = {
|
|||||||
return node.deleteAtRange(range)
|
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`.
|
* Insert a `fragment` at a `range`.
|
||||||
*
|
*
|
||||||
@@ -275,6 +384,46 @@ const Transforms = {
|
|||||||
return node.normalize()
|
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`.
|
* 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 {Selection} range
|
||||||
* @param {Mark or String} mark
|
* @param {Mark or String} mark (optional)
|
||||||
* @return {Node} node
|
* @return {Node} node
|
||||||
*/
|
*/
|
||||||
|
|
||||||
addMarkAtRange(range, mark) {
|
removeMarkAtRange(range, mark) {
|
||||||
mark = normalizeMark(mark)
|
mark = normalizeMark(mark)
|
||||||
let node = this
|
let node = this
|
||||||
|
|
||||||
@@ -318,7 +467,6 @@ const Transforms = {
|
|||||||
if (range.isCollapsed) return node
|
if (range.isCollapsed) return node
|
||||||
|
|
||||||
// Otherwise, find each of the text nodes within the range.
|
// Otherwise, find each of the text nodes within the range.
|
||||||
const { startKey, startOffset, endKey, endOffset } = range
|
|
||||||
let texts = node.getTextsAtRange(range)
|
let texts = node.getTextsAtRange(range)
|
||||||
|
|
||||||
// Apply the mark to each of the text nodes's matching characters.
|
// 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) => {
|
let characters = text.characters.map((char, i) => {
|
||||||
if (!isInRange(i, text, range)) return char
|
if (!isInRange(i, text, range)) return char
|
||||||
let { marks } = char
|
let { marks } = char
|
||||||
marks = marks.add(mark)
|
marks = mark
|
||||||
|
? marks.remove(mark)
|
||||||
|
: marks.clear()
|
||||||
return char.merge({ marks })
|
return char.merge({ marks })
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -552,46 +702,6 @@ const Transforms = {
|
|||||||
return node
|
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
|
* Add or remove a `mark` from the characters at `range`, depending on whether
|
||||||
* it's already there.
|
* it's already there.
|
||||||
|
@@ -151,12 +151,39 @@ function Plugin(options = {}) {
|
|||||||
onDrop(e, drop, state, editor) {
|
onDrop(e, drop, state, editor) {
|
||||||
switch (drop.type) {
|
switch (drop.type) {
|
||||||
case 'fragment': {
|
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
|
return state
|
||||||
.transform()
|
.transform()
|
||||||
.moveTo(drop.target)
|
.moveTo(drop.target)
|
||||||
.insertFragment(drop.fragment)
|
[drop.node.kind == 'block' ? 'insertBlock' : 'insertInline'](drop.node)
|
||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'text':
|
case 'text':
|
||||||
case 'html': {
|
case 'html': {
|
||||||
let transform = state
|
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