slate#Text TypeScript Examples

The following examples show how to use slate#Text. 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: utils.ts    From slate-ot with MIT License 7 votes vote down vote up
checkOp = (snapshot: Editor, op: Operation) => {
  switch (op.type) {
    case 'remove_text': {
      const leaf = Node.leaf(snapshot, op.path);
      const textToRemove = leaf.text.slice(
        op.offset,
        op.offset + op.text.length
      );

      expect(textToRemove).toBe(op.text);

      break;
    }

    case 'merge_node': {
      const prev = Node.get(snapshot, Path.previous(op.path));

      const prevLen = Text.isText(prev)
        ? prev.text.length
        : prev.children.length;

      expect(prevLen).toBe(op.position);

      break;
    }

    case 'remove_node': {
      // op.node needs to be checked
      break;
    }

    default:
      return;
  }
}
Example #2
Source File: MessageFormatEditor.tsx    From project-loved-web with MIT License 6 votes vote down vote up
export default function MessageFormatEditor({
  className,
  readOnly,
  setValue,
  value,
  ...props
}: MessageFormatEditorProps & TextareaHTMLAttributes<HTMLDivElement>) {
  const editor = useMemo(() => withReact(createEditor()), []);

  return (
    <Slate
      editor={editor}
      onChange={(value) => {
        if (!readOnly) {
          setValue!(((value[0] as Element).children[0] as Text).text);
        }
      }}
      value={[{ children: [{ text: value, type: 0 }] }]}
    >
      <Editable
        {...props}
        className={`slate-editor${readOnly ? '' : ' editable'} ${className ?? ''}`}
        decorate={decorate}
        onKeyDown={onKeyDown}
        readOnly={readOnly}
        renderLeaf={renderLeaf}
      />
    </Slate>
  );
}
Example #3
Source File: advanced-editable.component.ts    From slate-angular with MIT License 6 votes vote down vote up
generateDecorate(keywords: string) {
        this.decorate = ([node, path]) => {
            const ranges = [];

            if (keywords && Text.isText(node)) {
                const { text } = node;
                const parts = text.split(keywords);
                let offset = 0;

                parts.forEach((part, i) => {
                    if (i !== 0) {
                        ranges.push({
                            anchor: { path, offset: offset - keywords.length },
                            focus: { path, offset },
                            highlight: true,
                        });
                    }

                    offset = offset + part.length + keywords.length;
                });
            }

            return ranges;
        };
    }
Example #4
Source File: leaves.component.ts    From slate-angular with MIT License 6 votes vote down vote up
ngOnChanges(simpleChanges: SimpleChanges) {
        if (!this.initialized) {
            return;
        }
        const context = simpleChanges['context'];
        const previousValue: SlateTextContext = context.previousValue;
        const currentValue: SlateTextContext = context.currentValue;
        if (previousValue.text !== currentValue.text || !isDecoratorRangeListEqual(previousValue.decorations, currentValue.decorations)) {
            this.leaves = Text.decorations(this.context.text, this.context.decorations);
        }
        this.leafContexts = this.getLeafCotexts();
    }
Example #5
Source File: search-highlighting.component.ts    From slate-angular with MIT License 6 votes vote down vote up
generateDecorate() {
        this.decorate = ([node, path]) => {
            const ranges = [];

            if (this.keywords && Text.isText(node)) {
                const { text } = node;
                const parts = text.split(this.keywords);
                let offset = 0;

                parts.forEach((part, i) => {
                    if (i !== 0) {
                        ranges.push({
                            anchor: { path, offset: offset - this.keywords.length },
                            focus: { path, offset },
                            highlight: true,
                        });
                    }

                    offset = offset + part.length + this.keywords.length;
                });
            }

            return ranges;
        };
    }
Example #6
Source File: query_field.tsx    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
makeFragment = (text: string) => {
  const lines = text.split('\n').map((line: any) =>
    Block.create({
      type: 'paragraph',
      nodes: [Text.create(line)],
    } as any)
  );

  const fragment = Document.create({
    nodes: lines,
  });
  return fragment;
}
Example #7
Source File: slate.ts    From grafana-chinese with Apache License 2.0 6 votes vote down vote up
makeFragment = (text: string, syntax?: string): Document => {
  const lines = text.split('\n').map(line =>
    Block.create({
      type: 'code_line',
      nodes: [Text.create(line)],
    })
  );

  const block = Block.create({
    data: {
      syntax,
    },
    type: 'code_block',
    nodes: lines,
  });

  return Document.create({
    nodes: [block],
  });
}
Example #8
Source File: op-generator.ts    From slate-ot with MIT License 6 votes vote down vote up
generateRandomSplitNodeOp = (snapshot): Operation | null => {
  const randomPath = getRandomPathFrom(snapshot);

  const node = Node.get(snapshot, randomPath);
  const position = Text.isText(node)
    ? <number>fuzzer.randomInt(node.text.length + 1)
    : <number>fuzzer.randomInt(node.children.length + 1);

  return randomPath.length
    ? {
        type: 'split_node',
        path: randomPath,
        position,
        target: null,
        properties: {},
      }
    : null;
}
Example #9
Source File: op-generator.ts    From slate-ot with MIT License 6 votes vote down vote up
generateRandomInsertNodeOp = (snapshot): Operation => {
  const randomPath = getRandomPathTo(snapshot);

  const parent = <Ancestor>Node.get(snapshot, Path.parent(randomPath));

  let node;

  if (parent.children[0] && Text.isText(parent.children[0])) {
    node = { text: fuzzer.randomWord() };
  } else if (!parent.children[0] && fuzzer.randomInt(3) === 0) {
    node = { text: fuzzer.randomWord() };
  } else if (fuzzer.randomInt(2) === 0) {
    node = {
      type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
      children: [{ text: fuzzer.randomWord() }, { text: fuzzer.randomWord() }],
    };
  } else {
    node = {
      type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
      children: [
        {
          type: BLOCKS[fuzzer.randomInt(BLOCKS.length)],
          children: [
            { text: fuzzer.randomWord() },
            { text: fuzzer.randomWord() },
          ],
        },
      ],
    };
  }

  return {
    type: 'insert_node',
    path: randomPath,
    node,
  };
}
Example #10
Source File: op-generator.ts    From slate-ot with MIT License 6 votes vote down vote up
getRandomPathTo = (snapshot: Node): Path => {
  const path: Path = [];
  // let currentNode = snapshot;
  while (1) {
    // stop when you get to a leaf
    if (Text.isText(snapshot)) {
      return path;
    }

    if (snapshot.children.length === 0) {
      return [...path, 0];
    }

    // randomly stop at the next level
    if (fuzzer.randomInt(3) === 0) {
      const index = <number>fuzzer.randomInt(snapshot.children.length + 1);
      return [...path, index];
    }

    // continue
    const index = <number>fuzzer.randomInt(snapshot.children.length);
    path.push(index);
    snapshot = snapshot.children[index];
  }
  return path;
}
Example #11
Source File: op-generator.ts    From slate-ot with MIT License 6 votes vote down vote up
getRandomPathFrom = (snapshot: Node): Path => {
  if (Text.isText(snapshot) || snapshot.children.length === 0) {
    return [];
  }

  const path: Path = [];
  // let currentNode = snapshot;
  while (1) {
    // stop when you get to a leaf
    if (Text.isText(snapshot)) {
      return path;
    }

    if (snapshot.children.length === 0) {
      return path;
    }

    if (fuzzer.randomInt(3) === 0 && path.length > 0) {
      return path;
    }

    // continue
    const index = <number>fuzzer.randomInt(snapshot.children.length);
    path.push(index);
    snapshot = snapshot.children[index];
  }
  return path;
}
Example #12
Source File: MessageFormatEditor.tsx    From project-loved-web with MIT License 6 votes vote down vote up
function decorate([node, path]: NodeEntry): Range[] {
  if (!Text.isText(node)) {
    return [];
  }

  const ranges: Range[] = [];

  try {
    addRanges(ranges, path, parse(node.text, { captureLocation: true }));
  } catch {}

  return ranges;
}
Example #13
Source File: leaf.tsx    From slate-vue with MIT License 5 votes vote down vote up
Leaf = tsx.component({
  props: {
    text: {
      type: Object as PropType<Text>
    },
    leaf:  {
      type: Object as PropType<Text>
    },
  },
  inject: ['renderLeaf'],
  components: {
    string,
    fragment
  },
  // For types
  data(): Pick<providedByEditable, 'renderLeaf'> {
    return {}
  },
  render(h): VNode {
    const { renderLeaf = DefaultLeaf, text, leaf} = this;
    let children =  (
      <string text={text} editor={this.$editor} leaf={leaf}/>
      );
    if ((leaf as any)[PLACEHOLDER_SYMBOL]) {
      children = (
        <fragment>
          <span
            contenteditable={false}
            style={{
              pointerEvents: 'none',
              display: 'inline-block',
              verticalAlign: 'text-top',
              width: '0',
              maxWidth: '100%',
              whiteSpace: 'nowrap',
              opacity: '0.333',
            }}
          >
            {(leaf as any).placeholder}
          </span>
          {children}
        </fragment>
      )
    }

    const attributes: {
     'data-slate-leaf': true
    } = {
     'data-slate-leaf': true,
    };
    const renderChildren = renderLeaf({
      children,
      attributes,
      leaf,
      text
    })
    return h(renderChildren)
  }
})
Example #14
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 #15
Source File: advanced-editable.component.ts    From slate-angular with MIT License 5 votes vote down vote up
renderLeaf = (text: Text) => {
        if (text['highlight']) {
            return TestingLeafComponent;
        }
        return null;
    }
Example #16
Source File: leaves.component.ts    From slate-angular with MIT License 5 votes vote down vote up
leaves: Text[];
Example #17
Source File: leaves.component.ts    From slate-angular with MIT License 5 votes vote down vote up
ngOnInit() {
        this.leaves = Text.decorations(this.context.text, this.context.decorations);
        this.leafContexts = this.getLeafCotexts();
        this.initialized = true;
    }
Example #18
Source File: tables.component.ts    From slate-angular with MIT License 5 votes vote down vote up
renderText = (text: Text) => {
    if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
      return DemoTextMarkComponent;
    }
  }
Example #19
Source File: search-highlighting.component.ts    From slate-angular with MIT License 5 votes vote down vote up
renderLeaf = (text: Text) => {
        if (text['highlight']) {
            return DemoLeafComponent;
        }
        return null;
    }
Example #20
Source File: search-highlighting.component.ts    From slate-angular with MIT License 5 votes vote down vote up
renderText = (text: Text) => {
        if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
            return DemoTextMarkComponent;
        }
    }
Example #21
Source File: richtext.component.ts    From slate-angular with MIT License 5 votes vote down vote up
renderText = (text: Text) => {
        if (text[MarkTypes.bold] || text[MarkTypes.italic] || text[MarkTypes.code] || text[MarkTypes.underline]) {
            return DemoTextMarkComponent;
        }
    }
Example #22
Source File: string.tsx    From slate-vue with MIT License 5 votes vote down vote up
string = tsx.component({
  props: {
    leaf: {
      type: Object as PropType<Text>
    },
    editor: Object
  },
  inject: ['isLast', 'parent', 'text'],
  components: {
    TextString
  },
  data(): providedByText {
    return {}
  },
  render() {
    const { leaf, editor, isLast, parent, text } = this
    const path = VueEditor.findPath(editor, text as Node)
    const parentPath = Path.parent(path)

    // COMPAT: Render text inside void nodes with a zero-width space.
    // So the node can contain selection but the text is not visible.
    if (editor.isVoid(parent)) {
      return <ZeroWidthString length={Node.string(parent as Node).length} />
    }

    // COMPAT: If this is the last text node in an empty block, render a zero-
    // width space that will convert into a line break when copying and pasting
    // to support expected plain text.
    if (
      leaf.text === '' &&
      (parent as Element & Editor).children[(parent as Element & Editor).children.length - 1] === text &&
      !editor.isInline(parent) &&
      Editor.string(editor, parentPath) === ''
    ) {
      return <ZeroWidthString isLineBreak={true} />
    }

    // COMPAT: If the text is empty, it's because it's on the edge of an inline
    // node, so we render a zero-width space so that the selection can be
    // inserted next to it still.
    if (leaf.text === '') {
      return <ZeroWidthString />
    }

    // COMPAT: Browsers will collapse trailing new lines at the end of blocks,
    // so we need to add an extra trailing new lines to prevent that.
    if (isLast && leaf.text.slice(-1) === '\n') {
      return <TextString isTrailing={true} text={leaf.text} />
    }

    return <TextString text={leaf.text} />
  }
})
Example #23
Source File: index.ts    From grafana-chinese with Apache License 2.0 5 votes vote down vote up
/**
 * Return a decoration range for the given text.
 */
function createDecoration({
  text,
  textStart,
  textEnd,
  start,
  end,
  className,
  block,
}: {
  text: Text; // The text being decorated
  textStart: number; // Its start position in the whole text
  textEnd: number; // Its end position in the whole text
  start: number; // The position in the whole text where the token starts
  end: number; // The position in the whole text where the token ends
  className: string; // The prism token classname
  block: Block;
}): Decoration | null {
  if (start >= textEnd || end <= textStart) {
    // Ignore, the token is not in the text
    return null;
  }

  // Shrink to this text boundaries
  start = Math.max(start, textStart);
  end = Math.min(end, textEnd);

  // Now shift offsets to be relative to this text
  start -= textStart;
  end -= textStart;

  const myDec = block.createDecoration({
    object: 'decoration',
    anchor: {
      key: text.key,
      offset: start,
      object: 'point',
    },
    focus: {
      key: text.key,
      offset: end,
      object: 'point',
    },
    type: TOKEN_MARK,
    data: { className },
  });

  return myDec;
}
Example #24
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 #25
Source File: op-generator.ts    From slate-ot with MIT License 5 votes vote down vote up
generateRandomMergeNodeOp = (snapshot): Operation | null => {
  const randomPath = getRandomPathFrom(snapshot);

  if (randomPath.length == 0 || randomPath[randomPath.length - 1] == 0) {
    return null;
  }

  const prev = Node.get(snapshot, Path.previous(randomPath));

  const node = Node.get(snapshot, randomPath);

  const properties = {};

  // Object.keys(prev).forEach((key) => {
  //   if (key !== 'text' && key !== 'children') {
  //     properties[key] = null;
  //   }
  // });
  // Object.keys(node).forEach((key) => {
  //   if (key !== 'text' && key !== 'children') {
  //     properties[key] = node[key];
  //   }
  // });

  if (Text.isText(prev) && Text.isText(node)) {
    return {
      type: 'merge_node',
      path: randomPath,
      position: prev.text.length,
      target: null,
      properties,
    };
  }

  if (!Text.isText(prev) && !Text.isText(node)) {
    return {
      type: 'merge_node',
      path: randomPath,
      position: prev.children.length,
      target: null,
      properties,
    };
  }

  return null;
}
Example #26
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 #27
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 #28
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 #29
Source File: stringifyRichText.ts    From payload with MIT License 5 votes vote down vote up
stringifyRichText = (content: unknown): string => {
  if (Array.isArray(content)) {
    return content.reduce((output, node) => {
      const isTextNode = Text.isText(node);

      const {
        text,
      } = node;

      if (isTextNode) {
        // convert straight single quotations to curly
        // "\u201C" is starting double curly
        // "\u201D" is ending double curly
        const sanitizedText = text?.replace(/'/g, '\u2019'); // single quotes
        return `${output}${sanitizedText}`;
      }

      if (node) {
        let nodeHTML;
        switch (node.type) {
          case 'h1':
          case 'h2':
          case 'h3':
          case 'h4':
          case 'h5':
          case 'h6':
          case 'li':
          case 'p':
          case undefined:
            nodeHTML = `${stringifyRichText(node.children)}\n`;
            break;

          case 'ul':
          case 'ol':
            nodeHTML = `${stringifyRichText(node.children)}\n\n`;
            break;

          case 'link':
            nodeHTML = `${stringifyRichText(node.children)}`;
            break;

          case 'relationship':
            nodeHTML = `Relationship to ${node.relationTo}: ${node?.value?.id}\n\n`;
            break;

          case 'upload':
            nodeHTML = `${node.relationTo} Upload: ${node?.value?.id}\n\n`;
            break;

          default:
            nodeHTML = `${node.type}: ${JSON.stringify(node)}\n\n`;
            break;
        }

        return `${output}${nodeHTML}`;
      }

      return output;
    }, '');
  }

  return '';
}