manhteky123's picture
Upload 213 files
60f878e verified
import { createEditor, type Descendant, Range, Transforms } from 'slate';
import { isKeyHotkey } from 'is-hotkey';
import {
Editable,
RenderElementProps,
RenderLeafProps,
Slate,
withReact,
} from 'slate-react';
import {
type CustomElement,
type CustomText,
type LinkElement,
type ParagraphElement,
type TextProps,
} from '@plait/common';
import React, { useMemo, useCallback, useEffect, CSSProperties } from 'react';
import { withHistory } from 'slate-history';
import { isUrl, LinkEditor } from '@plait/text-plugins';
import { withText } from './plugins/with-text';
import { CustomEditor, RenderElementPropsFor } from './custom-types';
import './styles/index.scss';
import { LinkComponent, withInlineLink } from './plugins/with-link';
export type TextComponentProps = TextProps;
export const Text: React.FC<TextComponentProps> = (
props: TextComponentProps
) => {
const { text, readonly, onChange, onComposition, afterInit } = props;
const renderLeaf = useCallback(
(props: RenderLeafProps) => <Leaf {...props} />,
[]
);
const initialValue: Descendant[] = [text];
const editor = useMemo(() => {
const editor = withInlineLink(
withText(withHistory(withReact(createEditor())))
);
afterInit && afterInit(editor);
return editor;
}, []);
useEffect(() => {
if (text === editor.children[0]) {
return;
}
editor.children = [text];
editor.onChange();
}, [text, editor]);
const onKeyDown: React.KeyboardEventHandler<HTMLInputElement> = (event) => {
const { selection } = editor;
// Default left/right behavior is unit:'character'.
// This fails to distinguish between two cursor positions, such as
// <inline>foo<cursor/></inline> vs <inline>foo</inline><cursor/>.
// Here we modify the behavior to unit:'offset'.
// This lets the user step into and out of the inline without stepping over characters.
// You may wish to customize this further to only use unit:'offset' in specific cases.
if (selection && Range.isCollapsed(selection)) {
const { nativeEvent } = event;
if (isKeyHotkey('left', nativeEvent)) {
event.preventDefault();
Transforms.move(editor, { unit: 'offset', reverse: true });
return;
}
if (isKeyHotkey('right', nativeEvent)) {
event.preventDefault();
Transforms.move(editor, { unit: 'offset' });
return;
}
}
};
return (
<Slate
editor={editor}
initialValue={initialValue}
onChange={(value: Descendant[]) => {
onChange &&
onChange({
newText: editor.children[0] as ParagraphElement,
operations: editor.operations,
});
}}
>
<Editable
className="slate-editable-container plait-text-container"
renderElement={(props) => <Element {...props} />}
renderLeaf={renderLeaf}
readOnly={readonly === undefined ? true : readonly}
onCompositionStart={(event) => {
if (onComposition) {
onComposition(event as unknown as CompositionEvent);
}
}}
onCompositionUpdate={(event) => {
if (onComposition) {
onComposition(event as unknown as CompositionEvent);
}
}}
onCompositionEnd={(event) => {
if (onComposition) {
onComposition(event as unknown as CompositionEvent);
}
}}
onKeyDown={onKeyDown}
/>
</Slate>
);
};
const Element = (props: RenderElementProps) => {
const { attributes, children, element } = props as RenderElementPropsFor<
CustomElement & { type: string }
>;
switch (element.type) {
case 'link':
return (
<LinkComponent {...(props as RenderElementPropsFor<LinkElement>)} />
);
default:
return (
<ParagraphComponent
{...(props as RenderElementPropsFor<ParagraphElement>)}
/>
);
}
};
const ParagraphComponent = ({
attributes,
children,
element,
}: RenderElementPropsFor<ParagraphElement>) => {
const style = { textAlign: element.align } as CSSProperties;
return (
<div style={style} {...attributes}>
{children}
</div>
);
};
const Leaf: React.FC<RenderLeafProps> = ({ children, leaf, attributes }) => {
if ((leaf as CustomText).bold) {
children = <strong>{children}</strong>;
}
if ((leaf as CustomText).code) {
children = <code>{children}</code>;
}
if ((leaf as CustomText).italic) {
children = <em>{children}</em>;
}
if ((leaf as CustomText).underlined) {
children = <u>{children}</u>;
}
return (
<span style={{ color: (leaf as CustomText).color }} {...attributes}>
{children}
</span>
);
};