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 vote down vote up
initialValue: Descendant[] = [
    {
        type: 'paragraph',
        children: [
            {
                text: '',
            },
        ],
    },
]
Example #2
Source File: readonly.component.ts    From slate-angular with MIT License 6 votes vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
@Input() children: Descendant[];
Example #11
Source File: descendant.component.ts    From slate-angular with MIT License 5 votes vote down vote up
@Input() descendant: Descendant;
Example #12
Source File: global-normalize.ts    From slate-angular with MIT License 5 votes vote down vote up
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 vote down vote up
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>;
  }
})