Skip to content

Instantly share code, notes, and snippets.

@awinogradov
Last active November 28, 2024 17:15
Show Gist options
  • Save awinogradov/5c455878f56e9fb74a9887f1c01dfbb6 to your computer and use it in GitHub Desktop.
Save awinogradov/5c455878f56e9fb74a9887f1c01dfbb6 to your computer and use it in GitHub Desktop.
Slate custom
import cn from 'classnames';
import React, { CSSProperties, useCallback, useState } from 'react';
import { createEditor, BaseEditor, Descendant, Editor, Element, CustomTypes } from 'slate';
import { Slate, Editable, withReact, ReactEditor, RenderElementProps, RenderLeafProps } from 'slate-react';
import s from './slate.module.scss';
type CustomElement = { type: 'paragraph'; children: CustomText[] };
type CodeElement = { type: 'code'; children?: CustomText[] };
type Marks = { bold?: boolean; code?: boolean; italic?: boolean };
type CustomText = { type: 'text'; text: string } & Marks;
interface CustomRenderElementProps {
element: Element | CustomElement | CodeElement;
attributes: RenderElementProps['attributes'];
children: RenderElementProps['children'];
}
declare module 'slate' {
interface CustomTypes {
Editor: BaseEditor & ReactEditor & { type: 'editor' };
Element: CustomElement | CodeElement | CustomText;
Text: CustomText;
}
}
const CodeElement: React.FC<CustomRenderElementProps> = (props) => (
<pre {...props.attributes}>
<code>{props.children}</code>
</pre>
);
const DefaultElement: React.FC<CustomRenderElementProps> = (props) => <div {...props.attributes}>{props.children}</div>;
const Leaf: React.FC<RenderLeafProps> = (props) => {
const Tag = props.leaf.code ? 'code' : 'span';
const style: CSSProperties = {
fontWeight: props.leaf.bold ? 'bold' : undefined,
fontStyle: props.leaf.italic ? 'italic' : undefined,
};
return (
<Tag {...props.attributes} style={style}>
{props.children}
</Tag>
);
};
const hasMark = (editor: CustomTypes['Editor'], type: keyof Marks) => {
const marks = Editor.marks(editor);
return Boolean(marks?.[type]);
};
const toggleMark = (editor: CustomTypes['Editor'], mark: keyof Marks) => {
if (hasMark(editor, mark)) {
Editor.removeMark(editor, mark);
} else {
Editor.addMark(editor, mark, true);
}
};
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [{ type: 'text', text: 'A line of text in a paragraph.' }],
},
];
const SlatePage = () => {
const [editor] = useState(() => withReact(createEditor()));
const renderElement = useCallback((props: CustomRenderElementProps) => {
switch (props.element.type) {
case 'code':
return <CodeElement {...props} />;
default:
return <DefaultElement {...props} />;
}
}, []);
const renderLeaf = useCallback((props: RenderLeafProps) => <Leaf {...props} />, []);
const onEditorKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLDivElement>) => {
if (e.key === '&') {
// Prevent the ampersand character from being inserted.
e.preventDefault();
editor.insertText('and');
}
if (e.key === '`' && e.metaKey) {
// Prevent the "`" from being inserted by default.
e.preventDefault();
toggleMark(editor, 'code');
}
if (e.key === 'b' && e.metaKey) {
e.preventDefault();
toggleMark(editor, 'bold');
}
if (e.key === 'i' && e.metaKey) {
e.preventDefault();
toggleMark(editor, 'italic');
}
},
[editor],
);
const onEditorValueChange = useCallback(
(value: Descendant[]) => {
const isAstChanged = editor.operations.some((op) => op.type !== 'set_selection');
if (isAstChanged) {
console.log('value', value);
}
},
[editor],
);
return (
<div className={cn(s.Slate)}>
<Slate editor={editor} initialValue={initialValue} onChange={onEditorValueChange}>
<div>
<button
onMouseDown={(e) => {
e.preventDefault();
toggleMark(editor, 'bold');
}}
>
Bold
</button>
<button
onMouseDown={(e) => {
e.preventDefault();
toggleMark(editor, 'italic');
}}
>
Italic
</button>
<button
onMouseDown={(e) => {
e.preventDefault();
toggleMark(editor, 'code');
}}
>
Code
</button>
</div>
<Editable
className={s.SlateEditable}
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={onEditorKeyDown}
/>
</Slate>
</div>
);
};
export default SlatePage;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment