facebook / lexical

Lexical is an extensible text editor framework that provides excellent reliability, accessibility and performance.
https://lexical.dev
MIT License
17.5k stars 1.45k forks source link

Feature: Lexical collaboration editor doesnt clear cache #5961

Open rahuldesktop opened 3 weeks ago

rahuldesktop commented 3 weeks ago

I am using below code to fetch latest content I get form database, however it fetches latest content from database but it as soon as I implement this code, collaboration stops working.

<CollaborationPlugin
  id={`${documentType}-${documentID}`}
  cursorColor={"#008cff"}
  username={userData?.first_name}
  // providerFactory={createWebsocketProvider}
  // providerFactory={(id, yjsDocMap) => createWebsocketProvider(id, yjsDocMap, content)}
  // @ts-ignore
  providerFactory={(id, yjsDocMap) => {
    const doc = new Y.Doc();
    const WEBSOCKET_ENDPOINT =
      process.env.NEXT_PUBLIC_WEBSOCKET_ENDPOINT || "ws://localhost:1234";
    // @ts-ignore
    yjsDocMap.set(id, doc);
    // console.log("content==>> ", content);
    console.log("updatedContent==>> ", content);
    if (id) {
      if (content) {
        const sharedText = doc.getText("content");
        sharedText.insert(0, content);
      }
      const provider = new WebsocketProvider(
        WEBSOCKET_ENDPOINT,
        id,
        // @ts-ignore
        doc
      );
      // new IndexeddbPersistence(
      //   id,
      //   // @ts-ignore
      //   doc
      // );

      return provider;
    } else {
      return null;
    }
  }}
  shouldBootstrap={!skipCollaborationInit}
  initialEditorState={initialEditorState}
/>

To make collaboration work I have to use below code, but that caches content on socket and doesnt fetch latest content from DB

<CollaborationPlugin
  id={`${documentType}-${documentID}`}
  cursorColor={"#008cff"}
  username={userData?.first_name}
  providerFactory={createWebsocketProvider}
  shouldBootstrap={!skipCollaborationInit}
  initialEditorState={initialEditorState}
/>

I want to make a code in such a way that I fetch latest content from DB and Collaboration still works fine. Also I need to know how can I close or destroy connection. Any help would be much appreciated.

Here is the full code for the reference


/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 */
import { CharacterLimitPlugin } from "@lexical/react/LexicalCharacterLimitPlugin";
import { CheckListPlugin } from "@lexical/react/LexicalCheckListPlugin";
import LexicalClickableLinkPlugin from "@lexical/react/LexicalClickableLinkPlugin";
import { CollaborationPlugin } from "@lexical/react/LexicalCollaborationPlugin";
import * as Y from "yjs";
import { WebsocketProvider } from "y-websocket";
import LexicalErrorBoundary from "@lexical/react/LexicalErrorBoundary";
import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
import { HorizontalRulePlugin } from "@lexical/react/LexicalHorizontalRulePlugin";
import { ListPlugin } from "@lexical/react/LexicalListPlugin";
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
import { TabIndentationPlugin } from "@lexical/react/LexicalTabIndentationPlugin";
import { TablePlugin } from "@lexical/react/LexicalTablePlugin";
import useLexicalEditable from "@lexical/react/useLexicalEditable";
import * as React from "react";
import { useEffect, useState } from "react";
import { CAN_USE_DOM } from "./shared/canUseDOM";

import { createWebsocketProvider } from "./collaboration";
import { useSettings } from "./context/SettingsContext";
import { useSharedHistoryContext } from "./context/SharedHistoryContext";
import AutocompletePlugin from "./plugins/AutocompletePlugin";
import ContextMenuPlugin from "./plugins/ContextMenuPlugin";
import DraggableBlockPlugin from "./plugins/DraggableBlockPlugin";
import FloatingLinkEditorPlugin from "./plugins/FloatingLinkEditorPlugin";
import FloatingTextFormatToolbarPlugin from "./plugins/FloatingTextFormatToolbarPlugin";
import InlineImagePlugin from "./plugins/InlineImagePlugin";
import { LayoutPlugin } from "./plugins/LayoutPlugin/LayoutPlugin";
import LinkPlugin from "./plugins/LinkPlugin";
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentLevelPlugin";
import { MaxLengthPlugin } from "./plugins/MaxLengthPlugin";
import PageBreakPlugin from "./plugins/PageBreakPlugin";
import TabFocusPlugin from "./plugins/TabFocusPlugin";
import TableCellActionMenuPlugin from "./plugins/TableActionMenuPlugin";
import TableCellResizer from "./plugins/TableCellResizer";
import TableOfContentsPlugin from "./plugins/TableOfContentsPlugin";
import ToolbarPlugin from "./plugins/ToolbarPlugin";
import ContentEditable from "./ui/ContentEditable";
import Placeholder from "./ui/Placeholder";
import {
  $getRoot,
  $createParagraphNode,
  $insertNodes,
  $createTextNode,
  LexicalEditor,
} from "lexical";
import { $generateNodesFromDOM } from "@lexical/html";
import { IndexeddbPersistence } from "y-indexeddb";

let skipCollaborationInit = false;
if (
  typeof window !== "undefined" &&
  window.parent != null &&
  (window.parent.frames as any).right === window
) {
  skipCollaborationInit = true;
}

export default function Editor({
  userData,
  documentID,
  documentType,
  content,
  noOfOnlineUsers,
  setNoOfOnlineUsers,
  onlineUsersRef,
  isCollab,
  docVersion,
  updatedContent,
}): JSX.Element {
  const { historyState } = useSharedHistoryContext();
  const {
    settings: {
      isAutocomplete,
      isMaxLength,
      isCharLimit,
      isCharLimitUtf8,
      isRichText,
      showTableOfContents,
      shouldUseLexicalContextMenu,
      tableCellMerge,
      tableCellBackgroundColor,
    },
  } = useSettings();
  const isEditable = useLexicalEditable();
  const text = "";
  const placeholder = <Placeholder>{text}</Placeholder>;
  const [floatingAnchorElem, setFloatingAnchorElem] = useState<HTMLDivElement | null>(null);
  const [isSmallWidthViewport, setIsSmallWidthViewport] = useState<boolean>(false);
  const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);

  const onRef = (_floatingAnchorElem: HTMLDivElement) => {
    if (_floatingAnchorElem !== null) {
      setFloatingAnchorElem(_floatingAnchorElem);
    }
  };

  useEffect(() => {
    const updateViewPortWidth = () => {
      const isNextSmallWidthViewport =
        CAN_USE_DOM && window.matchMedia("(max-width: 1025px)").matches;

      if (isNextSmallWidthViewport !== isSmallWidthViewport) {
        setIsSmallWidthViewport(isNextSmallWidthViewport);
      }
    };
    updateViewPortWidth();
    window.addEventListener("resize", updateViewPortWidth);

    return () => {
      window.removeEventListener("resize", updateViewPortWidth);
    };
  }, [isSmallWidthViewport]);

  const initialEditorState = (editor: LexicalEditor) => {
    const parser = new DOMParser();
    const dom = parser.parseFromString(content, "text/html");

    const nodes = $generateNodesFromDOM(editor, dom);

    // Select the root
    $getRoot().select();
    // Insert them at a selection.
    $insertNodes(nodes);
  };

  return (
    <>
      {/* <div>test</div> */}
      {isRichText && <ToolbarPlugin setIsLinkEditMode={setIsLinkEditMode} />}
      <div className={`editor-container ${!isRichText ? "plain-text" : ""}`}>
        {isMaxLength && <MaxLengthPlugin maxLength={30} />}
        {isRichText ? (
          <>
            {/*<HistoryPlugin externalHistoryState={historyState} />*/}
            <PlainTextPlugin
              contentEditable={
                <div className="editor-scroller">
                  <div
                    className="editor border border-grey-300 rounded-bl-md rounded-br-md"
                    ref={onRef}
                  >
                    <ContentEditable />
                  </div>
                </div>
              }
              placeholder={placeholder}
              ErrorBoundary={LexicalErrorBoundary}
            />
            {isCollab &&
              (updatedContent ? (
                // Fetch latest content from DB
                <CollaborationPlugin
                  id={`${documentType}-${documentID}`}
                  cursorColor={"#008cff"}
                  username={userData?.first_name}
                  // @ts-ignore
                  providerFactory={(id, yjsDocMap) => {
                    const doc = new Y.Doc();
                    const WEBSOCKET_ENDPOINT =
                      process.env.NEXT_PUBLIC_WEBSOCKET_ENDPOINT || "ws://localhost:1234";
                    // @ts-ignore
                    yjsDocMap.set(id, doc);
                    // console.log("content==>> ", content);
                    console.log("updatedContent==>> ", content);
                    if (id) {
                      if (content) {
                        const sharedText = doc.getText("content");
                        sharedText.insert(0, content);
                      }
                      const provider = new WebsocketProvider(
                        WEBSOCKET_ENDPOINT,
                        id,
                        // @ts-ignore
                        doc
                      );
                      new IndexeddbPersistence(
                        id,
                        // @ts-ignore
                        doc
                      );

                      return provider;
                    } else {
                      return null;
                    }
                  }}
                  shouldBootstrap={!skipCollaborationInit}
                  initialEditorState={initialEditorState}
                />
              ) : (
                <CollaborationPlugin
                  id={`${documentType}-${documentID}`}
                  cursorColor={"#008cff"}
                  username={userData?.first_name}
                  providerFactory={createWebsocketProvider}
                  shouldBootstrap={!skipCollaborationInit}
                  initialEditorState={initialEditorState}
                />
              ))}
            {/*<CodeHighlightPlugin />*/}
            <ListPlugin />
            <CheckListPlugin />
            <ListMaxIndentLevelPlugin maxDepth={7} />
            <TablePlugin
              hasCellMerge={tableCellMerge}
              hasCellBackgroundColor={tableCellBackgroundColor}
            />
            <TableCellResizer />
            <InlineImagePlugin />
            <LinkPlugin />
            {!isEditable && <LexicalClickableLinkPlugin />}
            <HorizontalRulePlugin />
            <TabFocusPlugin />
            <TabIndentationPlugin />
            <PageBreakPlugin />
            <LayoutPlugin />
            {floatingAnchorElem && !isSmallWidthViewport && (
              <>
                <DraggableBlockPlugin anchorElem={floatingAnchorElem} />
                <FloatingLinkEditorPlugin
                  anchorElem={floatingAnchorElem}
                  isLinkEditMode={isLinkEditMode}
                  setIsLinkEditMode={setIsLinkEditMode}
                />
                <TableCellActionMenuPlugin anchorElem={floatingAnchorElem} cellMerge={true} />
                <FloatingTextFormatToolbarPlugin anchorElem={floatingAnchorElem} />
              </>
            )}
          </>
        ) : (
          <>
            <HistoryPlugin externalHistoryState={historyState} />
          </>
        )}
        {(isCharLimit || isCharLimitUtf8) && (
          <CharacterLimitPlugin charset={isCharLimit ? "UTF-16" : "UTF-8"} maxLength={5} />
        )}
        {isAutocomplete && <AutocompletePlugin />}
        <div>{showTableOfContents && <TableOfContentsPlugin />}</div>
        {shouldUseLexicalContextMenu && <ContextMenuPlugin />}
      </div>
    </>
  );
}