added new editor, locking, props persistance

This commit is contained in:
Fedor Katurov 2023-04-25 18:11:59 +06:00
parent 0940d6abf8
commit 601eda17de
22 changed files with 2860 additions and 123 deletions

View file

@ -0,0 +1,109 @@
import { FC, useCallback } from "react";
import {
FloatingToolbar,
Remirror,
useRemirror,
EditorComponent,
HeadingLevelButtonGroup,
FormattingButtonGroup,
} from "@remirror/react";
import jsx from "refractor/lang/jsx.js";
import typescript from "refractor/lang/typescript.js";
import { Extension, ExtensionPriority, RemirrorEventListener } from "remirror";
import {
BlockquoteExtension,
UnderlineExtension,
BoldExtension,
BulletListExtension,
CodeBlockExtension,
CodeExtension,
HardBreakExtension,
HeadingExtension,
ItalicExtension,
LinkExtension,
ListItemExtension,
MarkdownExtension,
OrderedListExtension,
StrikeExtension,
TableExtension,
TrailingNodeExtension,
GapCursorExtension,
} from "remirror/extensions";
import styles from "./styles.module.scss";
interface RemirrorEditorProps {
locked: boolean;
value?: string;
onChange?: (val: string) => void;
}
const RemirrorEditor: FC<RemirrorEditorProps> = ({
value,
locked,
onChange,
}) => {
const { manager, state, setState } = useRemirror({
extensions,
builtin: {
exitMarksOnArrowPress: false,
},
content: value,
stringHandler: "markdown",
});
const onStateChange = useCallback<RemirrorEventListener<Extension>>(
({ state, helpers }) => {
if (helpers && onChange) {
onChange(helpers.getMarkdown(state));
}
setState(state);
},
[onChange, setState]
);
return (
<Remirror
placeholder="Start typing..."
manager={manager}
classNames={[styles.editor]}
editable={!locked}
onChange={onStateChange}
state={state}
>
<EditorComponent />
{!locked && (
<FloatingToolbar>
<FormattingButtonGroup />
<HeadingLevelButtonGroup />
</FloatingToolbar>
)}
</Remirror>
);
};
const extensions = (): Extension[] => [
new LinkExtension({ autoLink: true }),
new BoldExtension(),
new UnderlineExtension(),
new StrikeExtension(),
new ItalicExtension(),
new HeadingExtension(),
new BlockquoteExtension(),
new BulletListExtension({ enableSpine: false }),
new OrderedListExtension(),
new ListItemExtension({
priority: ExtensionPriority.High,
// enableCollapsible: true,
}),
new CodeExtension(),
new CodeBlockExtension({ supportedLanguages: [jsx, typescript] }),
new TrailingNodeExtension(),
new TableExtension(),
new MarkdownExtension({ copyAsMarkdown: true }),
new GapCursorExtension(),
new HardBreakExtension(),
];
export { RemirrorEditor };

View file

@ -0,0 +1,14 @@
.editor {
outline: none;
height: 100%;
& > :first-child {
margin-top: 0 !important;
margin-left: 0 !important;
}
& > :last-child {
margin-bottom: 0 !important;
margin-right: 0 !important;
}
}

View file

@ -0,0 +1,35 @@
import { useEffect, useState } from "react";
const safelyGetStringValue = (key: string) => {
try {
return localStorage.getItem(key) ?? "";
} catch (error) {
console.warn(error);
return "";
}
};
const safelySetStringValue = (key: string, value: string) => {
try {
return localStorage.setItem(key, value);
} catch (error) {
console.warn(error);
}
};
export const usePersistedValue = (
id: string,
prefix: string
): [string, (val: string) => void] => {
const key = `${prefix}${id}`;
const [value, setValue] = useState(safelyGetStringValue(key));
useEffect(() => {
setValue(safelyGetStringValue(key));
}, [id, key]);
useEffect(() => {
safelySetStringValue(key, value);
}, [key, value]);
return [value, setValue];
};

View file

@ -0,0 +1,22 @@
import { FC } from "react";
import styles from "./styles.module.scss";
import { usePersistedValue } from "./hooks/usePersistedValue";
import { RemirrorEditor } from "../../components/RemirrorEditor";
interface MarkdownEditorContainerProps {
id: string;
locked: boolean;
}
export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
id,
locked,
}) => {
const [value, setValue] = usePersistedValue(id, "MarkdownEditorContainer");
return (
<div className={styles.editor}>
<RemirrorEditor value={value} onChange={setValue} locked={locked} />
</div>
);
};

View file

@ -0,0 +1,6 @@
.editor {
height: 100%;
padding: 16px;
overflow: scroll;
box-sizing: border-box;
}