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

Merge pull request #7960 from weseek/master

Release v6.2.0
Yuki Takei 2 лет назад
Родитель
Сommit
4e5ac01d07
100 измененных файлов с 618 добавлено и 395 удалено
  1. 1 1
      .github/ISSUE_TEMPLATE/config.yml
  2. 0 7
      .github/release-drafter-dev-6.2.x.yml
  3. 0 7
      .github/release-drafter-master.yml
  4. 6 0
      .github/release-drafter.yml
  5. 2 0
      .github/workflows/ci-app-prod.yml
  6. 3 3
      .github/workflows/ci-app.yml
  7. 3 3
      .github/workflows/ci-slackbot-proxy.yml
  8. 11 9
      .github/workflows/draft-release.yml
  9. 1 1
      .github/workflows/release-slackbot-proxy.yml
  10. 6 6
      .github/workflows/release.yml
  11. 3 3
      .github/workflows/reusable-app-prod.yml
  12. 1 1
      .github/workflows/reusable-app-reg-suit.yml
  13. 27 1
      CHANGELOG.md
  14. 3 3
      README.md
  15. 3 3
      README_JP.md
  16. 1 1
      SECURITY.md
  17. 14 1
      apps/app/.eslintrc.js
  18. 3 2
      apps/app/bin/download-cdn-resources.ts
  19. 1 1
      apps/app/bin/github-actions/update-readme.sh
  20. 3 0
      apps/app/config/logger/config.dev.js
  21. 2 1
      apps/app/config/next-i18next.config.js
  22. 6 5
      apps/app/docker/README.md
  23. 2 0
      apps/app/next.config.js
  24. 7 8
      apps/app/package.json
  25. 15 4
      apps/app/public/static/locales/en_US/admin.json
  26. 0 1
      apps/app/public/static/locales/en_US/translation.json
  27. 15 4
      apps/app/public/static/locales/ja_JP/admin.json
  28. 0 1
      apps/app/public/static/locales/ja_JP/translation.json
  29. 15 4
      apps/app/public/static/locales/zh_CN/admin.json
  30. 0 1
      apps/app/public/static/locales/zh_CN/translation.json
  31. 2 2
      apps/app/resource/locales/en_US/welcome.md
  32. 4 4
      apps/app/resource/locales/ja_JP/admin/userInvitation.ejs
  33. 4 4
      apps/app/resource/locales/ja_JP/admin/userResetPassword.ejs
  34. 6 6
      apps/app/resource/locales/ja_JP/admin/userWaitingActivation.ejs
  35. 2 2
      apps/app/resource/locales/ja_JP/welcome.md
  36. 2 2
      apps/app/resource/locales/zh_CN/welcome.md
  37. 1 3
      apps/app/src/client/models/Linker.js
  38. 1 1
      apps/app/src/client/services/AdminAppContainer.js
  39. 12 1
      apps/app/src/client/services/AdminCustomizeContainer.js
  40. 1 1
      apps/app/src/client/services/AdminExternalAccountsContainer.js
  41. 11 1
      apps/app/src/client/services/AdminGeneralSecurityContainer.js
  42. 1 2
      apps/app/src/client/services/AdminGitHubSecurityContainer.js
  43. 1 2
      apps/app/src/client/services/AdminGoogleSecurityContainer.js
  44. 2 2
      apps/app/src/client/services/AdminHomeContainer.js
  45. 1 1
      apps/app/src/client/services/AdminImportContainer.js
  46. 1 1
      apps/app/src/client/services/AdminLdapSecurityContainer.js
  47. 1 1
      apps/app/src/client/services/AdminLocalSecurityContainer.js
  48. 1 1
      apps/app/src/client/services/AdminMarkDownContainer.js
  49. 1 1
      apps/app/src/client/services/AdminNotificationContainer.js
  50. 1 2
      apps/app/src/client/services/AdminOidcSecurityContainer.js
  51. 1 2
      apps/app/src/client/services/AdminSamlSecurityContainer.js
  52. 1 1
      apps/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  53. 1 1
      apps/app/src/client/services/AdminUsersContainer.js
  54. 2 1
      apps/app/src/client/services/layout.ts
  55. 10 2
      apps/app/src/client/services/renderer/renderer.tsx
  56. 90 0
      apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
  57. 58 0
      apps/app/src/client/services/search-operation.ts
  58. 1 1
      apps/app/src/client/services/user-ui-settings.ts
  59. 1 1
      apps/app/src/client/util/bookmark-utils.ts
  60. 1 2
      apps/app/src/client/util/toastr.ts
  61. 32 28
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  62. 6 4
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  63. 8 3
      apps/app/src/components/Admin/AuditLog/ActivityTable.tsx
  64. 0 4
      apps/app/src/components/Admin/Common/AdminInstallButtonRow.tsx
  65. 18 18
      apps/app/src/components/Admin/Common/AdminNavigation.tsx
  66. 29 0
      apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  67. 2 6
      apps/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  68. 11 3
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  69. 11 3
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  70. 1 1
      apps/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx
  71. 2 2
      apps/app/src/components/Admin/Customize/CustomizeTitle.tsx
  72. 1 1
      apps/app/src/components/Admin/Customize/ThemeColorBox.tsx
  73. 3 23
      apps/app/src/components/Admin/ExportArchiveData/SelectCollectionsModal.tsx
  74. 4 31
      apps/app/src/components/Admin/ExportArchiveDataPage.tsx
  75. 5 5
      apps/app/src/components/Admin/G2GDataTransfer.tsx
  76. 7 7
      apps/app/src/components/Admin/G2GDataTransferExportForm.tsx
  77. 0 1
      apps/app/src/components/Admin/ImportData/GrowiArchive/UploadForm.jsx
  78. 3 21
      apps/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx
  79. 1 1
      apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  80. 25 23
      apps/app/src/components/Admin/ManageExternalAccount.tsx
  81. 18 10
      apps/app/src/components/Admin/Notification/GlobalNotification.jsx
  82. 9 22
      apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx
  83. 1 1
      apps/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx
  84. 1 1
      apps/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  85. 1 1
      apps/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  86. 1 1
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  87. 0 2
      apps/app/src/components/Admin/Security/SecurityManagement.tsx
  88. 22 0
      apps/app/src/components/Admin/Security/SecuritySetting.jsx
  89. 0 1
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  90. 9 21
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  91. 1 3
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  92. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupDeleteModal.tsx
  93. 1 2
      apps/app/src/components/Admin/UserGroup/UserGroupDropdown.tsx
  94. 1 2
      apps/app/src/components/Admin/UserGroup/UserGroupForm.tsx
  95. 1 3
      apps/app/src/components/Admin/UserGroup/UserGroupModal.tsx
  96. 1 1
      apps/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  97. 2 2
      apps/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  98. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupPageList.tsx
  99. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserFormByInput.jsx
  100. 1 1
      apps/app/src/components/Admin/UserGroupDetail/UserGroupUserModal.tsx

+ 1 - 1
.github/ISSUE_TEMPLATE/config.yml

@@ -4,5 +4,5 @@ contact_links:
     url: https://github.com/weseek/growi/discussions
     about: If you have feature requests or suggestions, you can create a new discussion and consider it with the community.
   - name: Questions
-    url: https://growi-slackin.weseek.co.jp/
+    url: https://communityinviter.com/apps/wsgrowi/invite/
     about: If you have questions, you can join our Slack team and talk about anything, anytime.

+ 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: |

+ 27 - 1
CHANGELOG.md

@@ -1,9 +1,35 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.1.11...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.15](https://github.com/weseek/growi/compare/v6.1.14...v6.1.15) - 2023-09-11
+
+### 🚀 Improvement
+
+- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (for v6.1.x) (#8057) @yuki-takei
+
+## [v6.1.14](https://github.com/weseek/growi/compare/v6.1.13...v6.1.14) - 2023-08-22
+
+### 🐛 Bug Fixes
+
+- fix: Add option to lightbox (6.1.x) (#8003) @yuki-takei
+
+## [v6.1.13](https://github.com/weseek/growi/compare/v6.1.12...v6.1.13) - 2023-08-17
+
+### 🐛 Bug Fixes
+
+- fix: Do not work img tag if use style property (#7988) @jam411
+- fix: "Searching..." label appearing unnecessarily (#7990) @yuki-takei
+
+## [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

+ 3 - 3
README.md

@@ -7,7 +7,7 @@
 </p>
 <p align="center">
   <a href="https://github.com/weseek/growi/releases/latest"><img src="https://img.shields.io/github/release/weseek/growi.svg"></a>
-  <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+  <a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 </p>
 
 <p align="center">
@@ -37,7 +37,7 @@
 # Features
 
 - **Features**
-  - Create hierarchical pages with markdown -> [HERE](https://docs.growi.org/en/guide/getting-started/five_minutes.html) is 5 minutes tutorial
+  - Create hierarchical pages with markdown -> [Try GROWI on the demo site](https://docs.growi.org/en/guide/getting-started/try_growi.html)
   - Simultaneously edit with multiple people by [HackMD(CodiMD)](https://hackmd.io/) integration
     - [GROWI Docs: HackMD(CodiMD) Integration](https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html)
   - Support Authentication with LDAP / Active Directory, OAuth
@@ -132,7 +132,7 @@ You can write issues and PRs in English or Japanese.
 
 ## Discussion
 
-If you have questions or suggestions, you can [join our Slack team](https://growi-slackin.weseek.co.jp/) and talk about anything, anytime.
+If you have questions or suggestions, you can [join our Slack team](https://communityinviter.com/apps/wsgrowi/invite/) and talk about anything, anytime.
 
 # License
 

+ 3 - 3
README_JP.md

@@ -6,7 +6,7 @@
   </p>
   <p align="center">
     <a href="https://github.com/weseek/growi/releases/latest"><img src="https://img.shields.io/github/release/weseek/growi.svg"></a>
-    <a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+    <a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
   </p>
 
 <p align="center">
@@ -36,7 +36,7 @@
 # 機能紹介
 
 - **主な機能**
-  - マークダウンを使用してページを階層構造で作成することが可能です。 -> 5 分間チュートリアルは[こちら](https://docs.growi.org/ja/guide/getting-started/five_minutes.html)。
+  - マークダウンを使用してページを階層構造で作成することが可能です。 -> [デモサイトで GROWI を体験する](https://docs.growi.org/ja/guide/getting-started/try_growi.html)。
   - [HackMD(CodiMd)](https://hackmd.io/) と連携することで同時多人数編集が可能です。
     - [GROWI Docs: HackMD(CodiMD) 連携](https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html)
   - LDAP / Active Direcotry , OAuth 認証をサポートしています。
@@ -129,7 +129,7 @@ Issue と Pull requests の作成は英語・日本語どちらでも受け付
 
 ## GROWI について話し合いましょう!
 
-質問や提案があれば、私たちの [Slack team](https://growi-slackin.weseek.co.jp/) にぜひご参加ください。
+質問や提案があれば、私たちの [Slack team](https://communityinviter.com/apps/wsgrowi/invite/) にぜひご参加ください。
 いつでも、どこでも GROWI について議論しましょう!
 
 # ライセンス

+ 1 - 1
SECURITY.md

@@ -13,7 +13,7 @@
 
 If you believe you have found a security vulnerability in any GROWI related repository, please report it to us using one of the methods described below.
 
-  * [Join our Slack team](https://growi-slackin.weseek.co.jp/) and send DM to `@yuki` who is the lead developer
+  * [Join our Slack team](https://communityinviter.com/apps/wsgrowi/invite/) and send DM to `@yuki` who is the lead developer
   * Report to JPCERT/CC[^jpcertcc]
     * [[PDF] JPCERT/CC Vulnerability Coordination and Disclosure Policy](https://www.jpcert.or.jp/english/vh/vul-coordination-disclosure-policy_2019.pdf)
 

+ 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

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

@@ -28,6 +28,9 @@ module.exports = {
   // 'growi:InterceptorManager': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:g2g-transfer': 'debug',
+  'growi:service:questionnaire': '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)
 
 

+ 2 - 0
apps/app/next.config.js

@@ -26,6 +26,8 @@ const getTranspilePackages = () => {
     'character-entities-legacy',
     'comma-separated-tokens',
     'decode-named-character-reference',
+    'devlop',
+    'fault',
     'escape-string-regexp',
     'hastscript',
     'html-void-elements',

+ 7 - 8
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.1.11",
+  "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",
@@ -184,6 +184,7 @@
     "rehype-toc": "^3.0.2",
     "remark-breaks": "^3.0.2",
     "remark-emoji": "^3.0.2",
+    "remark-frontmatter": "^4.0.1",
     "remark-gfm": "^3.0.1",
     "remark-math": "^5.1.1",
     "remark-toc": "^8.0.1",
@@ -195,8 +196,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 +227,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 +240,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 +248,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 +262,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"
   }
 }

+ 15 - 4
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": "Complete deletion of user homepage, when user deletion",
+      "desc": "When deleting a user, the user homepage and its sub pages are also completely deleted."
+    },
     "session": "Session",
     "max_age": "Max age (msec)",
     "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
@@ -475,7 +480,13 @@
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments_desc": "When the setting value is off, comments other than the latest two are omitted.",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
-      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
+      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range.",
+      "enable_marp": "Enable Marp ",
+      "enable_marp_desc": "Marp can be used in presentation preview. This option may make you vulnerable to XSS.",
+      "marp_official_site": "The Marp Official Site",
+      "marp_official_site_link": "https://marp.app",
+      "marp_in_growi" : "GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "Custom title",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
@@ -866,7 +877,7 @@
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "Open GROWI.cloud Settings"
@@ -1057,6 +1068,6 @@
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
   },
   "forbidden_page": {
-    "do_not_have_admin_permission": "Users without administrative rights cannot access the administration screen."
+    "do_not_have_admin_permission": "Users without administrative rights cannot access the administration screen"
   }
 }

+ 0 - 1
apps/app/public/static/locales/en_US/translation.json

@@ -442,7 +442,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   "toaster": {
-    "file_upload_succeeded": "File upload succeeded.",
     "file_upload_failed": "File upload failed.",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",

+ 15 - 4
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": "ユーザー削除時にユーザーホームページを完全削除する",
+      "desc": "ユーザーを削除する際に、ユーザーホームページとその配下のページも完全削除されます。"
+    },
     "session": "セッション",
     "max_age": "有効期間 (ミリ秒)",
     "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
@@ -483,7 +488,13 @@
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments_desc": "OFFの場合、最新2件のコメント以外が省略されます。",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
-      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
+      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。",
+      "enable_marp": "Marp を有効化する",
+      "enable_marp_desc": "プレゼンテーション表示に Marp を利用できるようになります。ただし、XSS に対して脆弱になる恐れがあります。",
+      "marp_official_site": "参考:Marp 公式サイト",
+      "marp_official_site_link": "https://marp.app",
+      "marp_in_growi" : "参考:GROWI Docs - Marp でスライドを作成する",
+      "marp_in_growi_link": "https://docs.growi.org/ja/guide/features/marp.html"
     },
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
@@ -874,7 +885,7 @@
     "plugin_card": "プラグインカード",
     "plugin_is_not_installed": "プラグインがインストールされていません",
     "install": "インストール",
-    "delete": "削除"
+    "confirm": "プラグインを削除しますか?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "GROWI.cloud の管理画面へ"
@@ -1065,6 +1076,6 @@
     "remove_plugin_success": "{{pluginName}}を削除しました"
   },
   "forbidden_page": {
-    "do_not_have_admin_permission": "管理者権限のないユーザーでは管理画面にはアクセスできません"
+    "do_not_have_admin_permission": "管理者権限のないユーザーでは管理画面にはアクセスできません"
   }
 }

+ 0 - 1
apps/app/public/static/locales/ja_JP/translation.json

@@ -475,7 +475,6 @@
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   "toaster": {
-    "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "initialize_successed": "{{target}}を初期化しました",
     "remove_share_link_success": "{{shareLinkId}}を削除しました",

+ 15 - 4
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": "用户删除时,完全删除用户主页",
+      "desc": "删除用户时,用户主页及其下属页面也会被完全删除。"
+    },
     "session": "会议",
     "max_age": "有效期间  (msec)",
     "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
@@ -483,7 +488,13 @@
       "show_all_reply_comments": "显示所有回复评论",
       "show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
-      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
+      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。",
+      "enable_marp": "启用 Marp",
+      "enable_marp_desc": "Marp 可在演示视图中使用。该选项可能会使您受到 XSS 的攻击。",
+      "marp_official_site": "参考资料:Marp 官方网站",
+      "marp_official_site_link": "https://marp.app",
+      "marp_in_growi" : "参考资料:GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "自定义标题",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
@@ -874,7 +885,7 @@
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "進入 GROWI.cloud 的管理界面"
@@ -1065,6 +1076,6 @@
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
   },
   "forbidden_page": {
-    "do_not_have_admin_permission": "没有管理权限的用户无法访问管理屏幕"
+    "do_not_have_admin_permission": "没有管理权限的用户无法访问管理屏幕"
   }
 }

+ 0 - 1
apps/app/public/static/locales/zh_CN/translation.json

@@ -431,7 +431,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
 	"toaster": {
-    "file_upload_succeeded": "文件上传成功",
     "file_upload_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "switch_disable_link_sharing_success": "成功更新分享链接设置",

+ 2 - 2
apps/app/resource/locales/en_US/welcome.md

@@ -20,7 +20,7 @@ Let's increase the information exchange everyday.
 - Once we finished, press the "**Update**" button to publish the page.
     - We can also save it by `Ctrl(⌘) + S`.
 
-For more information: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
+For more information: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
 
 <div class="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">
@@ -58,7 +58,7 @@ We can display the content list using a table and `$lsx`.
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 We welcome newcomers joining our slack channel to help improve GROWI.
 In addition to discussing development, we are also happy to answer your questions when you join.

+ 4 - 4
apps/app/resource/locales/ja_JP/admin/userInvitation.ejs

@@ -1,12 +1,12 @@
-Hi, <%- email %>
+こんにちは、 <%- email %>
 
-You are invited to our Wiki, you can log in with following account:
+GROWIに招待されました!次のアカウント情報でログインしてください:
 
 Email: <%- email %>
 Password: <%- password %>
-(This password was auto generated. Update required at the first time you logging in)
+(このパスワードは自動生成されます。 初回ログイン時にアップデートが必要です。)
 
-We are waiting for you!
+あなたの参加を心よりお待ちしております。
 <%- url %>
 
 --

+ 4 - 4
apps/app/resource/locales/ja_JP/admin/userResetPassword.ejs

@@ -1,10 +1,10 @@
-Hi, <%- email %>
+こんにちは、 <%- email %>
 
-Your password has been reset by the administrator, you can log in with following account:
+あなたのパスワードは管理者によってリセットされました。次のアカウントでログインできます。:
 
 Email: <%- email %>
-New Password: <%- password %>
-(This password was auto generated. Update required at the first time you logging in)
+新しいパスワード: <%- password %>
+(このパスワードは自動生成されます。 初回ログイン時にアップデートが必要です。)
 
 --
 <%- appTitle %>

+ 6 - 6
apps/app/resource/locales/ja_JP/admin/userWaitingActivation.ejs

@@ -1,17 +1,17 @@
-Hi, <%- adminUser.name %>
+こんにちは、 <%- adminUser.name %>
 
-A user registered to <%- appTitle %>.
+<%- appTitle %> に登録されているユーザー。
 
 
 ====
-Created user:
+作成したユーザー:
 
-Name: <%- createdUser.name %>
-User Name: <%- createdUser.username %>
+名前: <%- createdUser.name %>
+ユーザーネーム: <%- createdUser.username %>
 Email: <%- createdUser.email %>
 ====
 
-Please do some action with following URL:
+次のURLでアクションを起こしてください:
 <%- url %>/admin/users
 
 

+ 2 - 2
apps/app/resource/locales/ja_JP/welcome.md

@@ -19,7 +19,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
     - `Ctrl(⌘) + S` でも保存できます
 
-さらに詳しくはこちら: [チュートリアル#新規ページ作成](https://docs.growi.org/ja/guide/tutorial/create_page.html#新規ページ作成)
+さらに詳しくはこちら: [ページを作成する](https://docs.growi.org/ja/guide/features/create_page.html)
 
 <div class="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">Tips</div>
@@ -54,7 +54,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 GROWI をより良いものにするために、是非 Slack に参加してください。  
 開発に関する議論を行っている他、導入時の質問等も受け付けています。

+ 2 - 2
apps/app/resource/locales/zh_CN/welcome.md

@@ -20,7 +20,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 - 一旦我们完成了,按 "**更新**"按钮来发布页面。
     - 我们也可以通过`Ctrl(⌘) + S`来保存。
 
-了解更多信息: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
+了解更多信息: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
 
 <div class="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">
@@ -58,7 +58,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 
 # Slack
 
-<a href="https://growi-slackin.weseek.co.jp/"><img src="https://growi-slackin.weseek.co.jp/badge.svg"></a>
+<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
 
 我们欢迎新人加入我们的slack频道,帮助改善Growi。
 除了讨论发展问题,我们也很乐意在你加入时回答你的问题。

+ 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';

+ 12 - 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';
@@ -34,6 +34,7 @@ export default class AdminCustomizeContainer extends Container {
       isEnabledStaleNotification: false,
       isAllReplyShown: false,
       isSearchScopeChildrenAsDefault: false,
+      isEnabledMarp: false,
       currentCustomizeTitle: '',
       currentCustomizeNoscript: '',
       currentCustomizeCss: '',
@@ -71,6 +72,7 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
+        isEnabledMarp: customizeParams.isEnabledMarp,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeNoscript: customizeParams.customizeNoscript,
         currentCustomizeCss: customizeParams.customizeCss,
@@ -149,6 +151,13 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
   }
 
+  /**
+   * Switch isEnabledMarp
+   */
+  switchIsEnabledMarp() {
+    this.setState({ isEnabledMarp: !this.state.isEnabledMarp });
+  }
+
   /**
    * Change customize Title
    */
@@ -194,6 +203,7 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isAllReplyShown: this.state.isAllReplyShown,
         isSearchScopeChildrenAsDefault: this.state.isSearchScopeChildrenAsDefault,
+        isEnabledMarp: this.state.isEnabledMarp,
       });
       const { customizedParams } = response.data;
       this.setState({
@@ -206,6 +216,7 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isAllReplyShown: customizedParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizedParams.isSearchScopeChildrenAsDefault,
+        isEnabledMarp: customizedParams.isEnabledMarp,
       });
     }
     catch (err) {

+ 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';

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

@@ -1,10 +1,11 @@
 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 slides from '@growi/presentation';
+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';
@@ -18,6 +19,7 @@ import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
+import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import * as mermaid from '~/features/mermaid';
 import { RehypeSanitizeOption } from '~/interfaces/rehype';
@@ -66,6 +68,7 @@ export const generateViewOptions = (
     attachment.remarkPlugin,
     lsxGrowiDirective.remarkPlugin,
     refsGrowiDirective.remarkPlugin,
+    [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
   );
   if (config.isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
@@ -81,6 +84,7 @@ export const generateViewOptions = (
       drawio.sanitizeOption,
       mermaid.sanitizeOption,
       attachment.sanitizeOption,
+      slides.sanitizeOption,
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
     )]
@@ -115,6 +119,7 @@ export const generateViewOptions = (
     components.mermaid = mermaid.MermaidViewer;
     components.attachment = RichAttachment;
     components.img = LightBox;
+    components.slide = SlideViewer;
   }
 
   if (config.isEnabledXssPrevention) {
@@ -257,6 +262,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     attachment.remarkPlugin,
     lsxGrowiDirective.remarkPlugin,
     refsGrowiDirective.remarkPlugin,
+    [slides.remarkPlugin, { isEnabledMarp: config.isEnabledMarp }],
   );
   if (config.isEnabledLinebreaks) {
     remarkPlugins.push(breaks);
@@ -275,6 +281,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
       lsxGrowiDirective.sanitizeOption,
       refsGrowiDirective.sanitizeOption,
       addLineNumberAttribute.sanitizeOption,
+      slides.sanitizeOption,
     )]
     : () => {};
 
@@ -299,6 +306,7 @@ export const generatePreviewOptions = (config: RendererConfig, pagePath: string)
     components.mermaid = mermaid.MermaidViewer;
     components.attachment = RichAttachment;
     components.img = LightBox;
+    components.slide = SlideViewer;
   }
 
   if (config.isEnabledXssPrevention) {

+ 90 - 0
apps/app/src/client/services/renderer/slide-viewer-renderer.tsx

@@ -0,0 +1,90 @@
+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';
+import katex from 'rehype-katex';
+import sanitize from 'rehype-sanitize';
+import math from 'remark-math';
+import deepmerge from 'ts-deepmerge';
+import type { Pluggable } from 'unified';
+
+import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
+import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
+import * as mermaid from '~/features/mermaid';
+import { RehypeSanitizeOption } from '~/interfaces/rehype';
+import type { RendererOptions } from '~/interfaces/renderer-options';
+import type { RendererConfig } from '~/interfaces/services/renderer';
+import * as addLineNumberAttribute from '~/services/renderer/rehype-plugins/add-line-number-attribute';
+import * as attachment from '~/services/renderer/remark-plugins/attachment';
+import * as plantuml from '~/services/renderer/remark-plugins/plantuml';
+import * as xsvToTable from '~/services/renderer/remark-plugins/xsv-to-table';
+import {
+  commonSanitizeOption, generateCommonOptions, injectCustomSanitizeOption, verifySanitizePlugin,
+} from '~/services/renderer/renderer';
+
+
+export const generatePresentationViewOptions = (
+    config: RendererConfig,
+    pagePath: string,
+): RendererOptions => {
+  const options = generateCommonOptions(pagePath);
+
+  const { remarkPlugins, rehypePlugins, components } = options;
+
+  // add remark plugins
+  remarkPlugins.push(
+    math,
+    [plantuml.remarkPlugin, { plantumlUri: config.plantumlUri }],
+    drawio.remarkPlugin,
+    mermaid.remarkPlugin,
+    xsvToTable.remarkPlugin,
+    attachment.remarkPlugin,
+    lsxGrowiDirective.remarkPlugin,
+    refsGrowiDirective.remarkPlugin,
+  );
+
+  if (config.xssOption === RehypeSanitizeOption.CUSTOM) {
+    injectCustomSanitizeOption(config);
+  }
+
+
+  const rehypeSanitizePlugin: Pluggable<any[]> | (() => void) = config.isEnabledXssPrevention
+    ? [sanitize, deepmerge(
+      commonSanitizeOption,
+      drawio.sanitizeOption,
+      mermaid.sanitizeOption,
+      attachment.sanitizeOption,
+      lsxGrowiDirective.sanitizeOption,
+      refsGrowiDirective.sanitizeOption,
+      addLineNumberAttribute.sanitizeOption,
+    )]
+    : () => {};
+
+  // add rehype plugins
+  rehypePlugins.push(
+    [lsxGrowiDirective.rehypePlugin, { pagePath, isSharedPage: config.isSharedPage }],
+    [refsGrowiDirective.rehypePlugin, { pagePath }],
+    rehypeSanitizePlugin,
+    addLineNumberAttribute.rehypePlugin,
+    katex,
+  );
+
+  // add components
+  if (components != null) {
+    components.lsx = lsxGrowiDirective.LsxImmutable;
+    components.ref = refsGrowiDirective.RefImmutable;
+    components.refs = refsGrowiDirective.RefsImmutable;
+    components.refimg = refsGrowiDirective.RefImgImmutable;
+    components.refsimg = refsGrowiDirective.RefsImgImmutable;
+    components.gallery = refsGrowiDirective.GalleryImmutable;
+    components.drawio = drawio.DrawioViewer;
+    components.mermaid = mermaid.MermaidViewer;
+    components.attachment = RichAttachment;
+    components.img = LightBox;
+  }
+
+  if (config.isEnabledXssPrevention) {
+    verifySanitizePlugin(options, false);
+  }
+  return options;
+};

+ 58 - 0
apps/app/src/client/services/search-operation.ts

@@ -0,0 +1,58 @@
+import { useCallback, useEffect, useRef } from 'react';
+
+import { type SWRResponseWithUtils, withUtils } from '@growi/core/dist/swr';
+import { useRouter } from 'next/router';
+import useSWRImmutable from 'swr/immutable';
+
+
+type UseKeywordManagerUtils = {
+  pushState: (newKeyword: string) => void,
+}
+
+export const useKeywordManager = (): SWRResponseWithUtils<UseKeywordManagerUtils, string> => {
+  // routerRef solve the problem of infinite redrawing that occurs with routers
+  const router = useRouter();
+  const routerRef = useRef(router);
+
+  // parse URL Query
+  const queries = router.query.q;
+  const initialKeyword = (Array.isArray(queries) ? queries.join(' ') : queries) ?? '';
+
+  const swrResponse = useSWRImmutable<string>('searchKeyword', null, {
+    fallbackData: initialKeyword,
+  });
+
+  const { mutate } = swrResponse;
+  const pushState = useCallback((newKeyword: string) => {
+    mutate((prevKeyword) => {
+      if (prevKeyword !== newKeyword) {
+        const newUrl = new URL('/_search', 'http://example.com');
+        newUrl.searchParams.append('q', newKeyword);
+        routerRef.current.push(`${newUrl.pathname}${newUrl.search}`, '');
+      }
+
+      return newKeyword;
+    });
+  }, [mutate]);
+
+  // detect search keyword from the query of URL
+  useEffect(() => {
+    mutate(initialKeyword);
+  }, [mutate, initialKeyword]);
+
+  // browser back and forward
+  useEffect(() => {
+    routerRef.current.beforePopState(({ url }) => {
+      const newUrl = new URL(url, 'https://exmple.com');
+      const newKeyword = newUrl.searchParams.get('q');
+      if (newKeyword != null) {
+        mutate(newKeyword);
+      }
+      return true;
+    });
+  }, [mutate]);
+
+  return withUtils(swrResponse, {
+    pushState,
+  });
+};

+ 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

+ 29 - 0
apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx

@@ -133,6 +133,35 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             </div>
           </div>
 
+          <div className="form-group row">
+            <div className="offset-md-3 col-md-6 text-left">
+              <CustomizeFunctionOption
+                optionId="isEnabledMarp"
+                label={t('admin:customize_settings.function_options.enable_marp')}
+                isChecked={adminCustomizeContainer.state.isEnabledMarp || false}
+                onChecked={() => { adminCustomizeContainer.switchIsEnabledMarp() }}
+              >
+                <p className="form-text text-muted">
+                  {t('admin:customize_settings.function_options.enable_marp_desc')}
+                  <br></br>
+                  <a
+                    href={`${t('admin:customize_settings.function_options.marp_official_site_link')}`}
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >{`${t('admin:customize_settings.function_options.marp_official_site')}`}
+                  </a>
+                  <br></br>
+                  <a
+                    href={`${t('admin:customize_settings.function_options.marp_in_growi_link')}`}
+                    target="_blank"
+                    rel="noopener noreferrer"
+                  >{`${t('admin:customize_settings.function_options.marp_in_growi')}`}
+                  </a>
+                </p>
+              </CustomizeFunctionOption>
+            </div>
+          </div>
+
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
         </div>
       </div>

+ 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);
     }
   }, []);
 

+ 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.desc') }}
+            />
+          </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';

Некоторые файлы не были показаны из-за большого количества измененных файлов