1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-30 10:29:48 +02:00

Add controller (#2221)

* fold Stack into Editor

* switch Change objects to be tied to editors, not values

* introduce controller

* add the "commands" concept

* convert history into commands on `value.data`

* add the ability to not normalize on editor creation/setting

* convert schema to a mutable constructor

* add editor.command method

* convert plugin handlers to receive `next`

* switch commands to use the onCommand middleware

* add queries support, convert schema to queries

* split out browser plugin

* remove noop util

* fixes

* fixes

* start fixing tests, refactor hyperscript to be more literal

* fix slate-html-serializer tests

* fix schema tests with hyperscript

* fix text model tests with hyperscript

* fix more tests

* get all tests passing

* fix lint

* undo decorations example update

* update examples

* small changes to the api to make it nicer

* update docs

* update commands/queries plugin logic

* change normalizeNode and validateNode to be middleware

* fix decoration removal

* rename commands tests

* add useful errors to existing APIs

* update changelogs

* cleanup

* fixes

* update docs

* add editor docs
This commit is contained in:
Ian Storm Taylor
2018-10-09 14:03:27 -07:00
committed by GitHub
parent e6372d829a
commit 7a71de387c
709 changed files with 6073 additions and 5927 deletions

View File

@@ -4,6 +4,68 @@ This document maintains a list of changes to the `slate-hyperscript` package wit
---
### `0.11.0` — October 9, 2018
###### BREAKING
**Updated to the latest version of `slate`.** The `slate-hyperscript` codebase has been updated to be compatible with the latest version of `slate`, `0.42.0`. This is a backward incompatible upgrade, and so the peer dependency range has been bumped.
**`slate-hyperscript` no longer normalizes values.** This behavior was very problematic because it meant that you could not determine exactly what output you'd receive from any given hyperscript creation. The logic for creating child nodes was inconsistent, relying on the built-in normalization to help keep it "normal". While this is sometimes helpful, it makes writing tests for invalid states very tricky, if not impossible.
Now, `slate-hyperscript` does not do any normalization, meaning that you can create any document structure with it. For example, you can create a block node inside an inline node, even though a Slate editor wouldn't allow it. Or, if you don't create leaf text nodes, they won't exist in the output.
For example these are no longer equivalent:
```jsx
<document>
<paragraph>
<link>word</link>
</paragraph>
</document>
```
```jsx
<document>
<paragraph>
<text />
<link>word</link>
<text />
</paragraph>
</document>
```
Similarly, these are no longer equivalent either:
```jsx
<document>
<paragraph />
</document>
```
```jsx
<document>
<paragraph>
<text />
</paragraph>
</document>
```
This allows you to much more easily test invalid states and transition states. However, it means that you need to be more explicit in the "normal" states than previously.
**The `<text>` and `<mark>` creators now return useful objects.** This is a related change that makes the library more useful. Previously you could expect to receive a `value` from the `<value>` creator, but the others were less consistent. For example, the `<text>` creator would actually return an array, instead of the `Text` node that you expect.
```js
// Previously you had to do...
const text = <text>word</text>[0]
// But now it's more obvious...
const text = <text>word</text>
```
Similarly, the `mark` creator used to return a `Text` node. Now it returns a list of `Leaf` objects, which can be passed directly as children to the `<text>` creator.
---
### `0.10.0` — August 22, 2018
###### BREAKING

View File

@@ -0,0 +1,530 @@
import {
Decoration,
Document,
Leaf,
Mark,
Node,
Point,
Selection,
Text,
Value,
} from 'slate'
/**
* Auto-incrementing ID to keep track of paired decorations.
*
* @type {Number}
*/
let uid = 0
/**
* Create an anchor point.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {AnchorPoint}
*/
export function createAnchor(tagName, attributes, children) {
return new AnchorPoint(attributes)
}
/**
* Create a block.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Block}
*/
export function createBlock(tagName, attributes, children) {
const attrs = { ...attributes, object: 'block' }
const block = createNode('node', attrs, children)
return block
}
/**
* Create a cursor point.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {CursorPoint}
*/
export function createCursor(tagName, attributes, children) {
return new CursorPoint(attributes)
}
/**
* Create a decoration point, or wrap a list of leaves and set the decoration
* point tracker on them.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {DecorationPoint|List<Leaf>}
*/
export function createDecoration(tagName, attributes, children) {
const { key, data } = attributes
const type = tagName
if (key) {
return new DecorationPoint({ id: key, type, data })
}
const leaves = createLeaves('leaves', {}, children)
const first = leaves.first()
const last = leaves.last()
const id = `__decoration_${uid++}__`
const start = new DecorationPoint({ id, type, data })
const end = new DecorationPoint({ id, type, data })
setPoint(first, start, 0)
setPoint(last, end, last.text.length)
return leaves
}
/**
* Create a document.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Document}
*/
export function createDocument(tagName, attributes, children) {
const attrs = { ...attributes, object: 'document' }
const document = createNode('node', attrs, children)
return document
}
/**
* Create a focus point.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {FocusPoint}
*/
export function createFocus(tagName, attributes, children) {
return new FocusPoint(attributes)
}
/**
* Create an inline.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Inline}
*/
export function createInline(tagName, attributes, children) {
const attrs = { ...attributes, object: 'inline' }
const inline = createNode('node', attrs, children)
return inline
}
/**
* Create a list of leaves.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {List<Leaf>}
*/
export function createLeaves(tagName, attributes, children) {
const { marks = Mark.createSet() } = attributes
let length = 0
let leaves = Leaf.createList([])
let leaf
children.forEach(child => {
if (Leaf.isLeafList(child)) {
if (leaf) {
leaves = leaves.push(leaf)
leaf = null
}
child.forEach(l => {
l = preservePoint(l, obj => obj.addMarks(marks))
leaves = leaves.push(l)
})
} else {
if (!leaf) {
leaf = Leaf.create({ marks, text: '' })
length = 0
}
if (typeof child === 'string') {
const offset = leaf.text.length
leaf = preservePoint(leaf, obj => obj.insertText(offset, child))
length += child.length
}
if (isPoint(child)) {
setPoint(leaf, child, length)
}
}
})
if (!leaves.size && !leaf) {
leaf = Leaf.create({ marks, text: '' })
}
if (leaf) {
leaves = leaves.push(leaf)
}
return leaves
}
/**
* Create a list of leaves from a mark.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {List<Leaf>}
*/
export function createMark(tagName, attributes, children) {
const marks = Mark.createSet([attributes])
const leaves = createLeaves('leaves', { marks }, children)
return leaves
}
/**
* Create a node.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Node}
*/
export function createNode(tagName, attributes, children) {
const { object } = attributes
if (object === 'text') {
return createText('text', {}, children)
}
const nodes = []
let others = []
children.forEach(child => {
if (Node.isNode(child)) {
if (others.length) {
const text = createText('text', {}, others)
nodes.push(text)
}
nodes.push(child)
others = []
} else {
others.push(child)
}
})
if (others.length) {
const text = createText('text', {}, others)
nodes.push(text)
}
const node = Node.create({ ...attributes, nodes })
return node
}
/**
* Create a selection.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Selection}
*/
export function createSelection(tagName, attributes, children) {
const anchor = children.find(c => c instanceof AnchorPoint)
const focus = children.find(c => c instanceof FocusPoint)
const { marks, focused } = attributes
const selection = Selection.create({
marks,
isFocused: focused,
anchor: anchor && {
key: anchor.key,
offset: anchor.offset,
path: anchor.path,
},
focus: focus && {
key: focus.key,
offset: focus.offset,
path: focus.path,
},
})
return selection
}
/**
* Create a text node.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Text}
*/
export function createText(tagName, attributes, children) {
const { key } = attributes
const leaves = createLeaves('leaves', {}, children)
const text = Text.create({ key, leaves })
let length = 0
leaves.forEach(leaf => {
incrementPoint(leaf, length)
preservePoint(leaf, () => text)
length += leaf.text.length
})
return text
}
/**
* Create a value.
*
* @param {String} tagName
* @param {Object} attributes
* @param {Array} children
* @return {Value}
*/
export function createValue(tagName, attributes, children) {
const { data } = attributes
const document = children.find(Document.isDocument)
let selection = children.find(Selection.isSelection)
let anchor
let focus
let decorations = []
const partials = {}
// Search the document's texts to see if any of them have the anchor or
// focus information saved, or decorations applied.
if (document) {
document.getTexts().forEach(text => {
if (text.__anchor != null) {
anchor = Point.create({ key: text.key, offset: text.__anchor.offset })
}
if (text.__focus != null) {
focus = Point.create({ key: text.key, offset: text.__focus.offset })
}
if (text.__decorations != null) {
for (const dec of text.__decorations) {
const { id } = dec
const partial = partials[id]
delete partials[id]
if (!partial) {
dec.key = text.key
partials[id] = dec
continue
}
const decoration = Decoration.create({
anchor: {
key: partial.key,
offset: partial.offset,
},
focus: {
key: text.key,
offset: dec.offset,
},
mark: {
type: dec.type,
data: dec.data,
},
})
decorations.push(decoration)
}
}
})
}
if (Object.keys(partials).length > 0) {
throw new Error(
`Slate hyperscript must have both a start and an end defined for each decoration using the \`key=\` prop.`
)
}
if (anchor && !focus) {
throw new Error(
`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<anchor />\`. For collapsed selections, use \`<cursor />\` instead.`
)
}
if (!anchor && focus) {
throw new Error(
`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<focus />\`. For collapsed selections, use \`<cursor />\` instead.`
)
}
if (anchor || focus) {
if (!selection) {
selection = Selection.create({ anchor, focus, isFocused: true })
} else {
selection = selection.setPoints([anchor, focus])
}
} else if (!selection) {
selection = Selection.create()
}
selection = selection.normalize(document)
if (decorations.length > 0) {
decorations = decorations.map(d => d.normalize(document))
}
const value = Value.fromJSON({
data,
decorations,
document,
selection,
...attributes,
})
return value
}
/**
* Point classes that can be created at different points in the document and
* then searched for afterwards, for creating ranges.
*
* @type {Class}
*/
class CursorPoint {
constructor() {
this.offset = null
}
}
class AnchorPoint {
constructor(attrs = {}) {
const { key = null, offset = null, path = null } = attrs
this.key = key
this.offset = offset
this.path = path
}
}
class FocusPoint {
constructor(attrs = {}) {
const { key = null, offset = null, path = null } = attrs
this.key = key
this.offset = offset
this.path = path
}
}
class DecorationPoint {
constructor(attrs) {
const { id = null, data = {}, type } = attrs
this.id = id
this.offset = null
this.type = type
this.data = data
}
}
/**
* Increment any existing `point` on object by `n`.
*
* @param {Any} object
* @param {Number} n
*/
function incrementPoint(object, n) {
const { __anchor, __focus, __decorations } = object
if (__anchor != null) {
__anchor.offset += n
}
if (__focus != null && __focus !== __anchor) {
__focus.offset += n
}
if (__decorations != null) {
__decorations.forEach(d => (d.offset += n))
}
}
/**
* Check whether an `object` is a point.
*
* @param {Any} object
* @return {Boolean}
*/
function isPoint(object) {
return (
object instanceof AnchorPoint ||
object instanceof CursorPoint ||
object instanceof DecorationPoint ||
object instanceof FocusPoint
)
}
/**
* Preserve any point information on an object.
*
* @param {Any} object
* @param {Function} updator
* @return {Any}
*/
function preservePoint(object, updator) {
const { __anchor, __focus, __decorations } = object
const next = updator(object)
if (__anchor != null) next.__anchor = __anchor
if (__focus != null) next.__focus = __focus
if (__decorations != null) next.__decorations = __decorations
return next
}
/**
* Set a `point` on an `object`.
*
* @param {Any} object
* @param {*Point} point
* @param {Number} offset
*/
function setPoint(object, point, offset) {
if (point instanceof AnchorPoint || point instanceof CursorPoint) {
point.offset = offset
object.__anchor = point
}
if (point instanceof FocusPoint || point instanceof CursorPoint) {
point.offset = offset
object.__focus = point
}
if (point instanceof DecorationPoint) {
point.offset = offset
object.__decorations = object.__decorations || []
object.__decorations = object.__decorations.concat(point)
}
}

View File

@@ -1,286 +1,19 @@
import isPlainObject from 'is-plain-object'
import {
Block,
Decoration,
Document,
Inline,
Mark,
Node,
Point,
Selection,
Text,
Value,
} from 'slate'
/**
* Point classes that can be created at different points in the document and
* then searched for afterwards, for creating ranges.
*
* @type {Class}
*/
class CursorPoint {
constructor() {
this.offset = null
}
}
class AnchorPoint {
constructor(attrs = {}) {
const { key = null, offset = null, path = null } = attrs
this.key = key
this.offset = offset
this.path = path
}
}
class FocusPoint {
constructor(attrs = {}) {
const { key = null, offset = null, path = null } = attrs
this.key = key
this.offset = offset
this.path = path
}
}
class DecorationPoint {
constructor(attrs) {
const { key = null, data = {}, type } = attrs
this.id = key
this.offset = 0
this.type = type
this.data = data
}
combine = focus => {
if (!(focus instanceof DecorationPoint)) {
throw new Error('misaligned decorations')
}
return Decoration.create({
anchor: {
key: this.key,
offset: this.offset,
},
focus: {
key: focus.key,
offset: focus.offset,
},
mark: {
type: this.type,
data: this.data,
},
})
}
}
/**
* The default Slate hyperscript creator functions.
*
* @type {Object}
*/
const CREATORS = {
anchor(tagName, attributes, children) {
return new AnchorPoint(attributes)
},
block(tagName, attributes, children) {
return Block.create({
...attributes,
nodes: createChildren(children),
})
},
cursor(tagName, attributes, children) {
return new CursorPoint()
},
decoration(tagName, attributes, children) {
const { key, data } = attributes
const type = tagName
if (key) {
return new DecorationPoint({ key, type, data })
}
const nodes = createChildren(children)
const node = nodes[0]
const { __decorations = [] } = node
const __decoration = {
anchorOffset: 0,
focusOffset: nodes.reduce((len, n) => len + n.text.length, 0),
type,
data,
}
__decorations.push(__decoration)
node.__decorations = __decorations
return nodes
},
document(tagName, attributes, children) {
return Document.create({
...attributes,
nodes: createChildren(children),
})
},
focus(tagName, attributes, children) {
return new FocusPoint(attributes)
},
inline(tagName, attributes, children) {
return Inline.create({
...attributes,
nodes: createChildren(children),
})
},
mark(tagName, attributes, children) {
const marks = Mark.createSet([attributes])
const nodes = createChildren(children, { marks })
return nodes
},
selection(tagName, attributes, children) {
const anchor = children.find(c => c instanceof AnchorPoint)
const focus = children.find(c => c instanceof FocusPoint)
const { marks, focused } = attributes
const selection = Selection.create({
marks,
isFocused: focused,
anchor: anchor && {
key: anchor.key,
offset: anchor.offset,
path: anchor.path,
},
focus: focus && {
key: focus.key,
offset: focus.offset,
path: focus.path,
},
})
return selection
},
text(tagName, attributes, children) {
const nodes = createChildren(children, { key: attributes.key })
return nodes
},
value(tagName, attributes, children) {
const { data, normalize = true } = attributes
const document = children.find(Document.isDocument)
let selection = children.find(Selection.isSelection) || Selection.create()
let anchor
let focus
let decorations = []
const partials = {}
// Search the document's texts to see if any of them have the anchor or
// focus information saved, or decorations applied.
if (document) {
document.getTexts().forEach(text => {
if (text.__anchor != null) {
anchor = Point.create({ key: text.key, offset: text.__anchor.offset })
}
if (text.__focus != null) {
focus = Point.create({ key: text.key, offset: text.__focus.offset })
}
if (text.__decorations != null) {
text.__decorations.forEach(dec => {
const { id } = dec
let range
if (!id) {
range = Decoration.create({
anchor: {
key: text.key,
offset: dec.anchorOffset,
},
focus: {
key: text.key,
offset: dec.focusOffset,
},
mark: {
type: dec.type,
data: dec.data,
},
})
} else if (partials[id]) {
const partial = partials[id]
delete partials[id]
range = Decoration.create({
anchor: {
key: partial.key,
offset: partial.offset,
},
focus: {
key: text.key,
offset: dec.offset,
},
mark: {
type: dec.type,
data: dec.data,
},
})
} else {
dec.key = text.key
partials[id] = dec
}
if (range) {
decorations.push(range)
}
})
}
})
}
if (Object.keys(partials).length > 0) {
throw new Error(
`Slate hyperscript must have both a start and an end defined for each decoration using the \`key=\` prop.`
)
}
if (anchor && !focus) {
throw new Error(
`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<anchor />\`. For collapsed selections, use \`<cursor />\` instead.`
)
}
if (!anchor && focus) {
throw new Error(
`Slate hyperscript ranges must have both \`<anchor />\` and \`<focus />\` defined if one is defined, but you only defined \`<focus />\`. For collapsed selections, use \`<cursor />\` instead.`
)
}
let value = Value.fromJSON(
{ data, document, selection, ...attributes },
{ normalize }
)
if (anchor || focus) {
selection = selection.setPoints([anchor, focus])
selection = selection.setIsFocused(true)
selection = selection.normalize(value.document)
value = value.set('selection', selection)
}
if (decorations.length > 0) {
decorations = decorations.map(d => d.normalize(value.document))
decorations = Decoration.createList(decorations)
value = value.set('decorations', decorations)
}
return value
},
}
createAnchor,
createBlock,
createCursor,
createDecoration,
createDocument,
createFocus,
createInline,
createMark,
createNode,
createSelection,
createText,
createValue,
} from './creators'
/**
* Create a Slate hyperscript function with `options`.
@@ -290,7 +23,39 @@ const CREATORS = {
*/
function createHyperscript(options = {}) {
const creators = resolveCreators(options)
const { blocks = {}, inlines = {}, marks = {}, decorations = {} } = options
const creators = {
anchor: createAnchor,
block: createBlock,
cursor: createCursor,
decoration: createDecoration,
document: createDocument,
focus: createFocus,
inline: createInline,
mark: createMark,
node: createNode,
selection: createSelection,
text: createText,
value: createValue,
...(options.creators || {}),
}
for (const key in blocks) {
creators[key] = normalizeCreator(blocks[key], createBlock)
}
for (const key in inlines) {
creators[key] = normalizeCreator(inlines[key], createInline)
}
for (const key in marks) {
creators[key] = normalizeCreator(marks[key], createMark)
}
for (const key in decorations) {
creators[key] = normalizeCreator(decorations[key], createDecoration)
}
function create(tagName, attributes, ...children) {
const creator = creators[tagName]
@@ -312,195 +77,22 @@ function createHyperscript(options = {}) {
.filter(child => Boolean(child))
.reduce((memo, child) => memo.concat(child), [])
const element = creator(tagName, attributes, children)
return element
const ret = creator(tagName, attributes, children)
return ret
}
return create
}
/**
* Create an array of `children`, storing selection anchor and focus.
*
* @param {Array} children
* @param {Object} options
* @return {Array}
*/
function createChildren(children, options = {}) {
const array = []
let length = 0
// When creating the new node, try to preserve a key if one exists.
const firstNodeOrText = children.find(c => typeof c !== 'string')
const firstText = Text.isText(firstNodeOrText) ? firstNodeOrText : null
const key = options.key ? options.key : firstText ? firstText.key : undefined
let node = Text.create({ key, leaves: [{ text: '', marks: options.marks }] })
// Create a helper to update the current node while preserving any stored
// anchor or focus information.
function setNode(next) {
const { __anchor, __focus, __decorations } = node
if (__anchor != null) next.__anchor = __anchor
if (__focus != null) next.__focus = __focus
if (__decorations != null) next.__decorations = __decorations
node = next
}
children.forEach((child, index) => {
const isLast = index === children.length - 1
// If the child is a non-text node, push the current node and the new child
// onto the array, then creating a new node for future selection tracking.
if (Node.isNode(child) && !Text.isText(child)) {
if (
node.text.length ||
node.__anchor != null ||
node.__focus != null ||
node.getMarksAtIndex(0).size
) {
array.push(node)
}
array.push(child)
node = isLast
? null
: Text.create({ leaves: [{ text: '', marks: options.marks }] })
length = 0
}
// If the child is a string insert it into the node.
if (typeof child == 'string') {
setNode(node.insertText(node.text.length, child, options.marks))
length += child.length
}
// If the node is a `Text` add its text and marks to the existing node. If
// the existing node is empty, and the `key` option wasn't set, preserve the
// child's key when updating the node.
if (Text.isText(child)) {
const { __anchor, __focus, __decorations } = child
let i = node.text.length
if (!options.key && node.text.length == 0) {
setNode(node.set('key', child.key))
}
child.getLeaves().forEach(leaf => {
let { marks } = leaf
if (options.marks) marks = marks.union(options.marks)
setNode(node.insertText(i, leaf.text, marks))
i += leaf.text.length
})
if (__anchor != null) {
node.__anchor = new AnchorPoint()
node.__anchor.offset = __anchor.offset + length
}
if (__focus != null) {
node.__focus = new FocusPoint()
node.__focus.offset = __focus.offset + length
}
if (__decorations != null) {
__decorations.forEach(d => {
if (d instanceof DecorationPoint) {
d.offset += length
} else {
d.anchorOffset += length
d.focusOffset += length
}
})
node.__decorations = node.__decorations || []
node.__decorations = node.__decorations.concat(__decorations)
}
length += child.text.length
}
if (child instanceof AnchorPoint || child instanceof CursorPoint) {
child.offset = length
node.__anchor = child
}
if (child instanceof FocusPoint || child instanceof CursorPoint) {
child.offset = length
node.__focus = child
}
if (child instanceof DecorationPoint) {
child.offset = length
node.__decorations = node.__decorations || []
node.__decorations = node.__decorations.concat(child)
}
})
// Make sure the most recent node is added.
if (node != null) {
array.push(node)
}
return array
}
/**
* Resolve a set of hyperscript creators an `options` object.
*
* @param {Object} options
* @return {Object}
*/
function resolveCreators(options) {
const {
blocks = {},
inlines = {},
marks = {},
decorations = {},
schema,
} = options
const creators = {
...CREATORS,
...(options.creators || {}),
}
Object.keys(blocks).map(key => {
creators[key] = normalizeNode(blocks[key], 'block')
})
Object.keys(inlines).map(key => {
creators[key] = normalizeNode(inlines[key], 'inline')
})
Object.keys(marks).map(key => {
creators[key] = normalizeMark(marks[key])
})
Object.keys(decorations).map(key => {
creators[key] = normalizeNode(decorations[key], 'decoration')
})
creators.value = (tagName, attributes = {}, children) => {
const attrs = { schema, ...attributes }
return CREATORS.value(tagName, attrs, children)
}
return creators
}
/**
* Normalize a node creator of `value` and `object`.
* Normalize a `creator` of `value`.
*
* @param {Function|Object|String} value
* @param {String} object
* @param {Function} creator
* @return {Function}
*/
function normalizeNode(value, object) {
function normalizeCreator(value, creator) {
if (typeof value == 'function') {
return value
}
@@ -514,7 +106,6 @@ function normalizeNode(value, object) {
const { key, ...rest } = attributes
const attrs = {
...value,
object,
key,
data: {
...(value.data || {}),
@@ -522,47 +113,12 @@ function normalizeNode(value, object) {
},
}
return CREATORS[object](tagName, attrs, children)
return creator(tagName, attrs, children)
}
}
throw new Error(
`Slate hyperscript ${object} creators can be either functions, objects or strings, but you passed: ${value}`
)
}
/**
* Normalize a mark creator of `value`.
*
* @param {Function|Object|String} value
* @return {Function}
*/
function normalizeMark(value) {
if (typeof value == 'function') {
return value
}
if (typeof value == 'string') {
value = { type: value }
}
if (isPlainObject(value)) {
return (tagName, attributes, children) => {
const attrs = {
...value,
data: {
...(value.data || {}),
...attributes,
},
}
return CREATORS.mark(tagName, attrs, children)
}
}
throw new Error(
`Slate hyperscript mark creators can be either functions, objects or strings, but you passed: ${value}`
`Slate hyperscript creators can be either functions, objects or strings, but you passed: ${value}`
)
}

View File

@@ -0,0 +1,12 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <block type="paragraph" />
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [],
}

View File

@@ -0,0 +1,23 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<inline type="link" />
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'inline',
type: 'link',
data: {},
nodes: [],
},
],
}

View File

@@ -3,18 +3,19 @@
import h from 'slate-hyperscript'
export const input = (
<document>
<block type="paragraph">word</block>
</document>
<block type="paragraph">
<inline type="link">word</inline>
</block>
)
export const output = {
object: 'document',
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
object: 'inline',
type: 'link',
data: {},
nodes: [
{

View File

@@ -0,0 +1,33 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<mark type="bold" />
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
],
},
],
}

View File

@@ -0,0 +1,33 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<mark type="bold">word</mark>
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
],
},
],
}

View File

@@ -0,0 +1,62 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<mark type="bold">
w<mark type="italic">or</mark>d
</mark>
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'w',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'or',
marks: [
{
object: 'mark',
type: 'italic',
data: {},
},
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: 'd',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
],
},
],
}

View File

@@ -0,0 +1,23 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <block type="paragraph">word</block>
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
},
],
}

View File

@@ -0,0 +1,27 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<text />
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [],
},
],
},
],
}

View File

@@ -0,0 +1,27 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<block type="paragraph">
<text>word</text>
</block>
)
export const output = {
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
},
],
}

View File

@@ -21,7 +21,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -6,14 +6,18 @@ export const input = (
<value>
<document>
<block type="paragraph">
<text />
<inline type="link">
on<anchor />e
</inline>
<text />
</block>
<block type="paragraph">
<text />
<inline type="link">
t<focus />wo
</inline>
<text />
</block>
</document>
</value>
@@ -33,13 +37,13 @@ export const output = {
nodes: [
{
object: 'block',
key: '3',
key: '4',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '13',
key: '0',
leaves: [
{
object: 'leaf',
@@ -50,13 +54,13 @@ export const output = {
},
{
object: 'inline',
key: '1',
key: '2',
type: 'link',
data: {},
nodes: [
{
object: 'text',
key: '0',
key: '1',
leaves: [
{
object: 'leaf',
@@ -69,7 +73,7 @@ export const output = {
},
{
object: 'text',
key: '14',
key: '3',
leaves: [
{
object: 'leaf',
@@ -82,13 +86,13 @@ export const output = {
},
{
object: 'block',
key: '7',
key: '9',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '11',
key: '5',
leaves: [
{
object: 'leaf',
@@ -99,13 +103,13 @@ export const output = {
},
{
object: 'inline',
key: '5',
key: '7',
type: 'link',
data: {},
nodes: [
{
object: 'text',
key: '4',
key: '6',
leaves: [
{
object: 'leaf',
@@ -118,7 +122,7 @@ export const output = {
},
{
object: 'text',
key: '12',
key: '8',
leaves: [
{
object: 'leaf',
@@ -135,13 +139,13 @@ export const output = {
object: 'selection',
anchor: {
object: 'point',
key: '0',
key: '1',
path: [0, 1, 0],
offset: 2,
},
focus: {
object: 'point',
key: '4',
key: '6',
path: [1, 1, 0],
offset: 1,
},

View File

@@ -24,7 +24,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '6',
key: '4',
data: {},
nodes: [
{

View File

@@ -24,7 +24,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '6',
key: '4',
data: {},
nodes: [
{

View File

@@ -24,7 +24,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '6',
key: '4',
data: {},
nodes: [
{

View File

@@ -25,7 +25,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '9',
key: '6',
data: {},
nodes: [
{

View File

@@ -25,7 +25,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '9',
key: '6',
data: {},
nodes: [
{

View File

@@ -25,7 +25,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '9',
key: '6',
data: {},
nodes: [
{

View File

@@ -21,7 +21,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -21,7 +21,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -21,7 +21,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -27,7 +27,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '6',
key: '5',
nodes: [
{
object: 'block',

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '6',
key: '5',
nodes: [
{
object: 'block',

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '6',
key: '5',
nodes: [
{
object: 'block',

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '3',
key: '2',
nodes: [
{
object: 'block',

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '3',
key: '2',
nodes: [
{
object: 'block',

View File

@@ -26,7 +26,7 @@ export const output = {
document: {
object: 'document',
data: {},
key: '3',
key: '2',
nodes: [
{
object: 'block',

View File

@@ -101,18 +101,7 @@ export const output = {
data: {
src: 'https://...',
},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [],
},
],
},
],
nodes: [],
},
],
},

View File

@@ -30,7 +30,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '3',
key: '2',
data: {},
nodes: [
{

View File

@@ -33,7 +33,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '6',
key: '4',
data: {},
nodes: [
{

View File

@@ -0,0 +1,11 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <document />
export const output = {
object: 'document',
data: {},
nodes: [],
}

View File

@@ -0,0 +1,12 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <inline type="link" />
export const output = {
object: 'inline',
type: 'link',
data: {},
nodes: [],
}

View File

@@ -0,0 +1,23 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <inline type="link">word</inline>
export const output = {
object: 'inline',
type: 'link',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
},
],
}

View File

@@ -1,41 +0,0 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<document>
<block type="paragraph">
<mark type="bold" />
</block>
</document>
)
export const output = {
object: 'document',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [
{
type: 'bold',
object: 'mark',
data: {},
},
],
},
],
},
],
},
],
}

View File

@@ -1,39 +0,0 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">word</block>
<text>invalid</text>
</document>
</value>
)
export const output = {
object: 'value',
document: {
object: 'document',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
},
],
},
],
},
}

View File

@@ -1,49 +0,0 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value normalize={false}>
<document>
<block type="paragraph">word</block>
<text>invalid</text>
</document>
</value>
)
export const output = {
object: 'value',
document: {
object: 'document',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
},
],
},
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'invalid',
marks: [],
},
],
},
],
},
}

View File

@@ -6,7 +6,7 @@ export const input = (
<value>
<document>
<block type="paragraph">
one<text key="a">two</text>three
<text key="a">two</text>
</block>
</document>
<selection>
@@ -25,7 +25,7 @@ export const output = {
object: 'value',
document: {
object: 'document',
key: '2',
key: '1',
data: {},
nodes: [
{
@@ -40,7 +40,7 @@ export const output = {
leaves: [
{
object: 'leaf',
text: 'onetwothree',
text: 'two',
marks: [],
},
],

View File

@@ -0,0 +1,16 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <text />
export const output = {
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [],
},
],
}

View File

@@ -0,0 +1,16 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <text>word</text>
export const output = {
object: 'text',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
}

View File

@@ -1,73 +0,0 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<document>
<block type="paragraph">
Cat <inline type="link">is</inline>
<text key="a"> cute</text>
</block>
</document>
)
export const options = {
preserveKeys: true,
}
export const output = {
object: 'document',
key: '6',
data: {},
nodes: [
{
object: 'block',
key: '4',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
key: '2',
leaves: [
{
object: 'leaf',
text: 'Cat ',
marks: [],
},
],
},
{
object: 'inline',
key: '1',
type: 'link',
data: {},
nodes: [
{
object: 'text',
key: '0',
leaves: [
{
object: 'leaf',
text: 'is',
marks: [],
},
],
},
],
},
{
object: 'text',
key: 'a',
leaves: [
{
object: 'leaf',
text: ' cute',
marks: [],
},
],
},
],
},
],
}

View File

@@ -0,0 +1,21 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = <text key="a">word</text>
export const options = {
preserveKeys: true,
}
export const output = {
object: 'text',
key: 'a',
leaves: [
{
object: 'leaf',
text: 'word',
marks: [],
},
],
}

View File

@@ -1,109 +0,0 @@
/** @jsx h */
import h from 'slate-hyperscript'
export const input = (
<value>
<document>
<block type="paragraph">
A string of <mark type="bold">bold</mark> in a{' '}
<inline type="link" data={{ src: 'http://slatejs.org' }}>
Slate
</inline>{' '}
editor!
</block>
<block type="image" data={{ src: 'https://...' }} />
</document>
</value>
)
export const output = {
object: 'value',
document: {
object: 'document',
data: {},
nodes: [
{
object: 'block',
type: 'paragraph',
data: {},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'A string of ',
marks: [],
},
{
object: 'leaf',
text: 'bold',
marks: [
{
object: 'mark',
type: 'bold',
data: {},
},
],
},
{
object: 'leaf',
text: ' in a ',
marks: [],
},
],
},
{
object: 'inline',
type: 'link',
data: {
src: 'http://slatejs.org',
},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: 'Slate',
marks: [],
},
],
},
],
},
{
object: 'text',
leaves: [
{
object: 'leaf',
text: ' editor!',
marks: [],
},
],
},
],
},
{
object: 'block',
type: 'image',
data: {
src: 'https://...',
},
nodes: [
{
object: 'text',
leaves: [
{
object: 'leaf',
text: '',
marks: [],
},
],
},
],
},
],
},
}