mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-18 13:11:17 +02:00
* Add base tests for isVoid with around text node * Ensure that void nodes are surrounded by text nodes in Node.normalize * Only wrap inline void nodes with text * Fix indentation * Add emojis example * Adapt unit test "transforms/fixtures/at-current-range/set-inline/with-is-void" * Adapt unit test "transforms/fixtures/at-current-range/insert-inline/with-inline" * Adapt unit test "transforms/fixtures/at-current-range/insert-inline/block-start" * add passing parent to leaf nodes, for rendering breaks * add zero-width spaces in empty text nodes, to allow selections * add zero-width space handling to copy/cut * fix delete handling around inline void nodes * fix tests for inline void nodes * fix style * fix void cursor handling across browsers * fix void rendering tests
This commit is contained in:
committed by
Ian Storm Taylor
parent
f7e132ed61
commit
d1c3700bd2
8
examples/emojis/Readme.md
Normal file
8
examples/emojis/Readme.md
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
# Emojis Example
|
||||
|
||||

|
||||
|
||||
This example shows you how you can insert inline void nodes. This is how you'd add emojis or inline images to Slate.
|
||||
|
||||
Check out the [Examples readme](..) to see how to run it!
|
144
examples/emojis/index.js
Normal file
144
examples/emojis/index.js
Normal file
@@ -0,0 +1,144 @@
|
||||
|
||||
import { Editor, Mark, Raw } from '../..'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import initialState from './state.json'
|
||||
import isUrl from 'is-url'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
|
||||
const EMOJIS = [
|
||||
'😃', '😬', '🍔'
|
||||
]
|
||||
|
||||
|
||||
/**
|
||||
* Define a schema.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
const schema = {
|
||||
nodes: {
|
||||
paragraph: props => <p>{props.children}</p>,
|
||||
emoji: (props) => {
|
||||
const { state, node } = props
|
||||
const { data } = node
|
||||
const code = data.get('code')
|
||||
const isSelected = state.selection.hasFocusIn(node)
|
||||
return <span className={`emoji ${isSelected ? 'selected' : ''}`} {...props.attributes} contentEditable={false}>{code}</span>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The links example.
|
||||
*
|
||||
* @type {Component}
|
||||
*/
|
||||
|
||||
class Emojis extends React.Component {
|
||||
|
||||
/**
|
||||
* Deserialize the raw initial state.
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
state = {
|
||||
state: Raw.deserialize(initialState, { terse: true })
|
||||
};
|
||||
|
||||
/**
|
||||
* On change.
|
||||
*
|
||||
* @param {State} state
|
||||
*/
|
||||
|
||||
onChange = (state) => {
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
/**
|
||||
* When clicking a emoji, insert it
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
|
||||
onClickEmoji = (e, code) => {
|
||||
e.preventDefault()
|
||||
let { state } = this.state
|
||||
|
||||
state = state
|
||||
.transform()
|
||||
.insertInline({
|
||||
type: 'emoji',
|
||||
isVoid: true,
|
||||
data: { code }
|
||||
})
|
||||
.apply()
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the app.
|
||||
*
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
render = () => {
|
||||
return (
|
||||
<div>
|
||||
{this.renderToolbar()}
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the toolbar.
|
||||
*
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
renderToolbar = () => {
|
||||
return (
|
||||
<div className="menu toolbar-menu">
|
||||
{EMOJIS.map((emoji, i) => {
|
||||
const onMouseDown = e => this.onClickEmoji(e, emoji)
|
||||
return (
|
||||
<span key={i} className="button" onMouseDown={onMouseDown}>
|
||||
<span className="material-icons">{emoji}</span>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the editor.
|
||||
*
|
||||
* @return {Element} element
|
||||
*/
|
||||
|
||||
renderEditor = () => {
|
||||
return (
|
||||
<div className="editor">
|
||||
<Editor
|
||||
schema={schema}
|
||||
state={this.state.state}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default Emojis
|
50
examples/emojis/state.json
Normal file
50
examples/emojis/state.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"text": "In addition to block nodes, you can create inline void nodes, like "
|
||||
},
|
||||
{
|
||||
"kind": "inline",
|
||||
"type": "emoji",
|
||||
"isVoid": true,
|
||||
"data": {
|
||||
"code": "😃"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"text": "!"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "inline",
|
||||
"type": "emoji",
|
||||
"isVoid": true,
|
||||
"data": {
|
||||
"code": "🍔"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"text": "This example shows emojis in action."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -165,3 +165,7 @@ input:focus {
|
||||
.hover-menu .button[data-active="true"] {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.emoji.selected {
|
||||
outline: 2px solid blue;
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import { Router, Route, Link, IndexRedirect, hashHistory } from 'react-router'
|
||||
import AutoMarkdown from './auto-markdown'
|
||||
import CodeHighlighting from './code-highlighting'
|
||||
import Embeds from './embeds'
|
||||
import Emojis from './emojis'
|
||||
import HoveringMenu from './hovering-menu'
|
||||
import Iframes from './iframes'
|
||||
import Images from './images'
|
||||
@@ -72,6 +73,7 @@ class App extends React.Component {
|
||||
{this.renderTab('Links', 'links')}
|
||||
{this.renderTab('Images', 'images')}
|
||||
{this.renderTab('Embeds', 'embeds')}
|
||||
{this.renderTab('Emojis', 'emojis')}
|
||||
{this.renderTab('Tables', 'tables')}
|
||||
{this.renderTab('Code Highlighting', 'code-highlighting')}
|
||||
{this.renderTab('Paste HTML', 'paste-html')}
|
||||
@@ -126,6 +128,7 @@ const router = (
|
||||
<Route path="auto-markdown" component={AutoMarkdown} />
|
||||
<Route path="code-highlighting" component={CodeHighlighting} />
|
||||
<Route path="embeds" component={Embeds} />
|
||||
<Route path="emojis" component={Emojis} />
|
||||
<Route path="hovering-menu" component={HoveringMenu} />
|
||||
<Route path="iframes" component={Iframes} />
|
||||
<Route path="images" component={Images} />
|
||||
|
@@ -691,6 +691,7 @@ class Content extends React.Component {
|
||||
<Node
|
||||
key={node.key}
|
||||
node={node}
|
||||
parent={state.document}
|
||||
schema={schema}
|
||||
state={state}
|
||||
editor={editor}
|
||||
|
@@ -32,6 +32,7 @@ class Leaf extends React.Component {
|
||||
isVoid: React.PropTypes.bool,
|
||||
marks: React.PropTypes.object.isRequired,
|
||||
node: React.PropTypes.object.isRequired,
|
||||
parent: React.PropTypes.object.isRequired,
|
||||
ranges: React.PropTypes.object.isRequired,
|
||||
schema: React.PropTypes.object.isRequired,
|
||||
state: React.PropTypes.object.isRequired,
|
||||
@@ -243,10 +244,15 @@ class Leaf extends React.Component {
|
||||
* @return {Element}
|
||||
*/
|
||||
|
||||
renderText({ text, index, ranges }) {
|
||||
// If the text is empty, we need to render a <br/> to get the block to have
|
||||
// the proper height.
|
||||
if (text == '') return <br />
|
||||
renderText({ parent, text, index, ranges }) {
|
||||
// COMPAT: If the text is empty and it's the only child, we need to render a
|
||||
// <br/> to get the block to have the proper height.
|
||||
if (text == '' && parent.kind == 'block' && parent.text == '') return <br />
|
||||
|
||||
// COMPAT: If the text is empty otherwise, it's because it's on the edge of
|
||||
// an inline void node, so we render a zero-width space so that the
|
||||
// selection can be inserted next to it still.
|
||||
if (text == '') return <span className="slate-zero-width-space">{'\u200B'}</span>
|
||||
|
||||
// COMPAT: Browsers will collapse trailing new lines at the end of blocks,
|
||||
// so we need to add an extra trailing new lines to prevent that.
|
||||
|
@@ -33,6 +33,7 @@ class Node extends React.Component {
|
||||
static propTypes = {
|
||||
editor: React.PropTypes.object.isRequired,
|
||||
node: React.PropTypes.object.isRequired,
|
||||
parent: React.PropTypes.object.isRequired,
|
||||
schema: React.PropTypes.object.isRequired,
|
||||
state: React.PropTypes.object.isRequired
|
||||
}
|
||||
@@ -237,6 +238,7 @@ class Node extends React.Component {
|
||||
<Node
|
||||
key={child.key}
|
||||
node={child}
|
||||
parent={this.props.node}
|
||||
editor={this.props.editor}
|
||||
schema={this.props.schema}
|
||||
state={this.props.state}
|
||||
@@ -251,7 +253,7 @@ class Node extends React.Component {
|
||||
*/
|
||||
|
||||
renderElement = () => {
|
||||
const { editor, node, state } = this.props
|
||||
const { editor, node, parent, state } = this.props
|
||||
const { Component } = this.state
|
||||
const children = node.nodes
|
||||
.map(child => this.renderNode(child))
|
||||
@@ -276,6 +278,7 @@ class Node extends React.Component {
|
||||
attributes={attributes}
|
||||
key={node.key}
|
||||
editor={editor}
|
||||
parent={parent}
|
||||
node={node}
|
||||
state={state}
|
||||
>
|
||||
@@ -325,7 +328,7 @@ class Node extends React.Component {
|
||||
*/
|
||||
|
||||
renderLeaf = (ranges, range, index, offset) => {
|
||||
const { node, schema, state } = this.props
|
||||
const { node, parent, schema, state } = this.props
|
||||
const text = range.text
|
||||
const marks = range.marks
|
||||
|
||||
@@ -335,6 +338,7 @@ class Node extends React.Component {
|
||||
index={index}
|
||||
marks={marks}
|
||||
node={node}
|
||||
parent={parent}
|
||||
ranges={ranges}
|
||||
schema={schema}
|
||||
state={state}
|
||||
|
@@ -31,6 +31,7 @@ class Void extends React.Component {
|
||||
children: React.PropTypes.any.isRequired,
|
||||
editor: React.PropTypes.object.isRequired,
|
||||
node: React.PropTypes.object.isRequired,
|
||||
parent: React.PropTypes.object.isRequired,
|
||||
schema: React.PropTypes.object.isRequired,
|
||||
state: React.PropTypes.object.isRequired,
|
||||
};
|
||||
@@ -66,7 +67,8 @@ class Void extends React.Component {
|
||||
|
||||
// Make the outer wrapper relative, so the spacer can overlay it.
|
||||
const style = {
|
||||
position: 'relative'
|
||||
position: 'relative',
|
||||
lineHeight: '0px'
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -89,21 +91,11 @@ class Void extends React.Component {
|
||||
*/
|
||||
|
||||
renderSpacer = () => {
|
||||
// COMPAT: In Firefox, if the <span> is positioned absolutely, it won't
|
||||
// receive the cursor properly when navigating via arrow keys.
|
||||
const style = IS_FIREFOX
|
||||
? {
|
||||
pointerEvents: 'none',
|
||||
width: '0px',
|
||||
height: '0px',
|
||||
lineHeight: '0px',
|
||||
visibility: 'hidden'
|
||||
}
|
||||
: {
|
||||
position: 'absolute',
|
||||
const style = {
|
||||
position: 'relative',
|
||||
top: '0px',
|
||||
left: '-9999px',
|
||||
textIndent: '-9999px'
|
||||
textIndent: '-9999px',
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -137,6 +129,7 @@ class Void extends React.Component {
|
||||
schema={schema}
|
||||
state={state}
|
||||
node={child}
|
||||
parent={node}
|
||||
ranges={ranges}
|
||||
index={index}
|
||||
text={text}
|
||||
|
@@ -1121,6 +1121,37 @@ const Node = {
|
||||
node = node.removeDescendant(key)
|
||||
})
|
||||
|
||||
// Ensure that void nodes are surrounded by text nodes
|
||||
node = node.mapDescendants((desc) => {
|
||||
if (desc.kind == 'text') {
|
||||
return desc
|
||||
}
|
||||
|
||||
const nodes = desc.nodes.reduce((accu, child, i) => {
|
||||
// We wrap only inline void nodes
|
||||
if (!child.isVoid || child.kind === 'block') {
|
||||
return accu.push(child)
|
||||
}
|
||||
|
||||
const prev = accu.last()
|
||||
const next = desc.nodes.get(i + 1)
|
||||
|
||||
if (!prev || prev.kind !== 'text') {
|
||||
accu = accu.push(Text.create())
|
||||
}
|
||||
|
||||
accu = accu.push(child)
|
||||
|
||||
if (!next || next.kind !== 'text') {
|
||||
accu = accu.push(Text.create())
|
||||
}
|
||||
|
||||
return accu
|
||||
}, List())
|
||||
|
||||
return desc.merge({ nodes })
|
||||
})
|
||||
|
||||
return node
|
||||
},
|
||||
|
||||
|
@@ -188,11 +188,16 @@ function Plugin(options = {}) {
|
||||
|
||||
const { fragment } = data
|
||||
const encoded = Base64.serializeNode(fragment)
|
||||
const range = native.getRangeAt(0)
|
||||
const contents = range.cloneContents()
|
||||
|
||||
// Remove any zero-width space spans from the cloned DOM so that they don't
|
||||
// show up elsewhere when copied.
|
||||
const zws = [].slice.call(contents.querySelectorAll('.slate-zero-width-space'))
|
||||
zws.forEach(zw => zw.parentNode.removeChild(zw))
|
||||
|
||||
// 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.
|
||||
const range = native.getRangeAt(0)
|
||||
const contents = range.cloneContents()
|
||||
const wrapper = window.document.createElement('span')
|
||||
const text = contents.childNodes[0]
|
||||
const char = text.textContent.slice(0, 1)
|
||||
@@ -328,6 +333,8 @@ function Plugin(options = {}) {
|
||||
case 'enter': return onKeyDownEnter(e, data, state)
|
||||
case 'backspace': return onKeyDownBackspace(e, data, state)
|
||||
case 'delete': return onKeyDownDelete(e, data, state)
|
||||
case 'left': return onKeyDownLeft(e, data, state)
|
||||
case 'right': return onKeyDownRight(e, data, state)
|
||||
case 'y': return onKeyDownY(e, data, state)
|
||||
case 'z': return onKeyDownZ(e, data, state)
|
||||
}
|
||||
@@ -450,6 +457,82 @@ function Plugin(options = {}) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* On `left` key down, move backward.
|
||||
*
|
||||
* COMPAT: This is required to solve for the case where an inline void node is
|
||||
* surrounded by empty text nodes with zero-width spaces in them. Without this
|
||||
* the zero-width spaces will cause two arrow keys to jump to the next text.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownLeft(e, data, state) {
|
||||
if (data.isCtrl) return
|
||||
if (data.isOpt) return
|
||||
if (state.isExpanded) return
|
||||
|
||||
const { document, startText } = state
|
||||
const hasVoidParent = document.hasVoidParent(startText)
|
||||
|
||||
if (
|
||||
startText.text == '' ||
|
||||
hasVoidParent
|
||||
) {
|
||||
const previousText = document.getPreviousText(startText)
|
||||
if (!previousText) return
|
||||
|
||||
debug('onKeyDownLeft', { data })
|
||||
|
||||
e.preventDefault()
|
||||
return state
|
||||
.transform()
|
||||
.collapseToEndOf(previousText)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On `right` key down, move forward.
|
||||
*
|
||||
* COMPAT: This is required to solve for the case where an inline void node is
|
||||
* surrounded by empty text nodes with zero-width spaces in them. Without this
|
||||
* the zero-width spaces will cause two arrow keys to jump to the next text.
|
||||
*
|
||||
* @param {Event} e
|
||||
* @param {Object} data
|
||||
* @param {State} state
|
||||
* @return {State}
|
||||
*/
|
||||
|
||||
function onKeyDownRight(e, data, state) {
|
||||
if (data.isCtrl) return
|
||||
if (data.isOpt) return
|
||||
if (state.isExpanded) return
|
||||
|
||||
const { document, startText } = state
|
||||
const hasVoidParent = document.hasVoidParent(startText)
|
||||
|
||||
if (
|
||||
startText.text == '' ||
|
||||
hasVoidParent
|
||||
) {
|
||||
const nextText = document.getNextText(startText)
|
||||
if (!nextText) return state
|
||||
|
||||
debug('onKeyDownRight', { data })
|
||||
|
||||
e.preventDefault()
|
||||
return state
|
||||
.transform()
|
||||
.collapseToStartOf(nextText)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* On `y` key down, redo.
|
||||
*
|
||||
|
@@ -117,7 +117,8 @@ export function deleteBackward(transform, n = 1) {
|
||||
if (prevBlock && prevBlock.isVoid) {
|
||||
after = selection
|
||||
} else if (prevInline && prevInline.isVoid) {
|
||||
after = selection
|
||||
const prevPrev = document.getPreviousText(previous)
|
||||
after = selection.collapseToEndOf(prevPrev)
|
||||
} else {
|
||||
after = selection.collapseToEndOf(previous)
|
||||
}
|
||||
@@ -321,7 +322,13 @@ export function insertInline(transform, inline) {
|
||||
}
|
||||
|
||||
else {
|
||||
const text = document.getTexts().find(n => !keys.includes(n.key))
|
||||
const text = document.getTexts().find((n) => {
|
||||
if (keys.includes(n.key)) return false
|
||||
const parent = document.getParent(n)
|
||||
if (parent.kind != 'inline') return false
|
||||
return true
|
||||
})
|
||||
|
||||
after = selection.collapseToEndOf(text)
|
||||
}
|
||||
|
||||
|
@@ -195,6 +195,16 @@ export function deleteForwardAtRange(transform, range, n = 1) {
|
||||
|
||||
if (range.isAtEndOf(text)) {
|
||||
const next = document.getNextText(text)
|
||||
const nextBlock = document.getClosestBlock(next)
|
||||
const nextInline = document.getClosestInline(next)
|
||||
|
||||
if (nextBlock && nextBlock.isVoid) {
|
||||
return transform.removeNodeByKey(nextBlock.key)
|
||||
}
|
||||
|
||||
if (nextInline && nextInline.isVoid) {
|
||||
return transform.removeNodeByKey(nextInline.key)
|
||||
}
|
||||
|
||||
range = range.merge({
|
||||
focusKey: next.key,
|
||||
|
@@ -1,9 +1,9 @@
|
||||
|
||||
<div contenteditable="true">
|
||||
<div style="position:relative;">
|
||||
<span style="position:absolute;top:0px;left:-9999px;text-indent:-9999px;">
|
||||
<div style="position:relative;line-height:0px;">
|
||||
<span style="position:relative;top:0px;left:-9999px;text-indent:-9999px;">
|
||||
<span>
|
||||
<br>
|
||||
<span class="slate-zero-width-space">​</span>
|
||||
</span>
|
||||
</span>
|
||||
<div contenteditable="false">
|
||||
|
12
test/rendering/fixtures/custom-inline-void/index.js
Normal file
12
test/rendering/fixtures/custom-inline-void/index.js
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
import React from 'react'
|
||||
|
||||
function Image(props) {
|
||||
return <img {...props.attributes} />
|
||||
}
|
||||
|
||||
export const schema = {
|
||||
nodes: {
|
||||
image: Image
|
||||
}
|
||||
}
|
@@ -1,10 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
type: default
|
||||
nodes:
|
||||
- kind: inline
|
||||
type: image
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: word
|
25
test/rendering/fixtures/custom-inline-void/output.html
Normal file
25
test/rendering/fixtures/custom-inline-void/output.html
Normal file
@@ -0,0 +1,25 @@
|
||||
|
||||
<div contenteditable="true">
|
||||
<div style="position:relative;">
|
||||
<span>
|
||||
<span>
|
||||
<span class="slate-zero-width-space">​</span>
|
||||
</span>
|
||||
</span>
|
||||
<span style="position:relative;line-height:0px;">
|
||||
<span style="position:relative;top:0px;left:-9999px;text-indent:-9999px;">
|
||||
<span>
|
||||
<span class="slate-zero-width-space">​</span>
|
||||
</span>
|
||||
</span>
|
||||
<span contenteditable="false">
|
||||
<img>
|
||||
</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>
|
||||
<span class="slate-zero-width-space">​</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
2
test/schema/fixtures/default-void-text-around/index.js
Normal file
2
test/schema/fixtures/default-void-text-around/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export default {}
|
8
test/schema/fixtures/default-void-text-around/input.yaml
Normal file
8
test/schema/fixtures/default-void-text-around/input.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: default
|
||||
nodes:
|
||||
- kind: inline
|
||||
isVoid: true
|
||||
type: image
|
13
test/schema/fixtures/default-void-text-around/output.yaml
Normal file
13
test/schema/fixtures/default-void-text-around/output.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: default
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
isVoid: true
|
||||
type: image
|
||||
- kind: text
|
||||
text: ""
|
@@ -4,6 +4,7 @@ nodes:
|
||||
isVoid: false
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
@@ -11,3 +12,4 @@ nodes:
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
- characters: []
|
||||
|
@@ -4,6 +4,7 @@ nodes:
|
||||
isVoid: false
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
@@ -11,3 +12,4 @@ nodes:
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
- characters: []
|
||||
|
@@ -4,6 +4,7 @@ nodes:
|
||||
isVoid: false
|
||||
data: {}
|
||||
nodes:
|
||||
- characters: []
|
||||
- type: link
|
||||
isVoid: true
|
||||
data: {}
|
||||
@@ -11,3 +12,4 @@ nodes:
|
||||
- characters:
|
||||
- text: " "
|
||||
marks: []
|
||||
- characters: []
|
||||
|
@@ -3,6 +3,10 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: link
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -8,6 +8,11 @@ document:
|
||||
data: {}
|
||||
isVoid: false
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- kind: range
|
||||
text: ""
|
||||
marks: []
|
||||
- kind: inline
|
||||
type: link
|
||||
isVoid: true
|
||||
@@ -18,3 +23,8 @@ document:
|
||||
- kind: range
|
||||
text: " "
|
||||
marks: []
|
||||
- kind: text
|
||||
ranges:
|
||||
- kind: range
|
||||
text: ""
|
||||
marks: []
|
||||
|
@@ -21,7 +21,7 @@ export default function (state) {
|
||||
})
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().last()
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
|
@@ -8,3 +8,5 @@ nodes:
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -21,7 +21,7 @@ export default function (state) {
|
||||
})
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
|
@@ -3,6 +3,8 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
|
@@ -21,7 +21,7 @@ export default function (state) {
|
||||
})
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().last()
|
||||
const updated = next.document.getTexts().get(1)
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
|
@@ -3,6 +3,10 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -1,33 +0,0 @@
|
||||
|
||||
import { Inline } 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)
|
||||
.insertInline(Inline.create({
|
||||
type: 'image',
|
||||
isVoid: true
|
||||
}))
|
||||
.apply()
|
||||
|
||||
const updated = next.document.getTexts().first()
|
||||
|
||||
assert.deepEqual(
|
||||
next.selection.toJS(),
|
||||
range.collapseToEndOf(updated).toJS()
|
||||
)
|
||||
|
||||
return next
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: word
|
@@ -3,6 +3,10 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: emoji
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -8,3 +8,5 @@ nodes:
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -3,6 +3,8 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
|
@@ -3,6 +3,10 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: hashtag
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
@@ -3,6 +3,8 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: image
|
||||
isVoid: true
|
||||
|
@@ -3,6 +3,10 @@ nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
text: ""
|
||||
- kind: inline
|
||||
type: emoji
|
||||
isVoid: true
|
||||
- kind: text
|
||||
text: ""
|
||||
|
Reference in New Issue
Block a user