mirror of
https://github.com/muerwre/markdown-home-tab.git
synced 2025-04-25 17:06:40 +07:00
added new editor, locking, props persistance
This commit is contained in:
parent
0940d6abf8
commit
601eda17de
22 changed files with 2860 additions and 123 deletions
109
src/modules/editor/components/RemirrorEditor/index.tsx
Normal file
109
src/modules/editor/components/RemirrorEditor/index.tsx
Normal 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 };
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
};
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
.editor {
|
||||
height: 100%;
|
||||
padding: 16px;
|
||||
overflow: scroll;
|
||||
box-sizing: border-box;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue