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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
LinkToolbar: (props: LinkToolbarProps) => JSX.Element
Example #19
Source File: runtime-util.ts From slate-vue with MIT License | 5 votes |
transform = function(editor: Editor, op: Operation, Vue?: any) {
switch (op.type) {
case 'insert_node': {
const { path, node } = op
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
getChildren(parent).splice(index, 0, clone(node))
break
}
case 'insert_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset)
node.text = before + text + after
break
}
case 'merge_node': {
const { path } = op
const node = Node.get(editor, path)
const prevPath = Path.previous(path)
const prev = Node.get(editor, prevPath)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
if (Text.isText(node) && Text.isText(prev)) {
prev.text += node.text
} else if (!Text.isText(node) && !Text.isText(prev)) {
getChildren(prev).push(...getChildren(node))
} else {
throw new Error(
`Cannot apply a "merge_node" operation at path [${path}] to nodes of different interaces: ${node} ${prev}`
)
}
getChildren(parent).splice(index, 1)
break
}
case 'move_node': {
const { path, newPath } = op
if (Path.isAncestor(path, newPath)) {
throw new Error(
`Cannot move a path [${path}] to new path [${newPath}] because the destination is inside itself.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
// This is tricky, but since the `path` and `newPath` both refer to
// the same snapshot in time, there's a mismatch. After either
// removing the original position, the second step's path can be out
// of date. So instead of using the `op.newPath` directly, we
// transform `op.path` to ascertain what the `newPath` would be after
// the operation was applied.
getChildren(parent).splice(index, 1)
const truePath = Path.transform(path, op)!
const newParent = Node.get(editor, Path.parent(truePath))
const newIndex = truePath[truePath.length - 1]
getChildren(newParent).splice(newIndex, 0, node)
break
}
case 'remove_node': {
const { path } = op
NODE_TO_KEY.delete(Node.get(editor, path))
const index = path[path.length - 1]
const parent = Node.parent(editor, path)
getChildren(parent).splice(index, 1)
break
}
case 'remove_text': {
const { path, offset, text } = op
const node = Node.leaf(editor, path)
const before = node.text.slice(0, offset)
const after = node.text.slice(offset + text.length)
node.text = before + after
break
}
case 'set_node': {
const { path, newProperties } = op
if (path.length === 0) {
throw new Error(`Cannot set properties on the root node!`)
}
const node = Node.get(editor, path)
for (const key in newProperties) {
if (key === 'children' || key === 'text') {
throw new Error(`Cannot set the "${key}" property of nodes!`)
}
const value = (newProperties as any)[key]
if(Vue) {
if (value == null) {
Vue.delete(node, key)
} else {
Vue.set(node, key, value)
}
}
}
break
}
case 'set_selection': {
break
}
case 'split_node': {
const { path, position, properties } = op
if (path.length === 0) {
throw new Error(
`Cannot apply a "split_node" operation at path [${path}] because the root node cannot be split.`
)
}
const node = Node.get(editor, path)
const parent = Node.parent(editor, path)
const index = path[path.length - 1]
let newNode: Descendant
if (Text.isText(node)) {
const before = node.text.slice(0, position)
const after = node.text.slice(position)
node.text = before
newNode = {
...node,
...(properties as Partial<Text>),
text: after,
}
} else {
const before = node.children.slice(0, position)
const after = node.children.slice(position)
node.children = before
newNode = {
...node,
...(properties as Partial<Element>),
children: after,
}
}
getChildren(parent).splice(index + 1, 0, newNode)
break
}
}
}
Example #20
Source File: Tables.d.ts From react-editor-kit with MIT License | 5 votes |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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;
}