Browse Source

Merge remote-tracking branch 'origin/master' into fix/128037-fix-lightbox

WNomunomu 2 years ago
parent
commit
b5d30e540d
100 changed files with 435 additions and 645 deletions
  1. 0 7
      .github/release-drafter-dev-6.2.x.yml
  2. 0 7
      .github/release-drafter-master.yml
  3. 6 0
      .github/release-drafter.yml
  4. 2 0
      .github/workflows/ci-app-prod.yml
  5. 3 3
      .github/workflows/ci-app.yml
  6. 3 3
      .github/workflows/ci-slackbot-proxy.yml
  7. 11 9
      .github/workflows/draft-release.yml
  8. 1 1
      .github/workflows/release-slackbot-proxy.yml
  9. 6 6
      .github/workflows/release.yml
  10. 3 3
      .github/workflows/reusable-app-prod.yml
  11. 1 1
      .github/workflows/reusable-app-reg-suit.yml
  12. 16 1
      CHANGELOG.md
  13. 14 1
      apps/app/.eslintrc.js
  14. 3 2
      apps/app/bin/download-cdn-resources.ts
  15. 1 1
      apps/app/bin/github-actions/update-readme.sh
  16. 2 0
      apps/app/config/logger/config.dev.js
  17. 2 1
      apps/app/config/next-i18next.config.js
  18. 6 5
      apps/app/docker/README.md
  19. 6 8
      apps/app/package.json
  20. 9 1
      apps/app/public/static/locales/en_US/admin.json
  21. 9 1
      apps/app/public/static/locales/ja_JP/admin.json
  22. 9 1
      apps/app/public/static/locales/zh_CN/admin.json
  23. 1 3
      apps/app/src/client/models/Linker.js
  24. 1 1
      apps/app/src/client/services/AdminAppContainer.js
  25. 1 1
      apps/app/src/client/services/AdminCustomizeContainer.js
  26. 1 1
      apps/app/src/client/services/AdminExternalAccountsContainer.js
  27. 11 1
      apps/app/src/client/services/AdminGeneralSecurityContainer.js
  28. 1 2
      apps/app/src/client/services/AdminGitHubSecurityContainer.js
  29. 1 2
      apps/app/src/client/services/AdminGoogleSecurityContainer.js
  30. 2 2
      apps/app/src/client/services/AdminHomeContainer.js
  31. 1 1
      apps/app/src/client/services/AdminImportContainer.js
  32. 1 1
      apps/app/src/client/services/AdminLdapSecurityContainer.js
  33. 1 1
      apps/app/src/client/services/AdminLocalSecurityContainer.js
  34. 1 1
      apps/app/src/client/services/AdminMarkDownContainer.js
  35. 1 1
      apps/app/src/client/services/AdminNotificationContainer.js
  36. 1 2
      apps/app/src/client/services/AdminOidcSecurityContainer.js
  37. 1 2
      apps/app/src/client/services/AdminSamlSecurityContainer.js
  38. 1 1
      apps/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  39. 1 1
      apps/app/src/client/services/AdminUsersContainer.js
  40. 2 1
      apps/app/src/client/services/layout.ts
  41. 2 2
      apps/app/src/client/services/renderer/renderer.tsx
  42. 1 1
      apps/app/src/client/services/user-ui-settings.ts
  43. 1 1
      apps/app/src/client/util/bookmark-utils.ts
  44. 1 2
      apps/app/src/client/util/toastr.ts
  45. 32 28
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  46. 6 4
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  47. 8 3
      apps/app/src/components/Admin/AuditLog/ActivityTable.tsx
  48. 0 4
      apps/app/src/components/Admin/Common/AdminInstallButtonRow.tsx
  49. 18 18
      apps/app/src/components/Admin/Common/AdminNavigation.tsx
  50. 2 6
      apps/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  51. 11 3
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  52. 11 3
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  53. 1 1
      apps/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx
  54. 2 2
      apps/app/src/components/Admin/Customize/CustomizeTitle.tsx
  55. 1 1
      apps/app/src/components/Admin/Customize/ThemeColorBox.tsx
  56. 3 23
      apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx
  57. 4 31
      apps/app/src/components/Admin/ExportArchiveDataPage.tsx
  58. 17 0
      apps/app/src/components/Admin/ForbiddenPage.tsx
  59. 5 5
      apps/app/src/components/Admin/G2GDataTransfer.tsx
  60. 7 7
      apps/app/src/components/Admin/G2GDataTransferExportForm.tsx
  61. 0 1
      apps/app/src/components/Admin/ImportData/GrowiArchive/UploadForm.jsx
  62. 3 21
      apps/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx
  63. 1 1
      apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  64. 25 23
      apps/app/src/components/Admin/ManageExternalAccount.tsx
  65. 18 10
      apps/app/src/components/Admin/Notification/GlobalNotification.jsx
  66. 9 22
      apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx
  67. 1 1
      apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx
  68. 1 1
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  69. 1 1
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  70. 1 1
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  71. 0 2
      apps/app/src/components/Admin/Security/SecurityManagement.tsx
  72. 22 0
      apps/app/src/components/Admin/Security/SecuritySetting.jsx
  73. 0 1
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  74. 9 21
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  75. 1 3
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  76. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  77. 1 2
      apps/app/src/components/Admin/UserGroup/UserGroupDropdown.tsx
  78. 1 2
      apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx
  79. 1 3
      apps/app/src/components/Admin/UserGroup/UserGroupModal.tsx
  80. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  81. 2 2
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  82. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx
  83. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  84. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserModal.tsx
  85. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx
  86. 10 5
      apps/app/src/components/Admin/Users/PasswordResetModal.jsx
  87. 1 2
      apps/app/src/components/Admin/Users/UserInviteModal.jsx
  88. 1 1
      apps/app/src/components/Admin/Users/UserMenu.tsx
  89. 1 1
      apps/app/src/components/Admin/Users/UserTable.tsx
  90. 0 243
      apps/app/src/components/ArchiveCreateModal.jsx
  91. 9 3
      apps/app/src/components/BookmarkButtons.tsx
  92. 12 14
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  93. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  94. 4 4
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  95. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx
  96. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx
  97. 5 5
      apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx
  98. 4 3
      apps/app/src/components/Bookmarks/BookmarkItem.tsx
  99. 1 1
      apps/app/src/components/Bookmarks/DragAndDropWrapper.tsx
  100. 2 1
      apps/app/src/components/Comments.tsx

+ 0 - 7
.github/release-drafter-dev-6.2.x.yml

@@ -1,7 +0,0 @@
-_extends: growi:.github/release-drafter.yml
-
-prerelease: true
-
-# Filter previous releases to consider only those with the tags starts with 'v6.2'
-include-pre-releases: true
-tag-prefix: v6.2

+ 0 - 7
.github/release-drafter-master.yml

@@ -1,7 +0,0 @@
-_extends: growi:.github/release-drafter.yml
-
-prerelease: true
-
-# Filter previous releases to consider only those with the master branch
-include-pre-releases: true
-filter-by-commitish: true

+ 6 - 0
.github/release-drafter.yml

@@ -1,3 +1,9 @@
+prerelease: true
+
+# Filter previous releases to consider target_commitish
+include-pre-releases: true
+filter-by-commitish: true
+
 categories:
   - title: 'BREAKING CHANGES'
     labels:

+ 2 - 0
.github/workflows/ci-app-prod.yml

@@ -4,6 +4,7 @@ on:
   push:
     branches:
       - master
+      - dev/7.*.x
       - dev/6.*.x
     paths:
       - .github/workflows/ci-app-prod.yml
@@ -19,6 +20,7 @@ on:
   pull_request:
     branches:
       - master
+      - dev/7.*.x
       - dev/6.*.x
     types: [opened, reopened, synchronize]
     paths:

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

@@ -60,7 +60,7 @@ jobs:
 
       - name: Install dependencies
         run: |
-          yarn global add turbo@1.10.9
+          yarn global add turbo
           yarn --frozen-lockfile
 
       - name: Lint
@@ -131,7 +131,7 @@ jobs:
 
       - name: Install dependencies
         run: |
-          yarn global add turbo@1.10.9
+          yarn global add turbo
           yarn --frozen-lockfile
 
       - name: Test
@@ -213,7 +213,7 @@ jobs:
 
       - name: Install dependencies
         run: |
-          yarn global add turbo@1.10.9
+          yarn global add turbo
           yarn --frozen-lockfile
 
       - name: turbo run dev:ci

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

@@ -62,7 +62,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
 
     - name: Lint
@@ -136,7 +136,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
 
     - name: yarn dev:ci
@@ -200,7 +200,7 @@ jobs:
 
     - name: Install turbo
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
     - name: Prune repositories
       run: |

+ 11 - 9
.github/workflows/draft-release.yml

@@ -29,17 +29,10 @@ jobs:
         uses: myrotvorets/info-from-package-json-action@1.2.0
         id: package-json
 
-      - name: Determine config file
-        id: determine-config-name
-        run: |
-          BRANCH_NAME="${{ github.ref_name }}"
-          BRANCH_NAME_REPLACED=${BRANCH_NAME/\//-}
-          echo "value=release-drafter-$BRANCH_NAME_REPLACED.yml" >> $GITHUB_OUTPUT
-
       - uses: release-drafter/release-drafter@v5
         id: release-drafter
         with:
-          config-name: ${{ steps.determine-config-name.outputs.value }}
+          config-name: release-drafter.yml
           name: v${{ steps.package-json.outputs.packageVersion }}
           tag: v${{ steps.package-json.outputs.packageVersion }}
           version: ${{ steps.package-json.outputs.packageVersion }}
@@ -64,11 +57,20 @@ jobs:
           RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
           echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
 
+      - name: Get base branch
+        id: base-branch
+        run: |
+          GITHUB_REF_NAME=${{ github.ref_name }}
+          WILDCARD_VERSION=${GITHUB_REF_NAME#dev/}
+          # set "release/current" or "release/X.X.x" to BASE_BRANCH
+          BASE_BRANCH=release/${{ github.ref_name == 'master' && 'current' || '$WILDCARD_VERSION' }}
+          echo "BASE_BRANCH=$BASE_BRANCH" >> $GITHUB_OUTPUT
+
       - name: Create/Update Pull Request
         uses: bakunyo/git-pr-release-action@master
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-          GIT_PR_RELEASE_BRANCH_PRODUCTION: release/current
+          GIT_PR_RELEASE_BRANCH_PRODUCTION: ${{ steps.base-branch.outputs.BASE_BRANCH }}
           GIT_PR_RELEASE_BRANCH_STAGING: ${{ github.ref_name }}
           GIT_PR_RELEASE_TEMPLATE: .github/git-pr-release-template.erb
           GIT_PR_RELEASE_TITLE: Release v${{ steps.release-version.outputs.RELEASE_VERSION }}

+ 1 - 1
.github/workflows/release-slackbot-proxy.yml

@@ -108,7 +108,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
 
     - name: Bump versions for next RC

+ 6 - 6
.github/workflows/release.yml

@@ -30,7 +30,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
 
     - name: Bump versions
@@ -63,11 +63,11 @@ jobs:
         commit_message: Release v${{ steps.package-json.outputs.packageVersion }}
         tagging_message: v${{ steps.package-json.outputs.packageVersion }}
 
-    - uses: ncipollo/release-action@v1
+    - uses: softprops/action-gh-release@v1
       with:
         body: ${{ github.event.pull_request.body }}
-        tag: v${{ steps.package-json.outputs.packageVersion }}
-        token: ${{ secrets.GITHUB_TOKEN }}
+        tag_name: v${{ steps.package-json.outputs.packageVersion }}
+        target_commitish: ${{ github.head_ref }}
 
     - name: Delete drafts
       uses: hugo19941994/delete-draft-releases@v1.0.1
@@ -93,7 +93,7 @@ jobs:
 
     - name: Install dependencies
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
 
     - name: Bump versions for next RC
@@ -118,7 +118,7 @@ jobs:
       uses: repo-sync/pull-request@v2
       with:
         source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
-        destination_branch: master
+        destination_branch: ${{ github.head_ref }}
         pr_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
         pr_label: flag/exclude-from-changelog,type/prepare-next-version
         pr_body: "[skip ci] An automated PR generated by create-pr-for-next-rc"

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

@@ -36,7 +36,7 @@ jobs:
 
     - name: Install turbo
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
     - name: Prune repositories
       run: |
@@ -147,7 +147,7 @@ jobs:
 
     - name: Install turbo
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
     - name: Prune repositories
       run: |
@@ -238,7 +238,7 @@ jobs:
 
     - name: Install turbo
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
     - name: Prune repositories
       run: |

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

@@ -62,7 +62,7 @@ jobs:
 
     - name: Install turbo
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
     - name: Prune repositories
       run: |

+ 16 - 1
CHANGELOG.md

@@ -1,9 +1,24 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.1.10...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.1.12...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.1.12](https://github.com/weseek/growi/compare/v6.1.11...v6.1.12) - 2023-08-14
+
+### 🐛 Bug Fixes
+
+- fix: Consider an empty page when renaming and duplicating (v6.1.x) (#7980) @yuki-takei
+- fix: Do not work image tag properties (#7977) @jam411
+
+## [v6.1.11](https://github.com/weseek/growi/compare/v6.1.10...v6.1.11) - 2023-08-07
+
+### 🐛 Bug Fixes
+
+- fix: Admin page permission when the user transit with next-routing (#7908) @WNomunomu
+- fix: Transitioning to a non-existent page under "/me" results in a 500 error (#7946) @miya
+- fix: Auto-scroll search result content 2 (#7943) @yuki-takei
+
 ## [v6.1.10](https://github.com/weseek/growi/compare/v6.1.9...v6.1.10) - 2023-08-01
 
 ### 🐛 Bug Fixes

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

@@ -1,6 +1,7 @@
 module.exports = {
   extends: [
     'next/core-web-vitals',
+    'weseek/react',
   ],
   plugins: [
     'regex',
@@ -32,11 +33,23 @@ module.exports = {
     '@typescript-eslint/no-this-alias': ['warn'],
   },
   overrides: [
+    {
+      // enable the rule specifically for JavaScript files
+      files: ['*.js', '*.jsx'],
+      rules: {
+        // set 'warn' temporarily -- 2023.08.14 Yuki Takei
+        'react/prop-types': 'warn',
+        // set 'warn' temporarily -- 2023.08.14 Yuki Takei
+        'no-unused-vars': ['warn'],
+      },
+    },
     {
       // enable the rule specifically for TypeScript files
       files: ['*.ts', '*.tsx'],
       rules: {
-        // '@typescript-eslint/explicit-module-boundary-types': ['error'],
+        'no-unused-vars': 'off',
+        // set 'warn' temporarily -- 2023.08.14 Yuki Takei
+        'react/prop-types': 'warn',
         // set 'warn' temporarily -- 2022.07.25 Yuki Takei
         '@typescript-eslint/explicit-module-boundary-types': ['warn'],
       },

+ 3 - 2
apps/app/bin/download-cdn-resources.ts

@@ -3,11 +3,12 @@
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
-import { envUtils } from '@growi/core';
+import { envUtils } from '@growi/core/dist/utils';
 
-import CdnResourcesDownloader from './cdn/cdn-resources-downloader';
 import loggerFactory from '../src/utils/logger';
 
+import CdnResourcesDownloader from './cdn/cdn-resources-downloader';
+
 const logger = loggerFactory('growi:bin:download-cdn-resources');
 
 // check env var

+ 1 - 1
apps/app/bin/github-actions/update-readme.sh

@@ -2,4 +2,4 @@
 
 cd docker
 
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`6\.1\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`6\.2\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md

+ 2 - 0
apps/app/config/logger/config.dev.js

@@ -29,6 +29,8 @@ module.exports = {
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:g2g-transfer': 'debug',
 
+  'growi:migration:add-installed-date-to-config': 'debug',
+
   /*
    * configure level for client
    */

+ 2 - 1
apps/app/config/next-i18next.config.js

@@ -1,6 +1,7 @@
 const path = require('path');
 
-const { isServer, AllLang, Lang } = require('@growi/core');
+const { AllLang, Lang } = require('@growi/core');
+const { isServer } = require('@growi/core/dist/utils');
 const I18nextChainedBackend = require('i18next-chained-backend').default;
 const I18NextHttpBackend = require('i18next-http-backend');
 const I18NextLocalStorageBackend = require('i18next-localstorage-backend').default;

+ 6 - 5
apps/app/docker/README.md

@@ -10,11 +10,12 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`6.1.0`, `6.1`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.0/apps/app/docker/Dockerfile)
-* [`6.0.15`, `6.0`](https://github.com/weseek/growi/blob/v6.0.15/packages/app/docker/Dockerfile)
-* [`5.1.7`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
-* [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
-* [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
+* [`6.2.0`, `6.2`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.0/apps/app/docker/Dockerfile)
+* [`6.1.0`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.8/apps/app/docker/Dockerfile)
+* [`6.0.15`, `6.0` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.15/packages/app/docker/Dockerfile)
+* [`5.1.7`, `5.1`, `5` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
+* [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
+* [`4.5.23`, `4.5`, `4` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 * [`4.5.23-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 
 

+ 6 - 8
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.1.11-RC.0",
+  "version": "6.2.0-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -115,7 +115,7 @@
     "extensible-custom-error": "^0.0.7",
     "form-data": "^4.0.0",
     "graceful-fs": "^4.1.11",
-    "hast-util-select": "^5.0.2",
+    "hast-util-select": "^5.0.5",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
     "i18next": "^22.4.10",
@@ -195,8 +195,7 @@
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
     "swr": "^2.0.3",
-    "throttle-debounce": "^3.0.1",
-    "toastr": "^2.1.2",
+    "throttle-debounce": "^5.0.0",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
     "unstated": "^2.1.1",
@@ -227,7 +226,6 @@
     "bootstrap": "^4.6.1",
     "codemirror": "^5.64.0",
     "connect-browser-sync": "^2.1.0",
-    "core-js": "=2.6.9",
     "diff2html": "^3.4.35",
     "eazy-logger": "^3.1.0",
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
@@ -241,8 +239,7 @@
     "jest": "^29.5.0",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
-    "jquery-slimscroll": "^1.3.8",
-    "jquery.cookie": "~1.4.1",
+    "jquery": "^3.7.0",
     "load-css-file": "^1.0.0",
     "material-icons": "^1.11.3",
     "mongodb-memory-server": "^8.12.2",
@@ -250,6 +247,7 @@
     "null-loader": "^4.0.1",
     "penpal": "^4.0.0",
     "plantuml-encoder": "^1.2.5",
+    "popper.js": "^1.16.1",
     "prettier": "^1.19.1",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
@@ -263,7 +261,7 @@
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.2.0",
     "source-map-loader": "^4.0.1",
-    "swagger2openapi": "^5.3.1",
+    "swagger2openapi": "^7.0.8",
     "tsc-alias": "^1.2.9"
   }
 }

+ 9 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -3,7 +3,7 @@
     "display_name": "English"
   },
   "last_login": "Last login",
-  "wiki_management_home_page": "Wiki Management Home Page",
+  "wiki_management_homepage": "Wiki Management Homepage",
   "public": "Public",
   "anyone_with_the_link": "Anyone with the link",
   "specified_users": "Specified users",
@@ -45,6 +45,11 @@
     "admin_only": "Admin only",
     "admin_and_author": "Admin and author",
     "anyone": "Anyone",
+    "user_homepage_deletion": {
+      "user_homepage_deletion": "User homepage deletion",
+      "enable_user_homepage_deletion": "Enable user homepage deletion",
+      "when_deleting_a_user_the_user_homepage_is_also_deleted": "When deleting a user, the user homepage is also deleted."
+    },
     "session": "Session",
     "max_age": "Max age (msec)",
     "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
@@ -1055,5 +1060,8 @@
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
+  },
+  "forbidden_page": {
+    "do_not_have_admin_permission": "Users without administrative rights cannot access the administration screen."
   }
 }

+ 9 - 1
apps/app/public/static/locales/ja_JP/admin.json

@@ -11,7 +11,7 @@
   "Edit": "編集",
   "Description": "説明",
   "last_login": "最終ログイン",
-  "wiki_management_home_page": "Wiki管理トップ",
+  "wiki_management_homepage": "Wiki管理トップ",
   "public": "公開",
   "anyone_with_the_link": "リンクを知っている人のみ",
   "specified_users": "特定ユーザーのみ",
@@ -53,6 +53,11 @@
     "admin_only": "管理者のみ可能",
     "admin_and_author": "管理者とページ作者が可能",
     "anyone": "誰でも可能",
+    "user_homepage_deletion": {
+      "user_homepage_deletion": "ユーザーページの削除",
+      "enable_user_homepage_deletion": "ユーザーページの削除を有効化",
+      "when_deleting_a_user_the_user_homepage_is_also_deleted": "ユーザー削除時にユーザーページも削除します。"
+    },
     "session": "セッション",
     "max_age": "有効期間 (ミリ秒)",
     "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
@@ -1063,5 +1068,8 @@
     "activate_plugin_success": "{{pluginName}}を有効化しました",
     "deactivate_plugin_success": "{{pluginName}}を無効化しました",
     "remove_plugin_success": "{{pluginName}}を削除しました"
+  },
+  "forbidden_page": {
+    "do_not_have_admin_permission": "管理者権限のないユーザーでは管理画面にはアクセスできません。"
   }
 }

+ 9 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -11,7 +11,7 @@
   "Edit": "编辑",
   "Description": "描述",
   "last_login": "上次登录",
-  "wiki_management_home_page": "Wiki管理首页",
+  "wiki_management_homepage": "Wiki管理首页",
   "public": "公共",
   "anyone_with_the_link": "任何人",
   "specified_users": "仅指定用户",
@@ -53,6 +53,11 @@
 		"admin_only": "仅管理员",
 		"admin_and_author": "管理员|作者",
 		"anyone": "任何人",
+    "user_homepage_deletion": {
+      "user_homepage_deletion": "删除用户页面",
+      "enable_user_homepage_deletion": "启用删除用户页面",
+      "when_deleting_a_user_the_user_homepage_is_also_deleted": "当一个用户被删除时,用户页面也会被删除。"
+    },
     "session": "会议",
     "max_age": "有效期间  (msec)",
     "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
@@ -1063,5 +1068,8 @@
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
+  },
+  "forbidden_page": {
+    "do_not_have_admin_permission": "没有管理权限的用户无法访问管理屏幕。"
   }
 }

+ 1 - 3
apps/app/src/client/models/Linker.js

@@ -1,7 +1,5 @@
 
-import { pagePathUtils } from '@growi/core';
-
-const { encodeSpaces } = pagePathUtils;
+import { encodeSpaces } from '@growi/core/dist/utils/page-path-utils';
 
 export default class Linker {
 

+ 1 - 1
apps/app/src/client/services/AdminAppContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';

+ 1 - 1
apps/app/src/client/services/AdminCustomizeContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';

+ 1 - 1
apps/app/src/client/services/AdminExternalAccountsContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';

+ 11 - 1
apps/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import {
@@ -38,6 +38,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       expandOtherOptionsForCompleteDeletion: false,
       isShowRestrictedByOwner: false,
       isShowRestrictedByGroup: false,
+      isUsersHomepageDeletionEnabled: false,
       isLocalEnabled: false,
       isLdapEnabled: false,
       isSamlEnabled: false,
@@ -73,6 +74,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       currentPageRecursiveCompleteDeletionAuthority: generalSetting.pageRecursiveCompleteDeletionAuthority,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
+      isUsersHomepageDeletionEnabled: generalSetting.isUsersHomepageDeletionEnabled,
       sessionMaxAge: generalSetting.sessionMaxAge,
       wikiMode: generalSetting.wikiMode,
       disableLinkSharing: shareLinkSetting.disableLinkSharing,
@@ -193,6 +195,13 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ isShowRestrictedByGroup:  !this.state.isShowRestrictedByGroup });
   }
 
+  /**
+   * Switch isUsersHomepageDeletionEnabled
+   */
+  switchIsUsersHomepageDeletionEnabled() {
+    this.setState({ isUsersHomepageDeletionEnabled: !this.state.isUsersHomepageDeletionEnabled });
+  }
+
   /**
    * Update restrictGuestMode
    * @memberOf AdminGeneralSecuritySContainer
@@ -209,6 +218,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       pageRecursiveCompleteDeletionAuthority: this.state.currentPageRecursiveCompleteDeletionAuthority,
       hideRestrictedByGroup: !this.state.isShowRestrictedByGroup,
       hideRestrictedByOwner: !this.state.isShowRestrictedByOwner,
+      isUsersHomepageDeletionEnabled: this.state.isUsersHomepageDeletionEnabled,
     };
 
     requestParams = await removeNullPropertyFromObject(requestParams);

+ 1 - 2
apps/app/src/client/services/AdminGitHubSecurityContainer.js

@@ -1,6 +1,5 @@
-import { isServer, pathUtils } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
-import urljoin from 'url-join';
 
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';

+ 1 - 2
apps/app/src/client/services/AdminGoogleSecurityContainer.js

@@ -1,6 +1,5 @@
-import { isServer, pathUtils } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
-import urljoin from 'url-join';
 
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';

+ 2 - 2
apps/app/src/client/services/AdminHomeContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -9,7 +9,7 @@ import { apiv3Get } from '../util/apiv3-client';
 const logger = loggerFactory('growi:services:AdminHomeContainer');
 
 /**
- * Service container for admin home page (AdminHome.jsx)
+ * Service container for admin homepage (AdminHome.jsx)
  * @extends {Container} unstated Container
  */
 export default class AdminHomeContainer extends Container {

+ 1 - 1
apps/app/src/client/services/AdminImportContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';

+ 1 - 1
apps/app/src/client/services/AdminLdapSecurityContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';

+ 1 - 1
apps/app/src/client/services/AdminLocalSecurityContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';

+ 1 - 1
apps/app/src/client/services/AdminMarkDownContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';

+ 1 - 1
apps/app/src/client/services/AdminNotificationContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import {

+ 1 - 2
apps/app/src/client/services/AdminOidcSecurityContainer.js

@@ -1,6 +1,5 @@
-import { isServer, pathUtils } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
-import urljoin from 'url-join';
 
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';

+ 1 - 2
apps/app/src/client/services/AdminSamlSecurityContainer.js

@@ -1,6 +1,5 @@
-import { isServer, pathUtils } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
-import urljoin from 'url-join';
 
 import loggerFactory from '~/utils/logger';
 import { removeNullPropertyFromObject } from '~/utils/object-utils';

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

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';

+ 1 - 1
apps/app/src/client/services/AdminUsersContainer.js

@@ -1,4 +1,4 @@
-import { isServer } from '@growi/core';
+import { isServer } from '@growi/core/dist/utils';
 import { debounce } from 'throttle-debounce';
 import { Container } from 'unstated';
 

+ 2 - 1
apps/app/src/client/services/layout.ts

@@ -1,4 +1,5 @@
-import type { IPage } from '~/interfaces/page';
+import type { IPage } from '@growi/core';
+
 import { useIsContainerFluid } from '~/stores/context';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useEditorMode } from '~/stores/ui';

+ 2 - 2
apps/app/src/client/services/renderer/renderer.tsx

@@ -1,10 +1,10 @@
 import assert from 'assert';
 
 import { isClient } from '@growi/core/dist/utils/browser-utils';
-import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client/index.mjs';
+import * as refsGrowiDirective from '@growi/remark-attachment-refs/dist/client';
 import * as drawio from '@growi/remark-drawio';
 // eslint-disable-next-line import/extensions
-import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client/index.mjs';
+import * as lsxGrowiDirective from '@growi/remark-lsx/dist/client';
 import katex from 'rehype-katex';
 import sanitize from 'rehype-sanitize';
 import slug from 'rehype-slug';

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

@@ -15,7 +15,7 @@ const _putUserUISettingsInBulk = (): Promise<AxiosResponse<IUserUISettings>> =>
   return result;
 };
 
-const _putUserUISettingsInBulkDebounced = debounce(1500, false, _putUserUISettingsInBulk);
+const _putUserUISettingsInBulkDebounced = debounce(1500, _putUserUISettingsInBulk);
 
 type ScheduleToPutFunction = (settings: Partial<IUserUISettings>) => Promise<AxiosResponse<IUserUISettings>>;
 const scheduleToPut: ScheduleToPutFunction = (settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {

+ 1 - 1
apps/app/src/client/util/bookmark-utils.ts

@@ -1,4 +1,4 @@
-import { IRevision, Ref } from '@growi/core';
+import type { IRevision, Ref } from '@growi/core';
 
 import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 

+ 1 - 2
apps/app/src/client/util/toastr.ts

@@ -1,5 +1,4 @@
 import { toast, ToastContent, ToastOptions } from 'react-toastify';
-import * as toastrLegacy from 'toastr';
 
 import { toArrayIfNot } from '~/utils/array-utils';
 
@@ -34,5 +33,5 @@ export const toastWarningOption: ToastOptions = {
   closeButton: true,
 };
 export const toastWarning = (content: ToastContent, option: ToastOptions = toastWarningOption): void => {
-  toastrLegacy.warning(content, option);
+  toast.warning(content, option);
 };

+ 32 - 28
apps/app/src/components/Admin/App/FileUploadSetting.tsx

@@ -70,34 +70,38 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         )}
       </div>
 
-      {props.fileUploadType === 'aws' && <AwsSettingMolecule
-        s3ReferenceFileWithRelayMode={props.s3ReferenceFileWithRelayMode}
-        s3Region={props.s3Region}
-        s3CustomEndpoint={props.s3CustomEndpoint}
-        s3Bucket={props.s3Bucket}
-        s3AccessKeyId={props.s3AccessKeyId}
-        s3SecretAccessKey={props.s3SecretAccessKey}
-        onChangeS3ReferenceFileWithRelayMode={props.onChangeS3ReferenceFileWithRelayMode}
-        onChangeS3Region={props.onChangeS3Region}
-        onChangeS3CustomEndpoint={props.onChangeS3CustomEndpoint}
-        onChangeS3Bucket={props.onChangeS3Bucket}
-        onChangeS3AccessKeyId={props.onChangeS3AccessKeyId}
-        onChangeS3SecretAccessKey={props.onChangeS3SecretAccessKey}
-      />}
-      {props.fileUploadType === 'gcs' && <GcsSettingMolecule
-        gcsReferenceFileWithRelayMode={props.gcsReferenceFileWithRelayMode}
-        gcsUseOnlyEnvVars={props.gcsUseOnlyEnvVars}
-        gcsApiKeyJsonPath={props.gcsApiKeyJsonPath}
-        gcsBucket={props.gcsBucket}
-        gcsUploadNamespace={props.gcsUploadNamespace}
-        envGcsApiKeyJsonPath={props.envGcsApiKeyJsonPath}
-        envGcsBucket={props.envGcsBucket}
-        envGcsUploadNamespace={props.envGcsUploadNamespace}
-        onChangeGcsReferenceFileWithRelayMode={props.onChangeGcsReferenceFileWithRelayMode}
-        onChangeGcsApiKeyJsonPath={props.onChangeGcsApiKeyJsonPath}
-        onChangeGcsBucket={props.onChangeGcsBucket}
-        onChangeGcsUploadNamespace={props.onChangeGcsUploadNamespace}
-      />}
+      {props.fileUploadType === 'aws' && (
+        <AwsSettingMolecule
+          s3ReferenceFileWithRelayMode={props.s3ReferenceFileWithRelayMode}
+          s3Region={props.s3Region}
+          s3CustomEndpoint={props.s3CustomEndpoint}
+          s3Bucket={props.s3Bucket}
+          s3AccessKeyId={props.s3AccessKeyId}
+          s3SecretAccessKey={props.s3SecretAccessKey}
+          onChangeS3ReferenceFileWithRelayMode={props.onChangeS3ReferenceFileWithRelayMode}
+          onChangeS3Region={props.onChangeS3Region}
+          onChangeS3CustomEndpoint={props.onChangeS3CustomEndpoint}
+          onChangeS3Bucket={props.onChangeS3Bucket}
+          onChangeS3AccessKeyId={props.onChangeS3AccessKeyId}
+          onChangeS3SecretAccessKey={props.onChangeS3SecretAccessKey}
+        />
+      )}
+      {props.fileUploadType === 'gcs' && (
+        <GcsSettingMolecule
+          gcsReferenceFileWithRelayMode={props.gcsReferenceFileWithRelayMode}
+          gcsUseOnlyEnvVars={props.gcsUseOnlyEnvVars}
+          gcsApiKeyJsonPath={props.gcsApiKeyJsonPath}
+          gcsBucket={props.gcsBucket}
+          gcsUploadNamespace={props.gcsUploadNamespace}
+          envGcsApiKeyJsonPath={props.envGcsApiKeyJsonPath}
+          envGcsBucket={props.envGcsBucket}
+          envGcsUploadNamespace={props.envGcsUploadNamespace}
+          onChangeGcsReferenceFileWithRelayMode={props.onChangeGcsReferenceFileWithRelayMode}
+          onChangeGcsApiKeyJsonPath={props.onChangeGcsApiKeyJsonPath}
+          onChangeGcsBucket={props.onChangeGcsBucket}
+          onChangeGcsUploadNamespace={props.onChangeGcsUploadNamespace}
+        />
+      )}
     </>
   );
 });

+ 6 - 4
apps/app/src/components/Admin/App/QuestionnaireSettings.tsx

@@ -63,9 +63,11 @@ const QuestionnaireSettings = (): JSX.Element => {
         </span>
       </p>
 
-      {isLoading && <div className="text-muted text-center mb-5">
-        <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
-      </div>}
+      {isLoading && (
+        <div className="text-muted text-center mb-5">
+          <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
+        </div>
+      )}
 
       {!isLoading && (
         <>
@@ -103,7 +105,7 @@ const QuestionnaireSettings = (): JSX.Element => {
             </div>
           </div>
 
-          <AdminUpdateButtonRow onClick={onSubmitHandler}/>
+          <AdminUpdateButtonRow onClick={onSubmitHandler} />
         </>
       )}
     </div>

+ 8 - 3
apps/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,7 +1,7 @@
 import React, { FC, useState, useCallback } from 'react';
 
-import { pagePathUtils } from '@growi/core';
-import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
+import { pagePathUtils } from '@growi/core/dist/utils';
+import { UserPicture } from '@growi/ui/dist/components';
 import { format } from 'date-fns';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { useTranslation } from 'react-i18next';
@@ -48,7 +48,12 @@ export const ActivityTable : FC<Props> = (props: Props) => {
                   { activity.user != null && (
                     <>
                       <UserPicture user={activity.user} />
-                      <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
+                      <a
+                        className="ml-2"
+                        href={pagePathUtils.userHomepagePath(activity.user)}
+                      >
+                        {activity.snapshot?.username}
+                      </a>
                     </>
                   )}
                 </td>

+ 0 - 4
apps/app/src/components/Admin/Common/AdminInstallButtonRow.tsx

@@ -1,7 +1,5 @@
 import React from 'react';
 
-import { useTranslation } from 'next-i18next';
-
 type Props = {
   onClick: () => void,
   disabled: boolean,
@@ -9,8 +7,6 @@ type Props = {
 }
 
 export const AdminInstallButtonRow = (props: Props): JSX.Element => {
-  // TODO: const { t } = useTranslation('admin');
-
   return (
     <div className="row my-3">
       <div className="mx-auto">

+ 18 - 18
apps/app/src/components/Admin/Common/AdminNavigation.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import { pathUtils } from '@growi/core';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import urljoin from 'url-join';
@@ -29,7 +29,7 @@ const MenuLabel = ({ menu }: { menu: string }) => {
     case 'plugins':                  return <><i className="mr-1 icon-fw icon-puzzle"></i>{          t('plugins.plugins')}</>;
     case 'search':                   return <><i className="mr-1 icon-fw icon-magnifier"></i>{       t('full_text_search_management.full_text_search_management') }</>;
     case 'cloud':                    return <><i className="mr-1 icon-fw icon-share-alt"></i>{       t('cloud_setting_management.to_cloud_settings')} </>;
-    default:                         return <><i className="mr-1 icon-fw icon-home"></i>{            t('wiki_management_home_page') }</>;
+    default:                         return <><i className="mr-1 icon-fw icon-home"></i>{            t('wiki_management_homepage') }</>;
       /* eslint-enable no-multi-spaces, max-len */
   }
 };
@@ -86,22 +86,22 @@ export const AdminNavigation = (): JSX.Element => {
     return (
       <>
         {/* eslint-disable no-multi-spaces */}
-        <MenuLink menu="home"                       isListGroupItems={isListGroupItems} isActive={pathname === '/admin'} isRoot />
-        <MenuLink menu="app"                        isListGroupItems={isListGroupItems} isActive={isActiveMenu('/app')} />
-        <MenuLink menu="security"                   isListGroupItems={isListGroupItems} isActive={isActiveMenu('/security')} />
-        <MenuLink menu="markdown"                   isListGroupItems={isListGroupItems} isActive={isActiveMenu('/markdown')} />
-        <MenuLink menu="customize"                  isListGroupItems={isListGroupItems} isActive={isActiveMenu('/customize')} />
-        <MenuLink menu="importer"                   isListGroupItems={isListGroupItems} isActive={isActiveMenu('/importer')} />
-        <MenuLink menu="export"                     isListGroupItems={isListGroupItems} isActive={isActiveMenu('/export')} />
-        <MenuLink menu="data-transfer"              isListGroupItems={isListGroupItems} isActive={isActiveMenu('/data-transfer')} />
-        <MenuLink menu="notification"               isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/notification', '/global-notification'])} />
-        <MenuLink menu="slack-integration"          isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration')} />
-        <MenuLink menu="slack-integration-legacy"   isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration-legacy')} />
-        <MenuLink menu="users"                      isListGroupItems={isListGroupItems} isActive={isActiveMenu('/users')} />
-        <MenuLink menu="user-groups"                isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/user-groups', 'user-group-detail'])} />
-        <MenuLink menu="audit-log"                  isListGroupItems={isListGroupItems} isActive={isActiveMenu('/audit-log')} />
-        <MenuLink menu="plugins"                    isListGroupItems={isListGroupItems} isActive={isActiveMenu('/plugins')} />
-        <MenuLink menu="search"                     isListGroupItems={isListGroupItems} isActive={isActiveMenu('/search')} />
+        <MenuLink menu="home" isListGroupItems={isListGroupItems} isActive={pathname === '/admin'} isRoot />
+        <MenuLink menu="app" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/app')} />
+        <MenuLink menu="security" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/security')} />
+        <MenuLink menu="markdown" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/markdown')} />
+        <MenuLink menu="customize" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/customize')} />
+        <MenuLink menu="importer" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/importer')} />
+        <MenuLink menu="export" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/export')} />
+        <MenuLink menu="data-transfer" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/data-transfer')} />
+        <MenuLink menu="notification" isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/notification', '/global-notification'])} />
+        <MenuLink menu="slack-integration" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration')} />
+        <MenuLink menu="slack-integration-legacy" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/slack-integration-legacy')} />
+        <MenuLink menu="users" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/users')} />
+        <MenuLink menu="user-groups" isListGroupItems={isListGroupItems} isActive={isActiveMenu(['/user-groups', 'user-group-detail'])} />
+        <MenuLink menu="audit-log" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/audit-log')} />
+        <MenuLink menu="plugins" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/plugins')} />
+        <MenuLink menu="search" isListGroupItems={isListGroupItems} isActive={isActiveMenu('/search')} />
         {growiCloudUri != null && growiAppIdForGrowiCloud != null
           && (
             <a

+ 2 - 6
apps/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useState } from 'react';
 
 import { useTranslation } from 'react-i18next';
 
@@ -26,10 +26,6 @@ const CustomizeLogoSetting = (): JSX.Element => {
   const [isDefaultLogoSelected, setIsDefaultLogoSelected] = useState<boolean>(isDefaultLogo ?? true);
   const [retrieveError, setRetrieveError] = useState<any>();
 
-  const currentLogo = useMemo(() => {
-    return isDefaultLogo ? DEFAULT_LOGO : CUSTOMIZED_LOGO;
-  }, [isDefaultLogo]);
-
   const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     if (e.target.files != null && e.target.files.length > 0) {
       const reader = new FileReader();
@@ -129,7 +125,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
                     {isCustomizedLogoUploaded && (
                       <>
                         <p>
-                          <img src='/attachment/brand-logo' className="picture picture-lg " id="settingBrandLogo" width="64" />
+                          <img src={CUSTOMIZED_LOGO} className="picture picture-lg " id="settingBrandLogo" width="64" />
                         </p>
                         <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
                           { t('admin:customize_settings.delete_logo') }

+ 11 - 3
apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx

@@ -61,13 +61,21 @@ const CustomizeNoscriptSetting = (props: Props): JSX.Element => {
             */}
           </div>
 
-          <a className="text-muted"
-            data-toggle="collapse" href="#collapseExampleHtml" role="button" aria-expanded="false" aria-controls="collapseExampleHtml">
+          <a
+            className="text-muted"
+            data-toggle="collapse"
+            href="#collapseExampleHtml"
+            role="button"
+            aria-expanded="false"
+            aria-controls="collapseExampleHtml"
+          >
             <i className="fa fa-fw fa-chevron-right" aria-hidden="true"></i>
             Example for Google Tag Manager
           </a>
           <div className="collapse" id="collapseExampleHtml">
-            <PrismAsyncLight style={oneDark} language={'javascript'}
+            <PrismAsyncLight
+              style={oneDark}
+              language="javascript"
             >
               {`<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
   height="0"

+ 11 - 3
apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -58,13 +58,21 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
             */}
           </div>
 
-          <a className="text-muted"
-            data-toggle="collapse" href="#collapseExampleScript" role="button" aria-expanded="false" aria-controls="collapseExampleScript">
+          <a
+            className="text-muted"
+            data-toggle="collapse"
+            href="#collapseExampleScript"
+            role="button"
+            aria-expanded="false"
+            aria-controls="collapseExampleScript"
+          >
             <i className="fa fa-fw fa-chevron-right" aria-hidden="true"></i>
             Example for Google Tag Manager
           </a>
           <div className="collapse" id="collapseExampleScript">
-            <PrismAsyncLight style={oneDark} language={'javascript'}
+            <PrismAsyncLight
+              style={oneDark}
+              language="javascript"
             >
               {`(function(w,d,s,l,i){
 w[l]=w[l]||[];

+ 1 - 1
apps/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx

@@ -1,6 +1,6 @@
 import React, { useMemo } from 'react';
 
-import { GrowiThemeMetadata, GrowiThemeSchemeType } from '@growi/core';
+import { type GrowiThemeMetadata, GrowiThemeSchemeType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 import { ThemeColorBox } from './ThemeColorBox';

+ 2 - 2
apps/app/src/components/Admin/Customize/CustomizeTitle.tsx

@@ -59,9 +59,9 @@ export const CustomizeTitle: FC = () => {
 
         {/* TODO i18n */}
         <div className="form-text text-muted col-12">
-            Default Value: <code>&#123;&#123;pagename&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
+          Default Value: <code>&#123;&#123;pagename&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
           <br />
-            Default Output Example: <code className="xml">&lt;title&gt;Page name - My GROWI&lt;&#047;title&gt;</code>
+          Default Output Example: <code className="xml">&lt;title&gt;Page name - My GROWI&lt;&#047;title&gt;</code>
         </div>
         <div className="form-group col-12">
           <input

+ 1 - 1
apps/app/src/components/Admin/Customize/ThemeColorBox.tsx

@@ -35,7 +35,7 @@ export const ThemeColorBox = (props: Props): JSX.Element => {
         </svg>
       </a>
       <span className="theme-option-name"><b>{ name }</b></span>
-      { !isPresetTheme && <span className='theme-option-badge badge badge-primary mt-1'>Plugin</span> }
+      { !isPresetTheme && <span className="theme-option-badge badge badge-primary mt-1">Plugin</span> }
     </div>
   );
 

+ 3 - 23
apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx

@@ -4,11 +4,9 @@ import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
-import * as toastr from 'toastr';
 
 import { apiPost } from '~/client/util/apiv1-client';
-
-// import { toastSuccess, toastError } from '~/client/util/toastr';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 
 
 const GROUPS_PAGE = [
@@ -72,37 +70,19 @@ const SelectCollectionsModal = (props: Props): JSX.Element => {
     try {
       // TODO: use apiv3Post
       const result = await apiPost<any>('/v3/export', { collections: Array.from(selectedCollections) });
-      // TODO: toastSuccess, toastError
 
       if (!result.ok) {
         throw new Error('Error occured.');
       }
 
-      // TODO: toastSuccess, toastError
-      toastr.success(undefined, 'Export process has requested.', {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '1200',
-        extendedTimeOut: '150',
-      });
+      toastSuccess('Export process has requested.');
 
       onExportingRequested();
       onClose();
       uncheckAll();
     }
     catch (err) {
-      // TODO: toastSuccess, toastError
-      toastr.error(err, 'Error', {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '3000',
-      });
+      toastError(err);
     }
   }, [onClose, onExportingRequested, selectedCollections, uncheckAll]);
 

+ 4 - 31
apps/app/src/components/Admin/ExportArchiveDataPage.tsx

@@ -1,11 +1,11 @@
 import React, { useCallback, useEffect, useState } from 'react';
 
 import { useTranslation } from 'react-i18next';
-import * as toastr from 'toastr';
 
 
 import { apiDelete } from '~/client/util/apiv1-client';
 import { apiv3Get } from '~/client/util/apiv3-client';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 import { useAdminSocket } from '~/stores/socket-io';
 
 import LabeledProgressBar from './Common/LabeledProgressBar';
@@ -34,7 +34,6 @@ const ExportArchiveDataPage = (): JSX.Element => {
       apiv3Get<{collections: any[]}>('/mongo/collections', {}),
       apiv3Get<{status: { zipFileStats: any[], isExporting: boolean, progressList: any[] }}>('/export/status', {}),
     ]);
-    // TODO: toastSuccess, toastError
 
     // filter only not ignored collection names
     const filteredCollections = collectionsData.collections.filter((collectionName) => {
@@ -69,16 +68,7 @@ const ExportArchiveDataPage = (): JSX.Element => {
         setExported(true);
         setZipFileStats(prev => prev.concat([addedZipFileStat]));
 
-        // TODO: toastSuccess, toastError
-        toastr.success(undefined, `New Archive Data '${addedZipFileStat.fileName}' is added`, {
-          closeButton: true,
-          progressBar: true,
-          newestOnTop: false,
-          showDuration: '100',
-          hideDuration: '100',
-          timeOut: '1200',
-          extendedTimeOut: '150',
-        });
+        toastSuccess(`New Archive Data '${addedZipFileStat.fileName}' is added`);
       });
     }
   }, [socket]);
@@ -89,27 +79,10 @@ const ExportArchiveDataPage = (): JSX.Element => {
 
       setZipFileStats(prev => prev.filter(stat => stat.fileName !== fileName));
 
-      // TODO: toastSuccess, toastError
-      toastr.success(undefined, `Deleted ${fileName}`, {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '1200',
-        extendedTimeOut: '150',
-      });
+      toastSuccess(`Deleted ${fileName}`);
     }
     catch (err) {
-      // TODO: toastSuccess, toastError
-      toastr.error(err, 'Error', {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '3000',
-      });
+      toastError(err);
     }
   }, []);
 

+ 17 - 0
apps/app/src/components/Admin/ForbiddenPage.tsx

@@ -0,0 +1,17 @@
+import React from 'react';
+
+import DefaultErrorPage from 'next/error';
+import { useTranslation } from 'react-i18next';
+
+
+export const ForbiddenPage = (): JSX.Element => {
+  const { t } = useTranslation('admin');
+
+  const errorMessage = t('forbidden_page.do_not_have_admin_permission');
+
+  return (
+    <>
+      <DefaultErrorPage statusCode={403} title={errorMessage} />
+    </>
+  );
+};

+ 5 - 5
apps/app/src/components/Admin/G2GDataTransfer.tsx

@@ -1,5 +1,5 @@
 import React, {
-  ChangeEvent, useCallback, useEffect, useState,
+  useCallback, useEffect, useState,
 } from 'react';
 
 import { useTranslation } from 'next-i18next';
@@ -217,7 +217,7 @@ const G2GDataTransfer = (): JSX.Element => {
             onChangeGcsBucket={onChangeGcsBucketHandler}
             onChangeGcsUploadNamespace={onChangeGcsUploadNamespaceHandler}
           /> */}
-          <h3 className='mb-1'>{t('export_management.export_archive_data')}</h3>
+          <h3 className="mb-1">{t('export_management.export_archive_data')}</h3>
           <G2GDataTransferExportForm
             allCollectionNames={collections}
             selectedCollections={selectedCollections}
@@ -246,12 +246,12 @@ const G2GDataTransfer = (): JSX.Element => {
       </form>
 
       {isTransferring && (
-        <div className='border rounded p-4'>
+        <div className="border rounded p-4">
           <div>
-            <G2GDataTransferStatusIcon className='mr-2 mb-2' status={g2gProgress.mongo} /> MongoDB
+            <G2GDataTransferStatusIcon className="mr-2 mb-2" status={g2gProgress.mongo} /> MongoDB
           </div>
           <div>
-            <G2GDataTransferStatusIcon className='mr-2' status={g2gProgress.attachments} /> Attachments
+            <G2GDataTransferStatusIcon className="mr-2" status={g2gProgress.attachments} /> Attachments
           </div>
         </div>
       )}

+ 7 - 7
apps/app/src/components/Admin/G2GDataTransferExportForm.tsx

@@ -122,7 +122,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     );
   };
 
-  const WarnForGroups = ({ errors }): JSX.Element => {
+  const WarnForGroups = ({ errors }: { errors: Error[] }): JSX.Element => {
     if (errors.length === 0) {
       return <></>;
     }
@@ -130,8 +130,8 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     return (
       <div className="alert alert-warning">
         <ul>
-          {errors.map((error, i) => {
-            return <li key={i}>{error}</li>;
+          {errors.map((error) => {
+            return <li>{error.message}</li>;
           })}
         </ul>
       </div>
@@ -162,7 +162,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     });
 
     // TODO: エラー対応
-    return <GroupImportItems groupList={collectionNames} groupName='Other' errors={[]} />;
+    return <GroupImportItems groupList={collectionNames} groupName="Other" errors={[]} />;
   };
 
   const configurationModal = useMemo(() => {
@@ -224,9 +224,9 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
       </div>
 
       {/* TODO: エラー追加 */}
-      <GroupImportItems groupList={GROUPS_PAGE} groupName='Page' errors={[]} />
-      <GroupImportItems groupList={GROUPS_USER} groupName='User' errors={[]} />
-      <GroupImportItems groupList={GROUPS_CONFIG} groupName='Config' errors={[]} />
+      <GroupImportItems groupList={GROUPS_PAGE} groupName="Page" errors={[]} />
+      <GroupImportItems groupList={GROUPS_USER} groupName="User" errors={[]} />
+      <GroupImportItems groupList={GROUPS_CONFIG} groupName="Config" errors={[]} />
       <OtherImportItems />
 
       {configurationModal}

+ 0 - 1
apps/app/src/components/Admin/ImportData/GrowiArchive/UploadForm.jsx

@@ -32,7 +32,6 @@ class UploadForm extends React.Component {
 
     try {
       const { data } = await apiv3PostForm('/import/upload', formData);
-      // TODO: toastSuccess, toastError
       this.props.onUpload(data);
     }
     catch (err) {

+ 3 - 21
apps/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx

@@ -2,11 +2,10 @@ import React, { Fragment } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import * as toastr from 'toastr';
 
 import { apiv3Delete, apiv3Get } from '~/client/util/apiv3-client';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 
-// import { toastSuccess, toastError } from '~/client/util/toastr';
 
 import ImportForm from './GrowiArchive/ImportForm';
 import UploadForm from './GrowiArchive/UploadForm';
@@ -59,27 +58,10 @@ class GrowiArchiveSection extends React.Component {
       await apiv3Delete('/import/all');
       this.resetState();
 
-      // TODO: toastSuccess, toastError
-      toastr.success(undefined, `Deleted ${fileName}`, {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '1200',
-        extendedTimeOut: '150',
-      });
+      toastSuccess(`Deleted ${fileName}`);
     }
     catch (err) {
-      // TODO: toastSuccess, toastError
-      toastr.error(err, 'Error', {
-        closeButton: true,
-        progressBar: true,
-        newestOnTop: false,
-        showDuration: '100',
-        hideDuration: '100',
-        timeOut: '3000',
-      });
+      toastError(err);
     }
   }
 

+ 1 - 1
apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx

@@ -1,4 +1,4 @@
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';

+ 25 - 23
apps/app/src/components/Admin/ManageExternalAccount.tsx

@@ -46,32 +46,34 @@ const ManageExternalAccount = (props: ManageExternalAccountProps): JSX.Element =
     />
   );
 
-  return <>
-    <p>
-      <Link
-        href="/admin/users"
-        prefetch={false}
-        className="btn btn-outline-secondary"
-      >
-        <i className="icon-fw ti ti-arrow-left" aria-hidden="true"></i>
-        {t('admin:user_management.back_to_user_management')}
-      </Link>
-    </p>
-    <h2>{t('admin:user_management.external_account_list')}</h2>
-    {(totalAccounts !== 0) ? (
-      <>
-        {pager}
-        <ExternalAccountTable />
-        {pager}
-      </>
-    )
-      : (
+  return (
+    <>
+      <p>
+        <Link
+          href="/admin/users"
+          prefetch={false}
+          className="btn btn-outline-secondary"
+        >
+          <i className="icon-fw ti ti-arrow-left" aria-hidden="true"></i>
+          {t('admin:user_management.back_to_user_management')}
+        </Link>
+      </p>
+      <h2>{t('admin:user_management.external_account_list')}</h2>
+      {(totalAccounts !== 0) ? (
         <>
-          { t('admin:user_management.external_account_none') }
+          {pager}
+          <ExternalAccountTable />
+          {pager}
         </>
       )
-    }
-  </>;
+        : (
+          <>
+            { t('admin:user_management.external_account_none') }
+          </>
+        )
+      }
+    </>
+  );
 };
 
 const ManageExternalAccountWrapper = withUnstatedContainers(ManageExternalAccount, [AdminExternalAccountsContainer]);

+ 18 - 10
apps/app/src/components/Admin/Notification/GlobalNotification.jsx

@@ -39,7 +39,8 @@ const GlobalNotification = (props) => {
       <p className="card well">
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('notification_settings.link_notification_help') }} />
-      </p><div className="row mb-4">
+      </p>
+      <div className="row mb-4">
         <div className="col-md-8 offset-md-2">
           <div className="custom-control custom-checkbox custom-checkbox-success">
             <input
@@ -47,14 +48,17 @@ const GlobalNotification = (props) => {
               className="custom-control-input"
               type="checkbox"
               checked={adminNotificationContainer.state.isNotificationForOwnerPageEnabled || false}
-              onChange={() => { adminNotificationContainer.switchIsNotificationForOwnerPageEnabled() } } />
+              onChange={() => { adminNotificationContainer.switchIsNotificationForOwnerPageEnabled() }}
+            />
             <label className="custom-control-label" htmlFor="isNotificationForOwnerPageEnabled">
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.just_me_notification_help') }} />
             </label>
           </div>
         </div>
-      </div><div className="row mb-4">
+      </div>
+
+      <div className="row mb-4">
         <div className="col-md-8 offset-md-2">
           <div className="custom-control custom-checkbox custom-checkbox-success">
             <input
@@ -62,7 +66,8 @@ const GlobalNotification = (props) => {
               className="custom-control-input"
               type="checkbox"
               checked={adminNotificationContainer.state.isNotificationForGroupPageEnabled || false}
-              onChange={() => { adminNotificationContainer.switchIsNotificationForGroupPageEnabled() } } />
+              onChange={() => { adminNotificationContainer.switchIsNotificationForGroupPageEnabled() }}
+            />
             <label className="custom-control-label" htmlFor="isNotificationForGroupPageEnabled">
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.group_notification_help') }} />
@@ -81,13 +86,16 @@ const GlobalNotification = (props) => {
           </button>
         </div>
       </div>
+
       <h2 className="border-bottom mb-5">{t('notification_settings.notification_list')}
-        <button className="btn btn-outline-secondary pull-right"
-          type="button" onClick={() => router.push('/admin/global-notification/new')}>{t('notification_settings.add_notification')}</button>
-        {/* <a href="/admin/global-notification/new">
-      <p className="btn btn-outline-secondary pull-right">{t('notification_setting.add_notification')}</p>
-    </a> */}
-      </h2><table className="table table-bordered">
+        <button
+          className="btn btn-outline-secondary pull-right"
+          type="button"
+          onClick={() => router.push('/admin/global-notification/new')}
+        >{t('notification_settings.add_notification')}
+        </button>
+      </h2>
+      <table className="table table-bordered">
         <thead>
           <tr>
             <th>ON/OFF</th>

+ 9 - 22
apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
@@ -7,29 +7,16 @@ import {
 } from 'reactstrap';
 
 const DeleteAllShareLinksModal = React.memo((props) => {
-  const { t } = props;
+  const { t, onClickDeleteButton, onClose } = props;
 
-  function closeModal() {
-    if (props.onClose == null) {
-      return;
-    }
+  const deleteAllLinkHandler = useCallback(() => {
+    onClickDeleteButton?.();
+    onClose?.();
+  }, [onClickDeleteButton, onClose]);
 
-    props.onClose();
-  }
-
-  function deleteAllLinkHandler() {
-    if (props.onClickDeleteButton == null) {
-      return;
-    }
-
-    props.onClickDeleteButton();
-
-    closeModal();
-  }
-
-  function closeButtonHandler() {
-    closeModal();
-  }
+  const closeButtonHandler = useCallback(() => {
+    onClose?.();
+  }, [onClose]);
 
   return (
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">

+ 1 - 1
apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx

@@ -1,7 +1,7 @@
 /* eslint-disable react/no-danger */
 import React from 'react';
 
-import { pathUtils } from '@growi/core';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import urljoin from 'url-join';

+ 1 - 1
apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { pathUtils } from '@growi/core';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import urljoin from 'url-join';

+ 1 - 1
apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { pathUtils } from '@growi/core';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import urljoin from 'url-join';

+ 1 - 1
apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -1,7 +1,7 @@
 /* eslint-disable react/no-danger */
 import React from 'react';
 
-import { pathUtils } from '@growi/core';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import { Collapse } from 'reactstrap';

+ 0 - 2
apps/app/src/components/Admin/Security/SecurityManagement.tsx

@@ -1,7 +1,5 @@
 import React, { useEffect, useCallback } from 'react';
 
-import PropTypes from 'prop-types';
-
 import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
 import { toastError } from '~/client/util/toastr';
 import { toArrayIfNot } from '~/utils/array-utils';

+ 22 - 0
apps/app/src/components/Admin/Security/SecuritySetting.jsx

@@ -453,6 +453,28 @@ class SecuritySetting extends React.Component {
           ].map(arr => this.renderPageDeletePermission(arr[0], arr[1], arr[2], arr[3]))
         }
 
+        <h4>{t('security_settings.user_homepage_deletion.user_homepage_deletion')}</h4>
+        <div className="row mb-4">
+          <div className="col-6 offset-3">
+            <div className="custom-control custom-switch custom-checkbox-success">
+              <input
+                type="checkbox"
+                className="custom-control-input"
+                id="is-user-page-deletion-enabled"
+                checked={adminGeneralSecurityContainer.state.isUsersHomepageDeletionEnabled}
+                onChange={() => { adminGeneralSecurityContainer.switchIsUsersHomepageDeletionEnabled() }}
+              />
+              <label className="custom-control-label" htmlFor="is-user-page-deletion-enabled">
+                {t('security_settings.user_homepage_deletion.enable_user_homepage_deletion')}
+              </label>
+            </div>
+            <p
+              className="form-text text-muted small"
+              dangerouslySetInnerHTML={{ __html: t('security_settings.user_homepage_deletion.when_deleting_a_user_the_user_homepage_is_also_deleted') }}
+            />
+          </div>
+        </div>
+
         <h4>{t('security_settings.session')}</h4>
         <div className="form-group row">
           <label className="text-left text-md-right col-md-3 col-form-label">{t('security_settings.max_age')}</label>

+ 0 - 1
apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx

@@ -6,7 +6,6 @@ import PropTypes from 'prop-types';
 import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 

+ 9 - 21
apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
@@ -7,28 +7,16 @@ import {
 } from 'reactstrap';
 
 const DeleteSlackBotSettingsModal = React.memo((props) => {
-  const { t } = useTranslation();
+  const { t, onClickDeleteButton, onClose } = useTranslation();
 
-  function closeModal() {
-    if (props.onClose == null) {
-      return;
-    }
+  const deleteSlackCredentialsHandler = useCallback(() => {
+    onClickDeleteButton?.();
+    onClose?.();
+  }, [onClickDeleteButton, onClose]);
 
-    props.onClose();
-  }
-
-  function deleteSlackCredentialsHandler() {
-    if (props.onClickDeleteButton == null) {
-      return;
-    }
-    props.onClickDeleteButton();
-
-    closeModal();
-  }
-
-  function closeButtonHandler() {
-    closeModal();
-  }
+  const closeButtonHandler = useCallback(() => {
+    onClose?.();
+  }, [onClose]);
 
   return (
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">

+ 1 - 3
apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -1,11 +1,9 @@
 /* eslint-disable react/prop-types */
-import React, { useState, useCallback } from 'react';
+import React, { useState } from 'react';
 
 import { SlackbotType } from '@growi/slack';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import { Tooltip } from 'reactstrap';
 
 import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';

+ 1 - 1
apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx

@@ -2,12 +2,12 @@ import React, {
   FC, useCallback, useState, useMemo,
 } from 'react';
 
+import type { IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-import { IUserGroupHasId } from '~/interfaces/user';
 
 /**
  * Delete User Group Select component

+ 1 - 2
apps/app/src/components/Admin/UserGroup/UserGroupDropdown.tsx

@@ -1,9 +1,8 @@
 import React, { FC, useCallback } from 'react';
 
+import type { IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
-import type { IUserGroupHasId } from '~/interfaces/user';
-
 type Props = {
   selectableUserGroups?: IUserGroupHasId[]
   onClickAddExistingUserGroupButton?(userGroup: IUserGroupHasId | null): void

+ 1 - 2
apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx

@@ -1,10 +1,9 @@
 import React, { FC, useCallback, useState } from 'react';
 
+import type { IUserGroupHasId } from '@growi/core';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 
-import { IUserGroupHasId } from '~/interfaces/user';
-
 type Props = {
   userGroup: IUserGroupHasId,
   parentUserGroup?: IUserGroupHasId,

+ 1 - 3
apps/app/src/components/Admin/UserGroup/UserGroupModal.tsx

@@ -2,14 +2,12 @@ import React, {
   FC, useState, useEffect, useCallback,
 } from 'react';
 
-import { Ref } from '@growi/core';
+import type { Ref, IUserGroup, IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-import { IUserGroup, IUserGroupHasId } from '~/interfaces/user';
-
 type Props = {
   userGroup?: IUserGroupHasId,
   buttonLabel?: string,

+ 1 - 1
apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -1,11 +1,11 @@
 import React, { FC, useState, useCallback } from 'react';
 
+import type { IUserGroup, IUserGroupHasId } from '@growi/core';
 import dynamic from 'next/dynamic';
 import { useTranslation } from 'react-i18next';
 
 import { apiv3Delete, apiv3Post, apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { IUserGroup, IUserGroupHasId } from '~/interfaces/user';
 import { useIsAclEnabled } from '~/stores/context';
 import { useSWRxUserGroupList, useSWRxChildUserGroupList, useSWRxUserGroupRelationList } from '~/stores/user-group';
 

+ 2 - 2
apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -2,7 +2,8 @@ import React, {
   useState, useCallback, useEffect, useMemo,
 } from 'react';
 
-import { objectIdUtils } from '@growi/core';
+import type { IUserGroup, IUserGroupHasId } from '@growi/core';
+import { objectIdUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Link from 'next/link';
@@ -12,7 +13,6 @@ import {
   apiv3Get, apiv3Put, apiv3Delete, apiv3Post,
 } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
-import { IUserGroup, IUserGroupHasId } from '~/interfaces/user';
 import { SearchTypes, SearchType } from '~/interfaces/user-group';
 import Xss from '~/services/xss';
 import { useIsAclEnabled } from '~/stores/context';

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx

@@ -1,10 +1,10 @@
 import React, { useEffect, useState, useCallback } from 'react';
 
+import type { IPageHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
-import { IPageHasId } from '~/interfaces/page';
 
 import { PageListItemS } from '../../PageList/PageListItemS';
 import PaginationWrapper from '../../PaginationWrapper';

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
+import { UserPicture } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserModal.tsx

@@ -1,11 +1,11 @@
 import React from 'react';
 
+import type { IUserGroupHasId } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody,
 } from 'reactstrap';
 
-import { IUserGroupHasId } from '~/interfaces/user';
 import { SearchTypes, SearchType } from '~/interfaces/user-group';
 
 import CheckBoxForSerchUserOption from './CheckBoxForSerchUserOption';

+ 1 - 1
apps/app/src/components/Admin/UserGroupDetail/UserGroupUserTable.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
+import { UserPicture } from '@growi/ui/dist/components';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 

+ 10 - 5
apps/app/src/components/Admin/Users/PasswordResetModal.jsx

@@ -30,7 +30,7 @@ class PasswordResetModal extends React.Component {
   }
 
   async resetPassword() {
-    const { t, userForPasswordResetModal } = this.props;
+    const { userForPasswordResetModal } = this.props;
     try {
       const res = await apiv3Put('/users/reset-password', { id: userForPasswordResetModal._id });
       const { newPassword } = res.data;
@@ -47,9 +47,13 @@ class PasswordResetModal extends React.Component {
 
     return (
       <>
-        <button type="submit" className={`btn ${isEmailSent ? 'btn-secondary' : 'btn-primary'}`}
-          onClick={this.onClickSendNewPasswordButton} disabled={!isMailerSetup || isEmailSending || isEmailSent}>
-          {isEmailSending && <i className='fa fa-spinner fa-pulse mx-2' />}
+        <button
+          type="submit"
+          className={`btn ${isEmailSent ? 'btn-secondary' : 'btn-primary'}`}
+          onClick={this.onClickSendNewPasswordButton}
+          disabled={!isMailerSetup || isEmailSending || isEmailSent}
+        >
+          {isEmailSending && <i className="fa fa-spinner fa-pulse mx-2" />}
           {!isEmailSending && (isEmailSent ? t('commons:Done') : t('commons:Send'))}
         </button>
         <button type="submit" className="btn btn-danger" onClick={this.props.onClose}>
@@ -119,7 +123,7 @@ class PasswordResetModal extends React.Component {
               {showPassword ? temporaryPassword : maskedPassword}
             </span>
           </code>
-          <CopyToClipboard text={ temporaryPassword } onCopy={() => this.setState({ showTooltip: true })}>
+          <CopyToClipboard text={temporaryPassword} onCopy={() => this.setState({ showTooltip: true })}>
             <button id="copy-tooltip" type="button" className="btn btn-outline-secondary border-0">
               <i className="fa fa-clone" aria-hidden="true"></i>
             </button>
@@ -210,6 +214,7 @@ const PasswordResetModalWrapperFC = (props) => {
 PasswordResetModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
 
+  isMailerSetup: PropTypes.bool.isRequired,
   isOpen: PropTypes.bool.isRequired,
   onClose: PropTypes.func.isRequired,
   userForPasswordResetModal: PropTypes.object,

+ 1 - 2
apps/app/src/components/Admin/Users/UserInviteModal.jsx

@@ -208,8 +208,6 @@ class UserInviteModal extends React.Component {
 
   async handleSubmit() {
     const { adminUsersContainer } = this.props;
-    // eslint-disable-next-line no-unused-vars
-    const { isCreateUserButtonPushed } = this.state;
 
     this.setState({ isCreateUserButtonPushed: true });
 
@@ -295,6 +293,7 @@ const UserInviteModalWrapper = withUnstatedContainers(UserInviteModalWrapperFC,
 UserInviteModal.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
+  isMailerSetup: PropTypes.bool.isRequired,
 };
 
 export default UserInviteModalWrapper;

+ 1 - 1
apps/app/src/components/Admin/Users/UserMenu.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useCallback } from 'react';
 
-import { IUserHasId, USER_STATUS } from '@growi/core';
+import { type IUserHasId, USER_STATUS } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import {
   UncontrolledDropdown, DropdownToggle, DropdownMenu,

+ 1 - 1
apps/app/src/components/Admin/Users/UserTable.tsx

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
 import type { IUserHasId } from '@growi/core';
-import { UserPicture } from '@growi/ui/dist/components/User/UserPicture';
+import { UserPicture } from '@growi/ui/dist/components';
 import dateFnsFormat from 'date-fns/format';
 import { useTranslation } from 'next-i18next';
 

+ 0 - 243
apps/app/src/components/ArchiveCreateModal.jsx

@@ -1,243 +0,0 @@
-import React, { useState, useCallback } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import {
-  Modal, ModalHeader, ModalBody, ModalFooter,
-} from 'reactstrap';
-
-import { apiv3Post } from '~/client/util/apiv3-client';
-import { toastSuccess, toastError } from '~/client/util/toastr';
-
-
-const ArchiveCreateModal = (props) => {
-  const { t } = useTranslation();
-  const { appContainer } = props;
-  const [isCommentDownload, setIsCommentDownload] = useState(false);
-  const [isAttachmentFileDownload, setIsAttachmentFileDownload] = useState(false);
-  const [isSubordinatedPageDownload, setIsSubordinatedPageDownload] = useState(false);
-  const [fileType, setFileType] = useState('markdown');
-  const [hierarchyType, setHierarchyType] = useState('allSubordinatedPage');
-  const [hierarchyValue, setHierarchyValue] = useState(1);
-
-  function changeIsCommentDownloadHandler() {
-    setIsCommentDownload(!isCommentDownload);
-  }
-
-  function changeIsAttachmentFileDownloadHandler() {
-    setIsAttachmentFileDownload(!isAttachmentFileDownload);
-  }
-
-  function changeIsSubordinatedPageDownloadHandler() {
-    setIsSubordinatedPageDownload(!isSubordinatedPageDownload);
-  }
-
-  function closeModalHandler() {
-    if (props.onClose == null) {
-      return;
-    }
-
-    props.onClose();
-  }
-
-  const handleChangeFileType = useCallback(
-    (filetype) => {
-      setFileType(filetype);
-    },
-    [],
-  );
-
-  function handleChangeSubordinatedType(hierarchyType) {
-    setHierarchyType(hierarchyType);
-  }
-
-  function handleHierarchyDepth(hierarchyValue) {
-    setHierarchyValue(hierarchyValue);
-  }
-
-
-  async function done() {
-    try {
-      await apiv3Post('/page/archive', {
-        rootPagePath: props.path,
-        isCommentDownload,
-        isAttachmentFileDownload,
-        isSubordinatedPageDownload,
-        fileType,
-        hierarchyType,
-        hierarchyValue,
-      });
-      toastSuccess(t('Submitted the request to create the archive'));
-      closeModalHandler();
-    }
-    catch (e) {
-      toastError(e);
-    }
-  }
-
-  return (
-    <Modal isOpen={props.isOpen} toggle={closeModalHandler}>
-      <ModalHeader tag="h4" toggle={closeModalHandler} className="bg-primary text-white">
-        {t('Create Archive Page')}
-      </ModalHeader>
-      <ModalBody>
-        <div className="form-group">
-          <div className="form-group">
-            <label>{t('Target page')}</label>
-            <br />
-            <code>{props.path}</code>
-          </div>
-
-          <div className="custom-control-inline">
-            <label>{t('File type')}: </label>
-          </div>
-          <div className="custom-control custom-radio custom-control-inline ">
-            <input
-              type="radio"
-              className="custom-control-input"
-              id="customRadio1"
-              name="isFileType"
-              value="customRadio1"
-              checked={fileType === 'markdown'}
-              onChange={() => {
-                handleChangeFileType('markdown');
-              }}
-            />
-            <label className="custom-control-label" htmlFor="customRadio1">
-              MarkDown(.md)
-            </label>
-          </div>
-
-          <div className="custom-control custom-radio custom-control-inline ">
-            <input
-              type="radio"
-              className="custom-control-input"
-              id="customRadio2"
-              name="isFileType"
-              value="customRadio2"
-              checked={fileType === 'pdf'}
-              onChange={() => {
-                handleChangeFileType('pdf');
-              }}
-            />
-            <label className="custom-control-label" htmlFor="customRadio2">
-              PDF(.pdf)
-            </label>
-          </div>
-        </div>
-
-        <div className="my-1 custom-control custom-checkbox custom-checkbox-info">
-          <input
-            className="custom-control-input"
-            name="comment"
-            id="commentFile"
-            type="checkbox"
-            checked={isCommentDownload}
-            onChange={changeIsCommentDownloadHandler}
-          />
-          <label className="custom-control-label" htmlFor="commentFile">
-            {t('Include Comment')}
-          </label>
-        </div>
-        <div className="my-1 custom-control custom-checkbox custom-checkbox-info">
-          <input
-            className="custom-control-input"
-            id="downloadFile"
-            type="checkbox"
-            checked={isAttachmentFileDownload}
-            onChange={changeIsAttachmentFileDownloadHandler}
-          />
-          <label className="custom-control-label" htmlFor="downloadFile">
-            {t('Include Attachment File')}
-          </label>
-        </div>
-        <div className="my-1 custom-control custom-checkbox custom-checkbox-info">
-          <input
-            className="custom-control-input"
-            id="subordinatedFile"
-            type="checkbox"
-            checked={isSubordinatedPageDownload}
-            onChange={changeIsSubordinatedPageDownloadHandler}
-          />
-          <label className="custom-control-label" htmlFor="subordinatedFile">
-            {t('Include Subordinated Page')}
-          </label>
-          {isSubordinatedPageDownload && (
-            <>
-              <div className="FormGroup">
-                <div className="my-1 custom-control custom-radio custom-control-inline ">
-                  <input
-                    type="radio"
-                    className="custom-control-input"
-                    id="customRadio3"
-                    name="isSubordinatedType"
-                    value="customRadio3"
-                    disabled={!isSubordinatedPageDownload}
-                    checked={hierarchyType === 'allSubordinatedPage'}
-                    onChange={() => {
-                      handleChangeSubordinatedType('allSubordinatedPage');
-                    }}
-                  />
-                  <label className="custom-control-label" htmlFor="customRadio3">
-                    {t('All Subordinated Page')}
-                  </label>
-                </div>
-              </div>
-              <div className="FormGroup">
-                <div className="my-1 custom-control custom-radio custom-control-inline">
-                  <input
-                    type="radio"
-                    className="custom-control-input"
-                    id="customRadio4"
-                    name="isSubordinatedType"
-                    value="customRadio4"
-                    disabled={!isSubordinatedPageDownload}
-                    checked={hierarchyType === 'decideHierarchy'}
-                    onChange={() => {
-                      handleChangeSubordinatedType('decideHierarchy');
-                    }}
-                  />
-                  <label className="my-1 custom-control-label" htmlFor="customRadio4">
-                    {t('Specify Hierarchy')}
-                  </label>
-                </div>
-              </div>
-              <div className="my-1 custom-control costom-control-inline">
-                <input
-                  type="number"
-                  min="1"
-                  max="10"
-                  disabled={hierarchyType === 'allSubordinatedPage'}
-                  value={hierarchyValue}
-                  placeholder="1"
-                  onChange={(e) => {
-                    handleHierarchyDepth(e.target.value);
-                  }}
-                />
-              </div>
-            </>
-          )}
-        </div>
-      </ModalBody>
-      <ModalFooter>
-        {/* TO DO implement correct number at GW-3053 */}
-        合計{props.totalPages}ページ取得
-        {props.errorMessage}
-        <button type="button" className="btn btn-primary" onClick={done}>
-          Done
-        </button>
-      </ModalFooter>
-    </Modal>
-  );
-};
-
-ArchiveCreateModal.propTypes = {
-  // appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func,
-  path: PropTypes.string.isRequired,
-  totalPages: PropTypes.number,
-  errorMessage: PropTypes.string,
-};
-
-export default ArchiveCreateModal;

+ 9 - 3
apps/app/src/components/BookmarkButtons.tsx

@@ -64,12 +64,18 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
     <div className={`btn-group btn-group-bookmark ${styles['btn-group-bookmark']}`} role="group" aria-label="Bookmark buttons">
 
       <BookmarkFolderMenu
-        isOpen={isBookmarkFolderMenuOpen} pageId={pageId} isBookmarked={isBookmarked ?? false}
+        isOpen={isBookmarkFolderMenuOpen}
+        pageId={pageId}
+        isBookmarked={isBookmarked ?? false}
         onToggle={toggleBookmarkFolderMenuHandler}
         onUnbookmark={unbookmarkHandler}
       >
-        <DropdownToggle id='bookmark-dropdown-btn' color="transparent" className={`shadow-none btn btn-bookmark border-0
-          ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}>
+        <DropdownToggle
+          id="bookmark-dropdown-btn"
+          color="transparent"
+          className={`shadow-none btn btn-bookmark border-0
+          ${isBookmarked ? 'active' : ''} ${isGuestUser ? 'disabled' : ''}`}
+        >
           <i className={`fa ${isBookmarked ? 'fa-bookmark' : 'fa-bookmark-o'}`}></i>
         </DropdownToggle>
       </BookmarkFolderMenu>

+ 12 - 14
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -2,6 +2,7 @@ import {
   FC, useCallback, useState,
 } from 'react';
 
+import type { IPageToDeleteWithMeta } from '@growi/core';
 import { DropdownToggle } from 'reactstrap';
 
 import {
@@ -13,7 +14,6 @@ import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import {
   BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE,
 } from '~/interfaces/bookmark-info';
-import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { onDeletedBookmarkFolderFunction } from '~/interfaces/ui';
 import { useBookmarkFolderDeleteModal } from '~/stores/modal';
 
@@ -29,7 +29,7 @@ type BookmarkFolderItemProps = {
   isOperable: boolean,
   level: number
   root: string
-  isUserHomePage?: boolean
+  isUserHomepage?: boolean
   onClickDeleteMenuItemHandler: (pageToDelete: IPageToDeleteWithMeta) => void
   bookmarkFolderTreeMutation: () => void
 }
@@ -38,7 +38,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
   const BASE_FOLDER_PADDING = 15;
   const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const {
-    isReadOnlyUser, bookmarkFolder, isOpen: _isOpen = false, isOperable, level, root, isUserHomePage,
+    isReadOnlyUser, bookmarkFolder, isOpen: _isOpen = false, isOperable, level, root, isUserHomepage,
     onClickDeleteMenuItemHandler, bookmarkFolderTreeMutation,
   } = props;
 
@@ -155,7 +155,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             bookmarkFolder={childFolder}
             level={level + 1}
             root={root}
-            isUserHomePage={isUserHomePage}
+            isUserHomepage={isUserHomepage}
             onClickDeleteMenuItemHandler={onClickDeleteMenuItemHandler}
             bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
           />
@@ -174,7 +174,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
           bookmarkedPage={bookmark.page}
           level={level + 1}
           parentFolder={bookmarkFolder}
-          canMoveToRoot={true}
+          canMoveToRoot
           onClickDeleteMenuItemHandler={onClickDeleteMenuItemHandler}
           bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
         />
@@ -222,7 +222,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
         isDropable={isDropable}
       >
         <li
-          className={'list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center'}
+          className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center"
           onClick={loadChildFolder}
           style={{ paddingLeft }}
         >
@@ -239,11 +239,9 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
               </button>
             )}
           </div>
-          {
-            <div>
-              <FolderIcon isOpen={isOpen} />
-            </div>
-          }
+          <div>
+            <FolderIcon isOpen={isOpen} />
+          </div>
           {isRenameAction ? (
             <BookmarkFolderNameInput
               onClickOutside={() => setIsRenameAction(false)}
@@ -252,8 +250,8 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             />
           ) : (
             <>
-              <div className='grw-foldertree-title-anchor pl-2' >
-                <p className={'text-truncate m-auto '}>{name}</p>
+              <div className="grw-foldertree-title-anchor pl-2">
+                <p className="text-truncate m-auto ">{name}</p>
               </div>
             </>
           )}
@@ -276,7 +274,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
               {/* Maximum folder hierarchy of 2 levels */}
               {!(bookmarkFolder.parent != null) && (
                 <button
-                  id='create-bookmark-folder-button'
+                  id="create-bookmark-folder-button"
                   type="button"
                   className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
                   onClick={onClickPlusButton}

+ 2 - 2
apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx

@@ -48,10 +48,10 @@ export const BookmarkFolderItemControl: React.FC<{
           {t('Rename')}
         </DropdownItem>
 
-        <DropdownItem divider/>
+        <DropdownItem divider />
 
         <DropdownItem
-          className='pt-2 grw-page-control-dropdown-item text-danger'
+          className="pt-2 grw-page-control-dropdown-item text-danger"
           onClick={onClickDelete}
         >
           <i className="icon-fw icon-trash grw-page-control-dropdown-icon"></i>

+ 4 - 4
apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -117,7 +117,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
         <DropdownItem
           toggle={false}
           onClick={onUnbookmarkHandler}
-          className={'grw-bookmark-folder-menu-item text-danger'}
+          className="grw-bookmark-folder-menu-item text-danger"
         >
           <i className="fa fa-bookmark"></i>{' '}
           <span className="mx-2">
@@ -160,7 +160,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
                 {folder.children?.map(child => (
                   <div key={child._id}>
                     <div
-                      className='dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0'
+                      className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
                       style={{ paddingLeft: '60px' }}
                       tabIndex={0}
                       role="menuitem"
@@ -187,14 +187,14 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
       isOpen={isOpen}
       onToggle={toggleHandler}
       direction={isBookmarkFolderExists ? 'up' : 'down'}
-      className='grw-bookmark-folder-dropdown'
+      className="grw-bookmark-folder-dropdown"
     >
       {children}
       <DropdownMenu
         right
         persist
         positionFixed
-        className='grw-bookmark-folder-menu'
+        className="grw-bookmark-folder-menu"
         modifiers={getCustomModifiers(true)}
       >
         { renderBookmarkMenuItem() }

+ 2 - 2
apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx

@@ -10,7 +10,7 @@ export const BookmarkFolderMenuItem: React.FC<{
   isSelected,
 }) => {
   return (
-    <div className='d-flex justify-content-start grw-bookmark-folder-menu-item-title'>
+    <div className="d-flex justify-content-start grw-bookmark-folder-menu-item-title">
       <input
         type="radio"
         checked={isSelected}
@@ -19,7 +19,7 @@ export const BookmarkFolderMenuItem: React.FC<{
         onChange={e => e.stopPropagation()}
         onClick={e => e.stopPropagation()}
       />
-      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className='p-2 m-0'>
+      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className="p-2 m-0">
         {itemName}
       </label>
     </div>

+ 2 - 2
apps/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx

@@ -1,6 +1,6 @@
 import { useTranslation } from 'next-i18next';
 
-import { inputValidator, ValidationTarget } from '~/client/util/input-validator';
+import { ValidationTarget } from '~/client/util/input-validator';
 import ClosableTextInput from '~/components/Common/ClosableTextInput';
 
 
@@ -19,7 +19,7 @@ export const BookmarkFolderNameInput = (props: Props): JSX.Element => {
   return (
     <div className="flex-fill folder-name-input">
       <ClosableTextInput
-        value={ value }
+        value={value}
         placeholder={t('bookmark_folder.input_placeholder')}
         onClickOutside={onClickOutside}
         onPressEnter={onPressEnter}

+ 5 - 5
apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx

@@ -1,11 +1,11 @@
 
 import React, { useCallback } from 'react';
 
+import type { IPageToDeleteWithMeta } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 
 import { toastSuccess } from '~/client/util/toastr';
-import { IPageToDeleteWithMeta } from '~/interfaces/page';
 import { OnDeletedFunction } from '~/interfaces/ui';
 import {
   useSWRxUserBookmarks, useSWRMUTxCurrentUserBookmarks,
@@ -27,13 +27,13 @@ import styles from './BookmarkFolderTree.module.scss';
 //  } & IPageHasId
 
 type Props = {
-  isUserHomePage?: boolean,
+  isUserHomepage?: boolean,
   userId?: string,
   isOperable: boolean,
 }
 
 export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
-  const { isUserHomePage, userId } = props;
+  const { isUserHomepage, userId } = props;
 
   // const acceptedTypes: DragItemType[] = [DRAG_ITEM_TYPE.FOLDER, DRAG_ITEM_TYPE.BOOKMARK];
   const { t } = useTranslation();
@@ -102,7 +102,7 @@ export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
   // };
 
   return (
-    <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}`} >
+    <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}`}>
       <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
         {bookmarkFolders?.map((bookmarkFolder) => {
           return (
@@ -114,7 +114,7 @@ export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
               isOpen={false}
               level={0}
               root={bookmarkFolder._id}
-              isUserHomePage={isUserHomePage}
+              isUserHomepage={isUserHomepage}
               onClickDeleteMenuItemHandler={onClickDeleteMenuItemHandler}
               bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
             />

+ 4 - 3
apps/app/src/components/Bookmarks/BookmarkItem.tsx

@@ -2,7 +2,9 @@ import React, { useCallback, useState } from 'react';
 
 import nodePath from 'path';
 
-import { DevidedPagePath, pathUtils } from '@growi/core';
+import type { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '@growi/core';
+import { DevidedPagePath } from '@growi/core/dist/models';
+import { pathUtils } from '@growi/core/dist/utils';
 import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 import { UncontrolledTooltip, DropdownToggle } from 'reactstrap';
@@ -13,7 +15,6 @@ import { addBookmarkToFolder, renamePage } from '~/client/util/bookmark-utils';
 import { ValidationTarget } from '~/client/util/input-validator';
 import { toastError, toastSuccess } from '~/client/util/toastr';
 import { BookmarkFolderItems, DragItemDataType, DRAG_ITEM_TYPE } from '~/interfaces/bookmark-info';
-import { IPageHasId, IPageInfoAll, IPageToDeleteWithMeta } from '~/interfaces/page';
 import { usePutBackPageModal } from '~/stores/modal';
 import { mutateAllPageInfo, useSWRMUTxCurrentPage, useSWRxPageInfo } from '~/stores/page';
 
@@ -163,7 +164,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
           )
           : <PageListItemS page={bookmarkedPage} pageTitle={pageTitle} isNarrowView />}
 
-        <div className='grw-foldertree-control'>
+        <div className="grw-foldertree-control">
           <PageItemControl
             pageId={bookmarkedPage._id}
             isEnableActions

+ 1 - 1
apps/app/src/components/Bookmarks/DragAndDropWrapper.tsx

@@ -66,7 +66,7 @@ export const DragAndDropWrapper = (props: DragAndDropWrapperProps): JSX.Element
   };
 
   return (
-    <div ref={c => getRef(c)} className={`grw-drag-drop-container ${isOver ? 'grw-accept-drop-item' : ''}` }>
+    <div ref={c => getRef(c)} className={`grw-drag-drop-container ${isOver ? 'grw-accept-drop-item' : ''}`}>
       {children}
     </div>
   );

+ 2 - 1
apps/app/src/components/Comments.tsx

@@ -1,6 +1,7 @@
 import React, { useEffect, useMemo, useRef } from 'react';
 
-import { type IRevisionHasId, pagePathUtils } from '@growi/core';
+import type { IRevisionHasId } from '@growi/core';
+import { pagePathUtils } from '@growi/core/dist/utils';
 import dynamic from 'next/dynamic';
 import { debounce } from 'throttle-debounce';
 

Some files were not shown because too many files changed in this diff