slate#Element TypeScript Examples

The following examples show how to use slate#Element. 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: link.ts    From slate-yjs-example with MIT License 7 votes vote down vote up
withLinks = <T extends Editor>(editor: T): T & LinkEditor => {
  const e = editor as T & LinkEditor;

  const { insertData, insertText, isInline } = e;

  e.isInline = (element: Element) => {
    return element.type === "link" ? true : isInline(element);
  };

  e.insertText = (text: string): void => {
    if (text && isUrl(text)) {
      wrapLink(editor, text);
    } else {
      insertText(text);
    }
  };

  e.insertData = (data: DataTransfer): void => {
    const text = data.getData("text/plain");

    if (text && isUrl(text)) {
      wrapLink(editor, text);
    } else {
      insertData(data);
    }
  };

  return e;
}
Example #2
Source File: utils.ts    From slate-ot with MIT License 7 votes vote down vote up
initialDoc: Element = {
  children: [
    {
      type: 'Paragraph',
      children: [{ text: 'AB', italic: true }, { text: 'CD' }, { text: 'EF' }],
    },
    {
      type: 'NumberedList',
      children: [{ text: 'GH', bold: true }, { text: 'IJ' }, { text: 'KL' }],
    },
    {
      type: 'BulletedList',
      children: [{ text: 'MN' }, { text: 'OPQ' }, { text: 'RST' }],
    },
  ],
}
Example #3
Source File: utilities.tsx    From payload with MIT License 6 votes vote down vote up
unwrapLink = (editor: Editor): void => {
  Transforms.unwrapNodes(editor, { match: (n) => Element.isElement(n) && n.type === 'link' });
}
Example #4
Source File: ListPluginAction.ts    From react-editor-kit with MIT License 6 votes vote down vote up
ListPluginAction: PluginAction = {
  action: ({ editor, element, elementType }, plugin) => {
    if (elementType === "list-item") {
      element = getAncestor(editor, element as Element, 1) as Element;
    }
    if (!element) {
      return;
    }

    const other =
      plugin.name === "ordered-list" ? "unordered-list" : "ordered-list";

    if (element && element.type == other) {
      Transforms.setNodes(
        editor,
        { type: plugin.name, children: [] },
        { at: ReactEditor.findPath(editor, element) }
      );
    } else {
      return toggleList(editor, plugin.name);
    }
  },
  isActionActive: ({ editor, element, elementType }, plugin) => {
    if (elementType !== "list-item") {
      return false;
    }
    const parent = getAncestor(editor, element as Element, 1);
    return parent?.type == plugin.name;
  },
}
Example #5
Source File: descendant.component.ts    From slate-angular with MIT License 6 votes vote down vote up
getContext(): SlateElementContext | SlateTextContext {
        if (Element.isElement(this.descendant)) {
            const computedContext = this.getCommonContext();
            const key = AngularEditor.findKey(this.viewContext.editor, this.descendant);
            const isInline = this.viewContext.editor.isInline(this.descendant);
            const isVoid = this.viewContext.editor.isVoid(this.descendant);
            const elementContext: SlateElementContext = {
                element: this.descendant,
                ...computedContext,
                attributes: {
                    'data-slate-node': 'element',
                    'data-slate-key': key.id
                },
                decorate: this.context.decorate,
                readonly: this.context.readonly
            };
            if (isInline) {
                elementContext.attributes['data-slate-inline'] = true;
            }
            if (isVoid) {
                elementContext.attributes['data-slate-void'] = true;
                elementContext.attributes.contenteditable = false;
            }
            return elementContext;
        } else {
            const computedContext = this.getCommonContext();
            const isLeafBlock = AngularEditor.isLeafBlock(this.viewContext.editor, this.context.parent);
            const textContext: SlateTextContext = {
                decorations: computedContext.decorations,
                isLast: isLeafBlock && this.index === this.context.parent.children.length - 1,
                parent: this.context.parent as Element,
                text: this.descendant
            };
            return textContext;
        }
    }
Example #6
Source File: TodoListEnterKeyHandler.ts    From react-editor-kit with MIT License 6 votes vote down vote up
TodoListEnterKeyHandler: KeyHandler = {
  pattern: "enter",
  handle: ({ editor, element, isElementEmpty }, event) => {
    if (isElementActive(editor, "todo-item")) {
      const { selection } = editor;
      if (!selection) {
        return false;
      }
      const path = ReactEditor.findPath(editor, element as Element);
      if (!event.shiftKey) {
        if (isElementEmpty) {
          const next = [path[0] + 1];
          Transforms.removeNodes(editor);
          Transforms.insertNodes(
            editor,
            {
              type: "paragraph",
              autoFocus: true,
              children: [{ text: "" }],
            },
            {
              at: next,
            }
          );
        } else {
          path[path.length - 1]++;
          Transforms.insertNodes(editor, defaultTodoItem({ autoFocus: true }), {
            at: path,
          });
        }
        event.preventDefault();
        return true;
      }
    }
    return false;
  },
}
Example #7
Source File: injectVoid.ts    From payload with MIT License 6 votes vote down vote up
injectVoidElement = (editor: Editor, element: Element): void => {
  const lastSelectedElementIsEmpty = isLastSelectedElementEmpty(editor);
  const previous = Editor.previous(editor);

  if (lastSelectedElementIsEmpty) {
    // If previous node is void
    if (Editor.isVoid(editor, previous?.[0])) {
      // Insert a blank element between void nodes
      // so user can place cursor between void nodes
      Transforms.insertNodes(editor, { children: [{ text: '' }] });
      Transforms.setNodes(editor, element);
    // Otherwise just set the empty node equal to new void
    } else {
      Transforms.setNodes(editor, element);
    }
  } else {
    Transforms.insertNodes(editor, element);
  }

  // Add an empty node after the new void
  Transforms.insertNodes(editor, { children: [{ text: '' }] });
}
Example #8
Source File: MessageFormatEditor.tsx    From project-loved-web with MIT License 6 votes vote down vote up
function renderLeaf({ attributes, children, leaf }: RenderLeafProps): JSX.Element {
  let className: string | undefined;

  switch (leaf.type) {
    case TYPE.argument:
      className = 'variable';
      break;
    case TYPE.number:
    case TYPE.date:
    case TYPE.time:
    case TYPE.select:
    case TYPE.plural:
      className = 'variable-format';
      break;
    case TYPE.pound:
      className = 'pound';
      break;
    case TYPE.tag:
      className = 'tag';
      break;
  }

  return (
    <span
      {...attributes}
      className={`messageformat${className == null ? '' : ` messageformat-${className}`}`}
    >
      {children}
    </span>
  );
}
Example #9
Source File: toggle.tsx    From payload with MIT License 6 votes vote down vote up
toggleElement = (editor, format) => {
  const isActive = isElementActive(editor, format);
  const isList = listTypes.includes(format);

  let type = format;

  if (isActive) {
    type = undefined;
  } else if (isList) {
    type = 'li';
  }

  if (editor.blurSelection) {
    Transforms.select(editor, editor.blurSelection);
  }

  Transforms.unwrapNodes(editor, {
    match: (n) => Element.isElement(n) && listTypes.includes(n.type as string),
    split: true,
    mode: 'lowest',
  });

  Transforms.setNodes(editor, { type });

  if (!isActive && isList) {
    const block = { type: format, children: [] };
    Transforms.wrapNodes(editor, block);
  }

  ReactEditor.focus(editor);
}
Example #10
Source File: richtext.component.ts    From slate-angular with MIT License 6 votes vote down vote up
toggleBlock = (format) => {
        const isActive = this.isBlockActive(format)
        const isList = LIST_TYPES.includes(format)

        Transforms.unwrapNodes(this.editor, {
            match: n =>
                LIST_TYPES.includes(Element.isElement(n) && n.type),
            split: true,
        })
        const newProperties: Partial<Element> = {
            type: isActive ? 'paragraph' : isList ? 'list-item' : format,
        }
        Transforms.setNodes(this.editor, newProperties)

        if (!isActive && isList) {
            const block = { type: format, children: [] }
            Transforms.wrapNodes(this.editor, block)
        }
    }
Example #11
Source File: fuzzer.ts    From slate-ot with MIT License 6 votes vote down vote up
testDoc: Element = {
  children: [
    {
      type: 'paragraph',
      children: [{ text: 'ABC' }, { text: 'Test' }],
    },
    {
      type: 'checklist',
      children: [{ text: '123' }, { text: 'xyz' }],
    },
  ],
}
Example #12
Source File: mentions.component.ts    From slate-angular with MIT License 6 votes vote down vote up
valueChange(value: Element[]) {
        const { selection, operations } = this.editor;
        if (operations[0].type === 'insert_text' && operations[0].text === this.trigger) {
            this.target = { anchor: Editor.before(this.editor, selection.anchor), focus: selection.focus };
            this.searchText = '';
            this.activeIndex = 0;
            this.updateSuggestionsLocation();
            return;
        }

        if (selection && Range.isCollapsed(selection) && this.target) {
            const beforeRange = Editor.range(this.editor, this.target.anchor, selection.focus);
            const beforeText = Editor.string(this.editor, beforeRange);
            const beforeMatch = beforeText && beforeText.match(/^@(\w*)$/);
            if (beforeMatch) {
                this.searchText = beforeText.slice(1);
                this.activeIndex = 0;
                this.updateSuggestionsLocation();
                return;
            }
        }

        if (this.target) {
            this.target = null;
            this.updateSuggestionsLocation();
        }
    }
Example #13
Source File: Layout.ts    From react-editor-kit with MIT License 6 votes vote down vote up
createLayout = (layout: Layout): Element => {
  const children: Element[] = layout.map((cell) => ({
    type: "layout-cell",
    width: (100 * cell) / layout.length,
    children: [{ type: "paragraph", children: [{ text: "" }] }],
  }));
  children[0].autoFocus = true;
  return { type: "layout", children };
}
Example #14
Source File: editable.component.ts    From slate-angular with MIT License 6 votes vote down vote up
writeValue(value: Element[]) {
        if (value && value.length) {
            if (check(value)) {
                this.editor.children = value;
            } else {
                this.editor.onError({
                    code: SlateErrorCode.InvalidValueError,
                    name: 'initialize invalid data',
                    data: value
                });
                this.editor.children = normalize(value);
            }
            this.initializeContext();
            this.cdr.markForCheck();
        }
    }
Example #15
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 #16
Source File: runtime-util.ts    From slate-vue with MIT License 6 votes vote down vote up
getChildren = (node: Node): any => {
  return Editor.isEditor(node) ? (node as VueEditor)._state: (node as Element).children
}
Example #17
Source File: image-editable.component.ts    From slate-angular with MIT License 6 votes vote down vote up
withImage = (editor: Editor) => {
    const { isBlockCard, isVoid } = editor;
    editor.isBlockCard = (node: Element) => {
        if (Element.isElement(node) && node.type === 'image') {
            return true;
        }
        return isBlockCard(node);
    }
    editor.isVoid = (node: Element) => {
        if (Element.isElement(node) && node.type === 'image') {
            return true;
        }
        return isVoid(node);
    }
    return editor;
}
Example #18
Source File: LinkToolbar.d.ts    From react-editor-kit with MIT License 5 votes vote down vote up
LinkToolbar: (props: LinkToolbarProps) => JSX.Element
Example #19
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 #20
Source File: Tables.d.ts    From react-editor-kit with MIT License 5 votes vote down vote up
useTables: () => {
    addColumn: (element: Element, before?: boolean) => void;
    addRow: (element: Element, before?: boolean) => void;
    deleteColumn: (element: Element) => void;
    deleteRow: (element: Element) => void;
}
Example #21
Source File: markdown-shortcuts.component.ts    From slate-angular with MIT License 5 votes vote down vote up
withShortcuts = editor => {
    const { deleteBackward, insertText } = editor;

    editor.insertText = text => {
        const { selection } = editor;

        if (text === ' ' && selection && Range.isCollapsed(selection)) {
            const { anchor } = selection;
            const block = Editor.above<Element>(editor, {
                match: n => Editor.isBlock(editor, n)
            });
            const path = block ? block[1] : [];
            const start = Editor.start(editor, path);
            const range = { anchor, focus: start };
            const beforeText = Editor.string(editor, range);
            const type = SHORTCUTS[beforeText];

            if (type) {
                Transforms.select(editor, range);
                Transforms.delete(editor);
                Transforms.setNodes(editor, { type }, { match: n => Editor.isBlock(editor, n) });

                if (type === 'list-item') {
                    const list: BulletedListElement = { type: 'bulleted-list', children: [] };
                    Transforms.wrapNodes<Element>(editor, list, {
                        match: n => !Editor.isEditor(n) && Element.isElement(n) && n.type === 'list-item'
                    });
                }

                return;
            }
        }

        insertText(text);
    };

    editor.deleteBackward = (...args) => {
        const { selection } = editor;

        if (selection && Range.isCollapsed(selection)) {
            const match = Editor.above<Element>(editor, {
                match: n => Editor.isBlock(editor, n)
            });

            if (match) {
                const [block, path] = match;
                const start = Editor.start(editor, path);

                if (block.type !== 'paragraph' && Point.equals(selection.anchor, start)) {
                    Transforms.setNodes(editor, { type: 'paragraph' });

                    if (block.type === 'list-item') {
                        Transforms.unwrapNodes(editor, {
                            match: n => Element.isElement(n) && n.type === 'bulleted-list',
                            split: true
                        });
                    }

                    return;
                }
            }

            deleteBackward(...args);
        }
    };

    return editor;
}
Example #22
Source File: Tables.ts    From react-editor-kit with MIT License 5 votes vote down vote up
useTables = () => {
  const { editor } = useEditorKit();

  const addColumn = (element: Element, before = false) => {
    const [row, rowPath] = findRow(editor, element);
    const index = row.children.indexOf(element);
    const [table] = Editor.parent(editor, rowPath);
    table.children.forEach((row) => {
      const path = ReactEditor.findPath(editor, row.children[index]);
      if (!before) {
        path[path.length - 1]++;
      }
      Transforms.insertNodes(editor, cell(), { at: path });
    });
  };

  const addRow = (element: Element, before = false) => {
    const [row, rowPath] = findRow(editor, element);
    if (!before) {
      rowPath[rowPath.length - 1]++;
    }

    const children = Array.from({ length: row.children.length }).map(() =>
      cell()
    );
    const newRow = {
      type: "table-row",
      children,
    };
    Transforms.insertNodes(editor, newRow, { at: rowPath });
  };

  const deleteRow = (element: Element) => {
    const [, rowPath] = findRow(editor, element);
    rowPath[rowPath.length - 1];
    Transforms.delete(editor, { at: rowPath });
  };

  const deleteColumn = (element: Element) => {
    const [row, rowPath] = findRow(editor, element);
    const index = row.children.indexOf(element);
    const [table] = Editor.parent(editor, rowPath);
    table.children.forEach((row) => {
      const path = ReactEditor.findPath(editor, row.children[index]);
      path[path.length - 1];
      Transforms.delete(editor, { at: path });
    });
  };

  return { addColumn, addRow, deleteColumn, deleteRow };
}
Example #23
Source File: index.tsx    From payload with MIT License 5 votes vote down vote up
indent = {
  Button: () => {
    const editor = useSlate();
    const handleIndent = useCallback((e, dir) => {
      e.preventDefault();

      if (editor.blurSelection) {
        Transforms.select(editor, editor.blurSelection);
      }

      if (dir === 'left') {
        Transforms.unwrapNodes(editor, {
          match: (n) => Element.isElement(n) && [indentType, ...listTypes].includes(n.type),
          split: true,
          mode: 'lowest',
        });

        if (isElementActive(editor, 'li')) {
          const [, parentLocation] = Editor.parent(editor, editor.selection);
          const [, parentDepth] = parentLocation;

          if (parentDepth > 0 || parentDepth === 0) {
            Transforms.unwrapNodes(editor, {
              match: (n) => Element.isElement(n) && n.type === 'li',
              split: true,
              mode: 'lowest',
            });
          } else {
            Transforms.unsetNodes(editor, ['type']);
          }
        }
      }

      if (dir === 'right') {
        const isCurrentlyOL = isElementActive(editor, 'ol');
        const isCurrentlyUL = isElementActive(editor, 'ul');

        if (isCurrentlyOL || isCurrentlyUL) {
          Transforms.wrapNodes(editor, {
            type: 'li',
            children: [],
          });
          Transforms.wrapNodes(editor, { type: isCurrentlyOL ? 'ol' : 'ul', children: [{ text: ' ' }] });
          Transforms.setNodes(editor, { type: 'li' });
        } else {
          Transforms.wrapNodes(editor, { type: indentType, children: [] });
        }
      }

      ReactEditor.focus(editor);
    }, [editor]);

    const canDeIndent = isElementActive(editor, 'li') || isElementActive(editor, indentType);

    return (
      <React.Fragment>
        <button
          type="button"
          className={[
            baseClass,
            !canDeIndent && `${baseClass}--disabled`,
          ].filter(Boolean).join(' ')}
          onClick={canDeIndent ? (e) => handleIndent(e, 'left') : undefined}
        >
          <IndentLeft />
        </button>
        <button
          type="button"
          className={baseClass}
          onClick={(e) => handleIndent(e, 'right')}
        >
          <IndentRight />
        </button>
      </React.Fragment>
    );
  },
  Element: IndentWithPadding,
}
Example #24
Source File: SlateType.ts    From slate-ot with MIT License 5 votes vote down vote up
slateType = {
  name: 'slate-ot-type',

  uri: 'http://sharejs.org/types/slate-ot-type',

  create(init: Element): Editor {
    const e = createEditor();
    e.children = init.children;
    return <Editor>init;
  },

  apply(snapshot: Editor, op: Operation[] | Operation) {
    slateType.normalize(op).forEach((o) => {
      if (o.type === 'set_node' && o.path.length === 0) {
        for (const key in o.newProperties) {
          if (key === 'id' || key === 'type' || key === 'children') {
            throw new Error(`Cannot set the "${key}" property of nodes!`);
          }

          const value = o.newProperties[key];

          if (value == null) {
            delete snapshot[key];
          } else {
            snapshot[key] = value;
          }
        }
      } else {
        Transforms.transform(snapshot, o);
      }
    });
    return snapshot;
  },

  transform(
    leftOps: Operation[],
    rightOps: Operation[],
    side: 'left' | 'right'
  ): Operation[] {
    let [leftRes] = xTransformMxN(leftOps, rightOps, side);
    return leftRes;
  },

  // serialize(snapshot) {
  //   return JSON.stringify(snapshot);
  // },

  // deserialize(data) {
  //   // return Value.fromJSON(data);
  // },

  normalize(op: Operation | Operation[]): Operation[] {
    return Array.isArray(op) ? op : [op];
  },
}
Example #25
Source File: text.tsx    From slate-vue with MIT License 4 votes vote down vote up
Text = tsx.component({
  props: {
    text: {
      type: Object as PropType<SlateText>
    },
    isLast: Boolean,
    parent: {
      type: Object as PropType<Element>
    },
    decorations: {
      type: Array as PropType<Array<Range>>
    },
  },
  components: {
    leaf
  },
  inject: ['decorate', 'placeholder'],
  provide(): object {
    return {
      'text': this.text,
      'isLast': this.isLast,
      'parent': this.parent
    }
  },
  data(): Pick<providedByEditable, 'placeholder' | 'decorate'> & UseRef {
    return {
      ref: null
    }
  },
  hooks() {
    const ref = this.ref = useRef(null);
    const {text} = this;
    const editor = this.$editor;
    const key = VueEditor.findKey(editor, text)
    const initRef = () => {
      useEffect(()=>{
        if (ref.current) {
          KEY_TO_ELEMENT.set(key, ref.current)
          NODE_TO_ELEMENT.set(text, ref.current)
          ELEMENT_TO_NODE.set(ref.current, text)
        } else {
          KEY_TO_ELEMENT.delete(key)
          NODE_TO_ELEMENT.delete(text)
        }
      })
    };

    initRef()
  },
  render(h, ctx): VNode {
    const { text, placeholder } = this
    let decorations: Array<any> = this.decorations;
    if(!decorations) {
      const editor = this.$editor
      const p = VueEditor.findPath(editor, text)
      if(this.decorate) {
        decorations = this.decorate([text, p])
      }

      // init placeholder
      if (
        placeholder &&
        editor.children.length === 1 &&
        Array.from(Node.texts(editor)).length === 1 &&
        Node.string(editor) === ''
      ) {
        const start = Editor.start(editor, [])
        decorations.push({
          [PLACEHOLDER_SYMBOL]: true,
          placeholder,
          anchor: start,
          focus: start,
        })
      }
    }
    const leaves = SlateText.decorations(text, decorations)
    const children = []
    for (let i = 0; i < leaves.length; i++) {
      const leaf = leaves[i];
      children.push(
        <leaf
          leaf={leaf}
        />
        )
    }
    return (
      <span data-slate-node="text" ref={this.ref!.id}>
        {children}
      </span>
    )
  }
})
Example #26
Source File: global-normalize.spec.ts    From slate-angular with MIT License 4 votes vote down vote up
describe('global-normalize', () => {
    const invalidData3: any[] = [
        {
            type: 'paragraph',
            children: [
                { text: '' }
            ]
        },
        {
            type: 'numbered-list',
            children: [
                {
                    type: 'list-item',
                    children: [
                        {
                            type: 'paragraph',
                            children: [
                                {
                                    text: ''
                                }
                            ]
                        },
                        {
                            type: 'paragraph',
                            children: []
                        }
                    ]
                }
            ]
        }
    ];

    it('should return true', () => {
        const validData: Element[] = [
            {
                type: 'paragraph',
                children: [
                    { text: 'This is editable ' },
                    { text: 'rich', bold: true },
                    { text: ' text, ' },
                    { text: 'much', bold: true, italic: true },
                    { text: ' better than a ' },
                    { text: '<textarea>', ['code-line']: true },
                    { text: '!' }
                ]
            },
            {
                type: 'heading-one',
                children: [{ text: 'This is h1 ' }]
            },
            {
                type: 'heading-three',
                children: [{ text: 'This is h3 ' }]
            },
            {
                type: 'paragraph',
                children: [
                    {
                        text: `Since it's rich text, you can do things like turn a selection of text `
                    },
                    { text: 'bold', bold: true },
                    {
                        text: ', or add a semantically rendered block quote in the middle of the page, like this:'
                    }
                ]
            },
            {
                type: 'block-quote',
                children: [{ text: 'A wise quote.' }]
            },
            {
                type: 'paragraph',
                children: [{ text: 'Try it out for yourself!' }]
            },
            {
                type: 'paragraph',
                children: [{ text: '' }]
            }
        ];
        const result = check(validData);
        expect(result).toBeTrue();
    });

    it('should return false', () => {
        const invalidData1: any[] = [
            { text: '' }
        ];

        const result1 = check(invalidData1);
        expect(result1).toBeFalse();

        const invalidData2: any[] = [
            {
                type: 'paragraph',
                children: []
            }
        ];

        const result2 = check(invalidData2);
        expect(result2).toBeFalse();

        const result3 = check(invalidData3);
        expect(result3).toBeFalse();
    });

    it('should return valid data', () => {
        const result3 = normalize(invalidData3);
        expect(result3.length).toEqual(1);
        expect(result3[0]).toEqual(invalidData3[0]);
    })
});
Example #27
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>;
  }
})
Example #28
Source File: with-angular.spec.ts    From slate-angular with MIT License 4 votes vote down vote up
describe("with-angular", () => {
  let angularEditor: AngularEditor;
  function configEditor() {
    angularEditor = withAngular(createEditor());
    angularEditor.children = [
      {
        type: "paragraph",
        children: [
          { text: "This is editable " },
          { text: "rich" },
          { text: " text, " },
          { text: "much" },
          { text: " better than a " },
          { text: "<textarea>" },
          { text: "!" },
        ],
      },
    ];
  }
  beforeEach(() => {
    configEditor();
  });
  describe("onChange", () => {
    it("default onChange was called", fakeAsync(() => {
      spyOn(angularEditor, "onChange").and.callThrough();
      Transforms.select(angularEditor, {
        anchor: {
          path: [0, 0],
          offset: 0,
        },
        focus: {
          path: [0, 0],
          offset: 3,
        },
      });
      flush();
      expect(angularEditor.onChange).toHaveBeenCalled();
    }));
    it("custom onChange was called", fakeAsync(() => {
      let isOnChanged = false;
      EDITOR_TO_ON_CHANGE.set(angularEditor, () => {
        isOnChanged = true;
      });
      Transforms.select(angularEditor, {
        anchor: {
          path: [0, 0],
          offset: 0,
        },
        focus: {
          path: [0, 0],
          offset: 3,
        },
      });
      flush();
      expect(isOnChanged).toBeTruthy();
    }));
  });

  describe('apply', () => {
    let component: BasicEditableComponent;
    let fixture: ComponentFixture<BasicEditableComponent>;

    beforeEach(fakeAsync(() => {
      configureBasicEditableTestingModule([BasicEditableComponent]);
      fixture = TestBed.createComponent(BasicEditableComponent);
      component = fixture.componentInstance;
      component.value = [
        { type: 'paragraph', children: [{ text: 'first text!' }] },
        {
          type: "table",
          children: [
            {
              type: "table-row",
              children: [
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '1!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '2!' }]
                    }
                  ]
                },
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '3!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '4!' }]
                    }
                  ]
                },
              ],
            },
            {
              type: "table-row",
              children: [
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '5!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '6!' }]
                    }
                  ]
                },
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '7!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '8!' }]
                    }
                  ]
                },
              ],
            },
          ],
        },
        {
          type: "table",
          children: [
            {
              type: "table-row",
              children: [
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '1!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '2!' }]
                    }
                  ]
                },
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '3!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '4!' }]
                    }
                  ]
                },
              ],
            },
            {
              type: "table-row",
              children: [
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '5!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '6!' }]
                    }
                  ]
                },
                {
                  type: "table-cell",
                  children: [
                    {
                      type: 'paragraph',
                      children: [{ text: '7!' }]
                    },
                    {
                      type: 'paragraph',
                      children: [{ text: '8!' }]
                    }
                  ]
                },
              ],
            },
          ],
        },
        { type: 'paragraph', children: [{ text: 'last text!' }] }
      ];
      fixture.detectChanges();
      flush();
      fixture.detectChanges();
    }));

    afterEach(() => {
      fixture.destroy();
    });

    it('move node to sibling when there have a common parent', fakeAsync(() => {
      const oldPath = [1,0,0,0];
      const newPath = [1,0,0,1];
      const tablePath = [1];
      const tableRowPath = [1, 0];
      const tableCellPath = [1, 0, 0];
      const tableNode = Node.get(component.editor, tablePath);
      const tableRowNode = Node.get(component.editor, tableRowPath);
      const tableCellNode = Node.get(component.editor, tableCellPath);
      Transforms.moveNodes(component.editor, {
        at: oldPath,
        to: newPath,
      });
      tick(100);
      const newTableNode = Node.get(component.editor, tablePath);
      const newTableRowNode = Node.get(component.editor, tableRowPath);
      const newTableCellNode = Node.get(component.editor, tableCellPath);
      expect(tableNode).not.toEqual(newTableNode);
      validKey(tableNode, newTableNode);
      expect(tableRowNode).not.toEqual(newTableRowNode);
      validKey(tableRowNode, newTableRowNode);
      expect(tableCellNode).not.toEqual(newTableCellNode);
      validKey(tableCellNode, newTableCellNode);
    }));

    it('move node to sibling when there is no common parent', fakeAsync(() => {
      const oldPath = [1,0,0,0];
      const newPath = [2,0,0,1];

      const tablePath = [1];
      const tableRowPath = [1, 0];
      const tableCellPath = [1, 0, 0];
      const tableNode = Node.get(component.editor, tablePath);
      const tableRowNode = Node.get(component.editor, tableRowPath);
      const tableCellNode = Node.get(component.editor, tableCellPath);

      const tablePath2 = [2];
      const tableRowPath2 = [2, 0];
      const tableCellPath2 = [2, 0, 0];
      const tableNode2 = Node.get(component.editor, tablePath2);
      const tableRowNode2 = Node.get(component.editor, tableRowPath2);
      const tableCellNode2 = Node.get(component.editor, tableCellPath2);

      Transforms.moveNodes(component.editor, {
        at: oldPath,
        to: newPath,
      });
      tick(100);

      // valid move origin
      const newTableNode = Node.get(component.editor, tablePath);
      const newTableRowNode = Node.get(component.editor, tableRowPath);
      const newTableCellNode = Node.get(component.editor, tableCellPath);
      expect(tableNode).not.toEqual(newTableNode);
      validKey(tableNode, newTableNode);
      expect(tableRowNode).not.toEqual(newTableRowNode);
      validKey(tableRowNode, newTableRowNode);
      expect(tableCellNode).not.toEqual(newTableCellNode);
      validKey(tableCellNode, newTableCellNode);

      // valid move targit
      const newTableNode2 = Node.get(component.editor, tablePath2);
      const newTableRowNode2 = Node.get(component.editor, tableRowPath2);
      const newTableCellNode2 = Node.get(component.editor, tableCellPath2);
      expect(tableNode2).not.toEqual(newTableNode2);
      validKey(tableNode2, newTableNode2);
      expect(tableRowNode2).not.toEqual(newTableRowNode2);
      validKey(tableRowNode2, newTableRowNode2);
      expect(tableCellNode2).not.toEqual(newTableCellNode2);
      validKey(tableCellNode2, newTableCellNode2);
    }));

    it('can correctly insert the list in the last row', fakeAsync(() => {
      Transforms.select(component.editor, Editor.end(component.editor, [3]));
      component.editor.insertBreak();
      Transforms.wrapNodes(
        component.editor,
        { type: 'list-item', children: [] },
        {
            at: [4],
            split: true
        }
      );
      Transforms.wrapNodes(component.editor, { type: 'numbered-list', children: [] } as any, {
        at: [4, 0, 0],
        match: node => Element.isElement(node) && node.type === 'list-item'
      });

      expect(component.editor.children.length).toBe(5);
      expect((component.editor.children[4] as any).type).toBe('numbered-list');
    }));
  })
});
Example #29
Source File: Plugin.d.ts    From react-editor-kit with MIT License 4 votes vote down vote up
createPlugin: (plugin: Plugin, ...triggers: Trigger[]) => {
    triggers: Trigger[];
    /**
     * A name means your plugin can be called inside of React components
     * that existing under the <EditorKit> context scope using usePlugin("name").
     *
     * Names also allow you to replace internal plugins with your own. When you
     * register your plugins to <EditorKit plugins={[...]}>, the library will
     * check the names of each if it finds a name of an internal plugin then it will
     * swap the internal plugin with the plugin you registered. This allows you to
     * inject your own icons into Editor Kit by providing a plugin named "icon-provider",
     * for example.
     */
    name: string;
    /**
     * The order the Plugin is registered is important if you listen on events
     * that can only be handled by a single plugin. For example, EditorKit will
     * return early onKeyDown once a Plugin returns true from onKeyDown.
     *
     * Order can also be important for Plugins that contribute CSS (see below)
     * due to CSS cascading behaviour.
     *
     * Default order is the position in the Plugin array passed to EditorKit.
     *
     */
    order?: number;
    /**
     * Extend the Slate editor with additional functionality or
     * wrap existing functions like normalizeNode
     */
    withPlugin?(editor: ReactEditor): ReactEditor;
    /**
     * Array of actions that plugin provides. A name is required for each
     * if more than one is available.
     */
    actions?: PluginAction[];
    /**
     * Contribute style to an Element without having to render it. This avoids
     * having to create unnecessary wrapper Elements just to change things like
     * text-align of existing Elements.
     */
    styleElement?: (props: RenderElementProps) => CSSProperties | undefined;
    /**
     * Contribute classes to an Element without having to render it.
     * Similar to `styleElement` above.
     */
    getClasses?: (element: Element) => string | undefined;
    /**
     * Render an Element into the Editor. You can output any JSX content you like
     * here according to the `props.element.type`
     *
     * The is the same as Slate's renderElement function and requires that
     * you spread `props.attributes` on your root element and pass
     * the `props.children` prop to your lowest JSX element.
     */
    renderElement?: (props: RenderElementProps) => JSX.Element | undefined;
    /**
     * Render an Leaf into the Editor. You can output any JSX content you like
     * here according to the `props.leaf.type`. This Leaf content will become the
     * child of an Element that gets rendered by renderElement above.
     *
     * The is the same as Slate's renderLeaf function and requires that
     * you spread `props.attributes` on your root element and pass
     * the `props.children` prop to your lowest JSX element.
     */
    renderLeaf?: (props: RenderLeafProps, state: EditorState) => JSX.Element | undefined;
    /**
     *
     */
    decorate?: (entry: NodeEntry<import("slate").Node>, editor: ReactEditor) => Range[];
    /**
     * Similar to triggers but typical used for keyboard shortcuts or modifying the
     * Editors handling of delete/backspace or enter for certain elements
     */
    onKey?: KeyHandler[];
    /**
     * Respond to key down events on the Editor element. Use onKey if you are interested in a certain
     * key or key combo.
     */
    onKeyDown?(event: React.KeyboardEvent, state: EditorState): boolean | undefined;
    /**
     * Respond to click events that happen on the Editor element.
     */
    onClick?(event: React.MouseEvent<HTMLElement>, state: EditorState): void;
    /**
     * Allows contributing to the context menu and overriding the browser's default.
     * See ContextMenuContribution for more info.
     */
    onContextMenu?: ContextMenuContribution[];
    /**
     * Respond to drop events that happen on the Editor element.
     */
    onDrop?(event: React.DragEvent, state: EditorState): boolean;
    /**
     * Styles that should be applied to the Editor instance. EditorKit uses the npm
     * package stylis and supports nested selectors like:
     *
     *  .someClass {
     *    &:hover {
     *
     *    }
     *  }
     *
     */
    editorStyle?: string | StyleFunction;
    /**
     * These styles can target the whole page so be careful. Things like dialogs and toolbars
     * are rendered outside of the Editor and can be targeted here.
     */
    globalStyle?: string | StyleFunction;
}