ryoji-s 2 лет назад
Родитель
Сommit
8525c5ecdf
1 измененных файлов с 77 добавлено и 6 удалено
  1. 77 6
      apps/app/src/components/PageEditor/PageEditor.tsx

+ 77 - 6
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -12,6 +12,8 @@ import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { throttle, debounce } from 'throttle-debounce';
+import { SocketIOProvider } from 'y-socket.io';
+import * as Y from 'yjs';
 
 import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
@@ -468,16 +470,83 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
   }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);
 
+  const [ydoc, setYdoc] = useState<Y.Doc | null>(null);
+  const [provider, setProvider] = useState<SocketIOProvider | null>(null);
+  const [cPageId, setCPageId] = useState(pageId);
+
+  // cleanup ydoc and provider
+  useEffect(() => {
+    if (cPageId === pageId) {
+      return;
+    }
+    if (!provider || !ydoc || socket == null) {
+      return;
+    }
+
+    ydoc.destroy();
+    setYdoc(null);
+
+    provider.destroy();
+    provider.disconnect();
+    setProvider(null);
+
+    socket.off('sync:ydoc');
+
+    setCPageId(pageId);
+  }, [cPageId, pageId, provider, socket, ydoc]);
+
+  // setup ydoc
+  useEffect(() => {
+    if (ydoc != null) {
+      return;
+    }
+
+    const _ydoc = new Y.Doc();
+    setYdoc(_ydoc);
+  }, [initialValue, ydoc]);
+
+  // setup socketIOProvider
+  useEffect(() => {
+    if (ydoc == null || provider != null || socket == null) {
+      return;
+    }
+
+    const socketIOProvider = new SocketIOProvider(
+      'ws://localhost:3000',
+      `yjs/${pageId}`,
+      ydoc,
+      { autoConnect: true },
+    );
+    // TODO: apply provider awareness
+    // https://redmine.weseek.co.jp/issues/130770
+    socketIOProvider.awareness.setLocalState({ id: Math.random(), name: 'Perico' });
+    socketIOProvider.on('sync', (isSync: boolean) => {
+      // TODO: check behavior when ydoc diff is retrieved once from server but sync is false exactly 5 seconds later
+      setTimeout(() => {
+        if (!isSync) {
+          ydoc.getText('codemirror').insert(0, initialValue);
+        }
+      }, 5000);
+
+      if (isSync) {
+        socket.emit('sync:ydoc', { pageId, initialValue });
+      }
+    });
+    socketIOProvider.on('status', ({ status: _status }: { status: string }) => {
+      if (_status) console.log(_status);
+    });
+    setProvider(socketIOProvider);
+  }, [initialValue, pageId, provider, socket, ydoc]);
 
   // initialize
   useEffect(() => {
-    if (initialValue == null) {
+    if (ydoc == null) {
       return;
     }
-    codeMirrorEditor?.initDoc(initialValue);
-    setMarkdownToPreview(initialValue);
+    codeMirrorEditor?.initDoc(ydoc.getText('codemirror').toString());
+    setMarkdownToPreview(ydoc.getText('codemirror').toString());
     mutateIsEnabledUnsavedWarning(false);
-  }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning]);
+  }, [codeMirrorEditor, initialValue, mutateIsEnabledUnsavedWarning, ydoc]);
 
   // initial caret line
   useEffect(() => {
@@ -536,9 +605,9 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   // when transitioning to a different page, if the initialValue is the same,
   // UnControlled CodeMirror value does not reset, so explicitly set the value to initialValue
   const onRouterChangeComplete = useCallback(() => {
-    codeMirrorEditor?.initDoc(initialValue);
+    codeMirrorEditor?.initDoc(ydoc?.getText('codemirror').toString());
     codeMirrorEditor?.setCaretLine();
-  }, [codeMirrorEditor, initialValue]);
+  }, [codeMirrorEditor, ydoc]);
 
   useEffect(() => {
     router.events.on('routeChangeComplete', onRouterChangeComplete);
@@ -576,6 +645,8 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
           onChange={markdownChangedHandler}
           onSave={saveWithShortcut}
           indentSize={currentIndentSize ?? defaultIndentSize}
+          ydoc={ydoc}
+          provider={provider}
         />
       </div>
       <div className="page-editor-preview-container flex-expand-vert d-none d-lg-flex">