1
0
mirror of https://github.com/ianstormtaylor/slate.git synced 2025-08-30 18:39:51 +02:00

add voids: true option to transforms and queries (#3265)

This commit is contained in:
Ian Storm Taylor
2019-12-06 17:24:13 -05:00
committed by GitHub
parent ff516ccad8
commit 313debddba
48 changed files with 996 additions and 198 deletions

View File

@@ -145,7 +145,7 @@ export const createEditor = (): Editor => {
// Ensure that block and inline nodes have at least one text child. // Ensure that block and inline nodes have at least one text child.
if (Element.isElement(node) && node.children.length === 0) { if (Element.isElement(node) && node.children.length === 0) {
const child = { text: '' } const child = { text: '' }
Editor.insertNodes(editor, child, { at: path.concat(0) }) Editor.insertNodes(editor, child, { at: path.concat(0), voids: true })
return return
} }
@@ -175,43 +175,42 @@ export const createEditor = (): Editor => {
// other inline nodes, or parent blocks that only contain inlines and // other inline nodes, or parent blocks that only contain inlines and
// text. // text.
if (isInlineOrText !== shouldHaveInlines) { if (isInlineOrText !== shouldHaveInlines) {
Editor.removeNodes(editor, { at: path.concat(n) }) Editor.removeNodes(editor, { at: path.concat(n), voids: true })
n-- n--
continue } else if (Element.isElement(child)) {
}
if (Element.isElement(child)) {
// Ensure that inline nodes are surrounded by text nodes. // Ensure that inline nodes are surrounded by text nodes.
if (editor.isInline(child)) { if (editor.isInline(child)) {
if (prev == null || !Text.isText(prev)) { if (prev == null || !Text.isText(prev)) {
const newChild = { text: '' } const newChild = { text: '' }
Editor.insertNodes(editor, newChild, { at: path.concat(n) }) Editor.insertNodes(editor, newChild, {
at: path.concat(n),
voids: true,
})
n++ n++
continue } else if (isLast) {
}
if (isLast) {
const newChild = { text: '' } const newChild = { text: '' }
Editor.insertNodes(editor, newChild, { at: path.concat(n + 1) }) Editor.insertNodes(editor, newChild, {
at: path.concat(n + 1),
voids: true,
})
n++ n++
continue
} }
} }
} else { } else {
// Merge adjacent text nodes that are empty or match. // Merge adjacent text nodes that are empty or match.
if (prev != null && Text.isText(prev)) { if (prev != null && Text.isText(prev)) {
if (Text.equals(child, prev, { loose: true })) { if (Text.equals(child, prev, { loose: true })) {
Editor.mergeNodes(editor, { at: path.concat(n) }) Editor.mergeNodes(editor, { at: path.concat(n), voids: true })
n-- n--
continue
} else if (prev.text === '') { } else if (prev.text === '') {
Editor.removeNodes(editor, { at: path.concat(n - 1) }) Editor.removeNodes(editor, {
at: path.concat(n - 1),
voids: true,
})
n-- n--
continue
} else if (isLast && child.text === '') { } else if (isLast && child.text === '') {
Editor.removeNodes(editor, { at: path.concat(n) }) Editor.removeNodes(editor, { at: path.concat(n), voids: true })
n-- n--
continue
} }
} }
} }

View File

@@ -137,9 +137,10 @@ export const LocationQueries = {
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest' mode?: 'all' | 'highest'
reverse?: boolean reverse?: boolean
voids?: boolean
} = {} } = {}
): Iterable<ElementEntry> { ): Iterable<ElementEntry> {
for (const [node, path] of this.nodes(editor, options)) { for (const [node, path] of Editor.nodes(editor, options)) {
if (Element.isElement(node)) { if (Element.isElement(node)) {
yield [node, path] yield [node, path]
} }
@@ -239,9 +240,10 @@ export const LocationQueries = {
options: { options: {
at?: Location at?: Location
reverse?: boolean reverse?: boolean
voids?: boolean
} = {} } = {}
): Iterable<NodeEntry> { ): Iterable<NodeEntry> {
const { at = editor.selection, reverse = false } = options const { at = editor.selection, reverse = false, voids = false } = options
if (!at) { if (!at) {
return return
@@ -253,7 +255,7 @@ export const LocationQueries = {
for (const [n, p] of Node.levels(editor, path)) { for (const [n, p] of Node.levels(editor, path)) {
levels.push([n, p]) levels.push([n, p])
if (Element.isElement(n) && editor.isVoid(n)) { if (!voids && Element.isElement(n) && editor.isVoid(n)) {
break break
} }
} }
@@ -269,10 +271,18 @@ export const LocationQueries = {
* Get the first matching node in a single branch of the document. * Get the first matching node in a single branch of the document.
*/ */
match(editor: Editor, at: Location, match: NodeMatch): NodeEntry | undefined { match(
editor: Editor,
at: Location,
match: NodeMatch,
options: {
voids?: boolean
} = {}
): NodeEntry | undefined {
const { voids = false } = options
const path = Editor.path(editor, at) const path = Editor.path(editor, at)
for (const entry of Editor.levels(editor, { at: path })) { for (const entry of Editor.levels(editor, { at: path, voids })) {
if (Editor.isMatch(editor, entry, match)) { if (Editor.isMatch(editor, entry, match)) {
return entry return entry
} }
@@ -330,23 +340,21 @@ export const LocationQueries = {
* Get the matching node in the branch of the document after a location. * Get the matching node in the branch of the document after a location.
*/ */
next(editor: Editor, at: Location, match: NodeMatch): NodeEntry | undefined { next(
editor: Editor,
at: Location,
match: NodeMatch,
options: {
mode?: 'all' | 'highest'
voids?: boolean
} = {}
): NodeEntry | undefined {
const { mode = 'highest', voids = false } = options
const [, from] = Editor.last(editor, at) const [, from] = Editor.last(editor, at)
const [, to] = Editor.last(editor, []) const [, to] = Editor.last(editor, [])
const span: Span = [from, to] const span: Span = [from, to]
let i = 0 const [, next] = Editor.nodes(editor, { at: span, match, mode, voids })
return next
for (const entry of Editor.nodes(editor, {
at: span,
match,
mode: 'highest',
})) {
if (i === 1) {
return entry
}
i++
}
}, },
/** /**
@@ -377,6 +385,7 @@ export const LocationQueries = {
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest' mode?: 'all' | 'highest'
reverse?: boolean reverse?: boolean
voids?: boolean
} = {} } = {}
): Iterable<NodeEntry> { ): Iterable<NodeEntry> {
const { const {
@@ -384,6 +393,7 @@ export const LocationQueries = {
match, match,
mode = 'all', mode = 'all',
reverse = false, reverse = false,
voids = false,
} = options } = options
if (!at) { if (!at) {
@@ -407,20 +417,19 @@ export const LocationQueries = {
reverse, reverse,
from, from,
to, to,
pass: ([n]) => Element.isElement(n) && editor.isVoid(n), pass: ([n]) => (voids ? false : Element.isElement(n) && editor.isVoid(n)),
}) })
let prev: NodeEntry | undefined let prev: NodeEntry | undefined
for (const entry of iterable) { for (const entry of iterable) {
if (match != null) { if (match) {
if (mode === 'highest' && prev) { if (
const [, prevPath] = prev mode === 'highest' &&
const [, path] = entry prev &&
Path.compare(entry[1], prev[1]) === 0
if (Path.compare(path, prevPath) === 0) { ) {
continue continue
}
} }
if (!Editor.isMatch(editor, entry, match)) { if (!Editor.isMatch(editor, entry, match)) {
@@ -669,25 +678,25 @@ export const LocationQueries = {
previous( previous(
editor: Editor, editor: Editor,
at: Location, at: Location,
match: NodeMatch match: NodeMatch,
options: {
mode?: 'all' | 'highest'
voids?: boolean
} = {}
): NodeEntry | undefined { ): NodeEntry | undefined {
const { mode = 'highest', voids = false } = options
const [, from] = Editor.first(editor, at) const [, from] = Editor.first(editor, at)
const [, to] = Editor.first(editor, []) const [, to] = Editor.first(editor, [])
const span: Span = [from, to] const span: Span = [from, to]
let i = 0 const [, previous] = Editor.nodes(editor, {
for (const entry of Editor.nodes(editor, {
match,
at: span,
reverse: true, reverse: true,
mode: 'highest', at: span,
})) { match,
if (i === 1) { mode,
return entry voids,
} })
i++ return previous
}
}, },
/** /**
@@ -752,9 +761,10 @@ export const LocationQueries = {
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest' mode?: 'all' | 'highest'
reverse?: boolean reverse?: boolean
voids?: boolean
} = {} } = {}
): Iterable<TextEntry> { ): Iterable<TextEntry> {
for (const [node, path] of this.nodes(editor, options)) { for (const [node, path] of Editor.nodes(editor, options)) {
if (Text.isText(node)) { if (Text.isText(node)) {
yield [node, path] yield [node, path]
} }

View File

@@ -5,7 +5,14 @@ export const RangeQueries = {
* Convert a range into a non-hanging one. * Convert a range into a non-hanging one.
*/ */
unhangRange(editor: Editor, range: Range): Range { unhangRange(
editor: Editor,
range: Range,
options: {
voids?: boolean
} = {}
): Range {
const { voids = false } = options
let [start, end] = Range.edges(range) let [start, end] = Range.edges(range)
// PERF: exit early if we can guarantee that the range isn't hanging. // PERF: exit early if we can guarantee that the range isn't hanging.
@@ -22,6 +29,7 @@ export const RangeQueries = {
for (const [node, path] of Editor.texts(editor, { for (const [node, path] of Editor.texts(editor, {
at: before, at: before,
reverse: true, reverse: true,
voids,
})) { })) {
if (skip) { if (skip) {
skip = false skip = false

View File

@@ -23,11 +23,12 @@ export const NodeTransforms = {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
hanging?: boolean hanging?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { selection } = editor const { selection } = editor
const { hanging = false } = options const { hanging = false, voids = false } = options
let { at, match } = options let { at, match } = options
let select = false let select = false
@@ -41,19 +42,6 @@ export const NodeTransforms = {
const [node] = nodes const [node] = nodes
if (match == null) {
if (Path.isPath(at)) {
const path = at
match = ([, p]) => Path.equals(p, path)
} else if (Text.isText(node)) {
match = 'text'
} else if (editor.isInline(node)) {
match = ['inline', 'text']
} else {
match = 'block'
}
}
// By default, use the selection as the target location. But if there is // By default, use the selection as the target location. But if there is
// no selection, insert at the end of the document since that is such a // no selection, insert at the end of the document since that is such a
// common use case when inserting from a non-selected state. // common use case when inserting from a non-selected state.
@@ -78,6 +66,16 @@ export const NodeTransforms = {
} }
if (Point.isPoint(at)) { if (Point.isPoint(at)) {
if (match == null) {
if (Text.isText(node)) {
match = 'text'
} else if (editor.isInline(node)) {
match = ['inline', 'text']
} else {
match = 'block'
}
}
const atMatch = Editor.match(editor, at.path, match) const atMatch = Editor.match(editor, at.path, match)
if (atMatch) { if (atMatch) {
@@ -95,7 +93,7 @@ export const NodeTransforms = {
const parentPath = Path.parent(at) const parentPath = Path.parent(at)
let index = at[at.length - 1] let index = at[at.length - 1]
if (Editor.match(editor, parentPath, 'void')) { if (!voids && Editor.match(editor, parentPath, 'void')) {
return return
} }
@@ -125,26 +123,23 @@ export const NodeTransforms = {
options: { options: {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest'
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { at = editor.selection } = options const { at = editor.selection, mode = 'highest', voids = false } = options
let { match } = options let { match } = options
if (match == null) { if (match == null) {
if (Path.isPath(at)) { match = Path.isPath(at) ? matchPath(editor, at) : 'block'
const path = at
match = ([, p]) => Path.equals(p, path)
} else {
match = 'block'
}
} }
if (!at) { if (!at) {
return return
} }
const matches = Editor.nodes(editor, { at, match, mode: 'highest' }) const matches = Editor.nodes(editor, { at, match, mode, voids })
const pathRefs = Array.from(matches, ([, p]) => Editor.pathRef(editor, p)) const pathRefs = Array.from(matches, ([, p]) => Editor.pathRef(editor, p))
for (const pathRef of pathRefs) { for (const pathRef of pathRefs) {
@@ -161,15 +156,19 @@ export const NodeTransforms = {
const { length } = parent.children const { length } = parent.children
if (length === 1) { if (length === 1) {
Editor.moveNodes(editor, { at: path, to: Path.next(parentPath) }) const toPath = Path.next(parentPath)
Editor.removeNodes(editor, { at: parentPath }) Editor.moveNodes(editor, { at: path, to: toPath, voids })
Editor.removeNodes(editor, { at: parentPath, voids })
} else if (index === 0) { } else if (index === 0) {
Editor.moveNodes(editor, { at: path, to: parentPath }) Editor.moveNodes(editor, { at: path, to: parentPath, voids })
} else if (index === length - 1) { } else if (index === length - 1) {
Editor.moveNodes(editor, { at: path, to: Path.next(parentPath) }) const toPath = Path.next(parentPath)
Editor.moveNodes(editor, { at: path, to: toPath, voids })
} else { } else {
Editor.splitNodes(editor, { at: Path.next(path) }) const splitPath = Path.next(path)
Editor.moveNodes(editor, { at: path, to: Path.next(parentPath) }) const toPath = Path.next(parentPath)
Editor.splitNodes(editor, { at: splitPath, voids })
Editor.moveNodes(editor, { at: path, to: toPath, voids })
} }
} }
}) })
@@ -186,25 +185,21 @@ export const NodeTransforms = {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
hanging?: boolean hanging?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
let { match, at = editor.selection } = options let { match, at = editor.selection } = options
const { hanging = false } = options const { hanging = false, voids = false } = options
if (match == null) {
if (Path.isPath(at)) {
const path = at
match = ([, p]) => Path.equals(p, path)
} else {
match = 'block'
}
}
if (!at) { if (!at) {
return return
} }
if (match == null) {
match = Path.isPath(at) ? matchPath(editor, at) : 'block'
}
if (!hanging && Range.isRange(at)) { if (!hanging && Range.isRange(at)) {
at = Editor.unhangRange(editor, at) at = Editor.unhangRange(editor, at)
} }
@@ -224,7 +219,7 @@ export const NodeTransforms = {
} }
} }
const current = Editor.match(editor, at, match) const current = Editor.match(editor, at, match, { voids })
if (!current) { if (!current) {
return return
@@ -241,7 +236,7 @@ export const NodeTransforms = {
prevMatch = 'inline' prevMatch = 'inline'
} }
const prev = Editor.previous(editor, at, prevMatch) const prev = Editor.previous(editor, at, prevMatch, { voids })
if (!prev) { if (!prev) {
return return
@@ -288,13 +283,13 @@ export const NodeTransforms = {
// If the node isn't already the next sibling of the previous node, move // If the node isn't already the next sibling of the previous node, move
// it so that it is before merging. // it so that it is before merging.
if (!isPreviousSibling) { if (!isPreviousSibling) {
Editor.moveNodes(editor, { at: path, to: newPath }) Editor.moveNodes(editor, { at: path, to: newPath, voids })
} }
// If there was going to be an empty ancestor of the node that was merged, // If there was going to be an empty ancestor of the node that was merged,
// we remove it from the tree. // we remove it from the tree.
if (emptyRef) { if (emptyRef) {
Editor.removeNodes(editor, { at: emptyRef.current! }) Editor.removeNodes(editor, { at: emptyRef.current!, voids })
} }
// If the target node that we're merging with is empty, remove it instead // If the target node that we're merging with is empty, remove it instead
@@ -305,7 +300,7 @@ export const NodeTransforms = {
(Element.isElement(prevNode) && Editor.isEmpty(editor, prevNode)) || (Element.isElement(prevNode) && Editor.isEmpty(editor, prevNode)) ||
(Text.isText(prevNode) && prevNode.text === '') (Text.isText(prevNode) && prevNode.text === '')
) { ) {
Editor.removeNodes(editor, { at: prevPath }) Editor.removeNodes(editor, { at: prevPath, voids })
} else { } else {
editor.apply({ editor.apply({
type: 'merge_node', type: 'merge_node',
@@ -331,28 +326,30 @@ export const NodeTransforms = {
options: { options: {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest'
to: Path to: Path
voids?: boolean
} }
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { to, at = editor.selection } = options const {
to,
at = editor.selection,
mode = 'highest',
voids = false,
} = options
let { match } = options let { match } = options
if (match == null) {
if (Path.isPath(at)) {
const path = at
match = ([, p]) => Path.equals(p, path)
} else {
match = 'block'
}
}
if (!at) { if (!at) {
return return
} }
if (match == null) {
match = Path.isPath(at) ? matchPath(editor, at) : 'block'
}
const toRef = Editor.pathRef(editor, to) const toRef = Editor.pathRef(editor, to)
const targets = Editor.nodes(editor, { at, match, mode: 'highest' }) const targets = Editor.nodes(editor, { at, match, mode, voids })
const pathRefs = Array.from(targets, ([, p]) => Editor.pathRef(editor, p)) const pathRefs = Array.from(targets, ([, p]) => Editor.pathRef(editor, p))
for (const pathRef of pathRefs) { for (const pathRef of pathRefs) {
@@ -377,37 +374,41 @@ export const NodeTransforms = {
options: { options: {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest'
hanging?: boolean hanging?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
let { match, at = editor.selection } = options const { hanging = false, voids = false } = options
const { hanging = false } = options let { at = editor.selection, mode, match } = options
if (match == null) {
if (Path.isPath(at)) {
const path = at
match = ([, p]) => Path.equals(p, path)
} else {
match = 'block'
}
}
if (!at) { if (!at) {
return return
} }
if (match == null) {
match = Path.isPath(at) ? matchPath(editor, at) : 'block'
}
if (mode == null || mode === 'all') {
mode = 'highest'
}
if (!hanging && Range.isRange(at)) { if (!hanging && Range.isRange(at)) {
at = Editor.unhangRange(editor, at) at = Editor.unhangRange(editor, at)
} }
const depths = Editor.nodes(editor, { at, match, mode: 'highest' }) const depths = Editor.nodes(editor, { at, match, mode, voids })
const pathRefs = Array.from(depths, ([, p]) => Editor.pathRef(editor, p)) const pathRefs = Array.from(depths, ([, p]) => Editor.pathRef(editor, p))
for (const pathRef of pathRefs) { for (const pathRef of pathRefs) {
const path = pathRef.unref()! const path = pathRef.unref()!
const [node] = Editor.node(editor, path)
editor.apply({ type: 'remove_node', path, node }) if (path) {
const [node] = Editor.node(editor, path)
editor.apply({ type: 'remove_node', path, node })
}
} }
}) })
}, },
@@ -425,25 +426,26 @@ export const NodeTransforms = {
mode?: 'all' | 'highest' mode?: 'all' | 'highest'
hanging?: boolean hanging?: boolean
split?: boolean split?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
let { match, at = editor.selection } = options let { match, at = editor.selection } = options
const { hanging = false, mode = 'highest', split = false } = options const {
hanging = false,
if (match == null) { mode = 'highest',
if (Path.isPath(at)) { split = false,
const path = at voids = false,
match = ([, p]) => Path.equals(p, path) } = options
} else {
match = 'block'
}
}
if (!at) { if (!at) {
return return
} }
if (match == null) {
match = Path.isPath(at) ? matchPath(editor, at) : 'block'
}
if (!hanging && Range.isRange(at)) { if (!hanging && Range.isRange(at)) {
at = Editor.unhangRange(editor, at) at = Editor.unhangRange(editor, at)
} }
@@ -451,8 +453,8 @@ export const NodeTransforms = {
if (split && Range.isRange(at)) { if (split && Range.isRange(at)) {
const rangeRef = Editor.rangeRef(editor, at, { affinity: 'inward' }) const rangeRef = Editor.rangeRef(editor, at, { affinity: 'inward' })
const [start, end] = Range.edges(at) const [start, end] = Range.edges(at)
Editor.splitNodes(editor, { at: end, match }) Editor.splitNodes(editor, { at: end, match, voids })
Editor.splitNodes(editor, { at: start, match }) Editor.splitNodes(editor, { at: start, match, voids })
at = rangeRef.unref()! at = rangeRef.unref()!
if (options.at == null) { if (options.at == null) {
@@ -460,7 +462,12 @@ export const NodeTransforms = {
} }
} }
for (const [node, path] of Editor.nodes(editor, { at, match, mode })) { for (const [node, path] of Editor.nodes(editor, {
at,
match,
mode,
voids,
})) {
const properties: Partial<Node> = {} const properties: Partial<Node> = {}
const newProperties: Partial<Node> = {} const newProperties: Partial<Node> = {}
@@ -503,10 +510,17 @@ export const NodeTransforms = {
match?: NodeMatch match?: NodeMatch
always?: boolean always?: boolean
height?: number height?: number
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
let { match, at = editor.selection, height = 0, always = false } = options let {
match,
at = editor.selection,
height = 0,
always = false,
voids = false,
} = options
if (match == null) { if (match == null) {
match = 'block' match = 'block'
@@ -534,7 +548,7 @@ export const NodeTransforms = {
const beforeRef = Editor.pointRef(editor, at, { const beforeRef = Editor.pointRef(editor, at, {
affinity: 'backward', affinity: 'backward',
}) })
const highest = Editor.match(editor, at, match) const highest = Editor.match(editor, at, match, { voids })
if (!highest) { if (!highest) {
return return
@@ -543,7 +557,7 @@ export const NodeTransforms = {
const voidMatch = Editor.match(editor, at, 'void') const voidMatch = Editor.match(editor, at, 'void')
const nudge = 0 const nudge = 0
if (voidMatch) { if (!voids && voidMatch) {
const [voidNode, voidPath] = voidMatch const [voidNode, voidPath] = voidMatch
if (Element.isElement(voidNode) && editor.isInline(voidNode)) { if (Element.isElement(voidNode) && editor.isInline(voidNode)) {
@@ -552,7 +566,7 @@ export const NodeTransforms = {
if (!after) { if (!after) {
const text = { text: '' } const text = { text: '' }
const afterPath = Path.next(voidPath) const afterPath = Path.next(voidPath)
Editor.insertNodes(editor, text, { at: afterPath }) Editor.insertNodes(editor, text, { at: afterPath, voids })
after = Editor.point(editor, afterPath)! after = Editor.point(editor, afterPath)!
} }
@@ -575,13 +589,14 @@ export const NodeTransforms = {
for (const [node, path] of Editor.levels(editor, { for (const [node, path] of Editor.levels(editor, {
at: lowestPath, at: lowestPath,
reverse: true, reverse: true,
voids,
})) { })) {
let split = false let split = false
if ( if (
path.length < highestPath.length || path.length < highestPath.length ||
path.length === 0 || path.length === 0 ||
(Element.isElement(node) && editor.isVoid(node)) (!voids && Element.isElement(node) && editor.isVoid(node))
) { ) {
break break
} }
@@ -625,27 +640,29 @@ export const NodeTransforms = {
options: { options: {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest'
split?: boolean split?: boolean
voids?: boolean
} }
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { at = editor.selection, split = false } = options const {
at = editor.selection,
mode = 'highest',
split = false,
voids = false,
} = options
let { match } = options let { match } = options
if (match == null) {
if (Path.isPath(at)) {
const path = at
match = ([, p]) => Path.equals(p, path)
} else {
match = 'block'
}
}
if (!at) { if (!at) {
return return
} }
const matches = Editor.nodes(editor, { at, match, mode: 'highest' }) if (match == null) {
match = Path.isPath(at) ? matchPath(editor, at) : 'block'
}
const matches = Editor.nodes(editor, { at, match, mode, voids })
const pathRefs = Array.from(matches, ([, p]) => Editor.pathRef(editor, p)) const pathRefs = Array.from(matches, ([, p]) => Editor.pathRef(editor, p))
for (const pathRef of pathRefs) { for (const pathRef of pathRefs) {
@@ -660,6 +677,7 @@ export const NodeTransforms = {
Editor.liftNodes(editor, { Editor.liftNodes(editor, {
at: range, at: range,
match: ([, p]) => p.length === depth, match: ([, p]) => p.length === depth,
voids,
}) })
} }
}) })
@@ -676,11 +694,13 @@ export const NodeTransforms = {
options: { options: {
at?: Location at?: Location
match?: NodeMatch match?: NodeMatch
mode?: 'all' | 'highest'
split?: boolean split?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { split = false } = options const { mode = 'highest', split = false, voids = false } = options
let { match, at = editor.selection } = options let { match, at = editor.selection } = options
if (!at) { if (!at) {
@@ -689,8 +709,7 @@ export const NodeTransforms = {
if (match == null) { if (match == null) {
if (Path.isPath(at)) { if (Path.isPath(at)) {
const path = at match = matchPath(editor, at)
match = ([, p]) => Path.equals(p, path)
} else if (editor.isInline(element)) { } else if (editor.isInline(element)) {
match = ['inline', 'text'] match = ['inline', 'text']
} else { } else {
@@ -703,8 +722,8 @@ export const NodeTransforms = {
const rangeRef = Editor.rangeRef(editor, at, { const rangeRef = Editor.rangeRef(editor, at, {
affinity: 'inward', affinity: 'inward',
}) })
Editor.splitNodes(editor, { at: end, match }) Editor.splitNodes(editor, { at: end, match, voids })
Editor.splitNodes(editor, { at: start, match }) Editor.splitNodes(editor, { at: start, match, voids })
at = rangeRef.unref()! at = rangeRef.unref()!
if (options.at == null) { if (options.at == null) {
@@ -712,16 +731,14 @@ export const NodeTransforms = {
} }
} }
const roots: NodeEntry[] = editor.isInline(element) const roots = Array.from(
? Array.from( Editor.nodes(editor, {
Editor.nodes(editor, { at,
...options, match: editor.isInline(element) ? 'block' : 'editor',
at, mode: 'highest',
match: 'block', voids,
mode: 'highest', })
}) )
)
: [[editor, []]]
for (const [, rootPath] of roots) { for (const [, rootPath] of roots) {
const a = Range.isRange(at) const a = Range.isRange(at)
@@ -733,7 +750,7 @@ export const NodeTransforms = {
} }
const matches = Array.from( const matches = Array.from(
Editor.nodes(editor, { ...options, at: a, match, mode: 'highest' }) Editor.nodes(editor, { at: a, match, mode, voids })
) )
if (matches.length > 0) { if (matches.length > 0) {
@@ -749,12 +766,13 @@ export const NodeTransforms = {
const depth = commonPath.length + 1 const depth = commonPath.length + 1
const wrapperPath = Path.next(lastPath).slice(0, depth) const wrapperPath = Path.next(lastPath).slice(0, depth)
const wrapper = { ...element, children: [] } const wrapper = { ...element, children: [] }
Editor.insertNodes(editor, wrapper, { at: wrapperPath }) Editor.insertNodes(editor, wrapper, { at: wrapperPath, voids })
Editor.moveNodes(editor, { Editor.moveNodes(editor, {
at: range, at: range,
match: ([, p]) => p.length === depth, match: ([, p]) => p.length === depth,
to: wrapperPath.concat(0), to: wrapperPath.concat(0),
voids,
}) })
} }
} }
@@ -776,3 +794,11 @@ const deleteRange = (editor: Editor, range: Range): Point | null => {
return pointRef.unref() return pointRef.unref()
} }
} }
const matchPath = (
editor: Editor,
path: Path
): ((entry: NodeEntry) => boolean) => {
const [node] = Editor.node(editor, path)
return ([n]) => n === node
}

View File

@@ -22,10 +22,16 @@ export const TextTransforms = {
unit?: 'character' | 'word' | 'line' | 'block' unit?: 'character' | 'word' | 'line' | 'block'
reverse?: boolean reverse?: boolean
hanging?: boolean hanging?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { reverse = false, unit = 'character', distance = 1 } = options const {
reverse = false,
unit = 'character',
distance = 1,
voids = false,
} = options
let { at = editor.selection, hanging = false } = options let { at = editor.selection, hanging = false } = options
if (!at) { if (!at) {
@@ -39,7 +45,7 @@ export const TextTransforms = {
if (Point.isPoint(at)) { if (Point.isPoint(at)) {
const furthestVoid = Editor.match(editor, at.path, 'void') const furthestVoid = Editor.match(editor, at.path, 'void')
if (furthestVoid) { if (!voids && furthestVoid) {
const [, voidPath] = furthestVoid const [, voidPath] = furthestVoid
at = voidPath at = voidPath
} else { } else {
@@ -53,7 +59,7 @@ export const TextTransforms = {
} }
if (Path.isPath(at)) { if (Path.isPath(at)) {
Editor.removeNodes(editor, { at }) Editor.removeNodes(editor, { at, voids })
return return
} }
@@ -62,17 +68,17 @@ export const TextTransforms = {
} }
if (!hanging) { if (!hanging) {
at = Editor.unhangRange(editor, at) at = Editor.unhangRange(editor, at, { voids })
} }
let [start, end] = Range.edges(at) let [start, end] = Range.edges(at)
const startBlock = Editor.match(editor, start.path, 'block') const startBlock = Editor.match(editor, start.path, 'block', { voids })
const endBlock = Editor.match(editor, end.path, 'block') const endBlock = Editor.match(editor, end.path, 'block', { voids })
const isAcrossBlocks = const isAcrossBlocks =
startBlock && endBlock && !Path.equals(startBlock[1], endBlock[1]) startBlock && endBlock && !Path.equals(startBlock[1], endBlock[1])
const isSingleText = Path.equals(start.path, end.path) const isSingleText = Path.equals(start.path, end.path)
const startVoid = Editor.match(editor, start.path, 'void') const startVoid = voids ? null : Editor.match(editor, start.path, 'void')
const endVoid = Editor.match(editor, end.path, 'void') const endVoid = voids ? null : Editor.match(editor, end.path, 'void')
// If the start or end points are inside an inline void, nudge them out. // If the start or end points are inside an inline void, nudge them out.
if (startVoid) { if (startVoid) {
@@ -99,9 +105,10 @@ export const TextTransforms = {
// the start and end nodes. // the start and end nodes.
const matches = Editor.nodes(editor, { const matches = Editor.nodes(editor, {
at, at,
voids,
mode: 'highest', mode: 'highest',
match: ([n, p]) => match: ([n, p]) =>
(Element.isElement(n) && editor.isVoid(n)) || (!voids && Element.isElement(n) && editor.isVoid(n)) ||
(!Path.isCommon(p, start.path) && !Path.isCommon(p, end.path)), (!Path.isCommon(p, start.path) && !Path.isCommon(p, end.path)),
}) })
@@ -120,7 +127,7 @@ export const TextTransforms = {
for (const pathRef of pathRefs) { for (const pathRef of pathRefs) {
const path = pathRef.unref()! const path = pathRef.unref()!
Editor.removeNodes(editor, { at: path }) Editor.removeNodes(editor, { at: path, voids })
} }
if (!endVoid) { if (!endVoid) {
@@ -138,7 +145,11 @@ export const TextTransforms = {
endRef.current && endRef.current &&
startRef.current startRef.current
) { ) {
Editor.mergeNodes(editor, { at: endRef.current, hanging: true }) Editor.mergeNodes(editor, {
at: endRef.current,
hanging: true,
voids,
})
} }
const point = endRef.unref() || startRef.unref() const point = endRef.unref() || startRef.unref()
@@ -159,11 +170,12 @@ export const TextTransforms = {
options: { options: {
at?: Location at?: Location
hanging?: boolean hanging?: boolean
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { hanging = false, voids = false } = options
let { at = editor.selection } = options let { at = editor.selection } = options
const { hanging = false } = options
if (!fragment.length) { if (!fragment.length) {
return return
@@ -180,6 +192,11 @@ export const TextTransforms = {
at = at.anchor at = at.anchor
} else { } else {
const [, end] = Range.edges(at) const [, end] = Range.edges(at)
if (!voids && Editor.match(editor, end, 'void')) {
return
}
const pointRef = Editor.pointRef(editor, end) const pointRef = Editor.pointRef(editor, end)
Editor.delete(editor, { at }) Editor.delete(editor, { at })
at = pointRef.unref()! at = pointRef.unref()!
@@ -188,13 +205,13 @@ export const TextTransforms = {
at = Editor.start(editor, at) at = Editor.start(editor, at)
} }
if (Editor.match(editor, at.path, 'void')) { if (!voids && Editor.match(editor, at.path, 'void')) {
return return
} }
// If the insert point is at the edge of an inline node, move it outside // If the insert point is at the edge of an inline node, move it outside
// instead since it will need to be split otherwise. // instead since it will need to be split otherwise.
const inlineElementMatch = Editor.match(editor, at, 'inline') const inlineElementMatch = Editor.match(editor, at, 'inline', { voids })
if (inlineElementMatch) { if (inlineElementMatch) {
const [, inlinePath] = inlineElementMatch const [, inlinePath] = inlineElementMatch
@@ -208,7 +225,7 @@ export const TextTransforms = {
} }
} }
const blockMatch = Editor.match(editor, at, 'block')! const blockMatch = Editor.match(editor, at, 'block', { voids })!
const [, blockPath] = blockMatch const [, blockPath] = blockMatch
const isBlockStart = Editor.isStart(editor, at, blockPath) const isBlockStart = Editor.isStart(editor, at, blockPath)
const isBlockEnd = Editor.isEnd(editor, at, blockPath) const isBlockEnd = Editor.isEnd(editor, at, blockPath)
@@ -217,9 +234,7 @@ export const TextTransforms = {
const [, firstPath] = Node.first({ children: fragment }, []) const [, firstPath] = Node.first({ children: fragment }, [])
const [, lastPath] = Node.last({ children: fragment }, []) const [, lastPath] = Node.last({ children: fragment }, [])
// TODO: convert into a proper `Nodes.matches` iterable
const matches: NodeEntry[] = [] const matches: NodeEntry[] = []
const matcher = ([n, p]: NodeEntry) => { const matcher = ([n, p]: NodeEntry) => {
if ( if (
mergeStart && mergeStart &&
@@ -271,7 +286,9 @@ export const TextTransforms = {
} }
} }
const inlineMatch = Editor.match(editor, at, ['inline', 'text'])! const inlineMatch = Editor.match(editor, at, ['inline', 'text'], {
voids,
})!
const [, inlinePath] = inlineMatch const [, inlinePath] = inlineMatch
const isInlineStart = Editor.isStart(editor, at, inlinePath) const isInlineStart = Editor.isStart(editor, at, inlinePath)
const isInlineEnd = Editor.isEnd(editor, at, inlinePath) const isInlineEnd = Editor.isEnd(editor, at, inlinePath)
@@ -289,6 +306,7 @@ export const TextTransforms = {
Editor.splitNodes(editor, { Editor.splitNodes(editor, {
at, at,
match: hasBlocks ? 'block' : ['inline', 'text'], match: hasBlocks ? 'block' : ['inline', 'text'],
voids,
}) })
const startRef = Editor.pathRef( const startRef = Editor.pathRef(
@@ -301,16 +319,19 @@ export const TextTransforms = {
Editor.insertNodes(editor, starts, { Editor.insertNodes(editor, starts, {
at: startRef.current!, at: startRef.current!,
match: ['inline', 'text'], match: ['inline', 'text'],
voids,
}) })
Editor.insertNodes(editor, middles, { Editor.insertNodes(editor, middles, {
at: middleRef.current!, at: middleRef.current!,
match: 'block', match: 'block',
voids,
}) })
Editor.insertNodes(editor, ends, { Editor.insertNodes(editor, ends, {
at: endRef.current!, at: endRef.current!,
match: ['inline', 'text'], match: ['inline', 'text'],
voids,
}) })
if (!options.at) { if (!options.at) {
@@ -343,9 +364,11 @@ export const TextTransforms = {
text: string, text: string,
options: { options: {
at?: Location at?: Location
voids?: boolean
} = {} } = {}
) { ) {
Editor.withoutNormalizing(editor, () => { Editor.withoutNormalizing(editor, () => {
const { voids = false } = options
let { at = editor.selection } = options let { at = editor.selection } = options
if (!at) { if (!at) {
@@ -360,13 +383,19 @@ export const TextTransforms = {
if (Range.isCollapsed(at)) { if (Range.isCollapsed(at)) {
at = at.anchor at = at.anchor
} else { } else {
const pointRef = Editor.pointRef(editor, Range.end(at)) const end = Range.end(at)
Editor.delete(editor, { at })
if (!voids && Editor.match(editor, end, 'void')) {
return
}
const pointRef = Editor.pointRef(editor, end)
Editor.delete(editor, { at, voids })
at = pointRef.unref()! at = pointRef.unref()!
} }
} }
if (Editor.match(editor, at.path, 'void')) { if (!voids && Editor.match(editor, at.path, 'void')) {
return return
} }

View File

@@ -0,0 +1,18 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>one</block>
</editor>
)
export const run = editor => {
return Array.from(
Editor.nodes(editor, { at: [], match: 'text', voids: true })
)
}
export const output = [[<text>one</text>, [0, 0]]]

View File

@@ -0,0 +1,24 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>
one<inline void>two</inline>three
</block>
</editor>
)
export const run = editor => {
return Array.from(
Editor.nodes(editor, { at: [], match: 'text', voids: true })
)
}
export const output = [
[<text>one</text>, [0, 0]],
[<text>two</text>, [0, 1, 0]],
[<text>three</text>, [0, 2]],
]

View File

@@ -0,0 +1,35 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>
<text>
on
<anchor />e
</text>
</block>
<block void>
<text>
t<focus />
wo
</text>
</block>
</editor>
)
export const run = editor => {
Editor.delete(editor, { voids: true })
}
export const output = (
<editor>
<block void>
on
<cursor />
wo
</block>
</editor>
)

View File

@@ -0,0 +1,24 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>
<text>one</text>
</block>
</editor>
)
export const run = editor => {
Editor.delete(editor, { at: [0, 0], voids: true })
}
export const output = (
<editor>
<block void>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.insertFragment(editor, <fragment>fragment</fragment>)
}
export const input = (
<editor>
<block void>
wo
<cursor />
rd
</block>
</editor>
)
export const output = (
<editor>
<block void>
wo
<cursor />
rd
</block>
</editor>
)

View File

@@ -0,0 +1,37 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.insertFragment(editor, <fragment>fragment</fragment>)
}
export const input = (
<editor>
<block>
<text />
<inline void>
wo
<cursor />
rd
</inline>
<text />
</block>
</editor>
)
// TODO: argument to made that fragment should go into the inline
export const output = (
<editor>
<block>
<text />
<inline void>
wo
<cursor />
rd
</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.insertFragment(editor, <fragment>fragment</fragment>, { voids: true })
}
export const input = (
<editor>
<block void>
wo
<cursor />
rd
</block>
</editor>
)
export const output = (
<editor>
<block void>
wofragment
<cursor />
rd
</block>
</editor>
)

View File

@@ -0,0 +1,36 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.insertFragment(editor, <fragment>fragment</fragment>, { voids: true })
}
export const input = (
<editor>
<block>
<text />
<inline void>
wo
<cursor />
rd
</inline>
<text />
</block>
</editor>
)
// TODO: argument to made that fragment should go into the inline
export const output = (
<editor>
<block>
<text />
<inline void>wo</inline>
fragment
<cursor />
<inline void>rd</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,29 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>
one
<cursor />
</block>
</editor>
)
export const run = editor => {
Editor.insertNodes(editor, <text>two</text>, {
at: [0, 1],
voids: true,
})
}
export const output = (
<editor>
<block void>
onetwo
<cursor />
</block>
</editor>
)

View File

@@ -0,0 +1,37 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>
one
<inline void>
two
<cursor />
</inline>
three
</block>
</editor>
)
export const run = editor => {
Editor.insertNodes(editor, <text>four</text>, {
at: [0, 1, 1],
voids: true,
})
}
export const output = (
<editor>
<block>
one
<inline void>
twofour
<cursor />
</inline>
three
</block>
</editor>
)

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const run = editor => {
Editor.insertText(editor, 'x', { at: [0] })
}
export const output = (
<editor>
<block void>word</block>
</editor>
)

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const run = editor => {
Editor.insertText(editor, 'x', { at: [0, 0] })
}
export const output = (
<editor>
<block void>word</block>
</editor>
)

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const run = editor => {
Editor.insertText(editor, 'x', { at: [0], voids: true })
}
export const output = (
<editor>
<block void>x</block>
</editor>
)

View File

@@ -0,0 +1,20 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const run = editor => {
Editor.insertText(editor, 'x', { at: [0, 0], voids: true })
}
export const output = (
<editor>
<block void>x</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.liftNodes(editor, { at: [0, 0], voids: true })
}
export const input = (
<editor>
<block void>
<block>word</block>
</block>
</editor>
)
export const output = (
<editor>
<block>word</block>
</editor>
)

View File

@@ -0,0 +1,23 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>
<text>one</text>
<text>two</text>
</block>
</editor>
)
export const run = editor => {
Editor.mergeNodes(editor, { at: [0, 1], voids: true })
}
export const output = (
<editor>
<block void>onetwo</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>one</block>
<block void>two</block>
</editor>
)
export const run = editor => {
Editor.moveNodes(editor, {
at: [0, 0],
to: [1, 0],
voids: true,
})
}
export const output = (
<editor>
<block void>
<text />
</block>
<block void>onetwo</block>
</editor>
)

View File

@@ -0,0 +1,38 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>
<text />
<inline>
<cursor />
one
</inline>
<text />
<inline>two</inline>
<text />
</block>
</editor>
)
export const run = editor => {
Editor.moveNodes(editor, { at: [0, 1], to: [0, 3] })
}
export const output = (
<editor>
<block>
<text />
<inline>two</inline>
<text />
<inline>
<cursor />
one
</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>one</block>
</editor>
)
export const run = editor => {
Editor.removeNodes(editor, { at: [0, 0], voids: true })
}
export const output = (
<editor>
<block void>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,30 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block>
<text />
<inline void>one</inline>
<text />
</block>
</editor>
)
export const run = editor => {
Editor.removeNodes(editor, { at: [0, 1, 0], voids: true })
}
export const output = (
<editor>
<block>
<text />
<inline void>
<text />
</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const run = editor => {
Editor.setNodes(editor, { key: true }, { at: [0, 0], voids: true })
}
export const output = (
<editor>
<block void>
<text key>word</text>
</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.setNodes(editor, { key: 'a' }, { at: [0, 1] })
}
export const input = (
<editor>
<block>
<text />
<inline>word</inline>
<text />
</block>
</editor>
)
export const output = (
<editor>
<block>
<text />
<inline key="a">word</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.splitNodes(editor, { at: [0, 1], voids: true })
}
export const input = (
<editor>
<block void>
<block>one</block>
<block>two</block>
</block>
</editor>
)
export const output = (
<editor>
<block void>
<block>one</block>
</block>
<block void>
<block>two</block>
</block>
</editor>
)

View File

@@ -0,0 +1,37 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.splitNodes(editor, { at: [0, 1, 1], voids: true })
}
export const input = (
<editor>
<block>
<text />
<inline void>
<text>one</text>
<text>two</text>
</inline>
<text />
</block>
</editor>
)
export const output = (
<editor>
<block>
<text />
<inline void>
<text>one</text>
</inline>
<text />
<inline void>
<text>two</text>
</inline>
<text />
</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.unwrapNodes(editor, { at: [0], voids: true })
}
export const input = (
<editor>
<block void>
<block>word</block>
</block>
</editor>
)
export const output = (
<editor>
<block>word</block>
</editor>
)

View File

@@ -0,0 +1,28 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.wrapNodes(editor, <block a />, { at: [0] })
}
export const input = (
<editor>
<block>
<cursor />
word
</block>
</editor>
)
export const output = (
<editor>
<block a>
<block>
<cursor />
word
</block>
</block>
</editor>
)

View File

@@ -0,0 +1,22 @@
/** @jsx jsx */
import { Editor } from 'slate'
import { jsx } from '../../..'
export const run = editor => {
Editor.wrapNodes(editor, <block a />, { at: [0, 0], voids: true })
}
export const input = (
<editor>
<block void>word</block>
</editor>
)
export const output = (
<editor>
<block void>
<block a>word</block>
</block>
</editor>
)