mirror of
https://github.com/muerwre/markdown-home-tab.git
synced 2025-04-25 00:46:41 +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
1
.tool-versions
Normal file
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
||||||
|
nodejs 18.0.0
|
|
@ -21,3 +21,6 @@ yarn
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## TO-DO
|
||||||
|
|
||||||
|
- Use Remirror as editor
|
||||||
|
|
|
@ -21,10 +21,14 @@
|
||||||
"@milkdown/react": "^7.2.1",
|
"@milkdown/react": "^7.2.1",
|
||||||
"@milkdown/theme-nord": "^7.2.1",
|
"@milkdown/theme-nord": "^7.2.1",
|
||||||
"@milkdown/transformer": "^7.2.1",
|
"@milkdown/transformer": "^7.2.1",
|
||||||
|
"@remirror/pm": "^2.0.4",
|
||||||
|
"@remirror/react": "^2.0.27",
|
||||||
|
"@remirror/react-editors": "^1.0.27",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"dockview": "^1.7.1",
|
"dockview": "^1.7.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
"remirror": "^2.0.26",
|
||||||
"sass": "^1.62.0",
|
"sass": "^1.62.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vite-plugin-svgr": "^2.4.0",
|
"vite-plugin-svgr": "^2.4.0",
|
||||||
|
|
3
src/assets/images/locked.svg
Normal file
3
src/assets/images/locked.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48">
|
||||||
|
<path d="M220 976q-24.75 0-42.375-17.625T160 916V482q0-24.75 17.625-42.375T220 422h70v-96q0-78.85 55.606-134.425Q401.212 136 480.106 136T614.5 191.575Q670 247.15 670 326v96h70q24.75 0 42.375 17.625T800 482v434q0 24.75-17.625 42.375T740 976H220Zm0-60h520V482H220v434Zm260.168-140Q512 776 534.5 753.969T557 701q0-30-22.668-54.5t-54.5-24.5Q448 622 425.5 646.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM350 422h260v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426 196 388 233.917 350 271.833 350 326v96ZM220 916V482v434Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 617 B |
3
src/assets/images/unlocked.svg
Normal file
3
src/assets/images/unlocked.svg
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="48" viewBox="0 96 960 960" width="48">
|
||||||
|
<path d="M220 422h390v-96q0-54.167-37.882-92.083-37.883-37.917-92-37.917Q426 196 388 233.917 350 271.833 350 326h-60q0-79 55.606-134.5t134.5-55.5Q559 136 614.5 191.575T670 326v96h70q24.75 0 42.375 17.625T800 482v434q0 24.75-17.625 42.375T740 976H220q-24.75 0-42.375-17.625T160 916V482q0-24.75 17.625-42.375T220 422Zm0 494h520V482H220v434Zm260.168-140Q512 776 534.5 753.969T557 701q0-30-22.668-54.5t-54.5-24.5Q448 622 425.5 646.5t-22.5 55q0 30.5 22.668 52.5t54.5 22ZM220 916V482v434Z" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 582 B |
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 };
|
|
@ -12,11 +12,3 @@
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div[data-milkdown-root="true"] {
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
:global(.milkdown) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,22 @@
|
||||||
import { MilkdownProvider } from "@milkdown/react";
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { MarkdownEditor } from "../../components/MarkdownEditor";
|
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { usePersistedValue } from "./hooks/usePersistedValue";
|
import { usePersistedValue } from "./hooks/usePersistedValue";
|
||||||
|
import { RemirrorEditor } from "../../components/RemirrorEditor";
|
||||||
|
|
||||||
interface MarkdownEditorContainerProps {
|
interface MarkdownEditorContainerProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
|
||||||
id,
|
id,
|
||||||
|
locked,
|
||||||
}) => {
|
}) => {
|
||||||
const [value, setValue] = usePersistedValue(id, "MarkdownEditorContainer");
|
const [value, setValue] = usePersistedValue(id, "MarkdownEditorContainer");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.editor}>
|
<div className={styles.editor}>
|
||||||
<MilkdownProvider>
|
<RemirrorEditor value={value} onChange={setValue} locked={locked} />
|
||||||
<MarkdownEditor value={value} onChange={setValue} />
|
|
||||||
</MilkdownProvider>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
|
@ -1,5 +1,5 @@
|
||||||
import { DockviewApi, DockviewReadyEvent } from "dockview";
|
import { DockviewApi, DockviewReadyEvent } from "dockview";
|
||||||
import { useEffect, useRef } from "react";
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
import { createDefaultLayout } from "../utils/createDefaultLayout";
|
import { createDefaultLayout } from "../utils/createDefaultLayout";
|
||||||
|
|
||||||
export const useGridLayoutPersistance = () => {
|
export const useGridLayoutPersistance = () => {
|
||||||
|
@ -24,12 +24,22 @@ export const useGridLayoutPersistance = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const persistLayout = useCallback(() => {
|
||||||
|
if (!api.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const layout = api.current.toJSON();
|
||||||
|
|
||||||
|
localStorage.setItem("dockview_persistance_layout", JSON.stringify(layout));
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!api.current) {
|
if (!api.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const disposable = api.current.onDidLayoutChange(() => {
|
const onLayoutChange = api.current.onDidLayoutChange(() => {
|
||||||
if (!api.current) {
|
if (!api.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -38,18 +48,19 @@ export const useGridLayoutPersistance = () => {
|
||||||
createDefaultLayout(api.current);
|
createDefaultLayout(api.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout = api.current.toJSON();
|
persistLayout();
|
||||||
|
});
|
||||||
|
|
||||||
localStorage.setItem(
|
const onPanelChange = api.current.onDidActivePanelChange((event) => {
|
||||||
"dockview_persistance_layout",
|
console.log(event);
|
||||||
JSON.stringify(layout)
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposable.dispose();
|
onLayoutChange.dispose();
|
||||||
|
onPanelChange.dispose();
|
||||||
};
|
};
|
||||||
}, []);
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [persistLayout, api.current]);
|
||||||
|
|
||||||
return { api, onReady };
|
return { api, onReady, persistLayout };
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { DockviewReact, IDockviewPanelProps } from "dockview";
|
import { DockviewReact, IDockviewPanelProps } from "dockview";
|
||||||
import { useGridLayoutPersistance } from "./hooks/useGridLayoutPersistance";
|
import { useGridLayoutPersistance } from "./hooks/useGridLayoutPersistance";
|
||||||
import { FC, createElement, useCallback, useMemo } from "react";
|
import { FC, createElement, useCallback, useEffect, useMemo } from "react";
|
||||||
import { GridLayoutComponentProps } from "../../types";
|
import { GridLayoutComponentProps } from "../../types";
|
||||||
import { GridLayoutItemWrapper } from "../GridLayoutItemWrapper";
|
import { GridLayoutItemWrapper } from "../GridLayoutItemWrapper";
|
||||||
import { splitLayoutVertical } from "./utils/splitLayoutVertical";
|
import { splitLayoutVertical } from "./utils/splitLayoutVertical";
|
||||||
|
@ -12,10 +12,16 @@ export interface GridLayoutProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DefaultLayoutProps {
|
interface DefaultLayoutProps {
|
||||||
panelProps: IDockviewPanelProps<{ title: string }>;
|
panelProps: IDockviewPanelProps<{ title: string; locked?: boolean }>;
|
||||||
component: FC<GridLayoutComponentProps>;
|
component: FC<GridLayoutComponentProps>;
|
||||||
|
persistLayout: () => void;
|
||||||
}
|
}
|
||||||
const DefaultLayout = ({ component, panelProps }: DefaultLayoutProps) => {
|
|
||||||
|
const DefaultLayout = ({
|
||||||
|
component,
|
||||||
|
panelProps,
|
||||||
|
persistLayout,
|
||||||
|
}: DefaultLayoutProps) => {
|
||||||
const splitVertical = useCallback(() => {
|
const splitVertical = useCallback(() => {
|
||||||
splitLayoutVertical(panelProps.api.id, panelProps.containerApi);
|
splitLayoutVertical(panelProps.api.id, panelProps.containerApi);
|
||||||
}, [panelProps.api.id, panelProps.containerApi]);
|
}, [panelProps.api.id, panelProps.containerApi]);
|
||||||
|
@ -28,27 +34,47 @@ const DefaultLayout = ({ component, panelProps }: DefaultLayoutProps) => {
|
||||||
panelProps.api.close();
|
panelProps.api.close();
|
||||||
}, [panelProps.api]);
|
}, [panelProps.api]);
|
||||||
|
|
||||||
|
const locked = Boolean(panelProps.params.locked);
|
||||||
|
|
||||||
|
const lock = useCallback(() => {
|
||||||
|
panelProps.api.updateParameters({
|
||||||
|
...panelProps.params,
|
||||||
|
locked: !locked,
|
||||||
|
});
|
||||||
|
}, [locked, panelProps.api, panelProps.params]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
persistLayout();
|
||||||
|
}, [locked, persistLayout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GridLayoutItemWrapper
|
<GridLayoutItemWrapper
|
||||||
splitVertical={splitVertical}
|
splitVertical={splitVertical}
|
||||||
splitHorizontal={splitHorizontal}
|
splitHorizontal={splitHorizontal}
|
||||||
remove={remove}
|
remove={remove}
|
||||||
|
locked={locked}
|
||||||
|
lock={lock}
|
||||||
>
|
>
|
||||||
{createElement(component, {
|
{createElement(component, {
|
||||||
id: panelProps.api.id,
|
id: panelProps.api.id,
|
||||||
title: panelProps.params.title,
|
title: panelProps.params.title,
|
||||||
|
locked,
|
||||||
})}
|
})}
|
||||||
</GridLayoutItemWrapper>
|
</GridLayoutItemWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridLayout: FC<GridLayoutProps> = ({ component }) => {
|
export const GridLayout: FC<GridLayoutProps> = ({ component }) => {
|
||||||
const { onReady } = useGridLayoutPersistance();
|
const { onReady, persistLayout } = useGridLayoutPersistance();
|
||||||
|
|
||||||
const components = useMemo(
|
const components = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
default: (props: IDockviewPanelProps<{ title: string }>) => (
|
default: (props: IDockviewPanelProps<{ title: string }>) => (
|
||||||
<DefaultLayout panelProps={props} component={component} />
|
<DefaultLayout
|
||||||
|
panelProps={props}
|
||||||
|
component={component}
|
||||||
|
persistLayout={persistLayout}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
[component]
|
[component]
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
.layout {
|
.layout {
|
||||||
:global {
|
:global {
|
||||||
.vertical .sash {
|
.sash::after {
|
||||||
border-top: 1px solid var(--color-border);
|
content: "";
|
||||||
}
|
position: absolute;
|
||||||
|
inset: 1.5px;
|
||||||
.horizontal .sash {
|
background: var(--color-border);
|
||||||
border-right: 1px solid var(--color-border);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ export const createDefaultLayout = (api: DockviewApi) => {
|
||||||
title: "",
|
title: "",
|
||||||
params: {
|
params: {
|
||||||
title: "",
|
title: "",
|
||||||
|
locked: false,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const splitLayoutHorizontal = (
|
||||||
title: "",
|
title: "",
|
||||||
params: {
|
params: {
|
||||||
title: "",
|
title: "",
|
||||||
|
locked: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
referencePanel,
|
referencePanel,
|
||||||
|
|
|
@ -11,6 +11,7 @@ export const splitLayoutVertical = (
|
||||||
title: "",
|
title: "",
|
||||||
params: {
|
params: {
|
||||||
title: "",
|
title: "",
|
||||||
|
locked: false,
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
referencePanel,
|
referencePanel,
|
||||||
|
|
|
@ -5,13 +5,15 @@ import { IconButton } from "~/components/buttons/IconButton";
|
||||||
import DeleteIcon from "~/assets/images/delete.svg";
|
import DeleteIcon from "~/assets/images/delete.svg";
|
||||||
import SplitVertical from "~/assets/images/split-vertical.svg";
|
import SplitVertical from "~/assets/images/split-vertical.svg";
|
||||||
import SplitHorizontal from "~/assets/images/split-horizontal.svg";
|
import SplitHorizontal from "~/assets/images/split-horizontal.svg";
|
||||||
|
import Locked from "~/assets/images/locked.svg";
|
||||||
console.log(DeleteIcon);
|
import Unlocked from "~/assets/images/unlocked.svg";
|
||||||
|
|
||||||
type GridLayoutItemWrapperProps = PropsWithChildren & {
|
type GridLayoutItemWrapperProps = PropsWithChildren & {
|
||||||
splitVertical: () => void;
|
splitVertical: () => void;
|
||||||
splitHorizontal: () => void;
|
splitHorizontal: () => void;
|
||||||
remove: () => void;
|
remove: () => void;
|
||||||
|
locked: boolean;
|
||||||
|
lock: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GridLayoutItemWrapper: FC<GridLayoutItemWrapperProps> = ({
|
const GridLayoutItemWrapper: FC<GridLayoutItemWrapperProps> = ({
|
||||||
|
@ -19,25 +21,34 @@ const GridLayoutItemWrapper: FC<GridLayoutItemWrapperProps> = ({
|
||||||
splitVertical,
|
splitVertical,
|
||||||
splitHorizontal,
|
splitHorizontal,
|
||||||
remove,
|
remove,
|
||||||
|
locked,
|
||||||
|
lock,
|
||||||
}) => (
|
}) => (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
<div className={styles.menu}>
|
<div className={styles.menu}>
|
||||||
<IconButton
|
{!locked && (
|
||||||
onClick={splitVertical}
|
<>
|
||||||
role="button"
|
<IconButton
|
||||||
className={styles.button}
|
onClick={splitVertical}
|
||||||
>
|
role="button"
|
||||||
<SplitVertical />
|
className={styles.button}
|
||||||
</IconButton>
|
>
|
||||||
<IconButton
|
<SplitVertical />
|
||||||
onClick={splitHorizontal}
|
</IconButton>
|
||||||
role="button"
|
<IconButton
|
||||||
className={styles.button}
|
onClick={splitHorizontal}
|
||||||
>
|
role="button"
|
||||||
<SplitHorizontal />
|
className={styles.button}
|
||||||
</IconButton>
|
>
|
||||||
<IconButton onClick={remove} role="button" className={styles.button}>
|
<SplitHorizontal />
|
||||||
<DeleteIcon />
|
</IconButton>
|
||||||
|
<IconButton onClick={remove} role="button" className={styles.button}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<IconButton onClick={lock} role="button" className={styles.button}>
|
||||||
|
{locked ? <Locked /> : <Unlocked />}
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,21 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .menu {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 2px;
|
||||||
right: 0;
|
right: 4px;
|
||||||
|
transition: all 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
import { FC } from "react";
|
|
||||||
import {
|
|
||||||
Editor,
|
|
||||||
rootCtx,
|
|
||||||
defaultValueCtx,
|
|
||||||
editorViewOptionsCtx,
|
|
||||||
} from "@milkdown/core";
|
|
||||||
import { Milkdown, useEditor } from "@milkdown/react";
|
|
||||||
import { commonmark } from "@milkdown/preset-commonmark";
|
|
||||||
import { listener, listenerCtx } from "@milkdown/plugin-listener";
|
|
||||||
import { clipboard } from "@milkdown/plugin-clipboard";
|
|
||||||
|
|
||||||
import styles from "./styles.module.scss";
|
|
||||||
|
|
||||||
interface MarkdownEditorProps {
|
|
||||||
value?: string;
|
|
||||||
onChange?: (val: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const MarkdownEditor: FC<MarkdownEditorProps> = ({
|
|
||||||
value = "",
|
|
||||||
onChange,
|
|
||||||
}) => {
|
|
||||||
useEditor((root) =>
|
|
||||||
Editor.make()
|
|
||||||
.config((ctx) => {
|
|
||||||
ctx.set(rootCtx, root);
|
|
||||||
ctx.set(defaultValueCtx, value);
|
|
||||||
ctx.get(listenerCtx).markdownUpdated((_, markdown) => {
|
|
||||||
onChange?.(markdown);
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.update(editorViewOptionsCtx, (prev) => ({
|
|
||||||
...prev,
|
|
||||||
attributes: {
|
|
||||||
class: styles.editor,
|
|
||||||
spellcheck: "false",
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
})
|
|
||||||
.use(commonmark)
|
|
||||||
.use(listener)
|
|
||||||
.use(clipboard)
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Milkdown />;
|
|
||||||
};
|
|
|
@ -1,4 +1,5 @@
|
||||||
export interface GridLayoutComponentProps {
|
export interface GridLayoutComponentProps {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
locked: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { GridLayout } from "~/modules/layout/components/GridLayout";
|
import { GridLayout } from "~/modules/layout/components/GridLayout";
|
||||||
import { MarkdownEditorContainer } from "~/modules/layout/editor/containers/MarkdownEditorContainer/index";
|
import { MarkdownEditorContainer } from "~/modules/editor/containers/MarkdownEditorContainer/index";
|
||||||
|
|
||||||
const Editor: FC = () => <GridLayout component={MarkdownEditorContainer} />;
|
const Editor: FC = () => <GridLayout component={MarkdownEditorContainer} />;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue