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

Merge pull request #10600 from growilabs/support/156162-175866-app-client-services-biome

support: Configure biomef for app client services
Yuki Takei 3 месяцев назад
Родитель
Сommit
fd5921d352
39 измененных файлов с 1221 добавлено и 844 удалено
  1. 1 0
      apps/app/.eslintrc.js
  2. 0 5
      apps/app/src/client/services/AdminAppContainer.js
  3. 26 27
      apps/app/src/client/services/AdminCustomizeContainer.js
  4. 9 10
      apps/app/src/client/services/AdminExternalAccountsContainer.js
  5. 123 70
      apps/app/src/client/services/AdminGeneralSecurityContainer.js
  6. 27 18
      apps/app/src/client/services/AdminGitHubSecurityContainer.js
  7. 27 20
      apps/app/src/client/services/AdminGoogleSecurityContainer.js
  8. 4 8
      apps/app/src/client/services/AdminHomeContainer.js
  9. 0 2
      apps/app/src/client/services/AdminImportContainer.js
  10. 41 36
      apps/app/src/client/services/AdminLdapSecurityContainer.js
  11. 28 23
      apps/app/src/client/services/AdminLocalSecurityContainer.js
  12. 8 9
      apps/app/src/client/services/AdminMarkDownContainer.js
  13. 49 24
      apps/app/src/client/services/AdminNotificationContainer.js
  14. 67 52
      apps/app/src/client/services/AdminOidcSecurityContainer.js
  15. 51 38
      apps/app/src/client/services/AdminSamlSecurityContainer.js
  16. 5 5
      apps/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  17. 0 1
      apps/app/src/client/services/AdminSocketIoContainer.js
  18. 20 16
      apps/app/src/client/services/AdminUsersContainer.js
  19. 7 2
      apps/app/src/client/services/create-page/create-page.ts
  20. 95 81
      apps/app/src/client/services/create-page/use-create-page.tsx
  21. 21 18
      apps/app/src/client/services/create-page/use-create-template-page.ts
  22. 8 3
      apps/app/src/client/services/g2g-transfer.ts
  23. 0 1
      apps/app/src/client/services/maintenance-mode.ts
  24. 91 49
      apps/app/src/client/services/page-operation.ts
  25. 111 83
      apps/app/src/client/services/renderer/renderer.tsx
  26. 107 62
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  27. 117 64
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  28. 4 6
      apps/app/src/client/services/side-effects/hash-changed.ts
  29. 65 41
      apps/app/src/client/services/side-effects/page-updated.ts
  30. 1 1
      apps/app/src/client/services/side-effects/use-sticky.ts
  31. 3 2
      apps/app/src/client/services/update-page/conflict.tsx
  32. 7 2
      apps/app/src/client/services/update-page/update-page.ts
  33. 16 10
      apps/app/src/client/services/update-page/use-update-page.tsx
  34. 25 12
      apps/app/src/client/services/upload-attachments/upload-attachments.ts
  35. 4 4
      apps/app/src/client/services/use-print-mode.ts
  36. 23 22
      apps/app/src/client/services/use-start-editing.tsx
  37. 13 10
      apps/app/src/client/services/use-toastr-on-error.tsx
  38. 16 5
      apps/app/src/client/services/user-ui-settings.ts
  39. 1 2
      biome.json

+ 1 - 0
apps/app/.eslintrc.js

@@ -88,6 +88,7 @@ module.exports = {
     'src/server/service/page/**',
     'src/client/interfaces/**',
     'src/client/models/**',
+    'src/client/services/**',
     'src/client/util/**',
   ],
   settings: {

+ 0 - 5
apps/app/src/client/services/AdminAppContainer.js

@@ -8,7 +8,6 @@ import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
  * @extends {Container} unstated Container
  */
 export default class AdminAppContainer extends Container {
-
   constructor() {
     super();
 
@@ -42,7 +41,6 @@ export default class AdminAppContainer extends Container {
 
       isMaintenanceMode: false,
     };
-
   }
 
   /**
@@ -133,7 +131,6 @@ export default class AdminAppContainer extends Container {
     this.setState({ siteUrl });
   }
 
-
   /**
    * Change from address
    */
@@ -207,7 +204,6 @@ export default class AdminAppContainer extends Container {
     return appSettingParams;
   }
 
-
   /**
    * Update site url setting
    * @memberOf AdminAppContainer
@@ -294,5 +290,4 @@ export default class AdminAppContainer extends Container {
   async endMaintenanceMode() {
     await apiv3Post('/app-settings/maintenance-mode', { flag: false });
   }
-
 }

+ 26 - 27
apps/app/src/client/services/AdminCustomizeContainer.js

@@ -14,7 +14,6 @@ const logger = loggerFactory('growi:services:AdminCustomizeContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminCustomizeContainer extends Container {
-
   constructor() {
     super();
 
@@ -45,9 +44,9 @@ export default class AdminCustomizeContainer extends Container {
     this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
     this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
     this.switchPageListLimitationL = this.switchPageListLimitationL.bind(this);
-    this.switchPageListLimitationXL = this.switchPageListLimitationXL.bind(this);
+    this.switchPageListLimitationXL =
+      this.switchPageListLimitationXL.bind(this);
     this.switchShowPageSideAuthors = this.switchShowPageSideAuthors.bind(this);
-
   }
 
   /**
@@ -74,7 +73,8 @@ export default class AdminCustomizeContainer extends Container {
         pageLimitationXL: customizeParams.pageLimitationXL,
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
-        isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
+        isSearchScopeChildrenAsDefault:
+          customizeParams.isSearchScopeChildrenAsDefault,
         isEnabledMarp: customizeParams.isEnabledMarp,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeNoscript: customizeParams.customizeNoscript,
@@ -82,30 +82,29 @@ export default class AdminCustomizeContainer extends Container {
         currentCustomizeScript: customizeParams.customizeScript,
         showPageSideAuthors: customizeParams.showPageSideAuthors,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
     }
   }
 
-
   /**
    * Switch enabledTimeLine
    */
   switchEnableTimeline() {
-    this.setState({ isEnabledTimeline:  !this.state.isEnabledTimeline });
+    this.setState({ isEnabledTimeline: !this.state.isEnabledTimeline });
   }
 
   /**
    * Switch enabledAttachTitleHeader
    */
   switchEnabledAttachTitleHeader() {
-    this.setState({ isEnabledAttachTitleHeader:  !this.state.isEnabledAttachTitleHeader });
+    this.setState({
+      isEnabledAttachTitleHeader: !this.state.isEnabledAttachTitleHeader,
+    });
   }
 
-
   /**
    * S: Switch pageListLimitationS
    */
@@ -138,7 +137,9 @@ export default class AdminCustomizeContainer extends Container {
    * Switch enabledStaleNotification
    */
   switchEnableStaleNotification() {
-    this.setState({ isEnabledStaleNotification:  !this.state.isEnabledStaleNotification });
+    this.setState({
+      isEnabledStaleNotification: !this.state.isEnabledStaleNotification,
+    });
   }
 
   /**
@@ -152,7 +153,10 @@ export default class AdminCustomizeContainer extends Container {
    * Switch isSearchScopeChildrenAsDefault
    */
   switchIsSearchScopeChildrenAsDefault() {
-    this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
+    this.setState({
+      isSearchScopeChildrenAsDefault:
+        !this.state.isSearchScopeChildrenAsDefault,
+    });
   }
 
   /**
@@ -212,7 +216,8 @@ export default class AdminCustomizeContainer extends Container {
         pageLimitationXL: this.state.pageLimitationXL,
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isAllReplyShown: this.state.isAllReplyShown,
-        isSearchScopeChildrenAsDefault: this.state.isSearchScopeChildrenAsDefault,
+        isSearchScopeChildrenAsDefault:
+          this.state.isSearchScopeChildrenAsDefault,
         showPageSideAuthors: this.state.showPageSideAuthors,
       });
       const { customizedParams } = response.data;
@@ -225,11 +230,11 @@ export default class AdminCustomizeContainer extends Container {
         pageLimitationXL: customizedParams.pageLimitationXL,
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isAllReplyShown: customizedParams.isAllReplyShown,
-        isSearchScopeChildrenAsDefault: customizedParams.isSearchScopeChildrenAsDefault,
+        isSearchScopeChildrenAsDefault:
+          customizedParams.isSearchScopeChildrenAsDefault,
         showPageSideAuthors: customizedParams.showPageSideAuthors,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
@@ -248,8 +253,7 @@ export default class AdminCustomizeContainer extends Container {
       this.setState({
         isEnabledMarp: customizedParams.isEnabledMarp,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
@@ -268,8 +272,7 @@ export default class AdminCustomizeContainer extends Container {
       this.setState({
         customizeTitle: customizedParams.customizeTitle,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
@@ -284,8 +287,7 @@ export default class AdminCustomizeContainer extends Container {
       this.setState({
         currentCustomizeNoscript: customizedParams.customizeNoscript,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
@@ -304,8 +306,7 @@ export default class AdminCustomizeContainer extends Container {
       this.setState({
         currentCustomizeCss: customizedParams.customizeCss,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
@@ -325,11 +326,9 @@ export default class AdminCustomizeContainer extends Container {
       this.setState({
         currentCustomizeScript: customizedParams.customizeScript,
       });
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to update data');
     }
   }
-
 }

+ 9 - 10
apps/app/src/client/services/AdminExternalAccountsContainer.js

@@ -5,7 +5,6 @@ import loggerFactory from '~/utils/logger';
 
 import { apiv3Delete, apiv3Get } from '../util/apiv3-client';
 
-
 // eslint-disable-next-line no-unused-vars
 const logger = loggerFactory('growi:services:AdminexternalaccountsContainer');
 
@@ -14,7 +13,6 @@ const logger = loggerFactory('growi:services:AdminexternalaccountsContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminExternalAccountsContainer extends Container {
-
   constructor() {
     super();
 
@@ -28,7 +26,6 @@ export default class AdminExternalAccountsContainer extends Container {
       activePage: 1,
       pagingLimit: Infinity,
     };
-
   }
 
   /**
@@ -38,28 +35,29 @@ export default class AdminExternalAccountsContainer extends Container {
     return 'AdminExternalAccountsContainer';
   }
 
-
   /**
    * syncExternalAccounts of selectedPage
    * @memberOf AdminExternalAccountsContainer
    * @param {number} selectedPage
    */
   async retrieveExternalAccountsByPagingNum(selectedPage) {
-
     const params = { page: selectedPage };
     const { data } = await apiv3Get('/users/external-accounts', params);
 
     if (data.paginateResult == null) {
-      throw new Error('data must conclude \'paginateResult\' property.');
+      throw new Error("data must conclude 'paginateResult' property.");
     }
-    const { docs: externalAccounts, totalDocs: totalAccounts, limit: pagingLimit } = data.paginateResult;
+    const {
+      docs: externalAccounts,
+      totalDocs: totalAccounts,
+      limit: pagingLimit,
+    } = data.paginateResult;
     this.setState({
       externalAccounts,
       totalAccounts,
       pagingLimit,
       activePage: selectedPage,
     });
-
   }
 
   /**
@@ -69,10 +67,11 @@ export default class AdminExternalAccountsContainer extends Container {
    * @param {string} externalAccountId id of the External Account to be removed
    */
   async removeExternalAccountById(externalAccountId) {
-    const res = await apiv3Delete(`/users/external-accounts/${externalAccountId}/remove`);
+    const res = await apiv3Delete(
+      `/users/external-accounts/${externalAccountId}/remove`,
+    );
     const deletedUserData = res.data.externalAccount;
     await this.retrieveExternalAccountsByPagingNum(this.state.activePage);
     return deletedUserData.accountId;
   }
-
 }

+ 123 - 70
apps/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -2,8 +2,10 @@ import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import {
-  PageSingleDeleteConfigValue, PageSingleDeleteCompConfigValue,
-  PageRecursiveDeleteConfigValue, PageRecursiveDeleteCompConfigValue,
+  PageRecursiveDeleteCompConfigValue,
+  PageRecursiveDeleteConfigValue,
+  PageSingleDeleteCompConfigValue,
+  PageSingleDeleteConfigValue,
 } from '~/interfaces/page-delete-config';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';
 
@@ -15,7 +17,6 @@ import { toastError } from '../util/toastr';
  * @extends {Container} unstated Container
  */
 export default class AdminGeneralSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -29,9 +30,12 @@ export default class AdminGeneralSecurityContainer extends Container {
       wikiMode: '',
       currentRestrictGuestMode: '',
       currentPageDeletionAuthority: PageSingleDeleteConfigValue.AdminOnly,
-      currentPageRecursiveDeletionAuthority: PageRecursiveDeleteConfigValue.Inherit,
-      currentPageCompleteDeletionAuthority: PageSingleDeleteCompConfigValue.AdminOnly,
-      currentPageRecursiveCompleteDeletionAuthority: PageRecursiveDeleteCompConfigValue.Inherit,
+      currentPageRecursiveDeletionAuthority:
+        PageRecursiveDeleteConfigValue.Inherit,
+      currentPageCompleteDeletionAuthority:
+        PageSingleDeleteCompConfigValue.AdminOnly,
+      currentPageRecursiveCompleteDeletionAuthority:
+        PageRecursiveDeleteCompConfigValue.Inherit,
       currentGroupRestrictionDisplayMode: 'Hidden',
       currentOwnerRestrictionDisplayMode: 'Hidden',
       isAllGroupMembershipRequiredForPageCompleteDeletion: true,
@@ -57,33 +61,49 @@ export default class AdminGeneralSecurityContainer extends Container {
       shareLinksActivePage: 1,
     };
 
-    this.changeOwnerRestrictionDisplayMode = this.changeOwnerRestrictionDisplayMode.bind(this);
-    this.changeGroupRestrictionDisplayMode = this.changeGroupRestrictionDisplayMode.bind(this);
-    this.changePageDeletionAuthority = this.changePageDeletionAuthority.bind(this);
-    this.changePageCompleteDeletionAuthority = this.changePageCompleteDeletionAuthority.bind(this);
-    this.changePageRecursiveDeletionAuthority = this.changePageRecursiveDeletionAuthority.bind(this);
-    this.changePageRecursiveCompleteDeletionAuthority = this.changePageRecursiveCompleteDeletionAuthority.bind(this);
-    this.changePreviousPageRecursiveDeletionAuthority = this.changePreviousPageRecursiveDeletionAuthority.bind(this);
-    this.changePreviousPageRecursiveCompleteDeletionAuthority = this.changePreviousPageRecursiveCompleteDeletionAuthority.bind(this);
-
+    this.changeOwnerRestrictionDisplayMode =
+      this.changeOwnerRestrictionDisplayMode.bind(this);
+    this.changeGroupRestrictionDisplayMode =
+      this.changeGroupRestrictionDisplayMode.bind(this);
+    this.changePageDeletionAuthority =
+      this.changePageDeletionAuthority.bind(this);
+    this.changePageCompleteDeletionAuthority =
+      this.changePageCompleteDeletionAuthority.bind(this);
+    this.changePageRecursiveDeletionAuthority =
+      this.changePageRecursiveDeletionAuthority.bind(this);
+    this.changePageRecursiveCompleteDeletionAuthority =
+      this.changePageRecursiveCompleteDeletionAuthority.bind(this);
+    this.changePreviousPageRecursiveDeletionAuthority =
+      this.changePreviousPageRecursiveDeletionAuthority.bind(this);
+    this.changePreviousPageRecursiveCompleteDeletionAuthority =
+      this.changePreviousPageRecursiveCompleteDeletionAuthority.bind(this);
   }
 
   async retrieveSecurityData() {
     await this.retrieveSetupStratedies();
     const response = await apiv3Get('/security-setting/');
-    const { generalSetting, shareLinkSetting, generalAuth } = response.data.securityParams;
+    const { generalSetting, shareLinkSetting, generalAuth } =
+      response.data.securityParams;
     this.setState({
       currentRestrictGuestMode: generalSetting.restrictGuestMode,
       currentPageDeletionAuthority: generalSetting.pageDeletionAuthority,
-      currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
-      currentPageRecursiveDeletionAuthority: generalSetting.pageRecursiveDeletionAuthority,
-      currentPageRecursiveCompleteDeletionAuthority: generalSetting.pageRecursiveCompleteDeletionAuthority,
-      isAllGroupMembershipRequiredForPageCompleteDeletion: generalSetting.isAllGroupMembershipRequiredForPageCompleteDeletion,
+      currentPageCompleteDeletionAuthority:
+        generalSetting.pageCompleteDeletionAuthority,
+      currentPageRecursiveDeletionAuthority:
+        generalSetting.pageRecursiveDeletionAuthority,
+      currentPageRecursiveCompleteDeletionAuthority:
+        generalSetting.pageRecursiveCompleteDeletionAuthority,
+      isAllGroupMembershipRequiredForPageCompleteDeletion:
+        generalSetting.isAllGroupMembershipRequiredForPageCompleteDeletion,
       // Set display to 'Hidden' if hideRestrictedByOwner is anything but false.
-      currentOwnerRestrictionDisplayMode: generalSetting.hideRestrictedByOwner === false ? 'Displayed' : 'Hidden',
-      currentGroupRestrictionDisplayMode: generalSetting.hideRestrictedByGroup === false ? 'Displayed' : 'Hidden',
-      isUsersHomepageDeletionEnabled: generalSetting.isUsersHomepageDeletionEnabled,
-      isForceDeleteUserHomepageOnUserDeletion: generalSetting.isForceDeleteUserHomepageOnUserDeletion,
+      currentOwnerRestrictionDisplayMode:
+        generalSetting.hideRestrictedByOwner === false ? 'Displayed' : 'Hidden',
+      currentGroupRestrictionDisplayMode:
+        generalSetting.hideRestrictedByGroup === false ? 'Displayed' : 'Hidden',
+      isUsersHomepageDeletionEnabled:
+        generalSetting.isUsersHomepageDeletionEnabled,
+      isForceDeleteUserHomepageOnUserDeletion:
+        generalSetting.isForceDeleteUserHomepageOnUserDeletion,
       isRomUserAllowedToComment: generalSetting.isRomUserAllowedToComment,
       sessionMaxAge: generalSetting.sessionMaxAge,
       wikiMode: generalSetting.wikiMode,
@@ -97,7 +117,6 @@ export default class AdminGeneralSecurityContainer extends Container {
     });
   }
 
-
   /**
    * Workaround for the mangling in production build to break constructor.name
    */
@@ -110,7 +129,9 @@ export default class AdminGeneralSecurityContainer extends Container {
    * @return {bool} isWikiModeForced
    */
   get isWikiModeForced() {
-    return this.state.wikiMode === 'public' || this.state.wikiMode === 'private';
+    return (
+      this.state.wikiMode === 'public' || this.state.wikiMode === 'private'
+    );
   }
 
   /**
@@ -180,7 +201,10 @@ export default class AdminGeneralSecurityContainer extends Container {
    * Switch isAllGroupMembershipRequiredForPageCompleteDeletion
    */
   switchIsAllGroupMembershipRequiredForPageCompleteDeletion() {
-    this.setState({ isAllGroupMembershipRequiredForPageCompleteDeletion: !this.state.isAllGroupMembershipRequiredForPageCompleteDeletion });
+    this.setState({
+      isAllGroupMembershipRequiredForPageCompleteDeletion:
+        !this.state.isAllGroupMembershipRequiredForPageCompleteDeletion,
+    });
   }
 
   /**
@@ -190,7 +214,6 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ previousPageRecursiveDeletionAuthority: val });
   }
 
-
   /**
    * Change previousPageRecursiveCompleteDeletionAuthority
    */
@@ -216,14 +239,20 @@ export default class AdminGeneralSecurityContainer extends Container {
    * Switch isUsersHomepageDeletionEnabled
    */
   switchIsUsersHomepageDeletionEnabled() {
-    this.setState({ isUsersHomepageDeletionEnabled: !this.state.isUsersHomepageDeletionEnabled });
+    this.setState({
+      isUsersHomepageDeletionEnabled:
+        !this.state.isUsersHomepageDeletionEnabled,
+    });
   }
 
   /**
    * Switch isForceDeleteUserHomepageOnUserDeletion
    */
   switchIsForceDeleteUserHomepageOnUserDeletion() {
-    this.setState({ isForceDeleteUserHomepageOnUserDeletion: !this.state.isForceDeleteUserHomepageOnUserDeletion });
+    this.setState({
+      isForceDeleteUserHomepageOnUserDeletion:
+        !this.state.isForceDeleteUserHomepageOnUserDeletion,
+    });
   }
 
   /**
@@ -233,44 +262,62 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ isRomUserAllowedToComment: bool });
   }
 
-
   /**
    * Update restrictGuestMode
    * @memberOf AdminGeneralSecuritySContainer
    * @return {string} Appearance
    */
   async updateGeneralSecuritySetting(formData) {
-
-    let requestParams = formData != null ? {
-      sessionMaxAge: formData.sessionMaxAge,
-      restrictGuestMode: formData.restrictGuestMode,
-      pageDeletionAuthority: formData.pageDeletionAuthority,
-      pageCompleteDeletionAuthority: formData.pageCompleteDeletionAuthority,
-      pageRecursiveDeletionAuthority: formData.pageRecursiveDeletionAuthority,
-      pageRecursiveCompleteDeletionAuthority: formData.pageRecursiveCompleteDeletionAuthority,
-      isAllGroupMembershipRequiredForPageCompleteDeletion: formData.isAllGroupMembershipRequiredForPageCompleteDeletion,
-      hideRestrictedByGroup: formData.hideRestrictedByGroup,
-      hideRestrictedByOwner: formData.hideRestrictedByOwner,
-      isUsersHomepageDeletionEnabled: formData.isUsersHomepageDeletionEnabled,
-      isForceDeleteUserHomepageOnUserDeletion: formData.isForceDeleteUserHomepageOnUserDeletion,
-      isRomUserAllowedToComment: formData.isRomUserAllowedToComment,
-    } : {
-      sessionMaxAge: this.state.sessionMaxAge,
-      restrictGuestMode: this.state.currentRestrictGuestMode,
-      pageDeletionAuthority: this.state.currentPageDeletionAuthority,
-      pageCompleteDeletionAuthority: this.state.currentPageCompleteDeletionAuthority,
-      pageRecursiveDeletionAuthority: this.state.currentPageRecursiveDeletionAuthority,
-      pageRecursiveCompleteDeletionAuthority: this.state.currentPageRecursiveCompleteDeletionAuthority,
-      isAllGroupMembershipRequiredForPageCompleteDeletion: this.state.isAllGroupMembershipRequiredForPageCompleteDeletion,
-      hideRestrictedByGroup: this.state.currentGroupRestrictionDisplayMode === 'Hidden',
-      hideRestrictedByOwner: this.state.currentOwnerRestrictionDisplayMode === 'Hidden',
-      isUsersHomepageDeletionEnabled: this.state.isUsersHomepageDeletionEnabled,
-      isForceDeleteUserHomepageOnUserDeletion: this.state.isForceDeleteUserHomepageOnUserDeletion,
-      isRomUserAllowedToComment: this.state.isRomUserAllowedToComment,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            sessionMaxAge: formData.sessionMaxAge,
+            restrictGuestMode: formData.restrictGuestMode,
+            pageDeletionAuthority: formData.pageDeletionAuthority,
+            pageCompleteDeletionAuthority:
+              formData.pageCompleteDeletionAuthority,
+            pageRecursiveDeletionAuthority:
+              formData.pageRecursiveDeletionAuthority,
+            pageRecursiveCompleteDeletionAuthority:
+              formData.pageRecursiveCompleteDeletionAuthority,
+            isAllGroupMembershipRequiredForPageCompleteDeletion:
+              formData.isAllGroupMembershipRequiredForPageCompleteDeletion,
+            hideRestrictedByGroup: formData.hideRestrictedByGroup,
+            hideRestrictedByOwner: formData.hideRestrictedByOwner,
+            isUsersHomepageDeletionEnabled:
+              formData.isUsersHomepageDeletionEnabled,
+            isForceDeleteUserHomepageOnUserDeletion:
+              formData.isForceDeleteUserHomepageOnUserDeletion,
+            isRomUserAllowedToComment: formData.isRomUserAllowedToComment,
+          }
+        : {
+            sessionMaxAge: this.state.sessionMaxAge,
+            restrictGuestMode: this.state.currentRestrictGuestMode,
+            pageDeletionAuthority: this.state.currentPageDeletionAuthority,
+            pageCompleteDeletionAuthority:
+              this.state.currentPageCompleteDeletionAuthority,
+            pageRecursiveDeletionAuthority:
+              this.state.currentPageRecursiveDeletionAuthority,
+            pageRecursiveCompleteDeletionAuthority:
+              this.state.currentPageRecursiveCompleteDeletionAuthority,
+            isAllGroupMembershipRequiredForPageCompleteDeletion:
+              this.state.isAllGroupMembershipRequiredForPageCompleteDeletion,
+            hideRestrictedByGroup:
+              this.state.currentGroupRestrictionDisplayMode === 'Hidden',
+            hideRestrictedByOwner:
+              this.state.currentOwnerRestrictionDisplayMode === 'Hidden',
+            isUsersHomepageDeletionEnabled:
+              this.state.isUsersHomepageDeletionEnabled,
+            isForceDeleteUserHomepageOnUserDeletion:
+              this.state.isForceDeleteUserHomepageOnUserDeletion,
+            isRomUserAllowedToComment: this.state.isRomUserAllowedToComment,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
-    const response = await apiv3Put('/security-setting/general-setting', requestParams);
+    const response = await apiv3Put(
+      '/security-setting/general-setting',
+      requestParams,
+    );
     const { securitySettingParams } = response.data;
     return securitySettingParams;
   }
@@ -282,7 +329,10 @@ export default class AdminGeneralSecurityContainer extends Container {
     const requestParams = {
       disableLinkSharing: !this.state.disableLinkSharing,
     };
-    const response = await apiv3Put('/security-setting/share-link-setting', requestParams);
+    const response = await apiv3Put(
+      '/security-setting/share-link-setting',
+      requestParams,
+    );
     this.setDisableLinkSharing(!this.state.disableLinkSharing);
     return response;
   }
@@ -299,8 +349,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       });
       await this.retrieveSetupStratedies();
       this.setState({ [stateVariableName]: isEnabled });
-    }
-    catch (err) {
+    } catch (err) {
       toastError(err);
     }
   }
@@ -313,8 +362,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       const response = await apiv3Get('/security-setting/authentication');
       const { setupStrategies } = response.data;
       this.setState({ setupStrategies });
-    }
-    catch (err) {
+    } catch (err) {
       toastError(err);
     }
   }
@@ -323,18 +371,24 @@ export default class AdminGeneralSecurityContainer extends Container {
    * Retrieve All Sharelinks
    */
   async retrieveShareLinksByPagingNum(page) {
-
     const params = {
       page,
     };
 
-    const { data } = await apiv3Get('/security-setting/all-share-links', params);
+    const { data } = await apiv3Get(
+      '/security-setting/all-share-links',
+      params,
+    );
 
     if (data.paginateResult == null) {
-      throw new Error('data must conclude \'paginateResult\' property.');
+      throw new Error("data must conclude 'paginateResult' property.");
     }
 
-    const { docs: shareLinks, totalDocs: totalshareLinks, limit: shareLinksPagingLimit } = data.paginateResult;
+    const {
+      docs: shareLinks,
+      totalDocs: totalshareLinks,
+      limit: shareLinksPagingLimit,
+    } = data.paginateResult;
 
     this.setState({
       shareLinks,
@@ -385,5 +439,4 @@ export default class AdminGeneralSecurityContainer extends Container {
   async switchIsGitHubOAuthEnabled() {
     this.switchAuthentication('isGitHubEnabled', 'github');
   }
-
 }

+ 27 - 18
apps/app/src/client/services/AdminGitHubSecurityContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:security:AdminGitHubSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminGitHubSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -31,7 +30,6 @@ export default class AdminGitHubSecurityContainer extends Container {
       githubClientSecret: '',
       isSameUsernameTreatedAsIdenticalUser: false,
     };
-
   }
 
   /**
@@ -44,10 +42,10 @@ export default class AdminGitHubSecurityContainer extends Container {
       this.setState({
         githubClientId: githubOAuth.githubClientId,
         githubClientSecret: githubOAuth.githubClientSecret,
-        isSameUsernameTreatedAsIdenticalUser: githubOAuth.isSameUsernameTreatedAsIdenticalUser,
+        isSameUsernameTreatedAsIdenticalUser:
+          githubOAuth.isSameUsernameTreatedAsIdenticalUser,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
@@ -65,33 +63,44 @@ export default class AdminGitHubSecurityContainer extends Container {
    * Switch isSameUsernameTreatedAsIdenticalUser
    */
   switchIsSameUsernameTreatedAsIdenticalUser() {
-    this.setState({ isSameUsernameTreatedAsIdenticalUser: !this.state.isSameUsernameTreatedAsIdenticalUser });
+    this.setState({
+      isSameUsernameTreatedAsIdenticalUser:
+        !this.state.isSameUsernameTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Update githubSetting
    */
   async updateGitHubSetting(formData) {
-    let requestParams = formData != null ? {
-      githubClientId: formData.githubClientId,
-      githubClientSecret: formData.githubClientSecret,
-      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
-    } : {
-      githubClientId: this.state.githubClientId,
-      githubClientSecret: this.state.githubClientSecret,
-      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            githubClientId: formData.githubClientId,
+            githubClientSecret: formData.githubClientSecret,
+            isSameUsernameTreatedAsIdenticalUser:
+              formData.isSameUsernameTreatedAsIdenticalUser,
+          }
+        : {
+            githubClientId: this.state.githubClientId,
+            githubClientSecret: this.state.githubClientSecret,
+            isSameUsernameTreatedAsIdenticalUser:
+              this.state.isSameUsernameTreatedAsIdenticalUser,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
-    const response = await apiv3Put('/security-setting/github-oauth', requestParams);
+    const response = await apiv3Put(
+      '/security-setting/github-oauth',
+      requestParams,
+    );
     const { securitySettingParams } = response.data;
 
     this.setState({
       githubClientId: securitySettingParams.githubClientId,
       githubClientSecret: securitySettingParams.githubClientSecret,
-      isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
+      isSameUsernameTreatedAsIdenticalUser:
+        securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
     });
     return response;
   }
-
 }

+ 27 - 20
apps/app/src/client/services/AdminGoogleSecurityContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:security:AdminGoogleSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminGoogleSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -31,8 +30,6 @@ export default class AdminGoogleSecurityContainer extends Container {
       googleClientSecret: '',
       isSameEmailTreatedAsIdenticalUser: false,
     };
-
-
   }
 
   /**
@@ -45,10 +42,10 @@ export default class AdminGoogleSecurityContainer extends Container {
       this.setState({
         googleClientId: googleOAuth.googleClientId,
         googleClientSecret: googleOAuth.googleClientSecret,
-        isSameEmailTreatedAsIdenticalUser: googleOAuth.isSameEmailTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser:
+          googleOAuth.isSameEmailTreatedAsIdenticalUser,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
@@ -66,34 +63,44 @@ export default class AdminGoogleSecurityContainer extends Container {
    * Switch isSameEmailTreatedAsIdenticalUser
    */
   switchIsSameEmailTreatedAsIdenticalUser() {
-    this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
+    this.setState({
+      isSameEmailTreatedAsIdenticalUser:
+        !this.state.isSameEmailTreatedAsIdenticalUser,
+    });
   }
 
-
   /**
    * Update googleSetting
    */
   async updateGoogleSetting(formData) {
-    let requestParams = formData != null ? {
-      googleClientId: formData.googleClientId,
-      googleClientSecret: formData.googleClientSecret,
-      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
-    } : {
-      googleClientId: this.state.googleClientId,
-      googleClientSecret: this.state.googleClientSecret,
-      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            googleClientId: formData.googleClientId,
+            googleClientSecret: formData.googleClientSecret,
+            isSameEmailTreatedAsIdenticalUser:
+              formData.isSameEmailTreatedAsIdenticalUser,
+          }
+        : {
+            googleClientId: this.state.googleClientId,
+            googleClientSecret: this.state.googleClientSecret,
+            isSameEmailTreatedAsIdenticalUser:
+              this.state.isSameEmailTreatedAsIdenticalUser,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
-    const response = await apiv3Put('/security-setting/google-oauth', requestParams);
+    const response = await apiv3Put(
+      '/security-setting/google-oauth',
+      requestParams,
+    );
     const { securitySettingParams } = response.data;
 
     this.setState({
       googleClientId: securitySettingParams.googleClientId,
       googleClientSecret: securitySettingParams.googleClientSecret,
-      isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser:
+        securitySettingParams.isSameEmailTreatedAsIdenticalUser,
     });
     return response;
   }
-
 }

+ 4 - 8
apps/app/src/client/services/AdminHomeContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:services:AdminHomeContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminHomeContainer extends Container {
-
   constructor() {
     super();
 
@@ -37,7 +36,6 @@ export default class AdminHomeContainer extends Container {
       isV5Compatible: null,
       isMaintenanceMode: null,
     };
-
   }
 
   /**
@@ -59,7 +57,7 @@ export default class AdminHomeContainer extends Container {
       const response = await apiv3Get('/admin-home/');
       const { adminHomeParams } = response.data;
 
-      this.setState(prevState => ({
+      this.setState((prevState) => ({
         ...prevState,
         growiVersion: adminHomeParams.growiVersion,
         nodeVersion: adminHomeParams.nodeVersion,
@@ -69,8 +67,7 @@ export default class AdminHomeContainer extends Container {
         isV5Compatible: adminHomeParams.isV5Compatible,
         isMaintenanceMode: adminHomeParams.isMaintenanceMode,
       }));
-    }
-    catch (err) {
+    } catch (err) {
       logger.error(err);
       throw new Error('Failed to retrive AdminHome data');
     }
@@ -80,13 +77,13 @@ export default class AdminHomeContainer extends Container {
    * sets button text when copying system information
    */
   onCopyPrefilledHostInformation() {
-    this.setState(prevState => ({
+    this.setState((prevState) => ({
       ...prevState,
       copyState: this.copyStateValues.DONE,
     }));
 
     this.timer = setTimeout(() => {
-      this.setState(prevState => ({
+      this.setState((prevState) => ({
         ...prevState,
         copyState: this.copyStateValues.DEFAULT,
       }));
@@ -111,5 +108,4 @@ export default class AdminHomeContainer extends Container {
 
 *(Accessing https://{GROWI_HOST}/admin helps you to fill in above versions)*`;
   }
-
 }

+ 0 - 2
apps/app/src/client/services/AdminImportContainer.js

@@ -6,7 +6,6 @@ import { Container } from 'unstated';
  * @extends {Container} unstated Container
  */
 export default class AdminImportContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -27,5 +26,4 @@ export default class AdminImportContainer extends Container {
   static getClassName() {
     return 'AdminImportContainer';
   }
-
 }

+ 41 - 36
apps/app/src/client/services/AdminLdapSecurityContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:services:AdminLdapSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminLdapSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -38,7 +37,6 @@ export default class AdminLdapSecurityContainer extends Container {
       ldapGroupSearchFilter: '',
       ldapGroupDnProperty: '',
     };
-
   }
 
   /**
@@ -55,22 +53,21 @@ export default class AdminLdapSecurityContainer extends Container {
         ldapBindDNPassword: ldapAuth.ldapBindDNPassword,
         ldapSearchFilter: ldapAuth.ldapSearchFilter,
         ldapAttrMapUsername: ldapAuth.ldapAttrMapUsername,
-        isSameUsernameTreatedAsIdenticalUser: ldapAuth.isSameUsernameTreatedAsIdenticalUser,
+        isSameUsernameTreatedAsIdenticalUser:
+          ldapAuth.isSameUsernameTreatedAsIdenticalUser,
         ldapAttrMapMail: ldapAuth.ldapAttrMapMail,
         ldapAttrMapName: ldapAuth.ldapAttrMapName,
         ldapGroupSearchBase: ldapAuth.ldapGroupSearchBase,
         ldapGroupSearchFilter: ldapAuth.ldapGroupSearchFilter,
         ldapGroupDnProperty: ldapAuth.ldapGroupDnProperty,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
     }
   }
 
-
   /**
    * Workaround for the mangling in production build to break constructor.name
    */
@@ -90,40 +87,48 @@ export default class AdminLdapSecurityContainer extends Container {
    * Switch is same username treated as identical user
    */
   switchIsSameUsernameTreatedAsIdenticalUser() {
-    this.setState({ isSameUsernameTreatedAsIdenticalUser: !this.state.isSameUsernameTreatedAsIdenticalUser });
+    this.setState({
+      isSameUsernameTreatedAsIdenticalUser:
+        !this.state.isSameUsernameTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Update ldap option
    */
   async updateLdapSetting(formData) {
-    let requestParams = formData != null ? {
-      serverUrl: formData.serverUrl,
-      isUserBind: formData.isUserBind,
-      ldapBindDN: formData.ldapBindDN,
-      ldapBindDNPassword: formData.ldapBindDNPassword,
-      ldapSearchFilter: formData.ldapSearchFilter,
-      ldapAttrMapUsername: formData.ldapAttrMapUsername,
-      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
-      ldapAttrMapMail: formData.ldapAttrMapMail,
-      ldapAttrMapName: formData.ldapAttrMapName,
-      ldapGroupSearchBase: formData.ldapGroupSearchBase,
-      ldapGroupSearchFilter: formData.ldapGroupSearchFilter,
-      ldapGroupDnProperty: formData.ldapGroupDnProperty,
-    } : {
-      serverUrl: this.state.serverUrl,
-      isUserBind: this.state.isUserBind,
-      ldapBindDN: this.state.ldapBindDN,
-      ldapBindDNPassword: this.state.ldapBindDNPassword,
-      ldapSearchFilter: this.state.ldapSearchFilter,
-      ldapAttrMapUsername: this.state.ldapAttrMapUsername,
-      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
-      ldapAttrMapMail: this.state.ldapAttrMapMail,
-      ldapAttrMapName: this.state.ldapAttrMapName,
-      ldapGroupSearchBase: this.state.ldapGroupSearchBase,
-      ldapGroupSearchFilter: this.state.ldapGroupSearchFilter,
-      ldapGroupDnProperty: this.state.ldapGroupDnProperty,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            serverUrl: formData.serverUrl,
+            isUserBind: formData.isUserBind,
+            ldapBindDN: formData.ldapBindDN,
+            ldapBindDNPassword: formData.ldapBindDNPassword,
+            ldapSearchFilter: formData.ldapSearchFilter,
+            ldapAttrMapUsername: formData.ldapAttrMapUsername,
+            isSameUsernameTreatedAsIdenticalUser:
+              formData.isSameUsernameTreatedAsIdenticalUser,
+            ldapAttrMapMail: formData.ldapAttrMapMail,
+            ldapAttrMapName: formData.ldapAttrMapName,
+            ldapGroupSearchBase: formData.ldapGroupSearchBase,
+            ldapGroupSearchFilter: formData.ldapGroupSearchFilter,
+            ldapGroupDnProperty: formData.ldapGroupDnProperty,
+          }
+        : {
+            serverUrl: this.state.serverUrl,
+            isUserBind: this.state.isUserBind,
+            ldapBindDN: this.state.ldapBindDN,
+            ldapBindDNPassword: this.state.ldapBindDNPassword,
+            ldapSearchFilter: this.state.ldapSearchFilter,
+            ldapAttrMapUsername: this.state.ldapAttrMapUsername,
+            isSameUsernameTreatedAsIdenticalUser:
+              this.state.isSameUsernameTreatedAsIdenticalUser,
+            ldapAttrMapMail: this.state.ldapAttrMapMail,
+            ldapAttrMapName: this.state.ldapAttrMapName,
+            ldapGroupSearchBase: this.state.ldapGroupSearchBase,
+            ldapGroupSearchFilter: this.state.ldapGroupSearchFilter,
+            ldapGroupDnProperty: this.state.ldapGroupDnProperty,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
     const response = await apiv3Put('/security-setting/ldap', requestParams);
@@ -136,7 +141,8 @@ export default class AdminLdapSecurityContainer extends Container {
       ldapBindDNPassword: securitySettingParams.ldapBindDNPassword,
       ldapSearchFilter: securitySettingParams.ldapSearchFilter,
       ldapAttrMapUsername: securitySettingParams.ldapAttrMapUsername,
-      isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
+      isSameUsernameTreatedAsIdenticalUser:
+        securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
       ldapAttrMapMail: securitySettingParams.ldapAttrMapMail,
       ldapAttrMapName: securitySettingParams.ldapAttrMapName,
       ldapGroupSearchBase: securitySettingParams.ldapGroupSearchBase,
@@ -145,5 +151,4 @@ export default class AdminLdapSecurityContainer extends Container {
     });
     return response;
   }
-
 }

+ 28 - 23
apps/app/src/client/services/AdminLocalSecurityContainer.js

@@ -12,7 +12,6 @@ const logger = loggerFactory('growi:services:AdminLocalSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminLocalSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -33,7 +32,6 @@ export default class AdminLocalSecurityContainer extends Container {
       isPasswordResetEnabled: false,
       isEmailAuthenticationEnabled: false,
     };
-
   }
 
   async retrieveSecurityData() {
@@ -47,13 +45,11 @@ export default class AdminLocalSecurityContainer extends Container {
         isPasswordResetEnabled: localSetting.isPasswordResetEnabled,
         isEmailAuthenticationEnabled: localSetting.isEmailAuthenticationEnabled,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
     }
-
   }
 
   /**
@@ -63,7 +59,6 @@ export default class AdminLocalSecurityContainer extends Container {
     return 'AdminLocalSecurityContainer';
   }
 
-
   /**
    * Change registration mode
    */
@@ -75,32 +70,43 @@ export default class AdminLocalSecurityContainer extends Container {
    * Switch password reset enabled
    */
   switchIsPasswordResetEnabled() {
-    this.setState({ isPasswordResetEnabled: !this.state.isPasswordResetEnabled });
+    this.setState({
+      isPasswordResetEnabled: !this.state.isPasswordResetEnabled,
+    });
   }
 
   /**
    * Switch email authentication enabled
    */
   switchIsEmailAuthenticationEnabled() {
-    this.setState({ isEmailAuthenticationEnabled: !this.state.isEmailAuthenticationEnabled });
+    this.setState({
+      isEmailAuthenticationEnabled: !this.state.isEmailAuthenticationEnabled,
+    });
   }
 
   /**
    * update local security setting
    */
   async updateLocalSecuritySetting(formData) {
-    const requestParams = formData != null ? {
-      registrationMode: formData.registrationMode,
-      registrationWhitelist: formData.registrationWhitelist,
-      isPasswordResetEnabled: formData.isPasswordResetEnabled,
-      isEmailAuthenticationEnabled: formData.isEmailAuthenticationEnabled,
-    } : {
-      registrationMode: this.state.registrationMode,
-      registrationWhitelist: this.state.registrationWhitelist,
-      isPasswordResetEnabled: this.state.isPasswordResetEnabled,
-      isEmailAuthenticationEnabled: this.state.isEmailAuthenticationEnabled,
-    };
-    const response = await apiv3Put('/security-setting/local-setting', requestParams);
+    const requestParams =
+      formData != null
+        ? {
+            registrationMode: formData.registrationMode,
+            registrationWhitelist: formData.registrationWhitelist,
+            isPasswordResetEnabled: formData.isPasswordResetEnabled,
+            isEmailAuthenticationEnabled: formData.isEmailAuthenticationEnabled,
+          }
+        : {
+            registrationMode: this.state.registrationMode,
+            registrationWhitelist: this.state.registrationWhitelist,
+            isPasswordResetEnabled: this.state.isPasswordResetEnabled,
+            isEmailAuthenticationEnabled:
+              this.state.isEmailAuthenticationEnabled,
+          };
+    const response = await apiv3Put(
+      '/security-setting/local-setting',
+      requestParams,
+    );
 
     const { localSettingParams } = response.data;
 
@@ -108,11 +114,10 @@ export default class AdminLocalSecurityContainer extends Container {
       registrationMode: localSettingParams.registrationMode,
       registrationWhitelist: localSettingParams.registrationWhitelist,
       isPasswordResetEnabled: localSettingParams.isPasswordResetEnabled,
-      isEmailAuthenticationEnabled: localSettingParams.isEmailAuthenticationEnabled,
+      isEmailAuthenticationEnabled:
+        localSettingParams.isEmailAuthenticationEnabled,
     });
 
     return localSettingParams;
   }
-
-
 }

+ 8 - 9
apps/app/src/client/services/AdminMarkDownContainer.js

@@ -8,7 +8,6 @@ import { apiv3Get, apiv3Put } from '../util/apiv3-client';
  * @extends {Container} unstated Container
  */
 export default class AdminMarkDownContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -31,7 +30,8 @@ export default class AdminMarkDownContainer extends Container {
     };
 
     this.switchEnableXss = this.switchEnableXss.bind(this);
-    this.setAdminPreferredIndentSize = this.setAdminPreferredIndentSize.bind(this);
+    this.setAdminPreferredIndentSize =
+      this.setAdminPreferredIndentSize.bind(this);
   }
 
   /**
@@ -50,7 +50,8 @@ export default class AdminMarkDownContainer extends Container {
 
     this.setState({
       isEnabledLinebreaks: markdownParams.isEnabledLinebreaks,
-      isEnabledLinebreaksInComments: markdownParams.isEnabledLinebreaksInComments,
+      isEnabledLinebreaksInComments:
+        markdownParams.isEnabledLinebreaksInComments,
       adminPreferredIndentSize: markdownParams.adminPreferredIndentSize,
       isIndentSizeForced: markdownParams.isIndentSizeForced,
       isEnabledXss: markdownParams.isEnabledXss,
@@ -75,7 +76,6 @@ export default class AdminMarkDownContainer extends Container {
    * Update LineBreak Setting
    */
   async updateLineBreakSetting() {
-
     const response = await apiv3Put('/markdown-setting/lineBreak', {
       isEnabledLinebreaks: this.state.isEnabledLinebreaks,
       isEnabledLinebreaksInComments: this.state.isEnabledLinebreaksInComments,
@@ -88,7 +88,6 @@ export default class AdminMarkDownContainer extends Container {
    * Update
    */
   async updateIndentSetting() {
-
     const response = await apiv3Put('/markdown-setting/indent', {
       adminPreferredIndentSize: this.state.adminPreferredIndentSize,
       isIndentSizeForced: this.state.isIndentSizeForced,
@@ -104,13 +103,14 @@ export default class AdminMarkDownContainer extends Container {
     let { tagWhitelist = '' } = this.state;
     const { attrWhitelist = '{}' } = this.state;
 
-    tagWhitelist = Array.isArray(tagWhitelist) ? tagWhitelist : tagWhitelist.split(',');
+    tagWhitelist = Array.isArray(tagWhitelist)
+      ? tagWhitelist
+      : tagWhitelist.split(',');
 
     try {
       // Check if parsing is possible
       JSON.parse(attrWhitelist);
-    }
-    catch (err) {
+    } catch (err) {
       throw Error(`attrWhitelist parsing error occured: ${err.message}`);
     }
 
@@ -121,5 +121,4 @@ export default class AdminMarkDownContainer extends Container {
       attrWhitelist,
     });
   }
-
 }

+ 49 - 24
apps/app/src/client/services/AdminNotificationContainer.js

@@ -2,7 +2,10 @@ import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import {
-  apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
+  apiv3Delete,
+  apiv3Get,
+  apiv3Post,
+  apiv3Put,
 } from '../util/apiv3-client';
 
 /**
@@ -10,7 +13,6 @@ import {
  * @extends {Container} unstated Container
  */
 export default class AdminNotificationContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -32,7 +34,6 @@ export default class AdminNotificationContainer extends Container {
       isNotificationForGroupPageEnabled: false,
       globalNotifications: [],
     };
-
   }
 
   /**
@@ -55,8 +56,10 @@ export default class AdminNotificationContainer extends Container {
       currentBotType: notificationParams.currentBotType,
 
       userNotifications: notificationParams.userNotifications,
-      isNotificationForOwnerPageEnabled: notificationParams.isNotificationForOwnerPageEnabled,
-      isNotificationForGroupPageEnabled: notificationParams.isNotificationForGroupPageEnabled,
+      isNotificationForOwnerPageEnabled:
+        notificationParams.isNotificationForOwnerPageEnabled,
+      isNotificationForGroupPageEnabled:
+        notificationParams.isNotificationForGroupPageEnabled,
       globalNotifications: notificationParams.globalNotifications,
     });
   }
@@ -66,11 +69,14 @@ export default class AdminNotificationContainer extends Container {
    * @memberOf SlackAppConfiguration
    */
   async updateSlackAppConfiguration() {
-    const response = await apiv3Put('/notification-setting/slack-configuration', {
-      webhookUrl: this.state.webhookUrl,
-      isIncomingWebhookPrioritized: this.state.isIncomingWebhookPrioritized,
-      slackToken: this.state.slackToken,
-    });
+    const response = await apiv3Put(
+      '/notification-setting/slack-configuration',
+      {
+        webhookUrl: this.state.webhookUrl,
+        isIncomingWebhookPrioritized: this.state.isIncomingWebhookPrioritized,
+        slackToken: this.state.slackToken,
+      },
+    );
 
     return response;
   }
@@ -80,19 +86,26 @@ export default class AdminNotificationContainer extends Container {
    * @memberOf SlackAppConfiguration
    */
   async addNotificationPattern(pathPattern, channel) {
-    const response = await apiv3Post('/notification-setting/user-notification', {
-      pathPattern,
-      channel,
-    });
+    const response = await apiv3Post(
+      '/notification-setting/user-notification',
+      {
+        pathPattern,
+        channel,
+      },
+    );
 
-    this.setState({ userNotifications: response.data.responseParams.userNotifications });
+    this.setState({
+      userNotifications: response.data.responseParams.userNotifications,
+    });
   }
 
   /**
    * Delete user trigger notification pattern
    */
   async deleteUserTriggerNotificationPattern(notificatiionId) {
-    const response = await apiv3Delete(`/notification-setting/user-notification/${notificatiionId}`);
+    const response = await apiv3Delete(
+      `/notification-setting/user-notification/${notificatiionId}`,
+    );
     const deletedNotificaton = response.data;
     await this.retrieveNotificationData();
     return deletedNotificaton;
@@ -102,14 +115,20 @@ export default class AdminNotificationContainer extends Container {
    * Switch isNotificationForOwnerPageEnabled
    */
   switchIsNotificationForOwnerPageEnabled() {
-    this.setState({ isNotificationForOwnerPageEnabled: !this.state.isNotificationForOwnerPageEnabled });
+    this.setState({
+      isNotificationForOwnerPageEnabled:
+        !this.state.isNotificationForOwnerPageEnabled,
+    });
   }
 
   /**
    * Switch isNotificationForGroupPageEnabled
    */
   switchIsNotificationForGroupPageEnabled() {
-    this.setState({ isNotificationForGroupPageEnabled: !this.state.isNotificationForGroupPageEnabled });
+    this.setState({
+      isNotificationForGroupPageEnabled:
+        !this.state.isNotificationForGroupPageEnabled,
+    });
   }
 
   /**
@@ -117,10 +136,15 @@ export default class AdminNotificationContainer extends Container {
    * @memberOf SlackAppConfiguration
    */
   async updateGlobalNotificationForPages() {
-    const response = await apiv3Put('/notification-setting/notify-for-page-grant/', {
-      isNotificationForOwnerPageEnabled: this.state.isNotificationForOwnerPageEnabled,
-      isNotificationForGroupPageEnabled: this.state.isNotificationForGroupPageEnabled,
-    });
+    const response = await apiv3Put(
+      '/notification-setting/notify-for-page-grant/',
+      {
+        isNotificationForOwnerPageEnabled:
+          this.state.isNotificationForOwnerPageEnabled,
+        isNotificationForGroupPageEnabled:
+          this.state.isNotificationForGroupPageEnabled,
+      },
+    );
 
     return response;
   }
@@ -129,10 +153,11 @@ export default class AdminNotificationContainer extends Container {
    * Delete global notification pattern
    */
   async deleteGlobalNotificationPattern(notificatiionId) {
-    const response = await apiv3Delete(`/notification-setting/global-notification/${notificatiionId}`);
+    const response = await apiv3Delete(
+      `/notification-setting/global-notification/${notificatiionId}`,
+    );
     const deletedNotificaton = response.data;
     await this.retrieveNotificationData();
     return deletedNotificaton;
   }
-
 }

+ 67 - 52
apps/app/src/client/services/AdminOidcSecurityContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:services:AdminLdapSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminOidcSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -44,7 +43,6 @@ export default class AdminOidcSecurityContainer extends Container {
       isSameUsernameTreatedAsIdenticalUser: false,
       isSameEmailTreatedAsIdenticalUser: false,
     };
-
   }
 
   /**
@@ -71,11 +69,12 @@ export default class AdminOidcSecurityContainer extends Container {
         oidcAttrMapUserName: oidcAuth.oidcAttrMapUserName,
         oidcAttrMapName: oidcAuth.oidcAttrMapName,
         oidcAttrMapEmail: oidcAuth.oidcAttrMapEmail,
-        isSameUsernameTreatedAsIdenticalUser: oidcAuth.isSameUsernameTreatedAsIdenticalUser,
-        isSameEmailTreatedAsIdenticalUser: oidcAuth.isSameEmailTreatedAsIdenticalUser,
+        isSameUsernameTreatedAsIdenticalUser:
+          oidcAuth.isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser:
+          oidcAuth.isSameEmailTreatedAsIdenticalUser,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
@@ -93,59 +92,72 @@ export default class AdminOidcSecurityContainer extends Container {
    * Switch sameUsernameTreatedAsIdenticalUser
    */
   switchIsSameUsernameTreatedAsIdenticalUser() {
-    this.setState({ isSameUsernameTreatedAsIdenticalUser: !this.state.isSameUsernameTreatedAsIdenticalUser });
+    this.setState({
+      isSameUsernameTreatedAsIdenticalUser:
+        !this.state.isSameUsernameTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Switch sameEmailTreatedAsIdenticalUser
    */
   switchIsSameEmailTreatedAsIdenticalUser() {
-    this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
+    this.setState({
+      isSameEmailTreatedAsIdenticalUser:
+        !this.state.isSameEmailTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Update OpenID Connect
    */
   async updateOidcSetting(formData) {
-    let requestParams = formData != null ? {
-      oidcProviderName: formData.oidcProviderName,
-      oidcIssuerHost: formData.oidcIssuerHost,
-      oidcAuthorizationEndpoint: formData.oidcAuthorizationEndpoint,
-      oidcTokenEndpoint: formData.oidcTokenEndpoint,
-      oidcRevocationEndpoint: formData.oidcRevocationEndpoint,
-      oidcIntrospectionEndpoint: formData.oidcIntrospectionEndpoint,
-      oidcUserInfoEndpoint: formData.oidcUserInfoEndpoint,
-      oidcEndSessionEndpoint: formData.oidcEndSessionEndpoint,
-      oidcRegistrationEndpoint: formData.oidcRegistrationEndpoint,
-      oidcJWKSUri: formData.oidcJWKSUri,
-      oidcClientId: formData.oidcClientId,
-      oidcClientSecret: formData.oidcClientSecret,
-      oidcAttrMapId: formData.oidcAttrMapId,
-      oidcAttrMapUserName: formData.oidcAttrMapUserName,
-      oidcAttrMapName: formData.oidcAttrMapName,
-      oidcAttrMapEmail: formData.oidcAttrMapEmail,
-      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
-    } : {
-      oidcProviderName: this.state.oidcProviderName,
-      oidcIssuerHost: this.state.oidcIssuerHost,
-      oidcAuthorizationEndpoint: this.state.oidcAuthorizationEndpoint,
-      oidcTokenEndpoint: this.state.oidcTokenEndpoint,
-      oidcRevocationEndpoint: this.state.oidcRevocationEndpoint,
-      oidcIntrospectionEndpoint: this.state.oidcIntrospectionEndpoint,
-      oidcUserInfoEndpoint: this.state.oidcUserInfoEndpoint,
-      oidcEndSessionEndpoint: this.state.oidcEndSessionEndpoint,
-      oidcRegistrationEndpoint: this.state.oidcRegistrationEndpoint,
-      oidcJWKSUri: this.state.oidcJWKSUri,
-      oidcClientId: this.state.oidcClientId,
-      oidcClientSecret: this.state.oidcClientSecret,
-      oidcAttrMapId: this.state.oidcAttrMapId,
-      oidcAttrMapUserName: this.state.oidcAttrMapUserName,
-      oidcAttrMapName: this.state.oidcAttrMapName,
-      oidcAttrMapEmail: this.state.oidcAttrMapEmail,
-      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            oidcProviderName: formData.oidcProviderName,
+            oidcIssuerHost: formData.oidcIssuerHost,
+            oidcAuthorizationEndpoint: formData.oidcAuthorizationEndpoint,
+            oidcTokenEndpoint: formData.oidcTokenEndpoint,
+            oidcRevocationEndpoint: formData.oidcRevocationEndpoint,
+            oidcIntrospectionEndpoint: formData.oidcIntrospectionEndpoint,
+            oidcUserInfoEndpoint: formData.oidcUserInfoEndpoint,
+            oidcEndSessionEndpoint: formData.oidcEndSessionEndpoint,
+            oidcRegistrationEndpoint: formData.oidcRegistrationEndpoint,
+            oidcJWKSUri: formData.oidcJWKSUri,
+            oidcClientId: formData.oidcClientId,
+            oidcClientSecret: formData.oidcClientSecret,
+            oidcAttrMapId: formData.oidcAttrMapId,
+            oidcAttrMapUserName: formData.oidcAttrMapUserName,
+            oidcAttrMapName: formData.oidcAttrMapName,
+            oidcAttrMapEmail: formData.oidcAttrMapEmail,
+            isSameUsernameTreatedAsIdenticalUser:
+              formData.isSameUsernameTreatedAsIdenticalUser,
+            isSameEmailTreatedAsIdenticalUser:
+              formData.isSameEmailTreatedAsIdenticalUser,
+          }
+        : {
+            oidcProviderName: this.state.oidcProviderName,
+            oidcIssuerHost: this.state.oidcIssuerHost,
+            oidcAuthorizationEndpoint: this.state.oidcAuthorizationEndpoint,
+            oidcTokenEndpoint: this.state.oidcTokenEndpoint,
+            oidcRevocationEndpoint: this.state.oidcRevocationEndpoint,
+            oidcIntrospectionEndpoint: this.state.oidcIntrospectionEndpoint,
+            oidcUserInfoEndpoint: this.state.oidcUserInfoEndpoint,
+            oidcEndSessionEndpoint: this.state.oidcEndSessionEndpoint,
+            oidcRegistrationEndpoint: this.state.oidcRegistrationEndpoint,
+            oidcJWKSUri: this.state.oidcJWKSUri,
+            oidcClientId: this.state.oidcClientId,
+            oidcClientSecret: this.state.oidcClientSecret,
+            oidcAttrMapId: this.state.oidcAttrMapId,
+            oidcAttrMapUserName: this.state.oidcAttrMapUserName,
+            oidcAttrMapName: this.state.oidcAttrMapName,
+            oidcAttrMapEmail: this.state.oidcAttrMapEmail,
+            isSameUsernameTreatedAsIdenticalUser:
+              this.state.isSameUsernameTreatedAsIdenticalUser,
+            isSameEmailTreatedAsIdenticalUser:
+              this.state.isSameEmailTreatedAsIdenticalUser,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
     const response = await apiv3Put('/security-setting/oidc', requestParams);
@@ -154,10 +166,12 @@ export default class AdminOidcSecurityContainer extends Container {
     this.setState({
       oidcProviderName: securitySettingParams.oidcProviderName,
       oidcIssuerHost: securitySettingParams.oidcIssuerHost,
-      oidcAuthorizationEndpoint: securitySettingParams.oidcAuthorizationEndpoint,
+      oidcAuthorizationEndpoint:
+        securitySettingParams.oidcAuthorizationEndpoint,
       oidcTokenEndpoint: securitySettingParams.oidcTokenEndpoint,
       oidcRevocationEndpoint: securitySettingParams.oidcRevocationEndpoint,
-      oidcIntrospectionEndpoint: securitySettingParams.oidcIntrospectionEndpoint,
+      oidcIntrospectionEndpoint:
+        securitySettingParams.oidcIntrospectionEndpoint,
       oidcUserInfoEndpoint: securitySettingParams.oidcUserInfoEndpoint,
       oidcEndSessionEndpoint: securitySettingParams.oidcEndSessionEndpoint,
       oidcRegistrationEndpoint: securitySettingParams.oidcRegistrationEndpoint,
@@ -168,10 +182,11 @@ export default class AdminOidcSecurityContainer extends Container {
       oidcAttrMapUserName: securitySettingParams.oidcAttrMapUserName,
       oidcAttrMapName: securitySettingParams.oidcAttrMapName,
       oidcAttrMapEmail: securitySettingParams.oidcAttrMapEmail,
-      isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser,
+      isSameUsernameTreatedAsIdenticalUser:
+        securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser:
+        securitySettingParams.isSameEmailTreatedAsIdenticalUser,
     });
     return response;
   }
-
 }

+ 51 - 38
apps/app/src/client/services/AdminSamlSecurityContainer.js

@@ -13,7 +13,6 @@ const logger = loggerFactory('growi:security:AdminSamlSecurityContainer');
  * @extends {Container} unstated Container
  */
 export default class AdminSamlSecurityContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -49,7 +48,6 @@ export default class AdminSamlSecurityContainer extends Container {
       envAttrMapLastName: '',
       envABLCRule: '',
     };
-
   }
 
   /**
@@ -70,8 +68,10 @@ export default class AdminSamlSecurityContainer extends Container {
         samlAttrMapMail: samlAuth.samlAttrMapMail,
         samlAttrMapFirstName: samlAuth.samlAttrMapFirstName,
         samlAttrMapLastName: samlAuth.samlAttrMapLastName,
-        isSameUsernameTreatedAsIdenticalUser: samlAuth.isSameUsernameTreatedAsIdenticalUser,
-        isSameEmailTreatedAsIdenticalUser: samlAuth.isSameEmailTreatedAsIdenticalUser,
+        isSameUsernameTreatedAsIdenticalUser:
+          samlAuth.isSameUsernameTreatedAsIdenticalUser,
+        isSameEmailTreatedAsIdenticalUser:
+          samlAuth.isSameEmailTreatedAsIdenticalUser,
         samlABLCRule: samlAuth.samlABLCRule,
         envEntryPoint: samlAuth.samlEnvVarEntryPoint,
         envIssuer: samlAuth.samlEnvVarIssuer,
@@ -83,8 +83,7 @@ export default class AdminSamlSecurityContainer extends Container {
         envAttrMapLastName: samlAuth.samlEnvVarAttrMapLastName,
         envABLCRule: samlAuth.samlEnvVarABLCRule,
       });
-    }
-    catch (err) {
+    } catch (err) {
       this.setState({ retrieveError: err });
       logger.error(err);
       throw new Error('Failed to fetch data');
@@ -102,53 +101,66 @@ export default class AdminSamlSecurityContainer extends Container {
    * Switch isSameUsernameTreatedAsIdenticalUser
    */
   switchIsSameUsernameTreatedAsIdenticalUser() {
-    this.setState({ isSameUsernameTreatedAsIdenticalUser: !this.state.isSameUsernameTreatedAsIdenticalUser });
+    this.setState({
+      isSameUsernameTreatedAsIdenticalUser:
+        !this.state.isSameUsernameTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Switch isSameEmailTreatedAsIdenticalUser
    */
   switchIsSameEmailTreatedAsIdenticalUser() {
-    this.setState({ isSameEmailTreatedAsIdenticalUser: !this.state.isSameEmailTreatedAsIdenticalUser });
+    this.setState({
+      isSameEmailTreatedAsIdenticalUser:
+        !this.state.isSameEmailTreatedAsIdenticalUser,
+    });
   }
 
   /**
    * Update saml option
    */
   async updateSamlSetting(formData) {
-
-    let requestParams = formData != null ? {
-      entryPoint: formData.samlEntryPoint,
-      issuer: formData.samlIssuer,
-      cert: formData.samlCert,
-      attrMapId: formData.samlAttrMapId,
-      attrMapUsername: formData.samlAttrMapUsername,
-      attrMapMail: formData.samlAttrMapMail,
-      attrMapFirstName: formData.samlAttrMapFirstName,
-      attrMapLastName: formData.samlAttrMapLastName,
-      isSameUsernameTreatedAsIdenticalUser: formData.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: formData.isSameEmailTreatedAsIdenticalUser,
-      ABLCRule: formData.samlABLCRule,
-    } : {
-      entryPoint: this.state.samlEntryPoint,
-      issuer: this.state.samlIssuer,
-      cert: this.state.samlCert,
-      attrMapId: this.state.samlAttrMapId,
-      attrMapUsername: this.state.samlAttrMapUsername,
-      attrMapMail: this.state.samlAttrMapMail,
-      attrMapFirstName: this.state.samlAttrMapFirstName,
-      attrMapLastName: this.state.samlAttrMapLastName,
-      isSameUsernameTreatedAsIdenticalUser: this.state.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: this.state.isSameEmailTreatedAsIdenticalUser,
-      ABLCRule: this.state.samlABLCRule,
-    };
+    let requestParams =
+      formData != null
+        ? {
+            entryPoint: formData.samlEntryPoint,
+            issuer: formData.samlIssuer,
+            cert: formData.samlCert,
+            attrMapId: formData.samlAttrMapId,
+            attrMapUsername: formData.samlAttrMapUsername,
+            attrMapMail: formData.samlAttrMapMail,
+            attrMapFirstName: formData.samlAttrMapFirstName,
+            attrMapLastName: formData.samlAttrMapLastName,
+            isSameUsernameTreatedAsIdenticalUser:
+              formData.isSameUsernameTreatedAsIdenticalUser,
+            isSameEmailTreatedAsIdenticalUser:
+              formData.isSameEmailTreatedAsIdenticalUser,
+            ABLCRule: formData.samlABLCRule,
+          }
+        : {
+            entryPoint: this.state.samlEntryPoint,
+            issuer: this.state.samlIssuer,
+            cert: this.state.samlCert,
+            attrMapId: this.state.samlAttrMapId,
+            attrMapUsername: this.state.samlAttrMapUsername,
+            attrMapMail: this.state.samlAttrMapMail,
+            attrMapFirstName: this.state.samlAttrMapFirstName,
+            attrMapLastName: this.state.samlAttrMapLastName,
+            isSameUsernameTreatedAsIdenticalUser:
+              this.state.isSameUsernameTreatedAsIdenticalUser,
+            isSameEmailTreatedAsIdenticalUser:
+              this.state.isSameEmailTreatedAsIdenticalUser,
+            ABLCRule: this.state.samlABLCRule,
+          };
 
     requestParams = await removeNullPropertyFromObject(requestParams);
     const response = await apiv3Put('/security-setting/saml', requestParams);
     const { securitySettingParams } = response.data;
 
     this.setState({
-      missingMandatoryConfigKeys: securitySettingParams.missingMandatoryConfigKeys,
+      missingMandatoryConfigKeys:
+        securitySettingParams.missingMandatoryConfigKeys,
       samlEntryPoint: securitySettingParams.samlEntryPoint,
       samlIssuer: securitySettingParams.samlIssuer,
       samlCert: securitySettingParams.samlCert,
@@ -157,11 +169,12 @@ export default class AdminSamlSecurityContainer extends Container {
       samlAttrMapMail: securitySettingParams.samlAttrMapMail,
       samlAttrMapFirstName: securitySettingParams.samlAttrMapFirstName,
       samlAttrMapLastName: securitySettingParams.samlAttrMapLastName,
-      isSameUsernameTreatedAsIdenticalUser: securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
-      isSameEmailTreatedAsIdenticalUser: securitySettingParams.isSameEmailTreatedAsIdenticalUser,
+      isSameUsernameTreatedAsIdenticalUser:
+        securitySettingParams.isSameUsernameTreatedAsIdenticalUser,
+      isSameEmailTreatedAsIdenticalUser:
+        securitySettingParams.isSameEmailTreatedAsIdenticalUser,
       samlABLCRule: securitySettingParams.samlABLCRule,
     });
     return response;
   }
-
 }

+ 5 - 5
apps/app/src/client/services/AdminSlackIntegrationLegacyContainer.js

@@ -8,7 +8,6 @@ import { apiv3Get, apiv3Put } from '../util/apiv3-client';
  * @extends {Container} unstated Container
  */
 export default class AdminSlackIntegrationLegacyContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -26,7 +25,6 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
       isIncomingWebhookPrioritized: false,
       slackToken: '',
     };
-
   }
 
   /**
@@ -46,7 +44,8 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
     this.setState({
       isSlackbotConfigured: slackIntegrationParams.isSlackbotConfigured,
       webhookUrl: slackIntegrationParams.webhookUrl,
-      isIncomingWebhookPrioritized: slackIntegrationParams.isIncomingWebhookPrioritized,
+      isIncomingWebhookPrioritized:
+        slackIntegrationParams.isIncomingWebhookPrioritized,
       slackToken: slackIntegrationParams.slackToken,
     });
   }
@@ -69,7 +68,9 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
    * Switch incomingWebhookPrioritized
    */
   switchIsIncomingWebhookPrioritized() {
-    this.setState({ isIncomingWebhookPrioritized: !this.state.isIncomingWebhookPrioritized });
+    this.setState({
+      isIncomingWebhookPrioritized: !this.state.isIncomingWebhookPrioritized,
+    });
   }
 
   /**
@@ -92,5 +93,4 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
 
     return response;
   }
-
 }

+ 0 - 1
apps/app/src/client/services/AdminSocketIoContainer.js

@@ -1,2 +1 @@
-
 export default class AdminSocketIoContainer {}

+ 20 - 16
apps/app/src/client/services/AdminUsersContainer.js

@@ -3,16 +3,17 @@ import { debounce } from 'throttle-debounce';
 import { Container } from 'unstated';
 
 import {
-  apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
+  apiv3Delete,
+  apiv3Get,
+  apiv3Post,
+  apiv3Put,
 } from '../util/apiv3-client';
 
-
 /**
  * Service container for admin users page (Users.jsx)
  * @extends {Container} unstated Container
  */
 export default class AdminUsersContainer extends Container {
-
   constructor(appContainer) {
     super();
 
@@ -41,7 +42,9 @@ export default class AdminUsersContainer extends Container {
     this.hidePasswordResetModal = this.hidePasswordResetModal.bind(this);
     this.toggleUserInviteModal = this.toggleUserInviteModal.bind(this);
 
-    this.handleChangeSearchTextDebouce = debounce(3000, () => this.retrieveUsersByPagingNum(1));
+    this.handleChangeSearchTextDebouce = debounce(3000, () =>
+      this.retrieveUsersByPagingNum(1),
+    );
   }
 
   /**
@@ -62,12 +65,10 @@ export default class AdminUsersContainer extends Container {
     const all = 'all';
     if (this.isSelected(statusType)) {
       this.deleteStatusFromList(statusType);
-    }
-    else {
+    } else {
       if (statusType === all) {
         this.clearStatusList();
-      }
-      else {
+      } else {
         this.deleteStatusFromList(all);
       }
       this.addStatusToList(statusType);
@@ -132,7 +133,6 @@ export default class AdminUsersContainer extends Container {
    * @param {number} selectedPage
    */
   async retrieveUsersByPagingNum(selectedPage) {
-
     const params = {
       page: selectedPage,
       sort: this.state.sort,
@@ -145,10 +145,14 @@ export default class AdminUsersContainer extends Container {
     const { data } = await apiv3Get('/users', params);
 
     if (data.paginateResult == null) {
-      throw new Error('data must conclude \'paginateResult\' property.');
+      throw new Error("data must conclude 'paginateResult' property.");
     }
 
-    const { docs: users, totalDocs: totalUsers, limit: pagingLimit } = data.paginateResult;
+    const {
+      docs: users,
+      totalDocs: totalUsers,
+      limit: pagingLimit,
+    } = data.paginateResult;
 
     this.setState({
       users,
@@ -156,12 +160,11 @@ export default class AdminUsersContainer extends Container {
       pagingLimit,
       activePage: selectedPage,
     });
-
   }
 
   /**
- * retrieve user statistics
- */
+   * retrieve user statistics
+   */
   async retrieveUserStatistics() {
     const statsRes = await apiv3Get('/statistics/user');
     const userStatistics = statsRes.data.data;
@@ -211,7 +214,9 @@ export default class AdminUsersContainer extends Container {
    * @memberOf AdminUsersContainer
    */
   async toggleUserInviteModal() {
-    await this.setState({ isUserInviteModalShown: !this.state.isUserInviteModalShown });
+    await this.setState({
+      isUserInviteModalShown: !this.state.isUserInviteModalShown,
+    });
   }
 
   /**
@@ -304,5 +309,4 @@ export default class AdminUsersContainer extends Container {
     await this.retrieveUsersByPagingNum(this.state.activePage);
     return removedUserData;
   }
-
 }

+ 7 - 2
apps/app/src/client/services/create-page/create-page.ts

@@ -1,7 +1,12 @@
 import { apiv3Post } from '~/client/util/apiv3-client';
-import type { IApiv3PageCreateParams, IApiv3PageCreateResponse } from '~/interfaces/apiv3';
+import type {
+  IApiv3PageCreateParams,
+  IApiv3PageCreateResponse,
+} from '~/interfaces/apiv3';
 
-export const createPage = async(params: IApiv3PageCreateParams): Promise<IApiv3PageCreateResponse> => {
+export const createPage = async (
+  params: IApiv3PageCreateParams,
+): Promise<IApiv3PageCreateResponse> => {
   const res = await apiv3Post<IApiv3PageCreateResponse>('/page', params);
   return res.data;
 };

+ 95 - 81
apps/app/src/client/services/create-page/use-create-page.tsx

@@ -1,13 +1,15 @@
 import { useCallback, useState } from 'react';
-
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 
-import { exist, getIsNonUserRelatedGroupsGranted } from '~/client/services/page-operation';
+import {
+  exist,
+  getIsNonUserRelatedGroupsGranted,
+} from '~/client/services/page-operation';
 import { toastWarning } from '~/client/util/toastr';
 import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
 import { useCurrentPagePath, useSetIsUntitledPage } from '~/states/page';
-import { useEditorMode, EditorMode } from '~/states/ui/editor';
+import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import { useGrantedGroupsInheritanceSelectModalActions } from '~/states/ui/modal/granted-groups-inheritance-select';
 
 import { createPage } from './create-page';
@@ -26,13 +28,13 @@ type OnAborted = () => void;
 type OnTerminated = () => void;
 
 export type CreatePageOpts = {
-  skipPageExistenceCheck?: boolean,
-  skipTransition?: boolean,
-  onCreationStart?: OnCreated,
-  onCreated?: OnCreated,
-  onAborted?: OnAborted,
-  onTerminated?: OnTerminated,
-}
+  skipPageExistenceCheck?: boolean;
+  skipTransition?: boolean;
+  onCreationStart?: OnCreated;
+  onCreated?: OnCreated;
+  onAborted?: OnAborted;
+  onTerminated?: OnTerminated;
+};
 
 type CreatePage = (
   params: IApiv3PageCreateParams,
@@ -40,101 +42,113 @@ type CreatePage = (
 ) => Promise<void>;
 
 type UseCreatePage = () => {
-  isCreating: boolean,
-  create: CreatePage,
+  isCreating: boolean;
+  create: CreatePage;
 };
 
 export const useCreatePage: UseCreatePage = () => {
-
   const router = useRouter();
   const { t } = useTranslation();
 
   const currentPagePath = useCurrentPagePath();
   const { setEditorMode } = useEditorMode();
   const setIsUntitledPage = useSetIsUntitledPage();
-  const { open: openGrantedGroupsInheritanceSelectModal, close: closeGrantedGroupsInheritanceSelectModal } = useGrantedGroupsInheritanceSelectModalActions();
+  const {
+    open: openGrantedGroupsInheritanceSelectModal,
+    close: closeGrantedGroupsInheritanceSelectModal,
+  } = useGrantedGroupsInheritanceSelectModalActions();
 
   const [isCreating, setCreating] = useState(false);
 
-  const create: CreatePage = useCallback(async (params, opts = {}) => {
-    const {
-      onCreationStart, onCreated, onAborted, onTerminated,
-    } = opts;
-    const skipPageExistenceCheck = opts.skipPageExistenceCheck ?? false;
-    const skipTransition = opts.skipTransition ?? false;
-
-    // check the page existence
-    if (!skipPageExistenceCheck && params.path != null) {
-      const pagePath = params.path;
-
-      try {
-        const { isExist } = await exist(pagePath);
-
-        if (isExist) {
-          if (!skipTransition) {
-            // routing
-            if (pagePath !== currentPagePath) {
-              await router.push(`${pagePath}#edit`);
+  const create: CreatePage = useCallback(
+    async (params, opts = {}) => {
+      const { onCreationStart, onCreated, onAborted, onTerminated } = opts;
+      const skipPageExistenceCheck = opts.skipPageExistenceCheck ?? false;
+      const skipTransition = opts.skipTransition ?? false;
+
+      // check the page existence
+      if (!skipPageExistenceCheck && params.path != null) {
+        const pagePath = params.path;
+
+        try {
+          const { isExist } = await exist(pagePath);
+
+          if (isExist) {
+            if (!skipTransition) {
+              // routing
+              if (pagePath !== currentPagePath) {
+                await router.push(`${pagePath}#edit`);
+              }
+              setEditorMode(EditorMode.Editor);
+            } else {
+              toastWarning(
+                t('duplicated_page_alert.same_page_name_exists', {
+                  pageName: pagePath,
+                }),
+              );
             }
-            setEditorMode(EditorMode.Editor);
-          }
-          else {
-            toastWarning(t('duplicated_page_alert.same_page_name_exists', { pageName: pagePath }));
+            onAborted?.();
+            return;
           }
-          onAborted?.();
-          return;
+        } catch (err) {
+          throw err;
+        } finally {
+          onTerminated?.();
         }
       }
-      catch (err) {
-        throw err;
-      }
-      finally {
-        onTerminated?.();
-      }
-    }
 
-    const _create = async (onlyInheritUserRelatedGrantedGroups?: boolean) => {
-      try {
-        setCreating(true);
-        onCreationStart?.();
+      const _create = async (onlyInheritUserRelatedGrantedGroups?: boolean) => {
+        try {
+          setCreating(true);
+          onCreationStart?.();
 
-        params.onlyInheritUserRelatedGrantedGroups = onlyInheritUserRelatedGrantedGroups;
-        const response = await createPage(params);
+          params.onlyInheritUserRelatedGrantedGroups =
+            onlyInheritUserRelatedGrantedGroups;
+          const response = await createPage(params);
 
-        closeGrantedGroupsInheritanceSelectModal();
+          closeGrantedGroupsInheritanceSelectModal();
 
-        if (!skipTransition) {
-          await router.push(`/${response.page._id}#edit`);
-          setEditorMode(EditorMode.Editor);
-        }
+          if (!skipTransition) {
+            await router.push(`/${response.page._id}#edit`);
+            setEditorMode(EditorMode.Editor);
+          }
 
-        if (params.path == null) {
-          setIsUntitledPage(true);
-        }
+          if (params.path == null) {
+            setIsUntitledPage(true);
+          }
 
-        onCreated?.();
-      }
-      catch (err) {
-        throw err;
-      }
-      finally {
-        onTerminated?.();
-        setCreating(false);
-      }
-    };
-
-    // If parent page is granted to non-user-related groups, let the user select whether or not to inherit them.
-    if (params.parentPath != null) {
-      const { isNonUserRelatedGroupsGranted } = await getIsNonUserRelatedGroupsGranted(params.parentPath);
-      if (isNonUserRelatedGroupsGranted) {
-        // create and transit request will be made from modal
-        openGrantedGroupsInheritanceSelectModal(_create);
-        return;
+          onCreated?.();
+        } catch (err) {
+          throw err;
+        } finally {
+          onTerminated?.();
+          setCreating(false);
+        }
+      };
+
+      // If parent page is granted to non-user-related groups, let the user select whether or not to inherit them.
+      if (params.parentPath != null) {
+        const { isNonUserRelatedGroupsGranted } =
+          await getIsNonUserRelatedGroupsGranted(params.parentPath);
+        if (isNonUserRelatedGroupsGranted) {
+          // create and transit request will be made from modal
+          openGrantedGroupsInheritanceSelectModal(_create);
+          return;
+        }
       }
-    }
 
-    await _create();
-  }, [currentPagePath, setEditorMode, router, t, closeGrantedGroupsInheritanceSelectModal, setIsUntitledPage, openGrantedGroupsInheritanceSelectModal]);
+      await _create();
+    },
+    [
+      currentPagePath,
+      setEditorMode,
+      router,
+      t,
+      closeGrantedGroupsInheritanceSelectModal,
+      setIsUntitledPage,
+      openGrantedGroupsInheritanceSelectModal,
+    ],
+  );
 
   return {
     isCreating,

+ 21 - 18
apps/app/src/client/services/create-page/use-create-template-page.ts

@@ -1,5 +1,4 @@
 import { useCallback } from 'react';
-
 import { Origin } from '@growi/core';
 import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
 import { normalizePath } from '@growi/core/dist/utils/path-utils';
@@ -7,31 +6,35 @@ import { normalizePath } from '@growi/core/dist/utils/path-utils';
 import type { LabelType } from '~/interfaces/template';
 import { useCurrentPagePath } from '~/states/page';
 
-
 import { useCreatePage } from './use-create-page';
 
 type UseCreateTemplatePage = () => {
-  isCreatable: boolean,
-  isCreating: boolean,
-  createTemplate?: (label: LabelType) => Promise<void>,
-}
+  isCreatable: boolean;
+  isCreating: boolean;
+  createTemplate?: (label: LabelType) => Promise<void>;
+};
 
 export const useCreateTemplatePage: UseCreateTemplatePage = () => {
-
   const currentPagePath = useCurrentPagePath();
 
   const { isCreating, create } = useCreatePage();
-  const isCreatable = currentPagePath != null && isCreatablePage(normalizePath(`${currentPagePath}/_template`));
-
-  const createTemplate = useCallback(async(label: LabelType) => {
-    if (currentPagePath == null || !isCreatable) return;
-
-    return create(
-      {
-        path: normalizePath(`${currentPagePath}/${label}`), parentPath: currentPagePath, wip: false, origin: Origin.View,
-      },
-    );
-  }, [currentPagePath, isCreatable, create]);
+  const isCreatable =
+    currentPagePath != null &&
+    isCreatablePage(normalizePath(`${currentPagePath}/_template`));
+
+  const createTemplate = useCallback(
+    async (label: LabelType) => {
+      if (currentPagePath == null || !isCreatable) return;
+
+      return create({
+        path: normalizePath(`${currentPagePath}/${label}`),
+        parentPath: currentPagePath,
+        wip: false,
+        origin: Origin.View,
+      });
+    },
+    [currentPagePath, isCreatable, create],
+  );
 
   return {
     isCreatable,

+ 8 - 3
apps/app/src/client/services/g2g-transfer.ts

@@ -2,11 +2,16 @@ import { useCallback, useState } from 'react';
 
 import { apiv3Post } from '~/client/util/apiv3-client';
 
-export const useGenerateTransferKey = (): {transferKey: string, generateTransferKey: () => Promise<void>} => {
+export const useGenerateTransferKey = (): {
+  transferKey: string;
+  generateTransferKey: () => Promise<void>;
+} => {
   const [transferKey, setTransferKey] = useState('');
 
-  const generateTransferKey = useCallback(async() => {
-    const response = await apiv3Post('/g2g-transfer/generate-key', { appSiteUrl: window.location.origin });
+  const generateTransferKey = useCallback(async () => {
+    const response = await apiv3Post('/g2g-transfer/generate-key', {
+      appSiteUrl: window.location.origin,
+    });
     const { transferKey } = response.data;
     setTransferKey(transferKey);
   }, []);

+ 0 - 1
apps/app/src/client/services/maintenance-mode.ts

@@ -1,5 +1,4 @@
 import { useCallback } from 'react';
-
 import { useSetAtom } from 'jotai';
 
 import { _atomsForMaintenanceMode } from '../../states/global';

+ 91 - 49
apps/app/src/client/services/page-operation.ts

@@ -1,5 +1,4 @@
 import { useCallback } from 'react';
-
 import type { IPageHasId } from '@growi/core';
 import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
@@ -17,67 +16,81 @@ import { toastError } from '../util/toastr';
 
 const logger = loggerFactory('growi:services:page-operation');
 
-
-export const toggleSubscribe = async(pageId: string, currentStatus: SubscriptionStatusType | undefined): Promise<void> => {
+export const toggleSubscribe = async (
+  pageId: string,
+  currentStatus: SubscriptionStatusType | undefined,
+): Promise<void> => {
   try {
-    const newStatus = currentStatus === SubscriptionStatusType.SUBSCRIBE
-      ? SubscriptionStatusType.UNSUBSCRIBE
-      : SubscriptionStatusType.SUBSCRIBE;
+    const newStatus =
+      currentStatus === SubscriptionStatusType.SUBSCRIBE
+        ? SubscriptionStatusType.UNSUBSCRIBE
+        : SubscriptionStatusType.SUBSCRIBE;
 
     await apiv3Put('/page/subscribe', { pageId, status: newStatus });
-  }
-  catch (err) {
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const toggleLike = async(pageId: string, currentValue?: boolean): Promise<void> => {
+export const toggleLike = async (
+  pageId: string,
+  currentValue?: boolean,
+): Promise<void> => {
   try {
     await apiv3Put('/page/likes', { pageId, bool: !currentValue });
-  }
-  catch (err) {
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const toggleBookmark = async(pageId: string, currentValue?: boolean): Promise<void> => {
+export const toggleBookmark = async (
+  pageId: string,
+  currentValue?: boolean,
+): Promise<void> => {
   try {
     await apiv3Put('/bookmarks', { pageId, bool: !currentValue });
-  }
-  catch (err) {
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const updateContentWidth = async(pageId: string, newValue: boolean): Promise<void> => {
+export const updateContentWidth = async (
+  pageId: string,
+  newValue: boolean,
+): Promise<void> => {
   try {
-    await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
-  }
-  catch (err) {
+    await apiv3Put(`/page/${pageId}/content-width`, {
+      expandContentWidth: newValue,
+    });
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const bookmark = async(pageId: string): Promise<void> => {
+export const bookmark = async (pageId: string): Promise<void> => {
   try {
     await apiv3Put('/bookmarks', { pageId, bool: true });
-  }
-  catch (err) {
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const unbookmark = async(pageId: string): Promise<void> => {
+export const unbookmark = async (pageId: string): Promise<void> => {
   try {
     await apiv3Put('/bookmarks', { pageId, bool: false });
-  }
-  catch (err) {
+  } catch (err) {
     toastError(err);
   }
 };
 
-export const exportAsMarkdown = (pageId: string, revisionId: string, format: string): void => {
-  const url = new URL(urljoin(window.location.origin, '_api/v3/page/export', pageId));
+export const exportAsMarkdown = (
+  pageId: string,
+  revisionId: string,
+  format: string,
+): void => {
+  const url = new URL(
+    urljoin(window.location.origin, '_api/v3/page/export', pageId),
+  );
   url.searchParams.append('format', format);
   url.searchParams.append('revisionId', revisionId);
   window.location.href = url.href;
@@ -86,34 +99,46 @@ export const exportAsMarkdown = (pageId: string, revisionId: string, format: str
 /**
  * send request to fix broken paths caused by unexpected events such as server shutdown while renaming page paths
  */
-export const resumeRenameOperation = async(pageId: string): Promise<void> => {
+export const resumeRenameOperation = async (pageId: string): Promise<void> => {
   await apiv3Post('/pages/resume-rename', { pageId });
 };
 
 export type UpdateStateAfterSaveOption = {
-  supressEditingMarkdownMutation: boolean,
-}
+  supressEditingMarkdownMutation: boolean;
+};
 
-export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: UpdateStateAfterSaveOption): (() => Promise<void>) | undefined => {
+export const useUpdateStateAfterSave = (
+  pageId: string | undefined | null,
+  opts?: UpdateStateAfterSaveOption,
+): (() => Promise<void>) | undefined => {
   const isGuestUser = useIsGuestUser();
   const { fetchCurrentPage } = useFetchCurrentPage();
   const setRemoteLatestPageData = useSetRemoteLatestPageData();
 
   const setEditingMarkdown = useSetEditingMarkdown();
-  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(isGuestUser ? null : pageId);
-  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(isGuestUser ? null : pageId);
+  const { mutate: mutateCurrentGrantData } = useSWRxCurrentGrantData(
+    isGuestUser ? null : pageId,
+  );
+  const { mutate: mutateApplicableGrant } = useSWRxApplicableGrant(
+    isGuestUser ? null : pageId,
+  );
 
   // update swr 'currentPageId', 'currentPage', remote states
-  return useCallback(async() => {
-    if (pageId == null) { return }
+  return useCallback(async () => {
+    if (pageId == null) {
+      return;
+    }
 
     const updatedPage = await fetchCurrentPage({ pageId, force: true });
 
-    if (updatedPage == null || updatedPage.revision == null) { return }
+    if (updatedPage == null || updatedPage.revision == null) {
+      return;
+    }
 
     // supress to mutate only when updated from built-in editor
     // and see: https://github.com/growilabs/growi/pull/7118
-    const supressEditingMarkdownMutation = opts?.supressEditingMarkdownMutation ?? false;
+    const supressEditingMarkdownMutation =
+      opts?.supressEditingMarkdownMutation ?? false;
     if (!supressEditingMarkdownMutation) {
       setEditingMarkdown(updatedPage.revision.body);
     }
@@ -129,44 +154,61 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     };
 
     setRemoteLatestPageData(remoterevisionData);
-  },
-  [pageId, fetchCurrentPage, opts?.supressEditingMarkdownMutation, mutateCurrentGrantData, mutateApplicableGrant, setRemoteLatestPageData, setEditingMarkdown]);
+  }, [
+    pageId,
+    fetchCurrentPage,
+    opts?.supressEditingMarkdownMutation,
+    mutateCurrentGrantData,
+    mutateApplicableGrant,
+    setRemoteLatestPageData,
+    setEditingMarkdown,
+  ]);
 };
 
-export const unlink = async(path: string): Promise<void> => {
+export const unlink = async (path: string): Promise<void> => {
   await apiPost('/pages.unlink', { path });
 };
 
-
 interface PageExistResponse {
-  isExist: boolean,
+  isExist: boolean;
 }
 
-export const exist = async(path: string): Promise<PageExistResponse> => {
+export const exist = async (path: string): Promise<PageExistResponse> => {
   const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
   return res.data;
 };
 
 interface NonUserRelatedGroupsGrantedResponse {
-  isNonUserRelatedGroupsGranted: boolean,
+  isNonUserRelatedGroupsGranted: boolean;
 }
 
-export const getIsNonUserRelatedGroupsGranted = async(path: string): Promise<NonUserRelatedGroupsGrantedResponse> => {
-  const res = await apiv3Get<NonUserRelatedGroupsGrantedResponse>('/page/non-user-related-groups-granted', { path });
+export const getIsNonUserRelatedGroupsGranted = async (
+  path: string,
+): Promise<NonUserRelatedGroupsGrantedResponse> => {
+  const res = await apiv3Get<NonUserRelatedGroupsGrantedResponse>(
+    '/page/non-user-related-groups-granted',
+    { path },
+  );
   return res.data;
 };
 
-export const publish = async(pageId: string): Promise<IPageHasId> => {
+export const publish = async (pageId: string): Promise<IPageHasId> => {
   const res = await apiv3Put(`/page/${pageId}/publish`);
   return res.data;
 };
 
-export const unpublish = async(pageId: string): Promise<IPageHasId> => {
+export const unpublish = async (pageId: string): Promise<IPageHasId> => {
   const res = await apiv3Put(`/page/${pageId}/unpublish`);
   return res.data;
 };
 
-export const syncLatestRevisionBody = async(pageId: string, editingMarkdownLength?: number): Promise<SyncLatestRevisionBody> => {
-  const res = await apiv3Put(`/page/${pageId}/sync-latest-revision-body-to-yjs-draft`, { editingMarkdownLength });
+export const syncLatestRevisionBody = async (
+  pageId: string,
+  editingMarkdownLength?: number,
+): Promise<SyncLatestRevisionBody> => {
+  const res = await apiv3Put(
+    `/page/${pageId}/sync-latest-revision-body-to-yjs-draft`,
+    { editingMarkdownLength },
+  );
   return res.data;
 };

+ 111 - 83
apps/app/src/client/services/renderer/renderer.tsx

@@ -1,10 +1,9 @@
-import assert from 'assert';
-
 import { isClient } from '@growi/core/dist/utils/browser-utils';
 import * as presentation from '@growi/presentation/dist/client/services/sanitize-option';
 import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
 import * as drawio from '@growi/remark-drawio';
 import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
+import assert from 'assert';
 import katex from 'rehype-katex';
 import sanitize from 'rehype-sanitize';
 import slug from 'rehype-slug';
@@ -24,7 +23,7 @@ import * as callout from '~/features/callout';
 import * as mermaid from '~/features/mermaid';
 import * as plantuml from '~/features/plantuml';
 import type { RendererOptions } from '~/interfaces/renderer-options';
-import { type RendererConfigExt } from '~/interfaces/services/renderer';
+import type { RendererConfigExt } from '~/interfaces/services/renderer';
 import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
 import * as keywordHighlighter from '~/services/renderer/rehype-plugins/keyword-highlighter';
 import * as relocateToc from '~/services/renderer/rehype-plugins/relocate-toc';
@@ -32,29 +31,26 @@ import * as attachment from '~/services/renderer/remark-plugins/attachment';
 import * as codeBlock from '~/services/renderer/remark-plugins/codeblock';
 import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
 import {
-  getCommonSanitizeOption, generateCommonOptions, verifySanitizePlugin,
+  generateCommonOptions,
+  getCommonSanitizeOption,
+  verifySanitizePlugin,
 } from '~/services/renderer/renderer';
 import loggerFactory from '~/utils/logger';
 
 // import EasyGrid from './PreProcessor/EasyGrid';
 
-
 import '@growi/remark-lsx/dist/client/style.css';
 import '@growi/remark-attachment-refs/dist/client/style.css';
 
-
 const logger = loggerFactory('growi:cli:services:renderer');
 
-
 assert(isClient(), 'This module must be loaded only from client modules.');
 
-
 export const generateViewOptions = (
-    pagePath: string,
-    config: RendererConfigExt,
-    storeTocNode: (toc: HtmlElementNode) => void,
+  pagePath: string,
+  config: RendererConfigExt,
+  storeTocNode: (toc: HtmlElementNode) => void,
 ): RendererOptions => {
-
   const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
@@ -62,7 +58,10 @@ export const generateViewOptions = (
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
+    [
+      plantuml.remarkPlugin,
+      { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode },
+    ],
     [drawio.remarkPlugin, { isDarkMode: config.isDarkMode }],
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,
@@ -76,24 +75,31 @@ export const generateViewOptions = (
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      getCommonSanitizeOption(config),
-      presentation.sanitizeOption,
-      drawio.sanitizeOption,
-      mermaid.sanitizeOption,
-      callout.sanitizeOption,
-      attachment.sanitizeOption,
-      lsxGrowiDirective.sanitizeOption,
-      refsGrowiDirective.sanitizeOption,
-      codeBlock.sanitizeOption,
-    )]
-    : () => {};
+  const rehypeSanitizePlugin: Pluggable | (() => void) =
+    config.isEnabledXssPrevention
+      ? [
+          sanitize,
+          deepmerge(
+            getCommonSanitizeOption(config),
+            presentation.sanitizeOption,
+            drawio.sanitizeOption,
+            mermaid.sanitizeOption,
+            callout.sanitizeOption,
+            attachment.sanitizeOption,
+            lsxGrowiDirective.sanitizeOption,
+            refsGrowiDirective.sanitizeOption,
+            codeBlock.sanitizeOption,
+          ),
+        ]
+      : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
     slug,
-    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [
+      lsxGrowiDirective.rehypePlugin,
+      { pagePath, isSharedPage: config.isSharedPage },
+    ],
     [refsGrowiDirective.rehypePlugin, { pagePath }],
     rehypeSanitizePlugin,
     katex,
@@ -128,8 +134,10 @@ export const generateViewOptions = (
   return options;
 };
 
-export const generateTocOptions = (config: RendererConfigExt, tocNode: HtmlElementNode | undefined): RendererOptions => {
-
+export const generateTocOptions = (
+  config: RendererConfigExt,
+  tocNode: HtmlElementNode | undefined,
+): RendererOptions => {
   const options = generateCommonOptions(undefined);
 
   const { rehypePlugins } = options;
@@ -137,12 +145,13 @@ export const generateTocOptions = (config: RendererConfigExt, tocNode: HtmlEleme
   // add remark plugins
   // remarkPlugins.push();
 
-  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      getCommonSanitizeOption(config),
-      codeBlock.sanitizeOption,
-    )]
-    : () => {};
+  const rehypeSanitizePlugin: Pluggable | (() => void) =
+    config.isEnabledXssPrevention
+      ? [
+          sanitize,
+          deepmerge(getCommonSanitizeOption(config), codeBlock.sanitizeOption),
+        ]
+      : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
@@ -158,10 +167,10 @@ export const generateTocOptions = (config: RendererConfigExt, tocNode: HtmlEleme
 };
 
 export const generateSimpleViewOptions = (
-    config: RendererConfigExt,
-    pagePath: string,
-    highlightKeywords?: string | string[],
-    overrideIsEnabledLinebreaks?: boolean,
+  config: RendererConfigExt,
+  pagePath: string,
+  highlightKeywords?: string | string[],
+  overrideIsEnabledLinebreaks?: boolean,
 ): RendererOptions => {
   const options = generateCommonOptions(pagePath);
 
@@ -170,7 +179,10 @@ export const generateSimpleViewOptions = (
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
+    [
+      plantuml.remarkPlugin,
+      { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode },
+    ],
     [drawio.remarkPlugin, { isDarkMode: config.isDarkMode }],
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,
@@ -181,29 +193,37 @@ export const generateSimpleViewOptions = (
     refsGrowiDirective.remarkPlugin,
   );
 
-  const isEnabledLinebreaks = overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
+  const isEnabledLinebreaks =
+    overrideIsEnabledLinebreaks ?? config.isEnabledLinebreaks;
 
   if (isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      getCommonSanitizeOption(config),
-      presentation.sanitizeOption,
-      drawio.sanitizeOption,
-      mermaid.sanitizeOption,
-      callout.sanitizeOption,
-      attachment.sanitizeOption,
-      lsxGrowiDirective.sanitizeOption,
-      refsGrowiDirective.sanitizeOption,
-      codeBlock.sanitizeOption,
-    )]
-    : () => {};
+  const rehypeSanitizePlugin: Pluggable | (() => void) =
+    config.isEnabledXssPrevention
+      ? [
+          sanitize,
+          deepmerge(
+            getCommonSanitizeOption(config),
+            presentation.sanitizeOption,
+            drawio.sanitizeOption,
+            mermaid.sanitizeOption,
+            callout.sanitizeOption,
+            attachment.sanitizeOption,
+            lsxGrowiDirective.sanitizeOption,
+            refsGrowiDirective.sanitizeOption,
+            codeBlock.sanitizeOption,
+          ),
+        ]
+      : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
-    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [
+      lsxGrowiDirective.rehypePlugin,
+      { pagePath, isSharedPage: config.isSharedPage },
+    ],
     [refsGrowiDirective.rehypePlugin, { pagePath }],
     [keywordHighlighter.rehypePlugin, { keywords: highlightKeywords }],
     rehypeSanitizePlugin,
@@ -232,26 +252,21 @@ export const generateSimpleViewOptions = (
 };
 
 export const generatePresentationViewOptions = (
-    config: RendererConfigExt,
-    pagePath: string,
+  config: RendererConfigExt,
+  pagePath: string,
 ): RendererOptions => {
   // based on simple view options
   const options = generateSimpleViewOptions(config, pagePath);
 
   const { rehypePlugins } = options;
 
-
-  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      addLineNumberAttribute.sanitizeOption,
-    )]
-    : () => {};
+  const rehypeSanitizePlugin: Pluggable | (() => void) =
+    config.isEnabledXssPrevention
+      ? [sanitize, deepmerge(addLineNumberAttribute.sanitizeOption)]
+      : () => {};
 
   // add rehype plugins
-  rehypePlugins.push(
-    addLineNumberAttribute.rehypePlugin,
-    rehypeSanitizePlugin,
-  );
+  rehypePlugins.push(addLineNumberAttribute.rehypePlugin, rehypeSanitizePlugin);
 
   if (config.isEnabledXssPrevention) {
     verifySanitizePlugin(options, false);
@@ -259,7 +274,10 @@ export const generatePresentationViewOptions = (
   return options;
 };
 
-export const generatePreviewOptions = (config: RendererConfigExt, pagePath: string): RendererOptions => {
+export const generatePreviewOptions = (
+  config: RendererConfigExt,
+  pagePath: string,
+): RendererOptions => {
   const options = generateCommonOptions(pagePath);
 
   const { remarkPlugins, rehypePlugins, components } = options;
@@ -267,7 +285,10 @@ export const generatePreviewOptions = (config: RendererConfigExt, pagePath: stri
   // add remark plugins
   remarkPlugins.push(
     math,
-    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode }],
+    [
+      plantuml.remarkPlugin,
+      { plantumlUri: config.plantumlUri, isDarkMode: config.isDarkMode },
+    ],
     [drawio.remarkPlugin, { isDarkMode: config.isDarkMode }],
     mermaid.remarkPlugin,
     xsvToTable.remarkPlugin,
@@ -281,23 +302,30 @@ export const generatePreviewOptions = (config: RendererConfigExt, pagePath: stri
     remarkPlugins.push(breaks);
   }
 
-  const rehypeSanitizePlugin: Pluggable | (() => void) = config.isEnabledXssPrevention
-    ? [sanitize, deepmerge(
-      getCommonSanitizeOption(config),
-      drawio.sanitizeOption,
-      mermaid.sanitizeOption,
-      callout.sanitizeOption,
-      attachment.sanitizeOption,
-      lsxGrowiDirective.sanitizeOption,
-      refsGrowiDirective.sanitizeOption,
-      addLineNumberAttribute.sanitizeOption,
-      codeBlock.sanitizeOption,
-    )]
-    : () => {};
+  const rehypeSanitizePlugin: Pluggable | (() => void) =
+    config.isEnabledXssPrevention
+      ? [
+          sanitize,
+          deepmerge(
+            getCommonSanitizeOption(config),
+            drawio.sanitizeOption,
+            mermaid.sanitizeOption,
+            callout.sanitizeOption,
+            attachment.sanitizeOption,
+            lsxGrowiDirective.sanitizeOption,
+            refsGrowiDirective.sanitizeOption,
+            addLineNumberAttribute.sanitizeOption,
+            codeBlock.sanitizeOption,
+          ),
+        ]
+      : () => {};
 
   // add rehype plugins
   rehypePlugins.push(
-    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [
+      lsxGrowiDirective.rehypePlugin,
+      { pagePath, isSharedPage: config.isSharedPage },
+    ],
     [refsGrowiDirective.rehypePlugin, { pagePath }],
     addLineNumberAttribute.rehypePlugin,
     rehypeSanitizePlugin,

+ 107 - 62
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -1,98 +1,141 @@
 import { useCallback, useEffect } from 'react';
-
 import { Origin } from '@growi/core';
 import { globalEventTarget } from '@growi/core/dist/utils';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
 import { replaceDrawioInMarkdown } from '~/client/components/Page/markdown-drawio-util-for-view';
-import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
-import { useCurrentPageData, useSetRemoteLatestPageData } from '~/states/page';
+import {
+  extractRemoteRevisionDataFromErrorObj,
+  useUpdatePage,
+} from '~/client/services/update-page';
 import type { RemoteRevisionData } from '~/states/page';
+import { useCurrentPageData, useSetRemoteLatestPageData } from '~/states/page';
 import { useShareLinkId } from '~/states/page/hooks';
 import { useConflictDiffModalActions } from '~/states/ui/modal/conflict-diff';
 import { useDrawioModalActions } from '~/states/ui/modal/drawio';
 import loggerFactory from '~/utils/logger';
 
-
-const logger = loggerFactory('growi:cli:side-effects:useDrawioModalLauncherForView');
-
+const logger = loggerFactory(
+  'growi:cli:side-effects:useDrawioModalLauncherForView',
+);
 
 export const useDrawioModalLauncherForView = (opts?: {
-  onSaveSuccess?: () => void,
-  onSaveError?: (error: any) => void,
+  onSaveSuccess?: () => void;
+  onSaveError?: (error: any) => void;
 }): void => {
-
   const shareLinkId = useShareLinkId();
 
   const currentPage = useCurrentPageData();
 
   const { open: openDrawioModal } = useDrawioModalActions();
 
-  const { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModalActions();
+  const { open: openConflictDiffModal, close: closeConflictDiffModal } =
+    useConflictDiffModalActions();
 
   const _updatePage = useUpdatePage();
 
   const setRemoteLatestPageData = useSetRemoteLatestPageData();
 
   // eslint-disable-next-line max-len
-  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
-    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
-      return;
-    }
-
-    // There are cases where "revisionId" is not required for revision updates
-    // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
-    try {
-      await _updatePage({
-        pageId: currentPage._id,
-        revisionId,
-        body: newMarkdown,
-        origin: Origin.View,
-      });
-
-      closeConflictDiffModal();
-      opts?.onSaveSuccess?.();
-    }
-    catch (error) {
-      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
-      if (remoteRevidsionData != null) {
-        onConflict(remoteRevidsionData, newMarkdown);
+  const updatePage = useCallback(
+    async (
+      revisionId: string,
+      newMarkdown: string,
+      onConflict: (
+        conflictData: RemoteRevisionData,
+        newMarkdown: string,
+      ) => void,
+    ) => {
+      if (
+        currentPage == null ||
+        currentPage.revision == null ||
+        shareLinkId != null
+      ) {
+        return;
       }
 
-      logger.error('failed to save', error);
-      opts?.onSaveError?.(error);
-    }
-  }, [_updatePage, closeConflictDiffModal, currentPage, opts, shareLinkId]);
+      // There are cases where "revisionId" is not required for revision updates
+      // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
+      try {
+        await _updatePage({
+          pageId: currentPage._id,
+          revisionId,
+          body: newMarkdown,
+          origin: Origin.View,
+        });
+
+        closeConflictDiffModal();
+        opts?.onSaveSuccess?.();
+      } catch (error) {
+        const remoteRevidsionData =
+          extractRemoteRevisionDataFromErrorObj(error);
+        if (remoteRevidsionData != null) {
+          onConflict(remoteRevidsionData, newMarkdown);
+        }
+
+        logger.error('failed to save', error);
+        opts?.onSaveError?.(error);
+      }
+    },
+    [_updatePage, closeConflictDiffModal, currentPage, opts, shareLinkId],
+  );
 
   // eslint-disable-next-line max-len
-  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
-    return async(newMarkdown: string) => {
-      await updatePage(revisionId, newMarkdown, onConflict);
-    };
-  }, [updatePage]);
-
-  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
-    setRemoteLatestPageData(remoteRevidsionData);
-
-    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
-    if (resolveConflictHandler == null) {
-      return;
-    }
-
-    openConflictDiffModal(newMarkdown, resolveConflictHandler);
-  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+  const generateResolveConflictHandler = useCallback(
+    (
+      revisionId: string,
+      onConflict: (
+        conflictData: RemoteRevisionData,
+        newMarkdown: string,
+      ) => void,
+    ) => {
+      return async (newMarkdown: string) => {
+        await updatePage(revisionId, newMarkdown, onConflict);
+      };
+    },
+    [updatePage],
+  );
+
+  const onConflictHandler = useCallback(
+    (remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+      setRemoteLatestPageData(remoteRevidsionData);
+
+      const resolveConflictHandler = generateResolveConflictHandler(
+        remoteRevidsionData.remoteRevisionId,
+        onConflictHandler,
+      );
+      if (resolveConflictHandler == null) {
+        return;
+      }
 
-  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || currentPage.revision == null) {
-      return;
-    }
+      openConflictDiffModal(newMarkdown, resolveConflictHandler);
+    },
+    [
+      generateResolveConflictHandler,
+      openConflictDiffModal,
+      setRemoteLatestPageData,
+    ],
+  );
+
+  const saveByDrawioModal = useCallback(
+    async (drawioMxFile: string, bol: number, eol: number) => {
+      if (currentPage == null || currentPage.revision == null) {
+        return;
+      }
 
-    const currentRevisionId = currentPage.revision._id;
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
+      const currentRevisionId = currentPage.revision._id;
+      const currentMarkdown = currentPage.revision.body;
+      const newMarkdown = replaceDrawioInMarkdown(
+        drawioMxFile,
+        currentMarkdown,
+        bol,
+        eol,
+      );
 
-    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
-  }, [currentPage, onConflictHandler, updatePage]);
+      await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+    },
+    [currentPage, onConflictHandler, updatePage],
+  );
 
   // set handler to open DrawioModal
   useEffect(() => {
@@ -103,7 +146,9 @@ export const useDrawioModalLauncherForView = (opts?: {
 
     const handler = (evt: CustomEvent<DrawioEditByViewerProps>) => {
       const data = evt.detail;
-      openDrawioModal(data.drawioMxFile, drawioMxFile => saveByDrawioModal(drawioMxFile, data.bol, data.eol));
+      openDrawioModal(data.drawioMxFile, (drawioMxFile) =>
+        saveByDrawioModal(drawioMxFile, data.bol, data.eol),
+      );
     };
     globalEventTarget.addEventListener('launchDrawioModal', handler);
 

+ 117 - 64
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -1,99 +1,145 @@
 import { useCallback, useEffect } from 'react';
-
 import { Origin } from '@growi/core';
 import { globalEventTarget } from '@growi/core/dist/utils';
 import type { MarkdownTable } from '@growi/editor';
 
-import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/client/components/Page/markdown-table-util-for-view';
+import {
+  getMarkdownTableFromLine,
+  replaceMarkdownTableInMarkdown,
+} from '~/client/components/Page/markdown-table-util-for-view';
 import type { LaunchHandsonTableModalEventDetail } from '~/client/interfaces/handsontable-modal';
-import { extractRemoteRevisionDataFromErrorObj, useUpdatePage } from '~/client/services/update-page';
-import { useCurrentPageData, useSetRemoteLatestPageData } from '~/states/page';
+import {
+  extractRemoteRevisionDataFromErrorObj,
+  useUpdatePage,
+} from '~/client/services/update-page';
 import type { RemoteRevisionData } from '~/states/page';
+import { useCurrentPageData, useSetRemoteLatestPageData } from '~/states/page';
 import { useShareLinkId } from '~/states/page/hooks';
 import { useConflictDiffModalActions } from '~/states/ui/modal/conflict-diff';
 import { useHandsontableModalActions } from '~/states/ui/modal/handsontable';
 import loggerFactory from '~/utils/logger';
 
-
-const logger = loggerFactory('growi:cli:side-effects:useHandsontableModalLauncherForView');
-
+const logger = loggerFactory(
+  'growi:cli:side-effects:useHandsontableModalLauncherForView',
+);
 
 export const useHandsontableModalLauncherForView = (opts?: {
-  onSaveSuccess?: () => void,
-  onSaveError?: (error: any) => void,
+  onSaveSuccess?: () => void;
+  onSaveError?: (error: any) => void;
 }): void => {
-
   const shareLinkId = useShareLinkId();
 
   const currentPage = useCurrentPageData();
 
   const { open: openHandsontableModal } = useHandsontableModalActions();
 
-  const { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModalActions();
+  const { open: openConflictDiffModal, close: closeConflictDiffModal } =
+    useConflictDiffModalActions();
 
   const _updatePage = useUpdatePage();
 
   const setRemoteLatestPageData = useSetRemoteLatestPageData();
 
   // eslint-disable-next-line max-len
-  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
-    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
-      return;
-    }
-
-    try {
-      // There are cases where "revisionId" is not required for revision updates
-      // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
-      await _updatePage({
-        pageId: currentPage._id,
-        revisionId,
-        body: newMarkdown,
-        origin: Origin.View,
-      });
-
-      closeConflictDiffModal();
-      opts?.onSaveSuccess?.();
-    }
-    catch (error) {
-      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
-      if (remoteRevidsionData != null) {
-        onConflict?.(remoteRevidsionData, newMarkdown);
+  const updatePage = useCallback(
+    async (
+      revisionId: string,
+      newMarkdown: string,
+      onConflict: (
+        conflictData: RemoteRevisionData,
+        newMarkdown: string,
+      ) => void,
+    ) => {
+      if (
+        currentPage == null ||
+        currentPage.revision == null ||
+        shareLinkId != null
+      ) {
+        return;
       }
 
-      logger.error('failed to save', error);
-      opts?.onSaveError?.(error);
-    }
-  }, [_updatePage, closeConflictDiffModal, currentPage, opts, shareLinkId]);
+      try {
+        // There are cases where "revisionId" is not required for revision updates
+        // See: https://dev.growi.org/651a6f4a008fee2f99187431#origin-%E3%81%AE%E5%BC%B7%E5%BC%B1
+        await _updatePage({
+          pageId: currentPage._id,
+          revisionId,
+          body: newMarkdown,
+          origin: Origin.View,
+        });
+
+        closeConflictDiffModal();
+        opts?.onSaveSuccess?.();
+      } catch (error) {
+        const remoteRevidsionData =
+          extractRemoteRevisionDataFromErrorObj(error);
+        if (remoteRevidsionData != null) {
+          onConflict?.(remoteRevidsionData, newMarkdown);
+        }
+
+        logger.error('failed to save', error);
+        opts?.onSaveError?.(error);
+      }
+    },
+    [_updatePage, closeConflictDiffModal, currentPage, opts, shareLinkId],
+  );
 
   // eslint-disable-next-line max-len
-  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
-    return async(newMarkdown: string) => {
-      await updatePage(revisionId, newMarkdown, onConflict);
-    };
-  }, [updatePage]);
-
-  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
-    setRemoteLatestPageData(remoteRevidsionData);
-
-    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
-    if (resolveConflictHandler == null) {
-      return;
-    }
-
-    openConflictDiffModal(newMarkdown, resolveConflictHandler);
-  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+  const generateResolveConflictHandler = useCallback(
+    (
+      revisionId: string,
+      onConflict: (
+        conflictData: RemoteRevisionData,
+        newMarkdown: string,
+      ) => void,
+    ) => {
+      return async (newMarkdown: string) => {
+        await updatePage(revisionId, newMarkdown, onConflict);
+      };
+    },
+    [updatePage],
+  );
+
+  const onConflictHandler = useCallback(
+    (remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+      setRemoteLatestPageData(remoteRevidsionData);
+
+      const resolveConflictHandler = generateResolveConflictHandler(
+        remoteRevidsionData.remoteRevisionId,
+        onConflictHandler,
+      );
+      if (resolveConflictHandler == null) {
+        return;
+      }
 
-  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || currentPage.revision == null) {
-      return;
-    }
+      openConflictDiffModal(newMarkdown, resolveConflictHandler);
+    },
+    [
+      generateResolveConflictHandler,
+      openConflictDiffModal,
+      setRemoteLatestPageData,
+    ],
+  );
+
+  const saveByHandsontableModal = useCallback(
+    async (table: MarkdownTable, bol: number, eol: number) => {
+      if (currentPage == null || currentPage.revision == null) {
+        return;
+      }
 
-    const currentRevisionId = currentPage.revision._id;
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
+      const currentRevisionId = currentPage.revision._id;
+      const currentMarkdown = currentPage.revision.body;
+      const newMarkdown = replaceMarkdownTableInMarkdown(
+        table,
+        currentMarkdown,
+        bol,
+        eol,
+      );
 
-    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
-  }, [currentPage, onConflictHandler, updatePage]);
+      await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+    },
+    [currentPage, onConflictHandler, updatePage],
+  );
 
   // set handler to open HandsonTableModal
   useEffect(() => {
@@ -107,12 +153,19 @@ export const useHandsontableModalLauncherForView = (opts?: {
       const markdown = currentPage.revision.body;
       const { bol, eol } = evt.detail;
       const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
-      openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));
+      openHandsontableModal(currentMarkdownTable, false, (table) =>
+        saveByHandsontableModal(table, bol, eol),
+      );
     };
     globalEventTarget.addEventListener('launchHandsonTableModal', handler);
 
     return function cleanup() {
       globalEventTarget.removeEventListener('launchHandsonTableModal', handler);
     };
-  }, [currentPage, openHandsontableModal, saveByHandsontableModal, shareLinkId]);
+  }, [
+    currentPage,
+    openHandsontableModal,
+    saveByHandsontableModal,
+    shareLinkId,
+  ]);
 };

+ 4 - 6
apps/app/src/client/services/side-effects/hash-changed.ts

@@ -1,9 +1,8 @@
 import { useCallback, useEffect } from 'react';
-
 import { useRouter } from 'next/router';
 
 import { useIsEditable } from '~/states/page';
-import { useEditorMode, determineEditorModeByHash } from '~/states/ui/editor';
+import { determineEditorModeByHash, useEditorMode } from '~/states/ui/editor';
 
 /**
  * Change editorMode by browser forward/back operation
@@ -34,13 +33,12 @@ export const useHashChangedEffect = (): void => {
     return function cleanup() {
       window.removeEventListener('hashchange', hashchangeHandler);
     };
-
   }, [hashchangeHandler, isEditable]);
 
   /*
-  * Route changes by Next Router
-  * https://nextjs.org/docs/api-reference/next/router
-  */
+   * Route changes by Next Router
+   * https://nextjs.org/docs/api-reference/next/router
+   */
   useEffect(() => {
     router.events.on('routeChangeComplete', hashchangeHandler);
 

+ 65 - 41
apps/app/src/client/services/side-effects/page-updated.ts

@@ -1,71 +1,95 @@
 import { useCallback, useEffect } from 'react';
 
 import { SocketEventName } from '~/interfaces/websocket';
-import { useCurrentPageData, useFetchCurrentPage, useSetRemoteLatestPageData } from '~/states/page';
 import type { RemoteRevisionData } from '~/states/page';
+import {
+  useCurrentPageData,
+  useFetchCurrentPage,
+  useSetRemoteLatestPageData,
+} from '~/states/page';
 import { useGlobalSocket } from '~/states/socket-io';
-import { useEditorMode, EditorMode } from '~/states/ui/editor';
+import { EditorMode, useEditorMode } from '~/states/ui/editor';
 import { usePageStatusAlertActions } from '~/states/ui/modal/page-status-alert';
 import { useSWRxPageInfo } from '~/stores/page';
 
-
 export const usePageUpdatedEffect = (): void => {
-
   const setRemoteLatestPageData = useSetRemoteLatestPageData();
 
   const socket = useGlobalSocket();
   const { editorMode } = useEditorMode();
   const currentPage = useCurrentPageData();
   const { fetchCurrentPage } = useFetchCurrentPage();
-  const { open: openPageStatusAlert, close: closePageStatusAlert } = usePageStatusAlertActions();
+  const { open: openPageStatusAlert, close: closePageStatusAlert } =
+    usePageStatusAlertActions();
 
   const { mutate: mutatePageInfo } = useSWRxPageInfo(currentPage?._id);
 
-  const remotePageDataUpdateHandler = useCallback((data) => {
-    // Set remote page data
-    const { s2cMessagePageUpdated } = data;
-
-    const remoteData: RemoteRevisionData = {
-      remoteRevisionId: s2cMessagePageUpdated.revisionId,
-      remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
-      remoteRevisionLastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
-      remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
-    };
-
-    if (currentPage?._id != null && currentPage._id === s2cMessagePageUpdated.pageId) {
-      setRemoteLatestPageData(remoteData);
-
-      // Update PageInfo cache
-      mutatePageInfo();
-
-      // Open PageStatusAlert
-      const currentRevisionId = currentPage?.revision?._id;
-      const remoteRevisionId = s2cMessagePageUpdated.revisionId;
-      const isRevisionOutdated = (currentRevisionId != null || remoteRevisionId != null) && currentRevisionId !== remoteRevisionId;
-
-      // !!CAUTION!! Timing of calling openPageStatusAlert may clash with components/PageEditor/conflict.tsx
-      if (isRevisionOutdated && editorMode === EditorMode.View) {
-        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: () => fetchCurrentPage({ force: true }) });
-      }
-
-      // Clear cache
-      if (!isRevisionOutdated) {
-        closePageStatusAlert();
+  const remotePageDataUpdateHandler = useCallback(
+    (data) => {
+      // Set remote page data
+      const { s2cMessagePageUpdated } = data;
+
+      const remoteData: RemoteRevisionData = {
+        remoteRevisionId: s2cMessagePageUpdated.revisionId,
+        remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
+        remoteRevisionLastUpdateUser:
+          s2cMessagePageUpdated.remoteLastUpdateUser,
+        remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
+      };
+
+      if (
+        currentPage?._id != null &&
+        currentPage._id === s2cMessagePageUpdated.pageId
+      ) {
+        setRemoteLatestPageData(remoteData);
+
+        // Update PageInfo cache
+        mutatePageInfo();
+
+        // Open PageStatusAlert
+        const currentRevisionId = currentPage?.revision?._id;
+        const remoteRevisionId = s2cMessagePageUpdated.revisionId;
+        const isRevisionOutdated =
+          (currentRevisionId != null || remoteRevisionId != null) &&
+          currentRevisionId !== remoteRevisionId;
+
+        // !!CAUTION!! Timing of calling openPageStatusAlert may clash with components/PageEditor/conflict.tsx
+        if (isRevisionOutdated && editorMode === EditorMode.View) {
+          openPageStatusAlert({
+            hideEditorMode: EditorMode.Editor,
+            onRefleshPage: () => fetchCurrentPage({ force: true }),
+          });
+        }
+
+        // Clear cache
+        if (!isRevisionOutdated) {
+          closePageStatusAlert();
+        }
       }
-    }
-  // eslint-disable-next-line max-len
-  }, [currentPage?._id, currentPage?.revision?._id, setRemoteLatestPageData, mutatePageInfo, editorMode, openPageStatusAlert, fetchCurrentPage, closePageStatusAlert]);
+      // eslint-disable-next-line max-len
+    },
+    [
+      currentPage?._id,
+      currentPage?.revision?._id,
+      setRemoteLatestPageData,
+      mutatePageInfo,
+      editorMode,
+      openPageStatusAlert,
+      fetchCurrentPage,
+      closePageStatusAlert,
+    ],
+  );
 
   // listen socket for someone updating this page
   useEffect(() => {
-
-    if (socket == null) { return }
+    if (socket == null) {
+      return;
+    }
 
     socket.on(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
 
     return () => {
       socket.off(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
     };
-
   }, [remotePageDataUpdateHandler, socket]);
 };

+ 1 - 1
apps/app/src/client/services/side-effects/use-sticky.ts

@@ -1,4 +1,4 @@
-import { useState, useEffect } from 'react';
+import { useEffect, useState } from 'react';
 
 // Custom hook that accepts a selector string as an argument
 // and returns a boolean indicating whether the selected element is currently sticky.

+ 3 - 2
apps/app/src/client/services/update-page/conflict.tsx

@@ -3,10 +3,11 @@ import type { ErrorV3 } from '@growi/core/dist/models';
 import { PageUpdateErrorCode } from '~/interfaces/apiv3';
 import type { RemoteRevisionData } from '~/states/page';
 
-export const extractRemoteRevisionDataFromErrorObj = (errors: Array<ErrorV3>): RemoteRevisionData | undefined => {
+export const extractRemoteRevisionDataFromErrorObj = (
+  errors: Array<ErrorV3>,
+): RemoteRevisionData | undefined => {
   for (const error of errors) {
     if (error.code === PageUpdateErrorCode.CONFLICT) {
-
       const latestRevision = error.args.returnLatestRevision;
 
       const remoteRevidsionData = {

+ 7 - 2
apps/app/src/client/services/update-page/update-page.ts

@@ -1,7 +1,12 @@
 import { apiv3Put } from '~/client/util/apiv3-client';
-import type { IApiv3PageUpdateParams, IApiv3PageUpdateResponse } from '~/interfaces/apiv3';
+import type {
+  IApiv3PageUpdateParams,
+  IApiv3PageUpdateResponse,
+} from '~/interfaces/apiv3';
 
-export const updatePage = async(params: IApiv3PageUpdateParams): Promise<IApiv3PageUpdateResponse> => {
+export const updatePage = async (
+  params: IApiv3PageUpdateParams,
+): Promise<IApiv3PageUpdateResponse> => {
   const res = await apiv3Put<IApiv3PageUpdateResponse>('/page', params);
   return res.data;
 };

+ 16 - 10
apps/app/src/client/services/update-page/use-update-page.tsx

@@ -1,25 +1,31 @@
 import { useCallback } from 'react';
 
-import type { IApiv3PageUpdateParams, IApiv3PageUpdateResponse } from '~/interfaces/apiv3';
+import type {
+  IApiv3PageUpdateParams,
+  IApiv3PageUpdateResponse,
+} from '~/interfaces/apiv3';
 import { useSetIsUntitledPage } from '~/states/page';
 
 import { updatePage } from './update-page';
 
-
-type UseUpdatePage = (params: IApiv3PageUpdateParams) => Promise<IApiv3PageUpdateResponse>;
-
+type UseUpdatePage = (
+  params: IApiv3PageUpdateParams,
+) => Promise<IApiv3PageUpdateResponse>;
 
 export const useUpdatePage = (): UseUpdatePage => {
   const setIsUntitledPage = useSetIsUntitledPage();
 
-  const updatePageExt: UseUpdatePage = useCallback(async (params) => {
-    const result = await updatePage(params);
+  const updatePageExt: UseUpdatePage = useCallback(
+    async (params) => {
+      const result = await updatePage(params);
 
-    // set false to isUntitledPage
-    setIsUntitledPage(false);
+      // set false to isUntitledPage
+      setIsUntitledPage(false);
 
-    return result;
-  }, [setIsUntitledPage]);
+      return result;
+    },
+    [setIsUntitledPage],
+  );
 
   return updatePageExt;
 };

+ 25 - 12
apps/app/src/client/services/upload-attachments/upload-attachments.ts

@@ -1,23 +1,33 @@
 import type { IAttachment } from '@growi/core';
 
 import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client';
-import type { IApiv3GetAttachmentLimitParams, IApiv3GetAttachmentLimitResponse, IApiv3PostAttachmentResponse } from '~/interfaces/apiv3/attachment';
+import type {
+  IApiv3GetAttachmentLimitParams,
+  IApiv3GetAttachmentLimitResponse,
+  IApiv3PostAttachmentResponse,
+} from '~/interfaces/apiv3/attachment';
 import loggerFactory from '~/utils/logger';
 
-
 const logger = loggerFactory('growi:client:services:upload-attachment');
 
-
 type UploadOpts = {
-  onUploaded?: (attachment: IAttachment) => void,
-  onError?: (error: Error, file: File) => void,
-}
+  onUploaded?: (attachment: IAttachment) => void;
+  onError?: (error: Error, file: File) => void;
+};
 
-export const uploadAttachments = async(pageId: string, files: File[], opts?: UploadOpts): Promise<void> => {
-  files.forEach(async(file) => {
+export const uploadAttachments = async (
+  pageId: string,
+  files: File[],
+  opts?: UploadOpts,
+): Promise<void> => {
+  files.forEach(async (file) => {
     try {
       const params: IApiv3GetAttachmentLimitParams = { fileSize: file.size };
-      const { data: resLimit } = await apiv3Get<IApiv3GetAttachmentLimitResponse>('/attachment/limit', params);
+      const { data: resLimit } =
+        await apiv3Get<IApiv3GetAttachmentLimitResponse>(
+          '/attachment/limit',
+          params,
+        );
 
       if (!resLimit.isUploadable) {
         throw new Error(resLimit.errorMessage);
@@ -27,11 +37,14 @@ export const uploadAttachments = async(pageId: string, files: File[], opts?: Upl
       formData.append('file', file);
       formData.append('page_id', pageId);
 
-      const { data: resAdd } = await apiv3PostForm<IApiv3PostAttachmentResponse>('/attachment', formData);
+      const { data: resAdd } =
+        await apiv3PostForm<IApiv3PostAttachmentResponse>(
+          '/attachment',
+          formData,
+        );
 
       opts?.onUploaded?.(resAdd.attachment);
-    }
-    catch (e) {
+    } catch (e) {
       logger.error('failed to upload', e);
       opts?.onError?.(e, file);
     }

+ 4 - 4
apps/app/src/client/services/use-print-mode.ts

@@ -1,5 +1,4 @@
 import { useEffect, useState } from 'react';
-
 import { flushSync } from 'react-dom';
 
 export const usePrintMode = (): boolean => {
@@ -7,9 +6,10 @@ export const usePrintMode = (): boolean => {
 
   useEffect(() => {
     // force re-render on beforeprint
-    const handleBeforePrint = () => flushSync(() => {
-      setIsPrinting(true);
-    });
+    const handleBeforePrint = () =>
+      flushSync(() => {
+        setIsPrinting(true);
+      });
 
     const handleAfterPrint = () => {
       setIsPrinting(false);

+ 23 - 22
apps/app/src/client/services/use-start-editing.tsx

@@ -1,11 +1,10 @@
 import { useCallback } from 'react';
-
 import { Origin } from '@growi/core';
 import { getParentPath } from '@growi/core/dist/utils/path-utils';
 
 import { useCreatePage } from '~/client/services/create-page';
 import { usePageNotFound } from '~/states/page';
-import { useEditorMode, EditorMode } from '~/states/ui/editor';
+import { EditorMode, useEditorMode } from '~/states/ui/editor';
 
 import { shouldCreateWipPage } from '../../utils/should-create-wip-page';
 
@@ -14,25 +13,27 @@ export const useStartEditing = (): ((path?: string) => Promise<void>) => {
   const { setEditorMode } = useEditorMode();
   const { create } = useCreatePage();
 
-  return useCallback(async (path?: string) => {
-    if (!isNotFound) {
-      setEditorMode(EditorMode.Editor);
-      return;
-    }
-    // Create a new page if it does not exist and transit to the editor mode
-    try {
-      const parentPath = path != null ? getParentPath(path) : undefined; // does not have to exist
-      await create(
-        {
-          path, parentPath, wip: shouldCreateWipPage(path), origin: Origin.View,
-        },
-      );
-
-      setEditorMode(EditorMode.Editor);
-    }
-    catch (err) {
-      throw new Error(err);
-    }
-  }, [create, isNotFound, setEditorMode]);
+  return useCallback(
+    async (path?: string) => {
+      if (!isNotFound) {
+        setEditorMode(EditorMode.Editor);
+        return;
+      }
+      // Create a new page if it does not exist and transit to the editor mode
+      try {
+        const parentPath = path != null ? getParentPath(path) : undefined; // does not have to exist
+        await create({
+          path,
+          parentPath,
+          wip: shouldCreateWipPage(path),
+          origin: Origin.View,
+        });
 
+        setEditorMode(EditorMode.Editor);
+      } catch (err) {
+        throw new Error(err);
+      }
+    },
+    [create, isNotFound, setEditorMode],
+  );
 };

+ 13 - 10
apps/app/src/client/services/use-toastr-on-error.tsx

@@ -1,18 +1,21 @@
 import { useCallback } from 'react';
-
 import { useTranslation } from 'react-i18next';
 
 import { toastError } from '~/client/util/toastr';
 
-export const useToastrOnError = <P, R>(method?: (param?: P) => Promise<R|undefined>): (param?: P) => Promise<R|undefined> => {
+export const useToastrOnError = <P, R>(
+  method?: (param?: P) => Promise<R | undefined>,
+): ((param?: P) => Promise<R | undefined>) => {
   const { t } = useTranslation('commons');
 
-  return useCallback(async(param) => {
-    try {
-      return await method?.(param);
-    }
-    catch (err) {
-      toastError(t('toaster.create_failed', { target: 'a page' }));
-    }
-  }, [method, t]);
+  return useCallback(
+    async (param) => {
+      try {
+        return await method?.(param);
+      } catch (err) {
+        toastError(t('toaster.create_failed', { target: 'a page' }));
+      }
+    },
+    [method, t],
+  );
 };

+ 16 - 5
apps/app/src/client/services/user-ui-settings.ts

@@ -6,8 +6,12 @@ import { apiv3Put } from '~/client/util/apiv3-client';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
 
 let settingsForBulk: Partial<IUserUISettings> = {};
-const _putUserUISettingsInBulk = (): Promise<AxiosResponse<IUserUISettings>> => {
-  const result = apiv3Put<IUserUISettings>('/user-ui-settings', { settings: settingsForBulk });
+const _putUserUISettingsInBulk = (): Promise<
+  AxiosResponse<IUserUISettings>
+> => {
+  const result = apiv3Put<IUserUISettings>('/user-ui-settings', {
+    settings: settingsForBulk,
+  });
 
   // clear partial
   settingsForBulk = {};
@@ -15,7 +19,10 @@ const _putUserUISettingsInBulk = (): Promise<AxiosResponse<IUserUISettings>> =>
   return result;
 };
 
-const _putUserUISettingsInBulkDebounced = debounce(1500, _putUserUISettingsInBulk);
+const _putUserUISettingsInBulkDebounced = debounce(
+  1500,
+  _putUserUISettingsInBulk,
+);
 
 export const scheduleToPut = (settings: Partial<IUserUISettings>): void => {
   settingsForBulk = {
@@ -26,8 +33,12 @@ export const scheduleToPut = (settings: Partial<IUserUISettings>): void => {
   _putUserUISettingsInBulkDebounced();
 };
 
-export const updateUserUISettings = async(settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
-  const result = await apiv3Put<IUserUISettings>('/user-ui-settings', { settings });
+export const updateUserUISettings = async (
+  settings: Partial<IUserUISettings>,
+): Promise<AxiosResponse<IUserUISettings>> => {
+  const result = await apiv3Put<IUserUISettings>('/user-ui-settings', {
+    settings,
+  });
 
   return result;
 };

+ 1 - 2
biome.json

@@ -28,8 +28,7 @@
       "!apps/slackbot-proxy/src/public/bootstrap",
       "!packages/pdf-converter-client/src/index.ts",
       "!packages/pdf-converter-client/specs",
-      "!apps/app/src/client/components",
-      "!apps/app/src/client/services"
+      "!apps/app/src/client/components"
     ]
   },
   "formatter": {