Last active
November 28, 2024 17:15
-
-
Save awinogradov/5c455878f56e9fb74a9887f1c01dfbb6 to your computer and use it in GitHub Desktop.
Slate custom
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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