Shun Miyazawa 2 лет назад
Родитель
Сommit
7ec588fed6

+ 7 - 2
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
 
 import type EventEmitter from 'events';
 
-import { Origin } from '@growi/core';
+import { Origin, GlobalSocketEventName } from '@growi/core';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
@@ -11,6 +11,7 @@ import { useShareLinkId } from '~/stores/context';
 import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
+import { useDefaultSocket } from '~/stores/socket-io';
 import loggerFactory from '~/utils/logger';
 
 
@@ -28,6 +29,8 @@ export const useDrawioModalLauncherForView = (opts?: {
   onSaveError?: (error: any) => void,
 }): void => {
 
+  const { data: socket } = useDefaultSocket();
+
   const { data: shareLinkId } = useShareLinkId();
 
   const { data: currentPage } = useSWRxCurrentPage();
@@ -52,6 +55,8 @@ export const useDrawioModalLauncherForView = (opts?: {
         origin: Origin.View,
       });
 
+      socket?.emit(GlobalSocketEventName.YDocUpdate, { pageId: currentPage._id, newMarkdown });
+
       closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
@@ -64,7 +69,7 @@ export const useDrawioModalLauncherForView = (opts?: {
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
+  }, [closeConflictDiffModal, currentPage, opts, shareLinkId, socket]);
 
   // eslint-disable-next-line max-len
   const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {

+ 6 - 1
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
 
 import type EventEmitter from 'events';
 
-import { Origin } from '@growi/core';
+import { GlobalSocketEventName, Origin } from '@growi/core';
 
 import type MarkdownTable from '~/client/models/MarkdownTable';
 import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
@@ -11,6 +11,7 @@ import { useShareLinkId } from '~/stores/context';
 import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
+import { useDefaultSocket } from '~/stores/socket-io';
 import loggerFactory from '~/utils/logger';
 
 
@@ -27,6 +28,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
   onSaveSuccess?: () => void,
   onSaveError?: (error: any) => void,
 }): void => {
+  const { data: socket } = useDefaultSocket();
 
   const { data: shareLinkId } = useShareLinkId();
 
@@ -52,6 +54,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
         origin: Origin.View,
       });
 
+      socket?.emit(GlobalSocketEventName.YDocUpdate, { pageId: currentPage._id, newMarkdown });
+
       closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
@@ -64,6 +68,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
+  // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
 
   // eslint-disable-next-line max-len

+ 14 - 0
apps/app/src/server/service/socket-io.js

@@ -55,6 +55,7 @@ class SocketIoService {
     await this.setupLoginedUserRoomsJoinOnConnection();
     await this.setupDefaultSocketJoinRoomsEventHandler();
     await this.setupYjsConnection();
+    await this.setupYjsUpdateEvent();
   }
 
   getDefaultSocket() {
@@ -173,6 +174,19 @@ class SocketIoService {
     });
   }
 
+  setupYjsUpdateEvent() {
+    this.io.on('connection', (socket) => {
+      socket.on(GlobalSocketEventName.YDocUpdate, async({ pageId, newMarkdown }) => {
+        try {
+          await this.yjsConnectionManager.handleYDocUpdate(pageId, newMarkdown);
+        }
+        catch (error) {
+          logger.warn(error.message);
+        }
+      });
+    });
+  }
+
   async checkConnectionLimitsForAdmin(socket, next) {
     const namespaceName = socket.nsp.name;
 

+ 8 - 0
apps/app/src/server/service/yjs-connection-manager.ts

@@ -60,6 +60,14 @@ class YjsConnectionManager {
     persistedYdoc.destroy();
   }
 
+  public async handleYDocUpdate(pageId: string, newMarkdown: string): Promise<void> {
+    const currentYdoc = this.getCurrentYdoc(pageId);
+    const currentMarkdownLength = currentYdoc.getText('codemirror').length;
+    currentYdoc.getText('codemirror').delete(0, currentMarkdownLength);
+    currentYdoc.getText('codemirror').insert(0, newMarkdown);
+    Y.encodeStateAsUpdate(currentYdoc);
+  }
+
   private getCurrentYdoc(pageId: string): Y.Doc {
     const currentYdoc = this.ysocketio.documents.get(`yjs/${pageId}`);
     if (currentYdoc == null) {

+ 1 - 0
packages/core/src/interfaces/websocket.ts

@@ -2,5 +2,6 @@ export const GlobalSocketEventName = {
   // YDoc
   YDocSync: 'ydoc:sync',
   YDocSyncError: 'ydoc:sync:error',
+  YDocUpdate: 'ydoc:update',
 } as const;
 export type GlobalSocketEventName = typeof GlobalSocketEventName[keyof typeof GlobalSocketEventName];