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

Merge branch 'master' into fix/scroll-to-specific-pagetree-item

Yuki Takei 4 лет назад
Родитель
Сommit
b0de5d35f0
24 измененных файлов с 433 добавлено и 70 удалено
  1. 1 2
      packages/app/src/components/Common/ClosableTextInput.tsx
  2. 1 1
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  3. 1 1
      packages/app/src/components/Navbar/GrowiSubNavigation.tsx
  4. 18 19
      packages/app/src/components/Sidebar/PageTree/Item.tsx
  5. 24 3
      packages/app/src/server/service/page.ts
  6. 4 0
      packages/app/src/styles/theme/_apply-colors-dark.scss
  7. 4 0
      packages/app/src/styles/theme/_apply-colors-light.scss
  8. 1 1
      packages/app/src/styles/theme/default.scss
  9. 0 3
      packages/app/src/styles/theme/mixins/_list-group.scss
  10. 9 8
      packages/app/test/integration/service/v5.migration.test.js
  11. 357 6
      packages/app/test/integration/service/v5.non-public-page.test.ts
  12. 1 2
      packages/app/tsconfig.build.server.json
  13. 1 2
      packages/codemirror-textlint/tsconfig.build.json
  14. 1 2
      packages/core/tsconfig.build.cjs.json
  15. 1 2
      packages/core/tsconfig.build.esm.json
  16. 1 2
      packages/plugin-attachment-refs/tsconfig.build.cjs.json
  17. 1 2
      packages/plugin-attachment-refs/tsconfig.build.esm.json
  18. 1 2
      packages/plugin-lsx/tsconfig.build.cjs.json
  19. 1 2
      packages/plugin-lsx/tsconfig.build.esm.json
  20. 1 2
      packages/plugin-pukiwiki-like-linker/tsconfig.build.cjs.json
  21. 1 2
      packages/plugin-pukiwiki-like-linker/tsconfig.build.esm.json
  22. 1 2
      packages/slack/tsconfig.build.json
  23. 1 2
      packages/slackbot-proxy/tsconfig.build.json
  24. 1 2
      packages/ui/tsconfig.build.json

+ 1 - 2
packages/app/src/components/Common/ClosableTextInput.tsx

@@ -16,7 +16,6 @@ export type AlertInfo = {
 }
 
 type ClosableTextInputProps = {
-  isShown: boolean
   value?: string
   placeholder?: string
   inputValidator?(text: string): AlertInfo | Promise<AlertInfo> | null
@@ -107,7 +106,7 @@ const ClosableTextInput: FC<ClosableTextInputProps> = memo((props: ClosableTextI
 
 
   return (
-    <div className={props.isShown ? 'd-block' : 'd-none'}>
+    <div className="d-block flex-fill">
       <input
         value={inputText || ''}
         ref={inputRef}

+ 1 - 1
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -256,7 +256,7 @@ const GrowiContextualSubNavigation = (props) => {
             />
           ) }
         </div>
-        <div className={className}>
+        <div className={`${className} ${isCompactMode ? '' : 'mt-2'}`}>
           {isAbleToShowPageEditorModeManager && (
             <PageEditorModeManager
               onPageEditorModeButtonClicked={onPageEditorModeButtonClicked}

+ 1 - 1
packages/app/src/components/Navbar/GrowiSubNavigation.tsx

@@ -85,7 +85,7 @@ export const GrowiSubNavigation = (props: Props): JSX.Element => {
       {/* Right side */}
       <div className="d-flex">
 
-        <div className="d-flex flex-column" style={{ gap: `${isCompactMode ? '5px' : '0'}` }}>
+        <div className="d-flex flex-column py-md-2" style={{ gap: `${isCompactMode ? '5px' : '0'}` }}>
           { Controls && <Controls></Controls> }
         </div>
 

+ 18 - 19
packages/app/src/components/Sidebar/PageTree/Item.tsx

@@ -101,7 +101,7 @@ type ItemCountProps = {
 const ItemCount: FC<ItemCountProps> = (props:ItemCountProps) => {
   return (
     <>
-      <span className="grw-pagetree-count px-0 badge badge-pill badge-light text-muted">
+      <span className="grw-pagetree-count px-0 badge badge-pill badge-light">
         {props.descendantCount}
       </span>
     </>
@@ -413,22 +413,22 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
             </button>
           )}
         </div>
-        { isRenameInputShown && (
-          <ClosableTextInput
-            isShown
-            value={nodePath.basename(page.path ?? '')}
-            placeholder={t('Input page name')}
-            onClickOutside={() => { setRenameInputShown(false) }}
-            onPressEnter={onPressEnterForRenameHandler}
-            inputValidator={inputValidator}
-          />
-        )}
-        { !isRenameInputShown && (
-          <a href={`/${page._id}`} className="grw-pagetree-title-anchor flex-grow-1">
-            <p className={`text-truncate m-auto ${page.isEmpty && 'text-muted'}`}>{nodePath.basename(page.path ?? '') || '/'}</p>
-          </a>
-        )}
-        {(descendantCount > 0) && (
+        { isRenameInputShown
+          ? (
+            <ClosableTextInput
+              value={nodePath.basename(page.path ?? '')}
+              placeholder={t('Input page name')}
+              onClickOutside={() => { setRenameInputShown(false) }}
+              onPressEnter={onPressEnterForRenameHandler}
+              inputValidator={inputValidator}
+            />
+          )
+          : (
+            <a href={`/${page._id}`} className="grw-pagetree-title-anchor flex-grow-1">
+              <p className={`text-truncate m-auto ${page.isEmpty && 'text-muted'}`}>{nodePath.basename(page.path ?? '') || '/'}</p>
+            </a>
+          )}
+        {descendantCount > 0 && !isRenameInputShown && (
           <div className="grw-pagetree-count-wrapper">
             <ItemCount descendantCount={descendantCount} />
           </div>
@@ -457,9 +457,8 @@ const Item: FC<ItemProps> = (props: ItemProps) => {
         </div>
       </li>
 
-      {isEnableActions && (
+      {isEnableActions && isNewPageInputShown && (
         <ClosableTextInput
-          isShown={isNewPageInputShown}
           placeholder={t('Input page name')}
           onClickOutside={() => { setNewPageInputShown(false) }}
           onPressEnter={onPressEnterForCreateHandler}

+ 24 - 3
packages/app/src/server/service/page.ts

@@ -2571,17 +2571,38 @@ class PageService {
       async write(pages, encoding, callback) {
         const parentPaths = Array.from(new Set<string>(pages.map(p => pathlib.dirname(p.path))));
 
-        // 1. Remove unnecessary empty pages
+        // 1. Remove unnecessary empty pages & reset parent for pages which had had those empty pages
         const pageIdsToNotDelete = pages.map(p => p._id);
         const emptyPagePathsToDelete = pages.map(p => p.path);
+
+        const builder1 = new PageQueryBuilder(Page.find({ isEmpty: true }, { _id: 1 }), true);
+        builder1.addConditionToListByPathsArray(emptyPagePathsToDelete);
+        builder1.addConditionToExcludeByPageIdsArray(pageIdsToNotDelete);
+
+        const emptyPagesToDelete = await builder1.query.lean().exec();
+        const resetParentOperations = emptyPagesToDelete.map((p) => {
+          return {
+            updateOne: {
+              filter: {
+                parent: p._id,
+              },
+              update: {
+                parent: null,
+              },
+            },
+          };
+        });
+
+        await Page.bulkWrite(resetParentOperations);
+
         await Page.removeEmptyPages(pageIdsToNotDelete, emptyPagePathsToDelete);
 
         // 2. Create lacking parents as empty pages
         await Page.createEmptyPagesByPaths(parentPaths, user, false);
 
         // 3. Find parents
-        const builder = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }), true);
-        const parents = await builder
+        const builder2 = new PageQueryBuilder(Page.find({}, { _id: 1, path: 1 }), true);
+        const parents = await builder2
           .addConditionToListByPathsArray(parentPaths)
           .query
           .lean()

+ 4 - 0
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -284,6 +284,10 @@ ul.pagination {
       lighten($bgcolor-list-hover, 5%),
       $gray-500
     );
+    .grw-pagetree-count {
+      color: $gray-400;
+      background: $gray-700;
+    }
   }
   .private-legacy-pages-link {
     &:hover {

+ 4 - 0
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -179,6 +179,10 @@ $border-color: $border-color-global;
       $bgcolor-list-active,
       $gray-400
     );
+    .grw-pagetree-count {
+      color: $gray-500;
+      background: $gray-200;
+    }
   }
   .private-legacy-pages-link {
     &:hover {

+ 1 - 1
packages/app/src/styles/theme/default.scss

@@ -72,7 +72,7 @@ html[light] {
   $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
   // Sidebar contents
   $color-sidebar-context: $color-global;
-  $bgcolor-sidebar-context: $gray-100;
+  $bgcolor-sidebar-context: lighten($primary, 77%);
   // Sidebar list group
   $bgcolor-sidebar-list-group: $gray-50; // optional
 

+ 0 - 3
packages/app/src/styles/theme/mixins/_list-group.scss

@@ -38,9 +38,6 @@
     &.grw-pagetree-is-target {
       background: $bgcolor-hover;
     }
-    .grw-pagetree-count {
-      background: $bgcolor;
-    }
     .grw-pagetree-button {
       &:not(:hover) {
         svg {

+ 9 - 8
packages/app/test/integration/service/v5.migration.test.js

@@ -282,11 +282,13 @@ describe('V5 page migration', () => {
     });
 
     test("should replace empty page with same path with new non-empty page and update all related children's parent", async() => {
-      const page1 = await Page.findOne({ path: '/normalize_10', isEmpty: true });
-      const page2 = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId8, isEmpty: true });
-      const page3 = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId9 }); // not v5
-      const page4 = await Page.findOne({ path: '/normalize_10/normalize_11_gA/normalize_11_gB' });
-      const page5 = await Page.findOne({ path: '/normalize_10/normalize_12_gC' });
+      const page1 = await Page.findOne({ path: '/normalize_10', isEmpty: true, parent: { $ne: null } });
+      const page2 = await Page.findOne({
+        path: '/normalize_10/normalize_11_gA', _id: pageId8, isEmpty: true, parent: { $ne: null },
+      });
+      const page3 = await Page.findOne({ path: '/normalize_10/normalize_11_gA', _id: pageId9, parent: null }); // not v5
+      const page4 = await Page.findOne({ path: '/normalize_10/normalize_11_gA/normalize_11_gB', parent: { $ne: null } });
+      const page5 = await Page.findOne({ path: '/normalize_10/normalize_12_gC', parent: { $ne: null } });
       expectAllToBeTruthy([page1, page2, page3, page4, page5]);
 
       await normalizeParentRecursivelyByPages([page3], testUser1);
@@ -302,11 +304,10 @@ describe('V5 page migration', () => {
 
       expect(page1AM.isEmpty).toBeTruthy();
       expect(page3AM.parent).toStrictEqual(page1AM._id);
-      // Todo: enable the code below after this is solved: https://redmine.weseek.co.jp/issues/90060
-      // expect(page4AM.parent).toStrictEqual(page3AF._id);
+      expect(page4AM.parent).toStrictEqual(page3AM._id);
       expect(page5AM.parent).toStrictEqual(page1AM._id);
 
-      expect(page3AM.isEmpty).toBeFalsy();
+      expect(page3AM.isEmpty).toBe(false);
     });
   });
 

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

@@ -9,11 +9,18 @@ describe('PageService page operations with non-public pages', () => {
 
   let dummyUser1;
   let dummyUser2;
-
+  let npDummyUser1;
+  let npDummyUser2;
+  let npDummyUser3;
+  let groupIdA;
+  let groupIdB;
+  let groupIdC;
   let crowi;
   let Page;
   let Revision;
   let User;
+  let UserGroup;
+  let UserGroupRelation;
   let Tag;
   let PageTagRelation;
   let Bookmark;
@@ -32,11 +39,34 @@ describe('PageService page operations with non-public pages', () => {
     });
   };
 
+  /**
+   * Revert
+   */
+  // page id
+  const pageIdRevert1 = new mongoose.Types.ObjectId();
+  const pageIdRevert2 = new mongoose.Types.ObjectId();
+  const pageIdRevert3 = new mongoose.Types.ObjectId();
+  const pageIdRevert4 = new mongoose.Types.ObjectId();
+  const pageIdRevert5 = new mongoose.Types.ObjectId();
+  const pageIdRevert6 = new mongoose.Types.ObjectId();
+  // revision id
+  const revisionIdRevert1 = new mongoose.Types.ObjectId();
+  const revisionIdRevert2 = new mongoose.Types.ObjectId();
+  const revisionIdRevert3 = new mongoose.Types.ObjectId();
+  const revisionIdRevert4 = new mongoose.Types.ObjectId();
+  const revisionIdRevert5 = new mongoose.Types.ObjectId();
+  const revisionIdRevert6 = new mongoose.Types.ObjectId();
+  // tag id
+  const tagIdRevert1 = new mongoose.Types.ObjectId();
+  const tagIdRevert2 = new mongoose.Types.ObjectId();
+
   beforeAll(async() => {
     crowi = await getInstance();
     await crowi.configManager.updateConfigsInTheSameNamespace('crowi', { 'app:isV5Compatible': true });
 
     User = mongoose.model('User');
+    UserGroup = mongoose.model('UserGroup');
+    UserGroupRelation = mongoose.model('UserGroupRelation');
     Page = mongoose.model('Page');
     Revision = mongoose.model('Revision');
     Tag = mongoose.model('Tag');
@@ -45,16 +75,104 @@ describe('PageService page operations with non-public pages', () => {
     Comment = mongoose.model('Comment');
     ShareLink = mongoose.model('ShareLink');
     PageRedirect = mongoose.model('PageRedirect');
+    UserGroup = mongoose.model('UserGroup');
+    UserGroupRelation = mongoose.model('UserGroupRelation');
 
     /*
      * Common
      */
 
-    dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
-    dummyUser2 = await User.findOne({ username: 'v5DummyUser2' });
+    const npUserId1 = new mongoose.Types.ObjectId();
+    const npUserId2 = new mongoose.Types.ObjectId();
+    const npUserId3 = new mongoose.Types.ObjectId();
+    await User.insertMany([
+      {
+        _id: npUserId1, name: 'npUser1', username: 'npUser1', email: 'npUser1@example.com',
+      },
+      {
+        _id: npUserId2, name: 'npUser2', username: 'npUser2', email: 'npUser2@example.com',
+      },
+      {
+        _id: npUserId3, name: 'npUser3', username: 'npUser3', email: 'npUser3@example.com',
+      },
+    ]);
+
+    const groupIdIsolate = new mongoose.Types.ObjectId();
+    groupIdA = new mongoose.Types.ObjectId();
+    groupIdB = new mongoose.Types.ObjectId();
+    groupIdC = new mongoose.Types.ObjectId();
+    await UserGroup.insertMany([
+      {
+        _id: groupIdIsolate,
+        name: 'np_groupIsolate',
+      },
+      {
+        _id: groupIdA,
+        name: 'np_groupA',
+      },
+      {
+        _id: groupIdB,
+        name: 'np_groupB',
+        parent: groupIdA,
+      },
+      {
+        _id: groupIdC,
+        name: 'np_groupC',
+        parent: groupIdB,
+      },
+    ]);
+
+    await UserGroupRelation.insertMany([
+      {
+        relatedGroup: groupIdIsolate,
+        relatedUser: npUserId1,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdIsolate,
+        relatedUser: npUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: npUserId1,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: npUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdA,
+        relatedUser: npUserId3,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdB,
+        relatedUser: npUserId2,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdB,
+        relatedUser: npUserId3,
+        createdAt: new Date(),
+      },
+      {
+        relatedGroup: groupIdC,
+        relatedUser: npUserId3,
+        createdAt: new Date(),
+      },
+    ]);
 
     xssSpy = jest.spyOn(crowi.xss, 'process').mockImplementation(path => path);
 
+    dummyUser1 = await User.findOne({ username: 'v5DummyUser1' });
+    dummyUser2 = await User.findOne({ username: 'v5DummyUser2' });
+    npDummyUser1 = await User.findOne({ username: 'npUser1' });
+    npDummyUser2 = await User.findOne({ username: 'npUser2' });
+    npDummyUser3 = await User.findOne({ username: 'npUser3' });
+
     rootPage = await Page.findOne({ path: '/' });
     if (rootPage == null) {
       const pages = await Page.insertMany([{ path: '/', grant: Page.GRANT_PUBLIC }]);
@@ -80,6 +198,115 @@ describe('PageService page operations with non-public pages', () => {
     /**
      * Revert
      */
+    await Page.insertMany([
+      {
+        _id: pageIdRevert1,
+        path: '/trash/np_revert1',
+        grant: Page.GRANT_RESTRICTED,
+        revision: revisionIdRevert1,
+        status: Page.STATUS_DELETED,
+      },
+      {
+        _id: pageIdRevert2,
+        path: '/trash/np_revert2',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        revision: revisionIdRevert2,
+        status: Page.STATUS_DELETED,
+      },
+      {
+        _id: pageIdRevert3,
+        path: '/trash/np_revert3',
+        revision: revisionIdRevert3,
+        status: Page.STATUS_DELETED,
+        parent: rootPage._id,
+      },
+      {
+        _id: pageIdRevert4,
+        path: '/trash/np_revert3/middle/np_revert4',
+        grant: Page.GRANT_RESTRICTED,
+        revision: revisionIdRevert4,
+        status: Page.STATUS_DELETED,
+      },
+      {
+        _id: pageIdRevert5,
+        path: '/trash/np_revert5',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdA,
+        revision: revisionIdRevert5,
+        status: Page.STATUS_DELETED,
+      },
+      {
+        _id: pageIdRevert6,
+        path: '/trash/np_revert5/middle/np_revert6',
+        grant: Page.GRANT_USER_GROUP,
+        grantedGroup: groupIdB,
+        revision: revisionIdRevert6,
+        status: Page.STATUS_DELETED,
+      },
+    ]);
+    await Revision.insertMany([
+      {
+        _id: revisionIdRevert1,
+        pageId: pageIdRevert1,
+        body: 'np_revert1',
+        format: 'markdown',
+        author: dummyUser1._id,
+      },
+      {
+        _id: revisionIdRevert2,
+        pageId: pageIdRevert2,
+        body: 'np_revert2',
+        format: 'markdown',
+        author: npDummyUser1,
+      },
+      {
+        _id: revisionIdRevert3,
+        pageId: pageIdRevert3,
+        body: 'np_revert3',
+        format: 'markdown',
+        author: npDummyUser1,
+      },
+      {
+        _id: revisionIdRevert4,
+        pageId: pageIdRevert4,
+        body: 'np_revert4',
+        format: 'markdown',
+        author: npDummyUser1,
+      },
+      {
+        _id: revisionIdRevert5,
+        pageId: pageIdRevert5,
+        body: 'np_revert5',
+        format: 'markdown',
+        author: npDummyUser1,
+      },
+      {
+        _id: revisionIdRevert6,
+        pageId: pageIdRevert6,
+        body: 'np_revert6',
+        format: 'markdown',
+        author: npDummyUser1,
+      },
+    ]);
+
+    await Tag.insertMany([
+      { _id: tagIdRevert1, name: 'np_revertTag1' },
+      { _id: tagIdRevert2, name: 'np_revertTag2' },
+    ]);
+
+    await PageTagRelation.insertMany([
+      {
+        relatedPage: pageIdRevert1,
+        relatedTag: tagIdRevert1,
+        isPageTrashed: true,
+      },
+      {
+        relatedPage: pageIdRevert2,
+        relatedTag: tagIdRevert2,
+        isPageTrashed: true,
+      },
+    ]);
   });
 
   describe('Rename', () => {
@@ -104,8 +331,132 @@ describe('PageService page operations with non-public pages', () => {
     // });
   });
   describe('revert', () => {
-    // test('', async() => {
-    //   // write test code
-    // });
+    const revertDeletedPage = async(page, user, options = {}, isRecursively = false) => {
+      // mock return value
+      const mockedRevertRecursivelyMainOperation = jest.spyOn(crowi.pageService, 'revertRecursivelyMainOperation').mockReturnValue(null);
+      const revertedPage = await crowi.pageService.revertDeletedPage(page, user, options, isRecursively);
+
+      const argsForRecursivelyMainOperation = mockedRevertRecursivelyMainOperation.mock.calls[0];
+
+      // restores the original implementation
+      mockedRevertRecursivelyMainOperation.mockRestore();
+      if (isRecursively) {
+        await crowi.pageService.revertRecursivelyMainOperation(...argsForRecursivelyMainOperation);
+      }
+
+      return revertedPage;
+
+    };
+    test('should revert single deleted page with GRANT_RESTRICTED', async() => {
+      const trashedPage = await Page.findOne({ path: '/trash/np_revert1', status: Page.STATUS_DELETED, grant: Page.GRANT_RESTRICTED });
+      const revision = await Revision.findOne({ pageId: trashedPage._id });
+      const tag = await Tag.findOne({ name: 'np_revertTag1' });
+      const deletedPageTagRelation = await PageTagRelation.findOne({ relatedPage: trashedPage._id, relatedTag: tag._id, isPageTrashed: true });
+      expectAllToBeTruthy([trashedPage, revision, tag, deletedPageTagRelation]);
+
+      await revertDeletedPage(trashedPage, dummyUser1, {}, false);
+      const revertedPage = await Page.findOne({ path: '/np_revert1' });
+      const deltedPageBeforeRevert = await Page.findOne({ path: '/trash/np_revert1' });
+      const pageTagRelation = await PageTagRelation.findOne({ relatedPage: revertedPage._id, relatedTag: tag._id });
+      expectAllToBeTruthy([revertedPage, pageTagRelation]);
+
+      expect(deltedPageBeforeRevert).toBeNull();
+
+      // page with GRANT_RESTRICTED does not have parent
+      expect(revertedPage.parent).toBeNull();
+      expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED);
+      expect(revertedPage.grant).toBe(Page.GRANT_RESTRICTED);
+      expect(pageTagRelation.isPageTrashed).toBe(false);
+    });
+    test('should revert single deleted page with GRANT_USER_GROUP', async() => {
+      const beforeRevertPath = '/trash/np_revert2';
+      const user1 = await User.findOne({ name: 'npUser1' });
+      const trashedPage = await Page.findOne({ path: beforeRevertPath, status: Page.STATUS_DELETED, grant: Page.GRANT_USER_GROUP });
+      const revision = await Revision.findOne({ pageId: trashedPage._id });
+      const tag = await Tag.findOne({ name: 'np_revertTag2' });
+      const deletedPageTagRelation = await PageTagRelation.findOne({ relatedPage: trashedPage._id, relatedTag: tag._id, isPageTrashed: true });
+      expectAllToBeTruthy([trashedPage, revision, tag, deletedPageTagRelation]);
+
+      await revertDeletedPage(trashedPage, user1, {}, false);
+      const revertedPage = await Page.findOne({ path: '/np_revert2' });
+      const trashedPageBR = await Page.findOne({ path: beforeRevertPath });
+      const pageTagRelation = await PageTagRelation.findOne({ relatedPage: revertedPage._id, relatedTag: tag._id });
+      expectAllToBeTruthy([revertedPage, pageTagRelation]);
+      expect(trashedPageBR).toBeNull();
+
+      expect(revertedPage.parent).toStrictEqual(rootPage._id);
+      expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED);
+      expect(revertedPage.grant).toBe(Page.GRANT_USER_GROUP);
+      expect(revertedPage.grantedGroup).toStrictEqual(groupIdA);
+      expect(pageTagRelation.isPageTrashed).toBe(false);
+    });
+    test(`revert multiple pages: only target page should be reverted.
+          Non-existant middle page and leaf page with GRANT_RESTRICTED shoud not be reverted`, async() => {
+      const beforeRevertPath1 = '/trash/np_revert3';
+      const beforeRevertPath2 = '/trash/np_revert3/middle/np_revert4';
+      const trashedPage1 = await Page.findOne({ path: beforeRevertPath1, status: Page.STATUS_DELETED, grant: Page.GRANT_PUBLIC });
+      const trashedPage2 = await Page.findOne({ path: beforeRevertPath2, status: Page.STATUS_DELETED, grant: Page.GRANT_RESTRICTED });
+      const revision1 = await Revision.findOne({ pageId: trashedPage1._id });
+      const revision2 = await Revision.findOne({ pageId: trashedPage2._id });
+      expectAllToBeTruthy([trashedPage1, trashedPage2, revision1, revision2]);
+
+      await revertDeletedPage(trashedPage1, npDummyUser2, {}, true);
+      const revertedPage = await Page.findOne({ path: '/np_revert3' });
+      const middlePage = await Page.findOne({ path: '/np_revert3/middle' });
+      const notRestrictedPage = await Page.findOne({ path: '/np_revert3/middle/np_revert4' });
+      // AR => After Revert
+      const trashedPage1AR = await Page.findOne({ path: beforeRevertPath1 });
+      const trashedPage2AR = await Page.findOne({ path: beforeRevertPath2 });
+      const revision1AR = await Revision.findOne({ pageId: revertedPage._id });
+      const revision2AR = await Revision.findOne({ pageId: trashedPage2AR._id });
+      expectAllToBeTruthy([revertedPage, trashedPage2AR, revision1AR, revision2AR]);
+      expect(trashedPage1AR).toBeNull();
+      expect(notRestrictedPage).toBeNull();
+      expect(middlePage).toBeNull();
+
+      expect(revertedPage.parent).toStrictEqual(rootPage._id);
+      expect(revertedPage.status).toBe(Page.STATUS_PUBLISHED);
+      expect(revertedPage.grant).toBe(Page.GRANT_PUBLIC);
+    });
+    test('revert multiple pages: target page, initially non-existant page and leaf page with GRANT_USER_GROUP shoud be reverted', async() => {
+      const user = await User.findOne({ _id: npDummyUser3 });
+      const beforeRevertPath1 = '/trash/np_revert5';
+      const beforeRevertPath2 = '/trash/np_revert5/middle/np_revert6';
+      const beforeRevertPath3 = '/trash/np_revert5/middle';
+      const trashedPage1 = await Page.findOne({ path: beforeRevertPath1, status: Page.STATUS_DELETED, grant: Page.GRANT_USER_GROUP });
+      const trashedPage2 = await Page.findOne({ path: beforeRevertPath2, status: Page.STATUS_DELETED, grant: Page.GRANT_USER_GROUP });
+      const nonExistantPage3 = await Page.findOne({ path: beforeRevertPath3 }); // not exist
+      const revision1 = await Revision.findOne({ pageId: trashedPage1._id });
+      const revision2 = await Revision.findOne({ pageId: trashedPage2._id });
+      expectAllToBeTruthy([trashedPage1, trashedPage2, revision1, revision2, user]);
+      expect(nonExistantPage3).toBeNull();
+
+      await revertDeletedPage(trashedPage1, user, {}, true);
+      const revertedPage1 = await Page.findOne({ path: '/np_revert5' });
+      const newlyCreatedPage = await Page.findOne({ path: '/np_revert5/middle' });
+      const revertedPage2 = await Page.findOne({ path: '/np_revert5/middle/np_revert6' });
+
+      // // AR => After Revert
+      const trashedPage1AR = await Page.findOne({ path: beforeRevertPath1 });
+      const trashedPage2AR = await Page.findOne({ path: beforeRevertPath2 });
+      expectAllToBeTruthy([revertedPage1, newlyCreatedPage, revertedPage2]);
+      expect(trashedPage1AR).toBeNull();
+      expect(trashedPage2AR).toBeNull();
+
+      expect(newlyCreatedPage.isEmpty).toBe(true);
+
+      expect(revertedPage1.parent).toStrictEqual(rootPage._id);
+      expect(revertedPage2.parent).toStrictEqual(newlyCreatedPage._id);
+      expect(newlyCreatedPage.parent).toStrictEqual(revertedPage1._id);
+
+      expect(revertedPage1.status).toBe(Page.STATUS_PUBLISHED);
+      expect(revertedPage2.status).toBe(Page.STATUS_PUBLISHED);
+      expect(newlyCreatedPage.status).toBe(Page.STATUS_PUBLISHED);
+
+      expect(revertedPage1.grant).toBe(Page.GRANT_USER_GROUP);
+      expect(revertedPage1.grant).toBe(Page.GRANT_USER_GROUP);
+      expect(newlyCreatedPage.grant).toBe(Page.GRANT_PUBLIC);
+
+    });
   });
 });

+ 1 - 2
packages/app/tsconfig.build.server.json

@@ -5,9 +5,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
     "baseUrl": ".",
     "paths": {
       "~/*": ["./src/*"],

+ 1 - 2
packages/codemirror-textlint/tsconfig.build.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/core/tsconfig.build.cjs.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/core/tsconfig.build.esm.json

@@ -8,9 +8,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-attachment-refs/tsconfig.build.cjs.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-attachment-refs/tsconfig.build.esm.json

@@ -8,9 +8,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-lsx/tsconfig.build.cjs.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-lsx/tsconfig.build.esm.json

@@ -8,9 +8,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-pukiwiki-like-linker/tsconfig.build.cjs.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/plugin-pukiwiki-like-linker/tsconfig.build.esm.json

@@ -8,9 +8,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/slack/tsconfig.build.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {

+ 1 - 2
packages/slackbot-proxy/tsconfig.build.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
     "baseUrl": "./src",
     "paths": {
       "~/*": ["./*"]

+ 1 - 2
packages/ui/tsconfig.build.json

@@ -6,9 +6,8 @@
     "declaration": true,
     "noResolve": false,
     "preserveConstEnums": true,
-    "sourceMap": true,
+    "sourceMap": false,
     "noEmit": false,
-    "inlineSources": true,
 
     "baseUrl": ".",
     "paths": {