mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-09-01 19:22:35 +02:00
broke lots of stuff, but added tests
This commit is contained in:
18
Makefile
18
Makefile
@@ -31,6 +31,10 @@ dist: $(shell find ./lib)
|
||||
example-auto-markdown:
|
||||
@ $(browserify) --debug --transform babelify --outfile ./examples/auto-markdown/build.js ./examples/auto-markdown/index.js
|
||||
|
||||
# Build the links example.
|
||||
example-links:
|
||||
@ $(browserify) --debug --transform babelify --outfile ./examples/links/build.js ./examples/links/index.js
|
||||
|
||||
# Build the plain-text example.
|
||||
example-plain-text:
|
||||
@ $(browserify) --debug --transform babelify --outfile ./examples/plain-text/build.js ./examples/plain-text/index.js
|
||||
@@ -52,24 +56,28 @@ lint:
|
||||
@ $(standard) ./lib
|
||||
|
||||
# Build the test source.
|
||||
test/support/build.js: $(shell find ./lib) ./test/browser.js
|
||||
@ $(browserify) --debug --transform babelify --outfile ./test/support/build.js ./test/browser.js
|
||||
test/browser/support/build.js: $(shell find ./lib) ./test/browser/index.js
|
||||
@ $(browserify) --debug --transform babelify --outfile ./test/browser/support/build.js ./test/browser/index.js
|
||||
|
||||
# Run the tests.
|
||||
test: test-browser test-server
|
||||
|
||||
# Run the browser-side tests.
|
||||
test-browser: ./test/support/build.js
|
||||
@ $(mocha-phantomjs) --reporter spec --timeout 5000 ./test/support/browser.html
|
||||
test-browser: ./test/browser/support/build.js
|
||||
@ $(mocha-phantomjs) --reporter spec --timeout 5000 ./test/browser/support/browser.html
|
||||
|
||||
# Run the server-side tests.
|
||||
test-server:
|
||||
@ $(mocha) --reporter spec --timeout 5000 ./test/server.js
|
||||
@ $(mocha) --compilers js:babel-core/register --reporter spec --timeout 5000 ./test/server
|
||||
|
||||
# Watch the auto-markdown example.
|
||||
watch-example-auto-markdown:
|
||||
@ $(MAKE) example-auto-markdown browserify=$(watchify)
|
||||
|
||||
# Watch the links example.
|
||||
watch-example-links:
|
||||
@ $(MAKE) example-links browserify=$(watchify)
|
||||
|
||||
# Watch the plain-text example.
|
||||
watch-example-plain-text:
|
||||
@ $(MAKE) example-plain-text browserify=$(watchify)
|
||||
|
@@ -24,6 +24,7 @@ class App extends React.Component {
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Render the example.
|
||||
*
|
||||
* @return {Component} component
|
||||
@@ -153,12 +154,10 @@ class App extends React.Component {
|
||||
case '*':
|
||||
case '-':
|
||||
case '+':
|
||||
if (node.type == 'list-item') break
|
||||
transform = node.type == 'list-item'
|
||||
? transform
|
||||
: transform
|
||||
.setType('list-item')
|
||||
.wrap('bulleted-list')
|
||||
if (node.type == 'list-item') return
|
||||
transform = transform
|
||||
.setType('list-item')
|
||||
.wrapBlock('bulleted-list')
|
||||
break
|
||||
default:
|
||||
return
|
||||
@@ -196,7 +195,7 @@ class App extends React.Component {
|
||||
.transform()
|
||||
.setType('paragraph')
|
||||
|
||||
if (node.type == 'list-item') transform = transform.unwrap('bulleted-list')
|
||||
if (node.type == 'list-item') transform = transform.unwrapBlock('bulleted-list')
|
||||
|
||||
state = transform.apply()
|
||||
return state
|
||||
|
48
examples/links/index.css
Normal file
48
examples/links/index.css
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
html {
|
||||
background: #eee;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
main {
|
||||
background: #fff;
|
||||
padding: 10px;
|
||||
max-width: 40em;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.editor > * > * + * {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.menu {
|
||||
margin: 0 -10px;
|
||||
padding: 1px 0 9px 8px;
|
||||
border-bottom: 2px solid #eee;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.menu > * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.menu > * + * {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
color: #ccc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button[data-active="true"] {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-size: 18px;
|
||||
}
|
12
examples/links/index.html
Normal file
12
examples/links/index.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Editor | Links Example</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
<body>
|
||||
<main></main>
|
||||
<script src="build.js"></script>
|
||||
</body>
|
||||
</html>
|
159
examples/links/index.js
Normal file
159
examples/links/index.js
Normal file
@@ -0,0 +1,159 @@
|
||||
|
||||
import Editor, { Mark, Raw } from '../..'
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import state from './state.json'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
/**
|
||||
* App.
|
||||
*/
|
||||
|
||||
class App extends React.Component {
|
||||
|
||||
state = {
|
||||
state: Raw.deserialize(state)
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the current selection has a link in it.
|
||||
*
|
||||
* @return {Boolean} hasLinks
|
||||
*/
|
||||
|
||||
hasLinks() {
|
||||
let { state } = this.state
|
||||
const { currentInlineNodes } = state
|
||||
const hasLinks = currentInlineNodes.some(inline => inline.type == 'link')
|
||||
return hasLinks
|
||||
}
|
||||
|
||||
/**
|
||||
* When clicking a link, if the selection has a link in it, remove the link.
|
||||
* Otherwise, add a new link with an href and text.
|
||||
*
|
||||
* @param {Event} e
|
||||
*/
|
||||
|
||||
onClickLink(e) {
|
||||
e.preventDefault()
|
||||
let { state } = this.state
|
||||
const hasLinks = this.hasLinks()
|
||||
|
||||
if (hasLinks) {
|
||||
state = state
|
||||
.transform()
|
||||
.unwrapInline('link')
|
||||
.apply()
|
||||
}
|
||||
|
||||
else if (state.isCurrentlyExpanded) {
|
||||
// const href = window.prompt('Enter the URL of the link:')
|
||||
state = state
|
||||
.transform()
|
||||
.wrapInline('link', new Map({ href: 'https://google.com' }))
|
||||
.apply()
|
||||
}
|
||||
|
||||
else {
|
||||
const href = window.prompt('Enter the URL of the link:')
|
||||
const text = window.prompt('Enter the text for the link:')
|
||||
state = state
|
||||
.transform()
|
||||
.insertText(text)
|
||||
.extendBackward(text.length)
|
||||
.wrapInline('link', new Map({ href }))
|
||||
.apply()
|
||||
}
|
||||
|
||||
this.setState({ state })
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the app.
|
||||
*
|
||||
* @return {Component} component
|
||||
*/
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
{this.renderToolbar()}
|
||||
{this.renderEditor()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the toolbar.
|
||||
*
|
||||
* @return {Component} component
|
||||
*/
|
||||
|
||||
renderToolbar() {
|
||||
const hasLinks = this.hasLinks()
|
||||
return (
|
||||
<div className="menu">
|
||||
<span className="button" onMouseDown={e => this.onClickLink(e)} data-active={hasLinks}>
|
||||
<span className="material-icons">link</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the editor.
|
||||
*
|
||||
* @return {Component} component
|
||||
*/
|
||||
|
||||
renderEditor() {
|
||||
return (
|
||||
<div className="editor">
|
||||
<Editor
|
||||
state={this.state.state}
|
||||
renderNode={node => this.renderNode(node)}
|
||||
onChange={(state) => {
|
||||
console.groupCollapsed('Change!')
|
||||
console.log('Document:', state.document.toJS())
|
||||
console.log('Selection:', state.selection.toJS())
|
||||
console.log('Content:', Raw.serialize(state))
|
||||
console.groupEnd()
|
||||
this.setState({ state })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Render our custom `node`.
|
||||
*
|
||||
* @param {Node} node
|
||||
* @return {Component} component
|
||||
*/
|
||||
|
||||
renderNode(node) {
|
||||
switch (node.type) {
|
||||
case 'link': {
|
||||
return (props) => {
|
||||
const { data } = props.node
|
||||
const href = data.get('href')
|
||||
return <a href={href}>{props.children}</a>
|
||||
}
|
||||
}
|
||||
case 'paragraph': {
|
||||
return (props) => <p>{props.children}</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach.
|
||||
*/
|
||||
|
||||
const app = <App />
|
||||
const root = document.body.querySelector('main')
|
||||
ReactDOM.render(app, root)
|
57
examples/links/state.json
Normal file
57
examples/links/state.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "In addition to block nodes, you can create inline nodes, like "
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "inline",
|
||||
"type": "link",
|
||||
"data": {
|
||||
"href": "https://en.wikipedia.org/wiki/Hypertext"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "hyperlinks"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "!"
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "block",
|
||||
"type": "paragraph",
|
||||
"nodes": [
|
||||
{
|
||||
"kind": "text",
|
||||
"ranges": [
|
||||
{
|
||||
"text": "This example shows hyperlinks in action. It features two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Editor | Auto-markdown Example</title>
|
||||
<title>Editor | Table Example</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
||||
<link rel="stylesheet" href="index.css">
|
||||
</head>
|
||||
|
@@ -83,7 +83,7 @@ class Content extends React.Component {
|
||||
const { anchorNode, anchorOffset, focusNode, focusOffset } = native
|
||||
const anchor = OffsetKey.findPoint(anchorNode, anchorOffset)
|
||||
const focus = OffsetKey.findPoint(focusNode, focusOffset)
|
||||
const edges = document.filterNodes((node) => {
|
||||
const edges = document.filterDeep((node) => {
|
||||
return node.key == anchor.key || node.key == focus.key
|
||||
})
|
||||
|
||||
|
@@ -12,6 +12,7 @@ export default Editor
|
||||
|
||||
export { default as Block } from './models/block'
|
||||
export { default as Character } from './models/character'
|
||||
export { default as Data } from './models/data'
|
||||
export { default as Document } from './models/document'
|
||||
export { default as Inline } from './models/inline'
|
||||
export { default as Mark } from './models/mark'
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -60,6 +60,16 @@ class Selection extends SelectionRecord {
|
||||
return this.isExpanded
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the range's anchor of focus keys are not set yet.
|
||||
*
|
||||
* @return {Boolean} isUnset
|
||||
*/
|
||||
|
||||
get isUnset() {
|
||||
return this.anchorKey == null || this.focusKey == null
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the selection is forward.
|
||||
*
|
||||
@@ -109,7 +119,7 @@ class Selection extends SelectionRecord {
|
||||
|
||||
isAtStartOf(node) {
|
||||
const { startKey, startOffset } = this
|
||||
const first = node.kind == 'text' ? node : node.getFirstTextNode()
|
||||
const first = node.kind == 'text' ? node : node.getFirstText()
|
||||
return startKey == first.key && startOffset == 0
|
||||
}
|
||||
|
||||
@@ -122,7 +132,7 @@ class Selection extends SelectionRecord {
|
||||
|
||||
isAtEndOf(node) {
|
||||
const { endKey, endOffset } = this
|
||||
const last = node.kind == 'text' ? node : node.getLastTextNode()
|
||||
const last = node.kind == 'text' ? node : node.getLastText()
|
||||
return endKey == last.key && endOffset == last.length
|
||||
}
|
||||
|
||||
@@ -142,25 +152,25 @@ class Selection extends SelectionRecord {
|
||||
if (anchorKey == null || focusKey == null) return selection
|
||||
|
||||
// Asset that the anchor and focus nodes exist in the node tree.
|
||||
node.assertHasNode(anchorKey)
|
||||
node.assertHasNode(focusKey)
|
||||
let anchorNode = node.getNode(anchorKey)
|
||||
let focusNode = node.getNode(focusKey)
|
||||
node.assertHasDeep(anchorKey)
|
||||
node.assertHasDeep(focusKey)
|
||||
let anchorNode = node.getDeep(anchorKey)
|
||||
let focusNode = node.getDeep(focusKey)
|
||||
|
||||
// If the anchor node isn't a text node, match it to one.
|
||||
if (anchorNode.kind != 'text') {
|
||||
anchorNode = node.getTextNodeAtOffset(anchorOffset)
|
||||
let parent = node.getParentNode(anchorNode)
|
||||
let offset = parent.getNodeOffset(anchorNode)
|
||||
anchorNode = node.getTextAtOffset(anchorOffset)
|
||||
let parent = node.getParent(anchorNode)
|
||||
let offset = parent.getOffset(anchorNode)
|
||||
anchorOffset = anchorOffset - offset
|
||||
anchorKey = anchorNode.key
|
||||
}
|
||||
|
||||
// If the focus node isn't a text node, match it to one.
|
||||
if (focusNode.kind != 'text') {
|
||||
focusNode = node.getTextNodeAtOffset(focusOffset)
|
||||
let parent = node.getParentNode(focusNode)
|
||||
let offset = parent.getNodeOffset(focusNode)
|
||||
focusNode = node.getTextAtOffset(focusOffset)
|
||||
let parent = node.getParent(focusNode)
|
||||
let offset = parent.getOffset(focusNode)
|
||||
focusOffset = focusOffset - offset
|
||||
focusKey = focusNode.key
|
||||
}
|
||||
|
@@ -32,8 +32,16 @@ const NODE_LIKE_METHODS = [
|
||||
'deleteAtRange',
|
||||
'deleteBackwardAtRange',
|
||||
'deleteForwardAtRange',
|
||||
'insertAtRange',
|
||||
'splitAtRange'
|
||||
'insertTextAtRange',
|
||||
'markAtRange',
|
||||
'setBlockAtRange',
|
||||
'setInlineAtRange',
|
||||
'splitBlockAtRange',
|
||||
'splitInlineAtRange',
|
||||
'unmarkAtRange',
|
||||
'unwrapBlockAtRange',
|
||||
'wrapBlockAtRange',
|
||||
'wrapInlineAtRange'
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -140,7 +148,17 @@ class State extends Record(DEFAULTS) {
|
||||
*/
|
||||
|
||||
get currentBlockNodes() {
|
||||
return this.document.getBlockNodesAtRange(this.selection)
|
||||
return this.document.getBlocksAtRange(this.selection)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the inline nodes in the current selection.
|
||||
*
|
||||
* @return {OrderedMap} nodes
|
||||
*/
|
||||
|
||||
get currentInlineNodes() {
|
||||
return this.document.getInlinesAtRange(this.selection)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,7 +216,7 @@ class State extends Record(DEFAULTS) {
|
||||
|
||||
// Determine what the selection should be after deleting.
|
||||
const { startKey } = selection
|
||||
const startNode = document.getNode(startKey)
|
||||
const startNode = document.getDeep(startKey)
|
||||
|
||||
if (selection.isExpanded) {
|
||||
after = selection.moveToStart()
|
||||
@@ -209,8 +227,8 @@ class State extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
else if (selection.isAtStartOf(startNode)) {
|
||||
const parent = document.getParentNode(startNode)
|
||||
const previous = document.getPreviousNode(parent).nodes.first()
|
||||
const parent = document.getParent(startNode)
|
||||
const previous = document.getPrevious(parent).nodes.first()
|
||||
after = selection.moveToEndOf(previous)
|
||||
}
|
||||
|
||||
@@ -283,16 +301,31 @@ class State extends Record(DEFAULTS) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the nodes in the current selection to `type`.
|
||||
* Set the block nodes in the current selection to `type`.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
setType(type) {
|
||||
setBlock(type, data) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.setTypeAtRange(selection, type)
|
||||
document = document.setBlockAtRange(selection, type, data)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the inline nodes in the current selection to `type`.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
setInline(type, data) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.setInlineAtRange(selection, type, data)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
@@ -313,9 +346,9 @@ class State extends Record(DEFAULTS) {
|
||||
|
||||
// Determine what the selection should be after splitting.
|
||||
const { startKey } = selection
|
||||
const startNode = document.getNode(startKey)
|
||||
const parent = document.getParentNode(startNode)
|
||||
const next = document.getNextNode(parent)
|
||||
const startNode = document.getDeep(startKey)
|
||||
const parent = document.getParent(startNode)
|
||||
const next = document.getNext(parent)
|
||||
const text = next.nodes.first()
|
||||
selection = selection.moveToStartOf(text)
|
||||
|
||||
@@ -345,10 +378,10 @@ class State extends Record(DEFAULTS) {
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
wrap(type) {
|
||||
wrapBlock(type) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.wrapAtRange(selection, type)
|
||||
document = document.wrapBlockAtRange(selection, type)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
@@ -360,11 +393,43 @@ class State extends Record(DEFAULTS) {
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
unwrap(type) {
|
||||
unwrapBlock(type) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
selection = selection.normalize(document)
|
||||
document = document.unwrapAtRange(selection, type)
|
||||
document = document.unwrapBlockAtRange(selection, type)
|
||||
state = state.merge({ document, selection })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the current selection in new inline nodes of `type`.
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {Map} data
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
wrapInline(type, data) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
document = document.wrapInlineAtRange(selection, type, data)
|
||||
state = state.merge({ document })
|
||||
return state
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap the current selection from a parent of `type`.
|
||||
*
|
||||
* @param {String} type
|
||||
* @return {State} state
|
||||
*/
|
||||
|
||||
unwrapInline(type) {
|
||||
let state = this
|
||||
let { document, selection } = state
|
||||
selection = selection.normalize(document)
|
||||
document = document.unwrapInlineAtRange(selection, type)
|
||||
state = state.merge({ document, selection })
|
||||
return state
|
||||
}
|
||||
|
@@ -46,16 +46,24 @@ const TRANSFORM_TYPES = [
|
||||
'insertTextAtRange',
|
||||
'mark',
|
||||
'markAtRange',
|
||||
'setType',
|
||||
'setTypeAtRange',
|
||||
'split',
|
||||
'splitAtRange',
|
||||
'setBlock',
|
||||
'setBlockAtRange',
|
||||
'setInline',
|
||||
'setInlineAtRange',
|
||||
'splitBlock',
|
||||
'splitBlockAtRange',
|
||||
'splitInline',
|
||||
'splitInlineAtRange',
|
||||
'unmark',
|
||||
'unmarkAtRange',
|
||||
'unwrap',
|
||||
'unwrapAtRange',
|
||||
'wrap',
|
||||
'wrapAtRange'
|
||||
'unwrapBlock',
|
||||
'unwrapBlockAtRange',
|
||||
'unwrapInline',
|
||||
'unwrapInlineAtRange',
|
||||
'wrapBlock',
|
||||
'wrapBlockAtRange',
|
||||
'wrapInline',
|
||||
'wrapInlineAtRange'
|
||||
]
|
||||
|
||||
/**
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
import React from 'react'
|
||||
import keycode from 'keycode'
|
||||
import { IS_WINDOWS, IS_MAC } from '../utils/environment'
|
||||
import environment from '../utils/environment'
|
||||
|
||||
/**
|
||||
* Export.
|
||||
@@ -20,6 +20,7 @@ export default {
|
||||
|
||||
onKeyDown(e, state, editor) {
|
||||
const key = keycode(e.which)
|
||||
const { IS_WINDOWS, IS_MAC } = environment()
|
||||
|
||||
switch (key) {
|
||||
case 'enter': {
|
||||
|
@@ -17,9 +17,7 @@ import { Map } from 'immutable'
|
||||
*/
|
||||
|
||||
function serialize(state) {
|
||||
return {
|
||||
nodes: serializeNode(state.document)
|
||||
}
|
||||
return serializeNode(state.document)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,12 +42,12 @@ function serializeNode(node) {
|
||||
}
|
||||
case 'block':
|
||||
case 'inline': {
|
||||
return {
|
||||
data: node.data.toJSON(),
|
||||
kind: node.kind,
|
||||
nodes: node.nodes.toArray().map(node => serializeNode(node)),
|
||||
type: node.type
|
||||
}
|
||||
const obj = {}
|
||||
obj.kind = node.kind
|
||||
obj.type = node.type
|
||||
obj.nodes = node.nodes.toArray().map(node => serializeNode(node))
|
||||
if (node.data.size) obj.data = node.data.toJSON()
|
||||
return obj
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,10 +63,10 @@ function serializeCharacters(characters) {
|
||||
return groupByMarks(characters)
|
||||
.toArray()
|
||||
.map((range) => {
|
||||
return {
|
||||
text: range.text,
|
||||
marks: range.marks.map(serializeMark)
|
||||
}
|
||||
const obj = {}
|
||||
obj.text = range.text
|
||||
if (range.marks.size) obj.marks = range.marks.toArray().map(serializeMark)
|
||||
return obj
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,10 +78,10 @@ function serializeCharacters(characters) {
|
||||
*/
|
||||
|
||||
function serializeMark(mark) {
|
||||
return {
|
||||
type: mark.type,
|
||||
data: mark.data.toJSON()
|
||||
}
|
||||
const obj = {}
|
||||
obj.type = mark.type
|
||||
if (mark.data.size) obj.data = mark.data.toJSON()
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -3,16 +3,28 @@ import browser from 'detect-browser'
|
||||
import Parser from 'ua-parser-js'
|
||||
|
||||
/**
|
||||
* Detections.
|
||||
* Read the environment.
|
||||
*
|
||||
* @return {Object} environment
|
||||
*/
|
||||
|
||||
export const IS_ANDROID = browser.name === 'android'
|
||||
export const IS_CHROME = browser.name === 'chrome'
|
||||
export const IS_EDGE = browser.name === 'edge'
|
||||
export const IS_FIREFOX = browser.name === 'firefox'
|
||||
export const IS_IE = browser.name === 'ie'
|
||||
export const IS_IOS = browser.name === 'ios'
|
||||
export const IS_MAC = new Parser().getOS().name === 'Mac OS'
|
||||
export const IS_UBUNTU = new Parser().getOS().name === 'Ubuntu'
|
||||
export const IS_SAFARI = browser.name === 'safari'
|
||||
export const IS_WINDOWS = new Parser().getOS().name.includes('Windows')
|
||||
function environment() {
|
||||
return {
|
||||
IS_ANDROID: browser.name === 'android',
|
||||
IS_CHROME: browser.name === 'chrome',
|
||||
IS_EDGE: browser.name === 'edge',
|
||||
IS_FIREFOX: browser.name === 'firefox',
|
||||
IS_IE: browser.name === 'ie',
|
||||
IS_IOS: browser.name === 'ios',
|
||||
IS_MAC: new Parser().getOS().name === 'Mac OS',
|
||||
IS_UBUNTU: new Parser().getOS().name === 'Ubuntu',
|
||||
IS_SAFARI: browser.name === 'safari',
|
||||
IS_WINDOWS: new Parser().getOS().name.includes('Windows')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export.
|
||||
*/
|
||||
|
||||
export default environment
|
||||
|
@@ -8,7 +8,6 @@
|
||||
"keycode": "^2.1.2",
|
||||
"lodash": "^4.13.1",
|
||||
"react": "^15.1.0",
|
||||
"to-camel-case": "^1.0.0",
|
||||
"ua-parser-js": "^0.7.10",
|
||||
"uid": "0.0.2"
|
||||
},
|
||||
@@ -21,10 +20,13 @@
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^13.0.1",
|
||||
"component-type": "^1.2.1",
|
||||
"mocha": "^2.5.3",
|
||||
"mocha-phantomjs": "^4.0.2",
|
||||
"react-dom": "^15.1.0",
|
||||
"read-metadata": "^1.0.0",
|
||||
"standard": "^7.1.2",
|
||||
"to-camel-case": "^1.0.0",
|
||||
"watchify": "^3.7.0"
|
||||
}
|
||||
}
|
||||
|
185
test/helpers/assert-json.js
Normal file
185
test/helpers/assert-json.js
Normal file
@@ -0,0 +1,185 @@
|
||||
|
||||
import assert from 'assert'
|
||||
import type from 'component-type'
|
||||
|
||||
/**
|
||||
* Assertion error.
|
||||
*/
|
||||
|
||||
const AssertionError = assert.AssertionError
|
||||
|
||||
/**
|
||||
* Assert that an `actual` JSON object equals an `expected` value.
|
||||
*
|
||||
* @param {Object} actual
|
||||
* @param {Object} expected
|
||||
* @throws {AssertionError}
|
||||
*/
|
||||
|
||||
export function equal(actual, expected, message) {
|
||||
if (!test(actual, expected)) {
|
||||
throw new AssertionError({
|
||||
actual: actual,
|
||||
expected: wrap(actual, expected),
|
||||
operator: '==',
|
||||
stackStartFunction: equal
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an `actual` JSON object does not equal an `expected` value.
|
||||
*
|
||||
* @param {Object} actual
|
||||
* @param {Object} expected
|
||||
* @throws {AssertionError}
|
||||
*/
|
||||
|
||||
export function notEqual(actual, expected, message) {
|
||||
if (test(actual, expected)) {
|
||||
throw new AssertionError({
|
||||
actual: actual,
|
||||
expected: wrap(actual, expected),
|
||||
operator: '!=',
|
||||
stackStartFunction: notEqual
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an `actual` JSON object strict equals an `expected` value.
|
||||
*
|
||||
* @param {Object} actual
|
||||
* @param {Object} expected
|
||||
* @throws {AssertionError}
|
||||
*/
|
||||
|
||||
export function strictEqual(actual, expected, message) {
|
||||
if (!test(actual, expected, true)) {
|
||||
throw new AssertionError({
|
||||
actual: actual,
|
||||
expected: wrap(actual, expected),
|
||||
operator: '===',
|
||||
stackStartFunction: equal
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that an `actual` JSON object does not strict equal an `expected` value.
|
||||
*
|
||||
* @param {Object} actual
|
||||
* @param {Object} expected
|
||||
* @throws {AssertionError}
|
||||
*/
|
||||
|
||||
export function notStrictEqual(actual, expected, message) {
|
||||
if (test(actual, expected, true)) {
|
||||
throw new AssertionError({
|
||||
actual: actual,
|
||||
expected: wrap(actual, expected),
|
||||
operator: '!==',
|
||||
stackStartFunction: notEqual
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an `actual` JSON value equals an `expected` JSON value.
|
||||
*
|
||||
* If a function is passed as any value, it is called with the actual value and
|
||||
* must return a boolean.
|
||||
*
|
||||
* Strict mode uses strict equality, forces arrays to be of the same length, and
|
||||
* objects to have the same keys.
|
||||
*
|
||||
* @param {Mixed} actual
|
||||
* @param {Mixed} expected
|
||||
* @param {Boolean} strict
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function test(actual, expected, strict) {
|
||||
if (type(expected) == 'function') return !! expected(actual)
|
||||
if (type(actual) != type(expected)) return false
|
||||
|
||||
switch (type(expected)) {
|
||||
case 'object':
|
||||
return object(actual, expected, strict)
|
||||
case 'array':
|
||||
return array(actual, expected, strict)
|
||||
default:
|
||||
return strict ? actual === expected : actual == expected
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an `actual` object equals an `expected` object.
|
||||
*
|
||||
* @param {Object} object
|
||||
* @param {Object} expected
|
||||
* @param {Boolean} strict
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function object(actual, expected, strict) {
|
||||
if (strict) {
|
||||
var ka = Object.keys(actual).sort()
|
||||
var ke = Object.keys(expected).sort()
|
||||
if (!test(ka, ke, strict)) return false
|
||||
}
|
||||
|
||||
for (var key in expected) {
|
||||
if (!test(actual[key], expected[key], strict)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an `actual` array equals an `expected` array.
|
||||
*
|
||||
* @param {Array} actual
|
||||
* @param {Array} expected
|
||||
* @param {Boolean} strict
|
||||
* @return {Boolean}
|
||||
*/
|
||||
|
||||
function array(actual, expected, strict) {
|
||||
if (strict) {
|
||||
if (!test(actual.length, expected.length, strict)) return false
|
||||
}
|
||||
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
if (!test(actual[i], expected[i], strict)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an expected value to remove annoying false negatives.
|
||||
*
|
||||
* @param {Mixed} actual
|
||||
* @param {Mixed} expected
|
||||
* @return {Mixed}
|
||||
*/
|
||||
|
||||
function wrap(actual, expected) {
|
||||
if (type(expected) == 'function') return expected(actual) ? actual : expected
|
||||
if (type(actual) != type(expected)) return expected
|
||||
|
||||
if (type(expected) == 'object') {
|
||||
for (var key in expected) {
|
||||
expected[key] = wrap(actual[key], expected[key])
|
||||
}
|
||||
}
|
||||
|
||||
if (type(expected) == 'array') {
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
expected[i] = wrap(actual[i], expected[i])
|
||||
}
|
||||
}
|
||||
|
||||
return expected
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
|
||||
const assert = require('assert')
|
||||
const Editor = require('..')
|
||||
|
||||
/**
|
||||
* Tests.
|
||||
*/
|
||||
|
||||
describe('server', () => {
|
||||
|
||||
})
|
2
test/server/index.js
Normal file
2
test/server/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
import './transforms'
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ord
|
@@ -0,0 +1,18 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const second = texts.last()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: second.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: woother
|
@@ -0,0 +1,18 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const second = texts.last()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: second.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wordanother
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length - 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wor
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wrd
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ""
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ord
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const second = texts.last()
|
||||
const range = selection.merge({
|
||||
anchorKey: second.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: second.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wordanother
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wor
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wrd
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteBackwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- 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.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteForwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- 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.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteForwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: ord
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteForwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: another
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wordanother
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length - 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length - 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteForwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wor
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.deleteForwardAtRange(range)
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wrd
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 3,
|
||||
focusKey: first.key,
|
||||
focusOffset: 3
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: w
|
||||
- text: or
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: w
|
||||
- text: ora
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: w
|
||||
- text: or
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: wa
|
||||
- text: or
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 2,
|
||||
focusKey: first.key,
|
||||
focusOffset: 2
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: w
|
||||
- text: or
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,12 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: w
|
||||
- text: oar
|
||||
marks:
|
||||
- type: bold
|
||||
- text: d
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: aword
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, ' ')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- 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.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 0,
|
||||
focusKey: first.key,
|
||||
focusOffset: 0
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a few words ')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: a few words word
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: worda
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, ' ')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- 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.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: first.length,
|
||||
focusKey: first.key,
|
||||
focusOffset: first.length
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, ' a few words')
|
||||
.apply()
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word
|
@@ -0,0 +1,8 @@
|
||||
|
||||
nodes:
|
||||
- kind: block
|
||||
type: paragraph
|
||||
nodes:
|
||||
- kind: text
|
||||
ranges:
|
||||
- text: word a few words
|
@@ -0,0 +1,17 @@
|
||||
|
||||
export default function (state) {
|
||||
const { document, selection } = state
|
||||
const texts = document.getTextNodes()
|
||||
const first = texts.first()
|
||||
const range = selection.merge({
|
||||
anchorKey: first.key,
|
||||
anchorOffset: 1,
|
||||
focusKey: first.key,
|
||||
focusOffset: 1
|
||||
})
|
||||
|
||||
return state
|
||||
.transform()
|
||||
.insertTextAtRange(range, 'a')
|
||||
.apply()
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user