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

Merge branch 'master' into dependabot/npm_and_yarn/reveal.js-4.3.1

Luqman Grune 4 лет назад
Родитель
Сommit
b618241852

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
 {
   "npmClient": "yarn",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "useWorkspaces": true,
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "packages": [
   "packages": [
     "packages/*"
     "packages/*"
   ]
   ]

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "growi",
   "name": "growi",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "Team collaboration software using markdown",
   "description": "Team collaboration software using markdown",
   "tags": [
   "tags": [
     "wiki",
     "wiki",

+ 7 - 7
packages/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",
@@ -62,11 +62,11 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^5.0.0-RC.10",
-    "@growi/plugin-attachment-refs": "^5.0.0-RC.10",
-    "@growi/plugin-lsx": "^5.0.0-RC.10",
-    "@growi/plugin-pukiwiki-like-linker": "^5.0.0-RC.10",
-    "@growi/slack": "^5.0.0-RC.10",
+    "@growi/codemirror-textlint": "^5.0.0-RC.11",
+    "@growi/plugin-attachment-refs": "^5.0.0-RC.11",
+    "@growi/plugin-lsx": "^5.0.0-RC.11",
+    "@growi/plugin-pukiwiki-like-linker": "^5.0.0-RC.11",
+    "@growi/slack": "^5.0.0-RC.11",
     "@promster/express": "^7.0.2",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
     "@promster/server": "^7.0.4",
     "@slack/events-api": "^3.0.0",
     "@slack/events-api": "^3.0.0",
@@ -167,7 +167,7 @@
   },
   },
   "devDependencies": {
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.1.4",
     "@alienfast/i18next-loader": "^1.1.4",
-    "@growi/ui": "^5.0.0-RC.10",
+    "@growi/ui": "^5.0.0-RC.11",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
     "@types/compression": "^1.7.0",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",

+ 22 - 2
packages/app/src/components/PageList/PageListItemL.tsx

@@ -12,6 +12,7 @@ import urljoin from 'url-join';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { UserPicture, PageListMeta } from '@growi/ui';
 import { DevidedPagePath } from '@growi/core';
 import { DevidedPagePath } from '@growi/core';
 
 
+import { useSWRxPageInfo } from '../../stores/page';
 
 
 import { ISelectable } from '~/client/interfaces/selectable-all';
 import { ISelectable } from '~/client/interfaces/selectable-all';
 import { bookmark, unbookmark } from '~/client/services/page-operation';
 import { bookmark, unbookmark } from '~/client/services/page-operation';
@@ -20,7 +21,7 @@ import {
   usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal,
   usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal,
 } from '~/stores/modal';
 } from '~/stores/modal';
 import {
 import {
-  IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing,
+  IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing, isIPageInfoForEntity,
 } from '~/interfaces/page';
 } from '~/interfaces/page';
 import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import {
 import {
@@ -78,6 +79,9 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openDeleteModal } = usePageDeleteModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
   const { open: openPutBackPageModal } = usePutBackPageModal();
 
 
+  const shouldFetch = isSelected && (pageData != null || pageMeta != null);
+  const { data: pageInfo } = useSWRxPageInfo(shouldFetch ? pageData?._id : null);
+
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const elasticSearchResult = isIPageSearchMeta(pageMeta) ? pageMeta.elasticSearchResult : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
   const revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
 
 
@@ -137,6 +141,22 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
 
   const shouldDangerouslySetInnerHTMLForPaths = elasticSearchResult != null && elasticSearchResult.highlightedPath.length > 0;
   const shouldDangerouslySetInnerHTMLForPaths = elasticSearchResult != null && elasticSearchResult.highlightedPath.length > 0;
 
 
+  let likerCount;
+  if (isSelected && isIPageInfoForEntity(pageInfo)) {
+    likerCount = pageInfo.likerIds?.length;
+  }
+  else {
+    likerCount = pageData.liker.length;
+  }
+
+  let bookmarkCount;
+  if (isSelected && isIPageInfoForEntity(pageInfo)) {
+    bookmarkCount = pageInfo.bookmarkCount;
+  }
+  else {
+    bookmarkCount = pageMeta?.bookmarkCount;
+  }
+
   return (
   return (
     <li
     <li
       key={pageData._id}
       key={pageData._id}
@@ -199,7 +219,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
 
               {/* page meta */}
               {/* page meta */}
               <div className="d-none d-md-flex py-0 px-1 ml-2 text-nowrap">
               <div className="d-none d-md-flex py-0 px-1 ml-2 text-nowrap">
-                <PageListMeta page={pageData} bookmarkCount={pageMeta?.bookmarkCount} shouldSpaceOutIcon />
+                <PageListMeta page={pageData} likerCount={likerCount} bookmarkCount={bookmarkCount} shouldSpaceOutIcon />
               </div>
               </div>
 
 
               {/* doropdown icon includes page control buttons */}
               {/* doropdown icon includes page control buttons */}

+ 6 - 6
packages/app/src/server/routes/apiv3/index.js

@@ -16,7 +16,7 @@ module.exports = (crowi) => {
   // add custom functions to express response
   // add custom functions to express response
   require('./response')(express, crowi);
   require('./response')(express, crowi);
 
 
-  router.use('/healthcheck', require('./healthcheck')(crowi));
+  routerForAdmin.use('/healthcheck', require('./healthcheck')(crowi));
 
 
   // admin
   // admin
   routerForAdmin.use('/admin-home', require('./admin-home')(crowi));
   routerForAdmin.use('/admin-home', require('./admin-home')(crowi));
@@ -29,6 +29,10 @@ module.exports = (crowi) => {
   routerForAdmin.use('/export', require('./export')(crowi));
   routerForAdmin.use('/export', require('./export')(crowi));
   routerForAdmin.use('/import', require('./import')(crowi));
   routerForAdmin.use('/import', require('./import')(crowi));
   routerForAdmin.use('/search', require('./search')(crowi));
   routerForAdmin.use('/search', require('./search')(crowi));
+  routerForAdmin.use('/security-setting', require('./security-setting')(crowi));
+  routerForAdmin.use('/mongo', require('./mongo')(crowi));
+  routerForAdmin.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
+  routerForAdmin.use('/slack-integration-legacy-settings', require('./slack-integration-legacy-settings')(crowi));
 
 
 
 
   router.use('/in-app-notification', require('./in-app-notification')(crowi));
   router.use('/in-app-notification', require('./in-app-notification')(crowi));
@@ -37,11 +41,8 @@ module.exports = (crowi) => {
 
 
   router.use('/user-group-relations', require('./user-group-relation')(crowi));
   router.use('/user-group-relations', require('./user-group-relation')(crowi));
 
 
-  router.use('/mongo', require('./mongo')(crowi));
-
   router.use('/statistics', require('./statistics')(crowi));
   router.use('/statistics', require('./statistics')(crowi));
 
 
-  router.use('/security-setting', require('./security-setting')(crowi));
 
 
   router.use('/search', require('./search')(crowi));
   router.use('/search', require('./search')(crowi));
 
 
@@ -57,8 +58,7 @@ module.exports = (crowi) => {
   router.use('/attachment', require('./attachment')(crowi));
   router.use('/attachment', require('./attachment')(crowi));
 
 
   router.use('/slack-integration', require('./slack-integration')(crowi));
   router.use('/slack-integration', require('./slack-integration')(crowi));
-  router.use('/slack-integration-settings', require('./slack-integration-settings')(crowi));
-  router.use('/slack-integration-legacy-settings', require('./slack-integration-legacy-settings')(crowi));
+
   router.use('/staffs', require('./staffs')(crowi));
   router.use('/staffs', require('./staffs')(crowi));
 
 
   router.use('/forgot-password', require('./forgot-password')(crowi));
   router.use('/forgot-password', require('./forgot-password')(crowi));

+ 273 - 6
packages/app/test/integration/service/v5.non-public-page.test.ts

@@ -391,10 +391,118 @@ describe('PageService page operations with non-public pages', () => {
     /**
     /**
      * Delete
      * Delete
      */
      */
+    const pageIdDelete1 = new mongoose.Types.ObjectId();
+    const pageIdDelete2 = new mongoose.Types.ObjectId();
+    const pageIdDelete3 = new mongoose.Types.ObjectId();
+    const pageIdDelete4 = new mongoose.Types.ObjectId();
+    await Page.insertMany([
+      {
+        _id: pageIdDelete1,
+        path: '/npdel1_awl',
+        grant: Page.GRANT_RESTRICTED,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+      },
+      {
+        _id: pageIdDelete2,
+        path: '/npdel2_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 0,
+      },
+      {
+        _id: pageIdDelete3,
+        path: '/npdel3_top',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: rootPage._id,
+        descendantCount: 2,
+      },
+      {
+        _id: pageIdDelete4,
+        path: '/npdel3_top/npdel4_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdB,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: pageIdDelete3._id,
+        descendantCount: 1,
+      },
+      {
+        path: '/npdel3_top/npdel4_ug',
+        grant: Page.GRANT_RESTRICTED,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+      },
+      {
+        path: '/npdel3_top/npdel4_ug/npdel5_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdC,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: pageIdDelete4._id,
+        descendantCount: 0,
+      },
+    ]);
 
 
     /**
     /**
      * Delete completely
      * Delete completely
      */
      */
+    const pageIdDeleteComp1 = new mongoose.Types.ObjectId();
+    const pageIdDeleteComp2 = new mongoose.Types.ObjectId();
+    await Page.insertMany([
+      {
+        path: '/npdc1_awl',
+        grant: Page.GRANT_RESTRICTED,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+      },
+      {
+        path: '/npdc2_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: rootPage._id,
+      },
+      {
+        _id: pageIdDeleteComp1,
+        path: '/npdc3_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: rootPage._id,
+      },
+      {
+        _id: pageIdDeleteComp2,
+        path: '/npdc3_ug/npdc4_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdB,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: pageIdDeleteComp1,
+      },
+      {
+        path: '/npdc3_ug/npdc4_ug/npdc5_ug',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdC,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+        parent: pageIdDeleteComp2,
+      },
+      {
+        path: '/npdc3_ug/npdc4_ug',
+        grant: Page.GRANT_RESTRICTED,
+        status: Page.STATUS_PUBLISHED,
+        isEmpty: false,
+      },
+    ]);
 
 
     /**
     /**
      * Revert
      * Revert
@@ -747,14 +855,173 @@ describe('PageService page operations with non-public pages', () => {
 
 
   });
   });
   describe('Delete', () => {
   describe('Delete', () => {
-    // test('', async() => {
-    //   // write test code
-    // });
+
+    const deletePage = async(page, user, options, isRecursively) => {
+      const mockedDeleteRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'deleteRecursivelyMainOperation').mockReturnValue(null);
+      const mockedCreateAndSendNotifications = jest.spyOn(crowi.pageService, 'createAndSendNotifications').mockReturnValue(null);
+
+      const deletedPage = await crowi.pageService.deletePage(page, user, options, isRecursively);
+
+      const argsForDeleteRecursivelyMainOperation = mockedDeleteRecursivelyMainOperation.mock.calls[0];
+
+      mockedDeleteRecursivelyMainOperation.mockRestore();
+      mockedCreateAndSendNotifications.mockRestore();
+
+      if (isRecursively) {
+        await crowi.pageService.deleteRecursivelyMainOperation(...argsForDeleteRecursivelyMainOperation);
+      }
+
+      return deletedPage;
+    };
+    describe('Delete single page with grant RESTRICTED', () => {
+      test('should be able to delete', async() => {
+        const _pathT = '/npdel1_awl';
+        const _pageT = await Page.findOne({ path: _pathT, grant: Page.GRANT_RESTRICTED });
+        expect(_pageT).toBeTruthy();
+
+        const isRecursively = false;
+        await deletePage(_pageT, dummyUser1, {}, isRecursively);
+
+        const pageT = await Page.findOne({ path: `/trash${_pathT}` });
+        const pageN = await Page.findOne({ path: _pathT }); // should not exist
+        expect(pageT).toBeTruthy();
+        expect(pageN).toBeNull();
+        expect(pageT.grant).toBe(Page.GRANT_RESTRICTED);
+        expect(pageT.status).toBe(Page.STATUS_DELETED);
+      });
+    });
+    describe('Delete single page with grant USER_GROUP', () => {
+      test('should be able to delete', async() => {
+        const _path = '/npdel2_ug';
+        const _page1 = await Page.findOne({ path: _path, grantedGroup: groupIdA });
+        expect(_page1).toBeTruthy();
+
+        const isRecursively = false;
+        await deletePage(_page1, npDummyUser1, {}, isRecursively);
+
+        const pageN = await Page.findOne({ path: _path, grantedGroup: groupIdA });
+        const page1 = await Page.findOne({ path: `/trash${_path}`, grantedGroup: groupIdA });
+        expect(pageN).toBeNull();
+        expect(page1).toBeTruthy();
+        expect(page1.status).toBe(Page.STATUS_DELETED);
+        expect(page1.descendantCount).toBe(0);
+        expect(page1.parent).toBeNull();
+      });
+    });
+    describe('Delete multiple pages with grant USER_GROUP', () => {
+      test('should be able to delete all descendants except page with GRANT_RESTRICTED', async() => {
+        const _pathT = '/npdel3_top';
+        const _path1 = '/npdel3_top/npdel4_ug';
+        const _path2 = '/npdel3_top/npdel4_ug/npdel5_ug';
+        const _pageT = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A
+        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B
+        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C
+        const _pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
+        expect(_pageT).toBeTruthy();
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_pageR).toBeTruthy();
+
+        const isRecursively = true;
+        await deletePage(_pageT, npDummyUser1, {}, isRecursively);
+
+        const pageTNotExist = await Page.findOne({ path: _pathT, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A should not exist
+        const page1NotExist = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B should not exist
+        const page2NotExist = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C should not exist
+        const pageT = await Page.findOne({ path: `/trash${_pathT}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA }); // A
+        const page1 = await Page.findOne({ path: `/trash${_path1}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB }); // B
+        const page2 = await Page.findOne({ path: `/trash${_path2}`, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC }); // C
+        const pageR = await Page.findOne({ path: _path1, grant: Page.GRANT_RESTRICTED }); // Restricted
+        expect(page1NotExist).toBeNull();
+        expect(pageTNotExist).toBeNull();
+        expect(page2NotExist).toBeNull();
+        expect(pageT).toBeTruthy();
+        expect(page1).toBeTruthy();
+        expect(page2).toBeTruthy();
+        expect(pageR).toBeTruthy();
+        expect(pageT.status).toBe(Page.STATUS_DELETED);
+        expect(pageT.status).toBe(Page.STATUS_DELETED);
+        expect(page1.status).toBe(Page.STATUS_DELETED);
+        expect(page1.descendantCount).toBe(0);
+        expect(page2.descendantCount).toBe(0);
+        expect(page2.descendantCount).toBe(0);
+        expect(pageT.parent).toBeNull();
+        expect(page1.parent).toBeNull();
+        expect(page2.parent).toBeNull();
+      });
+    });
+
   });
   });
   describe('Delete completely', () => {
   describe('Delete completely', () => {
-    // test('', async() => {
-    //   // write test code
-    // });
+    const deleteCompletely = async(page, user, options = {}, isRecursively = false, preventEmitting = false) => {
+      const mockedDeleteCompletelyRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'deleteCompletelyRecursivelyMainOperation').mockReturnValue(null);
+      const mockedCreateAndSendNotifications = jest.spyOn(crowi.pageService, 'createAndSendNotifications').mockReturnValue(null);
+
+      await crowi.pageService.deleteCompletely(page, user, options, isRecursively, preventEmitting);
+
+      const argsForDeleteCompletelyRecursivelyMainOperation = mockedDeleteCompletelyRecursivelyMainOperation.mock.calls[0];
+
+      mockedDeleteCompletelyRecursivelyMainOperation.mockRestore();
+      mockedCreateAndSendNotifications.mockRestore();
+
+      if (isRecursively) {
+        await crowi.pageService.deleteCompletelyRecursivelyMainOperation(...argsForDeleteCompletelyRecursivelyMainOperation);
+      }
+
+      return;
+    };
+
+    describe('Delete single page with grant RESTRICTED', () => {
+      test('should be able to delete completely', async() => {
+        const _path = '/npdc1_awl';
+        const _page = await Page.findOne({ path: _path, grant: Page.GRANT_RESTRICTED });
+        expect(_page).toBeTruthy();
+
+        await deleteCompletely(_page, dummyUser1, {}, false);
+
+        const page = await Page.findOne({ path: _path, grant: Page.GRANT_RESTRICTED });
+        expect(page).toBeNull();
+      });
+    });
+    describe('Delete single page with grant USER_GROUP', () => {
+      test('should be able to delete completely', async() => {
+        const _path = '/npdc2_ug';
+        const _page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        expect(_page).toBeTruthy();
+
+        await deleteCompletely(_page, npDummyUser1, {}, false);
+
+        const page = await Page.findOne({ path: _path, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        expect(page).toBeNull();
+      });
+    });
+    describe('Delete multiple pages with grant USER_GROUP', () => {
+      test('should be able to delete all descendants completely except page with GRANT_RESTRICTED', async() => {
+        const _path1 = '/npdc3_ug';
+        const _path2 = '/npdc3_ug/npdc4_ug';
+        const _path3 = '/npdc3_ug/npdc4_ug/npdc5_ug';
+        const _page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        const _page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB });
+        const _page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC });
+        const _page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
+        expect(_page1).toBeTruthy();
+        expect(_page2).toBeTruthy();
+        expect(_page3).toBeTruthy();
+        expect(_page4).toBeTruthy();
+
+        await deleteCompletely(_page1, npDummyUser1, {}, true);
+
+        const page1 = await Page.findOne({ path: _path1, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdA });
+        const page2 = await Page.findOne({ path: _path2, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdB });
+        const page3 = await Page.findOne({ path: _path3, grant: Page.GRANT_USER_GROUP, grantedGroup: groupIdC });
+        const page4 = await Page.findOne({ path: _path2, grant: Page.GRANT_RESTRICTED });
+
+        expect(page1).toBeNull();
+        expect(page2).toBeNull();
+        expect(page3).toBeNull();
+        expect(page4).toBeTruthy();
+      });
+    });
   });
   });
   describe('revert', () => {
   describe('revert', () => {
     const revertDeletedPage = async(page, user, options = {}, isRecursively = false) => {
     const revertDeletedPage = async(page, user, options = {}, isRecursively = false) => {

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/codemirror-textlint",
   "name": "@growi/codemirror-textlint",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "license": "MIT",
   "license": "MIT",
   "main": "dist/index.js",
   "main": "dist/index.js",
   "scripts": {
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/core",
   "name": "@growi/core",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "GROWI Core Libraries",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-attachment-refs",
   "name": "@growi/plugin-attachment-refs",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-lsx/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-lsx",
   "name": "@growi/plugin-lsx",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "GROWI plugin to list pages",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/plugin-pukiwiki-like-linker/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-pukiwiki-like-linker",
   "name": "@growi/plugin-pukiwiki-like-linker",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "description": "GROWI plugin to add PukiwikiLikeLinker",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/slack",
   "name": "@growi/slack",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "license": "MIT",
   "license": "MIT",
   "main": "dist/index.js",
   "main": "dist/index.js",
   "typings": "dist/index.d.ts",
   "typings": "dist/index.d.ts",

+ 1 - 1
packages/slackbot-proxy/package.json

@@ -25,7 +25,7 @@
   },
   },
   "dependencies": {
   "dependencies": {
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
-    "@growi/slack": "^5.0.0-RC.10",
+    "@growi/slack": "^5.0.0-RC.11",
     "@slack/oauth": "^2.0.1",
     "@slack/oauth": "^2.0.1",
     "@slack/web-api": "^6.2.4",
     "@slack/web-api": "^6.2.4",
     "@tsed/common": "^6.43.0",
     "@tsed/common": "^6.43.0",

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/ui",
   "name": "@growi/ui",
-  "version": "5.0.0-RC.10",
+  "version": "5.0.0-RC.11",
   "description": "GROWI UI Libraries",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 12 - 2
packages/ui/src/components/PagePath/PageListMeta.jsx

@@ -28,9 +28,18 @@ export class PageListMeta extends React.Component {
       commentCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="icon-bubble" />{page.commentCount}</span>;
       commentCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="icon-bubble" />{page.commentCount}</span>;
     }
     }
 
 
+    // liker count section
+    let likedCount;
+    if (this.props.likerCount > 0) {
+      likedCount = this.props.likerCount;
+    }
+    else if (page.liker != null && page.liker.length > 0) {
+      likedCount = page.liker.length;
+    }
+
     let likerCount;
     let likerCount;
-    if (page.liker != null && page.liker.length > 0) {
-      likerCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="fa fa-heart-o" />{page.liker.length}</span>;
+    if (likedCount > 0) {
+      likerCount = <span className={`${shouldSpaceOutIcon ? 'mr-3' : ''}`}><i className="fa fa-heart-o" />{likedCount}</span>;
     }
     }
 
 
     let locked;
     let locked;
@@ -70,6 +79,7 @@ export class PageListMeta extends React.Component {
 
 
 PageListMeta.propTypes = {
 PageListMeta.propTypes = {
   page: PropTypes.object.isRequired,
   page: PropTypes.object.isRequired,
+  likerCount: PropTypes.number,
   bookmarkCount: PropTypes.number,
   bookmarkCount: PropTypes.number,
   shouldSpaceOutIcon: PropTypes.bool,
   shouldSpaceOutIcon: PropTypes.bool,
 };
 };