Untitled

 avatar
unknown
javascript
a year ago
6.5 kB
8
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