Просмотр исходного кода

create use collaborative editor mode

ryoji-s 2 лет назад
Родитель
Сommit
cb6a8ca01f

+ 5 - 114
packages/editor/src/components/CodeMirrorEditorMain.tsx

@@ -1,18 +1,10 @@
-import { useEffect, useState } from 'react';
+import { useEffect } from 'react';
 
 import type { Extension } from '@codemirror/state';
 import { keymap, scrollPastEnd } from '@codemirror/view';
-import { GlobalSocketEventName } from '@growi/core/dist/interfaces';
-import { useGlobalSocket, GLOBAL_SOCKET_NS } from '@growi/core/dist/swr';
-// see: https://github.com/yjs/y-codemirror.next#example
-// eslint-disable-next-line @typescript-eslint/ban-ts-comment
-// @ts-ignore
-import { yCollab } from 'y-codemirror.next';
-import { SocketIOProvider } from 'y-socket.io';
-import * as Y from 'yjs';
-
-import { GlobalCodeMirrorEditorKey, AcceptedUploadFileType, userColor } from '../consts';
-import { useCodeMirrorEditorIsolated } from '../stores';
+
+import { GlobalCodeMirrorEditorKey, AcceptedUploadFileType } from '../consts';
+import { useCodeMirrorEditorIsolated, useCollaborativeEditorMode } from '../stores';
 
 import { CodeMirrorEditor } from '.';
 
@@ -38,112 +30,11 @@ export const CodeMirrorEditorMain = (props: Props): JSX.Element => {
   } = props;
 
   const { data: codeMirrorEditor } = useCodeMirrorEditorIsolated(GlobalCodeMirrorEditorKey.MAIN);
-  const { data: socket } = useGlobalSocket();
 
-  const [ydoc, setYdoc] = useState<Y.Doc | null>(null);
-  const [provider, setProvider] = useState<SocketIOProvider | null>(null);
-  const [cPageId, setCPageId] = useState(pageId);
-  const [isInit, setIsInit] = useState(false);
+  useCollaborativeEditorMode(userName, pageId, initialValue, onOpenEditor, codeMirrorEditor);
 
   const acceptedFileTypeNoOpt = acceptedFileType ?? AcceptedUploadFileType.NONE;
 
-  // Cleanup ydoc and socketIOProvider when the page id changes
-  useEffect(() => {
-    if (cPageId === pageId) {
-      return;
-    }
-
-    // Cleanup existing ydoc
-    ydoc?.destroy();
-    setYdoc(null);
-
-    // Disconnect and destroy the existing socketIOProvider
-    provider?.disconnect();
-    provider?.destroy();
-    setProvider(null);
-
-    // Remove the sync event listener
-    // TODO: catch ydoc:sync:error GlobalSocketEventName.YDocSyncError
-    socket.off(GlobalSocketEventName.YDocSync);
-
-    // Reset initialization state and update the current pageId
-    setIsInit(false);
-    setCPageId(pageId);
-  }, [cPageId, pageId, provider, socket, ydoc]);
-
-  // Setup ydoc when it's not setuped
-  useEffect(() => {
-    if (ydoc != null) {
-      return;
-    }
-
-    const _ydoc = new Y.Doc();
-    setYdoc(_ydoc);
-  }, [ydoc]);
-
-  // Setup socketIOProvider and sync to server
-  useEffect(() => {
-    if (provider != null || ydoc == null || socket == null) {
-      return;
-    }
-
-    // Create a new SocketIOProvider
-    const socketIOProvider = new SocketIOProvider(
-      GLOBAL_SOCKET_NS,
-      `yjs/${pageId}`,
-      ydoc,
-      { autoConnect: true },
-    );
-
-    // Set local user information for awareness
-    socketIOProvider.awareness.setLocalStateField('user', {
-      name: userName ? `${userName}` : `Guest User ${Math.floor(Math.random() * 100)}`,
-      color: userColor.color,
-      colorLight: userColor.light,
-    });
-
-    // Add a sync event listener to initiate synchronization
-    socketIOProvider.on('sync', (isSync: boolean) => {
-      if (isSync) {
-        socket.emit(GlobalSocketEventName.YDocSync, { pageId, initialValue });
-      }
-    });
-
-    // Set the new provider
-    setProvider(socketIOProvider);
-  }, [initialValue, pageId, provider, socket, userName, ydoc]);
-
-  // Attach YDoc extensions to CodeMirror
-  useEffect(() => {
-    if (ydoc == null || provider == null) {
-      return;
-    }
-
-    const ytext = ydoc.getText('codemirror');
-    const undoManager = new Y.UndoManager(ytext);
-
-    const cleanup = codeMirrorEditor?.appendExtensions?.([
-      yCollab(ytext, provider.awareness, { undoManager }),
-    ]);
-
-    return cleanup;
-  }, [codeMirrorEditor, provider, ydoc]);
-
-  // Initialize markdown and preview
-  useEffect(() => {
-    if (ydoc == null || onOpenEditor == null || isInit === true) {
-      return;
-    }
-
-    const ytext = ydoc.getText('codemirror');
-
-    // Initialize CodeMirror Doc and Preview value
-    codeMirrorEditor?.initDoc(ytext.toString());
-    onOpenEditor(ytext.toString());
-
-    setIsInit(true);
-  }, [codeMirrorEditor, isInit, onOpenEditor, ydoc]);
-
   // setup additional extensions
   useEffect(() => {
     return codeMirrorEditor?.appendExtensions?.(additionalExtensions);

+ 1 - 0
packages/editor/src/stores/index.ts

@@ -1,2 +1,3 @@
 export * from './codemirror-editor';
 export * from './use-resolved-theme';
+export * from './use-collaborative-editor-mode';

+ 116 - 0
packages/editor/src/stores/use-collaborative-editor-mode.ts

@@ -0,0 +1,116 @@
+import { useEffect, useState } from 'react';
+
+import { GlobalSocketEventName } from '@growi/core/dist/interfaces';
+import { useGlobalSocket, GLOBAL_SOCKET_NS } from '@growi/core/dist/swr';
+// see: https://github.com/yjs/y-codemirror.next#example
+// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+// @ts-ignore
+import { yCollab } from 'y-codemirror.next';
+import { SocketIOProvider } from 'y-socket.io';
+import * as Y from 'yjs';
+
+import { userColor } from '../consts';
+import { UseCodeMirrorEditor } from '../services';
+
+export const useCollaborativeEditorMode = (
+    userName?: string,
+    pageId?: string,
+    initialValue?: string,
+    onOpenEditor?: (markdown: string) => void,
+    codeMirrorEditor?: UseCodeMirrorEditor,
+): void => {
+  const [ydoc, setYdoc] = useState<Y.Doc | null>(null);
+  const [provider, setProvider] = useState<SocketIOProvider | null>(null);
+  const [isInit, setIsInit] = useState(false);
+  const [cPageId, setCPageId] = useState(pageId);
+
+  const { data: socket } = useGlobalSocket();
+
+  const cleanupYDocAndProvider = () => {
+    if (cPageId === pageId) {
+      return;
+    }
+
+    ydoc?.destroy();
+    setYdoc(null);
+
+    provider?.disconnect();
+    provider?.destroy();
+    setProvider(null);
+
+    // TODO: catch ydoc:sync:error GlobalSocketEventName.YDocSyncError
+    socket.off(GlobalSocketEventName.YDocSync);
+
+    setIsInit(false);
+    setCPageId(pageId);
+  };
+
+  const setupYDoc = () => {
+    if (ydoc != null) {
+      return;
+    }
+
+    const _ydoc = new Y.Doc();
+    setYdoc(_ydoc);
+  };
+
+  const setupProvider = () => {
+    if (provider != null || ydoc == null || socket == null) {
+      return;
+    }
+
+    const socketIOProvider = new SocketIOProvider(
+      GLOBAL_SOCKET_NS,
+      `yjs/${pageId}`,
+      ydoc,
+      { autoConnect: true },
+    );
+
+    socketIOProvider.awareness.setLocalStateField('user', {
+      name: userName ? `${userName}` : `Guest User ${Math.floor(Math.random() * 100)}`,
+      color: userColor.color,
+      colorLight: userColor.light,
+    });
+
+    socketIOProvider.on('sync', (isSync: boolean) => {
+      if (isSync) {
+        socket.emit(GlobalSocketEventName.YDocSync, { pageId, initialValue });
+      }
+    });
+
+    setProvider(socketIOProvider);
+  };
+
+  const attachYDocExtensionsToCodeMirror = () => {
+    if (ydoc == null || provider == null) {
+      return;
+    }
+
+    const ytext = ydoc.getText('codemirror');
+    const undoManager = new Y.UndoManager(ytext);
+
+    const cleanup = codeMirrorEditor?.appendExtensions?.([
+      yCollab(ytext, provider.awareness, { undoManager }),
+    ]);
+
+    return cleanup;
+  };
+
+  const initializeEditor = () => {
+    if (ydoc == null || onOpenEditor == null || isInit === true) {
+      return;
+    }
+
+    const ytext = ydoc.getText('codemirror');
+    codeMirrorEditor?.initDoc(ytext.toString());
+    onOpenEditor(ytext.toString());
+
+    setIsInit(true);
+  };
+
+  useEffect(cleanupYDocAndProvider, [cPageId, pageId, provider, socket, ydoc]);
+  useEffect(setupYDoc, [ydoc]);
+  useEffect(setupProvider, [initialValue, pageId, provider, socket, userName, ydoc]);
+  useEffect(attachYDocExtensionsToCodeMirror, [codeMirrorEditor, provider, ydoc]);
+  useEffect(initializeEditor, [codeMirrorEditor, isInit, onOpenEditor, ydoc]);
+};