Updates
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import MarkdownEditorPreviewComponent from "./MarkdownEditorPreviewComponent";
|
||||
import MarkdownEditorComponent from "./MarkdownEditorComponent";
|
||||
import MarkdownEditorSelectorButtons from "./MarkdownEditorSelectorButtons";
|
||||
import Row from "../../layout/Row";
|
||||
import Stack from "../../layout/Stack";
|
||||
import AceEditor from "../../editors/AceEditor";
|
||||
|
||||
type Props = {
|
||||
value?: string;
|
||||
setValue?: React.Dispatch<React.SetStateAction<string>>;
|
||||
defaultSideBySide?: boolean;
|
||||
changeHandler?: (content: string) => void;
|
||||
editorProps?: ComponentProps<typeof AceEditor>;
|
||||
maxHeight?: string;
|
||||
};
|
||||
|
||||
export default function MarkdownEditor({
|
||||
value: existingValue,
|
||||
setValue: setExistingValue,
|
||||
defaultSideBySide,
|
||||
changeHandler,
|
||||
editorProps,
|
||||
maxHeight: existingMaxHeight,
|
||||
}: Props) {
|
||||
const [value, setValue] = React.useState<any>(existingValue || ``);
|
||||
|
||||
const [sideBySide, setSideBySide] = React.useState(
|
||||
defaultSideBySide || false
|
||||
);
|
||||
const [preview, setPreview] = React.useState(false);
|
||||
|
||||
const maxHeight = existingMaxHeight || "600px";
|
||||
|
||||
React.useEffect(() => {
|
||||
setExistingValue?.(value);
|
||||
changeHandler?.(value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Stack className="w-full items-stretch">
|
||||
<MarkdownEditorSelectorButtons
|
||||
{...{ preview, setPreview, setSideBySide, sideBySide }}
|
||||
/>
|
||||
|
||||
{sideBySide ? (
|
||||
<Row
|
||||
className={twMerge(
|
||||
`w-full grid xl:grid-cols-2 gap-6 max-h-[${maxHeight}]`,
|
||||
"overflow-auto"
|
||||
)}
|
||||
>
|
||||
<MarkdownEditorComponent
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
maxHeight={maxHeight}
|
||||
{...editorProps}
|
||||
/>
|
||||
<MarkdownEditorPreviewComponent
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
maxHeight={maxHeight}
|
||||
/>
|
||||
</Row>
|
||||
) : (
|
||||
<Stack
|
||||
className={twMerge(`w-full max-h-[${maxHeight}] h-full`)}
|
||||
>
|
||||
{preview ? (
|
||||
<MarkdownEditorPreviewComponent
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
maxHeight={maxHeight}
|
||||
/>
|
||||
) : (
|
||||
<MarkdownEditorComponent
|
||||
setValue={setValue}
|
||||
value={value}
|
||||
maxHeight={maxHeight}
|
||||
{...editorProps}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
import AceEditor from "../../editors/AceEditor";
|
||||
|
||||
type Props = ComponentProps<typeof AceEditor> & {
|
||||
value: string;
|
||||
setValue: React.Dispatch<any>;
|
||||
maxHeight: string;
|
||||
};
|
||||
|
||||
export default function MarkdownEditorComponent({
|
||||
value,
|
||||
setValue,
|
||||
maxHeight,
|
||||
...props
|
||||
}: Props) {
|
||||
return (
|
||||
<AceEditor
|
||||
mode="markdown"
|
||||
content={value}
|
||||
onChange={(newValue) => {
|
||||
setValue(newValue);
|
||||
}}
|
||||
wrapperProps={{
|
||||
style: { height: maxHeight },
|
||||
className: `max-h-[${maxHeight}]`,
|
||||
}}
|
||||
placeholder="## Write Some markdown ..."
|
||||
fontSize="14px"
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
import rehypeSanitize from "rehype-sanitize";
|
||||
import React from "react";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypePrismPlus from "rehype-prism-plus";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
export default function MarkdownEditor() {
|
||||
const [value, setValue] = React.useState<any>(
|
||||
`**Hello world!!!** <IFRAME SRC=\"javascript:javascript:alert(window.origin);\"></IFRAME>`
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
console.log("value", value);
|
||||
}, [value]);
|
||||
|
||||
return ReactDOM.createPortal(
|
||||
<MDEditor
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
previewOptions={{
|
||||
rehypePlugins: [rehypeSanitize, rehypePrismPlus],
|
||||
remarkPlugins: [remarkGfm],
|
||||
}}
|
||||
/>,
|
||||
document.getElementById("markdown-modal-root") as HTMLElement
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { serialize } from "next-mdx-remote/serialize";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import rehypePrismPlus from "rehype-prism-plus";
|
||||
import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote";
|
||||
import Border from "../../elements/Border";
|
||||
import { useMDXComponents } from "../mdx-components";
|
||||
import EmptyContent from "../../elements/EmptyContent";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
setValue: React.Dispatch<any>;
|
||||
maxHeight: string;
|
||||
};
|
||||
|
||||
export default function MarkdownEditorPreviewComponent({
|
||||
value,
|
||||
setValue,
|
||||
maxHeight,
|
||||
}: Props) {
|
||||
try {
|
||||
const [mdxSource, setMdxSource] =
|
||||
React.useState<
|
||||
MDXRemoteSerializeResult<
|
||||
Record<string, unknown>,
|
||||
Record<string, unknown>
|
||||
>
|
||||
>();
|
||||
|
||||
const { components } = useMDXComponents();
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
// const { data, content } = matter(value);
|
||||
|
||||
const parsedValue = value
|
||||
.replace(/---([^(---)]+)---/, "")
|
||||
.trim();
|
||||
|
||||
serialize(parsedValue, {
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
rehypePlugins: [rehypePrismPlus],
|
||||
},
|
||||
// scope: data,
|
||||
})
|
||||
.then((mdxSrc) => {
|
||||
setMdxSource(mdxSrc);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`Markdown Parsing Error => ${err.message}`);
|
||||
});
|
||||
} catch (error) {}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<Border
|
||||
className={twMerge(
|
||||
`w-full max-h-[${maxHeight}] h-[${maxHeight}] block px-6 pb-10`,
|
||||
"overflow-auto"
|
||||
)}
|
||||
>
|
||||
{mdxSource ? (
|
||||
<MDXRemote
|
||||
{...mdxSource}
|
||||
components={{
|
||||
...components,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</Border>
|
||||
);
|
||||
} catch (error) {
|
||||
return <EmptyContent title={`Markdown Syntax Error.`} />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from "react";
|
||||
import Button from "../../layout/Button";
|
||||
import Border from "../../elements/Border";
|
||||
import { LocalStorageDict } from "@/package-shared/dict/local-storage-dict";
|
||||
|
||||
type Props = {
|
||||
preview: boolean;
|
||||
setPreview: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
sideBySide: boolean;
|
||||
setSideBySide: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export default function MarkdownEditorSelectorButtons({
|
||||
preview,
|
||||
setPreview,
|
||||
setSideBySide,
|
||||
sideBySide,
|
||||
}: Props) {
|
||||
const [ready, setReady] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!ready) return;
|
||||
|
||||
if (sideBySide) {
|
||||
localStorage.setItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultSideBySide"],
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
localStorage.removeItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultSideBySide"]
|
||||
);
|
||||
}
|
||||
|
||||
if (preview) {
|
||||
localStorage.setItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultPreview"],
|
||||
"true"
|
||||
);
|
||||
} else {
|
||||
localStorage.removeItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultPreview"]
|
||||
);
|
||||
}
|
||||
}, [sideBySide, preview, ready]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (
|
||||
localStorage.getItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultPreview"]
|
||||
)
|
||||
) {
|
||||
setPreview(true);
|
||||
}
|
||||
if (
|
||||
!localStorage.getItem(
|
||||
LocalStorageDict["MarkdownEditorDefaultSideBySide"]
|
||||
)
|
||||
) {
|
||||
setSideBySide(false);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setReady(true);
|
||||
}, 200);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Border className="p-1.5">
|
||||
<Button
|
||||
title="Code"
|
||||
size="smaller"
|
||||
color={sideBySide ? "gray" : preview ? "gray" : "primary"}
|
||||
variant={
|
||||
sideBySide ? "outlined" : preview ? "outlined" : "normal"
|
||||
}
|
||||
onClick={() => {
|
||||
setSideBySide(false);
|
||||
setPreview(false);
|
||||
}}
|
||||
>
|
||||
Code
|
||||
</Button>
|
||||
<Button
|
||||
title="Preview"
|
||||
size="smaller"
|
||||
color={sideBySide ? "gray" : preview ? "primary" : "gray"}
|
||||
variant={
|
||||
sideBySide ? "outlined" : preview ? "normal" : "outlined"
|
||||
}
|
||||
onClick={() => {
|
||||
setSideBySide(false);
|
||||
setPreview(true);
|
||||
}}
|
||||
>
|
||||
Preview
|
||||
</Button>
|
||||
<Button
|
||||
title="Side By Side"
|
||||
size="smaller"
|
||||
color={sideBySide ? "primary" : "gray"}
|
||||
variant={sideBySide ? "normal" : "outlined"}
|
||||
onClick={() => {
|
||||
setSideBySide(true);
|
||||
}}
|
||||
>
|
||||
Side By Side
|
||||
</Button>
|
||||
</Border>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user