Untitled
unknown
javascript
a year ago
6.5 kB
14
Indexable
// editor.js
import { savePage } from "@/actions/save-page";
import { useToast } from "@/hooks/useToast";
import { useEdgeStore } from "@/lib/edgestore";
import { initSocket } from "@/lib/socket";
import {
AdmonitionDirectiveDescriptor,
codeBlockPlugin,
codeMirrorPlugin,
diffSourcePlugin,
directivesPlugin,
frontmatterPlugin,
headingsPlugin,
imagePlugin,
linkDialogPlugin,
linkPlugin,
listsPlugin,
markdownShortcutPlugin,
MDXEditor,
quotePlugin,
tablePlugin,
thematicBreakPlugin,
toolbarPlugin,
} from "@mdxeditor/editor";
import Toast from "awesome-toast-component";
import { useSession } from "next-auth/react";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useMemo, useRef, useTransition } from "react";
import { Toolbar } from "./Toolbar";
let saveToDBTimeout = null;
let intervalToSaveInDb = 1800;
const Editor = ({ markdown, isViewOnly }) => {
const session = useSession();
const params = useSearchParams();
const groupId = params.get("g");
const pageNumber = params.get("page");
const mdRef = useRef(null);
const [ToastSuccess, ToastError] = useToast();
const [isPending, startTransition] = useTransition();
const router = useRouter();
const socketRef = useRef(null);
const { edgestore } = useEdgeStore();
const allPlugins = useMemo(
() => (diffMarkdown, isSaving) =>
[
toolbarPlugin({
toolbarContents: () => (
<>
{isViewOnly ? (
<span>
<i>View only mode</i>
</span>
) : (
<Toolbar isSaving={isSaving} />
)}
</>
),
}),
listsPlugin(),
quotePlugin(),
headingsPlugin(),
linkPlugin(),
linkDialogPlugin(),
imagePlugin({
imageUploadHandler: async (e) => {
const tst = ToastSuccess("Uploading " + 0 + "%", {
timeout: 2000,
});
if (e) {
const res = await edgestore.publicFiles.upload({
file: e,
onProgressChange: (progress) => {
tst.setMessage("Uploading " + progress + "%");
},
});
tst.hide();
return res.url;
} else {
ToastError("Uploading error");
return "/error-upload.png";
}
},
}),
tablePlugin(),
thematicBreakPlugin(),
frontmatterPlugin(),
codeBlockPlugin({ defaultCodeBlockLanguage: "txt" }),
codeMirrorPlugin({
codeBlockLanguages: {
js: "JavaScript",
css: "CSS",
txt: "text",
tsx: "TypeScript",
},
}),
directivesPlugin({
directiveDescriptors: [AdmonitionDirectiveDescriptor],
}),
diffSourcePlugin({ viewMode: "rich-text", diffMarkdown }),
markdownShortcutPlugin(),
],
[]
);
useEffect(() => {
mdRef.current.setMarkdown(markdown);
const init = async () => {
// connect to websocket server
socketRef.current = await initSocket();
// handle websockets error
socketRef.current.on("connect_error", () => {
ToastError("Error establishing connection");
router.push("/");
});
socketRef.current.on("connect_failed", () => {
ToastError("Error establishing connection");
router.push("/");
});
// join room
socketRef.current.emit("joinRoom", {
group: groupId,
page: pageNumber,
user: session.data.user,
});
socketRef.current.on("userJoined", (user) => {
if (user.id !== session.data.user.id) {
new Toast(user.name + " joined the page!", {
theme: "light",
timeout: 800,
position: "bottom",
});
}
});
socketRef.current.on("userLeft", (user) => {
if (user.id !== session.data.user.id) {
new Toast(user.name + " left the page!", {
theme: "light",
timeout: 800,
position: "bottom",
});
}
});
socketRef.current.on("pageChange", ({ user, markdown }) => {
if (user.id !== session.data.user.id) {
mdRef.current.setMarkdown(markdown);
}
});
};
init();
return () => {
// disconnect the socket when user leaves the page
if (socketRef.current) {
socketRef.current.disconnect();
}
};
}, [params, markdown]);
const handleMarkdownChange = useCallback((e) => {
if (isViewOnly || isPending) return;
function normalizeString(str) {
return str
.trim()
.replace(/\s+/g, "")
.replace(/\r?\n|\r/g, "");
}
// preventing saving to database, without change when page first loads
if (normalizeString(e) === normalizeString(markdown)) {
return;
}
if (saveToDBTimeout) {
clearTimeout(saveToDBTimeout);
}
// Set a new timeout
saveToDBTimeout = setTimeout(async () => {
startTransition(async () => {
const data = await savePage(
mdRef.current.getMarkdown(),
groupId,
pageNumber
);
if (!data.success) {
ToastError(data.message);
}
mdRef.current.focus();
});
}, intervalToSaveInDb);
if (mdRef.current) {
socketRef.current.emit("pageChange", {
user: session.data.user,
markdown: mdRef.current.getMarkdown(),
});
}
}, []);
console.count("rendered time");
return (
<MDXEditor
markdown={markdown}
onChange={handleMarkdownChange}
ref={mdRef}
className="h-full max-h-full border-r border-b border-l border-slate-100 rounded-lg overflow-y-auto"
contentEditableClassName="prose max-w-full font-sans"
plugins={allPlugins(markdown, isPending)}
placeholder="Start Editing"
readOnly={isViewOnly}
autoFocus
/>
);
};
export default Editor;
//editor-wrapper.js
const Editor = dynamic(async () => await import("./Editor"), {
ssr: false,
loading: () => (
<div className="mt-4 ml-2 flex">
<Loader className="text-gray-500 animate-spin text-sm" />{" "}
<span className="ml-2">Loading Editor</span>
</div>
),
});
export const EditorWrapper = ()=>{
return <>
<h2>Editor will render here </h2>
<Editor>
</>
}
Editor is loading...
Leave a Comment