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

GC-707: websocket notification with socketClientId

Yuki Takei 7 лет назад
Родитель
Сommit
0a0966387c

+ 49 - 38
lib/models/page.js

@@ -961,12 +961,14 @@ module.exports = function(crowi) {
     return pageData.save();
   };
 
-  pageSchema.statics.create = function(path, body, user, options) {
+  pageSchema.statics.create = function(path, body, user, options = {}) {
     const Page = this
       , Revision = crowi.model('Revision')
       , format = options.format || 'markdown'
       , redirectTo = options.redirectTo || null
-      , grantUserGroupId = options.grantUserGroupId || null;
+      , grantUserGroupId = options.grantUserGroupId || null
+      , socketClientId = options.socketClientId || null
+      ;
 
     let grant = options.grant || GRANT_PUBLIC;
 
@@ -1010,18 +1012,22 @@ module.exports = function(crowi) {
         return Page.updateGrantUserGroup(savedPage, grant, grantUserGroupId, user);
       })
       .then(() => {
-        pageEvent.emit('create', savedPage, user);
+        if (socketClientId != null) {
+          pageEvent.emit('create', savedPage, user, socketClientId);
+        }
         return savedPage;
       });
   };
 
-  pageSchema.statics.updatePage = async function(pageData, body, user, options) {
+  pageSchema.statics.updatePage = async function(pageData, body, user, options = {}) {
     const Page = this
       , Revision = crowi.model('Revision')
       , grant = options.grant || null
       , grantUserGroupId = options.grantUserGroupId || null
       , isSyncRevisionToHackmd = options.isSyncRevisionToHackmd
+      , socketClientId = options.socketClientId || null
       ;
+
     // update existing page
     const newRevision = await Revision.prepareRevision(pageData, body, user);
 
@@ -1036,14 +1042,17 @@ module.exports = function(crowi) {
       savedPage = await Page.syncRevisionToHackmd(savedPage);
     }
 
-    pageEvent.emit('update', savedPage, user);
+    if (socketClientId != null) {
+      pageEvent.emit('update', savedPage, user, socketClientId);
+    }
     return savedPage;
   };
 
-  pageSchema.statics.deletePage = function(pageData, user, options) {
+  pageSchema.statics.deletePage = async function(pageData, user, options = {}) {
     const Page = this
       , newPath = Page.getDeletedPageName(pageData.path)
       , isTrashed = checkIfTrashed(pageData.path)
+      , socketClientId = options.socketClientId || null
       ;
 
     if (Page.isDeletableName(pageData.path)) {
@@ -1051,13 +1060,13 @@ module.exports = function(crowi) {
         return Page.completelyDeletePage(pageData, user, options);
       }
 
-      return Page.rename(pageData, newPath, user, {createRedirectPage: true})
-        .then((updatedPageData) => {
-          return Page.updatePageProperty(updatedPageData, {status: STATUS_DELETED, lastUpdateUser: user});
-        })
-        .then(() => {
-          return pageData;
-        });
+      let updatedPageData = await Page.rename(pageData, newPath, user, {createRedirectPage: true});
+      await Page.updatePageProperty(updatedPageData, {status: STATUS_DELETED, lastUpdateUser: user});
+
+      if (socketClientId != null) {
+        pageEvent.emit('delete', updatedPageData, user, socketClientId);
+      }
+      return updatedPageData;
     }
     else {
       return Promise.reject('Page is not deletable.');
@@ -1069,11 +1078,11 @@ module.exports = function(crowi) {
   };
 
   pageSchema.statics.deletePageRecursively = function(pageData, user, options) {
-    var Page = this
+    const Page = this
       , path = pageData.path
-      , options = options || {}
-      , isTrashed = checkIfTrashed(pageData.path);
+      , isTrashed = checkIfTrashed(pageData.path)
       ;
+    options = options || {};
 
     if (isTrashed) {
       return Page.completelyDeletePageRecursively(pageData, user, options);
@@ -1092,7 +1101,7 @@ module.exports = function(crowi) {
   };
 
   pageSchema.statics.revertDeletedPage = function(pageData, user, options) {
-    var Page = this
+    const Page = this
       , newPath = Page.getRevertDeletedPageName(pageData.path)
       ;
 
@@ -1106,7 +1115,7 @@ module.exports = function(crowi) {
           throw new Error('The new page of to revert is exists and the redirect path of the page is not the deleted page.');
         }
 
-        return Page.completelyDeletePage(originPageData);
+        return Page.completelyDeletePage(originPageData, options);
       }).then(function(done) {
         return Page.updatePageProperty(pageData, {status: STATUS_PUBLISHED, lastUpdateUser: user});
       }).then(function(done) {
@@ -1121,11 +1130,11 @@ module.exports = function(crowi) {
     });
   };
 
-  pageSchema.statics.revertDeletedPageRecursively = function(pageData, user, options) {
-    var Page = this
+  pageSchema.statics.revertDeletedPageRecursively = function(pageData, user, options = {}) {
+    const Page = this
       , path = pageData.path
-      , options = options || { includeDeletedPage: true}
       ;
+    options = Object.assign({ includeDeletedPage: true }, options);
 
     return new Promise(function(resolve, reject) {
       Page
@@ -1145,15 +1154,16 @@ module.exports = function(crowi) {
   /**
    * This is danger.
    */
-  pageSchema.statics.completelyDeletePage = function(pageData, user, options) {
+  pageSchema.statics.completelyDeletePage = function(pageData, user, options = {}) {
     // Delete Bookmarks, Attachments, Revisions, Pages and emit delete
-    var Bookmark = crowi.model('Bookmark')
+    const Bookmark = crowi.model('Bookmark')
       , Attachment = crowi.model('Attachment')
       , Comment = crowi.model('Comment')
       , Revision = crowi.model('Revision')
       , PageGroupRelation = crowi.model('PageGroupRelation')
       , Page = this
       , pageId = pageData._id
+      , socketClientId = options.socketClientId || null
       ;
 
     debug('Completely delete', pageData.path);
@@ -1174,18 +1184,20 @@ module.exports = function(crowi) {
       }).then(function(done) {
         return PageGroupRelation.removeAllByPage(pageData);
       }).then(function(done) {
-        pageEvent.emit('delete', pageData, user); // update as renamed page
+        if (socketClientId != null) {
+          pageEvent.emit('delete', pageData, user, socketClientId); // update as renamed page
+        }
         resolve(pageData);
       }).catch(reject);
     });
   };
 
-  pageSchema.statics.completelyDeletePageRecursively = function(pageData, user, options) {
+  pageSchema.statics.completelyDeletePageRecursively = function(pageData, user, options = {}) {
     // Delete Bookmarks, Attachments, Revisions, Pages and emit delete
-    var Page = this
+    const Page = this
       , path = pageData.path
-      , options = options || { includeDeletedPage: true }
       ;
+    options = Object.assign({ includeDeletedPage: true }, options);
 
     return new Promise(function(resolve, reject) {
       Page
@@ -1253,32 +1265,31 @@ module.exports = function(crowi) {
       });
   };
 
-  pageSchema.statics.rename = function(pageData, newPagePath, user, options) {
+  pageSchema.statics.rename = async function(pageData, newPagePath, user, options) {
     const Page = this
       , Revision = crowi.model('Revision')
       , path = pageData.path
       , createRedirectPage = options.createRedirectPage || 0
+      , socketClientId = options.socketClientId || null
       ;
 
     // sanitize path
     newPagePath = crowi.xss.process(newPagePath);
 
-    return Page.updatePageProperty(pageData, {updatedAt: Date.now(), path: newPagePath, lastUpdateUser: user})  // pageData の path を変更
-      .then((data) => {
+    await Page.updatePageProperty(pageData, {updatedAt: Date.now(), path: newPagePath, lastUpdateUser: user});
         // reivisions の path を変更
-        return Revision.updateRevisionListByPath(path, {path: newPagePath}, {});
-      })
-      .then(function(data) {
-        pageData.path = newPagePath;
+    await Revision.updateRevisionListByPath(path, {path: newPagePath}, {});
 
         if (createRedirectPage) {
           const body = 'redirect ' + newPagePath;
-          Page.create(path, body, user, {redirectTo: newPagePath});
+      await Page.create(path, body, user, {redirectTo: newPagePath});
         }
-        pageEvent.emit('update', pageData, user); // update as renamed page
 
-        return pageData;
-      });
+    let updatedPageData = await Page.findOne({path: newPagePath});
+    pageEvent.emit('delete', pageData, user, socketClientId);
+    pageEvent.emit('create', updatedPageData, user, socketClientId);
+
+    return updatedPageData;
   };
 
   pageSchema.statics.renameRecursively = function(pageData, newPagePathPrefix, user, options) {

+ 27 - 18
lib/routes/page.js

@@ -23,8 +23,14 @@ module.exports = function(crowi, app) {
   // register page events
 
   const pageEvent = crowi.event('page');
-  pageEvent.on('update', function(page, user) {
-    crowi.getIo().sockets.emit('page:update', {page, user});
+  pageEvent.on('create', function(page, user, socketClientId) {
+    crowi.getIo().sockets.emit('page:create', {page, user, socketClientId});
+  });
+  pageEvent.on('update', function(page, user, socketClientId) {
+    crowi.getIo().sockets.emit('page:update', {page, user, socketClientId});
+  });
+  pageEvent.on('delete', function(page, user, socketClientId) {
+    crowi.getIo().sockets.emit('page:delete', {page, user, socketClientId});
   });
 
   function getPathFromRequest(req) {
@@ -775,6 +781,7 @@ module.exports = function(crowi, app) {
     const grantUserGroupId = req.body.grantUserGroupId || null;
     const isSlackEnabled = !!req.body.isSlackEnabled;   // cast to boolean
     const slackChannels = req.body.slackChannels || null;
+    const socketClientId = req.body.socketClientId || undefined;
 
     if (body === null || pagePath === null) {
       return res.json(ApiResponse.error('Parameters body and path are required.'));
@@ -787,7 +794,8 @@ module.exports = function(crowi, app) {
           throw new Error('Page exists');
         }
 
-        return Page.create(pagePath, body, req.user, { grant: grant, grantUserGroupId: grantUserGroupId});
+        const options = {grant, grantUserGroupId, socketClientId};
+        return Page.create(pagePath, body, req.user, options);
       })
       .catch(function(err) {
         return res.json(ApiResponse.error(err));
@@ -835,6 +843,7 @@ module.exports = function(crowi, app) {
     const isSlackEnabled = !!req.body.isSlackEnabled;           // cast to boolean
     const slackChannels = req.body.slackChannels || null;
     const isSyncRevisionToHackmd = !!req.body.slackChannels;    // cast to boolean
+    const socketClientId = req.body.socketClientId || undefined;
 
     if (pageId === null || pageBody === null) {
       return res.json(ApiResponse.error('page_id and body are required.'));
@@ -847,7 +856,7 @@ module.exports = function(crowi, app) {
           throw new Error('Revision error.');
         }
 
-        const options = {};
+        const options = {isSyncRevisionToHackmd, socketClientId};
         if (grant != null) {
           options.grant = grant;
         }
@@ -855,10 +864,6 @@ module.exports = function(crowi, app) {
           options.grantUserGroupId = grantUserGroupId;
         }
 
-        if (isSyncRevisionToHackmd) {
-          options.isSyncRevisionToHackmd = true;
-        }
-
         // store previous revision
         previousRevision = pageData.revision;
 
@@ -1041,22 +1046,25 @@ module.exports = function(crowi, app) {
   api.remove = function(req, res) {
     const pageId = req.body.page_id;
     const previousRevision = req.body.revision_id || null;
+    const socketClientId = req.body.socketClientId || undefined;
 
     // get completely flag
     const isCompletely = (req.body.completely != null);
     // get recursively flag
     const isRecursively = (req.body.recursively != null);
 
+    const options = {socketClientId};
+
     Page.findPageByIdAndGrantedUser(pageId, req.user)
       .then(function(pageData) {
         debug('Delete page', pageData._id, pageData.path);
 
         if (isCompletely) {
           if (isRecursively) {
-            return Page.completelyDeletePageRecursively(pageData, req.user);
+            return Page.completelyDeletePageRecursively(pageData, req.user, options);
           }
           else {
-            return Page.completelyDeletePage(pageData, req.user);
+            return Page.completelyDeletePage(pageData, req.user, options);
           }
         }
 
@@ -1067,10 +1075,10 @@ module.exports = function(crowi, app) {
         }
 
         if (isRecursively) {
-          return Page.deletePageRecursively(pageData, req.user);
+          return Page.deletePageRecursively(pageData, req.user, options);
         }
         else {
-          return Page.deletePage(pageData, req.user);
+          return Page.deletePage(pageData, req.user, options);
         }
       })
       .then(function(data) {
@@ -1086,7 +1094,7 @@ module.exports = function(crowi, app) {
         return globalNotificationService.notifyPageDelete(page);
       })
       .catch(function(err) {
-        debug('Error occured while get setting', err, err.stack);
+        logger.error('Error occured while get setting', err, err.stack);
         return res.json(ApiResponse.error('Failed to delete page.'));
       });
   };
@@ -1098,8 +1106,9 @@ module.exports = function(crowi, app) {
    *
    * @apiParam {String} page_id Page Id.
    */
-  api.revertRemove = function(req, res) {
+  api.revertRemove = function(req, res, options) {
     const pageId = req.body.page_id;
+    const socketClientId = req.body.socketClientId || undefined;
 
     // get recursively flag
     const isRecursively = (req.body.recursively !== undefined);
@@ -1108,19 +1117,18 @@ module.exports = function(crowi, app) {
     .then(function(pageData) {
 
       if (isRecursively) {
-        return Page.revertDeletedPageRecursively(pageData, req.user);
+        return Page.revertDeletedPageRecursively(pageData, req.user, {socketClientId});
       }
       else {
-        return Page.revertDeletedPage(pageData, req.user);
+        return Page.revertDeletedPage(pageData, req.user, {socketClientId});
       }
     }).then(function(data) {
-      debug('Complete to revert deleted page', data.path);
       const result = {};
       result.page = data;
 
       return res.json(ApiResponse.success(result));
     }).catch(function(err) {
-      debug('Error occured while get setting', err, err.stack);
+      logger.error('Error occured while get setting', err, err.stack);
       return res.json(ApiResponse.error('Failed to revert deleted page.'));
     });
   };
@@ -1143,6 +1151,7 @@ module.exports = function(crowi, app) {
     const options = {
       createRedirectPage: req.body.create_redirect || 0,
       moveUnderTrees: req.body.move_trees || 0,
+      socketClientId: +req.body.socketClientId || undefined,
     };
     const isRecursiveMove = req.body.move_recursively || 0;
 

+ 2 - 0
lib/views/widget/not_found_content.html

@@ -45,4 +45,6 @@
     {% include '../_form.html' %}
 
   </div>
+
+  <div id="page-status-alert"></div>
 </div>

+ 48 - 8
resource/js/app.js

@@ -46,6 +46,8 @@ if (!window) {
 const userlang = $('body').data('userlang');
 const i18n = i18nFactory(userlang);
 
+const socket = io();
+
 // setup xss library
 const xss = new Xss();
 window.xss = xss;
@@ -89,6 +91,7 @@ crowi.setConfig(JSON.parse(document.getElementById('crowi-context-hydrate').text
 if (isLoggedin) {
   crowi.fetchUsers();
 }
+const socketClientId = crowi.getSocketClientId();
 
 const crowiRenderer = new GrowiRenderer(crowi, null, {
   mode: 'page',
@@ -219,6 +222,7 @@ const saveWithShortcut = function(markdown) {
   }
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
+  options.socketClientId = socketClientId;
 
   let promise = undefined;
   if (pageId == null) {
@@ -247,6 +251,7 @@ const saveWithSubmitButton = function() {
   }
   // get options
   const options = componentInstances.savePageControls.getCurrentOptionsToSave();
+  options.socketClientId = socketClientId;
 
   let promise = undefined;
   // get markdown
@@ -428,27 +433,62 @@ if (customHeaderEditorElem != null) {
 }
 
 // notification from websocket
-const socket = io();
+function updatePageStatusAlert(page, user) {
+  const pageStatusAlert = componentInstances.pageStatusAlert;
+  if (pageStatusAlert != null) {
+    pageStatusAlert.setLatestRevisionId(page._id.toString());
+    pageStatusAlert.setLastUpdateUsername(user.name);
+  }
+}
+socket.on('page:create', function(data) {
+  console.log(data);
+  // skip if triggered myself
+  if (data.socketClientId != null && data.socketClientId === socketClientId) {
+    return;
+  }
+
+  // update PageStatusAlert
+  if (data.page.path == pagePath) {
+    updatePageStatusAlert(data.page, data.user);
+  }
+});
 socket.on('page:update', function(data) {
-  // skip own trigger
-  if (data.user.username === crowi.me) {
+  console.log(data);
+  // skip if triggered myself
+  if (data.socketClientId != null && data.socketClientId === socketClientId) {
     return;
   }
+
   if (data.page.path == pagePath) {
     // update PageStatusAlert
-    const pageStatusAlert = componentInstances.pageStatusAlert;
-    if (pageStatusAlert != null) {
-      pageStatusAlert.setLatestRevisionId(data.page._id.toString());
-      pageStatusAlert.setLastUpdateUsername(data.user.name);
-    }
+    updatePageStatusAlert(data.page, data.user);
     // update PageEditorByHackmd
     const pageEditorByHackmd = componentInstances.pageEditorByHackmd;
     if (pageEditorByHackmd != null) {
+      // pageEditorByHackmd.setRevisionId(data.page.hasDraftOnHackmd);
+      // pageEditorByHackmd.setRevisionIdHackmdSynced(data.page.hasDraftOnHackmd);
       pageEditorByHackmd.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
     }
   }
 });
+socket.on('page:delete', function(data) {
+  console.log(data);
+  // skip if triggered myself
+  if (data.socketClientId != null && data.socketClientId === socketClientId) {
+    return;
+  }
+
+  // update PageStatusAlert
+  if (data.page.path == pagePath) {
+    updatePageStatusAlert(data.page, data.user);
+  }
+});
 socket.on('page:editingWithHackmd', function(data) {
+  // skip if triggered myself
+  if (data.socketClientId != null && data.socketClientId === socketClientId) {
+    return;
+  }
+
   if (data.page.path == pagePath) {
     // update PageStatusAlert
     const pageStatusAlert = componentInstances.pageStatusAlert;

+ 3 - 1
resource/js/legacy/crowi.js

@@ -379,10 +379,12 @@ $(function() {
       nameValueMap[obj.name] = obj.value;
     });
 
+    const data = $(this).serialize() + `&socketClientId=${crowi.getSocketClientId()}`;
+
     $.ajax({
       type: 'POST',
       url: '/_api/pages.rename',
-      data: $(this).serialize(),
+      data: data,
       dataType: 'json'
     })
     .done(function(res) {

+ 5 - 0
resource/js/util/Crowi.js

@@ -24,6 +24,7 @@ export default class Crowi {
     this.location = window.location || {};
     this.document = window.document || {};
     this.localStorage = window.localStorage || {};
+    this.socketClientId = Math.floor(Math.random() * 100000);
     this.pageEditor = undefined;
 
     this.fetchUsers = this.fetchUsers.bind(this);
@@ -68,6 +69,10 @@ export default class Crowi {
     this.pageEditor = pageEditor;
   }
 
+  getSocketClientId() {
+    return this.socketClientId;
+  }
+
   getEmojiStrategy() {
     return emojiStrategy;
   }