added slate editor, fixes
This commit is contained in:
parent
1d40d7e15f
commit
bc250e73bb
12 changed files with 828 additions and 16 deletions
|
|
@ -36,7 +36,7 @@ export function BookSearchList() {
|
|||
|
||||
<WorkingList workingsList={[...data.bookMany.items]} />
|
||||
|
||||
{data.bookMany.items.length != 0 && (
|
||||
{data.bookMany.items.length !== 0 && (
|
||||
<Pagination
|
||||
total={data.bookMany.total}
|
||||
page={workingsListPage}
|
||||
|
|
|
|||
362
app/client/components/EditorSlate.tsx
Normal file
362
app/client/components/EditorSlate.tsx
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
import { Button, Card, Stack, Text, Wrap } from '@chakra-ui/react';
|
||||
import isHotkey from 'is-hotkey';
|
||||
import React, { KeyboardEvent, MouseEvent, useCallback, useMemo, useState } from 'react';
|
||||
import { createEditor, Descendant, Editor, Element as SlateElement, Transforms } from 'slate';
|
||||
import { withHistory } from 'slate-history';
|
||||
import {
|
||||
Editable,
|
||||
RenderElementProps,
|
||||
RenderLeafProps,
|
||||
Slate,
|
||||
useSlate,
|
||||
withReact
|
||||
} from 'slate-react';
|
||||
import {
|
||||
CustomEditor,
|
||||
CustomElement,
|
||||
CustomElementType,
|
||||
CustomElementWithAlign,
|
||||
CustomTextKey
|
||||
} from '../types';
|
||||
import { Prose } from './ui/prose';
|
||||
|
||||
export function EditorSlate() {
|
||||
return (
|
||||
<>
|
||||
<Card.Root
|
||||
variant={'subtle'}
|
||||
width="100%"
|
||||
maxWidth="840px"
|
||||
minHeight="100%"
|
||||
flex={1}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
{/* <Card.Header>
|
||||
<Heading textAlign="center">Editor</Heading>
|
||||
</Card.Header> */}
|
||||
<Card.Body
|
||||
width="100%"
|
||||
css={{
|
||||
'& *': {
|
||||
fontSize: 'revert',
|
||||
fontWeight: 'revert'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<RichTextExample />
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const HOTKEYS: Record<string, CustomTextKey> = {
|
||||
'mod+b': 'bold',
|
||||
'mod+i': 'italic',
|
||||
'mod+u': 'underline',
|
||||
'mod+`': 'code'
|
||||
};
|
||||
|
||||
const LIST_TYPES = ['numbered-list', 'bulleted-list'] as const;
|
||||
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'] as const;
|
||||
|
||||
type AlignType = (typeof TEXT_ALIGN_TYPES)[number];
|
||||
type ListType = (typeof LIST_TYPES)[number];
|
||||
type CustomElementFormat = CustomElementType | AlignType | ListType;
|
||||
|
||||
const RichTextExample = () => {
|
||||
const renderElement = useCallback((props: RenderElementProps) => <Element {...props} />, []);
|
||||
const renderLeaf = useCallback((props: RenderLeafProps) => <Leaf {...props} />, []);
|
||||
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
|
||||
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [
|
||||
{ text: 'This is editable ' },
|
||||
{ text: 'rich', bold: true },
|
||||
{ text: ' text, ' },
|
||||
{ text: 'much', italic: true },
|
||||
{ text: ' better than a ' },
|
||||
{ text: '<textarea>', code: true },
|
||||
{ text: '!' }
|
||||
]
|
||||
},
|
||||
{
|
||||
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',
|
||||
align: 'center',
|
||||
children: [{ text: 'Try it out for yourself!' }]
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Slate editor={editor} initialValue={initialValue}>
|
||||
<Wrap justifyContent="center" alignItems="center" gap={1}>
|
||||
<MarkButton format="bold" icon="nf nf-oct-bold" />
|
||||
<MarkButton format="italic" icon="nf nf-oct-italic" />
|
||||
<MarkButton format="underline" icon="nf nf-md-format_underline" />
|
||||
<MarkButton format="code" icon="nf nf-oct-code" />
|
||||
<BlockButton format="heading-one" icon="nf nf-fa-heading" text="1" />
|
||||
<BlockButton format="heading-two" icon="nf nf-fa-heading" text="2" />
|
||||
<BlockButton format="block-quote" icon="nf nf-oct-quote" />
|
||||
<BlockButton format="numbered-list" icon="nf nf-fa-list_ol" />
|
||||
<BlockButton format="bulleted-list" icon="nf nf-fa-list_ul" />
|
||||
<BlockButton format="left" icon="nf nf-fa-align_left" />
|
||||
<BlockButton format="center" icon="nf nf-fa-align_center" />
|
||||
<BlockButton format="right" icon="nf nf-fa-align_right" />
|
||||
<BlockButton format="justify" icon="nf nf-fa-align_justify" />
|
||||
</Wrap>
|
||||
<Stack width="100%" display="flex" justifyContent="center" alignItems="center">
|
||||
<Prose
|
||||
width="100%"
|
||||
height="100%"
|
||||
maxWidth="unset"
|
||||
maxHeight="unset"
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Editable
|
||||
className="slate-editor"
|
||||
style={{
|
||||
outline: 'none',
|
||||
minWidth: '100%'
|
||||
}}
|
||||
renderElement={renderElement}
|
||||
renderLeaf={renderLeaf}
|
||||
placeholder=""
|
||||
spellCheck
|
||||
autoFocus
|
||||
onKeyDown={(event: KeyboardEvent<HTMLDivElement>) => {
|
||||
for (const hotkey in HOTKEYS) {
|
||||
if (isHotkey(hotkey, event as any)) {
|
||||
event.preventDefault();
|
||||
const mark = HOTKEYS[hotkey];
|
||||
toggleMark(editor, mark);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Prose>
|
||||
</Stack>
|
||||
</Slate>
|
||||
);
|
||||
};
|
||||
|
||||
const toggleBlock = (editor: CustomEditor, format: CustomElementFormat) => {
|
||||
const isActive = isBlockActive(editor, format, isAlignType(format) ? 'align' : 'type');
|
||||
const isList = isListType(format);
|
||||
|
||||
Transforms.unwrapNodes(editor, {
|
||||
match: n =>
|
||||
!Editor.isEditor(n) &&
|
||||
SlateElement.isElement(n) &&
|
||||
isListType(n.type) &&
|
||||
!isAlignType(format),
|
||||
split: true
|
||||
});
|
||||
let newProperties: Partial<SlateElement>;
|
||||
if (isAlignType(format)) {
|
||||
newProperties = {
|
||||
align: isActive ? undefined : format
|
||||
};
|
||||
} else {
|
||||
newProperties = {
|
||||
type: isActive ? 'paragraph' : isList ? 'list-item' : format
|
||||
};
|
||||
}
|
||||
Transforms.setNodes<SlateElement>(editor, newProperties);
|
||||
|
||||
if (!isActive && isList) {
|
||||
const block = { type: format, children: [] };
|
||||
Transforms.wrapNodes(editor, block);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleMark = (editor: CustomEditor, format: CustomTextKey) => {
|
||||
const isActive = isMarkActive(editor, format);
|
||||
|
||||
if (isActive) {
|
||||
Editor.removeMark(editor, format);
|
||||
} else {
|
||||
Editor.addMark(editor, format, true);
|
||||
}
|
||||
};
|
||||
|
||||
const isBlockActive = (
|
||||
editor: CustomEditor,
|
||||
format: CustomElementFormat,
|
||||
blockType: 'type' | 'align' = 'type'
|
||||
) => {
|
||||
const { selection } = editor;
|
||||
if (!selection) return false;
|
||||
|
||||
const [match] = Array.from(
|
||||
Editor.nodes(editor, {
|
||||
at: Editor.unhangRange(editor, selection),
|
||||
match: n => {
|
||||
if (!Editor.isEditor(n) && SlateElement.isElement(n)) {
|
||||
if (blockType === 'align' && isAlignElement(n)) {
|
||||
return n.align === format;
|
||||
}
|
||||
return n.type === format;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return !!match;
|
||||
};
|
||||
|
||||
const isMarkActive = (editor: CustomEditor, format: CustomTextKey) => {
|
||||
const marks = Editor.marks(editor);
|
||||
return marks ? marks[format] === true : false;
|
||||
};
|
||||
|
||||
const Element = ({ attributes, children, element }: RenderElementProps) => {
|
||||
const style: React.CSSProperties = {};
|
||||
if (isAlignElement(element)) {
|
||||
style.textAlign = element.align as AlignType;
|
||||
}
|
||||
switch (element.type) {
|
||||
case 'block-quote':
|
||||
return (
|
||||
<blockquote style={style} {...attributes}>
|
||||
{children}
|
||||
</blockquote>
|
||||
);
|
||||
case 'bulleted-list':
|
||||
return (
|
||||
<ul style={style} {...attributes}>
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
case 'heading-one':
|
||||
return (
|
||||
<h1 style={style} {...attributes}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
case 'heading-two':
|
||||
return (
|
||||
<h2 style={style} {...attributes}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
case 'list-item':
|
||||
return (
|
||||
<li style={style} {...attributes}>
|
||||
{children}
|
||||
</li>
|
||||
);
|
||||
case 'numbered-list':
|
||||
return (
|
||||
<ol style={style} {...attributes}>
|
||||
{children}
|
||||
</ol>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<p style={style} {...attributes}>
|
||||
{children}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
|
||||
if (leaf.bold) {
|
||||
children = <strong>{children}</strong>;
|
||||
}
|
||||
|
||||
if (leaf.code) {
|
||||
children = <code>{children}</code>;
|
||||
}
|
||||
|
||||
if (leaf.italic) {
|
||||
children = <em style={{ fontStyle: 'italic' }}>{children}</em>;
|
||||
}
|
||||
|
||||
if (leaf.underline) {
|
||||
children = <u>{children}</u>;
|
||||
}
|
||||
|
||||
return <span {...attributes}>{children}</span>;
|
||||
};
|
||||
|
||||
interface BlockButtonProps {
|
||||
format: CustomElementFormat;
|
||||
icon: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const BlockButton = ({ format, icon, text }: BlockButtonProps) => {
|
||||
const editor = useSlate();
|
||||
return (
|
||||
<Button
|
||||
variant={isBlockActive(editor, format) ? 'solid' : 'ghost'}
|
||||
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
toggleBlock(editor, format);
|
||||
}}
|
||||
gap={0.5}
|
||||
>
|
||||
<i className={icon} />
|
||||
<Stack alignItems="end">
|
||||
<Text fontSize="0.8rem">{text}</Text>
|
||||
</Stack>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
interface MarkButtonProps {
|
||||
format: CustomTextKey;
|
||||
icon: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const MarkButton = ({ format, icon, text }: MarkButtonProps) => {
|
||||
const editor = useSlate();
|
||||
return (
|
||||
<Button
|
||||
variant={isMarkActive(editor, format) ? 'solid' : 'ghost'}
|
||||
onMouseDown={(event: MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
toggleMark(editor, format);
|
||||
}}
|
||||
>
|
||||
<i className={icon} />
|
||||
{text}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const isAlignType = (format: CustomElementFormat): format is AlignType => {
|
||||
return TEXT_ALIGN_TYPES.includes(format as AlignType);
|
||||
};
|
||||
|
||||
const isListType = (format: CustomElementFormat): format is ListType => {
|
||||
return LIST_TYPES.includes(format as ListType);
|
||||
};
|
||||
|
||||
const isAlignElement = (element: CustomElement): element is CustomElementWithAlign => {
|
||||
return 'align' in element;
|
||||
};
|
||||
56
app/client/components/EditorTestSlate.tsx
Normal file
56
app/client/components/EditorTestSlate.tsx
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { Card, Heading } from '@chakra-ui/react';
|
||||
import { useState } from 'react';
|
||||
import { BaseEditor, createEditor, Descendant } from 'slate';
|
||||
import { Editable, ReactEditor, Slate, withReact } from 'slate-react';
|
||||
|
||||
type CustomElement = { type: 'paragraph'; children: CustomText[] };
|
||||
type CustomText = { text: string };
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
Editor: BaseEditor & ReactEditor;
|
||||
Element: CustomElement;
|
||||
Text: CustomText;
|
||||
}
|
||||
}
|
||||
|
||||
export function EditorSlate() {
|
||||
const initialValue: Descendant[] = [
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: [{ text: 'A line of text in a paragraph.' }]
|
||||
}
|
||||
];
|
||||
|
||||
const [editor] = useState(() => withReact(createEditor()));
|
||||
return (
|
||||
// Add the editable component inside the context.
|
||||
<>
|
||||
<Card.Root
|
||||
variant={'subtle'}
|
||||
width="100%"
|
||||
maxWidth="840px"
|
||||
minHeight="sm"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Card.Header>
|
||||
<Heading textAlign="center">Editor</Heading>
|
||||
</Card.Header>
|
||||
<Card.Body
|
||||
width="90%"
|
||||
css={{
|
||||
'& *': {
|
||||
fontSize: 'revert',
|
||||
fontWeight: 'revert'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Slate editor={editor} initialValue={initialValue}>
|
||||
<Editable />
|
||||
</Slate>
|
||||
</Card.Body>
|
||||
</Card.Root>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { WorkingCardProps, WorkingListCard } from './WorkingListCard';
|
|||
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
||||
return (
|
||||
<List.Root variant="plain" gap={4}>
|
||||
{workingsList.length != 0 ? (
|
||||
{workingsList.length !== 0 ? (
|
||||
workingsList.map(workingData => (
|
||||
<List.Item key={workingData.id}>
|
||||
<WorkingListCard {...workingData} />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Stack } from '@chakra-ui/react';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import { Editor } from '../components/Editor';
|
||||
import { EditorSlate } from '../components/EditorSlate';
|
||||
|
||||
export const Route = createFileRoute('/editor')({
|
||||
component: RouteComponent
|
||||
|
|
@ -8,8 +8,8 @@ export const Route = createFileRoute('/editor')({
|
|||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<Stack justifyContent="center" alignItems="center">
|
||||
<Editor />
|
||||
<Stack justifyContent="center" alignItems="center" minHeight="88vh">
|
||||
<EditorSlate />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Heading, Input, Stack } from '@chakra-ui/react';
|
||||
import { Heading, Input, Stack, Textarea } from '@chakra-ui/react';
|
||||
import { Button } from '@client/components/ui/button';
|
||||
import { Field } from '@client/components/ui/field';
|
||||
import { valibotResolver } from '@hookform/resolvers/valibot';
|
||||
|
|
@ -15,13 +15,51 @@ export const Route = createFileRoute('/new/book')({
|
|||
|
||||
function RouteComponent() {
|
||||
return (
|
||||
<Stack flexDirection={'column'} justifyContent={'center'} alignItems={'center'}>
|
||||
<Stack width="100%" flexDirection={'column'} justifyContent={'center'} alignItems={'center'}>
|
||||
<Heading>New book</Heading>
|
||||
<form></form>
|
||||
<NewBookForm />
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
title: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
function NewBookForm() {
|
||||
const schema = v.pipe(
|
||||
v.object({
|
||||
title: v.pipe(v.string(), v.nonEmpty('Title is required')),
|
||||
description: v.pipe(v.string(), v.nonEmpty('Password is required'))
|
||||
})
|
||||
);
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors }
|
||||
} = useForm<FormValues>({ resolver: valibotResolver(schema) });
|
||||
|
||||
return (
|
||||
<form style={{ width: '80%' }} onSubmit={handleSubmit(async values => {})}>
|
||||
<Stack>
|
||||
<Field label="Title" invalid={errors.title != null} errorText={errors.title?.message}>
|
||||
<Input {...register('title')} />
|
||||
</Field>
|
||||
<Field
|
||||
label="Description"
|
||||
invalid={errors.description != null}
|
||||
errorText={errors.description?.message}
|
||||
>
|
||||
<Textarea width="100%" lineHeight={4} {...register('description')} />
|
||||
</Field>
|
||||
<Button type="submit">Create new book</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
// function LoginForm() {
|
||||
// const schema = v.pipe(
|
||||
// v.object({
|
||||
|
|
@ -40,13 +78,6 @@ function RouteComponent() {
|
|||
//
|
||||
// return (
|
||||
// <form
|
||||
// onSubmit={handleSubmit(async values => {
|
||||
// const session = await login({
|
||||
// email: values.email,
|
||||
// password: values.password
|
||||
// });
|
||||
// if (session != null) window.history.back();
|
||||
// })}
|
||||
// >
|
||||
// <Stack>
|
||||
// <Field label="Email" invalid={errors.email != null} errorText={errors.email?.message}>
|
||||
|
|
|
|||
196
app/client/types.d.ts
vendored
Normal file
196
app/client/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
import { Descendant, BaseEditor, BaseRange, Range, Element } from 'slate';
|
||||
import { ReactEditor } from 'slate-react';
|
||||
import { HistoryEditor } from 'slate-history';
|
||||
|
||||
export type BlockQuoteElement = {
|
||||
type: 'block-quote';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type BulletedListElement = {
|
||||
type: 'bulleted-list';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type CheckListItemElement = {
|
||||
type: 'check-list-item';
|
||||
checked: boolean;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type EditableVoidElement = {
|
||||
type: 'editable-void';
|
||||
children: EmptyText[];
|
||||
};
|
||||
|
||||
export type HeadingElement = {
|
||||
type: 'heading-one';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type HeadingTwoElement = {
|
||||
type: 'heading-two';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type HeadingThreeElement = {
|
||||
type: 'heading-three';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type HeadingFourElement = {
|
||||
type: 'heading-four';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type HeadingFiveElement = {
|
||||
type: 'heading-five';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type HeadingSixElement = {
|
||||
type: 'heading-six';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type ImageElement = {
|
||||
type: 'image';
|
||||
url: string;
|
||||
children: EmptyText[];
|
||||
};
|
||||
|
||||
export type LinkElement = { type: 'link'; url: string; children: Descendant[] };
|
||||
|
||||
export type ButtonElement = { type: 'button'; children: Descendant[] };
|
||||
|
||||
export type BadgeElement = { type: 'badge'; children: Descendant[] };
|
||||
|
||||
export type ListItemElement = { type: 'list-item'; children: Descendant[] };
|
||||
|
||||
export type NumberedListItemElement = {
|
||||
type: 'numbered-list';
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type MentionElement = {
|
||||
type: 'mention';
|
||||
character: string;
|
||||
children: CustomText[];
|
||||
};
|
||||
|
||||
export type ParagraphElement = {
|
||||
type: 'paragraph';
|
||||
align?: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type TableElement = { type: 'table'; children: TableRow[] };
|
||||
|
||||
export type TableCellElement = { type: 'table-cell'; children: CustomText[] };
|
||||
|
||||
export type TableRowElement = { type: 'table-row'; children: TableCell[] };
|
||||
|
||||
export type TitleElement = { type: 'title'; children: Descendant[] };
|
||||
|
||||
export type VideoElement = { type: 'video'; url: string; children: EmptyText[] };
|
||||
|
||||
export type CodeBlockElement = {
|
||||
type: 'code-block';
|
||||
language: string;
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type CodeLineElement = {
|
||||
type: 'code-line';
|
||||
children: Descendant[];
|
||||
};
|
||||
|
||||
export type CustomElementWithAlign =
|
||||
| ParagraphElement
|
||||
| HeadingElement
|
||||
| HeadingTwoElement
|
||||
| HeadingThreeElement
|
||||
| HeadingFourElement
|
||||
| HeadingFiveElement
|
||||
| HeadingSixElement
|
||||
| BlockQuoteElement
|
||||
| BulletedListElement;
|
||||
|
||||
type CustomElement =
|
||||
| BlockQuoteElement
|
||||
| BulletedListElement
|
||||
| CheckListItemElement
|
||||
| EditableVoidElement
|
||||
| HeadingElement
|
||||
| HeadingTwoElement
|
||||
| HeadingThreeElement
|
||||
| HeadingFourElement
|
||||
| HeadingFiveElement
|
||||
| HeadingSixElement
|
||||
| ImageElement
|
||||
| LinkElement
|
||||
| ButtonElement
|
||||
| BadgeElement
|
||||
| ListItemElement
|
||||
| NumberedListItemElement
|
||||
| MentionElement
|
||||
| ParagraphElement
|
||||
| TableElement
|
||||
| TableRowElement
|
||||
| TableCellElement
|
||||
| TitleElement
|
||||
| VideoElement
|
||||
| CodeBlockElement
|
||||
| CodeLineElement;
|
||||
|
||||
export type CustomElementType = CustomElement['type'];
|
||||
|
||||
export type CustomText = {
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
code?: boolean;
|
||||
underline?: boolean;
|
||||
strikethrough?: boolean;
|
||||
// MARKDOWN PREVIEW SPECIFIC LEAF
|
||||
underlined?: boolean;
|
||||
title?: boolean;
|
||||
list?: boolean;
|
||||
hr?: boolean;
|
||||
blockquote?: boolean;
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type CustomTextKey = keyof Omit<CustomText, 'text'>;
|
||||
|
||||
export type EmptyText = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type RenderElementPropsFor<T> = RenderElementProps & {
|
||||
element: T;
|
||||
};
|
||||
|
||||
export type CustomEditor = BaseEditor &
|
||||
ReactEditor &
|
||||
HistoryEditor & {
|
||||
nodeToDecorations?: Map<Element, Range[]>;
|
||||
};
|
||||
|
||||
declare module 'slate' {
|
||||
interface CustomTypes {
|
||||
Editor: CustomEditor;
|
||||
Element: CustomElement;
|
||||
Text: CustomText;
|
||||
Range: BaseRange & {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue