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

Merge branch 'master' into imprv/115673-128224-create-presentationfail

Meiri Kikuta 2 лет назад
Родитель
Сommit
9583c12583
100 измененных файлов с 707 добавлено и 663 удалено
  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. 70 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. 1 0
      apps/app/config/logger/config.dev.js
  19. 1 1
      apps/app/package.json
  20. 6 3
      apps/app/public/static/locales/en_US/admin.json
  21. 0 1
      apps/app/public/static/locales/en_US/translation.json
  22. 7 4
      apps/app/public/static/locales/ja_JP/admin.json
  23. 0 1
      apps/app/public/static/locales/ja_JP/translation.json
  24. 6 3
      apps/app/public/static/locales/zh_CN/admin.json
  25. 0 1
      apps/app/public/static/locales/zh_CN/translation.json
  26. 2 2
      apps/app/resource/locales/en_US/welcome.md
  27. 4 4
      apps/app/resource/locales/ja_JP/admin/userInvitation.ejs
  28. 4 4
      apps/app/resource/locales/ja_JP/admin/userResetPassword.ejs
  29. 6 6
      apps/app/resource/locales/ja_JP/admin/userWaitingActivation.ejs
  30. 2 2
      apps/app/resource/locales/ja_JP/welcome.md
  31. 2 2
      apps/app/resource/locales/zh_CN/welcome.md
  32. 0 1
      apps/app/src/client/services/renderer/renderer.tsx
  33. 90 0
      apps/app/src/client/services/renderer/slide-viewer-renderer.tsx
  34. 58 0
      apps/app/src/client/services/search-operation.ts
  35. 32 28
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  36. 6 4
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  37. 0 4
      apps/app/src/components/Admin/Common/AdminInstallButtonRow.tsx
  38. 16 16
      apps/app/src/components/Admin/Common/AdminNavigation.tsx
  39. 2 6
      apps/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  40. 11 3
      apps/app/src/components/Admin/Customize/CustomizeNoscriptSetting.tsx
  41. 11 3
      apps/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  42. 2 2
      apps/app/src/components/Admin/Customize/CustomizeTitle.tsx
  43. 1 1
      apps/app/src/components/Admin/Customize/ThemeColorBox.tsx
  44. 17 0
      apps/app/src/components/Admin/ForbiddenPage.tsx
  45. 5 5
      apps/app/src/components/Admin/G2GDataTransfer.tsx
  46. 7 7
      apps/app/src/components/Admin/G2GDataTransferExportForm.tsx
  47. 1 1
      apps/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  48. 25 23
      apps/app/src/components/Admin/ManageExternalAccount.tsx
  49. 18 10
      apps/app/src/components/Admin/Notification/GlobalNotification.jsx
  50. 9 22
      apps/app/src/components/Admin/Security/DeleteAllShareLinksModal.jsx
  51. 0 2
      apps/app/src/components/Admin/Security/SecurityManagement.tsx
  52. 1 1
      apps/app/src/components/Admin/Security/SecuritySetting.jsx
  53. 0 1
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  54. 9 21
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx
  55. 1 3
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  56. 10 5
      apps/app/src/components/Admin/Users/PasswordResetModal.jsx
  57. 1 2
      apps/app/src/components/Admin/Users/UserInviteModal.jsx
  58. 0 243
      apps/app/src/components/ArchiveCreateModal.jsx
  59. 9 3
      apps/app/src/components/BookmarkButtons.tsx
  60. 8 10
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  61. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderItemControl.tsx
  62. 4 4
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  63. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx
  64. 2 2
      apps/app/src/components/Bookmarks/BookmarkFolderNameInput.tsx
  65. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderTree.tsx
  66. 1 1
      apps/app/src/components/Bookmarks/BookmarkItem.tsx
  67. 1 1
      apps/app/src/components/Bookmarks/DragAndDropWrapper.tsx
  68. 1 2
      apps/app/src/components/CompleteUserRegistration.tsx
  69. 1 1
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  70. 1 1
      apps/app/src/components/DeleteBookmarkFolderModal.tsx
  71. 1 5
      apps/app/src/components/DescendantsPageList.tsx
  72. 3 2
      apps/app/src/components/Hotkeys/Subscribers/EditPage.jsx
  73. 1 1
      apps/app/src/components/Hotkeys/Subscribers/FocusToGlobalSearch.jsx
  74. 0 1
      apps/app/src/components/Hotkeys/Subscribers/ShowStaffCredit.jsx
  75. 4 2
      apps/app/src/components/Icons/CompressIcon.tsx
  76. 2 1
      apps/app/src/components/Icons/ExpandIcon.tsx
  77. 6 4
      apps/app/src/components/Icons/FolderIcon.tsx
  78. 2 2
      apps/app/src/components/InvitedForm.tsx
  79. 1 1
      apps/app/src/components/Layout/AdminLayout.tsx
  80. 1 1
      apps/app/src/components/Layout/ShareLinkLayout.tsx
  81. 48 30
      apps/app/src/components/LoginForm.tsx
  82. 1 1
      apps/app/src/components/Me/AssociateModal.tsx
  83. 15 9
      apps/app/src/components/Me/OtherSettings.tsx
  84. 3 3
      apps/app/src/components/Navbar/AuthorInfo.tsx
  85. 4 5
      apps/app/src/components/Navbar/GlobalSearch.tsx
  86. 15 9
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  87. 15 13
      apps/app/src/components/Navbar/GrowiNavbar.tsx
  88. 1 1
      apps/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  89. 7 4
      apps/app/src/components/Navbar/PersonalDropdown.jsx
  90. 1 1
      apps/app/src/components/NotFoundPage.tsx
  91. 11 2
      apps/app/src/components/Page/RenderTagLabels.tsx
  92. 1 1
      apps/app/src/components/Page/RevisionRenderer.tsx
  93. 11 16
      apps/app/src/components/Page/TagEditModal.tsx
  94. 1 1
      apps/app/src/components/Page/TagLabels.tsx
  95. 1 1
      apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx
  96. 5 3
      apps/app/src/components/PageAlert/PageRedirectedAlert.tsx
  97. 7 7
      apps/app/src/components/PageCreateModal.jsx
  98. 2 6
      apps/app/src/components/PageDuplicateModal.tsx
  99. 1 1
      apps/app/src/components/PageEditor/Cheatsheet.tsx
  100. 1 1
      apps/app/src/components/PageEditor/ConflictDiffModal.tsx

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

@@ -4,5 +4,5 @@ contact_links:
     url: https://github.com/weseek/growi/discussions
     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.
     about: If you have feature requests or suggestions, you can create a new discussion and consider it with the community.
   - name: Questions
   - 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.
     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:
 categories:
   - title: 'BREAKING CHANGES'
   - title: 'BREAKING CHANGES'
     labels:
     labels:

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

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

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

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

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

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

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

@@ -29,17 +29,10 @@ jobs:
         uses: myrotvorets/info-from-package-json-action@1.2.0
         uses: myrotvorets/info-from-package-json-action@1.2.0
         id: package-json
         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
       - uses: release-drafter/release-drafter@v5
         id: release-drafter
         id: release-drafter
         with:
         with:
-          config-name: ${{ steps.determine-config-name.outputs.value }}
+          config-name: release-drafter.yml
           name: v${{ steps.package-json.outputs.packageVersion }}
           name: v${{ steps.package-json.outputs.packageVersion }}
           tag: v${{ steps.package-json.outputs.packageVersion }}
           tag: v${{ steps.package-json.outputs.packageVersion }}
           version: ${{ 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 }}`
           RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
           echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
           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
       - name: Create/Update Pull Request
         uses: bakunyo/git-pr-release-action@master
         uses: bakunyo/git-pr-release-action@master
         env:
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           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_BRANCH_STAGING: ${{ github.ref_name }}
           GIT_PR_RELEASE_TEMPLATE: .github/git-pr-release-template.erb
           GIT_PR_RELEASE_TEMPLATE: .github/git-pr-release-template.erb
           GIT_PR_RELEASE_TITLE: Release v${{ steps.release-version.outputs.RELEASE_VERSION }}
           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
     - name: Install dependencies
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
         yarn --frozen-lockfile
 
 
     - name: Bump versions for next RC
     - name: Bump versions for next RC

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

@@ -30,7 +30,7 @@ jobs:
 
 
     - name: Install dependencies
     - name: Install dependencies
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
         yarn --frozen-lockfile
 
 
     - name: Bump versions
     - name: Bump versions
@@ -63,11 +63,11 @@ jobs:
         commit_message: Release v${{ steps.package-json.outputs.packageVersion }}
         commit_message: Release v${{ steps.package-json.outputs.packageVersion }}
         tagging_message: 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:
       with:
         body: ${{ github.event.pull_request.body }}
         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
     - name: Delete drafts
       uses: hugo19941994/delete-draft-releases@v1.0.1
       uses: hugo19941994/delete-draft-releases@v1.0.1
@@ -93,7 +93,7 @@ jobs:
 
 
     - name: Install dependencies
     - name: Install dependencies
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
         yarn --frozen-lockfile
         yarn --frozen-lockfile
 
 
     - name: Bump versions for next RC
     - name: Bump versions for next RC
@@ -118,7 +118,7 @@ jobs:
       uses: repo-sync/pull-request@v2
       uses: repo-sync/pull-request@v2
       with:
       with:
         source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
         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_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
         pr_label: flag/exclude-from-changelog,type/prepare-next-version
         pr_label: flag/exclude-from-changelog,type/prepare-next-version
         pr_body: "[skip ci] An automated PR generated by create-pr-for-next-rc"
         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
     - name: Install turbo
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
 
     - name: Prune repositories
     - name: Prune repositories
       run: |
       run: |
@@ -147,7 +147,7 @@ jobs:
 
 
     - name: Install turbo
     - name: Install turbo
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
 
     - name: Prune repositories
     - name: Prune repositories
       run: |
       run: |
@@ -238,7 +238,7 @@ jobs:
 
 
     - name: Install turbo
     - name: Install turbo
       run: |
       run: |
-        yarn global add turbo@1.10.9
+        yarn global add turbo
 
 
     - name: Prune repositories
     - name: Prune repositories
       run: |
       run: |

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

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

+ 70 - 1
CHANGELOG.md

@@ -1,9 +1,78 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.1.10...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.2.0...HEAD)
 
 
 *Please do not manually update this file. We've automated the process.*
 *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.2.0](https://github.com/weseek/growi/compare/v6.1.12...v6.2.0) - 2023-09-14
+
+### 💎 Features
+
+- feat: Presentation preview and support Marp  (#8029) @reiji-h
+
+### 🚀 Improvement
+
+- imprv: Able to customize users homepage deletion (#7921) @yuki-takei
+- imprv: Search behavior (#8069) @yuki-takei
+- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (#8049) @ykanematsu
+- imprv: Correct update message (#8040) @reiji-h
+- imprv: Add installed date to questionnaire answer (#7971) @TatsuyaIse
+- imprv: Show modal when you delete plugin (#7875) @soumaeda
+- imprv: i18n resetting password mail body (#8058) @meiri-k
+- imprv: Create Japanese ejs files (#7957) @meiri-k
+- imprv: Clean up old toastr (#7949) @jam411
+- imprv: Persist the installed date in the Config collection (#7936) @TatsuyaIse
+
+### 🐛 Bug Fixes
+
+- fix: Pages can be created under a non-existent user page (#7974) @miya
+- fix: Pages can be created under a non-existent user page (During attachment upload) (#8001) @miya
+- fix: Type safe implementation for objects imported from ElasticsearchClient (#7862) @miya
+- fix: Consider an empty page when renaming and duplicating (#7979) @yuki-takei
+- fix: Remove redundant toastSuccess for pasted attachments (#8044) @jam411
+- fix: Fixing swagger for tag update api (#8010) @miya
+- fix: Modification of links in the docs (#8004) @miya
+
+### 🧰 Maintenance
+
+- support: Omit core-js v2 (#7944) @yuki-takei
+- support: Improve build settings (#7919) @yuki-takei
+- support: Url to join to the slack team (#8073) @WNomunomu
+
+## [v6.1.12](https://github.com/weseek/growi/compare/v6.1.11...v6.1.12) - 2023-08-14
+
+### 🐛 Bug Fixes
+
+- fix: Consider an empty page when renaming and duplicating (v6.1.x) (#7980) @yuki-takei
+- fix: Do not work image tag properties (#7977) @jam411
+
+## [v6.1.11](https://github.com/weseek/growi/compare/v6.1.10...v6.1.11) - 2023-08-07
+
+### 🐛 Bug Fixes
+
+- fix: Admin page permission when the user transit with next-routing (#7908) @WNomunomu
+- fix: Transitioning to a non-existent page under "/me" results in a 500 error (#7946) @miya
+- fix: Auto-scroll search result content 2 (#7943) @yuki-takei
+
 ## [v6.1.10](https://github.com/weseek/growi/compare/v6.1.9...v6.1.10) - 2023-08-01
 ## [v6.1.10](https://github.com/weseek/growi/compare/v6.1.9...v6.1.10) - 2023-08-01
 
 
 ### 🐛 Bug Fixes
 ### 🐛 Bug Fixes

+ 3 - 3
README.md

@@ -7,7 +7,7 @@
 </p>
 </p>
 <p align="center">
 <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://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>
 
 
 <p align="center">
 <p align="center">
@@ -37,7 +37,7 @@
 # Features
 # Features
 
 
 - **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
   - 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)
     - [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
   - Support Authentication with LDAP / Active Directory, OAuth
@@ -132,7 +132,7 @@ You can write issues and PRs in English or Japanese.
 
 
 ## Discussion
 ## 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
 # License
 
 

+ 3 - 3
README_JP.md

@@ -6,7 +6,7 @@
   </p>
   </p>
   <p align="center">
   <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://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>
 
 
 <p align="center">
 <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/) と連携することで同時多人数編集が可能です。
   - [HackMD(CodiMd)](https://hackmd.io/) と連携することで同時多人数編集が可能です。
     - [GROWI Docs: HackMD(CodiMD) 連携](https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html)
     - [GROWI Docs: HackMD(CodiMD) 連携](https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html)
   - LDAP / Active Direcotry , OAuth 認証をサポートしています。
   - LDAP / Active Direcotry , OAuth 認証をサポートしています。
@@ -129,7 +129,7 @@ Issue と Pull requests の作成は英語・日本語どちらでも受け付
 
 
 ## GROWI について話し合いましょう!
 ## GROWI について話し合いましょう!
 
 
-質問や提案があれば、私たちの [Slack team](https://growi-slackin.weseek.co.jp/) にぜひご参加ください。
+質問や提案があれば、私たちの [Slack team](https://communityinviter.com/apps/wsgrowi/invite/) にぜひご参加ください。
 いつでも、どこでも GROWI について議論しましょう!
 いつでも、どこでも 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.
 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]
   * 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)
     * [[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 = {
 module.exports = {
   extends: [
   extends: [
     'next/core-web-vitals',
     'next/core-web-vitals',
+    'weseek/react',
   ],
   ],
   plugins: [
   plugins: [
     'regex',
     'regex',
@@ -32,11 +33,23 @@ module.exports = {
     '@typescript-eslint/no-this-alias': ['warn'],
     '@typescript-eslint/no-this-alias': ['warn'],
   },
   },
   overrides: [
   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
       // enable the rule specifically for TypeScript files
       files: ['*.ts', '*.tsx'],
       files: ['*.ts', '*.tsx'],
       rules: {
       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
         // set 'warn' temporarily -- 2022.07.25 Yuki Takei
         '@typescript-eslint/explicit-module-boundary-types': ['warn'],
         '@typescript-eslint/explicit-module-boundary-types': ['warn'],
       },
       },

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

@@ -28,6 +28,7 @@ module.exports = {
   // 'growi:InterceptorManager': 'debug',
   // 'growi:InterceptorManager': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:g2g-transfer': 'debug',
   'growi:service:g2g-transfer': 'debug',
+  'growi:service:questionnaire': 'debug',
 
 
   'growi:migration:add-installed-date-to-config': 'debug',
   'growi:migration:add-installed-date-to-config': 'debug',
 
 

+ 1 - 1
apps/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "6.2.0-RC.0",
+  "version": "6.2.1-RC.0",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",

+ 6 - 3
apps/app/public/static/locales/en_US/admin.json

@@ -47,8 +47,8 @@
     "anyone": "Anyone",
     "anyone": "Anyone",
     "user_homepage_deletion": {
     "user_homepage_deletion": {
       "user_homepage_deletion": "User homepage deletion",
       "user_homepage_deletion": "User homepage deletion",
-      "enable_user_homepage_deletion": "Enable user homepage deletion",
-      "when_deleting_a_user_the_user_homepage_is_also_deleted": "When deleting a user, the user homepage is also deleted."
+      "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",
     "session": "Session",
     "max_age": "Max age (msec)",
     "max_age": "Max age (msec)",
@@ -881,7 +881,7 @@
     "plugin_card": "Plugin Card",
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   },
   "cloud_setting_management": {
   "cloud_setting_management": {
     "to_cloud_settings": "Open GROWI.cloud Settings"
     "to_cloud_settings": "Open GROWI.cloud Settings"
@@ -1070,5 +1070,8 @@
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
+  },
+  "forbidden_page": {
+    "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."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
   "toaster": {
   "toaster": {
-    "file_upload_succeeded": "File upload succeeded.",
     "file_upload_failed": "File upload failed.",
     "file_upload_failed": "File upload failed.",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
     "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",

+ 7 - 4
apps/app/public/static/locales/ja_JP/admin.json

@@ -54,9 +54,9 @@
     "admin_and_author": "管理者とページ作者が可能",
     "admin_and_author": "管理者とページ作者が可能",
     "anyone": "誰でも可能",
     "anyone": "誰でも可能",
     "user_homepage_deletion": {
     "user_homepage_deletion": {
-      "user_homepage_deletion": "ユーザーページの削除",
-      "enable_user_homepage_deletion": "ユーザーページの削除を有効化",
-      "when_deleting_a_user_the_user_homepage_is_also_deleted": "ユーザー削除時にユーザーページも削除します。"
+      "user_homepage_deletion": "ユーザーホームページの削除",
+      "enable_user_homepage_deletion": "ユーザー削除時にユーザーホームページを完全削除する",
+      "desc": "ユーザーを削除する際に、ユーザーホームページとその配下のページも完全削除されます。"
     },
     },
     "session": "セッション",
     "session": "セッション",
     "max_age": "有効期間 (ミリ秒)",
     "max_age": "有効期間 (ミリ秒)",
@@ -890,7 +890,7 @@
     "plugin_card": "プラグインカード",
     "plugin_card": "プラグインカード",
     "plugin_is_not_installed": "プラグインがインストールされていません",
     "plugin_is_not_installed": "プラグインがインストールされていません",
     "install": "インストール",
     "install": "インストール",
-    "delete": "削除"
+    "confirm": "プラグインを削除しますか?"
   },
   },
   "cloud_setting_management": {
   "cloud_setting_management": {
     "to_cloud_settings": "GROWI.cloud の管理画面へ"
     "to_cloud_settings": "GROWI.cloud の管理画面へ"
@@ -1079,5 +1079,8 @@
     "activate_plugin_success": "{{pluginName}}を有効化しました",
     "activate_plugin_success": "{{pluginName}}を有効化しました",
     "deactivate_plugin_success": "{{pluginName}}を無効化しました",
     "deactivate_plugin_success": "{{pluginName}}を無効化しました",
     "remove_plugin_success": "{{pluginName}}を削除しました"
     "remove_plugin_success": "{{pluginName}}を削除しました"
+  },
+  "forbidden_page": {
+    "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}}\" というページはありません。"
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   },
   "toaster": {
   "toaster": {
-    "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "initialize_successed": "{{target}}を初期化しました",
     "initialize_successed": "{{target}}を初期化しました",
     "remove_share_link_success": "{{shareLinkId}}を削除しました",
     "remove_share_link_success": "{{shareLinkId}}を削除しました",

+ 6 - 3
apps/app/public/static/locales/zh_CN/admin.json

@@ -55,8 +55,8 @@
 		"anyone": "任何人",
 		"anyone": "任何人",
     "user_homepage_deletion": {
     "user_homepage_deletion": {
       "user_homepage_deletion": "删除用户页面",
       "user_homepage_deletion": "删除用户页面",
-      "enable_user_homepage_deletion": "用删除用户页",
-      "when_deleting_a_user_the_user_homepage_is_also_deleted": "当一个用户被删除时,用户页面也会被删除。"
+      "enable_user_homepage_deletion": "用户删除时,完全删除用户页",
+      "desc": "删除用户时,用户主页及其下属页面也会被完全删除。"
     },
     },
     "session": "会议",
     "session": "会议",
     "max_age": "有效期间  (msec)",
     "max_age": "有效期间  (msec)",
@@ -889,7 +889,7 @@
     "plugin_card": "Plugin Card",
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   },
   "cloud_setting_management": {
   "cloud_setting_management": {
     "to_cloud_settings": "進入 GROWI.cloud 的管理界面"
     "to_cloud_settings": "進入 GROWI.cloud 的管理界面"
@@ -1078,5 +1078,8 @@
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "activate_plugin_success": "Succeeded to activating {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "deactivate_plugin_success": "Succeeded to deactivate {{pluginName}}",
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
     "remove_plugin_success": "Succeeded to removing {{pluginName}}"
+  },
+  "forbidden_page": {
+    "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."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
 	"toaster": {
 	"toaster": {
-    "file_upload_succeeded": "文件上传成功",
     "file_upload_failed": "文件上传失败",
     "file_upload_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "switch_disable_link_sharing_success": "成功更新分享链接设置",
     "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.
 - Once we finished, press the "**Update**" button to publish the page.
     - We can also save it by `Ctrl(⌘) + S`.
     - 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="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">
   <div class="card-header bg-primary text-light">
@@ -58,7 +58,7 @@ We can display the content list using a table and `$lsx`.
 
 
 # Slack
 # 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.
 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.
 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 %>
 Email: <%- email %>
 Password: <%- password %>
 Password: <%- password %>
-(This password was auto generated. Update required at the first time you logging in)
+(このパスワードは自動生成されます。 初回ログイン時にアップデートが必要です。)
 
 
-We are waiting for you!
+あなたの参加を心よりお待ちしております。
 <%- url %>
 <%- 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 %>
 Email: <%- email %>
-New Password: <%- password %>
-(This password was auto generated. Update required at the first time you logging in)
+新しいパスワード: <%- password %>
+(このパスワードは自動生成されます。 初回ログイン時にアップデートが必要です。)
 
 
 --
 --
 <%- appTitle %>
 <%- 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 %>
 Email: <%- createdUser.email %>
 ====
 ====
 
 
-Please do some action with following URL:
+次のURLでアクションを起こしてください:
 <%- url %>/admin/users
 <%- url %>/admin/users
 
 
 
 

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

@@ -19,7 +19,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
     - `Ctrl(⌘) + S` でも保存できます
     - `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="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">Tips</div>
   <div class="card-header bg-primary text-light">Tips</div>
@@ -54,7 +54,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 
 
 # Slack
 # 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 に参加してください。  
 GROWI をより良いものにするために、是非 Slack に参加してください。  
 開発に関する議論を行っている他、導入時の質問等も受け付けています。
 開発に関する議論を行っている他、導入時の質問等も受け付けています。

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

@@ -20,7 +20,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 - 一旦我们完成了,按 "**更新**"按钮来发布页面。
 - 一旦我们完成了,按 "**更新**"按钮来发布页面。
     - 我们也可以通过`Ctrl(⌘) + S`来保存。
     - 我们也可以通过`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="mt-4 card border-primary">
   <div class="card-header bg-primary text-light">
   <div class="card-header bg-primary text-light">
@@ -58,7 +58,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 
 
 # Slack
 # 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。
 我们欢迎新人加入我们的slack频道,帮助改善Growi。
 除了讨论发展问题,我们也很乐意在你加入时回答你的问题。
 除了讨论发展问题,我们也很乐意在你加入时回答你的问题。

+ 0 - 1
apps/app/src/client/services/renderer/renderer.tsx

@@ -19,7 +19,6 @@ import { DrawioViewerWithEditButton } from '~/components/ReactMarkdownComponents
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { Header } from '~/components/ReactMarkdownComponents/Header';
 import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { LightBox } from '~/components/ReactMarkdownComponents/LightBox';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
 import { RichAttachment } from '~/components/ReactMarkdownComponents/RichAttachment';
-// eslint-disable-next-line import/no-cycle
 import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
 import { SlideViewer } from '~/components/ReactMarkdownComponents/SlideViewer';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import { TableWithEditButton } from '~/components/ReactMarkdownComponents/TableWithEditButton';
 import * as mermaid from '~/features/mermaid';
 import * as mermaid from '~/features/mermaid';

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

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

@@ -70,34 +70,38 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         )}
         )}
       </div>
       </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>
         </span>
       </p>
       </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 && (
       {!isLoading && (
         <>
         <>
@@ -103,7 +105,7 @@ const QuestionnaireSettings = (): JSX.Element => {
             </div>
             </div>
           </div>
           </div>
 
 
-          <AdminUpdateButtonRow onClick={onSubmitHandler}/>
+          <AdminUpdateButtonRow onClick={onSubmitHandler} />
         </>
         </>
       )}
       )}
     </div>
     </div>

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

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

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

@@ -86,22 +86,22 @@ export const AdminNavigation = (): JSX.Element => {
     return (
     return (
       <>
       <>
         {/* eslint-disable no-multi-spaces */}
         {/* 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
         {growiCloudUri != null && growiAppIdForGrowiCloud != null
           && (
           && (
             <a
             <a

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

@@ -1,4 +1,4 @@
-import React, { useCallback, useMemo, useState } from 'react';
+import React, { useCallback, useState } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
@@ -26,10 +26,6 @@ const CustomizeLogoSetting = (): JSX.Element => {
   const [isDefaultLogoSelected, setIsDefaultLogoSelected] = useState<boolean>(isDefaultLogo ?? true);
   const [isDefaultLogoSelected, setIsDefaultLogoSelected] = useState<boolean>(isDefaultLogo ?? true);
   const [retrieveError, setRetrieveError] = useState<any>();
   const [retrieveError, setRetrieveError] = useState<any>();
 
 
-  const currentLogo = useMemo(() => {
-    return isDefaultLogo ? DEFAULT_LOGO : CUSTOMIZED_LOGO;
-  }, [isDefaultLogo]);
-
   const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
   const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     if (e.target.files != null && e.target.files.length > 0) {
     if (e.target.files != null && e.target.files.length > 0) {
       const reader = new FileReader();
       const reader = new FileReader();
@@ -129,7 +125,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
                     {isCustomizedLogoUploaded && (
                     {isCustomizedLogoUploaded && (
                       <>
                       <>
                         <p>
                         <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>
                         </p>
                         <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
                         <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
                           { t('admin:customize_settings.delete_logo') }
                           { 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>
           </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>
             <i className="fa fa-fw fa-chevron-right" aria-hidden="true"></i>
             Example for Google Tag Manager
             Example for Google Tag Manager
           </a>
           </a>
           <div className="collapse" id="collapseExampleHtml">
           <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"
               {`<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
   height="0"
   height="0"

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

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

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

@@ -59,9 +59,9 @@ export const CustomizeTitle: FC = () => {
 
 
         {/* TODO i18n */}
         {/* TODO i18n */}
         <div className="form-text text-muted col-12">
         <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 />
           <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>
         <div className="form-group col-12">
         <div className="form-group col-12">
           <input
           <input

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

@@ -35,7 +35,7 @@ export const ThemeColorBox = (props: Props): JSX.Element => {
         </svg>
         </svg>
       </a>
       </a>
       <span className="theme-option-name"><b>{ name }</b></span>
       <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>
     </div>
   );
   );
 
 

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

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

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

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  ChangeEvent, useCallback, useEffect, useState,
+  useCallback, useEffect, useState,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
@@ -217,7 +217,7 @@ const G2GDataTransfer = (): JSX.Element => {
             onChangeGcsBucket={onChangeGcsBucketHandler}
             onChangeGcsBucket={onChangeGcsBucketHandler}
             onChangeGcsUploadNamespace={onChangeGcsUploadNamespaceHandler}
             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
           <G2GDataTransferExportForm
             allCollectionNames={collections}
             allCollectionNames={collections}
             selectedCollections={selectedCollections}
             selectedCollections={selectedCollections}
@@ -246,12 +246,12 @@ const G2GDataTransfer = (): JSX.Element => {
       </form>
       </form>
 
 
       {isTransferring && (
       {isTransferring && (
-        <div className='border rounded p-4'>
+        <div className="border rounded p-4">
           <div>
           <div>
-            <G2GDataTransferStatusIcon className='mr-2 mb-2' status={g2gProgress.mongo} /> MongoDB
+            <G2GDataTransferStatusIcon className="mr-2 mb-2" status={g2gProgress.mongo} /> MongoDB
           </div>
           </div>
           <div>
           <div>
-            <G2GDataTransferStatusIcon className='mr-2' status={g2gProgress.attachments} /> Attachments
+            <G2GDataTransferStatusIcon className="mr-2" status={g2gProgress.attachments} /> Attachments
           </div>
           </div>
         </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) {
     if (errors.length === 0) {
       return <></>;
       return <></>;
     }
     }
@@ -130,8 +130,8 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     return (
     return (
       <div className="alert alert-warning">
       <div className="alert alert-warning">
         <ul>
         <ul>
-          {errors.map((error, i) => {
-            return <li key={i}>{error}</li>;
+          {errors.map((error) => {
+            return <li>{error.message}</li>;
           })}
           })}
         </ul>
         </ul>
       </div>
       </div>
@@ -162,7 +162,7 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
     });
     });
 
 
     // TODO: エラー対応
     // TODO: エラー対応
-    return <GroupImportItems groupList={collectionNames} groupName='Other' errors={[]} />;
+    return <GroupImportItems groupList={collectionNames} groupName="Other" errors={[]} />;
   };
   };
 
 
   const configurationModal = useMemo(() => {
   const configurationModal = useMemo(() => {
@@ -224,9 +224,9 @@ const G2GDataTransferExportForm = (props: Props): JSX.Element => {
       </div>
       </div>
 
 
       {/* TODO: エラー追加 */}
       {/* 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 />
       <OtherImportItems />
 
 
       {configurationModal}
       {configurationModal}

+ 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 { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 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]);
 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">
       <p className="card well">
         {/* eslint-disable-next-line react/no-danger */}
         {/* eslint-disable-next-line react/no-danger */}
         <span dangerouslySetInnerHTML={{ __html: t('notification_settings.link_notification_help') }} />
         <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="col-md-8 offset-md-2">
           <div className="custom-control custom-checkbox custom-checkbox-success">
           <div className="custom-control custom-checkbox custom-checkbox-success">
             <input
             <input
@@ -47,14 +48,17 @@ const GlobalNotification = (props) => {
               className="custom-control-input"
               className="custom-control-input"
               type="checkbox"
               type="checkbox"
               checked={adminNotificationContainer.state.isNotificationForOwnerPageEnabled || false}
               checked={adminNotificationContainer.state.isNotificationForOwnerPageEnabled || false}
-              onChange={() => { adminNotificationContainer.switchIsNotificationForOwnerPageEnabled() } } />
+              onChange={() => { adminNotificationContainer.switchIsNotificationForOwnerPageEnabled() }}
+            />
             <label className="custom-control-label" htmlFor="isNotificationForOwnerPageEnabled">
             <label className="custom-control-label" htmlFor="isNotificationForOwnerPageEnabled">
               {/* eslint-disable-next-line react/no-danger */}
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.just_me_notification_help') }} />
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.just_me_notification_help') }} />
             </label>
             </label>
           </div>
           </div>
         </div>
         </div>
-      </div><div className="row mb-4">
+      </div>
+
+      <div className="row mb-4">
         <div className="col-md-8 offset-md-2">
         <div className="col-md-8 offset-md-2">
           <div className="custom-control custom-checkbox custom-checkbox-success">
           <div className="custom-control custom-checkbox custom-checkbox-success">
             <input
             <input
@@ -62,7 +66,8 @@ const GlobalNotification = (props) => {
               className="custom-control-input"
               className="custom-control-input"
               type="checkbox"
               type="checkbox"
               checked={adminNotificationContainer.state.isNotificationForGroupPageEnabled || false}
               checked={adminNotificationContainer.state.isNotificationForGroupPageEnabled || false}
-              onChange={() => { adminNotificationContainer.switchIsNotificationForGroupPageEnabled() } } />
+              onChange={() => { adminNotificationContainer.switchIsNotificationForGroupPageEnabled() }}
+            />
             <label className="custom-control-label" htmlFor="isNotificationForGroupPageEnabled">
             <label className="custom-control-label" htmlFor="isNotificationForGroupPageEnabled">
               {/* eslint-disable-next-line react/no-danger */}
               {/* eslint-disable-next-line react/no-danger */}
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.group_notification_help') }} />
               <span dangerouslySetInnerHTML={{ __html: t('notification_settings.group_notification_help') }} />
@@ -81,13 +86,16 @@ const GlobalNotification = (props) => {
           </button>
           </button>
         </div>
         </div>
       </div>
       </div>
+
       <h2 className="border-bottom mb-5">{t('notification_settings.notification_list')}
       <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>
         <thead>
           <tr>
           <tr>
             <th>ON/OFF</th>
             <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 { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
@@ -7,29 +7,16 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 const DeleteAllShareLinksModal = React.memo((props) => {
 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 (
   return (
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">

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

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

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

@@ -470,7 +470,7 @@ class SecuritySetting extends React.Component {
             </div>
             </div>
             <p
             <p
               className="form-text text-muted small"
               className="form-text text-muted small"
-              dangerouslySetInnerHTML={{ __html: t('security_settings.user_homepage_deletion.when_deleting_a_user_the_user_homepage_is_also_deleted') }}
+              dangerouslySetInnerHTML={{ __html: t('security_settings.user_homepage_deletion.desc') }}
             />
             />
           </div>
           </div>
         </div>
         </div>

+ 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 { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 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 { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
@@ -7,28 +7,16 @@ import {
 } from 'reactstrap';
 } from 'reactstrap';
 
 
 const DeleteSlackBotSettingsModal = React.memo((props) => {
 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 (
   return (
     <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
     <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 */
 /* eslint-disable react/prop-types */
-import React, { useState, useCallback } from 'react';
+import React, { useState } from 'react';
 
 
 import { SlackbotType } from '@growi/slack';
 import { SlackbotType } from '@growi/slack';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 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 { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 import { toastSuccess, toastError } from '~/client/util/toastr';

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

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

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

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

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

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

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

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

+ 8 - 10
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -174,7 +174,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
           bookmarkedPage={bookmark.page}
           bookmarkedPage={bookmark.page}
           level={level + 1}
           level={level + 1}
           parentFolder={bookmarkFolder}
           parentFolder={bookmarkFolder}
-          canMoveToRoot={true}
+          canMoveToRoot
           onClickDeleteMenuItemHandler={onClickDeleteMenuItemHandler}
           onClickDeleteMenuItemHandler={onClickDeleteMenuItemHandler}
           bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
           bookmarkFolderTreeMutation={bookmarkFolderTreeMutation}
         />
         />
@@ -222,7 +222,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
         isDropable={isDropable}
         isDropable={isDropable}
       >
       >
         <li
         <li
-          className={'list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center'}
+          className="list-group-item list-group-item-action border-0 py-0 pr-3 d-flex align-items-center"
           onClick={loadChildFolder}
           onClick={loadChildFolder}
           style={{ paddingLeft }}
           style={{ paddingLeft }}
         >
         >
@@ -239,11 +239,9 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
               </button>
               </button>
             )}
             )}
           </div>
           </div>
-          {
-            <div>
-              <FolderIcon isOpen={isOpen} />
-            </div>
-          }
+          <div>
+            <FolderIcon isOpen={isOpen} />
+          </div>
           {isRenameAction ? (
           {isRenameAction ? (
             <BookmarkFolderNameInput
             <BookmarkFolderNameInput
               onClickOutside={() => setIsRenameAction(false)}
               onClickOutside={() => setIsRenameAction(false)}
@@ -252,8 +250,8 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
             />
             />
           ) : (
           ) : (
             <>
             <>
-              <div className='grw-foldertree-title-anchor pl-2' >
-                <p className={'text-truncate m-auto '}>{name}</p>
+              <div className="grw-foldertree-title-anchor pl-2">
+                <p className="text-truncate m-auto ">{name}</p>
               </div>
               </div>
             </>
             </>
           )}
           )}
@@ -276,7 +274,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
               {/* Maximum folder hierarchy of 2 levels */}
               {/* Maximum folder hierarchy of 2 levels */}
               {!(bookmarkFolder.parent != null) && (
               {!(bookmarkFolder.parent != null) && (
                 <button
                 <button
-                  id='create-bookmark-folder-button'
+                  id="create-bookmark-folder-button"
                   type="button"
                   type="button"
                   className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
                   className="border-0 rounded btn btn-page-item-control p-0 grw-visible-on-hover"
                   onClick={onClickPlusButton}
                   onClick={onClickPlusButton}

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

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

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

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

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

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

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

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

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

@@ -102,7 +102,7 @@ export const BookmarkFolderTree: React.FC<Props> = (props: Props) => {
   // };
   // };
 
 
   return (
   return (
-    <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}`} >
+    <div className={`grw-folder-tree-container ${styles['grw-folder-tree-container']}`}>
       <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
       <ul className={`grw-foldertree ${styles['grw-foldertree']} list-group px-2 py-2`}>
         {bookmarkFolders?.map((bookmarkFolder) => {
         {bookmarkFolders?.map((bookmarkFolder) => {
           return (
           return (

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

@@ -164,7 +164,7 @@ export const BookmarkItem = (props: Props): JSX.Element => {
           )
           )
           : <PageListItemS page={bookmarkedPage} pageTitle={pageTitle} isNarrowView />}
           : <PageListItemS page={bookmarkedPage} pageTitle={pageTitle} isNarrowView />}
 
 
-        <div className='grw-foldertree-control'>
+        <div className="grw-foldertree-control">
           <PageItemControl
           <PageItemControl
             pageId={bookmarkedPage._id}
             pageId={bookmarkedPage._id}
             isEnableActions
             isEnableActions

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

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

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

@@ -1,7 +1,6 @@
 import React, { FC } from 'react';
 import React, { FC } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
-import Link from 'next/link';
 
 
 export const CompleteUserRegistration: FC = () => {
 export const CompleteUserRegistration: FC = () => {
   const { t } = useTranslation();
   const { t } = useTranslation();
@@ -14,7 +13,7 @@ export const CompleteUserRegistration: FC = () => {
             <span>{t('login.registration_successful')}</span>
             <span>{t('login.registration_successful')}</span>
           </p>
           </p>
           {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
           {/* If the transition source is "/login", use <a /> tag since the transition will not occur if next/link is used. */}
-          <a href='/login'>
+          <a href="/login">
             <i className="icon-login mr-1" />{t('Sign in is here')}
             <i className="icon-login mr-1" />{t('Sign in is here')}
           </a>
           </a>
         </div>
         </div>

+ 1 - 1
apps/app/src/components/CompleteUserRegistrationForm.tsx

@@ -170,7 +170,7 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
               </div>
 
 
               <div className="input-group justify-content-center d-flex mt-5">
               <div className="input-group justify-content-center d-flex mt-5">
-                <button disabled={forceDisableForm || disableForm} className="btn btn-fill" id="register">
+                <button type="button" disabled={forceDisableForm || disableForm} className="btn btn-fill" id="register">
                   <div className="eff"></div>
                   <div className="eff"></div>
                   <span className="btn-label"><i className="icon-user-follow"></i></span>
                   <span className="btn-label"><i className="icon-user-follow"></i></span>
                   <span className="btn-label-text">{t('Create')}</span>
                   <span className="btn-label-text">{t('Create')}</span>

+ 1 - 1
apps/app/src/components/DeleteBookmarkFolderModal.tsx

@@ -48,7 +48,7 @@ const DeleteBookmarkFolderModal: FC = () => {
       <ModalBody>
       <ModalBody>
         <div className="form-group pb-1">
         <div className="form-group pb-1">
           <label>{ t('bookmark_folder.delete_modal.modal_body_description') }:</label><br />
           <label>{ t('bookmark_folder.delete_modal.modal_body_description') }:</label><br />
-          <FolderIcon isOpen={false}/> {deleteBookmarkFolderModalData?.bookmarkFolder?.name}
+          <FolderIcon isOpen={false} /> {deleteBookmarkFolderModalData?.bookmarkFolder?.name}
         </div>
         </div>
         {t('bookmark_folder.delete_modal.modal_body_alert')}
         {t('bookmark_folder.delete_modal.modal_body_alert')}
       </ModalBody>
       </ModalBody>

+ 1 - 5
apps/app/src/components/DescendantsPageList.tsx

@@ -82,10 +82,6 @@ const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element => {
     }
     }
   }, [onPagePutBacked, t]);
   }, [onPagePutBacked, t]);
 
 
-  function setPageNumber(selectedPageNumber) {
-    setActivePage(selectedPageNumber);
-  }
-
   if (pagingResult == null) {
   if (pagingResult == null) {
     return (
     return (
       <div className="wiki">
       <div className="wiki">
@@ -113,7 +109,7 @@ const DescendantsPageListSubstance = (props: SubstanceProps): JSX.Element => {
         <div className="my-4">
         <div className="my-4">
           <PaginationWrapper
           <PaginationWrapper
             activePage={activePage}
             activePage={activePage}
-            changePage={setPageNumber}
+            changePage={selectedPageNumber => setActivePage(selectedPageNumber)}
             totalItemsCount={pagingResult.totalCount}
             totalItemsCount={pagingResult.totalCount}
             pagingLimit={pagingResult.limit}
             pagingLimit={pagingResult.limit}
             align="center"
             align="center"

+ 3 - 2
apps/app/src/components/Hotkeys/Subscribers/EditPage.jsx

@@ -1,8 +1,9 @@
-import React, { useEffect } from 'react';
+import { useEffect } from 'react';
+
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import { EditorMode, useEditorMode } from '~/stores/ui';
 import { useIsEditable } from '~/stores/context';
 import { useIsEditable } from '~/stores/context';
+import { EditorMode, useEditorMode } from '~/stores/ui';
 
 
 const EditPage = (props) => {
 const EditPage = (props) => {
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();

+ 1 - 1
apps/app/src/components/Hotkeys/Subscribers/FocusToGlobalSearch.jsx

@@ -1,4 +1,4 @@
-import { FC, useEffect } from 'react';
+import { useEffect } from 'react';
 
 
 import { useIsEditable } from '~/stores/context';
 import { useIsEditable } from '~/stores/context';
 import { useGlobalSearchFormRef } from '~/stores/ui';
 import { useGlobalSearchFormRef } from '~/stores/ui';

+ 0 - 1
apps/app/src/components/Hotkeys/Subscribers/ShowStaffCredit.jsx

@@ -1,4 +1,3 @@
-import React from 'react';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
 import StaffCredit from '../../StaffCredit/StaffCredit';
 import StaffCredit from '../../StaffCredit/StaffCredit';

+ 4 - 2
apps/app/src/components/Icons/CompressIcon.tsx

@@ -2,7 +2,8 @@ import React from 'react';
 
 
 export const CompressIcon = ():JSX.Element => {
 export const CompressIcon = ():JSX.Element => {
   return (
   return (
-    <svg xmlns="http://www.w3.org/2000/svg"
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
       width="18"
       width="18"
       height="18"
       height="18"
       viewBox="0 0 45 45"
       viewBox="0 0 45 45"
@@ -11,7 +12,8 @@ export const CompressIcon = ():JSX.Element => {
         fill="currentColor"
         fill="currentColor"
         d="M22.45 44v-7.9l-3.85 3.8-2.1-2.1 7.45-7.4 7.35 7.4-2.1
         d="M22.45 44v-7.9l-3.85 3.8-2.1-2.1 7.45-7.4 7.35 7.4-2.1
             2.1-3.75-3.8V44ZM8.05 27.5v-3H40v3Zm0-6.05v-3H40v3Zm15.9-5.85-7.4-7.4 2.1-2.1
             2.1-3.75-3.8V44ZM8.05 27.5v-3H40v3Zm0-6.05v-3H40v3Zm15.9-5.85-7.4-7.4 2.1-2.1
-            3.75 3.8V2h3v7.9l3.85-3.8 2.1 2.1Z"/>
+            3.75 3.8V2h3v7.9l3.85-3.8 2.1 2.1Z"
+      />
     </svg>
     </svg>
   );
   );
 };
 };

+ 2 - 1
apps/app/src/components/Icons/ExpandIcon.tsx

@@ -2,7 +2,8 @@ import React from 'react';
 
 
 export const ExpandIcon = (): JSX.Element => {
 export const ExpandIcon = (): JSX.Element => {
   return (
   return (
-    <svg xmlns="http://www.w3.org/2000/svg"
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
       width="18"
       width="18"
       height="18"
       height="18"
       viewBox="0 0 45 45"
       viewBox="0 0 45 45"

+ 6 - 4
apps/app/src/components/Icons/FolderIcon.tsx

@@ -10,12 +10,14 @@ export const FolderIcon = (props: Props): JSX.Element => {
     <>
     <>
       {!isOpen ? (
       {!isOpen ? (
         <svg
         <svg
-          width ="20"
-          height ="20"
+          width="20"
+          height="20"
           viewBox="0 0 24 24"
           viewBox="0 0 24 24"
         >
         >
-          <path fill="currentColor"
-            d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z" />
+          <path
+            fill="currentColor"
+            d="M20,18H4V8H20M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z"
+          />
         </svg>
         </svg>
       ) : (
       ) : (
         <svg
         <svg

+ 2 - 2
apps/app/src/components/InvitedForm.tsx

@@ -58,8 +58,8 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
       <>
       <>
         { loginErrors != null && loginErrors.length > 0 ? (
         { loginErrors != null && loginErrors.length > 0 ? (
           <p className="alert alert-danger">
           <p className="alert alert-danger">
-            { loginErrors.map((err, index) => {
-              return <span key={index}>{ t(err.message) }<br/></span>;
+            { loginErrors.map((err) => {
+              return <span>{ t(err.message) }<br /></span>;
             }) }
             }) }
           </p>
           </p>
         ) : (
         ) : (

+ 1 - 1
apps/app/src/components/Layout/AdminLayout.tsx

@@ -28,7 +28,7 @@ const AdminLayout = ({
   return (
   return (
     <RawLayout>
     <RawLayout>
       <div className={`admin-page ${styles['admin-page']}`}>
       <div className={`admin-page ${styles['admin-page']}`}>
-        <GrowiNavbar isGlobalSearchHidden={true} />
+        <GrowiNavbar isGlobalSearchHidden />
 
 
         <header className="py-0 container-fluid">
         <header className="py-0 container-fluid">
           <h1 className="title px-3">{componentTitle}</h1>
           <h1 className="title px-3">{componentTitle}</h1>

+ 1 - 1
apps/app/src/components/Layout/ShareLinkLayout.tsx

@@ -25,7 +25,7 @@ export const ShareLinkLayout = ({ children }: Props): JSX.Element => {
 
 
   return (
   return (
     <RawLayout className={className}>
     <RawLayout className={className}>
-      <GrowiNavbar isGlobalSearchHidden={true} />
+      <GrowiNavbar isGlobalSearchHidden />
 
 
       <div className="page-wrapper d-flex d-print-block">
       <div className="page-wrapper d-flex d-print-block">
         <div className="flex-fill mw-0">
         <div className="flex-fill mw-0">

+ 48 - 30
apps/app/src/components/LoginForm.tsx

@@ -18,7 +18,6 @@ import { CompleteUserRegistration } from './CompleteUserRegistration';
 
 
 import styles from './LoginForm.module.scss';
 import styles from './LoginForm.module.scss';
 
 
-
 type LoginFormProps = {
 type LoginFormProps = {
   username?: string,
   username?: string,
   name?: string,
   name?: string,
@@ -142,8 +141,9 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
     if (errors == null || errors.length === 0) return <></>;
     if (errors == null || errors.length === 0) return <></>;
     return (
     return (
       <div className="alert alert-danger">
       <div className="alert alert-danger">
-        {errors.map((err, index) => {
-          return <small key={index} dangerouslySetInnerHTML={{ __html: tWithOpt(err.message, err.args) }}></small>;
+        {errors.map((err) => {
+          // eslint-disable-next-line react/no-danger
+          return <small dangerouslySetInnerHTML={{ __html: tWithOpt(err.message, err.args) }}></small>;
         })}
         })}
       </div>
       </div>
     );
     );
@@ -154,12 +154,11 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
     if (errors == null || errors.length === 0) return <></>;
     if (errors == null || errors.length === 0) return <></>;
     return (
     return (
       <ul className="alert alert-danger">
       <ul className="alert alert-danger">
-        {errors.map((err, index) => {
-          return (
-            <li key={index} className={index > 0 ? 'mt-1' : ''}>
-              {tWithOpt(err.message, err.args)}
-            </li>);
-        })}
+        {errors.map((err, index) => (
+          <li className={index > 0 ? 'mt-1' : ''}>
+            {tWithOpt(err.message, err.args)}
+          </li>
+        ))}
       </ul>
       </ul>
     );
     );
   }, [tWithOpt]);
   }, [tWithOpt]);
@@ -181,13 +180,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
       <>
       <>
         {/* !! - DO NOT DELETE HIDDEN ELEMENT - !! -- 7.12 ryoji-s */}
         {/* !! - DO NOT DELETE HIDDEN ELEMENT - !! -- 7.12 ryoji-s */}
         {/* Import font-awesome to prevent MongoStore.js "Unable to find the session to touch" error */}
         {/* Import font-awesome to prevent MongoStore.js "Unable to find the session to touch" error */}
-        <div className='sr-only'>
+        <div className="sr-only">
           <i className="fa fa-spinner fa-pulse" />
           <i className="fa fa-spinner fa-pulse" />
         </div>
         </div>
         {/* !! - END OF HIDDEN ELEMENT - !! */}
         {/* !! - END OF HIDDEN ELEMENT - !! */}
         {isLdapSetupFailed && (
         {isLdapSetupFailed && (
           <div className="alert alert-warning small">
           <div className="alert alert-warning small">
-            <strong><i className="icon-fw icon-info"></i>{t('login.enabled_ldap_has_configuration_problem')}</strong><br/>
+            <strong><i className="icon-fw icon-info"></i>{t('login.enabled_ldap_has_configuration_problem')}</strong><br />
             <span dangerouslySetInnerHTML={{ __html: t('login.set_env_var_for_logs') }}></span>
             <span dangerouslySetInnerHTML={{ __html: t('login.set_env_var_for_logs') }}></span>
           </div>
           </div>
         )}
         )}
@@ -201,8 +200,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 <i className="icon-user"></i>
                 <i className="icon-user"></i>
               </span>
               </span>
             </div>
             </div>
-            <input type="text" className="form-control rounded-0" data-testid="tiUsernameForLogin" placeholder="Username or E-mail"
-              onChange={(e) => { setUsernameForLogin(e.target.value) }} name="usernameForLogin" />
+            <input
+              type="text"
+              className="form-control rounded-0"
+              data-testid="tiUsernameForLogin"
+              placeholder="Username or E-mail"
+              onChange={(e) => { setUsernameForLogin(e.target.value) }}
+              name="usernameForLogin"
+            />
             {isLdapStrategySetup && (
             {isLdapStrategySetup && (
               <div className="input-group-append">
               <div className="input-group-append">
                 <small className="input-group-text text-success">
                 <small className="input-group-text text-success">
@@ -218,8 +223,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 <i className="icon-lock"></i>
                 <i className="icon-lock"></i>
               </span>
               </span>
             </div>
             </div>
-            <input type="password" className="form-control rounded-0" data-testid="tiPasswordForLogin" placeholder="Password"
-              onChange={(e) => { setPasswordForLogin(e.target.value) }} name="passwordForLogin" />
+            <input
+              type="password"
+              className="form-control rounded-0"
+              data-testid="tiPasswordForLogin"
+              placeholder="Password"
+              onChange={(e) => { setPasswordForLogin(e.target.value) }}
+              name="passwordForLogin"
+            />
           </div>
           </div>
 
 
           <div className="input-group my-4">
           <div className="input-group my-4">
@@ -335,6 +346,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
       resetRegisterErrors();
       resetRegisterErrors();
 
 
       const { redirectTo } = res.data;
       const { redirectTo } = res.data;
+
       if (redirectTo != null) {
       if (redirectTo != null) {
         router.push(redirectTo);
         router.push(redirectTo);
       }
       }
@@ -387,13 +399,11 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
         {
         {
           registerErrors != null && registerErrors.length > 0 && (
           registerErrors != null && registerErrors.length > 0 && (
             <p className="alert alert-danger">
             <p className="alert alert-danger">
-              {registerErrors.map((err, index) => {
-                return (
-                  <span key={index}>
-                    {t(err.message)}<br/>
-                  </span>
-                );
-              })}
+              {registerErrors.map(err => (
+                <span>
+                  {t(err.message)}<br />
+                </span>
+              ))}
             </p>
             </p>
           )
           )
         }
         }
@@ -406,7 +416,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           )
           )
         }
         }
 
 
-        <form role="form" onSubmit={e => handleRegisterFormSubmit(e, registerAction) } id="register-form">
+        <form role="form" onSubmit={e => handleRegisterFormSubmit(e, registerAction)} id="register-form">
 
 
           {!isEmailAuthenticationEnabled && (
           {!isEmailAuthenticationEnabled && (
             <div>
             <div>
@@ -437,13 +447,15 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                   </span>
                   </span>
                 </div>
                 </div>
                 {/* name */}
                 {/* name */}
-                <input type="text"
+                <input
+                  type="text"
                   className="form-control rounded-0"
                   className="form-control rounded-0"
                   onChange={(e) => { setNameForRegister(e.target.value) }}
                   onChange={(e) => { setNameForRegister(e.target.value) }}
                   placeholder={t('Name')}
                   placeholder={t('Name')}
                   name="name"
                   name="name"
                   defaultValue={props.name}
                   defaultValue={props.name}
-                  required />
+                  required
+                />
               </div>
               </div>
             </div>
             </div>
           )}
           )}
@@ -455,7 +467,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               </span>
               </span>
             </div>
             </div>
             {/* email */}
             {/* email */}
-            <input type="email"
+            <input
+              type="email"
               disabled={!isMailerSetup && isEmailAuthenticationEnabled}
               disabled={!isMailerSetup && isEmailAuthenticationEnabled}
               className="form-control rounded-0"
               className="form-control rounded-0"
               onChange={(e) => { setEmailForRegister(e.target.value) }}
               onChange={(e) => { setEmailForRegister(e.target.value) }}
@@ -490,12 +503,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                   </span>
                   </span>
                 </div>
                 </div>
                 {/* Password */}
                 {/* Password */}
-                <input type="password"
+                <input
+                  type="password"
                   className="form-control rounded-0"
                   className="form-control rounded-0"
                   onChange={(e) => { setPasswordForRegister(e.target.value) }}
                   onChange={(e) => { setPasswordForRegister(e.target.value) }}
                   placeholder={t('Password')}
                   placeholder={t('Password')}
                   name="password"
                   name="password"
-                  required />
+                  required
+                />
               </div>
               </div>
             </div>
             </div>
           )}
           )}
@@ -503,6 +518,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {/* Sign up button (submit) */}
           {/* Sign up button (submit) */}
           <div className="input-group justify-content-center my-4">
           <div className="input-group justify-content-center my-4">
             <button
             <button
+              type="submit"
               className="btn btn-fill rounded-0"
               className="btn btn-fill rounded-0"
               id="register"
               id="register"
               disabled={(!isMailerSetup && isEmailAuthenticationEnabled) || isLoading}
               disabled={(!isMailerSetup && isEmailAuthenticationEnabled) || isLoading}
@@ -525,7 +541,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
               id="login"
               id="login"
               className="link-switch"
               className="link-switch"
               style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
               style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
-              onClick={switchForm}>
+              onClick={switchForm}
+            >
               <i className="icon-fw icon-login"></i>
               <i className="icon-fw icon-login"></i>
               {t('Sign in is here')}
               {t('Sign in is here')}
             </a>
             </a>
@@ -566,7 +583,8 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                       id="register"
                       id="register"
                       className="link-switch"
                       className="link-switch"
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
-                      onClick={switchForm}>
+                      onClick={switchForm}
+                    >
                       <i className="ti ti-check-box"></i> {t('Sign up is here')}
                       <i className="ti ti-check-box"></i> {t('Sign up is here')}
                     </a>
                     </a>
                   </div>
                   </div>

+ 1 - 1
apps/app/src/components/Me/AssociateModal.tsx

@@ -61,7 +61,7 @@ const AssociateModal = (props: Props): JSX.Element => {
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
         <div>
         <div>
-          <Nav tabs className='mb-2'>
+          <Nav tabs className="mb-2">
             <NavLink
             <NavLink
               className={activeTab === 1 ? 'active' : ''}
               className={activeTab === 1 ? 'active' : ''}
               onClick={() => setActiveTab(1)}
               onClick={() => setActiveTab(1)}

+ 15 - 9
apps/app/src/components/Me/OtherSettings.tsx

@@ -44,9 +44,11 @@ const OtherSettings = (): JSX.Element => {
     <>
     <>
       <h2 className="border-bottom my-4">{t('questionnaire.settings')}</h2>
       <h2 className="border-bottom my-4">{t('questionnaire.settings')}</h2>
 
 
-      {isLoadingCurrentUser && <div className="text-muted text-center mb-5">
-        <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
-      </div>}
+      {isLoadingCurrentUser && (
+        <div className="text-muted text-center mb-5">
+          <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
+        </div>
+      )}
 
 
       <div className="form-group row">
       <div className="form-group row">
         <div className="offset-md-3 col-md-6 text-left">
         <div className="offset-md-3 col-md-6 text-left">
@@ -68,9 +70,11 @@ const OtherSettings = (): JSX.Element => {
               <p className="form-text text-muted small">
               <p className="form-text text-muted small">
                 {t('questionnaire.personal_settings_explanation')}
                 {t('questionnaire.personal_settings_explanation')}
               </p>
               </p>
-              {!growiIsQuestionnaireEnabled && <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-toggle-wrapper">
-                {t('questionnaire.disabled_by_admin')}
-              </UncontrolledTooltip> }
+              {!growiIsQuestionnaireEnabled && (
+                <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-toggle-wrapper">
+                  {t('questionnaire.disabled_by_admin')}
+                </UncontrolledTooltip>
+              ) }
             </div>
             </div>
           )}
           )}
         </div>
         </div>
@@ -90,9 +94,11 @@ const OtherSettings = (): JSX.Element => {
               {t('Update')}
               {t('Update')}
             </button>
             </button>
           </span>
           </span>
-          {!growiIsQuestionnaireEnabled && <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-update-btn-wrapper">
-            {t('questionnaire.disabled_by_admin')}
-          </UncontrolledTooltip>}
+          {!growiIsQuestionnaireEnabled && (
+            <UncontrolledTooltip placement="bottom" target="grw-questionnaire-settings-update-btn-wrapper">
+              {t('questionnaire.disabled_by_admin')}
+            </UncontrolledTooltip>
+          )}
         </div>
         </div>
       </div>
       </div>
     </>
     </>

+ 3 - 3
apps/app/src/components/Navbar/AuthorInfo.tsx

@@ -39,11 +39,11 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
 
 
   if (locate === 'footer') {
   if (locate === 'footer') {
     try {
     try {
-      return <p>{infoLabelForFooter} {format(new Date(date), formatType)} by <UserPicture user={user} size="sm"/> {userLabel}</p>;
+      return <p>{infoLabelForFooter} {format(new Date(date), formatType)} by <UserPicture user={user} size="sm" /> {userLabel}</p>;
     }
     }
     catch (err) {
     catch (err) {
       if (err instanceof RangeError) {
       if (err instanceof RangeError) {
-        return <p>{nullinfoLabelForFooter} <UserPicture user={user} size="sm"/> {userLabel}</p>;
+        return <p>{nullinfoLabelForFooter} <UserPicture user={user} size="sm" /> {userLabel}</p>;
       }
       }
       return <></>;
       return <></>;
     }
     }
@@ -61,7 +61,7 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
   return (
   return (
     <div className="d-flex align-items-center">
     <div className="d-flex align-items-center">
       <div className="mr-2">
       <div className="mr-2">
-        <UserPicture user={user} size="sm"/>
+        <UserPicture user={user} size="sm" />
       </div>
       </div>
       <div>
       <div>
         <div>{infoLabelForSubNav} {userLabel}</div>
         <div>{infoLabelForSubNav} {userLabel}</div>

+ 4 - 5
apps/app/src/components/Navbar/GlobalSearch.tsx

@@ -9,6 +9,7 @@ import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
 
 
 import { IFocusable } from '~/client/interfaces/focusable';
 import { IFocusable } from '~/client/interfaces/focusable';
+import { useKeywordManager } from '~/client/services/search-operation';
 import { IPageWithSearchMeta } from '~/interfaces/search';
 import { IPageWithSearchMeta } from '~/interfaces/search';
 import {
 import {
   useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
   useIsSearchScopeChildrenAsDefault, useIsSearchServiceReachable,
@@ -46,6 +47,8 @@ export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   const [isScopeChildren, setScopeChildren] = useState<boolean|undefined>(isSearchScopeChildrenAsDefault ?? false);
   const [isScopeChildren, setScopeChildren] = useState<boolean|undefined>(isSearchScopeChildrenAsDefault ?? false);
   const [isFocused, setFocused] = useState<boolean>(false);
   const [isFocused, setFocused] = useState<boolean>(false);
 
 
+  const { pushState } = useKeywordManager();
+
   useEffect(() => {
   useEffect(() => {
     setScopeChildren(isSearchScopeChildrenAsDefault);
     setScopeChildren(isSearchScopeChildrenAsDefault);
   }, [isSearchScopeChildrenAsDefault]);
   }, [isSearchScopeChildrenAsDefault]);
@@ -63,17 +66,13 @@ export const GlobalSearch = (props: GlobalSearchProps): JSX.Element => {
   }, [returnPathForURL, router]);
   }, [returnPathForURL, router]);
 
 
   const search = useCallback(() => {
   const search = useCallback(() => {
-    const url = new URL(window.location.href);
-    url.pathname = '/_search';
-
     // construct search query
     // construct search query
     let q = text;
     let q = text;
     if (isScopeChildren) {
     if (isScopeChildren) {
       q += ` prefix:${currentPagePath ?? window.location.pathname}`;
       q += ` prefix:${currentPagePath ?? window.location.pathname}`;
     }
     }
-    url.searchParams.append('q', q);
 
 
-    router.push(url.href);
+    pushState(q);
   }, [currentPagePath, isScopeChildren, router, text]);
   }, [currentPagePath, isScopeChildren, router, text]);
 
 
   const scopeLabel = isScopeChildren
   const scopeLabel = isScopeChildren

+ 15 - 9
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -339,11 +339,14 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
         return (
         return (
           <>
           <>
             {!isReadOnlyUser
             {!isReadOnlyUser
-              && <CreateTemplateMenuItems
-                onClickTemplateMenuItem={templateMenuItemClickHandler}
-              />
+              && (
+                <CreateTemplateMenuItems
+                  onClickTemplateMenuItem={templateMenuItemClickHandler}
+                />
+              )
             }
             }
-          </>);
+          </>
+        );
       }
       }
       return (
       return (
         <>
         <>
@@ -352,11 +355,14 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
             revisionId={revisionId}
             revisionId={revisionId}
             isLinkSharingDisabled={isLinkSharingDisabled}
             isLinkSharingDisabled={isLinkSharingDisabled}
           />
           />
-          {!isReadOnlyUser && <>
-            <DropdownItem divider />
-            <CreateTemplateMenuItems
-              onClickTemplateMenuItem={templateMenuItemClickHandler}
-            /></>
+          {!isReadOnlyUser && (
+            <>
+              <DropdownItem divider />
+              <CreateTemplateMenuItems
+                onClickTemplateMenuItem={templateMenuItemClickHandler}
+              />
+            </>
+          )
           }
           }
         </>
         </>
       );
       );

+ 15 - 13
apps/app/src/components/Navbar/GrowiNavbar.tsx

@@ -49,18 +49,20 @@ const NavbarRight = memo((): JSX.Element => {
         </li>
         </li>
 
 
         {!isReadOnlyUser
         {!isReadOnlyUser
-          && <li className="nav-item d-none d-md-block">
-            <button
-              className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
-              type="button"
-              ref={newButtonRef}
-              data-testid="newPageBtn"
-              onClick={() => openCreateModal(currentPagePath || '')}
-            >
-              <i className="icon-pencil mr-2"></i>
-              <span className="d-none d-lg-block">{ t('commons:New') }</span>
-            </button>
-          </li>
+          && (
+            <li className="nav-item d-none d-md-block">
+              <button
+                className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
+                type="button"
+                ref={newButtonRef}
+                data-testid="newPageBtn"
+                onClick={() => openCreateModal(currentPagePath || '')}
+              >
+                <i className="icon-pencil mr-2"></i>
+                <span className="d-none d-lg-block">{ t('commons:New') }</span>
+              </button>
+            </li>
+          )
         }
         }
 
 
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
         <li className="grw-apperance-mode-dropdown nav-item dropdown">
@@ -131,7 +133,7 @@ const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
   return isDefaultLogo
   return isDefaultLogo
     ? <GrowiLogo />
     ? <GrowiLogo />
     // eslint-disable-next-line @next/next/no-img-element
     // eslint-disable-next-line @next/next/no-img-element
-    : (<img src='/attachment/brand-logo' alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
+    : (<img src="/attachment/brand-logo" alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
 });
 });
 
 
 GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
 GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';

+ 1 - 1
apps/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -83,7 +83,7 @@ export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProp
   }
   }
 
 
   return (
   return (
-    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher" >
+    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher">
       <div
       <div
         id="grw-subnav-fixed-container"
         id="grw-subnav-fixed-container"
         className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
         className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}

+ 7 - 4
apps/app/src/components/Navbar/PersonalDropdown.jsx

@@ -24,9 +24,11 @@ const PersonalDropdown = () => {
   useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
   useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
 
 
   if (currentUser == null) {
   if (currentUser == null) {
-    return <div className="text-muted text-center mb-5">
-      <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
-    </div>;
+    return (
+      <div className="text-muted text-center mb-5">
+        <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
+      </div>
+    );
   }
   }
 
 
   const logoutHandler = async() => {
   const logoutHandler = async() => {
@@ -87,7 +89,8 @@ const PersonalDropdown = () => {
           data-testid="grw-proactive-questionnaire-modal-toggle-btn"
           data-testid="grw-proactive-questionnaire-modal-toggle-btn"
           type="button"
           type="button"
           className="dropdown-item"
           className="dropdown-item"
-          onClick={() => setQuestionnaireModalOpen(true)}>
+          onClick={() => setQuestionnaireModalOpen(true)}
+        >
           <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
           <i className="icon-fw icon-pencil"></i>{t('personal_dropdown.feedback')}
         </button>
         </button>
 
 

+ 1 - 1
apps/app/src/components/NotFoundPage.tsx

@@ -21,7 +21,7 @@ const NotFoundPage = (props: NotFoundPageProps): JSX.Element => {
     return {
     return {
       pagelist: {
       pagelist: {
         Icon: PageListIcon,
         Icon: PageListIcon,
-        Content: () => <DescendantsPageList path={path}/>,
+        Content: () => <DescendantsPageList path={path} />,
         i18n: t('page_list'),
         i18n: t('page_list'),
       },
       },
       timeLine: {
       timeLine: {

+ 11 - 2
apps/app/src/components/Page/RenderTagLabels.tsx

@@ -2,6 +2,8 @@ import React from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
+import { useKeywordManager } from '~/client/services/search-operation';
+
 import { NotAvailableForGuest } from '../NotAvailableForGuest';
 import { NotAvailableForGuest } from '../NotAvailableForGuest';
 import { NotAvailableForReadOnlyUser } from '../NotAvailableForReadOnlyUser';
 import { NotAvailableForReadOnlyUser } from '../NotAvailableForReadOnlyUser';
 
 
@@ -15,6 +17,8 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
   const { tags, isTagLabelsDisabled, openEditorModal } = props;
   const { tags, isTagLabelsDisabled, openEditorModal } = props;
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
+  const { pushState } = useKeywordManager();
+
   function openEditorHandler() {
   function openEditorHandler() {
     if (openEditorModal == null) {
     if (openEditorModal == null) {
       return;
       return;
@@ -28,7 +32,12 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
     <>
     <>
       {tags.map((tag) => {
       {tags.map((tag) => {
         return (
         return (
-          <a key={tag} href={`/_search?q=tag:${tag}`} className="grw-tag-label badge badge-secondary mr-2">
+          <a
+            key={tag}
+            type="button"
+            className="grw-tag-label badge badge-secondary mr-2"
+            onClick={() => pushState(`tag:${tag}`)}
+          >
             {tag}
             {tag}
           </a>
           </a>
         );
         );
@@ -41,7 +50,7 @@ const RenderTagLabels = React.memo((props: RenderTagLabelsProps) => {
               onClick={openEditorHandler}
               onClick={openEditorHandler}
             >
             >
               { isTagsEmpty && <>{ t('Add tags for this page') }</>}
               { isTagsEmpty && <>{ t('Add tags for this page') }</>}
-              <i className={`icon-plus ${isTagsEmpty && 'ml-1'}`}/>
+              <i className={`icon-plus ${isTagsEmpty && 'ml-1'}`} />
             </a>
             </a>
           </div>
           </div>
         </NotAvailableForReadOnlyUser>
         </NotAvailableForReadOnlyUser>

+ 1 - 1
apps/app/src/components/Page/RevisionRenderer.tsx

@@ -21,7 +21,7 @@ const ErrorFallback: React.FC<FallbackProps> = React.memo(({ error, resetErrorBo
     <div role="alert">
     <div role="alert">
       <p>Something went wrong:</p>
       <p>Something went wrong:</p>
       <pre>{error.message}</pre>
       <pre>{error.message}</pre>
-      <button className='btn btn-primary' onClick={resetErrorBoundary}>Reload</button>
+      <button type="button" className="btn btn-primary" onClick={resetErrorBoundary}>Reload</button>
     </div>
     </div>
   );
   );
 });
 });

+ 11 - 16
apps/app/src/components/Page/TagEditModal.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import {
 import {
@@ -15,32 +15,27 @@ type Props = {
 };
 };
 
 
 function TagEditModal(props: Props): JSX.Element {
 function TagEditModal(props: Props): JSX.Element {
+  const { onClose, onTagsUpdated } = props;
+
   const [tags, setTags] = useState<string[]>([]);
   const [tags, setTags] = useState<string[]>([]);
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
-  function onTagsUpdatedByTagsInput(tags: string[]) {
-    setTags(tags);
-  }
-
   useEffect(() => {
   useEffect(() => {
     setTags(props.tags);
     setTags(props.tags);
   }, [props.tags]);
   }, [props.tags]);
 
 
-  function closeModalHandler() {
-    if (props.onClose == null) {
-      return;
-    }
-    props.onClose();
-  }
+  const closeModalHandler = useCallback(() => {
+    onClose?.();
+  }, [onClose]);
 
 
-  function handleSubmit() {
-    if (props.onTagsUpdated == null) {
+  const handleSubmit = useCallback(() => {
+    if (onTagsUpdated == null) {
       return;
       return;
     }
     }
 
 
-    props.onTagsUpdated(tags);
+    onTagsUpdated(tags);
     closeModalHandler();
     closeModalHandler();
-  }
+  }, [closeModalHandler, onTagsUpdated, tags]);
 
 
   return (
   return (
     <Modal isOpen={props.isOpen} toggle={closeModalHandler} id="edit-tag-modal" autoFocus={false}>
     <Modal isOpen={props.isOpen} toggle={closeModalHandler} id="edit-tag-modal" autoFocus={false}>
@@ -48,7 +43,7 @@ function TagEditModal(props: Props): JSX.Element {
         {t('tag_edit_modal.edit_tags')}
         {t('tag_edit_modal.edit_tags')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
-        <TagsInput tags={tags} onTagsUpdated={onTagsUpdatedByTagsInput} autoFocus />
+        <TagsInput tags={tags} onTagsUpdated={tags => setTags(tags)} autoFocus />
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         <button type="button" className="btn btn-primary" onClick={handleSubmit}>
         <button type="button" className="btn btn-primary" onClick={handleSubmit}>

+ 1 - 1
apps/app/src/components/Page/TagLabels.tsx

@@ -37,7 +37,7 @@ export const TagLabels:FC<Props> = (props: Props) => {
   return (
   return (
     <>
     <>
       <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`} data-testid="grw-tag-labels">
       <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`} data-testid="grw-tag-labels">
-        <i className="tag-icon icon-tag mr-2"/>
+        <i className="tag-icon icon-tag mr-2" />
         <RenderTagLabels
         <RenderTagLabels
           tags={tags}
           tags={tags}
           openEditorModal={openEditorModal}
           openEditorModal={openEditorModal}

+ 1 - 1
apps/app/src/components/PageAccessoriesModal/ShareLink/ShareLinkList.tsx

@@ -50,7 +50,7 @@ const ShareLinkTr = (props: ShareLinkTrProps): JSX.Element => {
         {shareLink.description}
         {shareLink.description}
       </td>
       </td>
       <td style={{ verticalAlign: 'middle' }}>
       <td style={{ verticalAlign: 'middle' }}>
-        {shareLink.expiredAt && <span >{dateFnsFormat(new Date(shareLink.expiredAt), 'yyyy-MM-dd HH:mm')}</span>}
+        {shareLink.expiredAt && <span>{dateFnsFormat(new Date(shareLink.expiredAt), 'yyyy-MM-dd HH:mm')}</span>}
       </td>
       </td>
       <td style={{ maxWidth: '0', textAlign: 'center' }}>
       <td style={{ maxWidth: '0', textAlign: 'center' }}>
         <button className="btn btn-outline-warning" type="button" onClick={onDelete}>
         <button className="btn btn-outline-warning" type="button" onClick={onDelete}>

+ 5 - 3
apps/app/src/components/PageAlert/PageRedirectedAlert.tsx

@@ -32,9 +32,11 @@ export const PageRedirectedAlert = React.memo((): JSX.Element => {
   }
   }
 
 
   if (isUnlinked) {
   if (isUnlinked) {
-    return (<div className="alert alert-info d-edit-none py-3 px-4">
-      <strong>{ t('Unlinked') }: </strong> { t('page_page.notice.unlinked') }
-    </div>);
+    return (
+      <div className="alert alert-info d-edit-none py-3 px-4">
+        <strong>{ t('Unlinked') }: </strong> { t('page_page.notice.unlinked') }
+      </div>
+    );
   }
   }
 
 
   return (
   return (

+ 7 - 7
apps/app/src/components/PageCreateModal.jsx

@@ -1,5 +1,5 @@
 import React, {
 import React, {
-  useEffect, useState, useMemo,
+  useEffect, useState, useMemo, useCallback,
 } from 'react';
 } from 'react';
 
 
 import { pagePathUtils, pathUtils } from '@growi/core/dist/utils';
 import { pagePathUtils, pathUtils } from '@growi/core/dist/utils';
@@ -107,7 +107,7 @@ const PageCreateModal = () => {
    * join path, check if creatable, then redirect
    * join path, check if creatable, then redirect
    * @param {string} paths
    * @param {string} paths
    */
    */
-  async function redirectToEditor(...paths) {
+  const redirectToEditor = useCallback(async(...paths) => {
     try {
     try {
       const editorPath = generateEditorPath(...paths);
       const editorPath = generateEditorPath(...paths);
       await router.push(editorPath);
       await router.push(editorPath);
@@ -119,7 +119,7 @@ const PageCreateModal = () => {
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }
+  }, [closeCreateModal, mutateEditorMode, router]);
 
 
   /**
   /**
    * access today page
    * access today page
@@ -139,9 +139,9 @@ const PageCreateModal = () => {
     redirectToEditor(pageNameInput);
     redirectToEditor(pageNameInput);
   }
   }
 
 
-  function ppacSubmitHandler(input) {
+  const ppacSubmitHandler = useCallback((input) => {
     redirectToEditor(input);
     redirectToEditor(input);
-  }
+  }, [redirectToEditor]);
 
 
   /**
   /**
    * access template page
    * access template page
@@ -277,7 +277,7 @@ const PageCreateModal = () => {
 
 
           <div className="d-sm-flex align-items-center justify-items-between">
           <div className="d-sm-flex align-items-center justify-items-between">
 
 
-            <UncontrolledButtonDropdown id="dd-template-type" className='flex-fill text-center'>
+            <UncontrolledButtonDropdown id="dd-template-type" className="flex-fill text-center">
               <DropdownToggle id="template-type" caret>
               <DropdownToggle id="template-type" caret>
                 {template == null && t('template.option_label.select')}
                 {template == null && t('template.option_label.select')}
                 {template === 'children' && t('template.children.label')}
                 {template === 'children' && t('template.children.label')}
@@ -299,7 +299,7 @@ const PageCreateModal = () => {
               <button
               <button
                 data-testid="grw-btn-edit-page"
                 data-testid="grw-btn-edit-page"
                 type="button"
                 type="button"
-                className='grw-btn-create-page btn btn-outline-primary rounded-pill text-nowrap ml-3'
+                className="grw-btn-create-page btn btn-outline-primary rounded-pill text-nowrap ml-3"
                 onClick={createTemplatePage}
                 onClick={createTemplatePage}
                 disabled={template == null}
                 disabled={template == null}
               >
               >

+ 2 - 6
apps/app/src/components/PageDuplicateModal.tsx

@@ -80,14 +80,10 @@ const PageDuplicateModal = (): JSX.Element => {
     }
     }
   }, [isOpened, pageNameInput, subordinatedPages, checkExistPathsDebounce, page]);
   }, [isOpened, pageNameInput, subordinatedPages, checkExistPathsDebounce, page]);
 
 
-  /**
-   * change pageNameInput for PagePathAutoComplete
-   * @param {string} value
-   */
-  function ppacInputChangeHandler(value) {
+  const ppacInputChangeHandler = useCallback((value: string) => {
     setErrs(null);
     setErrs(null);
     setPageNameInput(value);
     setPageNameInput(value);
-  }
+  }, []);
 
 
   /**
   /**
    * change pageNameInput
    * change pageNameInput

+ 1 - 1
apps/app/src/components/PageEditor/Cheatsheet.tsx

@@ -38,7 +38,7 @@ export const Cheatsheet = (): JSX.Element => {
         className="code-highlighted"
         className="code-highlighted"
         PreTag="div"
         PreTag="div"
         style={oneDark}
         style={oneDark}
-        language={'text'}
+        language="text"
       >
       >
         {String(CheetSheetElm).replace(/\n$/, '')}
         {String(CheetSheetElm).replace(/\n$/, '')}
       </PrismAsyncLight>
       </PrismAsyncLight>

+ 1 - 1
apps/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -338,5 +338,5 @@ export const ConflictDiffModal = (props: ConflictDiffModalProps): JSX.Element =>
     afterResolvedHandler,
     afterResolvedHandler,
   };
   };
 
 
-  return <ConflictDiffModalCore {...propsForCore}/>;
+  return <ConflictDiffModalCore {...propsForCore} />;
 };
 };

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