mirror of
https://github.com/ianstormtaylor/slate.git
synced 2025-08-31 02:49:56 +02:00
remove marks, in favor of text properties (#3235)
* remove marks, in favor of text properties * fix lint * fix more examples * update docs
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
o<anchor />
|
||||
ne
|
||||
</block>
|
||||
<block>
|
||||
tw
|
||||
<focus />o
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -1,20 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="b">
|
||||
w<anchor />o
|
||||
</mark>
|
||||
r<focus />d
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -1,20 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="a">
|
||||
w<anchor />o
|
||||
</mark>
|
||||
r<focus />d
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -1,20 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'add_mark', mark: { key: 'a' } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<anchor />
|
||||
wo
|
||||
<focus />
|
||||
rd
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -1,27 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Editor } from 'slate'
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.delete()
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key="a">
|
||||
on
|
||||
<anchor />e
|
||||
</mark>
|
||||
<mark key="c">
|
||||
tw
|
||||
<focus />o
|
||||
</mark>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
||||
|
||||
export const skip = true
|
@@ -1,21 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from '../..'
|
||||
|
||||
export const run = editor => {
|
||||
editor.exec({ type: 'remove_mark', mark: { key: true } })
|
||||
}
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<block>
|
||||
<mark key>
|
||||
<anchor />
|
||||
one
|
||||
<focus />
|
||||
</mark>
|
||||
</block>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -1,9 +1,7 @@
|
||||
import {
|
||||
Element,
|
||||
Descendant,
|
||||
Mark,
|
||||
Node,
|
||||
Path,
|
||||
Range,
|
||||
Text,
|
||||
Editor,
|
||||
@@ -37,7 +35,7 @@ const resolveDescendants = (children: any[]): Descendant[] => {
|
||||
const prev = nodes[nodes.length - 1]
|
||||
|
||||
if (typeof child === 'string') {
|
||||
const text = { text: child, marks: [] }
|
||||
const text = { text: child }
|
||||
STRINGS.add(text)
|
||||
child = text
|
||||
}
|
||||
@@ -49,8 +47,7 @@ const resolveDescendants = (children: any[]): Descendant[] => {
|
||||
Text.isText(prev) &&
|
||||
STRINGS.has(prev) &&
|
||||
STRINGS.has(c) &&
|
||||
c.marks.every(m => Mark.exists(m, prev.marks)) &&
|
||||
prev.marks.every(m => Mark.exists(m, c.marks))
|
||||
Text.equals(prev, c, { loose: true })
|
||||
) {
|
||||
prev.text += c.text
|
||||
} else {
|
||||
@@ -143,43 +140,6 @@ export function createFragment(
|
||||
return resolveDescendants(children)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Text` object with a mark applied.
|
||||
*/
|
||||
|
||||
export function createMark(
|
||||
tagName: string,
|
||||
attributes: { [key: string]: any },
|
||||
children: any[]
|
||||
): Text {
|
||||
const mark = { ...attributes }
|
||||
const nodes = resolveDescendants(children)
|
||||
|
||||
if (nodes.length > 1) {
|
||||
throw new Error(
|
||||
`The <mark> hyperscript tag must only contain a single node's worth of children.`
|
||||
)
|
||||
}
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return { text: '', marks: [mark] }
|
||||
}
|
||||
|
||||
const [node] = nodes
|
||||
|
||||
if (!Text.isText(node)) {
|
||||
throw new Error(
|
||||
`The <mark> hyperscript tag must only contain text content as children.`
|
||||
)
|
||||
}
|
||||
|
||||
if (!Mark.exists(mark, node.marks)) {
|
||||
node.marks.push(mark)
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `Selection` object.
|
||||
*/
|
||||
@@ -237,7 +197,7 @@ export function createText(
|
||||
let [node] = nodes
|
||||
|
||||
if (node == null) {
|
||||
node = { text: '', marks: [] }
|
||||
node = { text: '' }
|
||||
}
|
||||
|
||||
if (!Text.isText(node)) {
|
||||
@@ -245,8 +205,8 @@ export function createText(
|
||||
The <text> hyperscript tag can only contain text content as children.`)
|
||||
}
|
||||
|
||||
// COMPAT: Re-create the node, because if they used the <text> tag we want to
|
||||
// guarantee that it won't be merge with other string children.
|
||||
// COMPAT: If they used the <text> tag we want to guarantee that it won't be
|
||||
// merge with other string children.
|
||||
STRINGS.delete(node)
|
||||
|
||||
Object.assign(node, attributes)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Element, Mark } from 'slate'
|
||||
import { Element } from 'slate'
|
||||
import {
|
||||
createAnchor,
|
||||
createCursor,
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
createElement,
|
||||
createFocus,
|
||||
createFragment,
|
||||
createMark,
|
||||
createSelection,
|
||||
createText,
|
||||
} from './creators'
|
||||
@@ -23,7 +22,6 @@ const DEFAULT_CREATORS = {
|
||||
element: createElement,
|
||||
focus: createFocus,
|
||||
fragment: createFragment,
|
||||
mark: createMark,
|
||||
selection: createSelection,
|
||||
text: createText,
|
||||
}
|
||||
@@ -54,16 +52,13 @@ const createHyperscript = (
|
||||
options: {
|
||||
creators?: HyperscriptCreators
|
||||
elements?: HyperscriptShorthands
|
||||
marks?: HyperscriptShorthands
|
||||
} = {}
|
||||
) => {
|
||||
const { elements = {}, marks = {} } = options
|
||||
const { elements = {} } = options
|
||||
const elementCreators = normalizeElements(elements)
|
||||
const markCreators = normalizeMarks(marks)
|
||||
const creators = {
|
||||
...DEFAULT_CREATORS,
|
||||
...elementCreators,
|
||||
...markCreators,
|
||||
...options.creators,
|
||||
}
|
||||
|
||||
@@ -132,32 +127,4 @@ const normalizeElements = (elements: HyperscriptShorthands) => {
|
||||
return creators
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a dictionary of mark shorthands into creator functions.
|
||||
*/
|
||||
|
||||
const normalizeMarks = (marks: HyperscriptShorthands) => {
|
||||
const creators: HyperscriptCreators<Mark> = {}
|
||||
|
||||
for (const tagName in marks) {
|
||||
const props = marks[tagName]
|
||||
|
||||
if (typeof props !== 'object') {
|
||||
throw new Error(
|
||||
`Properties specified for a hyperscript shorthand should be an object, but for the custom mark <${tagName}> tag you passed: ${props}`
|
||||
)
|
||||
}
|
||||
|
||||
creators[tagName] = (
|
||||
tagName: string,
|
||||
attributes: { [key: string]: any },
|
||||
children: any[]
|
||||
) => {
|
||||
return createMark('mark', { ...props, ...attributes }, children)
|
||||
}
|
||||
}
|
||||
|
||||
return creators
|
||||
}
|
||||
|
||||
export { createHyperscript, HyperscriptCreators, HyperscriptShorthands }
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Mark, Node, Path, Text } from 'slate'
|
||||
import { Node, Path, Text } from 'slate'
|
||||
|
||||
/**
|
||||
* A weak map to hold anchor tokens.
|
||||
@@ -23,23 +23,17 @@ export class Token {}
|
||||
*/
|
||||
|
||||
export class AnchorToken extends Token {
|
||||
focused: boolean
|
||||
marks: Mark[] | null
|
||||
offset?: number
|
||||
path?: Path
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
focused?: boolean
|
||||
marks?: Mark[] | null
|
||||
offset?: number
|
||||
path?: Path
|
||||
} = {}
|
||||
) {
|
||||
super()
|
||||
const { focused = true, marks = null, offset, path } = props
|
||||
this.focused = focused
|
||||
this.marks = marks
|
||||
const { offset, path } = props
|
||||
this.offset = offset
|
||||
this.path = path
|
||||
}
|
||||
@@ -50,23 +44,17 @@ export class AnchorToken extends Token {
|
||||
*/
|
||||
|
||||
export class FocusToken extends Token {
|
||||
focused: boolean
|
||||
marks: Mark[] | null
|
||||
offset?: number
|
||||
path?: Path
|
||||
|
||||
constructor(
|
||||
props: {
|
||||
focused?: boolean
|
||||
marks?: Mark[] | null
|
||||
offset?: number
|
||||
path?: Path
|
||||
} = {}
|
||||
) {
|
||||
super()
|
||||
const { focused = true, marks = null, offset, path } = props
|
||||
this.focused = focused
|
||||
this.marks = marks
|
||||
const { offset, path } = props
|
||||
this.offset = offset
|
||||
this.path = path
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -23,7 +23,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -31,7 +30,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -21,7 +21,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -29,7 +28,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -21,7 +21,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -29,7 +28,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -21,7 +21,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -29,7 +28,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -16,7 +16,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -17,7 +17,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -17,7 +17,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -21,7 +21,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -22,7 +22,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -21,7 +21,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -17,7 +17,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -1,34 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<cursor focused={false} />
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
}
|
@@ -1,40 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark>one</mark>
|
||||
<cursor />
|
||||
two
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [{}],
|
||||
},
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 3,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 3,
|
||||
},
|
||||
},
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark>
|
||||
one
|
||||
<cursor />
|
||||
</mark>
|
||||
two
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [{}],
|
||||
},
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 3,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 3,
|
||||
},
|
||||
},
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark>
|
||||
o<cursor />
|
||||
ne
|
||||
</mark>
|
||||
two
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [{}],
|
||||
},
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 1,
|
||||
},
|
||||
},
|
||||
}
|
@@ -1,42 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark>
|
||||
<cursor />
|
||||
one
|
||||
</mark>
|
||||
two
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: 'one',
|
||||
marks: [{}],
|
||||
},
|
||||
{
|
||||
text: 'two',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
}
|
@@ -1,34 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<cursor marks={[]} />
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
children: [
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
selection: {
|
||||
anchor: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
focus: {
|
||||
path: [0, 0],
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
}
|
@@ -18,7 +18,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -15,7 +15,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -8,7 +8,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -12,7 +12,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ export const output = [
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -7,6 +7,5 @@ export const input = <fragment>word</fragment>
|
||||
export const output = [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
@@ -1,16 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { createHyperscript } from 'slate-hyperscript'
|
||||
|
||||
const jsx = createHyperscript({
|
||||
marks: {
|
||||
b: { type: 'bold' },
|
||||
},
|
||||
})
|
||||
|
||||
export const input = <b>word</b>
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [{ type: 'bold' }],
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<mark type="a">
|
||||
<mark type="b">word</mark>
|
||||
</mark>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [{ type: 'b' }, { type: 'a' }],
|
||||
}
|
@@ -1,10 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = <mark>word</mark>
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [{}],
|
||||
}
|
@@ -1,14 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<mark>
|
||||
<text>word</text>
|
||||
</mark>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [{}],
|
||||
}
|
@@ -18,7 +18,6 @@ export const output = {
|
||||
children: [
|
||||
{
|
||||
text: 'word',
|
||||
marks: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@@ -2,9 +2,9 @@
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = <text />
|
||||
export const input = <text a />
|
||||
|
||||
export const output = {
|
||||
text: '',
|
||||
marks: [],
|
||||
a: true,
|
||||
}
|
||||
|
@@ -2,9 +2,9 @@
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = <text>word</text>
|
||||
export const input = <text a>word</text>
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [],
|
||||
a: true,
|
||||
}
|
||||
|
@@ -3,12 +3,13 @@
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<text>
|
||||
<text>word</text>
|
||||
<text b>
|
||||
<text a>word</text>
|
||||
</text>
|
||||
)
|
||||
|
||||
export const output = {
|
||||
text: 'word',
|
||||
marks: [],
|
||||
a: true,
|
||||
b: true,
|
||||
}
|
||||
|
@@ -6,11 +6,7 @@ import TextComponent from './text'
|
||||
import { ReactEditor } from '..'
|
||||
import { useEditor } from '../hooks/use-editor'
|
||||
import { NODE_TO_INDEX, NODE_TO_PARENT } from '../utils/weak-maps'
|
||||
import {
|
||||
RenderDecorationProps,
|
||||
RenderElementProps,
|
||||
RenderMarkProps,
|
||||
} from './editable'
|
||||
import { RenderElementProps, RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Children.
|
||||
@@ -20,18 +16,16 @@ const Children = (props: {
|
||||
decorate: (entry: NodeEntry) => Range[]
|
||||
decorations: Range[]
|
||||
node: Ancestor
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
selection: Range | null
|
||||
}) => {
|
||||
const {
|
||||
decorate,
|
||||
decorations,
|
||||
node,
|
||||
renderDecoration,
|
||||
renderElement,
|
||||
renderMark,
|
||||
renderLeaf,
|
||||
selection,
|
||||
} = props
|
||||
const editor = useEditor()
|
||||
@@ -65,9 +59,8 @@ const Children = (props: {
|
||||
decorations={ds}
|
||||
element={n}
|
||||
key={key.id}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={sel}
|
||||
/>
|
||||
)
|
||||
@@ -78,8 +71,7 @@ const Children = (props: {
|
||||
key={key.id}
|
||||
isLast={isLeafBlock && i === node.children.length}
|
||||
parent={node}
|
||||
renderDecoration={renderDecoration}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
text={n}
|
||||
/>
|
||||
)
|
||||
|
@@ -5,7 +5,7 @@ import React, {
|
||||
useMemo,
|
||||
useCallback,
|
||||
} from 'react'
|
||||
import { Editor, Element, NodeEntry, Node, Range, Text, Mark } from 'slate'
|
||||
import { Editor, Element, NodeEntry, Node, Range, Text } from 'slate'
|
||||
import debounce from 'debounce'
|
||||
import scrollIntoView from 'scroll-into-view-if-needed'
|
||||
|
||||
@@ -15,7 +15,6 @@ import { IS_FIREFOX, IS_SAFARI } from '../utils/environment'
|
||||
import { ReactEditor } from '..'
|
||||
import { ReadOnlyContext } from '../hooks/use-read-only'
|
||||
import { useSlate } from '../hooks/use-slate'
|
||||
import { Leaf } from '../utils/leaf'
|
||||
import {
|
||||
DOMElement,
|
||||
DOMNode,
|
||||
@@ -34,20 +33,6 @@ import {
|
||||
PLACEHOLDER_SYMBOL,
|
||||
} from '../utils/weak-maps'
|
||||
|
||||
/**
|
||||
* `RenderDecorationProps` are passed to the `renderDecoration` handler.
|
||||
*/
|
||||
|
||||
export interface RenderDecorationProps {
|
||||
children: any
|
||||
decoration: Range
|
||||
leaf: Leaf
|
||||
text: Text
|
||||
attributes: {
|
||||
'data-slate-decoration': true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `RenderElementProps` are passed to the `renderElement` handler.
|
||||
*/
|
||||
@@ -56,8 +41,8 @@ export interface RenderElementProps {
|
||||
children: any
|
||||
element: Element
|
||||
attributes: {
|
||||
'data-slate-inline'?: true
|
||||
'data-slate-node': 'element'
|
||||
'data-slate-inline'?: true
|
||||
'data-slate-void'?: true
|
||||
dir?: 'rtl'
|
||||
ref: any
|
||||
@@ -65,16 +50,15 @@ export interface RenderElementProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* `RenderMarkProps` are passed to the `renderMark` handler.
|
||||
* `RenderLeafProps` are passed to the `renderLeaf` handler.
|
||||
*/
|
||||
|
||||
export interface RenderMarkProps {
|
||||
export interface RenderLeafProps {
|
||||
children: any
|
||||
mark: Mark
|
||||
leaf: Leaf
|
||||
leaf: Text
|
||||
text: Text
|
||||
attributes: {
|
||||
'data-slate-mark': true
|
||||
'data-slate-leaf': true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,21 +74,19 @@ export const Editable = (
|
||||
readOnly?: boolean
|
||||
role?: string
|
||||
style?: React.CSSProperties
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
} & React.TextareaHTMLAttributes<HTMLDivElement>
|
||||
) => {
|
||||
const {
|
||||
autoFocus,
|
||||
decorate = defaultDecorate,
|
||||
onDOMBeforeInput: propsOnDOMBeforeInput,
|
||||
placeholder,
|
||||
readOnly = false,
|
||||
renderDecoration,
|
||||
renderElement,
|
||||
renderMark,
|
||||
autoFocus,
|
||||
renderLeaf,
|
||||
style = {},
|
||||
onDOMBeforeInput: propsOnDOMBeforeInput,
|
||||
...attributes
|
||||
} = props
|
||||
const editor = useSlate()
|
||||
@@ -906,9 +888,8 @@ export const Editable = (
|
||||
decorate={decorate}
|
||||
decorations={decorations}
|
||||
node={editor}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={editor.selection}
|
||||
/>
|
||||
</div>
|
||||
|
@@ -13,12 +13,7 @@ import {
|
||||
NODE_TO_INDEX,
|
||||
KEY_TO_ELEMENT,
|
||||
} from '../utils/weak-maps'
|
||||
import {
|
||||
RenderDecorationProps,
|
||||
RenderElementProps,
|
||||
RenderMarkProps,
|
||||
} from './editable'
|
||||
import { isRangeListEqual } from '../utils/leaf'
|
||||
import { RenderElementProps, RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Element.
|
||||
@@ -28,18 +23,16 @@ const Element = (props: {
|
||||
decorate: (entry: NodeEntry) => Range[]
|
||||
decorations: Range[]
|
||||
element: SlateElement
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderElement?: (props: RenderElementProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
selection: Range | null
|
||||
}) => {
|
||||
const {
|
||||
decorate,
|
||||
decorations,
|
||||
element,
|
||||
renderDecoration,
|
||||
renderElement = (p: RenderElementProps) => <DefaultElement {...p} />,
|
||||
renderMark,
|
||||
renderLeaf,
|
||||
selection,
|
||||
} = props
|
||||
const ref = useRef<HTMLElement>(null)
|
||||
@@ -53,9 +46,8 @@ const Element = (props: {
|
||||
decorate={decorate}
|
||||
decorations={decorations}
|
||||
node={element}
|
||||
renderDecoration={renderDecoration}
|
||||
renderElement={renderElement}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
selection={selection}
|
||||
/>
|
||||
)
|
||||
@@ -141,9 +133,8 @@ const MemoizedElement = React.memo(Element, (prev, next) => {
|
||||
return (
|
||||
prev.decorate === next.decorate &&
|
||||
prev.element === next.element &&
|
||||
prev.renderDecoration === next.renderDecoration &&
|
||||
prev.renderElement === next.renderElement &&
|
||||
prev.renderMark === next.renderMark &&
|
||||
prev.renderLeaf === next.renderLeaf &&
|
||||
isRangeListEqual(prev.decorations, next.decorations) &&
|
||||
(prev.selection === next.selection ||
|
||||
(!!prev.selection &&
|
||||
@@ -167,4 +158,29 @@ export const DefaultElement = (props: RenderElementProps) => {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of ranges is equal to another.
|
||||
*
|
||||
* PERF: this requires the two lists to also have the ranges inside them in the
|
||||
* same order, but this is an okay constraint for us since decorations are
|
||||
* kept in order, and the odd case where they aren't is okay to re-render for.
|
||||
*/
|
||||
|
||||
const isRangeListEqual = (list: Range[], another: Range[]): boolean => {
|
||||
if (list.length !== another.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const range = list[i]
|
||||
const other = another[i]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export default MemoizedElement
|
||||
|
@@ -2,9 +2,8 @@ import React from 'react'
|
||||
import { Text, Element } from 'slate'
|
||||
|
||||
import String from './string'
|
||||
import { Leaf as SlateLeaf } from '../utils/leaf'
|
||||
import { PLACEHOLDER_SYMBOL } from '../utils/weak-maps'
|
||||
import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
import { RenderLeafProps } from './editable'
|
||||
|
||||
/**
|
||||
* Individual leaves in a text node with unique formatting.
|
||||
@@ -12,10 +11,9 @@ import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
|
||||
const Leaf = (props: {
|
||||
isLast: boolean
|
||||
leaf: SlateLeaf
|
||||
leaf: Text
|
||||
parent: Element
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
text: Text
|
||||
}) => {
|
||||
const {
|
||||
@@ -23,117 +21,64 @@ const Leaf = (props: {
|
||||
isLast,
|
||||
text,
|
||||
parent,
|
||||
renderDecoration = (props: RenderDecorationProps) => (
|
||||
<DefaultDecoration {...props} />
|
||||
),
|
||||
renderMark = (props: RenderMarkProps) => <DefaultMark {...props} />,
|
||||
renderLeaf = (props: RenderLeafProps) => <DefaultLeaf {...props} />,
|
||||
} = props
|
||||
|
||||
let children = (
|
||||
<String isLast={isLast} leaf={leaf} parent={parent} text={text} />
|
||||
)
|
||||
|
||||
if (leaf[PLACEHOLDER_SYMBOL]) {
|
||||
children = (
|
||||
<React.Fragment>
|
||||
<span
|
||||
contentEditable={false}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'text-top',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}}
|
||||
>
|
||||
{leaf.placeholder}
|
||||
</span>
|
||||
{children}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
// COMPAT: Having the `data-` attributes on these leaf elements ensures that
|
||||
// in certain misbehaving browsers they aren't weirdly cloned/destroyed by
|
||||
// contenteditable behaviors. (2019/05/08)
|
||||
for (const mark of leaf.marks) {
|
||||
const ret = renderMark({
|
||||
children,
|
||||
leaf,
|
||||
mark,
|
||||
text,
|
||||
attributes: {
|
||||
'data-slate-mark': true,
|
||||
},
|
||||
})
|
||||
|
||||
if (ret) {
|
||||
children = ret
|
||||
}
|
||||
const attributes: {
|
||||
'data-slate-leaf': true
|
||||
} = {
|
||||
'data-slate-leaf': true,
|
||||
}
|
||||
|
||||
for (const decoration of leaf.decorations) {
|
||||
const p = {
|
||||
children,
|
||||
decoration,
|
||||
leaf,
|
||||
text,
|
||||
attributes: {
|
||||
'data-slate-decoration': true,
|
||||
},
|
||||
}
|
||||
|
||||
if (PLACEHOLDER_SYMBOL in decoration) {
|
||||
// @ts-ignore
|
||||
children = <PlaceholderDecoration {...p} />
|
||||
} else {
|
||||
// @ts-ignore
|
||||
const ret = renderDecoration(p)
|
||||
|
||||
if (ret) {
|
||||
children = ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <span data-slate-leaf>{children}</span>
|
||||
return renderLeaf({ attributes, children, leaf, text })
|
||||
}
|
||||
|
||||
const MemoizedLeaf = React.memo(Leaf, (prev, next) => {
|
||||
return (
|
||||
next.parent === prev.parent &&
|
||||
next.isLast === prev.isLast &&
|
||||
next.renderDecoration === prev.renderDecoration &&
|
||||
next.renderMark === prev.renderMark &&
|
||||
next.renderLeaf === prev.renderLeaf &&
|
||||
next.text === prev.text &&
|
||||
SlateLeaf.equals(next.leaf, prev.leaf)
|
||||
Text.matches(next.leaf, prev.leaf)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* The default custom decoration renderer.
|
||||
* The default custom leaf renderer.
|
||||
*/
|
||||
|
||||
export const DefaultDecoration = (props: RenderDecorationProps) => {
|
||||
export const DefaultLeaf = (props: RenderLeafProps) => {
|
||||
const { attributes, children } = props
|
||||
return <span {...attributes}>{children}</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* The default custom mark renderer.
|
||||
*/
|
||||
|
||||
export const DefaultMark = (props: RenderMarkProps) => {
|
||||
const { attributes, children } = props
|
||||
return <span {...attributes}>{children}</span>
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom decoration for the default placeholder behavior.
|
||||
*/
|
||||
|
||||
const PlaceholderDecoration = (props: RenderDecorationProps) => {
|
||||
const { decoration, attributes, children } = props
|
||||
const { placeholder } = decoration
|
||||
return (
|
||||
<span {...attributes}>
|
||||
<span
|
||||
contentEditable={false}
|
||||
style={{
|
||||
pointerEvents: 'none',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'text-top',
|
||||
width: '0',
|
||||
maxWidth: '100%',
|
||||
whiteSpace: 'nowrap',
|
||||
opacity: '0.333',
|
||||
}}
|
||||
>
|
||||
{placeholder}
|
||||
</span>
|
||||
{children}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default MemoizedLeaf
|
||||
|
@@ -2,7 +2,6 @@ import React from 'react'
|
||||
import { Editor, Text, Path, Element, Node } from 'slate'
|
||||
|
||||
import { ReactEditor, useEditor } from '..'
|
||||
import { Leaf } from '../utils/leaf'
|
||||
|
||||
/**
|
||||
* Leaf content strings.
|
||||
@@ -10,7 +9,7 @@ import { Leaf } from '../utils/leaf'
|
||||
|
||||
const String = (props: {
|
||||
isLast: boolean
|
||||
leaf: Leaf
|
||||
leaf: Text
|
||||
parent: Element
|
||||
text: Text
|
||||
}) => {
|
||||
|
@@ -2,9 +2,8 @@ import React, { useLayoutEffect, useRef } from 'react'
|
||||
import { Range, Element, Text as SlateText } from 'slate'
|
||||
|
||||
import Leaf from './leaf'
|
||||
import { Leaf as SlateLeaf } from '../utils/leaf'
|
||||
import { ReactEditor, useEditor } from '..'
|
||||
import { RenderDecorationProps, RenderMarkProps } from './editable'
|
||||
import { RenderLeafProps } from './editable'
|
||||
import {
|
||||
KEY_TO_ELEMENT,
|
||||
NODE_TO_ELEMENT,
|
||||
@@ -19,18 +18,10 @@ const Text = (props: {
|
||||
decorations: Range[]
|
||||
isLast: boolean
|
||||
parent: Element
|
||||
renderDecoration?: (props: RenderDecorationProps) => JSX.Element
|
||||
renderMark?: (props: RenderMarkProps) => JSX.Element
|
||||
renderLeaf?: (props: RenderLeafProps) => JSX.Element
|
||||
text: SlateText
|
||||
}) => {
|
||||
const {
|
||||
decorations,
|
||||
isLast,
|
||||
parent,
|
||||
renderDecoration,
|
||||
renderMark,
|
||||
text,
|
||||
} = props
|
||||
const { decorations, isLast, parent, renderLeaf, text } = props
|
||||
const editor = useEditor()
|
||||
const ref = useRef<HTMLSpanElement>(null)
|
||||
const leaves = getLeaves(text, decorations)
|
||||
@@ -47,8 +38,7 @@ const Text = (props: {
|
||||
leaf={leaf}
|
||||
text={text}
|
||||
parent={parent}
|
||||
renderDecoration={renderDecoration}
|
||||
renderMark={renderMark}
|
||||
renderLeaf={renderLeaf}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -76,12 +66,12 @@ const Text = (props: {
|
||||
* Get the leaves for a text node given decorations.
|
||||
*/
|
||||
|
||||
const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
const { text, marks } = node
|
||||
let leaves: SlateLeaf[] = [{ text, marks, decorations: [] }]
|
||||
const getLeaves = (node: SlateText, decorations: Range[]): SlateText[] => {
|
||||
let leaves: SlateText[] = [{ ...node }]
|
||||
|
||||
const compile = (range: Range, key?: string) => {
|
||||
const [start, end] = Range.edges(range)
|
||||
for (const dec of decorations) {
|
||||
const { anchor, focus, ...rest } = dec
|
||||
const [start, end] = Range.edges(dec)
|
||||
const next = []
|
||||
let o = 0
|
||||
|
||||
@@ -92,7 +82,7 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
|
||||
// If the range encompases the entire leaf, add the range.
|
||||
if (start.offset <= offset && end.offset >= offset + length) {
|
||||
leaf.decorations.push(range)
|
||||
Object.assign(leaf, rest)
|
||||
next.push(leaf)
|
||||
continue
|
||||
}
|
||||
@@ -115,14 +105,18 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
let after
|
||||
|
||||
if (end.offset < offset + length) {
|
||||
;[middle, after] = SlateLeaf.split(middle, end.offset - offset)
|
||||
const off = end.offset - offset
|
||||
after = { ...middle, text: middle.text.slice(off) }
|
||||
middle = { ...middle, text: middle.text.slice(0, off) }
|
||||
}
|
||||
|
||||
if (start.offset > offset) {
|
||||
;[before, middle] = SlateLeaf.split(middle, start.offset - offset)
|
||||
const off = start.offset - offset
|
||||
before = { ...middle, text: middle.text.slice(0, off) }
|
||||
middle = { ...middle, text: middle.text.slice(off) }
|
||||
}
|
||||
|
||||
middle.decorations.push(range)
|
||||
Object.assign(middle, rest)
|
||||
|
||||
if (before) {
|
||||
next.push(before)
|
||||
@@ -138,28 +132,16 @@ const getLeaves = (node: SlateText, decorations: Range[]): SlateLeaf[] => {
|
||||
leaves = next
|
||||
}
|
||||
|
||||
for (const range of decorations) {
|
||||
compile(range)
|
||||
}
|
||||
|
||||
return leaves
|
||||
}
|
||||
|
||||
const MemoizedText = React.memo(Text, (prev, next) => {
|
||||
if (
|
||||
return (
|
||||
next.parent === prev.parent &&
|
||||
next.isLast === prev.isLast &&
|
||||
next.renderDecoration === prev.renderDecoration &&
|
||||
next.renderMark === prev.renderMark &&
|
||||
next.renderLeaf === prev.renderLeaf &&
|
||||
next.text === prev.text
|
||||
) {
|
||||
return SlateLeaf.equals(
|
||||
{ ...next.text, decorations: next.decorations },
|
||||
{ ...prev.text, decorations: prev.decorations }
|
||||
)
|
||||
}
|
||||
|
||||
return false
|
||||
)
|
||||
})
|
||||
|
||||
export default MemoizedText
|
||||
|
@@ -1,6 +1,6 @@
|
||||
export * from './components/editable'
|
||||
export { DefaultElement } from './components/element'
|
||||
export { DefaultMark, DefaultDecoration } from './components/leaf'
|
||||
export { DefaultLeaf } from './components/leaf'
|
||||
export * from './hooks/use-editor'
|
||||
export * from './hooks/use-focused'
|
||||
export * from './hooks/use-read-only'
|
||||
|
@@ -1,113 +0,0 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Range, Mark } from 'slate'
|
||||
|
||||
/**
|
||||
* The `Leaf` interface represents the individual leaves inside a text node,
|
||||
* once decorations have been applied.
|
||||
*/
|
||||
|
||||
interface Leaf {
|
||||
decorations: Range[]
|
||||
marks: Mark[]
|
||||
text: string
|
||||
}
|
||||
|
||||
namespace Leaf {
|
||||
/**
|
||||
* Check if two leaves are equal.
|
||||
*/
|
||||
|
||||
export const equals = (leaf: Leaf, another: Leaf): boolean => {
|
||||
return (
|
||||
leaf.text === another.text &&
|
||||
leaf.decorations.length === another.decorations.length &&
|
||||
leaf.marks.length === another.marks.length &&
|
||||
leaf.marks.every(m => Mark.exists(m, another.marks)) &&
|
||||
another.marks.every(m => Mark.exists(m, leaf.marks)) &&
|
||||
isRangeListEqual(leaf.decorations, another.decorations)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a `Leaf` object.
|
||||
*/
|
||||
|
||||
export const isLeaf = (value: any): value is Leaf => {
|
||||
return (
|
||||
isPlainObject(value) &&
|
||||
typeof value.text === 'string' &&
|
||||
Mark.isMarkSet(value.marks) &&
|
||||
Range.isRangeList(value.decorations)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a leaf into two at an offset.
|
||||
*/
|
||||
|
||||
export const split = (leaf: Leaf, offset: number): [Leaf, Leaf] => {
|
||||
return [
|
||||
{
|
||||
text: leaf.text.slice(0, offset),
|
||||
marks: leaf.marks,
|
||||
decorations: [...leaf.decorations],
|
||||
},
|
||||
{
|
||||
text: leaf.text.slice(offset),
|
||||
marks: leaf.marks,
|
||||
decorations: [...leaf.decorations],
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a list of ranges is equal to another.
|
||||
*
|
||||
* PERF: this requires the two lists to also have the ranges inside them in the
|
||||
* same order, but this is an okay constraint for us since decorations are
|
||||
* kept in order, and the odd case where they aren't is okay to re-render for.
|
||||
*/
|
||||
|
||||
const isRangeListEqual = (list: Range[], another: Range[]): boolean => {
|
||||
if (list.length !== another.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const range = list[i]
|
||||
const other = another[i]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a map of ranges is equal to another.
|
||||
*/
|
||||
|
||||
const isRangeMapEqual = (
|
||||
map: Record<string, Range>,
|
||||
another: Record<string, Range>
|
||||
): boolean => {
|
||||
if (Object.keys(map).length !== Object.keys(another).length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const key in map) {
|
||||
const range = map[key]
|
||||
const other = another[key]
|
||||
|
||||
if (!Range.equals(range, other)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export { Leaf, isRangeListEqual, isRangeMapEqual }
|
@@ -1,4 +1,4 @@
|
||||
import { Node, Ancestor, Editor } from 'slate'
|
||||
import { Node, Ancestor, Editor, Text } from 'slate'
|
||||
|
||||
import { Key } from './key'
|
||||
|
||||
@@ -16,10 +16,11 @@ export const NODE_TO_PARENT: WeakMap<Node, Ancestor> = new WeakMap()
|
||||
*/
|
||||
|
||||
export const EDITOR_TO_ELEMENT: WeakMap<Editor, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
|
||||
export const EDITOR_TO_PLACEHOLDER: WeakMap<Editor, string> = new WeakMap()
|
||||
export const ELEMENT_TO_NODE: WeakMap<HTMLElement, Node> = new WeakMap()
|
||||
export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()
|
||||
export const KEY_TO_ELEMENT: WeakMap<Key, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_ELEMENT: WeakMap<Node, HTMLElement> = new WeakMap()
|
||||
export const NODE_TO_KEY: WeakMap<Node, Key> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Weak maps for storing editor-related state.
|
||||
@@ -30,8 +31,4 @@ export const IS_FOCUSED: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_DRAGGING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
export const IS_CLICKING: WeakMap<Editor, boolean> = new WeakMap()
|
||||
|
||||
/**
|
||||
* Symbols.
|
||||
*/
|
||||
|
||||
export const PLACEHOLDER_SYMBOL = Symbol('placeholder')
|
||||
export const PLACEHOLDER_SYMBOL = (Symbol('placeholder') as unknown) as string
|
||||
|
@@ -16,11 +16,8 @@ export const withReact = (editor: Editor): Editor => {
|
||||
const matches: [Path, Key][] = []
|
||||
|
||||
switch (op.type) {
|
||||
case 'add_mark':
|
||||
case 'insert_text':
|
||||
case 'remove_mark':
|
||||
case 'remove_text':
|
||||
case 'set_mark':
|
||||
case 'set_node': {
|
||||
for (const [node, path] of Editor.levels(editor, { at: op.path })) {
|
||||
const key = ReactEditor.findKey(editor, node)
|
||||
|
@@ -2,47 +2,14 @@ import {
|
||||
NodeEntry,
|
||||
Node,
|
||||
Text,
|
||||
Mark,
|
||||
Editor,
|
||||
MarkEntry,
|
||||
AncestorEntry,
|
||||
Descendant,
|
||||
DescendantEntry,
|
||||
} from 'slate'
|
||||
|
||||
import { MarkError, NodeError } from './errors'
|
||||
import { NodeRule, MarkRule, ChildValidation } from './rules'
|
||||
|
||||
/**
|
||||
* Check a mark object.
|
||||
*/
|
||||
|
||||
export const checkMark = (
|
||||
editor: Editor,
|
||||
entry: MarkEntry,
|
||||
rule: MarkRule
|
||||
): MarkError | undefined => {
|
||||
const { validate: v } = rule
|
||||
const [mark, index, node, path] = entry
|
||||
|
||||
if ('properties' in v) {
|
||||
for (const k in v.properties) {
|
||||
const p = v.properties[k]
|
||||
const value = mark[k]
|
||||
|
||||
if ((typeof p === 'function' && !p(value)) || p !== value) {
|
||||
return {
|
||||
code: 'mark_property_invalid',
|
||||
mark,
|
||||
index,
|
||||
node,
|
||||
path,
|
||||
property: k,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import { NodeError } from './errors'
|
||||
import { NodeRule, ChildValidation } from './rules'
|
||||
|
||||
/**
|
||||
* Check a node object.
|
||||
@@ -70,15 +37,6 @@ export const checkNode = (
|
||||
}
|
||||
}
|
||||
|
||||
if ('marks' in v && v.marks != null) {
|
||||
for (const entry of Node.marks(node)) {
|
||||
if (!Editor.isMarkMatch(editor, entry, v.marks)) {
|
||||
const [mark, index, n, p] = entry
|
||||
return { code: 'mark_invalid', node: n, path: p, mark, index }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ('text' in v && v.text != null) {
|
||||
const text = Node.text(node)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Ancestor, Descendant, Range, Mark, Node, Path, Text } from 'slate'
|
||||
import { Ancestor, Descendant, Node, Path } from 'slate'
|
||||
|
||||
export interface ChildInvalidError {
|
||||
code: 'child_invalid'
|
||||
@@ -56,23 +56,6 @@ export interface NodeTextInvalidError {
|
||||
text: string
|
||||
}
|
||||
|
||||
export interface MarkInvalidError {
|
||||
code: 'mark_invalid'
|
||||
node: Text
|
||||
path: Path
|
||||
mark: Mark
|
||||
index: number
|
||||
}
|
||||
|
||||
export interface MarkPropertyInvalidError {
|
||||
code: 'mark_property_invalid'
|
||||
mark: Mark
|
||||
index: number
|
||||
node: Text
|
||||
path: Path
|
||||
property: string
|
||||
}
|
||||
|
||||
export interface ParentInvalidError {
|
||||
code: 'parent_invalid'
|
||||
node: Ancestor
|
||||
@@ -86,19 +69,16 @@ export interface PreviousSiblingInvalidError {
|
||||
path: Path
|
||||
}
|
||||
|
||||
export type MarkError = MarkPropertyInvalidError
|
||||
|
||||
export type NodeError =
|
||||
| ChildInvalidError
|
||||
| ChildMaxInvalidError
|
||||
| ChildMinInvalidError
|
||||
| FirstChildInvalidError
|
||||
| LastChildInvalidError
|
||||
| MarkInvalidError
|
||||
| NextSiblingInvalidError
|
||||
| NodePropertyInvalidError
|
||||
| NodeTextInvalidError
|
||||
| ParentInvalidError
|
||||
| PreviousSiblingInvalidError
|
||||
|
||||
export type SchemaError = MarkError | NodeError
|
||||
export type SchemaError = NodeError
|
||||
|
@@ -1,16 +1,5 @@
|
||||
import { Editor, NodeMatch, MarkMatch } from 'slate'
|
||||
import { NodeError, MarkError } from './errors'
|
||||
|
||||
export interface MarkValidation {
|
||||
properties?: Record<string, any>
|
||||
}
|
||||
|
||||
export interface MarkRule {
|
||||
for: 'mark'
|
||||
match: MarkMatch
|
||||
validate: MarkValidation
|
||||
normalize: (editor: Editor, error: MarkError) => void
|
||||
}
|
||||
import { Editor, NodeMatch } from 'slate'
|
||||
import { NodeError } from './errors'
|
||||
|
||||
export interface ChildValidation {
|
||||
match?: NodeMatch
|
||||
@@ -22,7 +11,6 @@ export interface NodeValidation {
|
||||
children?: ChildValidation[]
|
||||
first?: NodeMatch
|
||||
last?: NodeMatch
|
||||
marks?: MarkMatch
|
||||
next?: NodeMatch
|
||||
parent?: NodeMatch
|
||||
previous?: NodeMatch
|
||||
@@ -37,4 +25,4 @@ export interface NodeRule {
|
||||
normalize: (editor: Editor, error: NodeError) => void
|
||||
}
|
||||
|
||||
export type SchemaRule = MarkRule | NodeRule
|
||||
export type SchemaRule = NodeRule
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { Editor, Text, NodeEntry } from 'slate'
|
||||
|
||||
import { NodeRule, SchemaRule, MarkRule } from './rules'
|
||||
import { NodeRule, SchemaRule } from './rules'
|
||||
import { NodeError } from './errors'
|
||||
import { checkNode, checkAncestor } from './checkers'
|
||||
|
||||
@@ -14,23 +14,16 @@ export const withSchema = (
|
||||
rules: SchemaRule[] = []
|
||||
): Editor => {
|
||||
const { normalizeNode } = editor
|
||||
const markRules: MarkRule[] = []
|
||||
const nodeRules: NodeRule[] = []
|
||||
const nodeRules: NodeRule[] = rules
|
||||
const parentRules: NodeRule[] = []
|
||||
|
||||
for (const rule of rules) {
|
||||
if (rule.for === 'mark') {
|
||||
markRules.push(rule)
|
||||
} else {
|
||||
nodeRules.push(rule)
|
||||
|
||||
if (
|
||||
'parent' in rule.validate ||
|
||||
'next' in rule.validate ||
|
||||
'previous' in rule.validate
|
||||
) {
|
||||
parentRules.push(rule)
|
||||
}
|
||||
if (
|
||||
'parent' in rule.validate ||
|
||||
'next' in rule.validate ||
|
||||
'previous' in rule.validate
|
||||
) {
|
||||
parentRules.push(rule)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,12 +129,6 @@ export const withSchema = (
|
||||
break
|
||||
}
|
||||
|
||||
case 'mark_invalid': {
|
||||
const { mark, path } = error
|
||||
Editor.removeMarks(editor, [mark], { at: path })
|
||||
break
|
||||
}
|
||||
|
||||
case 'parent_invalid': {
|
||||
const { path, index } = error
|
||||
const childPath = path.concat(index)
|
||||
|
@@ -1,27 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const schema = [
|
||||
{
|
||||
for: 'node',
|
||||
match: 'element',
|
||||
validate: {
|
||||
marks: [],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark a>text</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<element>text</element>
|
||||
</editor>
|
||||
)
|
@@ -1,27 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const schema = [
|
||||
{
|
||||
for: 'node',
|
||||
match: 'element',
|
||||
validate: {
|
||||
marks: [{ a: true }],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark b>text</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<element>text</element>
|
||||
</editor>
|
||||
)
|
@@ -1,27 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const schema = [
|
||||
{
|
||||
for: 'node',
|
||||
match: 'element',
|
||||
validate: {
|
||||
marks: [{ a: true }, { b: true }],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark c>text</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = (
|
||||
<editor>
|
||||
<element>text</element>
|
||||
</editor>
|
||||
)
|
@@ -1,23 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const schema = [
|
||||
{
|
||||
for: 'node',
|
||||
match: 'element',
|
||||
validate: {
|
||||
marks: [{ a: true }],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark a>text</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const output = input
|
@@ -88,11 +88,6 @@ export const createEditor = (): Editor => {
|
||||
|
||||
if (Command.isCoreCommand(command)) {
|
||||
switch (command.type) {
|
||||
case 'add_mark': {
|
||||
Editor.addMarks(editor, command.mark)
|
||||
break
|
||||
}
|
||||
|
||||
case 'delete_backward': {
|
||||
if (selection && Range.isCollapsed(selection)) {
|
||||
Editor.delete(editor, { unit: command.unit, reverse: true })
|
||||
@@ -136,11 +131,6 @@ export const createEditor = (): Editor => {
|
||||
Editor.insertText(editor, command.text)
|
||||
break
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
Editor.removeMarks(editor, [command.mark])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -154,7 +144,7 @@ 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: '', marks: [] }
|
||||
const child = { text: '' }
|
||||
Editor.insertNodes(editor, child, { at: path.concat(0) })
|
||||
return
|
||||
}
|
||||
@@ -194,23 +184,23 @@ export const createEditor = (): Editor => {
|
||||
// Ensure that inline nodes are surrounded by text nodes.
|
||||
if (editor.isInline(child)) {
|
||||
if (prev == null || !Text.isText(prev)) {
|
||||
const newChild = { text: '', marks: [] }
|
||||
const newChild = { text: '' }
|
||||
Editor.insertNodes(editor, newChild, { at: path.concat(n) })
|
||||
n++
|
||||
continue
|
||||
}
|
||||
|
||||
if (isLast) {
|
||||
const newChild = { text: '', marks: [] }
|
||||
const newChild = { text: '' }
|
||||
Editor.insertNodes(editor, newChild, { at: path.concat(n + 1) })
|
||||
n++
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Merge adjacent text nodes that are empty or have matching marks.
|
||||
// Merge adjacent text nodes that are empty or match.
|
||||
if (prev != null && Text.isText(prev)) {
|
||||
if (Text.matches(child, prev)) {
|
||||
if (Text.equals(child, prev, { loose: true })) {
|
||||
Editor.mergeNodes(editor, { at: path.concat(n) })
|
||||
n--
|
||||
continue
|
||||
@@ -238,11 +228,8 @@ export const createEditor = (): Editor => {
|
||||
|
||||
const getDirtyPaths = (op: Operation) => {
|
||||
switch (op.type) {
|
||||
case 'add_mark':
|
||||
case 'insert_text':
|
||||
case 'remove_mark':
|
||||
case 'remove_text':
|
||||
case 'set_mark':
|
||||
case 'set_node': {
|
||||
const { path } = op
|
||||
return Path.levels(path)
|
||||
|
@@ -3,7 +3,6 @@ export * from './interfaces/command'
|
||||
export * from './interfaces/editor'
|
||||
export * from './interfaces/element'
|
||||
export * from './interfaces/location'
|
||||
export * from './interfaces/mark'
|
||||
export * from './interfaces/node'
|
||||
export * from './interfaces/operation'
|
||||
export * from './interfaces/path'
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Mark, Node, Range } from '..'
|
||||
import { Node } from '..'
|
||||
|
||||
/**
|
||||
* `Command` objects represent an action that a user is taking on the editor.
|
||||
@@ -20,32 +20,18 @@ export const Command = {
|
||||
return isPlainObject(value) && typeof value.type === 'string'
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is an `AddMarkCommand` object.
|
||||
*/
|
||||
|
||||
isAddMarkCommand(value: any): value is AddMarkCommand {
|
||||
return (
|
||||
Command.isCommand(value) &&
|
||||
value.type === 'add_mark' &&
|
||||
Mark.isMark(value.mark)
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is a `CoreCommand` object.
|
||||
*/
|
||||
|
||||
isCoreCommand(value: any): value is CoreCommand {
|
||||
return (
|
||||
Command.isAddMarkCommand(value) ||
|
||||
Command.isDeleteBackwardCommand(value) ||
|
||||
Command.isDeleteForwardCommand(value) ||
|
||||
Command.isDeleteFragmentCommand(value) ||
|
||||
Command.isInsertTextCommand(value) ||
|
||||
Command.isInsertFragmentCommand(value) ||
|
||||
Command.isInsertBreakCommand(value) ||
|
||||
Command.isRemoveMarkCommand(value)
|
||||
Command.isInsertBreakCommand(value)
|
||||
)
|
||||
},
|
||||
|
||||
@@ -124,27 +110,6 @@ export const Command = {
|
||||
typeof value.text === 'string'
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is a `RemoveMarkCommand` object.
|
||||
*/
|
||||
|
||||
isRemoveMarkCommand(value: any): value is RemoveMarkCommand {
|
||||
return (
|
||||
Command.isCommand(value) &&
|
||||
value.type === 'remove_mark' &&
|
||||
Mark.isMark(value.mark)
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* The `AddMarkCommand` adds a mark to the current selection.
|
||||
*/
|
||||
|
||||
export interface AddMarkCommand {
|
||||
type: 'add_mark'
|
||||
mark: Mark
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,22 +175,12 @@ export interface InsertTextCommand {
|
||||
text: string
|
||||
}
|
||||
|
||||
/**
|
||||
* The `RemoveMarkCommand` removes a mark in the current selection.
|
||||
*/
|
||||
|
||||
export interface RemoveMarkCommand {
|
||||
type: 'remove_mark'
|
||||
mark: Mark
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -233,4 +188,3 @@ export type CoreCommand =
|
||||
| InsertFragmentCommand
|
||||
| InsertNodeCommand
|
||||
| InsertTextCommand
|
||||
| RemoveMarkCommand
|
||||
|
@@ -4,8 +4,6 @@ import { ElementQueries } from './queries/element'
|
||||
import { GeneralTransforms } from './transforms/general'
|
||||
import { GeneralQueries } from './queries/general'
|
||||
import { LocationQueries } from './queries/location'
|
||||
import { MarkQueries } from './queries/mark'
|
||||
import { MarkTransforms } from './transforms/mark'
|
||||
import { NodeTransforms } from './transforms/node'
|
||||
import { NodeQueries } from './queries/node'
|
||||
import { RangeQueries } from './queries/range'
|
||||
@@ -35,8 +33,6 @@ export const Editor = {
|
||||
...GeneralQueries,
|
||||
...GeneralTransforms,
|
||||
...LocationQueries,
|
||||
...MarkQueries,
|
||||
...MarkTransforms,
|
||||
...NodeQueries,
|
||||
...NodeTransforms,
|
||||
...RangeQueries,
|
||||
|
@@ -9,8 +9,6 @@ import {
|
||||
Element,
|
||||
ElementEntry,
|
||||
Location,
|
||||
Mark,
|
||||
MarkEntry,
|
||||
Node,
|
||||
NodeEntry,
|
||||
NodeMatch,
|
||||
@@ -21,46 +19,8 @@ import {
|
||||
Text,
|
||||
TextEntry,
|
||||
} from '../../..'
|
||||
import { MarkMatch } from '../../mark'
|
||||
|
||||
export const LocationQueries = {
|
||||
/**
|
||||
* Get the marks that are "active" at a location. These are the
|
||||
* marks that will be added to any text that is inserted.
|
||||
*
|
||||
* The `union: true` option can be passed to create a union of marks across
|
||||
* the text nodes in the selection, instead of creating an intersection, which
|
||||
* is the default.
|
||||
*
|
||||
* Note: to obey common rich text behavior, if the selection is collapsed at
|
||||
* the start of a text node and there are previous text nodes in the same
|
||||
* block, it will carry those marks forward from the previous text node. This
|
||||
* allows for continuation of marks from previous words.
|
||||
*/
|
||||
|
||||
activeMarks(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
union?: boolean
|
||||
hanging?: boolean
|
||||
} = {}
|
||||
): Mark[] {
|
||||
warning(
|
||||
false,
|
||||
'The `Editor.activeMarks` helper is deprecated, use `Editor.marks` instead.'
|
||||
)
|
||||
|
||||
return Array.from(
|
||||
Editor.marks(editor, {
|
||||
at: options.at,
|
||||
mode: options.union ? 'distinct' : 'universal',
|
||||
continuing: true,
|
||||
}),
|
||||
([m]) => m
|
||||
)
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the point after a location.
|
||||
*/
|
||||
@@ -305,116 +265,6 @@ export const LocationQueries = {
|
||||
yield* levels
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterate through all of the text nodes in the Editor.
|
||||
*/
|
||||
|
||||
*marks(
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
match?: MarkMatch
|
||||
mode?: 'all' | 'first' | 'distinct' | 'universal'
|
||||
reverse?: boolean
|
||||
continuing?: boolean
|
||||
} = {}
|
||||
): Iterable<MarkEntry> {
|
||||
const { match, mode = 'all', reverse = false, continuing = false } = options
|
||||
let { at = editor.selection } = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
// If the range is collapsed at the start of a text node, it should continue
|
||||
// the marks from the previous text node in the same block.
|
||||
if (
|
||||
continuing &&
|
||||
Range.isRange(at) &&
|
||||
Range.isCollapsed(at) &&
|
||||
at.anchor.offset === 0
|
||||
) {
|
||||
const { anchor } = at
|
||||
const prev = Editor.previous(editor, anchor, 'text')
|
||||
|
||||
if (prev && Path.isSibling(anchor.path, prev[1])) {
|
||||
const [, prevPath] = prev
|
||||
at = Editor.range(editor, prevPath)
|
||||
}
|
||||
}
|
||||
|
||||
const universalMarks: Mark[] = []
|
||||
const distinctMarks: Mark[] = []
|
||||
const universalEntries: MarkEntry[] = []
|
||||
let first = true
|
||||
|
||||
for (const entry of Editor.texts(editor, { reverse, at })) {
|
||||
const [node, path] = entry
|
||||
|
||||
if (mode === 'universal') {
|
||||
if (first) {
|
||||
for (let i = 0; i < node.marks.length; i++) {
|
||||
const mark = node.marks[i]
|
||||
const markEntry: MarkEntry = [mark, i, node, path]
|
||||
|
||||
if (match == null || Editor.isMarkMatch(editor, markEntry, match)) {
|
||||
universalMarks.push(mark)
|
||||
universalEntries.push(markEntry)
|
||||
}
|
||||
}
|
||||
|
||||
first = false
|
||||
continue
|
||||
}
|
||||
|
||||
// PERF: If we're in universal mode and the eligible marks hits zero
|
||||
// it can never increase again, so we can exit early.
|
||||
if (universalMarks.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (let i = universalMarks.length - 1; i >= 0; i--) {
|
||||
const existing = universalMarks[i]
|
||||
|
||||
if (!Mark.exists(existing, node.marks)) {
|
||||
universalMarks.splice(i, 1)
|
||||
universalEntries.splice(i, 1)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let index = 0; index < node.marks.length; index++) {
|
||||
const mark = node.marks[index]
|
||||
const markEntry: MarkEntry = [mark, index, node, path]
|
||||
|
||||
if (match != null && !Editor.isMarkMatch(editor, markEntry, match)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (mode === 'distinct') {
|
||||
if (Mark.exists(mark, distinctMarks)) {
|
||||
continue
|
||||
} else {
|
||||
distinctMarks.push(mark)
|
||||
}
|
||||
}
|
||||
|
||||
yield markEntry
|
||||
|
||||
// After matching a mark, if we're in first mode skip to the next text.
|
||||
if (mode === 'first') {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In universal mode, the marks are collected while iterating and we can
|
||||
// only be certain of which are universal when we've finished.
|
||||
if (mode === 'universal') {
|
||||
yield* universalEntries
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the first matching node in a single branch of the document.
|
||||
*/
|
||||
|
@@ -1,17 +0,0 @@
|
||||
import { Editor, Mark, MarkEntry, MarkMatch } from '../../..'
|
||||
|
||||
export const MarkQueries = {
|
||||
/**
|
||||
* Check if a mark entry is a match.
|
||||
*/
|
||||
|
||||
isMarkMatch(editor: Editor, entry: MarkEntry, match: MarkMatch): boolean {
|
||||
if (Array.isArray(match)) {
|
||||
return match.some(m => Editor.isMarkMatch(editor, entry, m))
|
||||
} else if (typeof match === 'function') {
|
||||
return match(entry)
|
||||
} else {
|
||||
return Mark.matches(entry[0], match)
|
||||
}
|
||||
},
|
||||
}
|
@@ -2,7 +2,6 @@ import { createDraft, finishDraft, isDraft } from 'immer'
|
||||
import {
|
||||
Node,
|
||||
Editor,
|
||||
Mark,
|
||||
Range,
|
||||
Point,
|
||||
Text,
|
||||
@@ -68,17 +67,6 @@ export const GeneralTransforms = {
|
||||
let selection = editor.selection && createDraft(editor.selection)
|
||||
|
||||
switch (op.type) {
|
||||
case 'add_mark': {
|
||||
const { path, mark } = op
|
||||
const node = Node.leaf(editor, path)
|
||||
|
||||
if (!Mark.exists(mark, node.marks)) {
|
||||
node.marks.push(mark)
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'insert_node': {
|
||||
const { path, node } = op
|
||||
const parent = Node.parent(editor, path)
|
||||
@@ -174,20 +162,6 @@ export const GeneralTransforms = {
|
||||
break
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
const { path, mark } = op
|
||||
const node = Node.leaf(editor, path)
|
||||
|
||||
for (let i = 0; i < node.marks.length; i++) {
|
||||
if (Mark.matches(node.marks[i], mark)) {
|
||||
node.marks.splice(i, 1)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'remove_node': {
|
||||
const { path } = op
|
||||
const index = path[path.length - 1]
|
||||
@@ -238,20 +212,6 @@ export const GeneralTransforms = {
|
||||
break
|
||||
}
|
||||
|
||||
case 'set_mark': {
|
||||
const { path, properties, newProperties } = op
|
||||
const node = Node.leaf(editor, path)
|
||||
|
||||
for (const mark of node.marks) {
|
||||
if (Mark.matches(mark, properties)) {
|
||||
Object.assign(mark, newProperties)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
case 'set_node': {
|
||||
const { path, newProperties } = op
|
||||
|
||||
@@ -260,7 +220,21 @@ export const GeneralTransforms = {
|
||||
}
|
||||
|
||||
const node = Node.get(editor, path)
|
||||
Object.assign(node, newProperties)
|
||||
|
||||
for (const key in newProperties) {
|
||||
if (key === 'children' || key === 'text') {
|
||||
throw new Error(`Cannot set the "${key}" property of nodes!`)
|
||||
}
|
||||
|
||||
const value = newProperties[key]
|
||||
|
||||
if (value == null) {
|
||||
delete node[key]
|
||||
} else {
|
||||
node[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
|
@@ -1,140 +0,0 @@
|
||||
import { Editor, Mark, Location, Range } from '../../..'
|
||||
|
||||
export const MarkTransforms = {
|
||||
/**
|
||||
* Add a set of marks to the text nodes at a location.
|
||||
*/
|
||||
|
||||
addMarks(
|
||||
editor: Editor,
|
||||
mark: Mark | Mark[],
|
||||
options: {
|
||||
at?: Location
|
||||
hanging?: boolean
|
||||
} = {}
|
||||
) {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const at = splitLocation(editor, options)
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
// De-dupe the marks being added to ensure the set is unique.
|
||||
const marks = Array.isArray(mark) ? mark : [mark]
|
||||
const set: Mark[] = []
|
||||
|
||||
for (const m of marks) {
|
||||
if (!Mark.exists(m, set)) {
|
||||
set.push(m)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [node, path] of Editor.texts(editor, { at })) {
|
||||
for (const m of set) {
|
||||
if (!Mark.exists(m, node.marks)) {
|
||||
editor.apply({ type: 'add_mark', path, mark: m })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
removeMarks(
|
||||
editor: Editor,
|
||||
mark: Mark | Mark[],
|
||||
options: {
|
||||
at?: Location
|
||||
hanging?: boolean
|
||||
} = {}
|
||||
) {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const at = splitLocation(editor, options)
|
||||
|
||||
if (at) {
|
||||
const marks = Array.isArray(mark) ? mark : [mark]
|
||||
for (const [m, i, node, path] of Editor.marks(editor, { at })) {
|
||||
if (Mark.exists(m, marks)) {
|
||||
editor.apply({ type: 'remove_mark', path, mark: m })
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
setMarks(
|
||||
editor: Editor,
|
||||
mark: Mark | Mark[],
|
||||
props: Partial<Mark>,
|
||||
options: {
|
||||
at?: Location
|
||||
hanging?: boolean
|
||||
} = {}
|
||||
) {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
const at = splitLocation(editor, options)
|
||||
|
||||
if (at) {
|
||||
const marks = Array.isArray(mark) ? mark : [mark]
|
||||
for (const [m, i, node, path] of Editor.marks(editor, { at })) {
|
||||
if (Mark.exists(m, marks)) {
|
||||
const newProps = {}
|
||||
|
||||
for (const k in props) {
|
||||
if (props[k] !== m[k]) {
|
||||
newProps[k] = props[k]
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(newProps).length > 0) {
|
||||
editor.apply({
|
||||
type: 'set_mark',
|
||||
path,
|
||||
properties: m,
|
||||
newProperties: newProps,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Split the text nodes at a range's edges to prepare for adding/removing marks.
|
||||
*/
|
||||
|
||||
const splitLocation = (
|
||||
editor: Editor,
|
||||
options: {
|
||||
at?: Location
|
||||
hanging?: boolean
|
||||
} = {}
|
||||
): Location | undefined => {
|
||||
let { at = editor.selection, hanging = false } = options
|
||||
|
||||
if (!at) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Range.isRange(at)) {
|
||||
if (!hanging) {
|
||||
at = Editor.unhangRange(editor, at)
|
||||
}
|
||||
|
||||
const rangeRef = Editor.rangeRef(editor, at, { affinity: 'inward' })
|
||||
const [start, end] = Range.edges(at)
|
||||
Editor.splitNodes(editor, { at: end, match: 'text' })
|
||||
Editor.splitNodes(editor, { at: start, match: 'text' })
|
||||
const range = rangeRef.unref()!
|
||||
|
||||
if (options.at == null) {
|
||||
Editor.select(editor, range)
|
||||
}
|
||||
|
||||
return range
|
||||
}
|
||||
|
||||
return at
|
||||
}
|
@@ -270,7 +270,7 @@ export const NodeTransforms = {
|
||||
// Ensure that the nodes are equivalent, and figure out what the position
|
||||
// and extra properties of the merge will be.
|
||||
if (Text.isText(node) && Text.isText(prevNode)) {
|
||||
const { text, marks, ...rest } = node
|
||||
const { text, ...rest } = node
|
||||
position = prevNode.text.length
|
||||
properties = rest as Partial<Text>
|
||||
} else if (Element.isElement(node) && Element.isElement(prevNode)) {
|
||||
@@ -413,7 +413,7 @@ export const NodeTransforms = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Set new properties on the nodes ...
|
||||
* Set new properties on the nodes at a location.
|
||||
*/
|
||||
|
||||
setNodes(
|
||||
@@ -422,12 +422,14 @@ export const NodeTransforms = {
|
||||
options: {
|
||||
at?: Location
|
||||
match?: NodeMatch
|
||||
mode?: 'all' | 'highest'
|
||||
hanging?: boolean
|
||||
split?: boolean
|
||||
} = {}
|
||||
) {
|
||||
Editor.withoutNormalizing(editor, () => {
|
||||
let { match, at = editor.selection } = options
|
||||
const { hanging = false } = options
|
||||
const { hanging = false, mode = 'highest', split = false } = options
|
||||
|
||||
if (match == null) {
|
||||
if (Path.isPath(at)) {
|
||||
@@ -446,21 +448,29 @@ export const NodeTransforms = {
|
||||
at = Editor.unhangRange(editor, at)
|
||||
}
|
||||
|
||||
for (const [node, path] of Editor.nodes(editor, {
|
||||
at,
|
||||
match,
|
||||
mode: 'highest',
|
||||
})) {
|
||||
if (split && Range.isRange(at)) {
|
||||
const rangeRef = Editor.rangeRef(editor, at, { affinity: 'inward' })
|
||||
const [start, end] = Range.edges(at)
|
||||
Editor.splitNodes(editor, { at: end, match })
|
||||
Editor.splitNodes(editor, { at: start, match })
|
||||
at = rangeRef.unref()!
|
||||
|
||||
if (options.at == null) {
|
||||
Editor.select(editor, at)
|
||||
}
|
||||
}
|
||||
|
||||
for (const [node, path] of Editor.nodes(editor, { at, match, mode })) {
|
||||
const properties: Partial<Node> = {}
|
||||
const newProperties: Partial<Node> = {}
|
||||
|
||||
// You can't set properties on the editor node.
|
||||
if (path.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const k in props) {
|
||||
if (
|
||||
k === 'marks' ||
|
||||
k === 'children' ||
|
||||
k === 'selection' ||
|
||||
k === 'text'
|
||||
) {
|
||||
if (k === 'children' || k === 'text') {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -540,7 +550,7 @@ export const NodeTransforms = {
|
||||
let after = Editor.after(editor, voidPath)
|
||||
|
||||
if (!after) {
|
||||
const text = { text: '', marks: [] }
|
||||
const text = { text: '' }
|
||||
const afterPath = Path.next(voidPath)
|
||||
Editor.insertNodes(editor, text, { at: afterPath })
|
||||
after = Editor.point(editor, afterPath)!
|
||||
@@ -581,7 +591,7 @@ export const NodeTransforms = {
|
||||
|
||||
if (always || !beforeRef || !Editor.isEdge(editor, point, path)) {
|
||||
split = true
|
||||
const { text, marks, children, ...properties } = node
|
||||
const { text, children, ...properties } = node
|
||||
editor.apply({
|
||||
type: 'split_node',
|
||||
path,
|
||||
|
@@ -1,68 +0,0 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Path, Text } from '..'
|
||||
|
||||
/**
|
||||
* `Mark` objects represent formatting that is applied to text in a Slate
|
||||
* document. They appear in leaf text nodes in the document.
|
||||
*/
|
||||
|
||||
export interface Mark {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const Mark = {
|
||||
/**
|
||||
* Check if a mark exists in a set of marks.
|
||||
*/
|
||||
|
||||
exists(mark: Mark, marks: Mark[]): boolean {
|
||||
return !!marks.find(f => Mark.matches(f, mark))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value implements the `Mark` interface.
|
||||
*/
|
||||
|
||||
isMark(value: any): value is Mark {
|
||||
return isPlainObject(value)
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value is an array of `Mark` objects.
|
||||
*/
|
||||
|
||||
isMarkSet(value: any): value is Mark[] {
|
||||
return Array.isArray(value) && (value.length === 0 || Mark.isMark(value[0]))
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a mark matches set of properties.
|
||||
*/
|
||||
|
||||
matches(mark: Mark, props: Partial<Mark>): boolean {
|
||||
for (const key in props) {
|
||||
if (mark[key] !== props[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* `MarkEntry` tuples are returned when iterating through the marks in a text
|
||||
* node. They include the index of the mark in the text node's marks array, as
|
||||
* well as the text node and its path in the root node.
|
||||
*/
|
||||
|
||||
export type MarkEntry = [Mark, number, Text, Path]
|
||||
|
||||
/**
|
||||
* `MarkMatch` values are used as shorthands for matching mark objects.
|
||||
*/
|
||||
|
||||
export type MarkMatch =
|
||||
| Partial<Mark>
|
||||
| ((entry: MarkEntry) => boolean)
|
||||
| MarkMatch[]
|
@@ -1,14 +1,5 @@
|
||||
import { produce } from 'immer'
|
||||
import {
|
||||
Editor,
|
||||
Element,
|
||||
ElementEntry,
|
||||
MarkEntry,
|
||||
Path,
|
||||
Range,
|
||||
Text,
|
||||
TextEntry,
|
||||
} from '..'
|
||||
import { Editor, Element, ElementEntry, Path, Range, Text, TextEntry } from '..'
|
||||
|
||||
/**
|
||||
* The `Node` union type represents all of the different types of nodes that
|
||||
@@ -372,27 +363,6 @@ export const Node = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an iterable of all the marks in all of the text nodes in a root node.
|
||||
*/
|
||||
|
||||
*marks(
|
||||
root: Node,
|
||||
options: {
|
||||
from?: Path
|
||||
to?: Path
|
||||
reverse?: boolean
|
||||
pass?: (node: NodeEntry) => boolean
|
||||
} = {}
|
||||
): Iterable<MarkEntry> {
|
||||
for (const [node, path] of Node.texts(root, options)) {
|
||||
for (let i = 0; i < node.marks.length; i++) {
|
||||
const mark = node.marks[i]
|
||||
yield [mark, i, node, path]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Return an iterable of all the node entries of a root node. Each entry is
|
||||
* returned as a `[Node, Path]` tuple, with the path referring to the node's
|
||||
|
@@ -1,13 +1,6 @@
|
||||
import { Mark, Node, Path, Range } from '..'
|
||||
import { Node, Path, Range } from '..'
|
||||
import isPlainObject from 'is-plain-object'
|
||||
|
||||
type AddMarkOperation = {
|
||||
type: 'add_mark'
|
||||
path: Path
|
||||
mark: Mark
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type InsertNodeOperation = {
|
||||
type: 'insert_node'
|
||||
path: Path
|
||||
@@ -39,13 +32,6 @@ type MoveNodeOperation = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type RemoveMarkOperation = {
|
||||
type: 'remove_mark'
|
||||
path: Path
|
||||
mark: Mark
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type RemoveNodeOperation = {
|
||||
type: 'remove_node'
|
||||
path: Path
|
||||
@@ -61,14 +47,6 @@ type RemoveTextOperation = {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type SetMarkOperation = {
|
||||
type: 'set_mark'
|
||||
path: Path
|
||||
properties: Partial<Mark>
|
||||
newProperties: Partial<Mark>
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
type SetNodeOperation = {
|
||||
type: 'set_node'
|
||||
path: Path
|
||||
@@ -113,11 +91,7 @@ type SplitNodeOperation = {
|
||||
* collaboration, and other features.
|
||||
*/
|
||||
|
||||
type Operation =
|
||||
| NodeOperation
|
||||
| MarkOperation
|
||||
| SelectionOperation
|
||||
| TextOperation
|
||||
type Operation = NodeOperation | SelectionOperation | TextOperation
|
||||
|
||||
type NodeOperation =
|
||||
| InsertNodeOperation
|
||||
@@ -127,8 +101,6 @@ type NodeOperation =
|
||||
| SetNodeOperation
|
||||
| SplitNodeOperation
|
||||
|
||||
type MarkOperation = AddMarkOperation | RemoveMarkOperation | SetMarkOperation
|
||||
|
||||
type SelectionOperation = SetSelectionOperation
|
||||
|
||||
type TextOperation = InsertTextOperation | RemoveTextOperation
|
||||
@@ -142,14 +114,6 @@ const Operation = {
|
||||
return Operation.isOperation(value) && value.type.endsWith('_node')
|
||||
},
|
||||
|
||||
/**
|
||||
* Check of a value is a `MarkOperation` object.
|
||||
*/
|
||||
|
||||
isMarkOperation(value: any): value is MarkOperation {
|
||||
return Operation.isOperation(value) && value.type.endsWith('_mark')
|
||||
},
|
||||
|
||||
/**
|
||||
* Check of a value is an `Operation` object.
|
||||
*/
|
||||
@@ -160,10 +124,6 @@ const Operation = {
|
||||
}
|
||||
|
||||
switch (value.type) {
|
||||
case 'add_mark': {
|
||||
return Path.isPath(value.path) && Mark.isMark(value.mark)
|
||||
}
|
||||
|
||||
case 'insert_node': {
|
||||
return Path.isPath(value.path) && Node.isNode(value.node)
|
||||
}
|
||||
@@ -189,10 +149,6 @@ const Operation = {
|
||||
return Path.isPath(value.path) && Path.isPath(value.newPath)
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
return Path.isPath(value.path) && Mark.isMark(value.mark)
|
||||
}
|
||||
|
||||
case 'remove_node': {
|
||||
return Path.isPath(value.path) && Node.isNode(value.node)
|
||||
}
|
||||
@@ -205,14 +161,6 @@ const Operation = {
|
||||
)
|
||||
}
|
||||
|
||||
case 'set_mark': {
|
||||
return (
|
||||
Path.isPath(value.path) &&
|
||||
isPlainObject(value.properties) &&
|
||||
isPlainObject(value.newProperties)
|
||||
)
|
||||
}
|
||||
|
||||
case 'set_node': {
|
||||
return (
|
||||
Path.isPath(value.path) &&
|
||||
@@ -285,10 +233,6 @@ const Operation = {
|
||||
|
||||
inverse(op: Operation): Operation {
|
||||
switch (op.type) {
|
||||
case 'add_mark': {
|
||||
return { ...op, type: 'remove_mark' }
|
||||
}
|
||||
|
||||
case 'insert_node': {
|
||||
return { ...op, type: 'remove_node' }
|
||||
}
|
||||
@@ -317,10 +261,6 @@ const Operation = {
|
||||
return { ...op, path: inversePath, newPath: inverseNewPath }
|
||||
}
|
||||
|
||||
case 'remove_mark': {
|
||||
return { ...op, type: 'add_mark' }
|
||||
}
|
||||
|
||||
case 'remove_node': {
|
||||
return { ...op, type: 'insert_node' }
|
||||
}
|
||||
@@ -329,7 +269,6 @@ const Operation = {
|
||||
return { ...op, type: 'insert_text' }
|
||||
}
|
||||
|
||||
case 'set_mark':
|
||||
case 'set_node': {
|
||||
const { properties, newProperties } = op
|
||||
return { ...op, properties: newProperties, newProperties: properties }
|
||||
@@ -363,15 +302,12 @@ const Operation = {
|
||||
}
|
||||
|
||||
export {
|
||||
AddMarkOperation,
|
||||
InsertNodeOperation,
|
||||
InsertTextOperation,
|
||||
MergeNodeOperation,
|
||||
MoveNodeOperation,
|
||||
RemoveMarkOperation,
|
||||
RemoveNodeOperation,
|
||||
RemoveTextOperation,
|
||||
SetMarkOperation,
|
||||
SetNodeOperation,
|
||||
SetSelectionOperation,
|
||||
SplitNodeOperation,
|
||||
|
@@ -1,29 +1,58 @@
|
||||
import isPlainObject from 'is-plain-object'
|
||||
import { Mark, Path } from '..'
|
||||
import { Path } from '..'
|
||||
|
||||
/**
|
||||
* `Text` objects represent the nodes that contain the actual text content of a
|
||||
* Slate document along with any formatting marks. They are always leaf nodes in
|
||||
* the document tree as they cannot contain any children.
|
||||
* Slate document along with any formatting properties. They are always leaf
|
||||
* nodes in the document tree as they cannot contain any children.
|
||||
*/
|
||||
|
||||
export interface Text {
|
||||
text: string
|
||||
marks: Mark[]
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export const Text = {
|
||||
/**
|
||||
* Check if two text nodes are equal.
|
||||
*/
|
||||
|
||||
equals(
|
||||
text: Text,
|
||||
another: Text,
|
||||
options: { loose?: boolean } = {}
|
||||
): boolean {
|
||||
const { loose = false } = options
|
||||
|
||||
for (const key in text) {
|
||||
if (loose && key === 'text') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (text[key] !== another[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (const key in another) {
|
||||
if (loose && key === 'text') {
|
||||
continue
|
||||
}
|
||||
|
||||
if (text[key] !== another[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if a value implements the `Text` interface.
|
||||
*/
|
||||
|
||||
isText(value: any): value is Text {
|
||||
return (
|
||||
isPlainObject(value) &&
|
||||
typeof value.text === 'string' &&
|
||||
Array.isArray(value.marks)
|
||||
)
|
||||
return isPlainObject(value) && typeof value.text === 'string'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -38,8 +67,7 @@ export const Text = {
|
||||
* Check if an text matches set of properties.
|
||||
*
|
||||
* Note: this is for matching custom properties, and it does not ensure that
|
||||
* the `text` property are two nodes equal. However, if `marks` are passed it
|
||||
* will ensure that the set of marks is exactly equal.
|
||||
* the `text` property are two nodes equal.
|
||||
*/
|
||||
|
||||
matches(text: Text, props: Partial<Text>): boolean {
|
||||
@@ -48,30 +76,6 @@ export const Text = {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'marks' && props.marks != null) {
|
||||
const existing = text.marks
|
||||
const { marks } = props
|
||||
|
||||
// PERF: If the lengths aren't the same, we know it's not a match.
|
||||
if (existing.length !== marks.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const m of existing) {
|
||||
if (!Mark.exists(m, marks)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for (const m of marks) {
|
||||
if (!Mark.exists(m, existing)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if (text[key] !== props[key]) {
|
||||
return false
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import { Element } from 'slate'
|
||||
|
||||
export const input = {
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
||||
export const test = value => {
|
||||
|
@@ -3,7 +3,6 @@ import { Element } from 'slate'
|
||||
export const input = [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
element: { children: [], type: 'bold', other: true },
|
||||
props: { type: 'bold' },
|
||||
}
|
||||
|
||||
export const test = ({ element, props }) => {
|
||||
return Mark.matches(element, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
element: { children: [], type: 'bold' },
|
||||
props: {},
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
marks: [{ type: 'bold' }],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
marks: [{ type: 'italic' }],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
marks: [{ type: 'bold', other: true }],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: {},
|
||||
marks: [{}],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: {},
|
||||
marks: [{ type: 'bold' }],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: {},
|
||||
marks: [],
|
||||
}
|
||||
|
||||
export const test = ({ mark, marks }) => {
|
||||
return Mark.exists(mark, marks)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = true
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMark(value)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,11 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
custom: 'value',
|
||||
}
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMark(value)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {}
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMark(value)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = true
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMarkSet(value)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = []
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMarkSet(value)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = [{}]
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMarkSet(value)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,9 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {}
|
||||
|
||||
export const test = value => {
|
||||
return Mark.isMarkSet(value)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
props: { type: 'bold' },
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
props: { type: 'italic' },
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = false
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold', other: true },
|
||||
props: { type: 'bold' },
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: {},
|
||||
props: {},
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -1,12 +0,0 @@
|
||||
import { Mark } from 'slate'
|
||||
|
||||
export const input = {
|
||||
mark: { type: 'bold' },
|
||||
props: {},
|
||||
}
|
||||
|
||||
export const test = ({ mark, props }) => {
|
||||
return Mark.matches(mark, props)
|
||||
}
|
||||
|
||||
export const output = true
|
@@ -2,7 +2,6 @@ import { Node } from 'slate'
|
||||
|
||||
export const input = {
|
||||
text: '',
|
||||
marks: [],
|
||||
}
|
||||
|
||||
export const test = value => {
|
||||
|
@@ -3,7 +3,6 @@ import { Node } from 'slate'
|
||||
export const input = [
|
||||
{
|
||||
text: '',
|
||||
marks: [],
|
||||
},
|
||||
]
|
||||
|
||||
|
@@ -1,22 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Node } from 'slate'
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark key="a">one</mark>
|
||||
<mark key="b">two</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const test = value => {
|
||||
return Array.from(Node.marks(value))
|
||||
}
|
||||
|
||||
export const output = [
|
||||
[{ key: 'a' }, 0, <mark key="a">one</mark>, [0, 0]],
|
||||
[{ key: 'b' }, 0, <mark key="b">two</mark>, [0, 1]],
|
||||
]
|
@@ -1,19 +0,0 @@
|
||||
/** @jsx jsx */
|
||||
|
||||
import { Node } from 'slate'
|
||||
import { jsx } from 'slate-hyperscript'
|
||||
|
||||
export const input = (
|
||||
<editor>
|
||||
<element>
|
||||
<mark key="a">one</mark>
|
||||
<mark key="b">two</mark>
|
||||
</element>
|
||||
</editor>
|
||||
)
|
||||
|
||||
export const test = value => {
|
||||
return Array.from(Node.marks(value, { from: [0, 1] }))
|
||||
}
|
||||
|
||||
export const output = [[{ key: 'b' }, 0, <mark key="b">two</mark>, [0, 1]]]
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user