mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-01-17 21:49:20 +01:00
Remove commands (#3351)
* remove commands in favor of editor-level functions * update examples * fix lint
This commit is contained in:
parent
c2d7905e19
commit
0bbe121d76
@ -103,13 +103,13 @@ Another special group of helper functions exposed on the `Editor` interface are
|
||||
|
||||
```js
|
||||
// Insert an element node at a specific path.
|
||||
Editor.insertNodes(editor, [element], { at: path })
|
||||
Transforms.insertNodes(editor, [element], { at: path })
|
||||
|
||||
// Split the nodes in half at a specific point.
|
||||
Editor.splitNodes(editor, { at: point })
|
||||
Transforms.splitNodes(editor, { at: point })
|
||||
|
||||
// Add a quote format to all the block nodes in the selection.
|
||||
Editor.setNodes(editor, { type: 'quote' })
|
||||
Transforms.setNodes(editor, { type: 'quote' })
|
||||
```
|
||||
|
||||
The editor-specific helpers are the ones you'll use most often when working with Slate editors, so it pays to become very familiar with them.
|
||||
|
@ -14,7 +14,7 @@ const withImages = editor => {
|
||||
if (command.type === 'insert_image') {
|
||||
const { url } = command
|
||||
const element = { type: 'image', url, children: [{ text: '' }] }
|
||||
Editor.insertNodes(editor, element)
|
||||
Transforms.insertNodes(editor, element)
|
||||
} else {
|
||||
exec(command)
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ const withParagraphs = editor => {
|
||||
if (Element.isElement(node) && node.type === 'paragraph') {
|
||||
for (const [child, childPath] of Node.children(editor, path)) {
|
||||
if (Element.isElement(child) && !editor.isInline(child)) {
|
||||
Editor.unwrapNodes(editor, { at: childPath })
|
||||
Transforms.unwrapNodes(editor, { at: childPath })
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -67,7 +67,7 @@ If you check the example above again, you'll notice the `return` statement:
|
||||
|
||||
```js
|
||||
if (Element.isElement(child) && !editor.isInline(child)) {
|
||||
Editor.unwrapNodes(editor, { at: childPath })
|
||||
Transforms.unwrapNodes(editor, { at: childPath })
|
||||
return
|
||||
}
|
||||
```
|
||||
@ -135,7 +135,7 @@ const withLinks = editor => {
|
||||
node.type === 'link' &&
|
||||
typeof node.url !== 'string'
|
||||
) {
|
||||
Editor.setNodes(editor, { url: null }, { at: path })
|
||||
Transforms.setNodes(editor, { url: null }, { at: path })
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ const App = () => {
|
||||
// Prevent the "`" from being inserted by default.
|
||||
event.preventDefault()
|
||||
// Otherwise, set the currently selected blocks type to "code".
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -208,7 +208,7 @@ const App = () => {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
// Toggle the block type depending on whether there's already a match.
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: match ? 'paragraph' : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
|
@ -36,7 +36,7 @@ const App = () => {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: match ? 'paragraph' : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -86,7 +86,7 @@ const App = () => {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: match ? 'paragraph' : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -97,7 +97,7 @@ const App = () => {
|
||||
// When "B" is pressed, bold the text in the selection.
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: true },
|
||||
// Apply it to text nodes, and split the text node up if the
|
||||
@ -177,7 +177,7 @@ const App = () => {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: match ? null : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -187,7 +187,7 @@ const App = () => {
|
||||
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: true },
|
||||
{ match: n => Text.isText(n), split: true }
|
||||
|
@ -49,7 +49,7 @@ const App = () => {
|
||||
const [match] = Editor.nodes(editor, {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: match ? null : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -59,7 +59,7 @@ const App = () => {
|
||||
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: true },
|
||||
{ match: n => Text.isText(n), split: true }
|
||||
@ -124,7 +124,7 @@ const App = () => {
|
||||
match: n => n.type === 'code',
|
||||
})
|
||||
const isCodeActive = !!match
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: isCodeActive ? null : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
@ -134,7 +134,7 @@ const App = () => {
|
||||
|
||||
case 'b': {
|
||||
event.preventDefault()
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: true },
|
||||
{ match: n => Text.isText(n), split: true }
|
||||
@ -161,7 +161,7 @@ const withCustom = editor => {
|
||||
// Define a command to toggle the bold formatting.
|
||||
if (command.type === 'toggle_bold_mark') {
|
||||
const isActive = CustomEditor.isBoldMarkActive(editor)
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ bold: isActive ? null : true },
|
||||
{ match: n => Text.isText(n), split: true }
|
||||
@ -171,7 +171,7 @@ const withCustom = editor => {
|
||||
// Define a command to toggle the code block formatting.
|
||||
else if (command.type === 'toggle_code_block') {
|
||||
const isActive = CustomEditor.isCodeBlockActive(editor)
|
||||
Editor.setNodes(
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ type: isActive ? null : 'code' },
|
||||
{ match: n => Editor.isBlock(editor, n) }
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { Command } from 'slate'
|
||||
|
||||
export interface RedoCommand {
|
||||
type: 'redo'
|
||||
}
|
||||
|
||||
export interface UndoCommand {
|
||||
type: 'undo'
|
||||
}
|
||||
|
||||
export type HistoryCommand = RedoCommand | UndoCommand
|
||||
|
||||
export const HistoryCommand = {
|
||||
/**
|
||||
* Check if a value is a `HistoryCommand` object.
|
||||
*/
|
||||
|
||||
isHistoryCommand(value: any): value is HistoryCommand {
|
||||
if (Command.isCommand(value)) {
|
||||
switch (value.type) {
|
||||
case 'redo':
|
||||
case 'undo':
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
@ -15,6 +15,8 @@ export const MERGING = new WeakMap<Editor, boolean | undefined>()
|
||||
|
||||
export interface HistoryEditor extends Editor {
|
||||
history: History
|
||||
undo: () => void
|
||||
redo: () => void
|
||||
}
|
||||
|
||||
export const HistoryEditor = {
|
||||
@ -30,7 +32,7 @@ export const HistoryEditor = {
|
||||
* Get the merge flag's current value.
|
||||
*/
|
||||
|
||||
isMerging(editor: Editor): boolean | undefined {
|
||||
isMerging(editor: HistoryEditor): boolean | undefined {
|
||||
return MERGING.get(editor)
|
||||
},
|
||||
|
||||
@ -38,16 +40,32 @@ export const HistoryEditor = {
|
||||
* Get the saving flag's current value.
|
||||
*/
|
||||
|
||||
isSaving(editor: Editor): boolean | undefined {
|
||||
isSaving(editor: HistoryEditor): boolean | undefined {
|
||||
return SAVING.get(editor)
|
||||
},
|
||||
|
||||
/**
|
||||
* Redo to the previous saved state.
|
||||
*/
|
||||
|
||||
redo(editor: HistoryEditor): void {
|
||||
editor.redo()
|
||||
},
|
||||
|
||||
/**
|
||||
* Undo to the previous saved state.
|
||||
*/
|
||||
|
||||
undo(editor: HistoryEditor): void {
|
||||
editor.undo()
|
||||
},
|
||||
|
||||
/**
|
||||
* Apply a series of changes inside a synchronous `fn`, without merging any of
|
||||
* the new operations into previous save point in the history.
|
||||
*/
|
||||
|
||||
withoutMerging(editor: Editor, fn: () => void): void {
|
||||
withoutMerging(editor: HistoryEditor, fn: () => void): void {
|
||||
const prev = HistoryEditor.isMerging(editor)
|
||||
MERGING.set(editor, false)
|
||||
fn()
|
||||
@ -59,7 +77,7 @@ export const HistoryEditor = {
|
||||
* their operations into the history.
|
||||
*/
|
||||
|
||||
withoutSaving(editor: Editor, fn: () => void): void {
|
||||
withoutSaving(editor: HistoryEditor, fn: () => void): void {
|
||||
const prev = HistoryEditor.isSaving(editor)
|
||||
SAVING.set(editor, false)
|
||||
fn()
|
||||
|
@ -1,4 +1,3 @@
|
||||
export * from './history'
|
||||
export * from './history-command'
|
||||
export * from './history-editor'
|
||||
export * from './with-history'
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Editor, Command, Operation, Path } from 'slate'
|
||||
import { Editor, Operation, Path } from 'slate'
|
||||
|
||||
import { HistoryCommand } from './history-command'
|
||||
import { HistoryEditor } from './history-editor'
|
||||
|
||||
/**
|
||||
@ -9,114 +8,109 @@ import { HistoryEditor } from './history-editor'
|
||||
*/
|
||||
|
||||
export const withHistory = (editor: Editor): HistoryEditor => {
|
||||
const { apply, exec } = editor
|
||||
editor.history = { undos: [], redos: [] }
|
||||
const e = editor as HistoryEditor
|
||||
const { apply } = e
|
||||
e.history = { undos: [], redos: [] }
|
||||
|
||||
editor.exec = (command: Command) => {
|
||||
if (
|
||||
HistoryEditor.isHistoryEditor(editor) &&
|
||||
HistoryCommand.isHistoryCommand(command)
|
||||
) {
|
||||
const { history } = editor
|
||||
const { undos, redos } = history
|
||||
e.redo = () => {
|
||||
const { history } = e
|
||||
const { redos } = history
|
||||
|
||||
if (command.type === 'redo' && redos.length > 0) {
|
||||
const batch = redos[redos.length - 1]
|
||||
if (redos.length > 0) {
|
||||
const batch = redos[redos.length - 1]
|
||||
|
||||
HistoryEditor.withoutSaving(editor, () => {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
for (const op of batch) {
|
||||
editor.apply(op)
|
||||
}
|
||||
})
|
||||
HistoryEditor.withoutSaving(e, () => {
|
||||
Editor.withoutNormalizing(e, () => {
|
||||
for (const op of batch) {
|
||||
e.apply(op)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
history.redos.pop()
|
||||
history.undos.push(batch)
|
||||
return
|
||||
}
|
||||
|
||||
if (command.type === 'undo' && undos.length > 0) {
|
||||
const batch = undos[undos.length - 1]
|
||||
|
||||
HistoryEditor.withoutSaving(editor, () => {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const inverseOps = batch.map(Operation.inverse).reverse()
|
||||
|
||||
for (const op of inverseOps) {
|
||||
// If the final operation is deselecting the editor, skip it. This is
|
||||
if (
|
||||
op === inverseOps[inverseOps.length - 1] &&
|
||||
op.type === 'set_selection' &&
|
||||
op.newProperties == null
|
||||
) {
|
||||
continue
|
||||
} else {
|
||||
editor.apply(op)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
history.redos.push(batch)
|
||||
history.undos.pop()
|
||||
return
|
||||
}
|
||||
history.redos.pop()
|
||||
history.undos.push(batch)
|
||||
}
|
||||
|
||||
exec(command)
|
||||
}
|
||||
|
||||
editor.apply = (op: Operation) => {
|
||||
if (HistoryEditor.isHistoryEditor(editor)) {
|
||||
const { operations, history } = editor
|
||||
const { undos } = history
|
||||
const lastBatch = undos[undos.length - 1]
|
||||
const lastOp = lastBatch && lastBatch[lastBatch.length - 1]
|
||||
const overwrite = shouldOverwrite(op, lastOp)
|
||||
let save = HistoryEditor.isSaving(editor)
|
||||
let merge = HistoryEditor.isMerging(editor)
|
||||
e.undo = () => {
|
||||
const { history } = e
|
||||
const { undos } = history
|
||||
|
||||
if (save == null) {
|
||||
save = shouldSave(op, lastOp)
|
||||
if (undos.length > 0) {
|
||||
const batch = undos[undos.length - 1]
|
||||
|
||||
HistoryEditor.withoutSaving(e, () => {
|
||||
Editor.withoutNormalizing(e, () => {
|
||||
const inverseOps = batch.map(Operation.inverse).reverse()
|
||||
|
||||
for (const op of inverseOps) {
|
||||
// If the final operation is deselecting the editor, skip it. This is
|
||||
if (
|
||||
op === inverseOps[inverseOps.length - 1] &&
|
||||
op.type === 'set_selection' &&
|
||||
op.newProperties == null
|
||||
) {
|
||||
continue
|
||||
} else {
|
||||
e.apply(op)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
history.redos.push(batch)
|
||||
history.undos.pop()
|
||||
}
|
||||
}
|
||||
|
||||
e.apply = (op: Operation) => {
|
||||
const { operations, history } = e
|
||||
const { undos } = history
|
||||
const lastBatch = undos[undos.length - 1]
|
||||
const lastOp = lastBatch && lastBatch[lastBatch.length - 1]
|
||||
const overwrite = shouldOverwrite(op, lastOp)
|
||||
let save = HistoryEditor.isSaving(e)
|
||||
let merge = HistoryEditor.isMerging(e)
|
||||
|
||||
if (save == null) {
|
||||
save = shouldSave(op, lastOp)
|
||||
}
|
||||
|
||||
if (save) {
|
||||
if (merge == null) {
|
||||
if (lastBatch == null) {
|
||||
merge = false
|
||||
} else if (operations.length !== 0) {
|
||||
merge = true
|
||||
} else {
|
||||
merge = shouldMerge(op, lastOp) || overwrite
|
||||
}
|
||||
}
|
||||
|
||||
if (save) {
|
||||
if (merge == null) {
|
||||
if (lastBatch == null) {
|
||||
merge = false
|
||||
} else if (operations.length !== 0) {
|
||||
merge = true
|
||||
} else {
|
||||
merge = shouldMerge(op, lastOp) || overwrite
|
||||
}
|
||||
if (lastBatch && merge) {
|
||||
if (overwrite) {
|
||||
lastBatch.pop()
|
||||
}
|
||||
|
||||
if (lastBatch && merge) {
|
||||
if (overwrite) {
|
||||
lastBatch.pop()
|
||||
}
|
||||
lastBatch.push(op)
|
||||
} else {
|
||||
const batch = [op]
|
||||
undos.push(batch)
|
||||
}
|
||||
|
||||
lastBatch.push(op)
|
||||
} else {
|
||||
const batch = [op]
|
||||
undos.push(batch)
|
||||
}
|
||||
while (undos.length > 100) {
|
||||
undos.shift()
|
||||
}
|
||||
|
||||
while (undos.length > 100) {
|
||||
undos.shift()
|
||||
}
|
||||
|
||||
if (shouldClear(op)) {
|
||||
history.redos = []
|
||||
}
|
||||
if (shouldClear(op)) {
|
||||
history.redos = []
|
||||
}
|
||||
}
|
||||
|
||||
apply(op)
|
||||
}
|
||||
|
||||
return editor as HistoryEditor
|
||||
return e
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8,7 +8,7 @@ describe('slate-history', () => {
|
||||
const { input, run, output } = module
|
||||
const editor = withTest(withHistory(input))
|
||||
run(editor)
|
||||
editor.exec({ type: 'undo' })
|
||||
editor.undo()
|
||||
assert.deepEqual(editor.children, output.children)
|
||||
assert.deepEqual(editor.selection, output.selection)
|
||||
})
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'delete_backward' })
|
||||
editor.deleteBackward()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'delete_backward' })
|
||||
editor.deleteBackward()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -1,10 +1,10 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { Transforms } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
Editor.delete(editor)
|
||||
Transforms.delete(editor)
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -1,10 +1,10 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { Transforms } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
Editor.delete(editor)
|
||||
Transforms.delete(editor)
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -1,10 +1,10 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { Transforms } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
Editor.delete(editor)
|
||||
Transforms.delete(editor)
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -4,7 +4,7 @@ import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_break' })
|
||||
editor.insertBreak()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -22,7 +22,7 @@ const fragment = (
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_fragment', fragment })
|
||||
editor.insertFragment(fragment)
|
||||
}
|
||||
|
||||
export const input = (
|
||||
@ -38,3 +38,5 @@ export const input = (
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 'text' })
|
||||
editor.insertText('text')
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -3,9 +3,9 @@
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 't' })
|
||||
editor.exec({ type: 'insert_text', text: 'w' })
|
||||
editor.exec({ type: 'insert_text', text: 'o' })
|
||||
editor.insertText('t')
|
||||
editor.insertText('w')
|
||||
editor.insertText('o')
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -1,14 +1,14 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { Transforms } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'insert_text', text: 't' })
|
||||
Editor.move(editor, { reverse: true })
|
||||
editor.exec({ type: 'insert_text', text: 'w' })
|
||||
Editor.move(editor, { reverse: true })
|
||||
editor.exec({ type: 'insert_text', text: 'o' })
|
||||
editor.insertText('t')
|
||||
Transforms.move(editor, { reverse: true })
|
||||
editor.insertText('w')
|
||||
Transforms.move(editor, { reverse: true })
|
||||
editor.insertText('o')
|
||||
}
|
||||
|
||||
export const input = (
|
||||
|
@ -1,5 +1,13 @@
|
||||
import React, { useEffect, useRef, useMemo, useCallback } from 'react'
|
||||
import { Editor, Element, NodeEntry, Node, Range, Text } from 'slate'
|
||||
import {
|
||||
Editor,
|
||||
Element,
|
||||
NodeEntry,
|
||||
Node,
|
||||
Range,
|
||||
Text,
|
||||
Transforms,
|
||||
} from 'slate'
|
||||
import debounce from 'debounce'
|
||||
import scrollIntoView from 'scroll-into-view-if-needed'
|
||||
|
||||
@ -252,7 +260,7 @@ export const Editable = (props: EditableProps) => {
|
||||
const range = ReactEditor.toSlateRange(editor, targetRange)
|
||||
|
||||
if (!selection || !Range.equals(selection, range)) {
|
||||
Editor.select(editor, range)
|
||||
Transforms.select(editor, range)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -264,7 +272,7 @@ export const Editable = (props: EditableProps) => {
|
||||
Range.isExpanded(selection) &&
|
||||
type.startsWith('delete')
|
||||
) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
return
|
||||
}
|
||||
|
||||
@ -272,60 +280,60 @@ export const Editable = (props: EditableProps) => {
|
||||
case 'deleteByComposition':
|
||||
case 'deleteByCut':
|
||||
case 'deleteByDrag': {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteContent':
|
||||
case 'deleteContentForward': {
|
||||
editor.exec({ type: 'delete_forward', unit: 'character' })
|
||||
Editor.deleteForward(editor)
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteContentBackward': {
|
||||
editor.exec({ type: 'delete_backward', unit: 'character' })
|
||||
Editor.deleteBackward(editor)
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteEntireSoftLine': {
|
||||
editor.exec({ type: 'delete_backward', unit: 'line' })
|
||||
editor.exec({ type: 'delete_forward', unit: 'line' })
|
||||
Editor.deleteBackward(editor, { unit: 'line' })
|
||||
Editor.deleteForward(editor, { unit: 'line' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteHardLineBackward': {
|
||||
editor.exec({ type: 'delete_backward', unit: 'block' })
|
||||
Editor.deleteBackward(editor, { unit: 'block' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteSoftLineBackward': {
|
||||
editor.exec({ type: 'delete_backward', unit: 'line' })
|
||||
Editor.deleteBackward(editor, { unit: 'line' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteHardLineForward': {
|
||||
editor.exec({ type: 'delete_forward', unit: 'block' })
|
||||
Editor.deleteForward(editor, { unit: 'block' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteSoftLineForward': {
|
||||
editor.exec({ type: 'delete_forward', unit: 'line' })
|
||||
Editor.deleteForward(editor, { unit: 'line' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteWordBackward': {
|
||||
editor.exec({ type: 'delete_backward', unit: 'word' })
|
||||
Editor.deleteBackward(editor, { unit: 'word' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'deleteWordForward': {
|
||||
editor.exec({ type: 'delete_forward', unit: 'word' })
|
||||
Editor.deleteForward(editor, { unit: 'word' })
|
||||
break
|
||||
}
|
||||
|
||||
case 'insertLineBreak':
|
||||
case 'insertParagraph': {
|
||||
editor.exec({ type: 'insert_break' })
|
||||
Editor.insertBreak(editor)
|
||||
break
|
||||
}
|
||||
|
||||
@ -336,9 +344,9 @@ export const Editable = (props: EditableProps) => {
|
||||
case 'insertReplacementText':
|
||||
case 'insertText': {
|
||||
if (data instanceof DataTransfer) {
|
||||
editor.exec({ type: 'insert_data', data })
|
||||
ReactEditor.insertData(editor, data)
|
||||
} else if (typeof data === 'string') {
|
||||
editor.exec({ type: 'insert_text', text: data })
|
||||
Editor.insertText(editor, data)
|
||||
}
|
||||
|
||||
break
|
||||
@ -378,9 +386,9 @@ export const Editable = (props: EditableProps) => {
|
||||
hasEditableTarget(editor, domRange.endContainer)
|
||||
) {
|
||||
const range = ReactEditor.toSlateRange(editor, domRange)
|
||||
Editor.select(editor, range)
|
||||
Transforms.select(editor, range)
|
||||
} else {
|
||||
Editor.deselect(editor)
|
||||
Transforms.deselect(editor)
|
||||
}
|
||||
}
|
||||
}, 100),
|
||||
@ -441,7 +449,7 @@ export const Editable = (props: EditableProps) => {
|
||||
if (IS_FIREFOX && !readOnly) {
|
||||
event.preventDefault()
|
||||
const text = (event as any).data as string
|
||||
editor.exec({ type: 'insert_text', text })
|
||||
Editor.insertText(editor, text)
|
||||
}
|
||||
},
|
||||
[readOnly]
|
||||
@ -517,7 +525,7 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
if (Editor.void(editor, { at: start })) {
|
||||
const range = Editor.range(editor, start)
|
||||
Editor.select(editor, range)
|
||||
Transforms.select(editor, range)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -536,7 +544,7 @@ export const Editable = (props: EditableProps) => {
|
||||
// type that we need. So instead, insert whenever a composition
|
||||
// ends since it will already have been committed to the DOM.
|
||||
if (!IS_SAFARI && !IS_FIREFOX && event.data) {
|
||||
editor.exec({ type: 'insert_text', text: event.data })
|
||||
Editor.insertText(editor, event.data)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -577,7 +585,7 @@ export const Editable = (props: EditableProps) => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -615,7 +623,7 @@ export const Editable = (props: EditableProps) => {
|
||||
// so that it shows up in the selection's fragment.
|
||||
if (voidMatch) {
|
||||
const range = Editor.range(editor, path)
|
||||
Editor.select(editor, range)
|
||||
Transforms.select(editor, range)
|
||||
}
|
||||
|
||||
setFragmentData(event.dataTransfer, editor)
|
||||
@ -641,8 +649,8 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
const range = ReactEditor.findEventRange(editor, event)
|
||||
const data = event.dataTransfer
|
||||
Editor.select(editor, range)
|
||||
editor.exec({ type: 'insert_data', data })
|
||||
Transforms.select(editor, range)
|
||||
ReactEditor.insertData(editor, data)
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -688,13 +696,21 @@ export const Editable = (props: EditableProps) => {
|
||||
// hotkeys ourselves. (2019/11/06)
|
||||
if (Hotkeys.isRedo(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
editor.exec({ type: 'redo' })
|
||||
|
||||
if (editor.undo) {
|
||||
editor.undo()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (Hotkeys.isUndo(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
editor.exec({ type: 'undo' })
|
||||
|
||||
if (editor.redo) {
|
||||
editor.redo()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@ -704,19 +720,19 @@ export const Editable = (props: EditableProps) => {
|
||||
// (2017/10/17)
|
||||
if (Hotkeys.isMoveLineBackward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, { unit: 'line', reverse: true })
|
||||
Transforms.move(editor, { unit: 'line', reverse: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (Hotkeys.isMoveLineForward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, { unit: 'line' })
|
||||
Transforms.move(editor, { unit: 'line' })
|
||||
return
|
||||
}
|
||||
|
||||
if (Hotkeys.isExtendLineBackward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, {
|
||||
Transforms.move(editor, {
|
||||
unit: 'line',
|
||||
edge: 'focus',
|
||||
reverse: true,
|
||||
@ -726,7 +742,7 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
if (Hotkeys.isExtendLineForward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, { unit: 'line', edge: 'focus' })
|
||||
Transforms.move(editor, { unit: 'line', edge: 'focus' })
|
||||
return
|
||||
}
|
||||
|
||||
@ -739,9 +755,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Editor.move(editor, { reverse: true })
|
||||
Transforms.move(editor, { reverse: true })
|
||||
} else {
|
||||
Editor.collapse(editor, { edge: 'start' })
|
||||
Transforms.collapse(editor, { edge: 'start' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -751,9 +767,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Editor.move(editor)
|
||||
Transforms.move(editor)
|
||||
} else {
|
||||
Editor.collapse(editor, { edge: 'end' })
|
||||
Transforms.collapse(editor, { edge: 'end' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -761,13 +777,13 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
if (Hotkeys.isMoveWordBackward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, { unit: 'word', reverse: true })
|
||||
Transforms.move(editor, { unit: 'word', reverse: true })
|
||||
return
|
||||
}
|
||||
|
||||
if (Hotkeys.isMoveWordForward(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
Editor.move(editor, { unit: 'word' })
|
||||
Transforms.move(editor, { unit: 'word' })
|
||||
return
|
||||
}
|
||||
|
||||
@ -788,7 +804,7 @@ export const Editable = (props: EditableProps) => {
|
||||
|
||||
if (Hotkeys.isSplitBlock(nativeEvent)) {
|
||||
event.preventDefault()
|
||||
editor.exec({ type: 'insert_break' })
|
||||
Editor.insertBreak(editor)
|
||||
return
|
||||
}
|
||||
|
||||
@ -796,9 +812,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_backward', unit: 'character' })
|
||||
Editor.deleteBackward(editor)
|
||||
}
|
||||
|
||||
return
|
||||
@ -808,9 +824,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_forward', unit: 'character' })
|
||||
Editor.deleteForward(editor)
|
||||
}
|
||||
|
||||
return
|
||||
@ -820,9 +836,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_backward', unit: 'line' })
|
||||
Editor.deleteBackward(editor, { unit: 'line' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -832,9 +848,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_forward', unit: 'line' })
|
||||
Editor.deleteForward(editor, { unit: 'line' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -844,9 +860,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_backward', unit: 'word' })
|
||||
Editor.deleteBackward(editor, { unit: 'word' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -856,9 +872,9 @@ export const Editable = (props: EditableProps) => {
|
||||
event.preventDefault()
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
editor.exec({ type: 'delete_fragment' })
|
||||
Editor.deleteFragment(editor)
|
||||
} else {
|
||||
editor.exec({ type: 'delete_forward', unit: 'word' })
|
||||
Editor.deleteForward(editor, { unit: 'word' })
|
||||
}
|
||||
|
||||
return
|
||||
@ -879,10 +895,7 @@ export const Editable = (props: EditableProps) => {
|
||||
!isEventHandled(event, attributes.onPaste)
|
||||
) {
|
||||
event.preventDefault()
|
||||
editor.exec({
|
||||
type: 'insert_data',
|
||||
data: event.clipboardData,
|
||||
})
|
||||
ReactEditor.insertData(editor, event.clipboardData)
|
||||
}
|
||||
},
|
||||
[readOnly, attributes.onPaste]
|
||||
@ -984,7 +997,10 @@ const isDOMEventHandled = (event: Event, handler?: (event: Event) => void) => {
|
||||
* Set the currently selected fragment to the clipboard.
|
||||
*/
|
||||
|
||||
const setFragmentData = (dataTransfer: DataTransfer, editor: Editor): void => {
|
||||
const setFragmentData = (
|
||||
dataTransfer: DataTransfer,
|
||||
editor: ReactEditor
|
||||
): void => {
|
||||
const { selection } = editor
|
||||
|
||||
if (!selection) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { useMemo, useState, useCallback } from 'react'
|
||||
import { Editor, Node, Range } from 'slate'
|
||||
import { Node } from 'slate'
|
||||
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
import { FocusedContext } from '../hooks/use-focused'
|
||||
@ -13,7 +13,7 @@ import { EDITOR_TO_ON_CHANGE } from '../utils/weak-maps'
|
||||
*/
|
||||
|
||||
export const Slate = (props: {
|
||||
editor: Editor
|
||||
editor: ReactEditor
|
||||
value: Node[]
|
||||
children: React.ReactNode
|
||||
onChange: (value: Node[]) => void
|
||||
@ -21,7 +21,7 @@ export const Slate = (props: {
|
||||
}) => {
|
||||
const { editor, children, onChange, value, ...rest } = props
|
||||
const [key, setKey] = useState(0)
|
||||
const context: [Editor] = useMemo(() => {
|
||||
const context: [ReactEditor] = useMemo(() => {
|
||||
editor.children = value
|
||||
Object.assign(editor, rest)
|
||||
return [editor]
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { Editor } from 'slate'
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
|
||||
/**
|
||||
* A React context for sharing the `Editor` class.
|
||||
* A React context for sharing the editor object.
|
||||
*/
|
||||
|
||||
export const EditorContext = createContext<Editor | null>(null)
|
||||
export const EditorContext = createContext<ReactEditor | null>(null)
|
||||
|
||||
/**
|
||||
* Get the current `Editor` class that the component lives under.
|
||||
* Get the current editor object from the React context.
|
||||
*/
|
||||
|
||||
export const useEditor = () => {
|
||||
|
@ -1,15 +1,16 @@
|
||||
import { Editor } from 'slate'
|
||||
import { createContext, useContext } from 'react'
|
||||
|
||||
import { ReactEditor } from '../plugin/react-editor'
|
||||
|
||||
/**
|
||||
* A React context for sharing the `Editor` class, in a way that re-renders the
|
||||
* A React context for sharing the editor object, in a way that re-renders the
|
||||
* context whenever changes occur.
|
||||
*/
|
||||
|
||||
export const SlateContext = createContext<[Editor] | null>(null)
|
||||
export const SlateContext = createContext<[ReactEditor] | null>(null)
|
||||
|
||||
/**
|
||||
* Get the current `Editor` class that the component lives under.
|
||||
* Get the current editor object from the React context.
|
||||
*/
|
||||
|
||||
export const useSlate = () => {
|
||||
|
@ -16,6 +16,5 @@ export { useSelected } from './hooks/use-selected'
|
||||
export { useSlate } from './hooks/use-slate'
|
||||
|
||||
// Plugin
|
||||
export { InsertDataCommand, ReactCommand } from './plugin/react-command'
|
||||
export { ReactEditor } from './plugin/react-editor'
|
||||
export { withReact } from './plugin/with-react'
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { Command } from 'slate'
|
||||
|
||||
/**
|
||||
* The `InsertDataCommand` inserts content from a `DataTransfer` object.
|
||||
*/
|
||||
|
||||
export interface InsertDataCommand {
|
||||
type: 'insert_data'
|
||||
data: DataTransfer
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ReactCommand` union for all commands that the React plugins defines.
|
||||
*/
|
||||
|
||||
export type ReactCommand = InsertDataCommand
|
||||
|
||||
export const ReactCommand = {
|
||||
/**
|
||||
* Check if a value is a `ReactCommand` object.
|
||||
*/
|
||||
|
||||
isReactCommand(value: any): value is InsertDataCommand {
|
||||
if (Command.isCommand(value)) {
|
||||
switch (value.type) {
|
||||
case 'insert_data':
|
||||
return value.data instanceof DataTransfer
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Editor, Element, Node, Path, Point, Range } from 'slate'
|
||||
import { Editor, Node, Path, Point, Range, Transforms } from 'slate'
|
||||
|
||||
import { Key } from '../utils/key'
|
||||
import {
|
||||
@ -22,7 +22,13 @@ import {
|
||||
normalizeDOMPoint,
|
||||
} from '../utils/dom'
|
||||
|
||||
export interface ReactEditor extends Editor {}
|
||||
/**
|
||||
* A React and DOM-specific version of the `Editor` interface.
|
||||
*/
|
||||
|
||||
export interface ReactEditor extends Editor {
|
||||
insertData: (data: DataTransfer) => void
|
||||
}
|
||||
|
||||
export const ReactEditor = {
|
||||
/**
|
||||
@ -129,7 +135,7 @@ export const ReactEditor = {
|
||||
}
|
||||
|
||||
if (selection) {
|
||||
Editor.deselect(editor)
|
||||
Transforms.deselect(editor)
|
||||
}
|
||||
},
|
||||
|
||||
@ -170,6 +176,14 @@ export const ReactEditor = {
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Insert data from a `DataTransfer` into the editor.
|
||||
*/
|
||||
|
||||
insertData(editor: ReactEditor, data: DataTransfer): void {
|
||||
editor.insertData(data)
|
||||
},
|
||||
|
||||
/**
|
||||
* Find the native DOM element from a Slate node.
|
||||
*/
|
||||
|
@ -1,8 +1,7 @@
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Editor, Node, Path, Operation, Command } from 'slate'
|
||||
import { Editor, Node, Path, Operation, Transforms } from 'slate'
|
||||
|
||||
import { ReactEditor } from './react-editor'
|
||||
import { ReactCommand } from './react-command'
|
||||
import { Key } from '../utils/key'
|
||||
import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
|
||||
|
||||
@ -10,18 +9,19 @@ import { EDITOR_TO_ON_CHANGE, NODE_TO_KEY } from '../utils/weak-maps'
|
||||
* `withReact` adds React and DOM specific behaviors to the editor.
|
||||
*/
|
||||
|
||||
export const withReact = (editor: Editor): Editor => {
|
||||
const { apply, exec, onChange } = editor
|
||||
export const withReact = (editor: Editor): ReactEditor => {
|
||||
const e = editor as ReactEditor
|
||||
const { apply, onChange } = e
|
||||
|
||||
editor.apply = (op: Operation) => {
|
||||
e.apply = (op: Operation) => {
|
||||
const matches: [Path, Key][] = []
|
||||
|
||||
switch (op.type) {
|
||||
case 'insert_text':
|
||||
case 'remove_text':
|
||||
case 'set_node': {
|
||||
for (const [node, path] of Editor.levels(editor, { at: op.path })) {
|
||||
const key = ReactEditor.findKey(editor, node)
|
||||
for (const [node, path] of Editor.levels(e, { at: op.path })) {
|
||||
const key = ReactEditor.findKey(e, node)
|
||||
matches.push([path, key])
|
||||
}
|
||||
|
||||
@ -32,10 +32,10 @@ export const withReact = (editor: Editor): Editor => {
|
||||
case 'remove_node':
|
||||
case 'merge_node':
|
||||
case 'split_node': {
|
||||
for (const [node, path] of Editor.levels(editor, {
|
||||
for (const [node, path] of Editor.levels(e, {
|
||||
at: Path.parent(op.path),
|
||||
})) {
|
||||
const key = ReactEditor.findKey(editor, node)
|
||||
const key = ReactEditor.findKey(e, node)
|
||||
matches.push([path, key])
|
||||
}
|
||||
|
||||
@ -51,53 +51,45 @@ export const withReact = (editor: Editor): Editor => {
|
||||
apply(op)
|
||||
|
||||
for (const [path, key] of matches) {
|
||||
const [node] = Editor.node(editor, path)
|
||||
const [node] = Editor.node(e, path)
|
||||
NODE_TO_KEY.set(node, key)
|
||||
}
|
||||
}
|
||||
|
||||
editor.exec = (command: Command) => {
|
||||
if (
|
||||
ReactCommand.isReactCommand(command) &&
|
||||
command.type === 'insert_data'
|
||||
) {
|
||||
const { data } = command
|
||||
const fragment = data.getData('application/x-slate-fragment')
|
||||
e.insertData = (data: DataTransfer) => {
|
||||
const fragment = data.getData('application/x-slate-fragment')
|
||||
|
||||
if (fragment) {
|
||||
const decoded = decodeURIComponent(window.atob(fragment))
|
||||
const parsed = JSON.parse(decoded) as Node[]
|
||||
Editor.insertFragment(editor, parsed)
|
||||
return
|
||||
}
|
||||
if (fragment) {
|
||||
const decoded = decodeURIComponent(window.atob(fragment))
|
||||
const parsed = JSON.parse(decoded) as Node[]
|
||||
Transforms.insertFragment(e, parsed)
|
||||
return
|
||||
}
|
||||
|
||||
const text = data.getData('text/plain')
|
||||
const text = data.getData('text/plain')
|
||||
|
||||
if (text) {
|
||||
const lines = text.split('\n')
|
||||
let split = false
|
||||
if (text) {
|
||||
const lines = text.split('\n')
|
||||
let split = false
|
||||
|
||||
for (const line of lines) {
|
||||
if (split) {
|
||||
Editor.splitNodes(editor)
|
||||
}
|
||||
|
||||
Editor.insertText(editor, line)
|
||||
split = true
|
||||
for (const line of lines) {
|
||||
if (split) {
|
||||
Transforms.splitNodes(e)
|
||||
}
|
||||
|
||||
Transforms.insertText(e, line)
|
||||
split = true
|
||||
}
|
||||
} else {
|
||||
exec(command)
|
||||
}
|
||||
}
|
||||
|
||||
editor.onChange = () => {
|
||||
e.onChange = () => {
|
||||
// COMPAT: React doesn't batch `setState` hook calls, which means that the
|
||||
// children and selection can get out of sync for one render pass. So we
|
||||
// have to use this unstable API to ensure it batches them. (2019/12/03)
|
||||
// https://github.com/facebook/react/issues/14259#issuecomment-439702367
|
||||
ReactDOM.unstable_batchedUpdates(() => {
|
||||
const onContextChange = EDITOR_TO_ON_CHANGE.get(editor)
|
||||
const onContextChange = EDITOR_TO_ON_CHANGE.get(e)
|
||||
|
||||
if (onContextChange) {
|
||||
onContextChange()
|
||||
@ -107,5 +99,5 @@ export const withReact = (editor: Editor): Editor => {
|
||||
})
|
||||
}
|
||||
|
||||
return editor
|
||||
return e
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import {
|
||||
Command,
|
||||
CoreCommand,
|
||||
Descendant,
|
||||
Editor,
|
||||
Element,
|
||||
@ -13,10 +11,9 @@ import {
|
||||
Range,
|
||||
RangeRef,
|
||||
Text,
|
||||
} from '.'
|
||||
import { DIRTY_PATHS } from './interfaces/editor/transforms/general'
|
||||
|
||||
const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
Transforms,
|
||||
} from './'
|
||||
import { DIRTY_PATHS, FLUSHING } from './utils/weak-maps'
|
||||
|
||||
/**
|
||||
* Create a new Slate `Editor` object.
|
||||
@ -31,6 +28,7 @@ export const createEditor = (): Editor => {
|
||||
isInline: () => false,
|
||||
isVoid: () => false,
|
||||
onChange: () => {},
|
||||
|
||||
apply: (op: Operation) => {
|
||||
for (const ref of Editor.pathRefs(editor)) {
|
||||
PathRef.transform(ref, op)
|
||||
@ -90,131 +88,101 @@ export const createEditor = (): Editor => {
|
||||
})
|
||||
}
|
||||
},
|
||||
exec: (command: Command) => {
|
||||
if (CoreCommand.isCoreCommand(command)) {
|
||||
const { selection } = editor
|
||||
|
||||
switch (command.type) {
|
||||
case 'add_mark': {
|
||||
if (selection) {
|
||||
const { key, value } = command
|
||||
addMark: (key: string, value: any) => {
|
||||
const { selection } = editor
|
||||
|
||||
if (Range.isExpanded(selection)) {
|
||||
Editor.setNodes(
|
||||
editor,
|
||||
{ [key]: value },
|
||||
{ match: Text.isText, split: true }
|
||||
)
|
||||
} else {
|
||||
const marks = {
|
||||
...(Editor.marks(editor) || {}),
|
||||
[key]: value,
|
||||
}
|
||||
|
||||
editor.marks = marks
|
||||
editor.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
if (selection) {
|
||||
if (Range.isExpanded(selection)) {
|
||||
Transforms.setNodes(
|
||||
editor,
|
||||
{ [key]: value },
|
||||
{ match: Text.isText, split: true }
|
||||
)
|
||||
} else {
|
||||
const marks = {
|
||||
...(Editor.marks(editor) || {}),
|
||||
[key]: value,
|
||||
}
|
||||
|
||||
case 'delete_backward': {
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Editor.delete(editor, { unit: command.unit, reverse: true })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'delete_forward': {
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Editor.delete(editor, { unit: command.unit })
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'delete_fragment': {
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
Editor.delete(editor)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_break': {
|
||||
Editor.splitNodes(editor, { always: true })
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_fragment': {
|
||||
Editor.insertFragment(editor, command.fragment)
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_node': {
|
||||
Editor.insertNodes(editor, command.node)
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_text': {
|
||||
if (selection) {
|
||||
// If the cursor is at the end of an inline, move it outside of
|
||||
// the inline before inserting
|
||||
if (Range.isCollapsed(selection)) {
|
||||
const inline = Editor.above(editor, {
|
||||
match: n => Editor.isInline(editor, n),
|
||||
mode: 'highest',
|
||||
})
|
||||
|
||||
if (inline) {
|
||||
const [, inlinePath] = inline
|
||||
|
||||
if (Editor.isEnd(editor, selection.anchor, inlinePath)) {
|
||||
const point = Editor.after(editor, inlinePath)!
|
||||
Editor.setSelection(editor, { anchor: point, focus: point })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { marks } = editor
|
||||
const { text } = command
|
||||
|
||||
if (marks) {
|
||||
const node = { text, ...marks }
|
||||
Editor.insertNodes(editor, node)
|
||||
} else {
|
||||
Editor.insertText(editor, text)
|
||||
}
|
||||
|
||||
editor.marks = null
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
if (selection) {
|
||||
const { key } = command
|
||||
|
||||
if (Range.isExpanded(selection)) {
|
||||
Editor.unsetNodes(editor, key, {
|
||||
match: Text.isText,
|
||||
split: true,
|
||||
})
|
||||
} else {
|
||||
const marks = { ...(Editor.marks(editor) || {}) }
|
||||
delete marks[key]
|
||||
editor.marks = marks
|
||||
editor.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
editor.marks = marks
|
||||
editor.onChange()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
deleteBackward: (unit: 'character' | 'word' | 'line' | 'block') => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Transforms.delete(editor, { unit, reverse: true })
|
||||
}
|
||||
},
|
||||
|
||||
deleteForward: (unit: 'character' | 'word' | 'line' | 'block') => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Transforms.delete(editor, { unit })
|
||||
}
|
||||
},
|
||||
|
||||
deleteFragment: () => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection && Range.isExpanded(selection)) {
|
||||
Transforms.delete(editor)
|
||||
}
|
||||
},
|
||||
|
||||
insertBreak: () => {
|
||||
Transforms.splitNodes(editor, { always: true })
|
||||
},
|
||||
|
||||
insertFragment: (fragment: Node[]) => {
|
||||
Transforms.insertFragment(editor, fragment)
|
||||
},
|
||||
|
||||
insertNode: (node: Node) => {
|
||||
Transforms.insertNodes(editor, node)
|
||||
},
|
||||
|
||||
insertText: (text: string) => {
|
||||
const { selection, marks } = editor
|
||||
|
||||
if (selection) {
|
||||
// If the cursor is at the end of an inline, move it outside of
|
||||
// the inline before inserting
|
||||
if (Range.isCollapsed(selection)) {
|
||||
const inline = Editor.above(editor, {
|
||||
match: n => Editor.isInline(editor, n),
|
||||
mode: 'highest',
|
||||
})
|
||||
|
||||
if (inline) {
|
||||
const [, inlinePath] = inline
|
||||
|
||||
if (Editor.isEnd(editor, selection.anchor, inlinePath)) {
|
||||
const point = Editor.after(editor, inlinePath)!
|
||||
Transforms.setSelection(editor, {
|
||||
anchor: point,
|
||||
focus: point,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (marks) {
|
||||
const node = { text, ...marks }
|
||||
Transforms.insertNodes(editor, node)
|
||||
} else {
|
||||
Transforms.insertText(editor, text)
|
||||
}
|
||||
|
||||
editor.marks = null
|
||||
}
|
||||
},
|
||||
|
||||
normalizeNode: (entry: NodeEntry) => {
|
||||
const [node, path] = entry
|
||||
|
||||
@ -226,7 +194,10 @@ export const createEditor = (): Editor => {
|
||||
// Ensure that block and inline nodes have at least one text child.
|
||||
if (Element.isElement(node) && node.children.length === 0) {
|
||||
const child = { text: '' }
|
||||
Editor.insertNodes(editor, child, { at: path.concat(0), voids: true })
|
||||
Transforms.insertNodes(editor, child, {
|
||||
at: path.concat(0),
|
||||
voids: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
@ -256,21 +227,21 @@ export const createEditor = (): Editor => {
|
||||
// other inline nodes, or parent blocks that only contain inlines and
|
||||
// text.
|
||||
if (isInlineOrText !== shouldHaveInlines) {
|
||||
Editor.removeNodes(editor, { at: path.concat(n), voids: true })
|
||||
Transforms.removeNodes(editor, { at: path.concat(n), voids: true })
|
||||
n--
|
||||
} else if (Element.isElement(child)) {
|
||||
// Ensure that inline nodes are surrounded by text nodes.
|
||||
if (editor.isInline(child)) {
|
||||
if (prev == null || !Text.isText(prev)) {
|
||||
const newChild = { text: '' }
|
||||
Editor.insertNodes(editor, newChild, {
|
||||
Transforms.insertNodes(editor, newChild, {
|
||||
at: path.concat(n),
|
||||
voids: true,
|
||||
})
|
||||
n++
|
||||
} else if (isLast) {
|
||||
const newChild = { text: '' }
|
||||
Editor.insertNodes(editor, newChild, {
|
||||
Transforms.insertNodes(editor, newChild, {
|
||||
at: path.concat(n + 1),
|
||||
voids: true,
|
||||
})
|
||||
@ -281,22 +252,43 @@ export const createEditor = (): Editor => {
|
||||
// Merge adjacent text nodes that are empty or match.
|
||||
if (prev != null && Text.isText(prev)) {
|
||||
if (Text.equals(child, prev, { loose: true })) {
|
||||
Editor.mergeNodes(editor, { at: path.concat(n), voids: true })
|
||||
Transforms.mergeNodes(editor, { at: path.concat(n), voids: true })
|
||||
n--
|
||||
} else if (prev.text === '') {
|
||||
Editor.removeNodes(editor, {
|
||||
Transforms.removeNodes(editor, {
|
||||
at: path.concat(n - 1),
|
||||
voids: true,
|
||||
})
|
||||
n--
|
||||
} else if (isLast && child.text === '') {
|
||||
Editor.removeNodes(editor, { at: path.concat(n), voids: true })
|
||||
Transforms.removeNodes(editor, {
|
||||
at: path.concat(n),
|
||||
voids: true,
|
||||
})
|
||||
n--
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
removeMark: (key: string) => {
|
||||
const { selection } = editor
|
||||
|
||||
if (selection) {
|
||||
if (Range.isExpanded(selection)) {
|
||||
Transforms.unsetNodes(editor, key, {
|
||||
match: Text.isText,
|
||||
split: true,
|
||||
})
|
||||
} else {
|
||||
const marks = { ...(Editor.marks(editor) || {}) }
|
||||
delete marks[key]
|
||||
editor.marks = marks
|
||||
editor.onChange()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return editor
|
||||
|
@ -1,5 +1,4 @@
|
||||
export * from './create-editor'
|
||||
export * from './interfaces/command'
|
||||
export * from './interfaces/editor'
|
||||
export * from './interfaces/element'
|
||||
export * from './interfaces/location'
|
||||
@ -12,3 +11,4 @@ export * from './interfaces/point-ref'
|
||||
export * from './interfaces/range'
|
||||
export * from './interfaces/range-ref'
|
||||
export * from './interfaces/text'
|
||||
export * from './transforms'
|
||||
|
@ -1,153 +0,0 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Node } from '..'
|
||||
|
||||
/**
|
||||
* `Command` objects represent an action that a user is taking on the editor.
|
||||
* They capture the semantic "intent" of a user while they edit a document.
|
||||
*/
|
||||
|
||||
export interface Command {
|
||||
type: string
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const Command = {
|
||||
/**
|
||||
* Check if a value is a `Command` object.
|
||||
*/
|
||||
|
||||
isCommand(value: any): value is Command {
|
||||
return isPlainObject(value) && typeof value.type === 'string'
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AddMarkCommand` adds properties to the text nodes in the selection.
|
||||
*/
|
||||
|
||||
export interface AddMarkCommand {
|
||||
type: 'add_mark'
|
||||
key: string
|
||||
value: any
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DeleteBackwardCommand` delete's content backward, meaning before the
|
||||
* current selection, by a specific `unit` of distance.
|
||||
*/
|
||||
|
||||
export interface DeleteBackwardCommand {
|
||||
type: 'delete_backward'
|
||||
unit: 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DeleteForwardCommand` delete's content forward, meaning after the
|
||||
* current selection, by a specific `unit` of distance.
|
||||
*/
|
||||
|
||||
export interface DeleteForwardCommand {
|
||||
type: 'delete_forward'
|
||||
unit: 'character' | 'word' | 'line' | 'block'
|
||||
}
|
||||
|
||||
/**
|
||||
* The `DeleteFragmentCommand` delete's the content of the current selection.
|
||||
*/
|
||||
|
||||
export interface DeleteFragmentCommand {
|
||||
type: 'delete_fragment'
|
||||
}
|
||||
|
||||
/**
|
||||
* The `InsertBreakCommand` breaks a block in two at the current selection.
|
||||
*/
|
||||
|
||||
export interface InsertBreakCommand {
|
||||
type: 'insert_break'
|
||||
}
|
||||
|
||||
/**
|
||||
* The `InsertFragmentCommand` inserts a list of nodes at the current selection.
|
||||
*/
|
||||
|
||||
export interface InsertFragmentCommand {
|
||||
type: 'insert_fragment'
|
||||
fragment: Node[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The `InsertNodeCommand` inserts a node at the current selection.
|
||||
*/
|
||||
|
||||
export interface InsertNodeCommand {
|
||||
type: 'insert_node'
|
||||
node: Node
|
||||
}
|
||||
|
||||
/**
|
||||
* The `InsertTextCommand` inserts a string of text at the current selection.
|
||||
*/
|
||||
|
||||
export interface InsertTextCommand {
|
||||
type: 'insert_text'
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The `RemoveMarkCommand` removes properties from text nodes in the selection.
|
||||
*/
|
||||
|
||||
export interface RemoveMarkCommand {
|
||||
type: 'remove_mark'
|
||||
key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The `CoreCommand` union is a set of all of the commands that are recognized
|
||||
* by Slate's "core" out of the box.
|
||||
*/
|
||||
|
||||
export type CoreCommand =
|
||||
| AddMarkCommand
|
||||
| DeleteBackwardCommand
|
||||
| DeleteForwardCommand
|
||||
| DeleteFragmentCommand
|
||||
| InsertBreakCommand
|
||||
| InsertFragmentCommand
|
||||
| InsertNodeCommand
|
||||
| InsertTextCommand
|
||||
| RemoveMarkCommand
|
||||
|
||||
export const CoreCommand = {
|
||||
/**
|
||||
* Check if a value is a `CoreCommand` object.
|
||||
*/
|
||||
|
||||
isCoreCommand(value: any): value is CoreCommand {
|
||||
if (Command.isCommand(value)) {
|
||||
switch (value.type) {
|
||||
case 'add_mark':
|
||||
return typeof value.key === 'string' && value.value != null
|
||||
case 'delete_backward':
|
||||
return typeof value.unit === 'string'
|
||||
case 'delete_forward':
|
||||
return typeof value.unit === 'string'
|
||||
case 'delete_fragment':
|
||||
return true
|
||||
case 'insert_break':
|
||||
return true
|
||||
case 'insert_fragment':
|
||||
return Node.isNodeList(value.fragment)
|
||||
case 'insert_node':
|
||||
return Node.isNode(value.node)
|
||||
case 'insert_text':
|
||||
return typeof value.text === 'string'
|
||||
case 'remove_mark':
|
||||
return typeof value.key === 'string'
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
}
|
1598
packages/slate/src/interfaces/editor.ts
Executable file
1598
packages/slate/src/interfaces/editor.ts
Executable file
File diff suppressed because it is too large
Load Diff
@ -1,40 +0,0 @@
|
||||
import { Command, Element, Operation, Range, Node, NodeEntry } from '../..'
|
||||
|
||||
import { ElementQueries } from './queries/element'
|
||||
import { GeneralTransforms } from './transforms/general'
|
||||
import { GeneralQueries } from './queries/general'
|
||||
import { LocationQueries } from './queries/location'
|
||||
import { NodeTransforms } from './transforms/node'
|
||||
import { RangeQueries } from './queries/range'
|
||||
import { SelectionTransforms } from './transforms/selection'
|
||||
import { TextTransforms } from './transforms/text'
|
||||
|
||||
/**
|
||||
* The `Editor` interface stores all the state of a Slate editor. It is extended
|
||||
* by plugins that wish to add their own helpers and implement new behaviors.
|
||||
*/
|
||||
|
||||
export interface Editor {
|
||||
children: Node[]
|
||||
selection: Range | null
|
||||
operations: Operation[]
|
||||
marks: Record<string, any> | null
|
||||
apply: (operation: Operation) => void
|
||||
exec: (command: Command) => void
|
||||
isInline: (element: Element) => boolean
|
||||
isVoid: (element: Element) => boolean
|
||||
normalizeNode: (entry: NodeEntry) => void
|
||||
onChange: () => void
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const Editor = {
|
||||
...ElementQueries,
|
||||
...GeneralQueries,
|
||||
...GeneralTransforms,
|
||||
...LocationQueries,
|
||||
...NodeTransforms,
|
||||
...RangeQueries,
|
||||
...SelectionTransforms,
|
||||
...TextTransforms,
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import { Editor, Element, Text } from '../../..'
|
||||
|
||||
export const ElementQueries = {
|
||||
/**
|
||||
* Check if a node has block children.
|
||||
*/
|
||||
|
||||
hasBlocks(editor: Editor, element: Element): boolean {
|
||||
return element.children.some(n => Editor.isBlock(editor, n))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a node has inline and text children.
|
||||
*/
|
||||
|
||||
hasInlines(editor: Editor, element: Element): boolean {
|
||||
return element.children.some(
|
||||
n => Text.isText(n) || Editor.isInline(editor, n)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a node has text children.
|
||||
*/
|
||||
|
||||
hasTexts(editor: Editor, element: Element): boolean {
|
||||
return element.children.every(n => Text.isText(n))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an element is empty, accounting for void nodes.
|
||||
*/
|
||||
|
||||
isEmpty(editor: Editor, element: Element): boolean {
|
||||
const { children } = element
|
||||
const [first] = children
|
||||
return (
|
||||
children.length === 0 ||
|
||||
(children.length === 1 &&
|
||||
Text.isText(first) &&
|
||||
first.text === '' &&
|
||||
!editor.isVoid(element))
|
||||
)
|
||||
},
|
||||
}
|
@ -1,273 +0,0 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import {
|
||||
Editor,
|
||||
Operation,
|
||||
Path,
|
||||
Point,
|
||||
Text,
|
||||
PathRef,
|
||||
PointRef,
|
||||
Element,
|
||||
NodeEntry,
|
||||
Range,
|
||||
RangeRef,
|
||||
Node,
|
||||
} from '../../..'
|
||||
|
||||
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
|
||||
export const POINT_REFS: WeakMap<Editor, Set<PointRef>> = new WeakMap()
|
||||
export const RANGE_REFS: WeakMap<Editor, Set<RangeRef>> = new WeakMap()
|
||||
|
||||
export const GeneralQueries = {
|
||||
/**
|
||||
* Check if a value is a block `Element` object.
|
||||
*/
|
||||
|
||||
isBlock(editor: Editor, value: any): value is Element {
|
||||
return Element.isElement(value) && !editor.isInline(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is an `Editor` object.
|
||||
*/
|
||||
|
||||
isEditor(value: any): value is Editor {
|
||||
return (
|
||||
isPlainObject(value) &&
|
||||
typeof value.apply === 'function' &&
|
||||
typeof value.exec === 'function' &&
|
||||
typeof value.isInline === 'function' &&
|
||||
typeof value.isVoid === 'function' &&
|
||||
typeof value.normalizeNode === 'function' &&
|
||||
typeof value.onChange === 'function' &&
|
||||
(value.marks === null || isPlainObject(value.marks)) &&
|
||||
(value.selection === null || Range.isRange(value.selection)) &&
|
||||
Node.isNodeList(value.children) &&
|
||||
Operation.isOperationList(value.operations)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is an inline `Element` object.
|
||||
*/
|
||||
|
||||
isInline(editor: Editor, value: any): value is Element {
|
||||
return Element.isElement(value) && editor.isInline(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the editor is currently normalizing after each operation.
|
||||
*/
|
||||
|
||||
isNormalizing(editor: Editor): boolean {
|
||||
const isNormalizing = NORMALIZING.get(editor)
|
||||
return isNormalizing === undefined ? true : isNormalizing
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is a void `Element` object.
|
||||
*/
|
||||
|
||||
isVoid(editor: Editor, value: any): value is Element {
|
||||
return Element.isElement(value) && editor.isVoid(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the marks that would be added to text at the current selection.
|
||||
*/
|
||||
|
||||
marks(editor: Editor): Record<string, any> | null {
|
||||
const { marks, selection } = editor
|
||||
|
||||
if (!selection) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (marks) {
|
||||
return marks
|
||||
}
|
||||
|
||||
if (Range.isExpanded(selection)) {
|
||||
const [match] = Editor.nodes(editor, { match: Text.isText })
|
||||
|
||||
if (match) {
|
||||
const [node] = match as NodeEntry<Text>
|
||||
const { text, ...rest } = node
|
||||
return rest
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
const { anchor } = selection
|
||||
const { path } = anchor
|
||||
let [node] = Editor.leaf(editor, path)
|
||||
|
||||
if (anchor.offset === 0) {
|
||||
const prev = Editor.previous(editor, { at: path, match: Text.isText })
|
||||
const block = Editor.above(editor, {
|
||||
match: n => Editor.isBlock(editor, n),
|
||||
})
|
||||
|
||||
if (prev && block) {
|
||||
const [prevNode, prevPath] = prev
|
||||
const [, blockPath] = block
|
||||
|
||||
if (Path.isAncestor(blockPath, prevPath)) {
|
||||
node = prevNode as Text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { text, ...rest } = node
|
||||
return rest
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a mutable ref for a `Path` object, which will stay in sync as new
|
||||
* operations are applied to the editor.
|
||||
*/
|
||||
|
||||
pathRef(
|
||||
editor: Editor,
|
||||
path: Path,
|
||||
options: {
|
||||
affinity?: 'backward' | 'forward' | null
|
||||
} = {}
|
||||
): PathRef {
|
||||
const { affinity = 'forward' } = options
|
||||
const ref: PathRef = {
|
||||
current: path,
|
||||
affinity,
|
||||
unref() {
|
||||
const { current } = ref
|
||||
const pathRefs = Editor.pathRefs(editor)
|
||||
pathRefs.delete(ref)
|
||||
ref.current = null
|
||||
return current
|
||||
},
|
||||
}
|
||||
|
||||
const refs = Editor.pathRefs(editor)
|
||||
refs.add(ref)
|
||||
return ref
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the set of currently tracked path refs of the editor.
|
||||
*/
|
||||
|
||||
pathRefs(editor: Editor): Set<PathRef> {
|
||||
let refs = PATH_REFS.get(editor)
|
||||
|
||||
if (!refs) {
|
||||
refs = new Set()
|
||||
PATH_REFS.set(editor, refs)
|
||||
}
|
||||
|
||||
return refs
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a mutable ref for a `Point` object, which will stay in sync as new
|
||||
* operations are applied to the editor.
|
||||
*/
|
||||
|
||||
pointRef(
|
||||
editor: Editor,
|
||||
point: Point,
|
||||
options: {
|
||||
affinity?: 'backward' | 'forward' | null
|
||||
} = {}
|
||||
): PointRef {
|
||||
const { affinity = 'forward' } = options
|
||||
const ref: PointRef = {
|
||||
current: point,
|
||||
affinity,
|
||||
unref() {
|
||||
const { current } = ref
|
||||
const pointRefs = Editor.pointRefs(editor)
|
||||
pointRefs.delete(ref)
|
||||
ref.current = null
|
||||
return current
|
||||
},
|
||||
}
|
||||
|
||||
const refs = Editor.pointRefs(editor)
|
||||
refs.add(ref)
|
||||
return ref
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the set of currently tracked point refs of the editor.
|
||||
*/
|
||||
|
||||
pointRefs(editor: Editor): Set<PointRef> {
|
||||
let refs = POINT_REFS.get(editor)
|
||||
|
||||
if (!refs) {
|
||||
refs = new Set()
|
||||
POINT_REFS.set(editor, refs)
|
||||
}
|
||||
|
||||
return refs
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a mutable ref for a `Range` object, which will stay in sync as new
|
||||
* operations are applied to the editor.
|
||||
*/
|
||||
|
||||
rangeRef(
|
||||
editor: Editor,
|
||||
range: Range,
|
||||
options: {
|
||||
affinity?: 'backward' | 'forward' | 'outward' | 'inward' | null
|
||||
} = {}
|
||||
): RangeRef {
|
||||
const { affinity = 'forward' } = options
|
||||
const ref: RangeRef = {
|
||||
current: range,
|
||||
affinity,
|
||||
unref() {
|
||||
const { current } = ref
|
||||
const rangeRefs = Editor.rangeRefs(editor)
|
||||
rangeRefs.delete(ref)
|
||||
ref.current = null
|
||||
return current
|
||||
},
|
||||
}
|
||||
|
||||
const refs = Editor.rangeRefs(editor)
|
||||
refs.add(ref)
|
||||
return ref
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the set of currently tracked range refs of the editor.
|
||||
*/
|
||||
|
||||
rangeRefs(editor: Editor): Set<RangeRef> {
|
||||
let refs = RANGE_REFS.get(editor)
|
||||
|
||||
if (!refs) {
|
||||
refs = new Set()
|
||||
RANGE_REFS.set(editor, refs)
|
||||
}
|
||||
|
||||
return refs
|
||||
},
|
||||
|
||||
/**
|
||||
* Call a function, deferring normalization until after it completes.
|
||||
*/
|
||||
|
||||
withoutNormalizing(editor: Editor, fn: () => void): void {
|
||||
const value = Editor.isNormalizing(editor)
|
||||
NORMALIZING.set(editor, false)
|
||||
fn()
|
||||
NORMALIZING.set(editor, value)
|
||||
Editor.normalize(editor)
|
||||
},
|
||||
}
|
@ -1,993 +0,0 @@
|
||||
import { reverse as reverseText } from 'esrever'
|
||||
|
||||
import {
|
||||
Ancestor,
|
||||
Descendant,
|
||||
Editor,
|
||||
Element,
|
||||
Location,
|
||||
Node,
|
||||
NodeEntry,
|
||||
Path,
|
||||
Point,
|
||||
Range,
|
||||
Span,
|
||||
Text,
|
||||
} from '../../..'
|
||||
|
||||
export const LocationQueries = {
|
||||
/**
|
||||
* Get the ancestor above a location in the document.
|
||||
*/
|
||||
|
||||
above<T extends Ancestor>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): NodeEntry<T> | undefined {
|
||||
const {
|
||||
voids = false,
|
||||
mode = 'lowest',
|
||||
at = editor.selection,
|
||||
match,
|
||||
} = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
const path = Editor.path(editor, at)
|
||||
const reverse = mode === 'lowest'
|
||||
|
||||
for (const [n, p] of Editor.levels(editor, {
|
||||
at: path,
|
||||
voids,
|
||||
match,
|
||||
reverse,
|
||||
})) {
|
||||
if (!Text.isText(n) && !Path.equals(path, p)) {
|
||||
return [n, p]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the point after a location.
|
||||
*/
|
||||
|
||||
after(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
distance?: number
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
} = {}
|
||||
): Point | undefined {
|
||||
const anchor = Editor.point(editor, at, { edge: 'end' })
|
||||
const focus = Editor.end(editor, [])
|
||||
const range = { anchor, focus }
|
||||
const { distance = 1 } = options
|
||||
let d = 0
|
||||
let target
|
||||
|
||||
for (const p of Editor.positions(editor, { ...options, at: range })) {
|
||||
if (d > distance) {
|
||||
break
|
||||
}
|
||||
|
||||
if (d !== 0) {
|
||||
target = p
|
||||
}
|
||||
|
||||
d++
|
||||
}
|
||||
|
||||
return target
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the point before a location.
|
||||
*/
|
||||
|
||||
before(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
distance?: number
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
} = {}
|
||||
): Point | undefined {
|
||||
const anchor = Editor.start(editor, [])
|
||||
const focus = Editor.point(editor, at, { edge: 'start' })
|
||||
const range = { anchor, focus }
|
||||
const { distance = 1 } = options
|
||||
let d = 0
|
||||
let target
|
||||
|
||||
for (const p of Editor.positions(editor, {
|
||||
...options,
|
||||
at: range,
|
||||
reverse: true,
|
||||
})) {
|
||||
if (d > distance) {
|
||||
break
|
||||
}
|
||||
|
||||
if (d !== 0) {
|
||||
target = p
|
||||
}
|
||||
|
||||
d++
|
||||
}
|
||||
|
||||
return target
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the start and end points of a location.
|
||||
*/
|
||||
|
||||
edges(editor: Editor, at: Location): [Point, Point] {
|
||||
return [Editor.start(editor, at), Editor.end(editor, at)]
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the end point of a location.
|
||||
*/
|
||||
|
||||
end(editor: Editor, at: Location): Point {
|
||||
return Editor.point(editor, at, { edge: 'end' })
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first node at a location.
|
||||
*/
|
||||
|
||||
first(editor: Editor, at: Location): NodeEntry {
|
||||
const path = Editor.path(editor, at, { edge: 'start' })
|
||||
return Editor.node(editor, path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the fragment at a location.
|
||||
*/
|
||||
|
||||
fragment(editor: Editor, at: Location): Descendant[] {
|
||||
const range = Editor.range(editor, at)
|
||||
const fragment = Node.fragment(editor, range)
|
||||
return fragment
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a point is the end point of a location.
|
||||
*/
|
||||
|
||||
isEnd(editor: Editor, point: Point, at: Location): boolean {
|
||||
const end = Editor.end(editor, at)
|
||||
return Point.equals(point, end)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a point is an edge of a location.
|
||||
*/
|
||||
|
||||
isEdge(editor: Editor, point: Point, at: Location): boolean {
|
||||
return Editor.isStart(editor, point, at) || Editor.isEnd(editor, point, at)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a point is the start point of a location.
|
||||
*/
|
||||
|
||||
isStart(editor: Editor, point: Point, at: Location): boolean {
|
||||
// PERF: If the offset isn't `0` we know it's not the start.
|
||||
if (point.offset !== 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
const start = Editor.start(editor, at)
|
||||
return Point.equals(point, start)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the last node at a location.
|
||||
*/
|
||||
|
||||
last(editor: Editor, at: Location): NodeEntry {
|
||||
const path = Editor.path(editor, at, { edge: 'end' })
|
||||
return Editor.node(editor, path)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the leaf text node at a location.
|
||||
*/
|
||||
|
||||
leaf(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
} = {}
|
||||
): NodeEntry<Text> {
|
||||
const path = Editor.path(editor, at, options)
|
||||
const node = Node.leaf(editor, path)
|
||||
return [node, path]
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate through all of the levels at a location.
|
||||
*/
|
||||
|
||||
*levels<T extends Node>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
reverse?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): Iterable<NodeEntry<T>> {
|
||||
const { at = editor.selection, reverse = false, voids = false } = options
|
||||
let { match } = options
|
||||
|
||||
if (match == null) {
|
||||
match = () => true
|
||||
}
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
const levels: NodeEntry<T>[] = []
|
||||
const path = Editor.path(editor, at)
|
||||
|
||||
for (const [n, p] of Node.levels(editor, path)) {
|
||||
if (!match(n)) {
|
||||
continue
|
||||
}
|
||||
|
||||
levels.push([n, p])
|
||||
|
||||
if (!voids && Editor.isVoid(editor, n)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse) {
|
||||
levels.reverse()
|
||||
}
|
||||
|
||||
yield* levels
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the matching node in the branch of the document after a location.
|
||||
*/
|
||||
|
||||
next<T extends Node>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): NodeEntry<T> | undefined {
|
||||
const { mode = 'lowest', voids = false } = options
|
||||
let { match, at = editor.selection } = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
const [, from] = Editor.last(editor, at)
|
||||
const [, to] = Editor.last(editor, [])
|
||||
const span: Span = [from, to]
|
||||
|
||||
if (Path.isPath(at) && at.length === 0) {
|
||||
throw new Error(`Cannot get the next node from the root node!`)
|
||||
}
|
||||
|
||||
if (match == null) {
|
||||
if (Path.isPath(at)) {
|
||||
const [parent] = Editor.parent(editor, at)
|
||||
match = n => parent.children.includes(n)
|
||||
} else {
|
||||
match = () => true
|
||||
}
|
||||
}
|
||||
|
||||
const [, next] = Editor.nodes(editor, { at: span, match, mode, voids })
|
||||
return next
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the node at a location.
|
||||
*/
|
||||
|
||||
node(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
} = {}
|
||||
): NodeEntry {
|
||||
const path = Editor.path(editor, at, options)
|
||||
const node = Node.get(editor, path)
|
||||
return [node, path]
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate through all of the nodes in the Editor.
|
||||
*/
|
||||
|
||||
*nodes<T extends Node>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location | Span
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
universal?: boolean
|
||||
reverse?: boolean
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): Iterable<NodeEntry<T>> {
|
||||
const {
|
||||
at = editor.selection,
|
||||
mode = 'all',
|
||||
universal = false,
|
||||
reverse = false,
|
||||
voids = false,
|
||||
} = options
|
||||
let { match } = options
|
||||
|
||||
if (!match) {
|
||||
match = () => true
|
||||
}
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
let from
|
||||
let to
|
||||
|
||||
if (Span.isSpan(at)) {
|
||||
from = at[0]
|
||||
to = at[1]
|
||||
} else {
|
||||
const first = Editor.path(editor, at, { edge: 'start' })
|
||||
const last = Editor.path(editor, at, { edge: 'end' })
|
||||
from = reverse ? last : first
|
||||
to = reverse ? first : last
|
||||
}
|
||||
|
||||
const iterable = Node.nodes(editor, {
|
||||
reverse,
|
||||
from,
|
||||
to,
|
||||
pass: ([n]) => (voids ? false : Editor.isVoid(editor, n)),
|
||||
})
|
||||
|
||||
const matches: NodeEntry<T>[] = []
|
||||
let hit: NodeEntry<T> | undefined
|
||||
|
||||
for (const [node, path] of iterable) {
|
||||
const isLower = hit && Path.compare(path, hit[1]) === 0
|
||||
|
||||
// In highest mode any node lower than the last hit is not a match.
|
||||
if (mode === 'highest' && isLower) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!match(node)) {
|
||||
// If we've arrived at a leaf text node that is not lower than the last
|
||||
// hit, then we've found a branch that doesn't include a match, which
|
||||
// means the match is not universal.
|
||||
if (universal && !isLower && Text.isText(node)) {
|
||||
return
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// If there's a match and it's lower than the last, update the hit.
|
||||
if (mode === 'lowest' && isLower) {
|
||||
hit = [node, path]
|
||||
continue
|
||||
}
|
||||
|
||||
// In lowest mode we emit the last hit, once it's guaranteed lowest.
|
||||
const emit: NodeEntry<T> | undefined =
|
||||
mode === 'lowest' ? hit : [node, path]
|
||||
|
||||
if (emit) {
|
||||
if (universal) {
|
||||
matches.push(emit)
|
||||
} else {
|
||||
yield emit
|
||||
}
|
||||
}
|
||||
|
||||
hit = [node, path]
|
||||
}
|
||||
|
||||
// Since lowest is always emitting one behind, catch up at the end.
|
||||
if (mode === 'lowest' && hit) {
|
||||
if (universal) {
|
||||
matches.push(hit)
|
||||
} else {
|
||||
yield hit
|
||||
}
|
||||
}
|
||||
|
||||
// Universal defers to ensure that the match occurs in every branch, so we
|
||||
// yield all of the matches after iterating.
|
||||
if (universal) {
|
||||
yield* matches
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the parent node of a location.
|
||||
*/
|
||||
|
||||
parent(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
} = {}
|
||||
): NodeEntry<Ancestor> {
|
||||
const path = Editor.path(editor, at, options)
|
||||
const parentPath = Path.parent(path)
|
||||
const entry = Editor.node(editor, parentPath)
|
||||
return entry as NodeEntry<Ancestor>
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the path of a location.
|
||||
*/
|
||||
|
||||
path(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
depth?: number
|
||||
edge?: 'start' | 'end'
|
||||
} = {}
|
||||
): Path {
|
||||
const { depth, edge } = options
|
||||
|
||||
if (Path.isPath(at)) {
|
||||
if (edge === 'start') {
|
||||
const [, firstPath] = Node.first(editor, at)
|
||||
at = firstPath
|
||||
} else if (edge === 'end') {
|
||||
const [, lastPath] = Node.last(editor, at)
|
||||
at = lastPath
|
||||
}
|
||||
}
|
||||
|
||||
if (Range.isRange(at)) {
|
||||
if (edge === 'start') {
|
||||
at = Range.start(at)
|
||||
} else if (edge === 'end') {
|
||||
at = Range.end(at)
|
||||
} else {
|
||||
at = Path.common(at.anchor.path, at.focus.path)
|
||||
}
|
||||
}
|
||||
|
||||
if (Point.isPoint(at)) {
|
||||
at = at.path
|
||||
}
|
||||
|
||||
if (depth != null) {
|
||||
at = at.slice(0, depth)
|
||||
}
|
||||
|
||||
return at
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the start or end point of a location.
|
||||
*/
|
||||
|
||||
point(
|
||||
editor: Editor,
|
||||
at: Location,
|
||||
options: {
|
||||
edge?: 'start' | 'end'
|
||||
} = {}
|
||||
): Point {
|
||||
const { edge = 'start' } = options
|
||||
|
||||
if (Path.isPath(at)) {
|
||||
let path
|
||||
|
||||
if (edge === 'end') {
|
||||
const [, lastPath] = Node.last(editor, at)
|
||||
path = lastPath
|
||||
} else {
|
||||
const [, firstPath] = Node.first(editor, at)
|
||||
path = firstPath
|
||||
}
|
||||
|
||||
const node = Node.get(editor, path)
|
||||
|
||||
if (!Text.isText(node)) {
|
||||
throw new Error(
|
||||
`Cannot get the ${edge} point in the node at path [${at}] because it has no ${edge} text node.`
|
||||
)
|
||||
}
|
||||
|
||||
return { path, offset: edge === 'end' ? node.text.length : 0 }
|
||||
}
|
||||
|
||||
if (Range.isRange(at)) {
|
||||
const [start, end] = Range.edges(at)
|
||||
return edge === 'start' ? start : end
|
||||
}
|
||||
|
||||
return at
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate through all of the positions in the document where a `Point` can be
|
||||
* placed.
|
||||
*
|
||||
* By default it will move forward by individual offsets at a time, but you
|
||||
* can pass the `unit: 'character'` option to moved forward one character, word,
|
||||
* or line at at time.
|
||||
*
|
||||
* Note: void nodes are treated as a single point, and iteration will not
|
||||
* happen inside their content.
|
||||
*/
|
||||
|
||||
*positions(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
unit?: 'offset' | 'character' | 'word' | 'line' | 'block'
|
||||
reverse?: boolean
|
||||
} = {}
|
||||
): Iterable<Point> {
|
||||
const { at = editor.selection, unit = 'offset', reverse = false } = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
const range = Editor.range(editor, at)
|
||||
const [start, end] = Range.edges(range)
|
||||
const first = reverse ? end : start
|
||||
let string = ''
|
||||
let available = 0
|
||||
let offset = 0
|
||||
let distance: number | null = null
|
||||
let isNewBlock = false
|
||||
|
||||
const advance = () => {
|
||||
if (distance == null) {
|
||||
if (unit === 'character') {
|
||||
distance = getCharacterDistance(string)
|
||||
} else if (unit === 'word') {
|
||||
distance = getWordDistance(string)
|
||||
} else if (unit === 'line' || unit === 'block') {
|
||||
distance = string.length
|
||||
} else {
|
||||
distance = 1
|
||||
}
|
||||
|
||||
string = string.slice(distance)
|
||||
}
|
||||
|
||||
// Add or substract the offset.
|
||||
offset = reverse ? offset - distance : offset + distance
|
||||
// Subtract the distance traveled from the available text.
|
||||
available = available - distance!
|
||||
// If the available had room to spare, reset the distance so that it will
|
||||
// advance again next time. Otherwise, set it to the overflow amount.
|
||||
distance = available >= 0 ? null : 0 - available
|
||||
}
|
||||
|
||||
for (const [node, path] of Editor.nodes(editor, { at, reverse })) {
|
||||
if (Element.isElement(node)) {
|
||||
// Void nodes are a special case, since we don't want to iterate over
|
||||
// their content. We instead always just yield their first point.
|
||||
if (editor.isVoid(node)) {
|
||||
yield Editor.start(editor, path)
|
||||
continue
|
||||
}
|
||||
|
||||
if (editor.isInline(node)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (Editor.hasInlines(editor, node)) {
|
||||
const e = Path.isAncestor(path, end.path)
|
||||
? end
|
||||
: Editor.end(editor, path)
|
||||
const s = Path.isAncestor(path, start.path)
|
||||
? start
|
||||
: Editor.start(editor, path)
|
||||
|
||||
const text = Editor.string(editor, { anchor: s, focus: e })
|
||||
string = reverse ? reverseText(text) : text
|
||||
isNewBlock = true
|
||||
}
|
||||
}
|
||||
|
||||
if (Text.isText(node)) {
|
||||
const isFirst = Path.equals(path, first.path)
|
||||
available = node.text.length
|
||||
offset = reverse ? available : 0
|
||||
|
||||
if (isFirst) {
|
||||
available = reverse ? first.offset : available - first.offset
|
||||
offset = first.offset
|
||||
}
|
||||
|
||||
if (isFirst || isNewBlock || unit === 'offset') {
|
||||
yield { path, offset }
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// If there's no more string, continue to the next block.
|
||||
if (string === '') {
|
||||
break
|
||||
} else {
|
||||
advance()
|
||||
}
|
||||
|
||||
// If the available space hasn't overflow, we have another point to
|
||||
// yield in the current text node.
|
||||
if (available >= 0) {
|
||||
yield { path, offset }
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
isNewBlock = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the matching node in the branch of the document before a location.
|
||||
*/
|
||||
|
||||
previous<T extends Node>(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: NodeMatch<T>
|
||||
mode?: 'all' | 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): NodeEntry<T> | undefined {
|
||||
const { mode = 'lowest', voids = false } = options
|
||||
let { match, at = editor.selection } = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
const [, from] = Editor.first(editor, at)
|
||||
const [, to] = Editor.first(editor, [])
|
||||
const span: Span = [from, to]
|
||||
|
||||
if (Path.isPath(at) && at.length === 0) {
|
||||
throw new Error(`Cannot get the previous node from the root node!`)
|
||||
}
|
||||
|
||||
if (match == null) {
|
||||
if (Path.isPath(at)) {
|
||||
const [parent] = Editor.parent(editor, at)
|
||||
match = n => parent.children.includes(n)
|
||||
} else {
|
||||
match = () => true
|
||||
}
|
||||
}
|
||||
|
||||
const [, previous] = Editor.nodes(editor, {
|
||||
reverse: true,
|
||||
at: span,
|
||||
match,
|
||||
mode,
|
||||
voids,
|
||||
})
|
||||
|
||||
return previous
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a range of a location.
|
||||
*/
|
||||
|
||||
range(editor: Editor, at: Location, to?: Location): Range {
|
||||
if (Range.isRange(at) && !to) {
|
||||
return at
|
||||
}
|
||||
|
||||
const start = Editor.start(editor, at)
|
||||
const end = Editor.end(editor, to || at)
|
||||
return { anchor: start, focus: end }
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the start point of a location.
|
||||
*/
|
||||
|
||||
start(editor: Editor, at: Location): Point {
|
||||
return Editor.point(editor, at, { edge: 'start' })
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the text string content of a location.
|
||||
*
|
||||
* Note: the text of void nodes is presumed to be an empty string, regardless
|
||||
* of what their actual content is.
|
||||
*/
|
||||
|
||||
string(editor: Editor, at: Location): string {
|
||||
const range = Editor.range(editor, at)
|
||||
const [start, end] = Range.edges(range)
|
||||
let text = ''
|
||||
|
||||
for (const [node, path] of Editor.nodes(editor, {
|
||||
at: range,
|
||||
match: Text.isText,
|
||||
})) {
|
||||
let t = node.text
|
||||
|
||||
if (Path.equals(path, end.path)) {
|
||||
t = t.slice(0, end.offset)
|
||||
}
|
||||
|
||||
if (Path.equals(path, start.path)) {
|
||||
t = t.slice(start.offset)
|
||||
}
|
||||
|
||||
text += t
|
||||
}
|
||||
|
||||
return text
|
||||
},
|
||||
|
||||
/**
|
||||
* Match a void node in the current branch of the editor.
|
||||
*/
|
||||
|
||||
void(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
mode?: 'highest' | 'lowest'
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): NodeEntry<Element> | undefined {
|
||||
return Editor.above(editor, {
|
||||
...options,
|
||||
match: n => Editor.isVoid(editor, n),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Constants for string distance checking.
|
||||
*/
|
||||
|
||||
const SPACE = /\s/
|
||||
const PUNCTUATION = /[\u0021-\u0023\u0025-\u002A\u002C-\u002F\u003A\u003B\u003F\u0040\u005B-\u005D\u005F\u007B\u007D\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]/
|
||||
const CHAMELEON = /['\u2018\u2019]/
|
||||
const SURROGATE_START = 0xd800
|
||||
const SURROGATE_END = 0xdfff
|
||||
const ZERO_WIDTH_JOINER = 0x200d
|
||||
|
||||
/**
|
||||
* Check if a character is a word character. The `remaining` argument is used
|
||||
* because sometimes you must read subsequent characters to truly determine it.
|
||||
*/
|
||||
|
||||
const isWordCharacter = (char: string, remaining: string): boolean => {
|
||||
if (SPACE.test(char)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Chameleons count as word characters as long as they're in a word, so
|
||||
// recurse to see if the next one is a word character or not.
|
||||
if (CHAMELEON.test(char)) {
|
||||
let next = remaining.charAt(0)
|
||||
const length = getCharacterDistance(next)
|
||||
next = remaining.slice(0, length)
|
||||
const rest = remaining.slice(length)
|
||||
|
||||
if (isWordCharacter(next, rest)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (PUNCTUATION.test(char)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance to the end of the first character in a string of text.
|
||||
*/
|
||||
|
||||
const getCharacterDistance = (text: string): number => {
|
||||
let offset = 0
|
||||
// prev types:
|
||||
// SURR: surrogate pair
|
||||
// MOD: modifier (technically also surrogate pair)
|
||||
// ZWJ: zero width joiner
|
||||
// VAR: variation selector
|
||||
// BMP: sequenceable character from basic multilingual plane
|
||||
let prev: 'SURR' | 'MOD' | 'ZWJ' | 'VAR' | 'BMP' | null = null
|
||||
let charCode = text.charCodeAt(0)
|
||||
|
||||
while (charCode) {
|
||||
if (isSurrogate(charCode)) {
|
||||
const modifier = isModifier(charCode, text, offset)
|
||||
|
||||
// Early returns are the heart of this function, where we decide if previous and current
|
||||
// codepoints should form a single character (in terms of how many of them should selection
|
||||
// jump over).
|
||||
if (prev === 'SURR' || prev === 'BMP') {
|
||||
break
|
||||
}
|
||||
|
||||
offset += 2
|
||||
prev = modifier ? 'MOD' : 'SURR'
|
||||
charCode = text.charCodeAt(offset)
|
||||
// Absolutely fine to `continue` without any checks because if `charCode` is NaN (which
|
||||
// is the case when out of `text` range), next `while` loop won"t execute and we"re done.
|
||||
continue
|
||||
}
|
||||
|
||||
if (charCode === ZERO_WIDTH_JOINER) {
|
||||
offset += 1
|
||||
prev = 'ZWJ'
|
||||
charCode = text.charCodeAt(offset)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (isBMPEmoji(charCode)) {
|
||||
if (prev && prev !== 'ZWJ' && prev !== 'VAR') {
|
||||
break
|
||||
}
|
||||
offset += 1
|
||||
prev = 'BMP'
|
||||
charCode = text.charCodeAt(offset)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (isVariationSelector(charCode)) {
|
||||
if (prev && prev !== 'ZWJ') {
|
||||
break
|
||||
}
|
||||
offset += 1
|
||||
prev = 'VAR'
|
||||
charCode = text.charCodeAt(offset)
|
||||
continue
|
||||
}
|
||||
|
||||
// Modifier 'groups up' with what ever character is before that (even whitespace), need to
|
||||
// look ahead.
|
||||
if (prev === 'MOD') {
|
||||
offset += 1
|
||||
break
|
||||
}
|
||||
|
||||
// If while loop ever gets here, we're done (e.g latin chars).
|
||||
break
|
||||
}
|
||||
|
||||
return offset || 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance to the end of the first word in a string of text.
|
||||
*/
|
||||
|
||||
const getWordDistance = (text: string): number => {
|
||||
let length = 0
|
||||
let i = 0
|
||||
let started = false
|
||||
let char
|
||||
|
||||
while ((char = text.charAt(i))) {
|
||||
const l = getCharacterDistance(char)
|
||||
char = text.slice(i, i + l)
|
||||
const rest = text.slice(i + l)
|
||||
|
||||
if (isWordCharacter(char, rest)) {
|
||||
started = true
|
||||
length += l
|
||||
} else if (!started) {
|
||||
length += l
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
i += l
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `code` is a surrogate
|
||||
*/
|
||||
|
||||
const isSurrogate = (code: number): boolean =>
|
||||
SURROGATE_START <= code && code <= SURROGATE_END
|
||||
|
||||
/**
|
||||
* Does `code` form Modifier with next one.
|
||||
*
|
||||
* https://emojipedia.org/modifiers/
|
||||
*/
|
||||
|
||||
const isModifier = (code: number, text: string, offset: number): boolean => {
|
||||
if (code === 0xd83c) {
|
||||
const next = text.charCodeAt(offset + 1)
|
||||
return next <= 0xdfff && next >= 0xdffb
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `code` a Variation Selector.
|
||||
*
|
||||
* https://codepoints.net/variation_selectors
|
||||
*/
|
||||
|
||||
const isVariationSelector = (code: number): boolean => {
|
||||
return code <= 0xfe0f && code >= 0xfe00
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `code` one of the BMP codes used in emoji sequences.
|
||||
*
|
||||
* https://emojipedia.org/emoji-zwj-sequences/
|
||||
*/
|
||||
|
||||
const isBMPEmoji = (code: number): boolean => {
|
||||
// This requires tiny bit of maintanance, better ideas?
|
||||
// Fortunately it only happens if new Unicode Standard
|
||||
// is released. Fails gracefully if upkeep lags behind,
|
||||
// same way Slate previously behaved with all emojis.
|
||||
return (
|
||||
code === 0x2764 || // heart (❤)
|
||||
code === 0x2642 || // male (♂)
|
||||
code === 0x2640 || // female (♀)
|
||||
code === 0x2620 || // scull (☠)
|
||||
code === 0x2695 || // medical (⚕)
|
||||
code === 0x2708 || // plane (✈️)
|
||||
code === 0x25ef // large circle (◯)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper type for narrowing matched nodes with a predicate.
|
||||
*/
|
||||
|
||||
type NodeMatch<T extends Node> =
|
||||
| ((node: Node) => node is T)
|
||||
| ((node: Node) => boolean)
|
@ -1,51 +0,0 @@
|
||||
import { Editor, Text, Path, Range } from '../../..'
|
||||
|
||||
export const RangeQueries = {
|
||||
/**
|
||||
* Convert a range into a non-hanging one.
|
||||
*/
|
||||
|
||||
unhangRange(
|
||||
editor: Editor,
|
||||
range: Range,
|
||||
options: {
|
||||
voids?: boolean
|
||||
} = {}
|
||||
): Range {
|
||||
const { voids = false } = options
|
||||
let [start, end] = Range.edges(range)
|
||||
|
||||
// PERF: exit early if we can guarantee that the range isn't hanging.
|
||||
if (start.offset !== 0 || end.offset !== 0 || Range.isCollapsed(range)) {
|
||||
return range
|
||||
}
|
||||
|
||||
const endBlock = Editor.above(editor, {
|
||||
at: end,
|
||||
match: n => Editor.isBlock(editor, n),
|
||||
})
|
||||
const blockPath = endBlock ? endBlock[1] : []
|
||||
const first = Editor.start(editor, [])
|
||||
const before = { anchor: first, focus: end }
|
||||
let skip = true
|
||||
|
||||
for (const [node, path] of Editor.nodes(editor, {
|
||||
at: before,
|
||||
match: Text.isText,
|
||||
reverse: true,
|
||||
voids,
|
||||
})) {
|
||||
if (skip) {
|
||||
skip = false
|
||||
continue
|
||||
}
|
||||
|
||||
if (node.text !== '' || Path.isBefore(path, blockPath)) {
|
||||
end = { path, offset: node.text.length }
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return { anchor: start, focus: end }
|
||||
},
|
||||
}
|
@ -10,55 +10,10 @@ import {
|
||||
Descendant,
|
||||
NodeEntry,
|
||||
Path,
|
||||
} from '../../..'
|
||||
|
||||
export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
|
||||
Transforms,
|
||||
} from '..'
|
||||
|
||||
export const GeneralTransforms = {
|
||||
/**
|
||||
* Normalize any dirty objects in the editor.
|
||||
*/
|
||||
|
||||
normalize(
|
||||
editor: Editor,
|
||||
options: {
|
||||
force?: boolean
|
||||
} = {}
|
||||
) {
|
||||
const { force = false } = options
|
||||
|
||||
if (!Editor.isNormalizing(editor)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (force) {
|
||||
const allPaths = Array.from(Node.nodes(editor), ([, p]) => p)
|
||||
DIRTY_PATHS.set(editor, allPaths)
|
||||
}
|
||||
|
||||
if (getDirtyPaths(editor).length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const max = getDirtyPaths(editor).length * 42 // HACK: better way?
|
||||
let m = 0
|
||||
|
||||
while (getDirtyPaths(editor).length !== 0) {
|
||||
if (m > max) {
|
||||
throw new Error(`
|
||||
Could not completely normalize the editor after ${max} iterations! This is usually due to incorrect normalization logic that leaves a node in an invalid state.
|
||||
`)
|
||||
}
|
||||
|
||||
const path = getDirtyPaths(editor).pop()!
|
||||
const entry = Editor.node(editor, path)
|
||||
editor.normalizeNode(entry)
|
||||
m++
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Transform the editor by an operation.
|
||||
*/
|
||||
@ -328,7 +283,3 @@ export const GeneralTransforms = {
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const getDirtyPaths = (editor: Editor) => {
|
||||
return DIRTY_PATHS.get(editor) || []
|
||||
}
|
11
packages/slate/src/transforms/index.ts
Normal file
11
packages/slate/src/transforms/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { GeneralTransforms } from './general'
|
||||
import { NodeTransforms } from './node'
|
||||
import { SelectionTransforms } from './selection'
|
||||
import { TextTransforms } from './text'
|
||||
|
||||
export const Transforms = {
|
||||
...GeneralTransforms,
|
||||
...NodeTransforms,
|
||||
...SelectionTransforms,
|
||||
...TextTransforms,
|
||||
}
|
@ -7,7 +7,8 @@ import {
|
||||
Point,
|
||||
Range,
|
||||
Text,
|
||||
} from '../../..'
|
||||
Transforms,
|
||||
} from '..'
|
||||
|
||||
export const NodeTransforms = {
|
||||
/**
|
||||
@ -69,7 +70,7 @@ export const NodeTransforms = {
|
||||
} else {
|
||||
const [, end] = Range.edges(at)
|
||||
const pointRef = Editor.pointRef(editor, end)
|
||||
Editor.delete(editor, { at })
|
||||
Transforms.delete(editor, { at })
|
||||
at = pointRef.unref()!
|
||||
}
|
||||
}
|
||||
@ -96,7 +97,7 @@ export const NodeTransforms = {
|
||||
const [, matchPath] = entry
|
||||
const pathRef = Editor.pathRef(editor, matchPath)
|
||||
const isAtEnd = Editor.isEnd(editor, at, matchPath)
|
||||
Editor.splitNodes(editor, { at, match, mode, voids })
|
||||
Transforms.splitNodes(editor, { at, match, mode, voids })
|
||||
const path = pathRef.unref()!
|
||||
at = isAtEnd ? Path.next(path) : path
|
||||
} else {
|
||||
@ -121,7 +122,7 @@ export const NodeTransforms = {
|
||||
const point = Editor.end(editor, at)
|
||||
|
||||
if (point) {
|
||||
Editor.select(editor, point)
|
||||
Transforms.select(editor, point)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -173,18 +174,18 @@ export const NodeTransforms = {
|
||||
|
||||
if (length === 1) {
|
||||
const toPath = Path.next(parentPath)
|
||||
Editor.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
Editor.removeNodes(editor, { at: parentPath, voids })
|
||||
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
Transforms.removeNodes(editor, { at: parentPath, voids })
|
||||
} else if (index === 0) {
|
||||
Editor.moveNodes(editor, { at: path, to: parentPath, voids })
|
||||
Transforms.moveNodes(editor, { at: path, to: parentPath, voids })
|
||||
} else if (index === length - 1) {
|
||||
const toPath = Path.next(parentPath)
|
||||
Editor.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
} else {
|
||||
const splitPath = Path.next(path)
|
||||
const toPath = Path.next(parentPath)
|
||||
Editor.splitNodes(editor, { at: splitPath, voids })
|
||||
Editor.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
Transforms.splitNodes(editor, { at: splitPath, voids })
|
||||
Transforms.moveNodes(editor, { at: path, to: toPath, voids })
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -232,11 +233,11 @@ export const NodeTransforms = {
|
||||
} else {
|
||||
const [, end] = Range.edges(at)
|
||||
const pointRef = Editor.pointRef(editor, end)
|
||||
Editor.delete(editor, { at })
|
||||
Transforms.delete(editor, { at })
|
||||
at = pointRef.unref()!
|
||||
|
||||
if (options.at == null) {
|
||||
Editor.select(editor, at)
|
||||
Transforms.select(editor, at)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -296,13 +297,13 @@ export const NodeTransforms = {
|
||||
// If the node isn't already the next sibling of the previous node, move
|
||||
// it so that it is before merging.
|
||||
if (!isPreviousSibling) {
|
||||
Editor.moveNodes(editor, { at: path, to: newPath, voids })
|
||||
Transforms.moveNodes(editor, { at: path, to: newPath, voids })
|
||||
}
|
||||
|
||||
// If there was going to be an empty ancestor of the node that was merged,
|
||||
// we remove it from the tree.
|
||||
if (emptyRef) {
|
||||
Editor.removeNodes(editor, { at: emptyRef.current!, voids })
|
||||
Transforms.removeNodes(editor, { at: emptyRef.current!, voids })
|
||||
}
|
||||
|
||||
// If the target node that we're merging with is empty, remove it instead
|
||||
@ -313,7 +314,7 @@ export const NodeTransforms = {
|
||||
(Element.isElement(prevNode) && Editor.isEmpty(editor, prevNode)) ||
|
||||
(Text.isText(prevNode) && prevNode.text === '')
|
||||
) {
|
||||
Editor.removeNodes(editor, { at: prevPath, voids })
|
||||
Transforms.removeNodes(editor, { at: prevPath, voids })
|
||||
} else {
|
||||
editor.apply({
|
||||
type: 'merge_node',
|
||||
@ -469,12 +470,22 @@ export const NodeTransforms = {
|
||||
const rangeRef = Editor.rangeRef(editor, at, { affinity: 'inward' })
|
||||
const [start, end] = Range.edges(at)
|
||||
const splitMode = mode === 'lowest' ? 'lowest' : 'highest'
|
||||
Editor.splitNodes(editor, { at: end, match, mode: splitMode, voids })
|
||||
Editor.splitNodes(editor, { at: start, match, mode: splitMode, voids })
|
||||
Transforms.splitNodes(editor, {
|
||||
at: end,
|
||||
match,
|
||||
mode: splitMode,
|
||||
voids,
|
||||
})
|
||||
Transforms.splitNodes(editor, {
|
||||
at: start,
|
||||
match,
|
||||
mode: splitMode,
|
||||
voids,
|
||||
})
|
||||
at = rangeRef.unref()!
|
||||
|
||||
if (options.at == null) {
|
||||
Editor.select(editor, at)
|
||||
Transforms.select(editor, at)
|
||||
}
|
||||
}
|
||||
|
||||
@ -579,7 +590,7 @@ export const NodeTransforms = {
|
||||
if (!after) {
|
||||
const text = { text: '' }
|
||||
const afterPath = Path.next(voidPath)
|
||||
Editor.insertNodes(editor, text, { at: afterPath, voids })
|
||||
Transforms.insertNodes(editor, text, { at: afterPath, voids })
|
||||
after = Editor.point(editor, afterPath)!
|
||||
}
|
||||
|
||||
@ -635,7 +646,7 @@ export const NodeTransforms = {
|
||||
|
||||
if (options.at == null) {
|
||||
const point = afterRef.current || Editor.end(editor, [])
|
||||
Editor.select(editor, point)
|
||||
Transforms.select(editor, point)
|
||||
}
|
||||
|
||||
beforeRef.unref()
|
||||
@ -668,7 +679,7 @@ export const NodeTransforms = {
|
||||
obj[key] = null
|
||||
}
|
||||
|
||||
Editor.setNodes(editor, obj, options)
|
||||
Transforms.setNodes(editor, obj, options)
|
||||
},
|
||||
|
||||
/**
|
||||
@ -717,7 +728,7 @@ export const NodeTransforms = {
|
||||
range = Range.intersection(rangeRef.current!, range)!
|
||||
}
|
||||
|
||||
Editor.liftNodes(editor, {
|
||||
Transforms.liftNodes(editor, {
|
||||
at: range,
|
||||
match: n => node.children.includes(n),
|
||||
voids,
|
||||
@ -769,12 +780,12 @@ export const NodeTransforms = {
|
||||
const rangeRef = Editor.rangeRef(editor, at, {
|
||||
affinity: 'inward',
|
||||
})
|
||||
Editor.splitNodes(editor, { at: end, match, voids })
|
||||
Editor.splitNodes(editor, { at: start, match, voids })
|
||||
Transforms.splitNodes(editor, { at: end, match, voids })
|
||||
Transforms.splitNodes(editor, { at: start, match, voids })
|
||||
at = rangeRef.unref()!
|
||||
|
||||
if (options.at == null) {
|
||||
Editor.select(editor, at)
|
||||
Transforms.select(editor, at)
|
||||
}
|
||||
}
|
||||
|
||||
@ -816,9 +827,9 @@ export const NodeTransforms = {
|
||||
const depth = commonPath.length + 1
|
||||
const wrapperPath = Path.next(lastPath.slice(0, depth))
|
||||
const wrapper = { ...element, children: [] }
|
||||
Editor.insertNodes(editor, wrapper, { at: wrapperPath, voids })
|
||||
Transforms.insertNodes(editor, wrapper, { at: wrapperPath, voids })
|
||||
|
||||
Editor.moveNodes(editor, {
|
||||
Transforms.moveNodes(editor, {
|
||||
at: range,
|
||||
match: n => commonNode.children.includes(n),
|
||||
to: wrapperPath.concat(0),
|
||||
@ -840,7 +851,7 @@ const deleteRange = (editor: Editor, range: Range): Point | null => {
|
||||
} else {
|
||||
const [, end] = Range.edges(range)
|
||||
const pointRef = Editor.pointRef(editor, end)
|
||||
Editor.delete(editor, { at: range })
|
||||
Transforms.delete(editor, { at: range })
|
||||
return pointRef.unref()
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Editor, Location, Point, Range } from '../../..'
|
||||
import { Editor, Location, Point, Range, Transforms } from '..'
|
||||
|
||||
export const SelectionTransforms = {
|
||||
/**
|
||||
@ -17,15 +17,15 @@ export const SelectionTransforms = {
|
||||
if (!selection) {
|
||||
return
|
||||
} else if (edge === 'anchor') {
|
||||
Editor.select(editor, selection.anchor)
|
||||
Transforms.select(editor, selection.anchor)
|
||||
} else if (edge === 'focus') {
|
||||
Editor.select(editor, selection.focus)
|
||||
Transforms.select(editor, selection.focus)
|
||||
} else if (edge === 'start') {
|
||||
const [start] = Range.edges(selection)
|
||||
Editor.select(editor, start)
|
||||
Transforms.select(editor, start)
|
||||
} else if (edge === 'end') {
|
||||
const [, end] = Range.edges(selection)
|
||||
Editor.select(editor, end)
|
||||
Transforms.select(editor, end)
|
||||
}
|
||||
},
|
||||
|
||||
@ -98,7 +98,7 @@ export const SelectionTransforms = {
|
||||
}
|
||||
}
|
||||
|
||||
Editor.setSelection(editor, props)
|
||||
Transforms.setSelection(editor, props)
|
||||
},
|
||||
|
||||
/**
|
||||
@ -110,7 +110,7 @@ export const SelectionTransforms = {
|
||||
target = Editor.range(editor, target)
|
||||
|
||||
if (selection) {
|
||||
Editor.setSelection(editor, target)
|
||||
Transforms.setSelection(editor, target)
|
||||
return
|
||||
}
|
||||
|
||||
@ -160,9 +160,9 @@ export const SelectionTransforms = {
|
||||
const newPoint = Object.assign(point, props)
|
||||
|
||||
if (edge === 'anchor') {
|
||||
Editor.setSelection(editor, { anchor: newPoint })
|
||||
Transforms.setSelection(editor, { anchor: newPoint })
|
||||
} else {
|
||||
Editor.setSelection(editor, { focus: newPoint })
|
||||
Transforms.setSelection(editor, { focus: newPoint })
|
||||
}
|
||||
},
|
||||
|
@ -8,7 +8,8 @@ import {
|
||||
Text,
|
||||
Point,
|
||||
Range,
|
||||
} from '../../..'
|
||||
Transforms,
|
||||
} from '..'
|
||||
|
||||
export const TextTransforms = {
|
||||
/**
|
||||
@ -60,7 +61,7 @@ export const TextTransforms = {
|
||||
}
|
||||
|
||||
if (Path.isPath(at)) {
|
||||
Editor.removeNodes(editor, { at, voids })
|
||||
Transforms.removeNodes(editor, { at, voids })
|
||||
return
|
||||
}
|
||||
|
||||
@ -150,7 +151,7 @@ export const TextTransforms = {
|
||||
|
||||
for (const pathRef of pathRefs) {
|
||||
const path = pathRef.unref()!
|
||||
Editor.removeNodes(editor, { at: path, voids })
|
||||
Transforms.removeNodes(editor, { at: path, voids })
|
||||
}
|
||||
|
||||
if (!endVoid) {
|
||||
@ -168,7 +169,7 @@ export const TextTransforms = {
|
||||
endRef.current &&
|
||||
startRef.current
|
||||
) {
|
||||
Editor.mergeNodes(editor, {
|
||||
Transforms.mergeNodes(editor, {
|
||||
at: endRef.current,
|
||||
hanging: true,
|
||||
voids,
|
||||
@ -178,7 +179,7 @@ export const TextTransforms = {
|
||||
const point = endRef.unref() || startRef.unref()
|
||||
|
||||
if (options.at == null && point) {
|
||||
Editor.select(editor, point)
|
||||
Transforms.select(editor, point)
|
||||
}
|
||||
})
|
||||
},
|
||||
@ -221,7 +222,7 @@ export const TextTransforms = {
|
||||
}
|
||||
|
||||
const pointRef = Editor.pointRef(editor, end)
|
||||
Editor.delete(editor, { at })
|
||||
Transforms.delete(editor, { at })
|
||||
at = pointRef.unref()!
|
||||
}
|
||||
} else if (Path.isPath(at)) {
|
||||
@ -339,7 +340,7 @@ export const TextTransforms = {
|
||||
isInlineEnd ? Path.next(inlinePath) : inlinePath
|
||||
)
|
||||
|
||||
Editor.splitNodes(editor, {
|
||||
Transforms.splitNodes(editor, {
|
||||
at,
|
||||
match: n =>
|
||||
hasBlocks
|
||||
@ -356,21 +357,21 @@ export const TextTransforms = {
|
||||
: inlinePath
|
||||
)
|
||||
|
||||
Editor.insertNodes(editor, starts, {
|
||||
Transforms.insertNodes(editor, starts, {
|
||||
at: startRef.current!,
|
||||
match: n => Text.isText(n) || Editor.isInline(editor, n),
|
||||
mode: 'highest',
|
||||
voids,
|
||||
})
|
||||
|
||||
Editor.insertNodes(editor, middles, {
|
||||
Transforms.insertNodes(editor, middles, {
|
||||
at: middleRef.current!,
|
||||
match: n => Editor.isBlock(editor, n),
|
||||
mode: 'lowest',
|
||||
voids,
|
||||
})
|
||||
|
||||
Editor.insertNodes(editor, ends, {
|
||||
Transforms.insertNodes(editor, ends, {
|
||||
at: endRef.current!,
|
||||
match: n => Text.isText(n) || Editor.isInline(editor, n),
|
||||
mode: 'highest',
|
||||
@ -389,7 +390,7 @@ export const TextTransforms = {
|
||||
}
|
||||
|
||||
const end = Editor.end(editor, path)
|
||||
Editor.select(editor, end)
|
||||
Transforms.select(editor, end)
|
||||
}
|
||||
|
||||
startRef.unref()
|
||||
@ -433,9 +434,9 @@ export const TextTransforms = {
|
||||
}
|
||||
|
||||
const pointRef = Editor.pointRef(editor, end)
|
||||
Editor.delete(editor, { at, voids })
|
||||
Transforms.delete(editor, { at, voids })
|
||||
at = pointRef.unref()!
|
||||
Editor.setSelection(editor, { anchor: at, focus: at })
|
||||
Transforms.setSelection(editor, { anchor: at, focus: at })
|
||||
}
|
||||
}
|
||||
|
200
packages/slate/src/utils/string.ts
Normal file
200
packages/slate/src/utils/string.ts
Normal file
@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Constants for string distance checking.
|
||||
*/
|
||||
|
||||
const SPACE = /\s/
|
||||
const PUNCTUATION = /[\u0021-\u0023\u0025-\u002A\u002C-\u002F\u003A\u003B\u003F\u0040\u005B-\u005D\u005F\u007B\u007D\u00A1\u00A7\u00AB\u00B6\u00B7\u00BB\u00BF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E3B\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]/
|
||||
const CHAMELEON = /['\u2018\u2019]/
|
||||
const SURROGATE_START = 0xd800
|
||||
const SURROGATE_END = 0xdfff
|
||||
const ZERO_WIDTH_JOINER = 0x200d
|
||||
|
||||
/**
|
||||
* Get the distance to the end of the first character in a string of text.
|
||||
*/
|
||||
|
||||
export const getCharacterDistance = (text: string): number => {
|
||||
let offset = 0
|
||||
// prev types:
|
||||
// SURR: surrogate pair
|
||||
// MOD: modifier (technically also surrogate pair)
|
||||
// ZWJ: zero width joiner
|
||||
// VAR: variation selector
|
||||
// BMP: sequenceable character from basic multilingual plane
|
||||
let prev: 'SURR' | 'MOD' | 'ZWJ' | 'VAR' | 'BMP' | null = null
|
||||
let charCode = text.charCodeAt(0)
|
||||
|
||||
while (charCode) {
|
||||
if (isSurrogate(charCode)) {
|
||||
const modifier = isModifier(charCode, text, offset)
|
||||
|
||||
// Early returns are the heart of this function, where we decide if previous and current
|
||||
// codepoints should form a single character (in terms of how many of them should selection
|
||||
// jump over).
|
||||
if (prev === 'SURR' || prev === 'BMP') {
|
||||
break
|
||||
}
|
||||
|
||||
offset += 2
|
||||
prev = modifier ? 'MOD' : 'SURR'
|
||||
charCode = text.charCodeAt(offset)
|
||||
// Absolutely fine to `continue` without any checks because if `charCode` is NaN (which
|
||||
// is the case when out of `text` range), next `while` loop won"t execute and we"re done.
|
||||
continue
|
||||
}
|
||||
|
||||
if (charCode === ZERO_WIDTH_JOINER) {
|
||||
offset += 1
|
||||
prev = 'ZWJ'
|
||||
charCode = text.charCodeAt(offset)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (isBMPEmoji(charCode)) {
|
||||
if (prev && prev !== 'ZWJ' && prev !== 'VAR') {
|
||||
break
|
||||
}
|
||||
offset += 1
|
||||
prev = 'BMP'
|
||||
charCode = text.charCodeAt(offset)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (isVariationSelector(charCode)) {
|
||||
if (prev && prev !== 'ZWJ') {
|
||||
break
|
||||
}
|
||||
offset += 1
|
||||
prev = 'VAR'
|
||||
charCode = text.charCodeAt(offset)
|
||||
continue
|
||||
}
|
||||
|
||||
// Modifier 'groups up' with what ever character is before that (even whitespace), need to
|
||||
// look ahead.
|
||||
if (prev === 'MOD') {
|
||||
offset += 1
|
||||
break
|
||||
}
|
||||
|
||||
// If while loop ever gets here, we're done (e.g latin chars).
|
||||
break
|
||||
}
|
||||
|
||||
return offset || 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the distance to the end of the first word in a string of text.
|
||||
*/
|
||||
|
||||
export const getWordDistance = (text: string): number => {
|
||||
let length = 0
|
||||
let i = 0
|
||||
let started = false
|
||||
let char
|
||||
|
||||
while ((char = text.charAt(i))) {
|
||||
const l = getCharacterDistance(char)
|
||||
char = text.slice(i, i + l)
|
||||
const rest = text.slice(i + l)
|
||||
|
||||
if (isWordCharacter(char, rest)) {
|
||||
started = true
|
||||
length += l
|
||||
} else if (!started) {
|
||||
length += l
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
i += l
|
||||
}
|
||||
|
||||
return length
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a character is a word character. The `remaining` argument is used
|
||||
* because sometimes you must read subsequent characters to truly determine it.
|
||||
*/
|
||||
|
||||
const isWordCharacter = (char: string, remaining: string): boolean => {
|
||||
if (SPACE.test(char)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Chameleons count as word characters as long as they're in a word, so
|
||||
// recurse to see if the next one is a word character or not.
|
||||
if (CHAMELEON.test(char)) {
|
||||
let next = remaining.charAt(0)
|
||||
const length = getCharacterDistance(next)
|
||||
next = remaining.slice(0, length)
|
||||
const rest = remaining.slice(length)
|
||||
|
||||
if (isWordCharacter(next, rest)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if (PUNCTUATION.test(char)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `code` is a surrogate
|
||||
*/
|
||||
|
||||
const isSurrogate = (code: number): boolean =>
|
||||
SURROGATE_START <= code && code <= SURROGATE_END
|
||||
|
||||
/**
|
||||
* Does `code` form Modifier with next one.
|
||||
*
|
||||
* https://emojipedia.org/modifiers/
|
||||
*/
|
||||
|
||||
const isModifier = (code: number, text: string, offset: number): boolean => {
|
||||
if (code === 0xd83c) {
|
||||
const next = text.charCodeAt(offset + 1)
|
||||
return next <= 0xdfff && next >= 0xdffb
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `code` a Variation Selector.
|
||||
*
|
||||
* https://codepoints.net/variation_selectors
|
||||
*/
|
||||
|
||||
const isVariationSelector = (code: number): boolean => {
|
||||
return code <= 0xfe0f && code >= 0xfe00
|
||||
}
|
||||
|
||||
/**
|
||||
* Is `code` one of the BMP codes used in emoji sequences.
|
||||
*
|
||||
* https://emojipedia.org/emoji-zwj-sequences/
|
||||
*/
|
||||
|
||||
const isBMPEmoji = (code: number): boolean => {
|
||||
// This requires tiny bit of maintanance, better ideas?
|
||||
// Fortunately it only happens if new Unicode Standard
|
||||
// is released. Fails gracefully if upkeep lags behind,
|
||||
// same way Slate previously behaved with all emojis.
|
||||
return (
|
||||
code === 0x2764 || // heart (❤)
|
||||
code === 0x2642 || // male (♂)
|
||||
code === 0x2640 || // female (♀)
|
||||
code === 0x2620 || // scull (☠)
|
||||
code === 0x2695 || // medical (⚕)
|
||||
code === 0x2708 || // plane (✈️)
|
||||
code === 0x25ef // large circle (◯)
|
||||
)
|
||||
}
|
8
packages/slate/src/utils/weak-maps.ts
Normal file
8
packages/slate/src/utils/weak-maps.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Editor, Path, PathRef, PointRef, RangeRef } from '..'
|
||||
|
||||
export const DIRTY_PATHS: WeakMap<Editor, Path[]> = new WeakMap()
|
||||
export const FLUSHING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const NORMALIZING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const PATH_REFS: WeakMap<Editor, Set<PathRef>> = new WeakMap()
|
||||
export const POINT_REFS: WeakMap<Editor, Set<PointRef>> = new WeakMap()
|
||||
export const RANGE_REFS: WeakMap<Editor, Set<RangeRef>> = new WeakMap()
|
@ -5,7 +5,12 @@ import { createHyperscript } from 'slate-hyperscript'
|
||||
|
||||
describe('slate', () => {
|
||||
fixtures(__dirname, 'interfaces', ({ module }) => {
|
||||
const { input, test, output } = module
|
||||
let { input, test, output } = module
|
||||
|
||||
if (Editor.isEditor(input)) {
|
||||
input = withTest(input)
|
||||
}
|
||||
|
||||
const result = test(input)
|
||||
assert.deepEqual(result, output)
|
||||
})
|
||||
@ -32,13 +37,6 @@ describe('slate', () => {
|
||||
assert.deepEqual(editor.selection, output.selection)
|
||||
})
|
||||
|
||||
fixtures(__dirname, 'queries', ({ module }) => {
|
||||
const { input, run, output } = module
|
||||
const editor = withTest(input)
|
||||
const result = run(editor)
|
||||
assert.deepEqual(result, output)
|
||||
})
|
||||
|
||||
fixtures(__dirname, 'transforms', ({ module }) => {
|
||||
const { input, run, output } = module
|
||||
const editor = withTest(input)
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.above(editor, {
|
||||
at: [0, 0, 0],
|
||||
match: n => Editor.isBlock(editor, n),
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.above(editor, {
|
||||
at: [0, 0, 0],
|
||||
match: n => Editor.isBlock(editor, n),
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.above(editor, {
|
||||
at: [0, 1, 0],
|
||||
match: n => Editor.isInline(editor, n),
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.after(editor, [1, 0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.after(editor, [0, 0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.after(editor, { path: [0, 0], offset: 1 })
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.after(editor, {
|
||||
anchor: { path: [0, 0], offset: 1 },
|
||||
focus: { path: [1, 0], offset: 2 },
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.before(editor, [1, 0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.before(editor, { path: [0, 0], offset: 1 })
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.before(editor, {
|
||||
anchor: { path: [0, 0], offset: 1 },
|
||||
focus: { path: [0, 1], offset: 2 },
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -10,7 +10,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.before(editor, [0, 0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.edges(editor, [0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.edges(editor, { path: [0, 0], offset: 1 })
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.edges(editor, {
|
||||
anchor: { path: [0, 0], offset: 1 },
|
||||
focus: { path: [0, 0], offset: 3 },
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.end(editor, [0])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.end(editor, { path: [0, 0], offset: 1 })
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
return Editor.end(editor, {
|
||||
anchor: { path: [0, 0], offset: 1 },
|
||||
focus: { path: [0, 0], offset: 2 },
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasBlocks(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasBlocks(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -15,7 +15,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.hasBlocks(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasBlocks(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasInlines(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasInlines(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -15,7 +15,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.hasInlines(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasInlines(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasTexts(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.hasTexts(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -15,7 +15,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.hasTexts(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.hasTexts(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isBlock(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isBlock(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEdge(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEdge(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEdge(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isEmpty(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isEmpty(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isEmpty(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isEmpty(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -15,7 +15,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isEmpty(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -13,7 +13,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isEmpty(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isEmpty(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -15,7 +15,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isEmpty(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEnd(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEnd(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isEnd(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isInline(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isInline(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isStart(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isStart(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -12,7 +12,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const { anchor } = editor.selection
|
||||
return Editor.isStart(editor, anchor, [0])
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isVoid(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -9,7 +9,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const block = editor.children[0]
|
||||
return Editor.isVoid(editor, block)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isVoid(editor, inline)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
import { jsx } from '../../..'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
@ -11,7 +11,7 @@ export const input = (
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const run = editor => {
|
||||
export const test = editor => {
|
||||
const inline = editor.children[0].children[1]
|
||||
return Editor.isVoid(editor, inline)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user