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]} />
|
<WorkingList workingsList={[...data.bookMany.items]} />
|
||||||
|
|
||||||
{data.bookMany.items.length != 0 && (
|
{data.bookMany.items.length !== 0 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
total={data.bookMany.total}
|
total={data.bookMany.total}
|
||||||
page={workingsListPage}
|
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[] }) {
|
export function WorkingList({ workingsList }: { workingsList: WorkingCardProps[] }) {
|
||||||
return (
|
return (
|
||||||
<List.Root variant="plain" gap={4}>
|
<List.Root variant="plain" gap={4}>
|
||||||
{workingsList.length != 0 ? (
|
{workingsList.length !== 0 ? (
|
||||||
workingsList.map(workingData => (
|
workingsList.map(workingData => (
|
||||||
<List.Item key={workingData.id}>
|
<List.Item key={workingData.id}>
|
||||||
<WorkingListCard {...workingData} />
|
<WorkingListCard {...workingData} />
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Stack } from '@chakra-ui/react';
|
import { Stack } from '@chakra-ui/react';
|
||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { Editor } from '../components/Editor';
|
import { EditorSlate } from '../components/EditorSlate';
|
||||||
|
|
||||||
export const Route = createFileRoute('/editor')({
|
export const Route = createFileRoute('/editor')({
|
||||||
component: RouteComponent
|
component: RouteComponent
|
||||||
|
|
@ -8,8 +8,8 @@ export const Route = createFileRoute('/editor')({
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<Stack justifyContent="center" alignItems="center">
|
<Stack justifyContent="center" alignItems="center" minHeight="88vh">
|
||||||
<Editor />
|
<EditorSlate />
|
||||||
</Stack>
|
</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 { Button } from '@client/components/ui/button';
|
||||||
import { Field } from '@client/components/ui/field';
|
import { Field } from '@client/components/ui/field';
|
||||||
import { valibotResolver } from '@hookform/resolvers/valibot';
|
import { valibotResolver } from '@hookform/resolvers/valibot';
|
||||||
|
|
@ -15,13 +15,51 @@ export const Route = createFileRoute('/new/book')({
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<Stack flexDirection={'column'} justifyContent={'center'} alignItems={'center'}>
|
<Stack width="100%" flexDirection={'column'} justifyContent={'center'} alignItems={'center'}>
|
||||||
<Heading>New book</Heading>
|
<Heading>New book</Heading>
|
||||||
<form></form>
|
<NewBookForm />
|
||||||
</Stack>
|
</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() {
|
// function LoginForm() {
|
||||||
// const schema = v.pipe(
|
// const schema = v.pipe(
|
||||||
// v.object({
|
// v.object({
|
||||||
|
|
@ -40,13 +78,6 @@ function RouteComponent() {
|
||||||
//
|
//
|
||||||
// return (
|
// return (
|
||||||
// <form
|
// <form
|
||||||
// onSubmit={handleSubmit(async values => {
|
|
||||||
// const session = await login({
|
|
||||||
// email: values.email,
|
|
||||||
// password: values.password
|
|
||||||
// });
|
|
||||||
// if (session != null) window.history.back();
|
|
||||||
// })}
|
|
||||||
// >
|
// >
|
||||||
// <Stack>
|
// <Stack>
|
||||||
// <Field label="Email" invalid={errors.email != null} errorText={errors.email?.message}>
|
// <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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ export async function toBookChapterSchema(model: BookChapter): Promise<BookChapt
|
||||||
updatedAt: model.createdAt.toISOString(),
|
updatedAt: model.createdAt.toISOString(),
|
||||||
foreword: model.foreword,
|
foreword: model.foreword,
|
||||||
footnote: model.footnote,
|
footnote: model.footnote,
|
||||||
|
sequenceNumber: model.sequenceNumber,
|
||||||
reviewsTotal: await call(async () => {
|
reviewsTotal: await call(async () => {
|
||||||
const reviewsCount = await prisma.review.count({
|
const reviewsCount = await prisma.review.count({
|
||||||
where: {
|
where: {
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,18 @@ input PaginationFilterInput {
|
||||||
size: Int
|
size: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input BookCreateInput {
|
||||||
|
title: String!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input BookChapterCreateInput {
|
||||||
|
title: String!
|
||||||
|
content: String!
|
||||||
|
foreword: String
|
||||||
|
footnote: String
|
||||||
|
}
|
||||||
|
|
||||||
input BookManyInput {
|
input BookManyInput {
|
||||||
filter: PaginationFilterInput!
|
filter: PaginationFilterInput!
|
||||||
}
|
}
|
||||||
|
|
@ -88,6 +100,7 @@ type BookChapter {
|
||||||
updatedAt: String!
|
updatedAt: String!
|
||||||
foreword: String!
|
foreword: String!
|
||||||
footnote: String!
|
footnote: String!
|
||||||
|
sequenceNumber: Int!
|
||||||
reviewsTotal: Int!
|
reviewsTotal: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ model BookChapter {
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
sequenceNumber Int @default(1)
|
||||||
|
|
||||||
title String @db.VarChar(255)
|
title String @db.VarChar(255)
|
||||||
foreword String @db.MediumText
|
foreword String @db.MediumText
|
||||||
footnote String @db.MediumText
|
footnote String @db.MediumText
|
||||||
|
|
|
||||||
148
package-lock.json
generated
148
package-lock.json
generated
|
|
@ -31,12 +31,16 @@
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
"is-hotkey": "^0.2.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
|
"slate": "^0.114.0",
|
||||||
|
"slate-history": "^0.113.1",
|
||||||
|
"slate-react": "^0.114.2",
|
||||||
"tsx": "^4.3.0",
|
"tsx": "^4.3.0",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "^5.3.2",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
|
|
@ -56,6 +60,7 @@
|
||||||
"@types/cookie-parser": "^1.4.8",
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/is-hotkey": "^0.1.10",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/node": "^20.9.3",
|
"@types/node": "^20.9.3",
|
||||||
"@types/react": "^18.0.38",
|
"@types/react": "^18.0.38",
|
||||||
|
|
@ -5068,6 +5073,12 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@juggle/resize-observer": {
|
||||||
|
"version": "3.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
|
||||||
|
"integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/@kamilkisiela/fast-url-parser": {
|
"node_modules/@kamilkisiela/fast-url-parser": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@kamilkisiela/fast-url-parser/-/fast-url-parser-1.1.4.tgz",
|
||||||
|
|
@ -5950,6 +5961,13 @@
|
||||||
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/is-hotkey": {
|
||||||
|
"version": "0.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz",
|
||||||
|
"integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/js-yaml": {
|
"node_modules/@types/js-yaml": {
|
||||||
"version": "4.0.9",
|
"version": "4.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
|
||||||
|
|
@ -8492,6 +8510,12 @@
|
||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/compute-scroll-into-view": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
|
@ -9060,6 +9084,19 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/direction": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"direction": "cli.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
|
||||||
|
|
@ -11103,6 +11140,16 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "3.7.6",
|
"version": "3.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-3.7.6.tgz",
|
||||||
|
|
@ -11460,6 +11507,12 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-hotkey": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz",
|
||||||
|
"integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-interactive": {
|
"node_modules/is-interactive": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
|
||||||
|
|
@ -11549,6 +11602,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-plain-object": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-regex": {
|
"node_modules/is-regex": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
|
||||||
|
|
@ -12076,7 +12138,6 @@
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lodash.includes": {
|
"node_modules/lodash.includes": {
|
||||||
|
|
@ -14037,6 +14098,15 @@
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/scroll-into-view-if-needed": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"compute-scroll-into-view": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/scuid": {
|
"node_modules/scuid": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/scuid/-/scuid-1.1.0.tgz",
|
||||||
|
|
@ -14374,6 +14444,82 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/slate": {
|
||||||
|
"version": "0.114.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/slate/-/slate-0.114.0.tgz",
|
||||||
|
"integrity": "sha512-r3KHl22433DlN5BpLAlL4b3D8ItoGKAkj91YT6GhP39XuLoBT+YFd9ObKuL/okgiPb5lbwnW+71fM45hHceN9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
|
"tiny-warning": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/slate-dom": {
|
||||||
|
"version": "0.114.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/slate-dom/-/slate-dom-0.114.0.tgz",
|
||||||
|
"integrity": "sha512-3LWIfiDPNQSY+SCPsvMTErCkx2gXTViLoWISisw6uM+unwiOkEF9ZmpHp88/SSmcq6k3P4aIquehUNeNUlkdiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
|
"direction": "^1.0.4",
|
||||||
|
"is-hotkey": "^0.2.0",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"scroll-into-view-if-needed": "^3.1.0",
|
||||||
|
"tiny-invariant": "1.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"slate": ">=0.99.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/slate-dom/node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/slate-history": {
|
||||||
|
"version": "0.113.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.113.1.tgz",
|
||||||
|
"integrity": "sha512-J9NSJ+UG2GxoW0lw5mloaKcN0JI0x2IA5M5FxyGiInpn+QEutxT1WK7S/JneZCMFJBoHs1uu7S7e6pxQjubHmQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-plain-object": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"slate": ">=0.65.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/slate-react": {
|
||||||
|
"version": "0.114.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.114.2.tgz",
|
||||||
|
"integrity": "sha512-yqJnX1/7A30szl9BxW3qX99MZy6mM6VtUi1rXTy0JpRMTKv3rduo0WOxqcX90tpt0ke2pzHGbrLLr1buIN4vrw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@juggle/resize-observer": "^3.4.0",
|
||||||
|
"direction": "^1.0.4",
|
||||||
|
"is-hotkey": "^0.2.0",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"scroll-into-view-if-needed": "^3.1.0",
|
||||||
|
"tiny-invariant": "1.3.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=18.2.0",
|
||||||
|
"react-dom": ">=18.2.0",
|
||||||
|
"slate": ">=0.114.0",
|
||||||
|
"slate-dom": ">=0.110.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/slate-react/node_modules/tiny-invariant": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/slice-ansi": {
|
"node_modules/slice-ansi": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,16 @@
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
|
"is-hotkey": "^0.2.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
|
"slate": "^0.114.0",
|
||||||
|
"slate-history": "^0.113.1",
|
||||||
|
"slate-react": "^0.114.2",
|
||||||
"tsx": "^4.3.0",
|
"tsx": "^4.3.0",
|
||||||
"typescript": "^5.3.2",
|
"typescript": "^5.3.2",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
|
|
@ -62,6 +66,7 @@
|
||||||
"@types/cookie-parser": "^1.4.8",
|
"@types/cookie-parser": "^1.4.8",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/is-hotkey": "^0.1.10",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"@types/node": "^20.9.3",
|
"@types/node": "^20.9.3",
|
||||||
"@types/react": "^18.0.38",
|
"@types/react": "^18.0.38",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue