slate#Descendant TypeScript Examples
The following examples show how to use
slate#Descendant.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: placeholder.component.ts From slate-angular with MIT License | 6 votes |
initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text: '',
},
],
},
]
Example #2
Source File: readonly.component.ts From slate-angular with MIT License | 6 votes |
initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
'This example shows what happens when the Editor is set to readOnly, it is not editable',
},
],
},
]
Example #3
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
transform = function(editor: Editor, op: Operation) {
switch (op.type) {
case 'insert_node': {
const { path, node } = op
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
getChildren(parent).splice(index, 0, clone(node))
break
}
case 'insert_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset)
node.text = before + text + after
break
}
case 'merge_node': {
const { path } = op
const node = Node.get(editor, path)
const prevPath = Path.previous(path)
const prev = Node.get(editor, prevPath)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
if (Text.isText(node) && Text.isText(prev)) {
prev.text += node.text
} else if (!Text.isText(node) && !Text.isText(prev)) {
getChildren(prev).push(...getChildren(node))
} else {
throw new Error(
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interaces: ${node} ${prev}`
)
}
getChildren(parent).splice(index, 1)
break
}
case 'move_node': {
const { path, newPath } = op
if (Path.isAncestor(path, newPath)) {
throw new Error(
`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
// This is tricky, but since the `path` and `newPath` both refer to
// the same snapshot in time, there's a mismatch. After either
// removing the original position, the second step's path can be out
// of date. So instead of using the `op.newPath` directly, we
// transform `op.path` to ascertain what the `newPath` would be after
// the operation was applied.
getChildren(parent).splice(index, 1)
const truePath = Path.transform(path, op)!
const newParent = Node.get(editor, Path.parent(truePath))
const newIndex = truePath[truePath.length - 1]
getChildren(newParent).splice(newIndex, 0, node)
break
}
case 'remove_node': {
const { path } = op
NODE_TO_KEY.delete(Node.get(editor, path))
const index = path[path.length - 1]
const parent = Node.parent(editor, path)
getChildren(parent).splice(index, 1)
break
}
case 'remove_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset + text.length)
node.text = before + after
break
}
case 'set_node': {
const { path, newProperties } = op
if (path.length === 0) {
throw new Error(`Cannot set properties on the root node!`)
}
const node = Node.get(editor, path)
for (const key in newProperties) {
if (key === 'children' || key === 'text') {
throw new Error(`Cannot set the "${key}" property of nodes!`)
}
const value = (newProperties as any)[key]
if (value == null) {
Vue.delete(node, key)
} else {
Vue.set(node, key, value)
}
}
break
}
case 'set_selection': {
break
}
case 'split_node': {
const { path, position, properties } = op
if (path.length === 0) {
throw new Error(
`Cannot apply a "split_node" operation at path [${path}] because the root node cannot be split.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
let newNode: Descendant
if (Text.isText(node)) {
const before = node.text.slice(0, position)
const after = node.text.slice(position)
node.text = before
newNode = {
...node,
...(properties as Partial<Text>),
text: after,
}
} else {
const before = node.children.slice(0, position)
const after = node.children.slice(position)
node.children = before
newNode = {
...node,
...(properties as Partial<Element>),
children: after,
}
}
getChildren(parent).splice(index + 1, 0, newNode)
break
}
}
}
Example #4
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
runtimeNode = {
child(root: Node, index: number): Descendant {
if (Text.isText(root)) {
throw new Error(
`Cannot get the child of a text node: ${JSON.stringify(root)}`
)
}
const c = getChildren(root)[index] as Descendant
if (c == null) {
throw new Error(
`Cannot get child at index \`${index}\` in node: ${JSON.stringify(
root
)}`
)
}
return c
},
has(root: Node, path: Path): boolean {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
return false
}
node = children[p]
}
return true
},
get(root: Node, path: Path): Node {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
throw new Error(
`Cannot find a descendant at path [${path}] in node: ${JSON.stringify(
root
)}`
)
}
node = children[p]
}
return node
},
first(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
n = children[0]
p.push(0)
}
}
return [n, p]
},
last(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
const i = children.length - 1
n = children[i]
p.push(i)
}
}
return [n, p]
},
*nodes(
root: Node,
options: {
from?: Path
to?: Path
reverse?: boolean
pass?: (entry: NodeEntry) => boolean
} = {}
): Generator<NodeEntry> {
const { pass, reverse = false } = options
const { from = [], to } = options
const visited = new Set()
let p: Path = []
let n = root
while (true) {
if (to && (reverse ? Path.isBefore(p, to) : Path.isAfter(p, to))) {
break
}
if (!visited.has(n)) {
yield [n, p]
}
// If we're allowed to go downward and we haven't decsended yet, do.
if (
!visited.has(n) &&
!Text.isText(n) &&
getChildren(n).length !== 0 &&
(pass == null || pass([n, p]) === false)
) {
visited.add(n)
let nextIndex = reverse ? getChildren(n).length - 1 : 0
if (Path.isAncestor(p, from)) {
nextIndex = from[p.length]
}
p = p.concat(nextIndex)
n = Node.get(root, p)
continue
}
// If we're at the root and we can't go down, we're done.
if (p.length === 0) {
break
}
// If we're going forward...
if (!reverse) {
const newPath = Path.next(p)
if (Node.has(root, newPath)) {
p = newPath
n = Node.get(root, p)
continue
}
}
// If we're going backward...
if (reverse && p[p.length - 1] !== 0) {
const newPath = Path.previous(p)
p = newPath
n = Node.get(root, p)
continue
}
// Otherwise we're going upward...
p = Path.parent(p)
n = Node.get(root, p)
visited.add(n)
}
}
}
Example #5
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
transform = function(editor: Editor, op: Operation, Vue?: any) {
switch (op.type) {
case 'insert_node': {
const { path, node } = op
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
getChildren(parent).splice(index, 0, clone(node))
break
}
case 'insert_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset)
node.text = before + text + after
break
}
case 'merge_node': {
const { path } = op
const node = Node.get(editor, path)
const prevPath = Path.previous(path)
const prev = Node.get(editor, prevPath)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
if (Text.isText(node) && Text.isText(prev)) {
prev.text += node.text
} else if (!Text.isText(node) && !Text.isText(prev)) {
getChildren(prev).push(...getChildren(node))
} else {
throw new Error(
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interaces: ${node} ${prev}`
)
}
getChildren(parent).splice(index, 1)
break
}
case 'move_node': {
const { path, newPath } = op
if (Path.isAncestor(path, newPath)) {
throw new Error(
`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
// This is tricky, but since the `path` and `newPath` both refer to
// the same snapshot in time, there's a mismatch. After either
// removing the original position, the second step's path can be out
// of date. So instead of using the `op.newPath` directly, we
// transform `op.path` to ascertain what the `newPath` would be after
// the operation was applied.
getChildren(parent).splice(index, 1)
const truePath = Path.transform(path, op)!
const newParent = Node.get(editor, Path.parent(truePath))
const newIndex = truePath[truePath.length - 1]
getChildren(newParent).splice(newIndex, 0, node)
break
}
case 'remove_node': {
const { path } = op
NODE_TO_KEY.delete(Node.get(editor, path))
const index = path[path.length - 1]
const parent = Node.parent(editor, path)
getChildren(parent).splice(index, 1)
break
}
case 'remove_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset + text.length)
node.text = before + after
break
}
case 'set_node': {
const { path, newProperties } = op
if (path.length === 0) {
throw new Error(`Cannot set properties on the root node!`)
}
const node = Node.get(editor, path)
for (const key in newProperties) {
if (key === 'children' || key === 'text') {
throw new Error(`Cannot set the "${key}" property of nodes!`)
}
const value = (newProperties as any)[key]
if(Vue) {
if (value == null) {
Vue.delete(node, key)
} else {
Vue.set(node, key, value)
}
}
}
break
}
case 'set_selection': {
break
}
case 'split_node': {
const { path, position, properties } = op
if (path.length === 0) {
throw new Error(
`Cannot apply a "split_node" operation at path [${path}] because the root node cannot be split.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
let newNode: Descendant
if (Text.isText(node)) {
const before = node.text.slice(0, position)
const after = node.text.slice(position)
node.text = before
newNode = {
...node,
...(properties as Partial<Text>),
text: after,
}
} else {
const before = node.children.slice(0, position)
const after = node.children.slice(position)
node.children = before
newNode = {
...node,
...(properties as Partial<Element>),
children: after,
}
}
getChildren(parent).splice(index + 1, 0, newNode)
break
}
}
}
Example #6
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
runtimeNode = {
child(root: Node, index: number): Descendant {
if (Text.isText(root)) {
throw new Error(
`Cannot get the child of a text node: ${JSON.stringify(root)}`
)
}
const c = getChildren(root)[index] as Descendant
if (c == null) {
throw new Error(
`Cannot get child at index \`${index}\` in node: ${JSON.stringify(
root
)}`
)
}
return c
},
has(root: Node, path: Path): boolean {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
return false
}
node = children[p]
}
return true
},
get(root: Node, path: Path): Node {
let node = root
for (let i = 0; i < path.length; i++) {
const p = path[i]
const children = getChildren(node)
if (Text.isText(node) || !children[p]) {
throw new Error(
`Cannot find a descendant at path [${path}] in node: ${JSON.stringify(
root
)}`
)
}
node = children[p]
}
return node
},
first(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
n = children[0]
p.push(0)
}
}
return [n, p]
},
last(root: Node, path: Path): NodeEntry {
const p = path.slice()
let n = Node.get(root, p)
const children = getChildren(n)
while (n) {
if (Text.isText(n) || children.length === 0) {
break
} else {
const i = children.length - 1
n = children[i]
p.push(i)
}
}
return [n, p]
},
*nodes(
root: Node,
options: {
from?: Path
to?: Path
reverse?: boolean
pass?: (entry: NodeEntry) => boolean
} = {}
): Generator<NodeEntry> {
const { pass, reverse = false } = options
const { from = [], to } = options
const visited = new Set()
let p: Path = []
let n = root
while (true) {
if (to && (reverse ? Path.isBefore(p, to) : Path.isAfter(p, to))) {
break
}
if (!visited.has(n)) {
yield [n, p]
}
// If we're allowed to go downward and we haven't decsended yet, do.
if (
!visited.has(n) &&
!Text.isText(n) &&
getChildren(n).length !== 0 &&
(pass == null || pass([n, p]) === false)
) {
visited.add(n)
let nextIndex = reverse ? getChildren(n).length - 1 : 0
if (Path.isAncestor(p, from)) {
nextIndex = from[p.length]
}
p = p.concat(nextIndex)
n = Node.get(root, p)
continue
}
// If we're at the root and we can't go down, we're done.
if (p.length === 0) {
break
}
// If we're going forward...
if (!reverse) {
const newPath = Path.next(p)
if (Node.has(root, newPath)) {
p = newPath
n = Node.get(root, p)
continue
}
}
// If we're going backward...
if (reverse && p[p.length - 1] !== 0) {
const newPath = Path.previous(p)
p = newPath
n = Node.get(root, p)
continue
}
// Otherwise we're going upward...
p = Path.parent(p)
n = Node.get(root, p)
visited.add(n)
}
}
}
Example #7
Source File: page.tsx From platyplus with MIT License | 5 votes |
Page: React.FC = () => {
const { slug } = useParams()
const { state: contents, setState: setContents } = usePage<Descendant[]>({
slug,
path: 'contents'
})
const isConfigEnabled = useConfigEnabled()
const { state: title, setState: setTitle } = usePageTitle({ slug })
const ref = useRef(null)
useClickAway(ref, () => setEditing(false))
const edit = () => isConfigEnabled && setEditing(true)
const [editing, setEditing] = useState(false)
return (
<HeaderTitleWrapper
title={title}
component={
<InlineValue
editable={isConfigEnabled}
value={title}
onChange={setTitle}
/>
}
>
<Animation.Fade in={!!contents}>
{(props) => (
<div {...props} ref={ref} style={{ height: '100%' }} onClick={edit}>
{contents && (
<RichText
readOnly={!editing}
value={contents}
onChange={setContents}
/>
)}
</div>
)}
</Animation.Fade>
</HeaderTitleWrapper>
)
}
Example #8
Source File: rich-text.tsx From platyplus with MIT License | 5 votes |
RichText: React.FC<
FormControlBaseProps<Descendant[]> & { readOnly?: boolean }
> = ({
value: valueProp,
defaultValue = [
{
type: 'paragraph',
children: [{ text: '' }]
}
],
// eslint-disable-next-line @typescript-eslint/no-empty-function
onChange = () => {},
readOnly = false
}) => {
const fallbackValue =
valueProp !== undefined && valueProp.length ? valueProp : defaultValue
const [state, setState] = useState(fallbackValue)
const renderElement = useCallback((props) => <Element {...props} />, [])
const renderLeaf = useCallback((props) => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
useEffect(() => {
// TODO doesn't work
setState(valueProp)
}, [valueProp, editor])
useEffect(() => {
if (!readOnly) {
ReactEditor.focus(editor)
}
}, [readOnly, editor])
return (
<Slate
editor={editor}
value={state}
onChange={(v) => {
if (!readOnly) {
setState(v)
const isAstChange = editor.operations.some(
(op) => 'set_selection' !== op.type
)
if (isAstChange) {
onChange(v, null)
}
}
}}
>
{!readOnly && (
<ButtonToolbar>
<MarkButton format="bold" icon="bold" />
<MarkButton format="italic" icon="italic" />
<MarkButton format="underline" icon="underline" />
<MarkButton format="code" icon="code" />
<BlockButton format="heading-one" icon="header" />
<BlockButton format="heading-two" icon="header" />
<BlockButton format="block-quote" icon="quote-left" />
<BlockButton format="numbered-list" icon="list-ol" />
<BlockButton format="bulleted-list" icon="list-ul" />
</ButtonToolbar>
)}
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}
placeholder="Enter some rich text…"
spellCheck
autoFocus
readOnly={readOnly}
onKeyDown={(event) => {
for (const hotkey in HOTKEYS) {
if (isHotkey(hotkey, event)) {
event.preventDefault()
const mark = HOTKEYS[hotkey]
toggleMark(editor, mark)
}
}
}}
/>
</Slate>
)
}
Example #9
Source File: inlines.component.ts From slate-angular with MIT License | 5 votes |
initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
'In addition to block nodes, you can create inline nodes. Here is a ',
},
{
type: 'link',
url: 'https://en.wikipedia.org/wiki/Hypertext',
children: [{ text: 'hyperlink' }],
},
{
text: ', and here is a more unusual inline: an ',
},
{
type: 'button',
children: [{ text: 'editable button' }],
},
{
text: '!',
},
],
},
{
type: 'paragraph',
children: [
{
text:
'There are two ways to add links. You can either add a link via the toolbar icon above, or if you want in on a little secret, copy a URL to your keyboard and paste it while a range of text is selected. ',
},
// The following is an example of an inline at the end of a block.
// This is an edge case that can cause issues.
{
type: 'link',
url: 'https://twitter.com/JustMissEmma/status/1448679899531726852',
children: [{ text: 'Finally, here is our favorite dog video.' }],
},
{ text: '' },
],
},
]
Example #10
Source File: children.component.ts From slate-angular with MIT License | 5 votes |
@Input() children: Descendant[];
Example #11
Source File: descendant.component.ts From slate-angular with MIT License | 5 votes |
@Input() descendant: Descendant;
Example #12
Source File: global-normalize.ts From slate-angular with MIT License | 5 votes |
isValid = (value: Descendant) => (
Element.isElement(value) &&
value.children.length > 0 &&
(value.children as Descendant[]).every((child) => isValid(child))) ||
Text.isText(value)
Example #13
Source File: children.tsx From slate-vue with MIT License | 4 votes |
Children: any = tsx.component({
props: {
// only element or editor
node: {
type: Object as PropType<Ancestor>
}
},
components: {
TextComponent,
ElementComponent,
fragment
},
mixins: [SlateMixin],
mounted() {
elementWatcherPlugin(this, 'children')
},
render() {
const editor = this.$editor;
const {node} = this;
const path = VueEditor.findPath(editor, node)
const isLeafBlock =
Element.isElement(node) &&
!editor.isInline(node) &&
Editor.hasInlines(editor, node)
const children = []
const childArr: any = Editor.isEditor(node) ? (node as VueEditor)._state : (node as Element).children
// cacheVnode in manual to reuse
let cacheVnode = null;
for(let i=0;i<childArr.length;i++) {
const n = childArr[i] as Descendant;
const key = VueEditor.findKey(editor, n)
const p = path.concat(i);
const range = Editor.range(editor, p)
// set n and its index in children
NODE_TO_INDEX.set(n, i)
// set n and its parent
NODE_TO_PARENT.set(n, node)
// when modify vnode, only new vnode or spliting vnode must be update, others will be reuse
// #62, #63: sometimes(like paste) no cacheVnode but have key, avoid getting in
if(editor._operation && KEY_TO_VNODE.get(key)) {
const operationPath = (editor._operation as any).path as Path
// split_node
if(editor._operation.type === 'split_node') {
// only sibling
if(Path.isSibling(p, operationPath)) {
if(!Path.equals(p, operationPath) && !Path.equals(p, Path.next(operationPath))) {
cacheVnode = KEY_TO_VNODE.get(key)
children.push(cacheVnode)
continue;
}
}
}
// merge_node
if(editor._operation.type === 'merge_node') {
const parentPath = Path.parent(operationPath)
if(Path.isSibling(p, parentPath)) {
if(!Path.isParent(p, operationPath)) {
cacheVnode = KEY_TO_VNODE.get(key)
children.push(cacheVnode)
continue;
}
}
}
// remove_node
if(editor._operation.type === 'remove_node') {
if(Path.isSibling(p, operationPath)) {
if(!Path.equals(p, operationPath)) {
cacheVnode = KEY_TO_VNODE.get(key)
children.push(cacheVnode)
continue;
}
}
}
}
if(Element.isElement(n)) {
// set selected
cacheVnode =
<ElementComponent
element={n}
key={key.id}
/>
children.push(cacheVnode)
} else {
cacheVnode = <TextComponent
isLast={isLeafBlock && i === childArr.length - 1}
parent={node}
text={n}
key={key.id}
/>
children.push(cacheVnode)
}
// set key and vnode
KEY_TO_VNODE.set(key, cacheVnode as any)
}
return <fragment>{children}</fragment>;
}
})