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

Merge pull request #8594 from weseek/feat/142109-reflect-update-results-from-view-in-ydoc

feat: Reflect update results from view in ydoc
Shun Miyazawa 2 лет назад
Родитель
Сommit
4ccfb8db4b

+ 7 - 0
apps/app/src/server/crowi/index.js

@@ -37,8 +37,10 @@ import SearchService from '../service/search';
 import { SlackIntegrationService } from '../service/slack-integration';
 import UserGroupService from '../service/user-group';
 import { UserNotificationService } from '../service/user-notification';
+import { instantiateYjsConnectionManager } from '../service/yjs-connection-manager';
 import { getMongoUri, mongoOptions } from '../util/mongoose-utils';
 
+
 const logger = loggerFactory('growi:crowi');
 const httpErrorHandler = require('../middlewares/http-error-handler');
 
@@ -475,9 +477,14 @@ Crowi.prototype.start = async function() {
 
   // setup terminus
   this.setupTerminus(httpServer);
+
   // attach to socket.io
   this.socketIoService.attachServer(httpServer);
 
+  // Initialization YjsConnectionManager
+  instantiateYjsConnectionManager(this.socketIoService.io);
+  this.socketIoService.setupYjsConnection();
+
   // listen
   const serverListening = httpServer.listen(this.port, () => {
     logger.info(`[${this.node_env}] Express server is listening on port ${this.port}`);

+ 9 - 1
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -1,4 +1,4 @@
-import { allOrigin } from '@growi/core';
+import { Origin, allOrigin } from '@growi/core';
 import type {
   IPage, IRevisionHasId, IUserHasId,
 } from '@growi/core';
@@ -20,6 +20,7 @@ import {
 import type { PageDocument, PageModel } from '~/server/models/page';
 import { configManager } from '~/server/service/config-manager';
 import { preNotifyService } from '~/server/service/pre-notify';
+import { getYjsConnectionManager } from '~/server/service/yjs-connection-manager';
 import Xss from '~/services/xss';
 import XssOption from '~/services/xss/xssOption';
 import loggerFactory from '~/utils/logger';
@@ -79,6 +80,13 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
 
 
   async function postAction(req: UpdatePageRequest, res: ApiV3Response, updatedPage: PageDocument) {
+    // Reflect the updates in ydoc
+    const origin = req.body.origin;
+    if (origin === Origin.View || origin === undefined) {
+      const yjsConnectionManager = getYjsConnectionManager();
+      await yjsConnectionManager.handleYDocUpdate(req.body.pageId, req.body.body);
+    }
+
     // persist activity
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,

+ 3 - 6
apps/app/src/server/service/socket-io.js

@@ -5,7 +5,7 @@ import loggerFactory from '~/utils/logger';
 
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
 
-import YjsConnectionManager from './yjs-connection-manager';
+import { getYjsConnectionManager } from './yjs-connection-manager';
 
 const expressSession = require('express-session');
 const passport = require('passport');
@@ -37,9 +37,6 @@ class SocketIoService {
     });
     this.io.attach(server);
 
-    // create the YjsConnectionManager instance
-    this.yjsConnectionManager = new YjsConnectionManager(this.io);
-
     // create namespace for admin
     this.adminNamespace = this.io.of('/admin');
 
@@ -54,7 +51,6 @@ class SocketIoService {
 
     await this.setupLoginedUserRoomsJoinOnConnection();
     await this.setupDefaultSocketJoinRoomsEventHandler();
-    await this.setupYjsConnection();
   }
 
   getDefaultSocket() {
@@ -160,10 +156,11 @@ class SocketIoService {
   }
 
   setupYjsConnection() {
+    const yjsConnectionManager = getYjsConnectionManager();
     this.io.on('connection', (socket) => {
       socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
         try {
-          await this.yjsConnectionManager.handleYDocSync(pageId, initialValue);
+          await yjsConnectionManager.handleYDocSync(pageId, initialValue);
         }
         catch (error) {
           logger.warn(error.message);

+ 35 - 5
apps/app/src/server/service/yjs-connection-manager.ts

@@ -5,16 +5,18 @@ import * as Y from 'yjs';
 
 import { getMongoUri } from '../util/mongoose-utils';
 
-export const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
-export const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
+const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
+const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
 
 class YjsConnectionManager {
 
+  private static instance: YjsConnectionManager;
+
   private ysocketio: YSocketIO;
 
   private mdb: MongodbPersistence;
 
-  constructor(io: Server) {
+  private constructor(io: Server) {
     this.ysocketio = new YSocketIO(io);
     this.ysocketio.initialize();
 
@@ -22,8 +24,19 @@ class YjsConnectionManager {
       collectionName: MONGODB_PERSISTENCE_COLLECTION_NAME,
       flushSize: MONGODB_PERSISTENCE_FLUSH_SIZE,
     });
+  }
+
+  public static getInstance(io?: Server) {
+    if (this.instance != null) {
+      return this.instance;
+    }
+
+    if (io == null) {
+      throw new Error("'io' is required if initialize YjsConnectionManager");
+    }
 
-    this.getCurrentYdoc = this.getCurrentYdoc.bind(this);
+    this.instance = new YjsConnectionManager(io);
+    return this.instance;
   }
 
   public async handleYDocSync(pageId: string, initialValue: string): Promise<void> {
@@ -60,6 +73,16 @@ class YjsConnectionManager {
     persistedYdoc.destroy();
   }
 
+  public async handleYDocUpdate(pageId: string, newValue: string): Promise<void> {
+    // TODO: https://redmine.weseek.co.jp/issues/132775
+    // It's necessary to confirm that the user is not editing the target page in the Editor
+    const currentYdoc = this.getCurrentYdoc(pageId);
+    const currentMarkdownLength = currentYdoc.getText('codemirror').length;
+    currentYdoc.getText('codemirror').delete(0, currentMarkdownLength);
+    currentYdoc.getText('codemirror').insert(0, newValue);
+    Y.encodeStateAsUpdate(currentYdoc);
+  }
+
   private getCurrentYdoc(pageId: string): Y.Doc {
     const currentYdoc = this.ysocketio.documents.get(`yjs/${pageId}`);
     if (currentYdoc == null) {
@@ -70,4 +93,11 @@ class YjsConnectionManager {
 
 }
 
-export default YjsConnectionManager;
+export const instantiateYjsConnectionManager = (io: Server): YjsConnectionManager => {
+  return YjsConnectionManager.getInstance(io);
+};
+
+// export the singleton instance
+export const getYjsConnectionManager = (): YjsConnectionManager => {
+  return YjsConnectionManager.getInstance();
+};