import { css, cx } from '@emotion/css';
import isHotkey from 'is-hotkey';
import React, { useCallback, useMemo } from 'react';
import { createEditor, Editor, Element as SlateElement, Node, Transforms } from 'slate';
import { withHistory } from 'slate-history';
import type { ReactEditor } from 'slate-react';
import { Editable, Slate, useSlate, withReact } from 'slate-react';
import { EllipsisWithTooltip } from '../schema-component/antd/input/EllipsisWithTooltip';
import { Button, Icon, Toolbar } from './components';
const HOTKEYS = {
'mod+b': 'bold',
'mod+i': 'italic',
'mod+u': 'underline',
'mod+`': 'code',
};
const LIST_TYPES = ['numbered-list', 'bulleted-list'];
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
const DEFAULT_VALUE = [
{
type: 'paragraph',
children: [{ text: '' }],
},
];
export const RichText = (props: any) => {
const { value, placeholder = '', className, readOnly, autop = true, ellipsis, onChange } = props;
const renderElement = useCallback((props) => , []);
const renderLeaf = useCallback((props) => , []);
const editor = useMemo(() => withHistory(withReact(createEditor() as ReactEditor)), []);
const slateValue = useMemo(() => {
editor.children = JSON.parse(JSON.stringify(value || DEFAULT_VALUE));
Editor.normalize(editor, { force: true });
return editor.children;
}, [editor, value]);
if (readOnly) {
const slateContent = (
);
const slatePlainText = serialize(slateValue);
const content = (
{ellipsis ? slatePlainText : slateContent}
);
return content;
}
return (
{
for (const hotkey in HOTKEYS) {
if (isHotkey(hotkey, event as any)) {
event.preventDefault();
const mark = HOTKEYS[hotkey];
toggleMark(editor, mark);
}
}
}}
/>
);
};
const serialize = (nodes) => {
return nodes.map((n) => Node.string(n)).join('\n');
};
const toggleBlock = (editor, format) => {
const isActive = isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type');
const isList = LIST_TYPES.includes(format);
Transforms.unwrapNodes(editor, {
match: (n: Node) =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
LIST_TYPES.includes((n as any).type) &&
!TEXT_ALIGN_TYPES.includes(format),
split: true,
});
let newProperties: Partial;
if (TEXT_ALIGN_TYPES.includes(format)) {
newProperties = {
align: isActive ? undefined : format,
};
} else {
newProperties = {
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
};
}
Transforms.setNodes(editor, newProperties);
if (!isActive && isList) {
const block = { type: format, children: [] };
Transforms.wrapNodes(editor, block);
}
};
const toggleMark = (editor, format) => {
const isActive = isMarkActive(editor, format);
if (isActive) {
Editor.removeMark(editor, format);
} else {
Editor.addMark(editor, format, true);
}
};
const isBlockActive = (editor, format, blockType = 'type') => {
const { selection } = editor;
if (!selection) return false;
const [match] = Array.from(
Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format,
}),
);
return !!match;
};
const isMarkActive = (editor, format) => {
const marks = Editor.marks(editor);
return marks ? marks[format] === true : false;
};
const Element = ({ attributes, children, element }) => {
const style = { textAlign: element.align };
switch (element.type) {
case 'block-quote':
return (
{children}
);
case 'bulleted-list':
return (
);
case 'heading-one':
return (
{children}
);
case 'heading-two':
return (
{children}
);
case 'list-item':
return (
{children}
);
case 'numbered-list':
return (
{children}
);
default:
return (
{children}
);
}
};
const Leaf = ({ attributes, children, leaf }) => {
if (leaf.bold) {
children = {children} ;
}
if (leaf.code) {
children = {children};
}
if (leaf.italic) {
children = {children} ;
}
if (leaf.underline) {
children = {children} ;
}
return {children} ;
};
const BlockButton = ({ format, icon }) => {
const editor = useSlate();
return (
{
event.preventDefault();
toggleBlock(editor, format);
}}
>
{icon}
);
};
const MarkButton = ({ format, icon }) => {
const editor = useSlate();
return (
{
event.preventDefault();
toggleMark(editor, format);
}}
>
{icon}
);
};