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

Merge branch 'master' into dependabot/github_actions/peter-evans/dockerhub-description-3

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

+ 3 - 3
.github/workflows/ci-app.yml

@@ -27,7 +27,7 @@ jobs:
 
       - name: Cache/Restore node_modules
         id: cache-dependencies
-        uses: actions/cache@v2
+        uses: actions/cache@v3
         with:
           path: |
             **/node_modules
@@ -81,7 +81,7 @@ jobs:
 
       - name: Cache/Restore node_modules
         id: cache-dependencies
-        uses: actions/cache@v2
+        uses: actions/cache@v3
         with:
           path: |
             **/node_modules
@@ -141,7 +141,7 @@ jobs:
 
       - name: Cache/Restore node_modules
         id: cache-dependencies
-        uses: actions/cache@v2
+        uses: actions/cache@v3
         with:
           path: |
             **/node_modules

+ 2 - 2
.github/workflows/ci-slackbot-proxy.yml

@@ -28,7 +28,7 @@ jobs:
 
     - name: Cache/Restore node_modules
       id: cache-dependencies
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules
@@ -86,7 +86,7 @@ jobs:
 
     - name: Cache/Restore node_modules
       id: cache-dependencies
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules

+ 1 - 1
.github/workflows/release-rc.yml

@@ -44,7 +44,7 @@ jobs:
       uses: docker/setup-buildx-action@v1
 
     - name: Cache Docker layers
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: /tmp/.buildx-cache
         key: ${{ runner.os }}-buildx-app-${{ github.sha }}

+ 3 - 3
.github/workflows/reusable-app-prod.yml

@@ -33,7 +33,7 @@ jobs:
 
     - name: Cache/Restore node_modules
       id: cache-dependencies
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules
@@ -119,7 +119,7 @@ jobs:
 
     - name: Cache/Restore node_modules (not reused)
       id: cache-dependencies
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules
@@ -208,7 +208,7 @@ jobs:
         echo "::set-output name=value::`yarn cache dir --silent`"
 
     - name: Cache/Restore dependencies
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules

+ 1 - 1
.github/workflows/reusable-app-reg-suit.yml

@@ -57,7 +57,7 @@ jobs:
         cache-dependency-path: '**/yarn.lock'
 
     - name: Cache/Restore node_modules
-      uses: actions/cache@v2
+      uses: actions/cache@v3
       with:
         path: |
           **/node_modules

+ 1 - 1
packages/app/package.json

@@ -234,7 +234,7 @@
     "react-waypoint": "^10.1.0",
     "reactstrap": "^8.9.0",
     "replacestream": "^4.0.3",
-    "reveal.js": "^3.5.0",
+    "reveal.js": "^4.3.1",
     "sass": "^1.43.4",
     "sass-loader": "^10.1.1",
     "simple-load-script": "^1.0.2",

+ 2 - 11
packages/app/src/client/legacy/crowi-presentation.js

@@ -1,12 +1,4 @@
-const Reveal = require('reveal.js');
-
-require('reveal.js/lib/js/head.min');
-require('reveal.js/lib/js/html5shiv');
-
-if (!window) {
-  window = {};
-}
-window.Reveal = Reveal;
+import Reveal from 'reveal.js';
 
 Reveal.initialize({
   controls: true,
@@ -30,8 +22,7 @@ Reveal.initialize({
 });
 
 require.ensure([], () => {
-  require('reveal.js/lib/js/classList');
-  require('reveal.js/plugin/zoom-js/zoom');
+  require('reveal.js/plugin/zoom/zoom');
   require('reveal.js/plugin/notes/notes');
   require('../util/reveal/plugins/growi-renderer');
 

+ 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 { DevidedPagePath } from '@growi/core';
 
+import { useSWRxPageInfo } from '../../stores/page';
 
 import { ISelectable } from '~/client/interfaces/selectable-all';
 import { bookmark, unbookmark } from '~/client/services/page-operation';
@@ -20,7 +21,7 @@ import {
   usePageRenameModal, usePageDuplicateModal, usePageDeleteModal, usePutBackPageModal,
 } from '~/stores/modal';
 import {
-  IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing,
+  IPageInfoAll, IPageInfoForEntity, IPageInfoForListing, IPageWithMeta, isIPageInfoForListing, isIPageInfoForEntity,
 } from '~/interfaces/page';
 import { IPageSearchMeta, isIPageSearchMeta } from '~/interfaces/search';
 import {
@@ -78,6 +79,9 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
   const { open: openDeleteModal } = usePageDeleteModal();
   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 revisionShortBody = isIPageInfoForListing(pageMeta) ? pageMeta.revisionShortBody : null;
 
@@ -137,6 +141,22 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
   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 (
     <li
       key={pageData._id}
@@ -199,7 +219,7 @@ const PageListItemLSubstance: ForwardRefRenderFunction<ISelectable, Props> = (pr
 
               {/* page meta */}
               <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>
 
               {/* doropdown icon includes page control buttons */}

+ 2 - 2
packages/app/src/styles/_vendor-presentation.scss

@@ -4,8 +4,8 @@
 @import '~bootstrap/scss/mixins';
 @import '~bootstrap/scss/utilities';
 
-@import '~reveal.js/css/reveal.css';
-@import '~reveal.js/css/theme/black.css';
+@import '~reveal.js/dist/reveal.css';
+@import '~reveal.js/dist/theme/black.css';
 
 // hljs
 .reveal {

+ 154 - 3
packages/app/test/integration/service/v5.non-public-page.test.ts

@@ -391,6 +391,64 @@ describe('PageService page operations with non-public pages', () => {
     /**
      * 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
@@ -797,9 +855,102 @@ describe('PageService page operations with non-public pages', () => {
 
   });
   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', () => {
     const deleteCompletely = async(page, user, options = {}, isRecursively = false, preventEmitting = false) => {

+ 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>;
     }
 
+    // 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;
-    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;
@@ -70,6 +79,7 @@ export class PageListMeta extends React.Component {
 
 PageListMeta.propTypes = {
   page: PropTypes.object.isRequired,
+  likerCount: PropTypes.number,
   bookmarkCount: PropTypes.number,
   shouldSpaceOutIcon: PropTypes.bool,
 };

+ 4 - 3
yarn.lock

@@ -17878,9 +17878,10 @@ reusify@^1.0.0:
   resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
   integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
 
-reveal.js@^3.5.0:
-  version "3.6.0"
-  resolved "https://registry.yarnpkg.com/reveal.js/-/reveal.js-3.6.0.tgz#ce0e64f30cbebd6e5ce885c2f384085c5e5821e8"
+reveal.js@^4.3.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/reveal.js/-/reveal.js-4.3.1.tgz#24a63300cff92cf5cbdd40a46f5d3699614d49ca"
+  integrity sha512-1kyEnWeUkaCdBdX//XXq9dtBK95ppvIlSwlHelrP8/wrX6LcsYp4HT9WTFoFEOUBfVqkm8C2aHQ367o+UKfcxw==
 
 rewire@^5.0.0:
   version "5.0.0"