Преглед изворни кода

Merge branch 'master' into imprv/136128-hide-title-of-untitled-page

reiji-h пре 2 година
родитељ
комит
f004f9869b
100 измењених фајлова са 1108 додато и 4680 уклоњено
  1. 0 1
      .github/dependabot.yml
  2. 1 1
      .github/workflows/auto-approve.yml
  3. 3 3
      .github/workflows/ci-app-prod.yml
  4. 4 5
      .github/workflows/release-rc-scheduled.yml
  5. 0 23
      .github/workflows/release-rc.yml
  6. 1 8
      .github/workflows/release-slackbot-proxy.yml
  7. 2 27
      .github/workflows/release.yml
  8. 1 2
      .github/workflows/reusable-app-build-image.yml
  9. 154 1
      CHANGELOG.md
  10. 0 1
      apps/app/.eslintignore
  11. 0 1191
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  12. 0 126
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss
  13. 0 65
      apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts
  14. 0 344
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  15. 0 174
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  16. 0 672
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  17. 0 536
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  18. 0 22
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  19. 0 727
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  20. 2 2
      apps/app/docker/README.md
  21. 0 6
      apps/app/docker/codebuild/buildspec.yml
  22. 0 1
      apps/app/next-env.d.ts
  23. 2 2
      apps/app/package.json
  24. 3 0
      apps/app/public/static/locales/en_US/translation.json
  25. 3 0
      apps/app/public/static/locales/ja_JP/translation.json
  26. 3 0
      apps/app/public/static/locales/zh_CN/translation.json
  27. 1 1
      apps/app/resource/locales/en_US/welcome.md
  28. 1 1
      apps/app/resource/locales/ja_JP/welcome.md
  29. 2 2
      apps/app/src/components/Admin/App/AppSetting.jsx
  30. 40 24
      apps/app/src/components/Admin/SlackIntegration/BotTypeCard.tsx
  31. 19 23
      apps/app/src/components/Admin/SlackIntegration/Bridge.tsx
  32. 0 57
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.jsx
  33. 59 0
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx
  34. 3 3
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  35. 0 57
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.jsx
  36. 57 0
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx
  37. 2 2
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  38. 3 3
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  39. 21 19
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.tsx
  40. 3 3
      apps/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx
  41. 1 2
      apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx
  42. 12 12
      apps/app/src/components/Admin/SlackIntegration/SlackIntegration.tsx
  43. 5 5
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  44. 4 0
      apps/app/src/components/Admin/SlackIntegration/slack-integration-util.ts
  45. 0 20
      apps/app/src/components/Admin/SlackIntegration/slak-integration-util.js
  46. 8 1
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  47. 4 4
      apps/app/src/components/Admin/UserManagement.tsx
  48. 9 9
      apps/app/src/components/Admin/Users/UserTable.tsx
  49. 10 0
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.module.scss
  50. 7 9
      apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx
  51. 1 1
      apps/app/src/components/Bookmarks/BookmarkFolderMenuItem.tsx
  52. 6 2
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  53. 17 0
      apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss
  54. 5 5
      apps/app/src/components/Common/PagePathNav/PagePathNav.tsx
  55. 2 1
      apps/app/src/components/CompleteUserRegistration.tsx
  56. 10 0
      apps/app/src/components/CompleteUserRegistrationForm.module.scss
  57. 42 18
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  58. 4 4
      apps/app/src/components/DataTransferForm.tsx
  59. 3 2
      apps/app/src/components/DeleteBookmarkFolderModal.tsx
  60. 1 1
      apps/app/src/components/DescendantsPageListModal.tsx
  61. 8 0
      apps/app/src/components/ExpandOrContractButton.module.scss
  62. 9 2
      apps/app/src/components/ExpandOrContractButton.tsx
  63. 3 1
      apps/app/src/components/FontFamily/use-growi-custom-icons.tsx
  64. 1 1
      apps/app/src/components/FontFamily/use-lato.tsx
  65. 2 1
      apps/app/src/components/FontFamily/use-material-symbols-outlined.tsx
  66. 1 1
      apps/app/src/components/FontFamily/use-source-han-code-jp.tsx
  67. 10 0
      apps/app/src/components/InstallerForm.module.scss
  68. 34 21
      apps/app/src/components/InstallerForm.tsx
  69. 1 2
      apps/app/src/components/InvitedForm.tsx
  70. 9 8
      apps/app/src/components/Layout/Admin.module.scss
  71. 28 118
      apps/app/src/components/Layout/NoLoginLayout.module.scss
  72. 11 5
      apps/app/src/components/Layout/NoLoginLayout.tsx
  73. 0 18
      apps/app/src/components/LoginForm.module.scss
  74. 44 0
      apps/app/src/components/LoginForm/ExternalAuthButton.tsx
  75. 104 0
      apps/app/src/components/LoginForm/LoginForm.module.scss
  76. 64 102
      apps/app/src/components/LoginForm/LoginForm.tsx
  77. 1 0
      apps/app/src/components/LoginForm/index.ts
  78. 2 2
      apps/app/src/components/Me/UISettings.tsx
  79. 1 0
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  80. 1 1
      apps/app/src/components/PageAccessoriesModal/PageAccessoriesModal.tsx
  81. 1 1
      apps/app/src/components/PageControls/BookmarkButtons.tsx
  82. 1 1
      apps/app/src/components/PageControls/LikeButtons.tsx
  83. 8 7
      apps/app/src/components/PageControls/PageControls.tsx
  84. 1 2
      apps/app/src/components/PageControls/SeenUserInfo.tsx
  85. 3 2
      apps/app/src/components/PageControls/SubscribeButton.tsx
  86. 1 0
      apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss
  87. 11 11
      apps/app/src/components/PageEditor/HandsontableModal.tsx
  88. 3 3
      apps/app/src/components/PageEditor/MarkdownTableDataImportForm.tsx
  89. 2 3
      apps/app/src/components/PageHeader/PageHeader.tsx
  90. 4 3
      apps/app/src/components/PageHeader/PagePathHeader.module.scss
  91. 45 28
      apps/app/src/components/PageHeader/PagePathHeader.tsx
  92. 12 1
      apps/app/src/components/PageHeader/PageTitleHeader.module.scss
  93. 21 7
      apps/app/src/components/PageHeader/PageTitleHeader.tsx
  94. 14 12
      apps/app/src/components/PageHistory/PageRevisionTable.tsx
  95. 2 21
      apps/app/src/components/PageHistory/RevisionDiff.module.scss
  96. 48 26
      apps/app/src/components/PageHistory/RevisionDiff.tsx
  97. 0 5
      apps/app/src/components/PageStatusAlert.module.scss
  98. 22 7
      apps/app/src/components/ReactMarkdownComponents/LightBox.tsx
  99. 8 0
      apps/app/src/components/RevisionComparer/RevisionComparer.module.scss
  100. 26 26
      apps/app/src/components/RevisionComparer/RevisionComparer.tsx

+ 0 - 1
.github/dependabot.yml

@@ -26,5 +26,4 @@ updates:
       - dependency-name: string-width
       - dependency-name: "@handsontable/react"
       - dependency-name: handsontable
-      - dependency-name: reveal.js
 

+ 1 - 1
.github/workflows/auto-approve.yml

@@ -20,7 +20,7 @@ jobs:
         with:
           github-token: '${{ secrets.GITHUB_TOKEN }}'
       - name: Approve a PR
-        if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' }}
+        if: ${{ steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor' || steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' }}
         run: gh pr review --approve "$PR_URL"
         env:
           PR_URL: ${{ github.event.pull_request.html_url }}

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

@@ -49,7 +49,7 @@ concurrency:
 jobs:
 
   test-prod-node18:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     with:
       node-version: 18.x
       skip-cypress: true
@@ -58,7 +58,7 @@ jobs:
 
 
   test-prod-node20:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     with:
       node-version: 20.x
       skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
@@ -71,7 +71,7 @@ jobs:
   run-reg-suit-node20:
     needs: [test-prod-node20]
 
-    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@dev/7.0.x
+    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
 
     if: always()
 

+ 4 - 5
.github/workflows/release-rc-v7.yml → .github/workflows/release-rc-scheduled.yml

@@ -1,10 +1,9 @@
-name: Release Docker Images for RC (for dev/7.0.x)
+name: Release Docker Images for RC (for master)
 
 on:
-  push:
-    branches:
-      - dev/7.0.x
-
+  schedule:
+    # Weekdays at 24:00hrs (JST) Executed
+    - cron: '0 15 * * 1-5'
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}

+ 0 - 23
.github/workflows/release-rc.yml

@@ -18,7 +18,6 @@ jobs:
 
     outputs:
       TAGS: ${{ steps.meta.outputs.tags }}
-      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
@@ -37,16 +36,6 @@ jobs:
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
 
-    - name: Docker meta for ghcr.io
-      uses: docker/metadata-action@v4
-      id: meta-ghcr
-      with:
-        images: ghcr.io/weseek/growi
-        sep-tags: ','
-        tags: |
-          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
-          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
-
 
   build-image-rc:
     uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
@@ -69,15 +58,3 @@ jobs:
     secrets:
       DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 
-  publish-image-rc-ghcr:
-    needs: [determine-tags, build-image-rc]
-
-    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
-    with:
-      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
-      registry: ghcr.io
-      image-name: weseek/growi
-      tag-temporary: latest-rc
-    secrets:
-      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-

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

@@ -26,7 +26,7 @@ jobs:
       id: meta
       uses: docker/metadata-action@v4
       with:
-        images: weseek/growi-slackbot-proxy,ghcr.io/weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
+        images: weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
         tags: |
           type=raw,value=latest
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
@@ -35,13 +35,6 @@ jobs:
       run: |
         echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
 
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v2
-      with:
-        registry: ghcr.io
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-
     - name: Authenticate to Google Cloud for GROWI.cloud
       uses: google-github-actions/auth@v1
       with:

+ 2 - 27
.github/workflows/release.yml

@@ -82,7 +82,6 @@ jobs:
 
     outputs:
       TAGS: ${{ steps.meta.outputs.tags }}
-      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
@@ -103,18 +102,6 @@ jobs:
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
 
-    - name: Docker meta for ghcr.io
-      uses: docker/metadata-action@v4
-      id: meta-ghcr
-      with:
-        images: ghcr.io/weseek/growi
-        sep-tags: ','
-        tags: |
-          type=raw,value=latest
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
-          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
-
 
   build-image:
     needs: create-github-release
@@ -140,21 +127,9 @@ jobs:
     secrets:
       DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 
-  publish-image-ghcr:
-    needs: [determine-tags, build-image]
-
-    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
-    with:
-      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
-      registry: ghcr.io
-      image-name: weseek/growi
-      tag-temporary: latest
-    secrets:
-      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
-
 
   post-publish:
-    needs: [create-github-release, publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image]
     runs-on: ubuntu-latest
 
     steps:
@@ -179,7 +154,7 @@ jobs:
 
 
   create-pr-for-next-rc:
-    needs: [create-github-release, publish-image, publish-image-ghcr]
+    needs: [create-github-release, publish-image]
     runs-on: ubuntu-latest
 
     steps:

+ 1 - 2
.github/workflows/reusable-app-build-image.yml

@@ -51,6 +51,5 @@ jobs:
         CODEBUILD__imageOverride: ${{ (matrix.platform == 'amd64' && 'aws/codebuild/amazonlinux2-x86_64-standard:4.0') || 'aws/codebuild/amazonlinux2-aarch64-standard:2.0' }}
         CODEBUILD__environmentTypeOverride: ${{ (matrix.platform == 'amd64' && 'LINUX_CONTAINER') || 'ARM_CONTAINER' }}
         CODEBUILD__environmentVariablesOverride: '[
-          { "name": "IMAGE_TAG", "type": "PLAINTEXT", "value": "docker.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" },
-          { "name": "IMAGE_TAG_GHCR", "type": "PLAINTEXT", "value": "ghcr.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" }
+          { "name": "IMAGE_TAG", "type": "PLAINTEXT", "value": "docker.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" }
         ]'

+ 154 - 1
CHANGELOG.md

@@ -1,9 +1,162 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.3.1...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.0.1...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v7.0.1](https://github.com/weseek/growi/compare/v7.0.0...v7.0.1) - 2024-04-02
+
+### 🚀 Improvement
+
+* imprv: PagePathNav and PagePathHeader styles (#8643) @yuki-takei
+* imprv: Prevent tooltip flickering (#8642) @yuki-takei
+* imprv: PersonalDropdown style (#8641) @yuki-takei
+* imprv: Support color scheme in Page History (diff2html) (#8637) @yuki-takei
+* imprv: Disable RequestedAuthnContext in SAML authentication (v7.0.x) (#8635) @yuki-takei
+
+### 🐛 Bug Fixes
+
+* fix: Admin users badge color in /admin/users (#8655) @satof3
+* fix: Custom logo style (#8656) @satof3
+* fix: Error when creating TTL index (#8653) @miya
+* fix: Login form style is broken (#8651) @yuki-takei
+* fix: Login buttons for external auth provider does not work (#8648) @yuki-takei
+* fix: Set `d-none` when the sidebar is closed in order to prevent scrollbars from appearing (#8640) @yuki-takei
+* fix: Style for Handsontable in dark mode (#8639) @yuki-takei
+* fix: Supress PageAccessoriesModal rerendering for keeping radio button selections of PageHistory (#8638) @yuki-takei
+* fix: Uncorrect update bookmark button clicked on page control (#8608) @jam411
+* fix: Counting comments when removing the thread (#8624) @yukendev
+
+### 🧰 Maintenance
+
+* support: Add light and dark badge color (#8652) @satof3
+* support: Omit configurations for publishing to GitHub Container Registry(ghcr.io) (#8628) @yuki-takei
+
+## [v7.0.0](https://github.com/weseek/growi/compare/v6.3.2...v7.0.0) - 2024-03-27
+
+### BREAKING CHANGES
+
+* support: Remove obsolete route for attachment on MongoDB GridFS (#8239) @yuki-takei
+* support: Omit promster (#8105) @yuki-takei
+* support: Omit HackMD (CodiMD) (#8094) @yuki-takei
+* support: Revoke well classes (#8041) @soumaeda
+
+### 💎 Features
+
+* feat: Not required latest revision when updating from Editor (#8522) @miya
+* feat: Add editing user list on page header (#8486) @jam411
+* feat: implement to add table when doing a line break in table markdown (#8461) @WNomunomu
+* feat: WIP Page (#8484) @miya
+* feat: 140020 140992 sidebar style (#8497) @yukendev
+* feat: Select parent page from PageSelectModal (#8446) @WNomunomu
+* feat: CollapsedParentsDropdown to display ancestor pathname (#8444) @miya
+* feat: Enable and fix page duplication of user unrelated pages (#8442) @arafubeatbox
+* feat: Multiple group grant for page (#8331) @arafubeatbox
+* feat: Built-in Collaborative Editor (#8176) @jam411
+* feat: Changing page title and path in edit mode (#8252) @WNomunomu
+* feat: Notification count badge (#8372) @miya
+* feat: Unread filtering for In-app notification (#8373) @miya
+* feat: Show notifications in sidebar (#8371) @miya
+* feat: Uniform behavior when creating pages from create new page button (#8297) @jam411
+* feat: New incremental search (#8238) @miya
+* feat: Allow deletion of user homepage when the user is deleted (#8224) @jam411
+* feat: LDAP/Keycloak group sync (#7857) @arafubeatbox
+* feat: Implementation of autocompletion function for emoji input (#8137) @WNomunomu
+* feat: Create page when click edit button if page is not found (#8174) @jam411
+* feat: Presentation preview and support Marp  (#8029) @reiji-h
+
+### 🚀 Improvement
+
+* imprv: New login design (#8607) @satof3
+* imprv: Multi group grant page duplicate explanation (#8507) @arafubeatbox
+* imprv: Update design for user homepage side contents (#8465) @jam411
+* imprv: Update page accessories modal design (#8466) @jam411
+* imprv: Creating/updating page APIs (#8459) @yuki-takei
+* imprv: improve PageHeader component (#8439) @WNomunomu
+* imprv: Use unzip stream instead of unzipper (#8378) @ryu-sato
+* imprv: Allow plugin that contain slashes in the branch name to be installed (#8359) @ryu-sato
+* imprv: Refactor DrawioViewer re-rendering by the resizing trigger (#8314) @yuki-takei
+* imprv: Upload handler use apiv3 post (#8279) @reiji-h
+* imprv: Apply content headers for attachment response (#8245) @yuki-takei
+* imprv: Add Marp preset template for ja_JP and zh_CN (#8179) @AikaHiyama
+* imprv: Update RichAttachments feat on shared pages (#8206) @jam411
+* imprv: Certify shared page attachment middleware (#8211) @yuki-takei
+* imprv: Able to edit tags in editor (#8167) @soumaeda
+* imprv: Responsive layout (#8200) @yuki-takei
+* imprv: Sidebar on edit (#8181) @yuki-takei
+* imprv: Sidebar mode (#8160) @yuki-takei
+* imprv: Limit the file types in editor. (#8146) @reiji-h
+* imprv: Support Ctrl+V file paste. (#8124) @reiji-h
+* imprv: i18n for marp settings (#8110) @moekumasaka
+* imprv: Download a markdown file using the page name as the file name (#8061) @soumaeda
+* imprv: i18n "Create /Sidebar page" label (#8085) @yuki-takei
+* imprv: Admin customize presentation form (#8083) @meiri-k
+* imprv: Search behavior (#8069) @yuki-takei
+* imprv: i18n resetting password mail body (#8058) @meiri-k
+* imprv: Add installed date to questionnaire answer (#7971) @TatsuyaIse
+* imprv: Export md with page name (#8005) @soumaeda
+* imprv: Show modal when you delete plugin (#7875) @soumaeda
+* imprv: Create  Japanese  ejs files (#7957) @meiri-k
+* imprv: Sidebar shows skelton with suspense (#7975) @yuki-takei
+
+### 🐛 Bug Fixes
+
+* fix: Show both UserGroups and ExternalUserGroups for group delete modal transfer select (#8519) @arafubeatbox
+* fix: Multi group grant page becomes public when one of groups deleted (#8518) @arafubeatbox
+* fix: pages are not displayed in page tree (#8515) @WNomunomu
+* fix: Page being able to delete completely when not allowed (#8374) @arafubeatbox
+* fix: Logs are not saved when viewing the page (#8406) @miya
+* fix: Normalize duplicated root pages to valid paths when server startup (#8414) @miya
+* fix: Configured auditlog environment variables are not reflected in the administration screen (#8383) @miya
+* fix: Plugin is broken after unzipping (#8358) @ryu-sato
+* fix: Keycloak group sync config not loaded on sync execution (#8339) @arafubeatbox
+* fix: SAML callback action throws the field is undefined error when the ACL Rule string is only white space (#8322) @yuki-takei
+* fix: Update deleteCompletelyUserHomeBySystem for v4 process (#8289) @jam411
+* fix: Remove groups not related to the user from the user groups that are specified automatically when creating child pages (#8266) @arafubeatbox
+* fix: Certify shared page attachment middleware (#8255) @yuki-takei
+* fix: Show liker counts in lsx (#8194) @yuki-takei
+* fix: Marp is enabled incorrectly problem (#8100) @reiji-h
+* fix: Fixing swagger for tag update api (#8010) @miya
+* fix: Modification of links in the docs (#8004) @miya
+* fix: Type safe implementation for objects imported from ElasticsearchClient (#7862) @miya
+* fix: Consider an empty page when renaming and duplicating (v6.1.x) (#7980) @yuki-takei
+* fix: Consider an empty page when renaming and duplicating (#7979) @yuki-takei
+
+### 🧰 Maintenance
+
+* support: Upgrade Next.js v14 (#8586) @yuki-takei
+* imprv: New login design (#8607) @satof3
+* support: Node.js v20 (#8528) @miya
+* support: Upgrade react bootstrap typeahead (#8500) @jam411
+* ci(deps): bump ip from 2.0.0 to 2.0.1 (#8508) @dependabot
+* support: React Testing Library (#8393) @miya
+* ci(deps-dev): bump vite from 4.5.1 to 4.5.2 (#8392) @dependabot
+* support: Build GROWI custom icons (#8356) @yukendev
+* ci(deps-dev): bump vite from 4.5.0 to 4.5.1 (#8302) @dependabot
+* support: Remove obsolete route for attachment on MongoDB GridFS (#8239) @yuki-takei
+* support: Install material-symbols (#8182) @yuki-takei
+* ci(deps-dev): bump postcss from 8.4.26 to 8.4.31 (#8142) @dependabot
+* ci(deps): bump cypress-io/github-action from 5 to 6 (#8051) @dependabot
+* ci(deps): bump amannn/action-semantic-pull-request from 5.0.2 to 5.3.0 (#8127) @dependabot
+* ci(deps): bump aws-actions/configure-aws-credentials from 2 to 4 (#8128) @dependabot
+* support: Internationalization USER_REGISTRATION_APPROVAL_REQUEST label for v62x (#8130) @jam411
+* ci(deps): bump get-func-name from 2.0.0 to 2.0.2 (#8119) @dependabot
+* support: Omit promster (#8105) @yuki-takei
+* support: Omit HackMD (CodiMD) (#8094) @yuki-takei
+* support: Revoke well classes (#8041) @soumaeda
+* support: Upgrade to reactstrap v9 (#7984) @jam411
+* support: Update CodeMirror to v6 (#7968) @yuki-takei
+
+## [v6.3.2](https://github.com/weseek/growi/compare/v6.3.1...v6.3.2) - 2024-03-25
+
+### 🐛 Bug Fixes
+
+* fix: Show both UserGroups and ExternalUserGroups for group delete modal transfer select (#8519) @arafubeatbox
+
+### 🧰 Maintenance
+
+* ci(deps): bump ip from 2.0.0 to 2.0.1 (#8508) @dependabot
+
 ## [v6.3.1](https://github.com/weseek/growi/compare/v6.3.0...v6.3.1) - 2024-02-01
 
 ### 💎 Features

+ 0 - 1
apps/app/.eslintignore

@@ -1,4 +1,3 @@
-/_obsolete/**
 /dist/**
 /transpiled/**
 /public/**

+ 0 - 1191
apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -1,1191 +0,0 @@
-import React, { useCallback, memo } from 'react';
-
-import { commands } from 'codemirror';
-import * as loadCssSync from 'load-css-file';
-import PropTypes from 'prop-types';
-import { Button } from 'reactstrap';
-import * as loadScript from 'simple-load-script';
-import { throttle, debounce } from 'throttle-debounce';
-import urljoin from 'url-join';
-
-import InterceptorManager from '~/services/interceptor-manager';
-import {
-  useHandsontableModal, useDrawioModal, useTemplateModal, useLinkEditModal,
-} from '~/stores/modal';
-import loggerFactory from '~/utils/logger';
-
-import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
-
-import AbstractEditor from './AbstractEditor';
-import CommentMentionHelper from './CommentMentionHelper';
-import EditorIcon from './EditorIcon';
-import EmojiPicker from './EmojiPicker';
-import EmojiPickerHelper from './EmojiPickerHelper';
-import GridEditModal from './GridEditModal';
-// TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-// import geu from './GridEditorUtil';
-import mdu from './MarkdownDrawioUtil';
-import markdownLinkUtil from './MarkdownLinkUtil';
-import markdownListUtil from './MarkdownListUtil';
-import MarkdownTableInterceptor from './MarkdownTableInterceptor';
-import mtu from './MarkdownTableUtil';
-import pasteHelper from './PasteHelper';
-import PreventMarkdownListInterceptor from './PreventMarkdownListInterceptor';
-import SimpleCheatsheet from './SimpleCheatsheet';
-
-import styles from './CodeMirrorEditor.module.scss';
-
-require('codemirror/addon/hint/show-hint.css'); // Import from CodeMirrorEditor.module.scss not working
-require('codemirror/addon/display/placeholder');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/matchtags');
-require('codemirror/addon/edit/closetag');
-require('codemirror/addon/edit/continuelist');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/search/searchcursor');
-require('codemirror/addon/search/match-highlighter');
-require('codemirror/addon/selection/active-line');
-require('codemirror/addon/scroll/annotatescrollbar');
-require('codemirror/addon/scroll/scrollpastend');
-require('codemirror/addon/fold/foldcode');
-require('codemirror/addon/fold/foldgutter');
-require('codemirror/addon/fold/markdown-fold');
-require('codemirror/addon/fold/brace-fold');
-require('codemirror/addon/display/placeholder');
-require('~/client/util/codemirror/autorefresh.ext');
-require('~/client/util/codemirror/drawio-fold.ext');
-require('~/client/util/codemirror/gfm-growi.mode');
-// import modes to highlight
-require('codemirror/mode/clike/clike');
-require('codemirror/mode/css/css');
-require('codemirror/mode/django/django');
-require('codemirror/mode/erlang/erlang');
-require('codemirror/mode/gfm/gfm');
-require('codemirror/mode/go/go');
-require('codemirror/mode/javascript/javascript');
-require('codemirror/mode/jsx/jsx');
-require('codemirror/mode/mathematica/mathematica');
-require('codemirror/mode/nginx/nginx');
-require('codemirror/mode/perl/perl');
-require('codemirror/mode/php/php');
-require('codemirror/mode/python/python');
-require('codemirror/mode/r/r');
-require('codemirror/mode/ruby/ruby');
-require('codemirror/mode/rust/rust');
-require('codemirror/mode/sass/sass');
-require('codemirror/mode/shell/shell');
-require('codemirror/mode/sql/sql');
-require('codemirror/mode/stex/stex');
-require('codemirror/mode/stylus/stylus');
-require('codemirror/mode/swift/swift');
-require('codemirror/mode/toml/toml');
-require('codemirror/mode/vb/vb');
-require('codemirror/mode/vue/vue');
-require('codemirror/mode/xml/xml');
-require('codemirror/mode/yaml/yaml');
-
-
-const MARKDOWN_TABLE_ACTIVATED_CLASS = 'markdown-table-activated';
-const MARKDOWN_LINK_ACTIVATED_CLASS = 'markdown-link-activated';
-
-class CodeMirrorEditor extends AbstractEditor {
-
-  constructor(props) {
-    super(props);
-    this.logger = loggerFactory('growi:PageEditor:CodeMirrorEditor');
-
-    this.state = {
-      isGfmMode: this.props.isGfmMode,
-      isLoadingKeymap: false,
-      isSimpleCheatsheetShown: this.props.isGfmMode && this.props.value?.length === 0,
-      isCheatsheetModalShown: false,
-      additionalClassSet: new Set(),
-      isEmojiPickerShown: false,
-      emojiSearchText: '',
-      startPosWithEmojiPickerModeTurnedOn: null,
-      isEmojiPickerMode: false,
-      isTemplateModalOpened: false,
-    };
-
-    this.cm = React.createRef();
-    this.gridEditModal = React.createRef();
-    this.linkEditModal = React.createRef();
-    this.drawioModal = React.createRef();
-
-    this.init();
-
-    this.getCodeMirror = this.getCodeMirror.bind(this);
-
-    this.getBol = this.getBol.bind(this);
-    this.getEol = this.getEol.bind(this);
-
-    this.loadTheme = this.loadTheme.bind(this);
-    this.loadKeymapMode = this.loadKeymapMode.bind(this);
-    this.setKeymapMode = this.setKeymapMode.bind(this);
-    this.handleEnterKey = this.handleEnterKey.bind(this);
-    this.handleCtrlEnterKey = this.handleCtrlEnterKey.bind(this);
-
-    this.scrollCursorIntoViewHandler = this.scrollCursorIntoViewHandler.bind(this);
-    this.scrollCursorIntoViewHandlerThrottled = throttle(500, this.scrollCursorIntoViewHandler);
-    this.pasteHandler = this.pasteHandler.bind(this);
-    this.cursorHandler = this.cursorHandler.bind(this);
-    this.cursorHandlerDebounced = debounce(200, throttle(500, this.cursorHandler));
-    this.changeHandler = this.changeHandler.bind(this);
-    this.turnOnEmojiPickerMode = this.turnOnEmojiPickerMode.bind(this);
-    this.turnOffEmojiPickerMode = this.turnOffEmojiPickerMode.bind(this);
-    this.windowClickHandler = this.windowClickHandler.bind(this);
-    this.keyDownHandler = this.keyDownHandler.bind(this);
-    this.keyDownHandlerForEmojiPicker = this.keyDownHandlerForEmojiPicker.bind(this);
-    this.keyDownHandlerForEmojiPickerThrottled = throttle(400, this.keyDownHandlerForEmojiPicker);
-    this.showEmojiPicker = this.showEmojiPicker.bind(this);
-    this.keyPressHandlerForEmojiPicker = this.keyPressHandlerForEmojiPicker.bind(this);
-    this.keyPressHandlerForEmojiPickerThrottled = debounce(50, throttle(200, this.keyPressHandlerForEmojiPicker));
-    this.keyPressHandler = this.keyPressHandler.bind(this);
-
-    this.updateCheatsheetStates = this.updateCheatsheetStates.bind(this);
-
-    this.renderLoadingKeymapOverlay = this.renderLoadingKeymapOverlay.bind(this);
-    this.renderCheatsheetModalButton = this.renderCheatsheetModalButton.bind(this);
-
-    this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
-    // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-    // this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
-
-    this.foldDrawioSection = this.foldDrawioSection.bind(this);
-    this.clickDrawioIconHandler = this.clickDrawioIconHandler.bind(this);
-    this.clickTableIconHandler = this.clickTableIconHandler.bind(this);
-
-    this.showTemplateModal = this.showTemplateModal.bind(this);
-    this.showLinkEditModal = this.showLinkEditModal.bind(this);
-
-  }
-
-  init() {
-    this.cmCdnRoot = 'https://cdn.jsdelivr.net/npm/codemirror@5.42.0';
-    this.cmNoCdnScriptRoot = '/static/js/cdn';
-    this.cmNoCdnStyleRoot = '/static/styles/cdn';
-    this.interceptorManager = new InterceptorManager();
-    this.interceptorManager.addInterceptors([
-      new PreventMarkdownListInterceptor(),
-      new MarkdownTableInterceptor(),
-    ]);
-
-    this.loadedThemeSet = new Set(['eclipse', 'elegant']); // themes imported in _vendor.scss
-    this.loadedKeymapSet = new Set();
-  }
-
-  componentDidMount() {
-    // ensure to be able to resolve 'this' to use 'codemirror.commands.save'
-    this.getCodeMirror().codeMirrorEditor = this;
-
-    // mark clean
-    this.getCodeMirror().getDoc().markClean();
-
-    // fold drawio section
-    this.foldDrawioSection();
-
-    // initialize commentMentionHelper if comment editor is opened
-    if (this.props.isComment) {
-      this.commentMentionHelper = new CommentMentionHelper(this.getCodeMirror());
-    }
-    this.emojiPickerHelper = new EmojiPickerHelper(this.getCodeMirror());
-
-    // HACKME: Find a better way to handle onClick for Editor
-    document.addEventListener('click', this.windowClickHandler);
-  }
-
-  componentWillUnmount() {
-    // HACKME: Find a better way to handle onClick for Editor
-    document.removeEventListener('click', this.windowClickHandler);
-  }
-
-  componentWillReceiveProps(nextProps) {
-    this.initializeEditorSettings(nextProps.editorSettings);
-
-    // fold drawio section
-    this.foldDrawioSection();
-  }
-
-  initializeEditorSettings(editorSettings) {
-    if (editorSettings == null) {
-      return;
-    }
-
-    // load theme
-    const theme = editorSettings.theme;
-    if (theme != null) {
-      this.loadTheme(theme);
-    }
-
-    // set keymap
-    const keymapMode = editorSettings.keymapMode;
-    if (keymapMode != null) {
-      this.setKeymapMode(keymapMode);
-    }
-  }
-
-  getCodeMirror() {
-    return this.cm.current?.editor;
-  }
-
-  /**
-   * @inheritDoc
-   */
-  forceToFocus() {
-    // use setInterval with reluctance -- 2018.01.11 Yuki Takei
-    const intervalId = setInterval(() => {
-      const editor = this.getCodeMirror();
-      editor.focus();
-      if (editor.hasFocus()) {
-        clearInterval(intervalId);
-        // refresh
-        editor.refresh();
-      }
-    }, 100);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setValue(newValue) {
-    this.getCodeMirror().getDoc().setValue(newValue);
-
-    // mark clean
-    this.getCodeMirror().getDoc().markClean();
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setGfmMode(bool) {
-    // update state
-    this.setState({
-      isGfmMode: bool,
-    });
-
-    this.updateCheatsheetStates(bool, null);
-
-    // update CodeMirror option
-    const mode = bool ? 'gfm' : undefined;
-    this.getCodeMirror().setOption('mode', mode);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setCaretLine(line) {
-    if (Number.isNaN(line)) {
-      return;
-    }
-
-    const editor = this.getCodeMirror();
-    const linePosition = Math.max(0, line - 1);
-
-    editor.setCursor({ line: linePosition }); // leave 'ch' field as null/undefined to indicate the end of line
-
-    setTimeout(() => {
-      this.setScrollTopByLine(linePosition);
-    }, 100);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  setScrollTopByLine(line) {
-    if (Number.isNaN(line)) {
-      return;
-    }
-
-    const editor = this.getCodeMirror();
-    // get top position of the line
-    const top = editor.charCoords({ line: line - 1, ch: 0 }, 'local').top;
-    editor.scrollTo(null, top);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return editor.getDoc().getRange(this.getBol(), curPos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrToEol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return editor.getDoc().getRange(curPos, this.getEol());
-  }
-
-  /**
-   * @inheritDoc
-   */
-  getStrFromBolToSelectedUpperPos() {
-    const editor = this.getCodeMirror();
-    const pos = this.selectUpperPos(editor.getCursor('from'), editor.getCursor('to'));
-    return editor.getDoc().getRange(this.getBol(), pos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceBolToCurrentPos(text) {
-    const editor = this.getCodeMirror();
-    const pos = this.selectLowerPos(editor.getCursor('from'), editor.getCursor('to'));
-    editor.getDoc().replaceRange(text, this.getBol(), pos);
-  }
-
-  /**
-   * @inheritDoc
-   */
-  replaceLine(text) {
-    const editor = this.getCodeMirror();
-    editor.getDoc().replaceRange(text, this.getBol(), this.getEol());
-  }
-
-  /**
-   * @inheritDoc
-   */
-  insertText(text) {
-    const editor = this.getCodeMirror();
-    editor.getDoc().replaceSelection(text);
-  }
-
-  /**
-   * return the postion of the BOL(beginning of line)
-   */
-  getBol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    return { line: curPos.line, ch: 0 };
-  }
-
-  /**
-   * return the postion of the EOL(end of line)
-   */
-  getEol() {
-    const editor = this.getCodeMirror();
-    const curPos = editor.getCursor();
-    const lineLength = editor.getDoc().getLine(curPos.line).length;
-    return { line: curPos.line, ch: lineLength };
-  }
-
-  /**
-   * select the upper position of pos1 and pos2
-   * @param {{line: number, ch: number}} pos1
-   * @param {{line: number, ch: number}} pos2
-   */
-  selectUpperPos(pos1, pos2) {
-    // if both is in same line
-    if (pos1.line === pos2.line) {
-      return (pos1.ch < pos2.ch) ? pos1 : pos2;
-    }
-    return (pos1.line < pos2.line) ? pos1 : pos2;
-  }
-
-  /**
-   * select the lower position of pos1 and pos2
-   * @param {{line: number, ch: number}} pos1
-   * @param {{line: number, ch: number}} pos2
-   */
-  selectLowerPos(pos1, pos2) {
-    // if both is in same line
-    if (pos1.line === pos2.line) {
-      return (pos1.ch < pos2.ch) ? pos2 : pos1;
-    }
-    return (pos1.line < pos2.line) ? pos2 : pos1;
-  }
-
-  loadCss(source) {
-    return new Promise((resolve) => {
-      loadCssSync(source);
-      resolve();
-    });
-  }
-
-  /**
-   * load Theme
-   * @see https://codemirror.net/doc/manual.html#config
-   *
-   * @param {string} theme
-   */
-  loadTheme(theme) {
-    if (!this.loadedThemeSet.has(theme)) {
-      const url = this.props.noCdn
-        ? urljoin(this.cmNoCdnStyleRoot, `codemirror-theme-${theme}.css`)
-        : urljoin(this.cmCdnRoot, `theme/${theme}.min.css`);
-
-      this.loadCss(url);
-
-      // update Set
-      this.loadedThemeSet.add(theme);
-    }
-  }
-
-  /**
-   * load assets for Key Maps
-   * @param {*} keymapMode 'default' or 'vim' or 'emacs' or 'sublime'
-   */
-  loadKeymapMode(keymapMode) {
-    const loadCss = this.loadCss;
-    const scriptList = [];
-    const cssList = [];
-
-    // add dependencies
-    if (this.loadedKeymapSet.size === 0) {
-      const dialogScriptUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnScriptRoot, 'codemirror-dialog.js')
-        : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.js');
-      const dialogStyleUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnStyleRoot, 'codemirror-dialog.css')
-        : urljoin(this.cmCdnRoot, 'addon/dialog/dialog.min.css');
-
-      scriptList.push(loadScript(dialogScriptUrl));
-      cssList.push(loadCss(dialogStyleUrl));
-    }
-    // load keymap
-    if (!this.loadedKeymapSet.has(keymapMode)) {
-      const keymapScriptUrl = this.props.noCdn
-        ? urljoin(this.cmNoCdnScriptRoot, `codemirror-keymap-${keymapMode}.js`)
-        : urljoin(this.cmCdnRoot, `keymap/${keymapMode}.min.js`);
-      scriptList.push(loadScript(keymapScriptUrl));
-      // update Set
-      this.loadedKeymapSet.add(keymapMode);
-    }
-
-    // set loading state
-    this.setState({ isLoadingKeymap: true });
-
-    return Promise.all(scriptList.concat(cssList))
-      .then(() => {
-        this.setState({ isLoadingKeymap: false });
-      });
-  }
-
-  /**
-   * set Key Maps
-   * @see https://codemirror.net/doc/manual.html#keymaps
-   *
-   * @param {string} keymapMode 'default' or 'vim' or 'emacs' or 'sublime'
-   */
-  setKeymapMode(keymapMode) {
-    if (!keymapMode.match(/^(vim|emacs|sublime)$/)) {
-      // reset
-      this.getCodeMirror().setOption('keyMap', 'default');
-      return;
-    }
-
-    this.loadKeymapMode(keymapMode)
-      .then(() => {
-        let errorCount = 0;
-        const timer = setInterval(() => {
-          if (errorCount > 10) { // cancel over 3000ms
-            this.logger.error(`Timeout to load keyMap '${keymapMode}'`);
-            clearInterval(timer);
-          }
-
-          try {
-            this.getCodeMirror().setOption('keyMap', keymapMode);
-            clearInterval(timer);
-          }
-          catch (e) {
-            this.logger.info(`keyMap '${keymapMode}' has not been initialized. retry..`);
-
-            // continue if error occured
-            errorCount++;
-          }
-        }, 300);
-      });
-  }
-
-  /**
-   * handle ENTER key
-   */
-  handleEnterKey() {
-    if (!this.state.isGfmMode) {
-      commands.newlineAndIndent(this.getCodeMirror());
-      return;
-    }
-
-    const context = {
-      handlers: [], // list of handlers which process enter key
-      editor: this,
-      autoFormatMarkdownTable: this.props.editorSettings.autoFormatMarkdownTable,
-    };
-
-    const interceptorManager = this.interceptorManager;
-    interceptorManager.process('preHandleEnter', context)
-      .then(() => {
-        if (context.handlers.length === 0) {
-          markdownListUtil.newlineAndIndentContinueMarkdownList(this);
-        }
-      });
-  }
-
-  /**
-   * handle Ctrl+ENTER key
-   */
-  handleCtrlEnterKey() {
-    if (this.props.onCtrlEnter != null) {
-      this.props.onCtrlEnter();
-    }
-  }
-
-  scrollCursorIntoViewHandler(editor, event) {
-    if (this.props.onScrollCursorIntoView != null) {
-      const line = editor.getCursor().line;
-      this.props.onScrollCursorIntoView(line);
-    }
-  }
-
-  cursorHandler(editor, event) {
-    const { additionalClassSet } = this.state;
-    const hasCustomClass = additionalClassSet.has(MARKDOWN_TABLE_ACTIVATED_CLASS);
-    const hasLinkClass = additionalClassSet.has(MARKDOWN_LINK_ACTIVATED_CLASS);
-
-    const isInTable = mtu.isInTable(editor);
-    const isInLink = markdownLinkUtil.isInLink(editor);
-
-    if (!hasCustomClass && isInTable) {
-      additionalClassSet.add(MARKDOWN_TABLE_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (hasCustomClass && !isInTable) {
-      additionalClassSet.delete(MARKDOWN_TABLE_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (!hasLinkClass && isInLink) {
-      additionalClassSet.add(MARKDOWN_LINK_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-
-    if (hasLinkClass && !isInLink) {
-      additionalClassSet.delete(MARKDOWN_LINK_ACTIVATED_CLASS);
-      this.setState({ additionalClassSet });
-    }
-  }
-
-  changeHandler(editor, data, value) {
-    if (this.props.onChange != null) {
-      const isClean = data.origin == null || editor.isClean() || value === this.props.value;
-      this.props.onChange(value, isClean);
-    }
-
-    this.updateCheatsheetStates(null, value);
-
-    // Show username hint on comment editor
-    if (this.props.isComment) {
-      this.commentMentionHelper.showUsernameHint();
-    }
-
-  }
-
-  turnOnEmojiPickerMode(pos) {
-    this.setState({
-      isEmojiPickerMode: true,
-      startPosWithEmojiPickerModeTurnedOn: pos,
-    });
-  }
-
-  turnOffEmojiPickerMode() {
-    this.setState({
-      isEmojiPickerMode: false,
-    });
-  }
-
-  showEmojiPicker(initialSearchingText) {
-    // show emoji picker with a stored word
-    this.setState({
-      isEmojiPickerShown: true,
-      emojiSearchText: initialSearchingText ?? '',
-    });
-
-    const resetStartPos = initialSearchingText == null;
-    if (resetStartPos) {
-      this.setState({ startPosWithEmojiPickerModeTurnedOn: null });
-    }
-
-    this.turnOffEmojiPickerMode();
-  }
-
-  keyPressHandlerForEmojiPicker(editor, event) {
-    const char = event.key;
-    const isEmojiPickerMode = this.state.isEmojiPickerMode;
-
-    // evaluate whether emoji picker mode to be turned on
-    if (!isEmojiPickerMode) {
-      const startPos = this.emojiPickerHelper.shouldModeTurnOn(char);
-      if (startPos == null) {
-        return;
-      }
-
-      this.turnOnEmojiPickerMode(startPos);
-      return;
-    }
-
-    // evaluate whether EmojiPicker to be opened
-    const startPos = this.state.startPosWithEmojiPickerModeTurnedOn;
-    if (this.emojiPickerHelper.shouldOpen(startPos)) {
-      const initialSearchingText = this.emojiPickerHelper.getInitialSearchingText(startPos);
-      this.showEmojiPicker(initialSearchingText);
-      return;
-    }
-
-    this.turnOffEmojiPickerMode();
-  }
-
-  keyPressHandler(editor, event) {
-    this.keyPressHandlerForEmojiPickerThrottled(editor, event);
-  }
-
-  keyDownHandlerForEmojiPicker(editor, event) {
-    const key = event.key;
-
-    if (!this.state.isEmojiPickerMode) {
-      return;
-    }
-
-    if (['ArrowRight', 'ArrowLeft', 'ArrowUp', 'ArrowDown', 'BackSpace'].includes(key)) {
-      this.turnOffEmojiPickerMode();
-    }
-  }
-
-  keyDownHandler(editor, event) {
-    this.keyDownHandlerForEmojiPickerThrottled(editor, event);
-  }
-
-  windowClickHandler() {
-    this.turnOffEmojiPickerMode();
-  }
-
-  /**
-   * CodeMirror paste event handler
-   * see: https://codemirror.net/doc/manual.html#events
-   * @param {any} editor An editor instance of CodeMirror
-   * @param {any} event
-   */
-  pasteHandler(editor, event) {
-    const types = event.clipboardData.types;
-
-    // files
-    if (types.includes('Files')) {
-      event.preventDefault();
-      this.dispatchPasteFiles(event);
-    }
-    // text
-    else if (types.includes('text/plain')) {
-      pasteHelper.pasteText(this, event);
-    }
-
-  }
-
-  /**
-   * update states which related to cheatsheet
-   * @param {boolean} isGfmModeTmp (use state.isGfmMode if null is set)
-   * @param {string} valueTmp (get value from codemirror if null is set)
-   */
-  updateCheatsheetStates(isGfmModeTmp, valueTmp) {
-    const isGfmMode = isGfmModeTmp || this.state.isGfmMode;
-    const value = valueTmp || this.getCodeMirror().getDoc().getValue();
-
-    // update isSimpleCheatsheetShown
-    const isSimpleCheatsheetShown = isGfmMode && value.length === 0;
-    this.setState({ isSimpleCheatsheetShown });
-  }
-
-  markdownHelpButtonClickedHandler() {
-    if (this.props.onMarkdownHelpButtonClicked != null) {
-      this.props.onMarkdownHelpButtonClicked();
-    }
-  }
-
-  renderLoadingKeymapOverlay() {
-    // centering
-    const style = {
-      top: 0,
-      right: 0,
-      bottom: 0,
-      left: 0,
-    };
-
-    return this.state.isLoadingKeymap
-      ? (
-        <div className="overlay overlay-loading-keymap">
-          <span style={style} className="overlay-content">
-            <div className="speeding-wheel d-inline-block"></div> Loading Keymap ...
-          </span>
-        </div>
-      )
-      : '';
-  }
-
-  renderCheatsheetModalButton() {
-    return (
-      <button type="button" className="btn-link gfm-cheatsheet-modal-link small" onClick={() => { this.markdownHelpButtonClickedHandler() }}>
-        <span className="material-symbols-outlined">help</span> Markdown
-      </button>
-    );
-  }
-
-  renderCheatsheetOverlay() {
-    const cheatsheetModalButton = this.renderCheatsheetModalButton();
-
-    return (
-      <div className="overlay overlay-gfm-cheatsheet mt-1 p-3">
-        { this.state.isSimpleCheatsheetShown
-          ? (
-            <div className="text-end">
-              {cheatsheetModalButton}
-              <div className="mb-2 d-none d-md-block">
-                <SimpleCheatsheet />
-              </div>
-            </div>
-          )
-          : (
-            <div className="me-4 mb-2">
-              {cheatsheetModalButton}
-            </div>
-          )
-        }
-      </div>
-    );
-  }
-
-  renderEmojiPicker() {
-    const { emojiSearchText } = this.state;
-    return this.state.isEmojiPickerShown
-      ? (
-        <div className="text-start">
-          <div className="mb-2 d-none d-md-block">
-            <EmojiPicker
-              onClose={() => this.setState({ isEmojiPickerShown: false })}
-              onSelected={emoji => this.emojiPickerHelper.addEmoji(emoji, this.state.startPosWithEmojiPickerModeTurnedOn)}
-              emojiSearchText={emojiSearchText}
-              emojiPickerHelper={this.emojiPickerHelper}
-              isOpen={this.state.isEmojiPickerShown}
-            />
-          </div>
-        </div>
-      )
-      : '';
-  }
-
-  /**
-   * return a function to replace a selected range with prefix + selection + suffix
-   *
-   * The cursor after replacing is inserted between the selection and the suffix.
-   */
-  createReplaceSelectionHandler(prefix, suffix) {
-    return () => {
-      const cm = this.getCodeMirror();
-      const selection = cm.getDoc().getSelection();
-      const curStartPos = cm.getCursor('from');
-      const curEndPos = cm.getCursor('to');
-
-      const curPosAfterReplacing = {};
-      curPosAfterReplacing.line = curEndPos.line;
-      if (curStartPos.line === curEndPos.line) {
-        curPosAfterReplacing.ch = curEndPos.ch + prefix.length;
-      }
-      else {
-        curPosAfterReplacing.ch = curEndPos.ch;
-      }
-
-      cm.getDoc().replaceSelection(prefix + selection + suffix);
-      cm.setCursor(curPosAfterReplacing);
-      cm.focus();
-    };
-  }
-
-  /**
-   * return a function to add prefix to selected each lines
-   *
-   * The cursor after editing is inserted between the end of the selection.
-   */
-  createAddPrefixToEachLinesHandler(prefix) {
-    return () => {
-      const cm = this.getCodeMirror();
-      const startLineNum = cm.getCursor('from').line;
-      const endLineNum = cm.getCursor('to').line;
-
-      const lines = [];
-      for (let i = startLineNum; i <= endLineNum; i++) {
-        lines.push(prefix + cm.getDoc().getLine(i));
-      }
-      const replacement = `${lines.join('\n')}\n`;
-      cm.getDoc().replaceRange(replacement, { line: startLineNum, ch: 0 }, { line: endLineNum + 1, ch: 0 });
-
-      cm.setCursor(endLineNum, cm.getDoc().getLine(endLineNum).length);
-      cm.focus();
-    };
-  }
-
-  /**
-   * make a selected line a header
-   *
-   * The cursor after editing is inserted between the end of the line.
-   */
-  makeHeaderHandler() {
-    const cm = this.getCodeMirror();
-    const lineNum = cm.getCursor('from').line;
-    const line = cm.getDoc().getLine(lineNum);
-    let prefix = '#';
-    if (!line.startsWith('#')) {
-      prefix += ' ';
-    }
-    cm.getDoc().replaceRange(prefix, { line: lineNum, ch: 0 }, { line: lineNum, ch: 0 });
-    cm.focus();
-  }
-
-  // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-  // showGridEditorHandler() {
-  //   this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
-  // }
-
-  showTemplateModal() {
-    const onSubmit = templateText => this.insertText(templateText);
-    this.props.onClickTemplateBtn({ onSubmit });
-  }
-
-  showLinkEditModal() {
-    const onSubmit = (linkText) => {
-      return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText);
-    };
-
-    const defaultMarkdownLink = markdownLinkUtil.getMarkdownLink(this.getCodeMirror());
-
-    this.props.onClickLinkEditBtn(defaultMarkdownLink, onSubmit);
-  }
-
-  // fold draw.io section (``` drawio ~ ```)
-  foldDrawioSection() {
-    const editor = this.getCodeMirror();
-    const lineNumbers = mdu.findAllDrawioSection(editor);
-    lineNumbers.forEach((lineNumber) => {
-      editor.foldCode({ line: lineNumber, ch: 0 }, { scanUp: false }, 'fold');
-    });
-  }
-
-  clickDrawioIconHandler() {
-    const drawioMxFile = mdu.getMarkdownDrawioMxfile(this.getCodeMirror());
-
-    this.props.onClickDrawioBtn(
-      drawioMxFile,
-      // onSave
-      (drawioMxFile) => {
-        mdu.replaceFocusedDrawioWithEditor(this.getCodeMirror(), drawioMxFile);
-        // Fold the section after the drawio section (```drawio) has been updated.
-        this.foldDrawioSection();
-      },
-    );
-  }
-
-  clickTableIconHandler() {
-    const markdownTable = mtu.getMarkdownTable(this.getCodeMirror());
-
-    this.props.onClickTableBtn(
-      markdownTable,
-      this.getCodeMirror(),
-      this.props.editorSettings.autoFormatMarkdownTable,
-    );
-  }
-
-  getNavbarItems() {
-    return [
-      <Button
-        key="nav-item-bold"
-        color={null}
-        size="sm"
-        title="Bold"
-        onClick={this.createReplaceSelectionHandler('**', '**')}
-      >
-        <EditorIcon icon="Bold" />
-      </Button>,
-      <Button
-        key="nav-item-italic"
-        color={null}
-        size="sm"
-        title="Italic"
-        onClick={this.createReplaceSelectionHandler('*', '*')}
-      >
-        <EditorIcon icon="Italic" />
-      </Button>,
-      <Button
-        key="nav-item-strikethrough"
-        color={null}
-        size="sm"
-        title="Strikethrough"
-        onClick={this.createReplaceSelectionHandler('~~', '~~')}
-      >
-        <EditorIcon icon="Strikethrough" />
-      </Button>,
-      <Button
-        key="nav-item-header"
-        color={null}
-        size="sm"
-        title="Heading"
-        onClick={this.makeHeaderHandler}
-      >
-        <EditorIcon icon="Heading" />
-      </Button>,
-      <Button
-        key="nav-item-code"
-        color={null}
-        size="sm"
-        title="Inline Code"
-        onClick={this.createReplaceSelectionHandler('`', '`')}
-      >
-        <EditorIcon icon="InlineCode" />
-      </Button>,
-      <Button
-        key="nav-item-quote"
-        color={null}
-        size="sm"
-        title="Quote"
-        onClick={this.createAddPrefixToEachLinesHandler('> ')}
-      >
-        <EditorIcon icon="Quote" />
-      </Button>,
-      <Button
-        key="nav-item-ul"
-        color={null}
-        size="sm"
-        title="List"
-        onClick={this.createAddPrefixToEachLinesHandler('- ')}
-      >
-        <EditorIcon icon="List" />
-      </Button>,
-      <Button
-        key="nav-item-ol"
-        color={null}
-        size="sm"
-        title="Numbered List"
-        onClick={this.createAddPrefixToEachLinesHandler('1. ')}
-      >
-        <EditorIcon icon="NumberedList" />
-      </Button>,
-      <Button
-        key="nav-item-checkbox"
-        color={null}
-        size="sm"
-        title="Check List"
-        onClick={this.createAddPrefixToEachLinesHandler('- [ ] ')}
-      >
-        <EditorIcon icon="CheckList" />
-      </Button>,
-      <Button
-        key="nav-item-attachment"
-        color={null}
-        size="sm"
-        title="Attachment"
-        onClick={this.props.onAddAttachmentButtonClicked}
-      >
-        <EditorIcon icon="Attachment" />
-      </Button>,
-      <Button
-        key="nav-item-link"
-        color={null}
-        size="sm"
-        title="Link"
-        onClick={this.showLinkEditModal}
-      >
-        <EditorIcon icon="Link" />
-      </Button>,
-      <Button
-        key="nav-item-image"
-        color={null}
-        size="sm"
-        title="Image"
-        onClick={this.createReplaceSelectionHandler('![', ']()')}
-      >
-        <EditorIcon icon="Image" />
-      </Button>,
-      // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-      // <Button
-      //   key="nav-item-grid"
-      //   color={null}
-      //   size="sm"
-      //   title="Grid"
-      //   onClick={this.showGridEditorHandler}
-      // >
-      //   <EditorIcon icon="Grid" />
-      // </Button>,
-      <Button
-        key="nav-item-table"
-        color={null}
-        size="sm"
-        title="Table"
-        onClick={this.clickTableIconHandler}
-      >
-        <EditorIcon icon="Table" />
-      </Button>,
-      <Button
-        key="nav-item-drawio"
-        color={null}
-        bssize="small"
-        title="draw.io"
-        onClick={this.clickDrawioIconHandler}
-      >
-        <EditorIcon icon="Drawio" />
-      </Button>,
-      <Button
-        key="nav-item-emoji"
-        color={null}
-        bssize="small"
-        title="Emoji"
-        onClick={() => this.showEmojiPicker()}
-      >
-        <EditorIcon icon="Emoji" />
-      </Button>,
-      <Button
-        key="nav-item-template"
-        color={null}
-        bssize="small"
-        title="Template"
-        onClick={() => this.showTemplateModal()}
-      >
-        <EditorIcon icon="Template" />
-      </Button>,
-    ];
-  }
-
-
-  render() {
-    const additionalClasses = Array.from(this.state.additionalClassSet).join(' ');
-    const placeholder = this.state.isGfmMode ? 'Input with Markdown..' : 'Input with Plain Text..';
-
-    const gutters = [];
-    if (this.props.lineNumbers != null) {
-      gutters.push('CodeMirror-linenumbers', 'CodeMirror-foldgutter');
-    }
-
-    return (
-      <div className={`grw-codemirror-editor ${styles['grw-codemirror-editor']}`}>
-
-        <UncontrolledCodeMirror
-          ref={this.cm}
-          className={additionalClasses}
-          placeholder="search"
-          value={this.props.value}
-          options={{
-            indentUnit: this.props.indentSize,
-            theme: this.props.editorSettings.theme ?? 'elegant',
-            styleActiveLine: this.props.editorSettings.styleActiveLine,
-            lineWrapping: true,
-            scrollPastEnd: true,
-            autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-            autoCloseTags: true,
-            placeholder,
-            matchBrackets: true,
-            emoji: true,
-            matchTags: { bothTags: true },
-            // folding
-            foldGutter: this.props.lineNumbers,
-            gutters,
-            // match-highlighter, matchesonscrollbar, annotatescrollbar options
-            highlightSelectionMatches: { annotateScrollbar: true },
-            // continuelist, indentlist
-            extraKeys: {
-              Enter: this.handleEnterKey,
-              'Ctrl-Enter': this.handleCtrlEnterKey,
-              'Cmd-Enter': this.handleCtrlEnterKey,
-              Tab: 'indentMore',
-              'Shift-Tab': 'indentLess',
-              'Ctrl-Q': (cm) => { cm.foldCode(cm.getCursor()) },
-            },
-          }}
-          onCursor={this.cursorHandlerDebounced}
-          onScroll={(editor, data) => {
-            if (this.props.onScroll != null) {
-            // add line data
-              const line = editor.lineAtHeight(data.top, 'local');
-              data.line = line;
-              this.props.onScroll(data);
-            }
-          }}
-          onChange={this.changeHandler}
-          onDragEnter={(editor, event) => {
-            if (this.props.onDragEnter != null) {
-              this.props.onDragEnter(event);
-            }
-          }}
-          onKeyPress={this.keyPressHandler}
-          onKeyDown={this.keyDownHandler}
-          onPasteFiles={this.pasteHandler}
-          onScrollCursorIntoView={this.scrollCursorIntoViewHandlerThrottled}
-        />
-
-        { this.renderLoadingKeymapOverlay() }
-
-        { this.renderCheatsheetOverlay() }
-        { this.renderEmojiPicker() }
-
-        {/*
-        // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
-        <GridEditModal
-          ref={this.gridEditModal}
-          onSave={(grid) => { return geu.replaceGridWithHtmlWithEditor(this.getCodeMirror(), grid) }}
-        />
-         */}
-      </div>
-    );
-  }
-
-}
-
-CodeMirrorEditor.propTypes = Object.assign({
-  lineNumbers: PropTypes.bool,
-  editorSettings: PropTypes.object.isRequired,
-  onMarkdownHelpButtonClicked: PropTypes.func,
-  onAddAttachmentButtonClicked: PropTypes.func,
-}, AbstractEditor.propTypes);
-
-CodeMirrorEditor.defaultProps = {
-  lineNumbers: true,
-};
-
-const CodeMirrorEditorMemoized = memo(CodeMirrorEditor);
-
-
-const CodeMirrorEditorFc = React.forwardRef((props, ref) => {
-  const { open: openDrawioModal } = useDrawioModal();
-  const { open: openHandsontableModal } = useHandsontableModal();
-  const { open: openTemplateModal } = useTemplateModal();
-  const { open: openLinkEditModal } = useLinkEditModal();
-
-  const openDrawioModalHandler = useCallback((drawioMxFile, onSave) => {
-    openDrawioModal(drawioMxFile, onSave);
-  }, [openDrawioModal]);
-
-  const openTableModalHandler = useCallback((markdownTable, editor, autoFormatMarkdownTable) => {
-    openHandsontableModal(markdownTable, editor, autoFormatMarkdownTable);
-  }, [openHandsontableModal]);
-
-  const openTemplateModalHandler = useCallback((onSubmit) => {
-    openTemplateModal(onSubmit);
-  }, [openTemplateModal]);
-
-  const openLinkEditModalHandler = useCallback((defaultMarkdownLink, onSubmit) => {
-    openLinkEditModal(defaultMarkdownLink, onSubmit);
-  }, [openLinkEditModal]);
-
-  return (
-    <CodeMirrorEditorMemoized
-      ref={ref}
-      onClickDrawioBtn={openDrawioModalHandler}
-      onClickTableBtn={openTableModalHandler}
-      onClickTemplateBtn={openTemplateModalHandler}
-      onClickLinkEditBtn={openLinkEditModalHandler}
-      {...props}
-    />
-  );
-});
-
-CodeMirrorEditorFc.displayName = 'CodeMirrorEditorFc';
-
-export default memo(CodeMirrorEditorFc);

+ 0 - 126
apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss

@@ -1,126 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-.grw-codemirror-editor :global {
-  @import '~codemirror/lib/codemirror';
-
-  // addons
-  @import '~codemirror/addon/fold/foldgutter';
-  @import '~codemirror/addon/lint/lint';
-
-  // themes
-  @import '~codemirror/theme/elegant';
-  @import '~codemirror/theme/eclipse';
-
-  .CodeMirror {
-    font-family: var(--font-family-monospace);
-    font-size: 15px;
-
-    pre.CodeMirror-line.grw-cm-header-line {
-      padding-top: 0.16em;
-      padding-bottom: 0.08em;
-      font-family: var(--font-family-monospace);
-
-      // '#'
-      .cm-formatting-header {
-        font-style: italic;
-        font-weight: bold;
-        opacity: 0.5;
-      }
-
-      .cm-header-1 {
-        font-size: 1.9em;
-      }
-      .cm-header-2 {
-        font-size: 1.6em;
-      }
-      .cm-header-3 {
-        font-size: 1.4em;
-      }
-      .cm-header-4 {
-        font-size: 1.35em;
-      }
-      .cm-header-5 {
-        font-size: 1.25em;
-      }
-      .cm-header-6 {
-        font-size: 1.2em;
-      }
-    }
-
-    .cm-matchhighlight {
-      color: bs.$gray-900 !important;
-      background-color: cyan;
-    }
-
-    .CodeMirror-selection-highlight-scrollbar {
-      background-color: darkcyan;
-    }
-
-    // overwrite .CodeMirror-placeholder
-    pre.CodeMirror-line-like.CodeMirror-placeholder {
-      color: bs.$text-muted;
-    }
-
-    // overwrite .CodeMirror-scroll
-    .CodeMirror-scroll {
-      box-sizing: border-box;
-    }
-  }
-
-  // patch to fix https://github.com/codemirror/CodeMirror/issues/4089
-  // see also https://github.com/codemirror/CodeMirror/commit/51a1e7da60a99e019f026a118dc7c98c2b1f9d62
-  .CodeMirror-wrap > div > textarea {
-    font-size: #{bs.$line-height-base}rem;
-  }
-
-  // overwrite .CodeMirror-hints
-  .CodeMirror-hints {
-    max-height: 30em !important;
-
-    // active line
-    .CodeMirror-hint-active.crowi-emoji-autocomplete {
-      .img-container {
-        padding-top: 0.3em;
-        padding-bottom: 0.3em;
-        font-size: 15px; // adjust to .wiki
-      }
-    }
-  }
-
-  // cheat sheat
-  .overlay.overlay-gfm-cheatsheet {
-    align-items: flex-end;
-    justify-content: flex-end;
-
-    pointer-events: none;
-
-    .card.gfm-cheatsheet {
-      box-shadow: unset;
-      opacity: 0.6;
-      .card-body {
-        min-width: 30em;
-        padding-bottom: 0;
-        font-family: var(--font-family-monospace);
-        color: bs.$text-muted;
-      }
-      ul > li {
-        list-style: none;
-      }
-    }
-
-    .gfm-cheatsheet-modal-link {
-      color: bs.$text-muted;
-      pointer-events: all;
-      cursor: pointer;
-      background-color: transparent;
-      border: none;
-
-      opacity: 0.6;
-
-      &:hover,
-      &:focus {
-        opacity: 1;
-      }
-    }
-  }
-}

+ 0 - 65
apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts

@@ -1,65 +0,0 @@
-import { Editor } from 'codemirror';
-import { i18n } from 'next-i18next';
-import { debounce } from 'throttle-debounce';
-
-import { apiv3Get } from '~/client/util/apiv3-client';
-
-type UsersListForHints = {
-  text: string
-  displayText: string
-}
-export default class CommentMentionHelper {
-
-  editor: Editor;
-
-  constructor(editor: Editor) {
-    this.editor = editor;
-  }
-
-  getUsenameHint = (): void => {
-    // Get word that contains `@` character at the begining
-    const currentPos = this.editor.getCursor();
-    const wordStart = this.editor.findWordAt(currentPos).anchor.ch - 1;
-    const wordEnd = this.editor.findWordAt(currentPos).head.ch;
-
-    const searchFrom = { line: currentPos.line, ch: wordStart };
-    const searchTo = { line: currentPos.line, ch: wordEnd };
-
-    const searchMention = this.editor.getRange(searchFrom, searchTo);
-    const isMentioning = searchMention.charAt(0) === '@';
-
-    // Return nothing if not mentioning
-    if (!isMentioning) {
-      return;
-    }
-
-    // Get username after `@` character and search username
-    const mention = searchMention.slice(1);
-    this.editor.showHint({
-      completeSingle: false,
-      hint: async() => {
-        if (mention.length > 0) {
-          const users = await this.getUsersList(mention);
-          return {
-            // Returns default value if i18n is null because it cannot do early return.
-            list: users.length > 0 ? users : [{ text: '', displayText: i18n != null ? i18n.t('page_comment.no_user_found') : 'No user found' }],
-            from: searchFrom,
-            to: searchTo,
-          };
-        }
-      },
-    });
-  };
-
-  getUsersList = async(q: string): Promise<UsersListForHints[]> => {
-    const limit = 20;
-    const { data } = await apiv3Get('/users/usernames', { q, limit });
-    return data.activeUser.usernames.map((username: string) => ({
-      text: `@${username} `,
-      displayText: username,
-    }));
-  };
-
-  showUsernameHint = debounce(800, () => this.getUsenameHint());
-
-}

+ 0 - 344
apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx

@@ -1,344 +0,0 @@
-import React, {
-  useState, useEffect, useRef, useMemo, useCallback,
-} from 'react';
-
-import type { IRevisionOnConflict } from '@growi/core';
-import { UserPicture } from '@growi/ui/dist/components';
-import CodeMirror from 'codemirror/lib/codemirror';
-import { format, parseISO } from 'date-fns';
-import { useTranslation } from 'next-i18next';
-import {
-  Modal, ModalHeader, ModalBody, ModalFooter,
-} from 'reactstrap';
-
-import { useSaveOrUpdate } from '~/client/services/page-operation';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { OptionsToSave } from '~/interfaces/page-operation';
-import { useCurrentPathname, useCurrentUser } from '~/stores/context';
-import { useCurrentPagePath, useSWRxCurrentPage, useCurrentPageId } from '~/stores/page';
-import {
-  useRemoteRevisionBody, useRemoteRevisionId, useRemoteRevisionLastUpdatedAt, useRemoteRevisionLastUpdateUser, useSetRemoteLatestPageData,
-} from '~/stores/remote-latest-page';
-
-import ExpandOrContractButton from '../ExpandOrContractButton';
-import { UncontrolledCodeMirror } from '../UncontrolledCodeMirror';
-
-require('codemirror/lib/codemirror.css');
-require('codemirror/addon/merge/merge');
-require('codemirror/addon/merge/merge.css');
-const DMP = require('diff_match_patch');
-
-Object.keys(DMP).forEach((key) => { window[key] = DMP[key] });
-
-type ConflictDiffModalProps = {
-  isOpen?: boolean;
-  onClose?: (() => void);
-  markdownOnEdit: string;
-  optionsToSave: OptionsToSave | undefined;
-  afterResolvedHandler: () => void,
-};
-
-type ConflictDiffModalCoreProps = {
-  isOpen?: boolean;
-  onClose?: (() => void);
-  optionsToSave: OptionsToSave | undefined;
-  request: IRevisionOnConflictWithStringDate,
-  origin: IRevisionOnConflictWithStringDate,
-  latest: IRevisionOnConflictWithStringDate,
-  afterResolvedHandler: () => void,
-};
-
-type IRevisionOnConflictWithStringDate = Omit<IRevisionOnConflict, 'createdAt'> & {
-  createdAt: string
-}
-
-const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element => {
-  const {
-    onClose, request, origin, latest, optionsToSave, afterResolvedHandler,
-  } = props;
-
-  const { t } = useTranslation('');
-  const [resolvedRevision, setResolvedRevision] = useState<string>('');
-  const [isRevisionselected, setIsRevisionSelected] = useState<boolean>(false);
-  const [isModalExpanded, setIsModalExpanded] = useState<boolean>(false);
-  const [codeMirrorRef, setCodeMirrorRef] = useState<HTMLDivElement | null>(null);
-
-  const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
-  const { data: pageId } = useCurrentPageId();
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: currentPathname } = useCurrentPathname();
-
-  const saveOrUpdate = useSaveOrUpdate();
-
-  const uncontrolledRef = useRef<CodeMirror>(null);
-
-  useEffect(() => {
-    if (codeMirrorRef != null) {
-      CodeMirror.MergeView(codeMirrorRef, {
-        value: origin.revisionBody,
-        origLeft: request.revisionBody,
-        origRight: latest.revisionBody,
-        lineNumbers: true,
-        collapseIdentical: true,
-        showDifferences: true,
-        highlightDifferences: true,
-        connect: 'connect',
-        readOnly: true,
-        revertButtons: false,
-      });
-    }
-  }, [codeMirrorRef, origin.revisionBody, request.revisionBody, latest.revisionBody]);
-
-  const close = useCallback(() => {
-    if (onClose != null) {
-      onClose();
-    }
-  }, [onClose]);
-
-  const onResolveConflict = useCallback(async() => {
-    if (currentPathname == null) { return }
-    // disable button after clicked
-    setIsRevisionSelected(false);
-
-    const codeMirrorVal = uncontrolledRef.current?.editor.doc.getValue();
-
-    try {
-      const { page } = await saveOrUpdate(
-        codeMirrorVal,
-        { pageId, path: currentPagePath || currentPathname, revisionId: remoteRevisionId },
-        optionsToSave,
-      );
-      const remotePageData = {
-        remoteRevisionId: page.revision._id,
-        remoteRevisionBody: page.revision.body,
-        remoteRevisionLastUpdateUser: page.lastUpdateUser,
-        remoteRevisionLastUpdatedAt: page.updatedAt,
-        revisionIdHackmdSynced: page.revisionIdHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      setRemoteLatestPageData(remotePageData);
-      afterResolvedHandler();
-
-      close();
-
-      toastSuccess('Saved successfully');
-    }
-    catch (error) {
-      toastError(`Error occured: ${error.message}`);
-    }
-
-  }, [afterResolvedHandler, close, currentPagePath, currentPathname, optionsToSave, pageId, remoteRevisionId, saveOrUpdate, setRemoteLatestPageData]);
-
-  // TODO: No longer support custom close icon in bootstrap v5
-  // const resizeAndCloseButtons = useMemo(() => (
-  //   <div className="d-flex flex-nowrap">
-  //     <ExpandOrContractButton
-  //       isWindowExpanded={isModalExpanded}
-  //       expandWindow={() => setIsModalExpanded(true)}
-  //       contractWindow={() => setIsModalExpanded(false)}
-  //     />
-  //     <button type="button" className="close text-white" onClick={close} aria-label="Close">
-  //       <span aria-hidden="true">&times;</span>
-  //     </button>
-  //   </div>
-  // ), [isModalExpanded, close]);
-
-  const isOpen = props.isOpen ?? false;
-
-  return (
-    <Modal
-      isOpen={isOpen}
-      toggle={close}
-      backdrop="static"
-      className={`${isModalExpanded ? ' grw-modal-expanded' : ''}`}
-      size="xl"
-    >
-      {/* <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3" close={resizeAndCloseButtons}> */}
-      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3">
-        <span className="material-symbols-outlined">error</span>{t('modal_resolve_conflict.resolve_conflict')}
-      </ModalHeader>
-      <ModalBody className="mx-4 my-1">
-        { isOpen
-        && (
-          <div className="row">
-            <div className="col-12 text-center mt-2 mb-4">
-              <h2 className="fw-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={request.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {request.user.username}</p>
-                  <p className="my-0">{request.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.origin_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={origin.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {origin.user.username}</p>
-                  <p className="my-0">{origin.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-4">
-              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
-              <div className="d-flex align-items-center my-3">
-                <div>
-                  <UserPicture user={latest.user} size="lg" noLink noTooltip />
-                </div>
-                <div className="ms-3 text-muted">
-                  <p className="my-0">updated by {latest.user.username}</p>
-                  <p className="my-0">{latest.createdAt}</p>
-                </div>
-              </div>
-            </div>
-            <div className="col-12" ref={(el) => { setCodeMirrorRef(el) }}></div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(request.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'mine' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(origin.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-4">
-              <div className="text-center my-4">
-                <button
-                  type="button"
-                  className="btn btn-outline-primary"
-                  onClick={() => {
-                    setIsRevisionSelected(true);
-                    setResolvedRevision(latest.revisionBody);
-                  }}
-                >
-                  <span className="material-symbols-outlined">arrow_circle_down</span>
-                  {t('modal_resolve_conflict.select_revision', { revision: 'theirs' })}
-                </button>
-              </div>
-            </div>
-            <div className="col-12">
-              <div className="border border-dark">
-                <h3 className="fw-bold my-2 mx-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
-                <UncontrolledCodeMirror
-                  ref={uncontrolledRef}
-                  value={resolvedRevision}
-                  options={{
-                    placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
-                  }}
-                />
-              </div>
-            </div>
-          </div>
-        )}
-      </ModalBody>
-      <ModalFooter>
-        <button
-          type="button"
-          className="btn btn-outline-secondary"
-          onClick={onClose}
-        >
-          {t('Cancel')}
-        </button>
-        <button
-          type="button"
-          className="btn btn-primary ms-3"
-          onClick={onResolveConflict}
-          disabled={!isRevisionselected}
-        >
-          {t('modal_resolve_conflict.resolve_and_save')}
-        </button>
-      </ModalFooter>
-    </Modal>
-  );
-};
-
-
-export const ConflictDiffModal = (props: ConflictDiffModalProps): JSX.Element => {
-  const {
-    isOpen, onClose, optionsToSave, afterResolvedHandler,
-  } = props;
-  const { data: currentUser } = useCurrentUser();
-
-  // state for current page
-  const { data: currentPage } = useSWRxCurrentPage();
-
-  // state for latest page
-  const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { data: remoteRevisionBody } = useRemoteRevisionBody();
-  const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
-  const { data: remoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
-
-  const currentTime: Date = new Date();
-
-  const isRemotePageDataInappropriate = remoteRevisionId == null || remoteRevisionBody == null || remoteRevisionLastUpdateUser == null;
-
-  if (!isOpen || currentUser == null || currentPage == null || isRemotePageDataInappropriate) {
-    return <></>;
-  }
-
-  const currentPageCreatedAtFixed = typeof currentPage.updatedAt === 'string'
-    ? parseISO(currentPage.updatedAt)
-    : currentPage.updatedAt;
-
-  const request: IRevisionOnConflictWithStringDate = {
-    revisionId: '',
-    revisionBody: props.markdownOnEdit,
-    createdAt: format(currentTime, 'yyyy/MM/dd HH:mm:ss'),
-    user: currentUser,
-  };
-  const origin: IRevisionOnConflictWithStringDate = {
-    revisionId: currentPage?.revision._id,
-    revisionBody: currentPage?.revision.body,
-    createdAt: format(currentPageCreatedAtFixed, 'yyyy/MM/dd HH:mm:ss'),
-    user: currentPage?.lastUpdateUser,
-  };
-  const latest: IRevisionOnConflictWithStringDate = {
-    revisionId: remoteRevisionId,
-    revisionBody: remoteRevisionBody,
-    createdAt: format(new Date(remoteRevisionLastUpdatedAt || currentTime.toString()), 'yyyy/MM/dd HH:mm:ss'),
-    user: remoteRevisionLastUpdateUser,
-  };
-
-  const propsForCore = {
-    isOpen,
-    onClose,
-    optionsToSave,
-    request,
-    origin,
-    latest,
-    afterResolvedHandler,
-  };
-
-  return <ConflictDiffModalCore {...propsForCore} />;
-};

+ 0 - 174
apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -1,174 +0,0 @@
-import React, {
-  FC, useCallback, useRef,
-} from 'react';
-
-import { useTranslation } from 'next-i18next';
-import { useRipple } from 'react-use-ripple';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import { useUserUISettings } from '~/client/services/user-ui-settings';
-import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
-import { Themes, useNextThemes } from '~/stores/use-next-themes';
-
-import SidebarDockIcon from '../Icons/SidebarDockIcon';
-import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
-
-type AppearanceModeDropdownProps = {
-  isAuthenticated: boolean,
-}
-export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: AppearanceModeDropdownProps) => {
-
-  const { t } = useTranslation('commons');
-
-  const { isAuthenticated } = props;
-
-  const {
-    setTheme, resolvedTheme, useOsSettings, isDarkMode, isForcedByGrowiTheme,
-  } = useNextThemes();
-  const { data: isPreferDrawerMode, update: updatePreferDrawerMode } = usePreferDrawerModeByUser();
-  const { data: isPreferDrawerModeOnEdit, mutate: mutatePreferDrawerModeOnEdit } = usePreferDrawerModeOnEditByUser();
-  const { scheduleToPut } = useUserUISettings();
-
-  // ripple
-  const buttonRef = useRef(null);
-  useRipple(buttonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  const preferDrawerModeSwitchModifiedHandler = useCallback((preferDrawerMode: boolean, isEditMode: boolean) => {
-    if (isEditMode) {
-      mutatePreferDrawerModeOnEdit(preferDrawerMode);
-      scheduleToPut({ preferDrawerModeOnEditByUser: preferDrawerMode });
-    }
-    else {
-      updatePreferDrawerMode(preferDrawerMode);
-    }
-  }, [updatePreferDrawerMode, mutatePreferDrawerModeOnEdit, scheduleToPut]);
-
-  const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
-    if (isChecked) {
-      setTheme(Themes.SYSTEM);
-    }
-    else {
-      setTheme(resolvedTheme ?? Themes.LIGHT);
-    }
-  }, [resolvedTheme, setTheme]);
-
-  const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
-    setTheme(isDarkMode ? Themes.DARK : Themes.LIGHT);
-  }, [setTheme]);
-
-  /* eslint-disable react/prop-types */
-  const IconWithTooltip = ({
-    id, label, children, additionalClasses,
-  }) => (
-    <>
-      <div id={id} className={`px-2 grw-icon-container ${additionalClasses != null ? additionalClasses : ''}`}>{children}</div>
-      <UncontrolledTooltip placement="bottom" fade={false} target={id}>{label}</UncontrolledTooltip>
-    </>
-  );
-
-  const dropdownDivider = <div className="dropdown-divider"></div>;
-
-  const renderSidebarModeSwitch = useCallback((isEditMode: boolean) => {
-    return (
-      <>
-        <h6 className="dropdown-header">{t(isEditMode ? 'personal_dropdown.sidebar_mode_editor' : 'personal_dropdown.sidebar_mode')}</h6>
-        <form className="px-4">
-          <div className="justify-content-center">
-            <div className="col-auto mb-0 d-flex align-items-center">
-              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-drawer' : 'iwt-sidebar-drawer'} label="Drawer" additionalClasses="grw-sidebar-mode-icon">
-                <SidebarDrawerIcon />
-              </IconWithTooltip>
-              <div className="form-check form-switch form-check-secondary ms-2">
-                <input
-                  id={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}
-                  className="form-check-input"
-                  type="checkbox"
-                  checked={isEditMode ? !isPreferDrawerModeOnEdit : !isPreferDrawerMode}
-                  onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked, isEditMode)}
-                />
-                <label className="form-label form-check-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
-              </div>
-              <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-dock' : 'iwt-sidebar-dock'} label="Dock" additionalClasses="grw-sidebar-mode-icon">
-                <SidebarDockIcon />
-              </IconWithTooltip>
-            </div>
-          </div>
-        </form>
-      </>
-    );
-  }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
-
-  return (
-    <div className="dropend">
-      {/* setting button */}
-      {/* remove .dropdown-toggle for hide caret */}
-      {/* See https://stackoverflow.com/a/44577512/13183572 */}
-      <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <span className="material-symbols-outlined">settings</span>
-      </button>
-
-      {/* dropdown */}
-      <div className="dropdown-menu">
-
-        {/* sidebar mode */}
-        {renderSidebarModeSwitch(false)}
-
-        {/* side bar mode on editor */}
-        {isAuthenticated && (
-          <>
-            {dropdownDivider}
-            {renderSidebarModeSwitch(true)}
-          </>
-        )}
-
-        {/* color mode */}
-        { !isForcedByGrowiTheme && (
-          <>
-            {dropdownDivider}
-            <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
-            <form className="px-4">
-              <div className="justify-content-center">
-                <div className="col-auto d-flex align-items-center">
-                  <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                  <span className="material-symbols-outlined">light_mode</span>
-                  </IconWithTooltip>
-                  <div className="form-check form-switch form-check-secondary ms-2">
-                    <input
-                      id="swUserPreference"
-                      className="form-check-input"
-                      type="checkbox"
-                      checked={isDarkMode}
-                      disabled={useOsSettings}
-                      onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
-                    />
-                    <label className="form-label form-check-label" htmlFor="swUserPreference"></label>
-                  </div>
-                  <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
-                  <span className="material-symbols-outlined">dark_mode</span>
-                  </IconWithTooltip>
-                </div>
-              </div>
-              <div>
-                <div className="col-auto">
-                  <div className="form-check">
-                    <input
-                      id="cbFollowOs"
-                      className="form-check-input"
-                      type="checkbox"
-                      checked={useOsSettings}
-                      onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
-                    />
-                    <label className="form-label form-check-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
-                  </div>
-                </div>
-              </div>
-            </form>
-          </>
-        ) }
-
-      </div>
-
-    </div>
-  );
-
-};

+ 0 - 672
apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss

@@ -1,672 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as *;
-
-@use '../variables' as var;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/count-badge';
-@use './mixins/hsl-button';
-@use './hsl-functions' as hsl;
-
-// determine optional variables
-:root[data-bs-theme='dark'] {
-  $color-list: var(--color-list,var(--color-global));
-  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
-  $color-list-hover: var(--color-list-hover,var(--color-global));
-  $color-list-active: var(--color-list-active,var(--color-reversal));
-  $bgcolor-list-hover: var(--bgcolor-list-hover,var(--bgcolor-global));
-  $bgcolor-list-active: var(--bgcolor-list-active,var(--primary));
-  $color-table: var(--color-table,white);
-  $bgcolor-table: var(--bgcolor-table,#343a40);
-  $border-color-table: var(--border-color-table,lighten(#343a40, 7.5%));
-  $color-table-hover: var(--color-table-hover,rgba(white, 0.075));
-  $bgcolor-table-hover: var(--bgcolor-table-hover,lighten(#343a40, 7.5%));
-  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
-  $color-tags: var(--color-tags,#949494);
-  $bgcolor-tags: var(--bgcolor-tags,var(--dark));
-  $border-color-global: var(--border-color-global,#{$gray-500});
-  $border-color-toc: var(--border-color-toc,#{$border-color-global});
-  $color-dropdown: var(--color-dropdown,var(--color-global));
-  $bgcolor-dropdown: var(--bgcolor-dropdown,var(--bgcolor-global));
-  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
-  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--light));
-  $bgcolor-dropdown-link-hover: var(--bgcolor-dropdown-link-hover,hsl.lighten(var(--bgcolor-global), 15%));
-  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--light));
-  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
-  $body-bg: var(--bgcolor-global);
-  $body-color: var(--color-global);
-
-  // override bootstrap variables
-  // $text-muted: $gray-550;
-  $table-dark-color: $color-table;
-  $table-dark-bg: $bgcolor-table;
-  $table-dark-border-color: $border-color-table;
-  $table-dark-hover-color: $color-table-hover;
-  $table-dark-hover-bg: $bgcolor-table-hover;
-  $border-color: $border-color-global;
-  $dropdown-color: $color-dropdown;
-  $dropdown-bg: $bgcolor-dropdown;
-  $dropdown-link-color: $color-dropdown-link;
-  $dropdown-link-hover-color: $color-dropdown-link-hover;
-  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
-  $dropdown-link-active-color: $color-dropdown-link-active;
-  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-  @import './mixins/list-group';
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-  // @import './reboot-bootstrap-text';
-  // @import './reboot-bootstrap-border-colors';
-  // @import './reboot-bootstrap-tables';
-  // @import './reboot-bootstrap-theme-colors';
-  // @import 'hsl-reboot-bootstrap-theme-colors';
-  // @import './reboot-bootstrap-dropdown';
-
-
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-
-  //   // List Group
-  //   @include override-list-group-item(
-  //     $color-list,
-  //     $bgcolor-sidebar-list-group,
-  //     $color-list-hover,
-  //     $bgcolor-list-hover,
-  //     $color-list-active,
-  //     $bgcolor-list-active
-  //   );
-  //   /*
-  //     * Form
-  //     */
-  //   input.form-control,
-  //   select.form-control,
-  //   select.form-select,
-  //   textarea.form-control {
-  //     color: var(--color-global);
-  //     background-color: hsl.darken(var(--bgcolor-global), 5%);
-  //     border-color: $border-color-global;
-  //     &:focus {
-  //       background-color: var(--bgcolor-global);
-  //     }
-  //     // FIXME: accent color
-  //     // border: 1px solid darken($border, 30%);
-  //   }
-
-  //   .form-control[disabled],
-  //   .form-control[readonly] {
-  //     color: hsl.lighten(var(--color-global),10%);
-  //     background-color: hsl.lighten(var(--bgcolor-global),5%);
-  //   }
-
-  // TODO: theme-color() dropped in bootstrap v5
-  // TODO: .input-group-prepend dropped in bootstrap v5
-  // https://redmine.weseek.co.jp/issues/128307
-  //   .input-group > .input-group-prepend > .input-group-text {
-  //     color: theme-color('light');
-  //     background-color: theme-color('secondary');
-  //     border: 1px solid theme-color('secondary');
-  //     border-right: none;
-  //     &.text-muted {
-  //       color: theme-color('light') !important;
-  //     }
-  //   }
-
-  //   .input-group input {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   label.form-check-label::before {
-  //     background-color: hsl.darken(var(--bgcolor-global),5%);
-  //   }
-
-  //   .rbt-input-multi .rbt-input-main {
-  //     color: black;
-  //   }
-  //   /*
-  //   * Table
-  //   */
-  //   .table {
-  //     @extend .table-dark !optional;
-  //     thead th {
-  //       vertical-align: bottom;
-  //       border-bottom: 2px solid #d6dadf;
-  //     }
-  //   }
-
-  //   /*
-  //   * Card
-  //   */
-  //   .card:not([class*='bg-']):not(.custom-card):not(.card-disabled) {
-  //     @extend .bg-dark;
-  //   }
-
-  //   .card.custom-card {
-  //     border-color: var(--secondary);
-  //   }
-
-  //   .card.card-disabled {
-  //     background-color: lighten($dark, 10%);
-  //     border-color: var(--secondary);
-  //   }
-
-  //   /*
-  //   * Pagination
-  //   */
-  //   ul.pagination {
-  //     li.page-item.disabled {
-  //       button.page-link {
-  //         color: $gray-400;
-  //       }
-  //     }
-  //     li.page-item.active {
-  //       button.page-link {
-  //         color: hsl.contrast(var(--primary));
-  //         background-color: var(--primary);
-  //         &:hover,
-  //         &:focus {
-  //           color: hsl.contrast(var(--primary));
-  //           background-color: var(--primary);
-  //         }
-  //       }
-  //     }
-  //     li.page-item {
-  //       button.page-link {
-  //         @extend .btn-dark;
-  //         color: var(--primary);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Login form
-  //   */
-  //   .nologin {
-  //     // background color
-  //     $color-gradient: #3c465c;
-  //     background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-  //       linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
-  //       linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
-  //       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
-
-  //     .nologin-header {
-  //       background-color: rgba(black, 0.5);
-
-  //       .logo {
-  //         background-color: rgba(white, 0);
-  //         fill: rgba(white, 0.5);
-  //       }
-
-  //       h1 {
-  //         color: rgba(white, 0.5);
-  //       }
-  //     }
-
-  //     .nologin-dialog {
-  //       background-color: rgba(black, 0.5);
-  //       .link-switch {
-  //         color: #7b9bd5;
-  //         &:hover {
-  //           color: lighten(#7b9bd5,10%);
-  //         }
-  //       }
-  //     }
-
-  //     .input-group {
-  //       .input-group-text {
-  //         color: darken(white, 30%);
-  //         background-color: rgba($gray-700, 0.7);
-  //       }
-
-  //       .form-control {
-  //         color: white;
-  //         background-color: rgba(#505050, 0.7);
-  //         box-shadow: unset;
-
-  //         &::placeholder {
-  //           color: darken(white, 30%);
-  //         }
-  //       }
-  //     }
-
-  //     .btn-fill {
-  //       .btn-label {
-  //         color: $gray-300;
-  //       }
-  //       .btn-label-text {
-  //         color: $gray-400;
-  //       }
-  //     }
-
-  //     .grw-external-auth-form {
-  //       border-color: gray !important;
-  //     }
-
-  //     .btn-external-auth-tab {
-  //       @extend .btn-dark;
-  //     }
-
-  //     // footer link text
-  //     .link-growi-org {
-  //       color: rgba(white, 0.4);
-
-  //       &:hover,
-  //       &.focus {
-  //         color: rgba(white, 0.7);
-
-  //         .growi {
-  //           color: darken(var.$growi-green, 5%);
-  //         }
-
-  //         .org {
-  //           color: darken(var.$growi-blue, 5%);
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI subnavigation
-  //   */
-  //   .grw-drawer-toggler {
-  //     @include button-variant($dark, $dark);
-  //     @include mixins-buttons.button-svg-icon-variant($dark, $dark);
-  //     color: $gray-400;
-  //     box-shadow: none !important;
-  //   }
-
-  //   /**
-  //    * GROWI PagePathHierarchicalLink
-  //    */
-  //   .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-  //     color: $gray-400;
-  //   }
-
-  //   /*
-  //   * GROWI page list
-  //   */
-  //   .page-list {
-  //     .page-list-ul {
-  //       > li {
-  //         > span.page-list-meta {
-  //           color: hsl.darken(var(--color-global),10%);
-  //         }
-  //       }
-  //     }
-
-  //     // List group
-  //     .list-group-item {
-  //       &.active {
-  //         background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
-  //       }
-  //       &.list-group-item-action:hover {
-  //         background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
-  //       }
-  //       .page-list-snippet {
-  //         color: hsl.darken(var(--color-global),10%);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI ToC
-  //   */
-  //   .revision-toc-content {
-  //     ::marker {
-  //       color: hsl.lighten(var(--color-global),30%);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI subnavigation
-  //   */
-  //   .grw-subnav {
-  //     background-color: var(--bgcolor-subnav);
-  //   }
-
-  //   .grw-subnav-fixed-container .grw-subnav {
-  //     background-color: hsl.alpha(var(--bgcolor-subnav),85%);
-  //   }
-
-  //   .grw-page-editor-mode-manager {
-  //     .btn-outline-primary {
-  //       &:hover {
-  //         color: var(--primary);
-  //         background-color: $gray-700;
-  //       }
-  //     }
-  //   }
-
-  //   // Search drop down
-  //   #search-typeahead-asynctypeahead {
-  //     background-color: var(--bgcolor-global);
-  //     .table {
-  //       background-color: transparent;
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Sidebar
-  //   */
-  //   .grw-sidebar {
-  //     --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
-  //     --gray-500-hs: 210,13%;
-  //     --gray-500-l: 61%;
-  //     // List
-  //     @include override-list-group-item(
-  //       $color-list,
-  //       $bgcolor-sidebar-list-group,
-  //       $color-list-hover,
-  //       $bgcolor-list-hover,
-  //       $color-list-active,
-  //       $bgcolor-list-active
-  //     );
-  //     // Pagetree
-  //     .grw-pagetree, .grw-foldertree {
-  //       @include override-list-group-item-for-pagetree(
-  //         var(--color-sidebar-context),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),8%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),15%),
-  //         hsl.darken(var(--color-sidebar-context),15%),
-  //         hsl.darken(var(--color-sidebar-context),10%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),18%),
-  //         hsl.lighten(var(--bgcolor-sidebar-context),24%)
-  //       );
-  //       .grw-pagetree-triangle-btn, .grw-foldertree-triangle-btn {
-  //         @include mixins-buttons.button-outline-svg-icon-variant(var(--secondary), $gray-200);
-  //       }
-  //       .btn-page-item-control {
-  //         @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
-  //         &:hover {
-  //           background-color: hsl.lighten(var(--bgcolor-sidebar-context),20%);
-  //         }
-  //         &:not(:disabled):not(.disabled):active,
-  //         &:not(:disabled):not(.disabled).active {
-  //           background-color: hsl.lighten(var(--bgcolor-sidebar-context),34%);
-  //         }
-  //         box-shadow: none !important;
-  //       }
-  //     }
-
-  //     // bookmarks
-  //     .grw-folder-tree-container {
-  //       .grw-drop-item-area , .grw-foldertree-item-container {
-  //         .grw-accept-drop-item {
-  //           border-color: hsl.lighten(var(--bgcolor-sidebar-context), 30%) !important;
-  //         }
-  //       }
-  //     }
-  //     .private-legacy-pages-link {
-  //       &:hover {
-  //         background: var(--bgcolor-list-hover);
-  //       }
-  //     }
-  //   }
-
-  //   .btn.btn-page-item-control {
-  //     @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
-  //     &:hover {
-  //       background-color: $gray-700;
-  //     }
-  //     &:not(:disabled):not(.disabled):active,
-  //     &:not(:disabled):not(.disabled).active {
-  //       color: $gray-200;
-  //       background-color: $gray-600;
-  //     }
-  //     box-shadow: none !important;
-  //   }
-
-  //   // Bookmark item on user page
-  //   .grw-user-page-list-m {
-  //     @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-  //     .grw-foldertree {
-  //       @include override-list-group-item-for-pagetree(
-  //         $body-color,
-  //         hsl.lighten($body-bg, 8%),
-  //         hsl.lighten($body-bg, 15%),
-  //         hsl.darken($body-color, 15%),
-  //         hsl.darken($body-color, 10%),
-  //         hsl.lighten($body-bg, 18%),
-  //         hsl.lighten($body-bg, 24%)
-  //       );
-  //       .grw-foldertree-triangle-btn {
-  //         @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
-  //       }
-  //     }
-  //     .grw-folder-tree-container {
-  //       .grw-drop-item-area , .grw-foldertree-item-container {
-  //         .grw-accept-drop-item {
-  //           border-color: hsl.lighten(var($body-bg), 30%) !important;
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   // Bookmark dropdown menu
-  //   .grw-bookmark-folder-dropdown  {
-  //     .grw-bookmark-folder-menu {
-  //       .form-control{
-  //         &:focus {
-  //           color: $body-color
-  //         }
-  //       }
-  //       .grw-bookmark-folder-menu-item  {
-  //         @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
-  //         .grw-bookmark-folder-menu-item-title {
-  //           color: $body-color
-  //         }
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * Popover
-  //   */
-  //   .popover {
-  //     background-color: var(--bgcolor-global);
-  //     border-color: var(--secondary);
-  //     .popover-header {
-  //       color: white;
-  //       background-color: var(--secondary);
-  //       border-color: var(--secondary);
-  //     }
-  //     .popover-body {
-  //       color: inherit;
-  //     }
-
-  // TODO: Check renamed .arrow to .popover-arrow
-  // see: https://getbootstrap.com/docs/5.2/migration/#popovers
-
-  //     &.bs-popover-top .arrow {
-  //       &::before {
-  //         border-top-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-top-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-bottom .arrow {
-  //       &::before {
-  //         border-bottom-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-bottom-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-right .arrow {
-  //       &::before {
-  //         border-right-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-right-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //     &.bs-popover-left .arrow {
-  //       &::before {
-  //         border-left-color: var(--secondary);
-  //       }
-
-  //       &::after {
-  //         border-left-color: var(--bgcolor-global);
-  //       }
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI Grid Edit Modal
-  //   */
-  //   .grw-grid-edit-preview {
-  //     background: $gray-900;
-  //   }
-
-  //   /*
-  //   * Slack
-  //   */
-  //   .grw-slack-notification {
-  //     background-color: transparent;
-  //     $color-slack: #4b144c;
-
-  //     .form-control {
-  //       background: var(--bgcolor-global);
-  //     }
-
-  //     .form-check-label {
-  //       &::before {
-  //         background-color: var(--secondary);
-  //         border-color: transparent;
-  //       }
-  //       &::after {
-  //         background-color: darken($color-slack, 5%);
-  //         background-image: url(/images/icons/slack/slack-logo-dark-off.svg);
-  //       }
-  //     }
-
-  //     .form-check-input:checked ~ .form-check-label {
-  //       &::before {
-  //         background-color: lighten($color-slack, 10%);
-  //       }
-  //       &::after {
-  //         background-color: darken($color-slack, 5%);
-  //         background-image: url(/images/icons/slack/slack-logo-dark-on.svg);
-  //       }
-  //     }
-  //     .grw-slack-logo svg {
-  //       fill: #dd80de;
-  //     }
-
-  //     .grw-btn-slack {
-  //       background-color: black;
-  //       &:focus,
-  //       &:hover {
-  //         background-color: black;
-  //       }
-  //     }
-
-  //     .grw-btn-slack-triangle {
-  //       color: var(--secondary);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI HandsontableModal
-  //   */
-
-  //   .handsontable td {
-  //     color: black;
-  //   }
-
-  //   .grw-hot-modal-navbar {
-  //     background-color: var(--dark);
-  //   }
-
-  //   .wiki {
-  //     h1 {
-  //       border-color: hsl.lighten(var(--border-color-theme),10%);
-  //     }
-  //     h2 {
-  //       border-color: var(--border-color-theme);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI comment form
-  //   */
-  //   .comment-form {
-  //     #slack-mark-black {
-  //       display: none;
-  //     }
-  //   }
-
-  //   .page-comment-form .comment-form-main {
-  //     &:before {
-  //       border-right-color: var(--bgcolor-global);
-  //     }
-  //   }
-
-  //   /*
-  //   * GROWI tags
-  //   */
-  //   .grw-tag-labels {
-  //     .grw-tag-label {
-  //       color: $color-tags;
-  //       background-color: $bgcolor-tags;
-  //     }
-  //   }
-
-  //   mark.rbt-highlight-text {
-  //     color: var(--color-global);
-  //   }
-
-  //   /*
-  //   * GROWI popular tags
-  //   */
-  //   .grw-popular-tag-labels {
-  //     .grw-tag-label {
-  //       color: $color-tags;
-  //       background-color: $bgcolor-tags;
-  //     }
-  //   }
-
-  //   /*
-  //   * admin settings
-  //   */
-  //   .admin-setting-header {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * grw-side-contents
-  //   */
-  //   .grw-side-contents-sticky-container {
-  //     .grw-count-badge {
-  //       @include count-badge.count-badge($gray-400, $gray-700);
-  //     }
-
-  //     .grw-border-vr {
-  //       border-color: $border-color-toc;
-  //     }
-
-  //     .revision-toc {
-  //       border-color: $border-color-toc;
-  //     }
-  //   }
-
-  //   /*
-  //   * drawio
-  //   */
-  //   .drawio-viewer {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * modal
-  //   */
-  //   .grw-modal-head {
-  //     border-color: $border-color-global;
-  //   }
-
-  //   /*
-  //   * skeleton
-  //   */
-  //   .grw-skeleton {
-  //     background-color: hsl.lighten(var(--bgcolor-subnav),10%);
-  //   }
-}

+ 0 - 536
apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss

@@ -1,536 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as *;
-
-@use '../variables' as var;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/count-badge';
-@use './mixins/hsl-button';
-@use './hsl-functions' as hsl;
-
-// determine optional variables
-:root[data-bs-theme='light'] {
-  $color-list: var(--color-list,var(--color-global));
-  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
-  $color-list-hover: var(--color-list-hover,var(--color-global));
-  $bgcolor-list-hover: var(--bgcolor-list-hover, var(--bgcolor-global));
-  $bgcolor-list-active: var(--bgcolor-list-active, hsl.lighten(var(--primary),65%));
-  $color-list-active: var(--color-list-active,hsl(var(--primary-hs), clamp(10%, (100% - var(--primary-l)  - 65% - 51%) * 1000, 95%)));
-  $color-table: var(--color-table,var(--color-global));
-  $bgcolor-table: var(--bgcolor-table,null);
-  $border-color-table: var(--border-color-table,#{$gray-200});
-  $color-table-hover: var(--color-table-hover,var(--color-table));
-  $bgcolor-table-hover: var(--bgcolor-table-hover,rgba(black, 0.075));
-  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
-  $color-tags: var(--color-tags,var(--secondary));
-  $bgcolor-tags: var(--bgcolor-tags,#{$gray-200});
-  $border-color-global: var(--border-color-global,#{$gray-300});
-  $border-color-toc: var(--border-color-toc,#{$gray-300});
-  $color-dropdown: var(--color-dropdown,var(--color-global));
-  $bgcolor-dropdown: var(--color-dropdown,var(--bgcolor-global));
-  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
-  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--color-global));
-  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--color-reversal));
-  $bgcolor-dropdown-link-hover: hsl.darken(var(--bgcolor-global),15%);
-  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
-  $body-bg: var(--bgcolor-global);
-  $body-color: var(--color-global);
-
-  // override bootstrap variables
-  $text-muted: $gray-500;
-  $table-color: $color-table;
-  $table-bg: $bgcolor-table;
-  $table-border-color: $border-color-table;
-  $table-hover-color: $color-table-hover;
-  $table-hover-bg: $bgcolor-table-hover;
-  $border-color: $border-color-global;
-  $dropdown-color: $color-dropdown;
-  $dropdown-link-color: $color-dropdown-link;
-  $dropdown-link-hover-color: $color-dropdown-link-hover;
-  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
-  $dropdown-link-active-color: $color-dropdown-link-active;
-  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-  @import './mixins/list-group';
-  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-  // @import './reboot-bootstrap-text';
-  // @import './reboot-bootstrap-border-colors';
-  // @import './reboot-bootstrap-tables';
-  // @import './reboot-bootstrap-theme-colors';
-  // @import 'hsl-reboot-bootstrap-theme-colors';
-  // @import './reboot-bootstrap-dropdown';
-
-  // List Group
-  @include override-list-group-item(
-    $color-list,
-    $bgcolor-sidebar-list-group,
-    $color-list-hover,
-    $bgcolor-list-hover,
-    $color-list-active,
-    $bgcolor-list-active
-  );
-  /*
-  * Form
-  */
-  .form-control {
-    background-color: var(--bgcolor-global);
-  }
-
-  .form-control::placeholder {
-    color: hsl.darken(var(--bgcolor-global), 20%);
-  }
-
-  .form-control[disabled],
-  .form-control[readonly] {
-    color: hsl.lighten(var(--color-global),10%);
-    background-color: hsl.darken(var(--bgcolor-global),5%);
-  }
-
-  /*
-  * card
-  */
-  .card.card-disabled {
-    background-color: var(--bgcolor-global);
-    border-color: $gray-200;
-  }
-
-  /*
-  * GROWI Login form
-  */
-  .nologin {
-    // background color
-    $color-gradient: #3c465c;
-    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
-      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
-
-    .nologin-header {
-      background-color: rgba(white, 0.5);
-
-      .logo {
-        background-color: rgba(black, 0);
-        fill: rgba(black, 0.5);
-      }
-
-      h1 {
-        color: rgba(black, 0.5);
-      }
-    }
-
-    .nologin-dialog {
-      background-color: rgba(white, 0.5);
-      .link-switch {
-        color: #1939b8;
-        &:hover {
-          color: lighten(#1939b8,20%);
-        }
-      }
-    }
-
-    .dropdown-with-icon {
-      .dropdown-toggle {
-        color: white;
-        background-color: rgba($gray-600, 0.7);
-        box-shadow: unset;
-        &:focus {
-          color: white;
-          background-color: rgba($gray-600, 0.7);
-        }
-      }
-      i {
-        color: darken(white, 30%);
-        background-color: rgba($gray-700, 0.7);
-      }
-    }
-
-    .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba($gray-700, 0.7);
-      }
-
-      .form-control {
-        color: white;
-        background-color: rgba($gray-600, 0.7);
-        box-shadow: unset;
-
-        &::placeholder {
-          color: darken(white, 30%);
-        }
-      }
-    }
-
-    // footer link text
-    .link-growi-org {
-      color: rgba(black, 0.4);
-
-      &:hover,
-      &.focus {
-        color: black;
-
-        .growi {
-          color: darken(var.$growi-green, 20%);
-        }
-
-        .org {
-          color: darken(var.$growi-blue, 15%);
-        }
-      }
-    }
-  }
-
-  /*
-  * GROWI subnavigation
-  */
-  // .grw-subnav {
-  //   background-color: var(--bgcolor-subnav);
-  // }
-
-  .grw-subnav-fixed-container .grw-subnav {
-    background-color: hsl.alpha(var(--bgcolor-subnav),85%);
-  }
-
-  .grw-page-editor-mode-manager {
-    .btn-outline-primary {
-      &:hover {
-        color: var(--primary);
-        background-color: $gray-200;
-      }
-    }
-  }
-
-  .grw-drawer-toggler {
-    @include button-variant($light, $light);
-    @include mixins-buttons.button-svg-icon-variant($light, $light);
-    color: $gray-500;
-    box-shadow: none !important;
-  }
-
-  /**
-   * GROWI PagePathHierarchicalLink
-   */
-  // .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-  //   color: $gray-600;
-  // }
-  /*
-  * GROWI Sidebar
-  */
-  .grw-sidebar {
-    // List
-    @include override-list-group-item(
-      $color-list,
-      $bgcolor-sidebar-list-group,
-      $color-list-hover,
-      $bgcolor-list-hover,
-      $color-list-active,
-      $bgcolor-list-active
-    );
-    // sidebar-centent-bg
-    .grw-navigation-wrap {
-      // Drop a shadow on the light theme. The dark theme makes '$ bgcolor-sidebar-context' brighter than the body.
-      box-shadow: 0px 0px 3px rgba(black, 0.24);
-    }
-    // Pagetree
-    .grw-pagetree, .grw-foldertree {
-      @include override-list-group-item-for-pagetree(
-        var(--color-sidebar-context),
-        hsl.darken(var(--bgcolor-sidebar-context),5%),
-        hsl.darken(var(--bgcolor-sidebar-context),12%),
-        hsl.lighten(var(--color-sidebar-context),10%),
-        hsl.lighten(var(--color-sidebar-context),8%),
-        hsl.darken(var(--bgcolor-sidebar-context),15%),
-        hsl.darken(var(--bgcolor-sidebar-context),24%)
-      );
-
-      .grw-pagetree-triangle-btn, .grw-foldertree-triangle-btn {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, var(--primary));
-      }
-    }
-
-    // bookmark
-    .grw-folder-tree-container {
-      .grw-drop-item-area, .grw-foldertree-item-container  {
-        .grw-accept-drop-item {
-          border-color: hsl.darken(var(--bgcolor-sidebar-context), 30%) !important;
-        }
-      }
-    }
-
-    .private-legacy-pages-link {
-      &:hover {
-        background: $bgcolor-list-hover;
-      }
-    }
-  }
-
-  .btn.btn-page-item-control {
-    --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
-    --gray-500-hs: 210,13%;
-    --gray-500-l: 61%;
-    @include hsl-button.button-outline-variant(var(--gray-500), var(--primary), #{hsl.lighten(var(--primary), 52%)}, transparent);
-    &:hover {
-      background-color: hsl.lighten(var(--primary), 58%);
-    }
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: var(--primary);
-    }
-    box-shadow: none !important;
-  }
-
-
-  // Bookmark item on user page
-  .grw-user-page-list-m {
-    @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-    .grw-foldertree {
-      @include override-list-group-item-for-pagetree(
-        $body-color,
-        hsl.darken($body-bg, 5%),
-        hsl.darken($body-bg, 12%),
-        hsl.lighten($body-color, 10%),
-        hsl.lighten($body-color, 8%),
-        hsl.darken($body-bg, 15%),
-        hsl.darken($body-bg, 24%)
-      );
-      .grw-foldertree-triangle-btn {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
-      }
-    }
-    .grw-folder-tree-container {
-      .grw-drop-item-area, .grw-foldertree-item-container  {
-        .grw-accept-drop-item {
-          border-color: hsl.darken(var($body-bg), 30%) !important;
-        }
-      }
-    }
-  }
-
-  // Bookmark dropdown menu
-  .grw-bookmark-folder-dropdown  {
-    .grw-bookmark-folder-menu {
-      .form-control{
-        &:focus {
-          color: $body-color
-        }
-      }
-      .grw-bookmark-folder-menu-item {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
-        .grw-bookmark-folder-menu-item-title {
-          color: $body-color
-        }
-      }
-    }
-  }
-
-  /*
-  * GROWI page list
-  */
-  .page-list {
-    .page-list-ul {
-      > li {
-        > span.page-list-meta {
-          color: hsl.lighten(var(--color-global),10%);
-        }
-      }
-    }
-    // List group
-    .list-group-item {
-      &.active {
-        background-color: hsl.lighten(var(--primary),77%) !important;
-      }
-      &.list-group-item-action:hover {
-        background-color: hsl.lighten(var(--primary),72%) !important;
-      }
-      .page-list-snippet {
-        color: hsl.lighten(var(--color-global),10%);
-      }
-    }
-  }
-
-  /*
-  * GROWI ToC
-  */
-  .revision-toc-content {
-    ::marker {
-      color: hsl.darken(var(--bgcolor-global),20%);
-    }
-  }
-
-  /*
-  * GROWI Editor
-  */
-  .grw-editor-navbar-bottom {
-    background-color: $gray-100;
-
-    #slack-mark-white {
-      display: none;
-    }
-
-    .input-group-text {
-      margin-right: 1px;
-      color: var(--secondary);
-      border-color: var(--light);
-    }
-
-    .btn.btn-outline-secondary {
-      border-color: $border-color;
-    }
-  }
-
-  /*
-  * GROWI Link Edit Modal
-  */
-  .link-edit-modal {
-    span i {
-      color: $gray-400;
-    }
-  }
-
-  /*
-  * GROWI Grid Edit Modal
-  */
-
-  .grw-grid-edit-preview {
-    background: $gray-100;
-  }
-
-  /*
-  * Slack
-  */
-  .grw-slack-notification {
-    background-color: white;
-    $color-slack: #4b144c;
-
-    .form-control {
-      background: white;
-    }
-
-    .form-check-label {
-      &::before {
-        background-color: $gray-200;
-        border-color: transparent;
-      }
-      &::after {
-        background-color: white;
-        background-image: url(/images/icons/slack/slack-logo-off.svg);
-      }
-    }
-    .form-check-input:checked ~ .form-check-label {
-      &::before {
-        background-color: lighten($color-slack, 60%);
-      }
-      &::after {
-        background-image: url(/images/icons/slack/slack-logo-on.svg);
-      }
-    }
-    .grw-slack-logo svg {
-      fill: #af30b0;
-    }
-
-    .grw-btn-slack {
-      background-color: white;
-
-      &:hover,
-      &:focus {
-        background-color: white;
-      }
-    }
-
-    .grw-btn-slack-triangle {
-      color: var(--secondary);
-    }
-  }
-
-  /*
-  * GROWI HandsontableModal
-  */
-  .grw-hot-modal-navbar {
-    background-color: var(--light);
-  }
-
-  .wiki {
-    h1 {
-      border-color: var(--border-color-theme);
-    }
-    h2 {
-      border-color: var(--border-color-theme);
-    }
-  }
-
-  /*
-  * GROWI comment form
-  */
-  .comment-form {
-    #slack-mark-white {
-      display: none;
-    }
-  }
-
-  .page-comment-form .comment-form-main {
-    &:before {
-      border-right-color: var(--bgcolor-global);
-    }
-  }
-
-  /*
-  * GROWI tags
-  */
-  .grw-tag-labels {
-    .grw-tag-label {
-      color: $color-tags;
-      background-color: $bgcolor-tags;
-    }
-  }
-
-  /*
-  * GROWI popular tags
-  */
-  .grw-popular-tag-labels {
-    .grw-tag-label {
-      color: $color-tags;
-      background-color: $bgcolor-tags;
-    }
-  }
-
-  /*
-  * grw-side-contents
-  */
-  .grw-side-contents-sticky-container {
-    .grw-count-badge {
-      @include count-badge.count-badge($gray-600, $gray-200);
-    }
-
-    .grw-border-vr {
-      border-color: $border-color-toc;
-    }
-    .revision-toc {
-      border-color: $border-color-toc;
-    }
-  }
-
-  /*
-  * drawio
-  */
-  .drawio-viewer {
-    border-color: $border-color-global;
-  }
-
-  /*
-  * admin settings
-  */
-  .admin-setting-header {
-    border-color: $border-color;
-  }
-
-  /*
-  * modal
-  */
-  .grw-modal-head {
-    border-color: $border-color-global;
-  }
-
-  /*
-  * skeleton
-  */
-  .grw-skeleton {
-    background-color: hsl.darken(var(--bgcolor-subnav),10%);
-  }
-}

+ 0 - 22
apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss

@@ -1,22 +0,0 @@
-.toast-success {
-  background-color: var(--success) !important;
-}
-
-.toast-error {
-  background-color: var(--danger) !important;
-}
-
-.toast-info {
-  background-color: var(--info) !important;
-}
-
-.toast-warning {
-  background-color: var(--warning) !important;
-}
-
-:root {
-  --toastify-color-info: var(--info);
-  --toastify-color-success: var(--success);
-  --toastify-color-warning: var(--warning);
-  --toastify-color-error: var(--danger);
-}

+ 0 - 727
apps/app/_obsolete/src/styles/theme/apply-colors.scss

@@ -1,727 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as *;
-
-@use '../variables' as var;
-@use '../mixins';
-@use '../atoms/mixins/code';
-@use './mixins/hsl-button';
-@use './hsl-functions' as hsl;
-
-@import 'apply-colors-dark';
-@import 'apply-colors-light';
-
-//
-//== Apply to Bootstrap
-//
-
-// determine optional variables
-$bgcolor-search-top-dropdown: var(--bgcolor-search-top-dropdown,var(--secondary));
-$bgcolor-sidebar-nav-item-active: var(--bgcolor-sidebar-nav-item-active,#{hsl.darken(var(--primary),10%)});
-$text-shadow-sidebar-nav-item-active: var(--text-shadow-sidebar-nav-item-active,1px 1px 2px var(--primary));
-$bgcolor-inline-code: var(--bgcolor-inline-code, #{$gray-100});
-$color-inline-code: var(--color-inline-code, #{darken($red, 15%)});
-$bordercolor-inline-code: var(--bordercolor-inline-code, #{$gray-400});
-$bordercolor-nav-tabs: var(--bordercolor-nav-tabs, #{$gray-300});
-$bordercolor-nav-tabs-hover: var(--bordercolor-nav-tabs-hover,#{$gray-200} #{$gray-200} #{$bordercolor-nav-tabs});
-$border-nav-tabs-link-active: var(--border-nav-tabs-link-active, #{$gray-600});
-$bordercolor-nav-tabs-active: var(--bordercolor-nav-tabs-active,$bordercolor-nav-tabs $bordercolor-nav-tabs var(--bgcolor-global));
-$color-btn-reload-in-sidebar: var(--color-btn-reload-in-sidebar,#{$gray-500});
-$bgcolor-keyword-highlighted: var(--bgcolor-keyword-highlighted,#{var.$grw-marker-yellow});
-$color-page-list-group-item-meta: var(--color-page-list-group-item-meta,#{$gray-500});
-$color-search-page-list-title: var(--color-search-page-list-title,var(--color-global));
-
-// override bootstrap variables
-$body-bg: var(--bgcolor-global);
-$body-color: var(--color-global);
-$link-color: var(--color-link);
-$link-hover-color: var(--color-link-hover);
-$input-focus-color: var(--color-global);
-$nav-tabs-border-color: $bordercolor-nav-tabs;
-$nav-tabs-link-hover-border-color: $bordercolor-nav-tabs-hover;
-$nav-tabs-link-active-color: var(--color-nav-tabs-link-active);
-$nav-tabs-link-active-bg: var(--bgcolor-global);
-$nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
-$theme-colors: map-merge($theme-colors, ( primary: $primary ));
-
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-// @import 'reboot-bootstrap-buttons';
-// @import 'reboot-bootstrap-colors';
-// @import 'reboot-bootstrap-theme-colors';
-// @import 'hsl-reboot-bootstrap-theme-colors';
-// @import 'reboot-bootstrap-nav';
-// @import 'reboot-toastr-colors';
-
-// determine variables with bootstrap function (These variables can be used after importing bootstrap above)
-$color-modal-header: var(--color-modal-header,#{hsl.contrast(var(--primary))});
-
-code:not([class^='language-']) {
-  @include code.code-inline-color($color-inline-code, $bgcolor-inline-code, $bordercolor-inline-code);
-}
-
-.code-highlighted {
-  border-color: $bordercolor-inline-code;
-}
-
-//
-//== Apply to Bootstrap Elements
-//
-
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-// theme-color-level() dropped in bootstrap v5
-// Alert link
-// @each $color, $value in $theme-colors {
-//   .alert.alert-#{$color} {
-//     a,
-//     a:hover {
-//       color: theme-color-level($color, $alert-color-level - 2);
-//     }
-//   }
-// }
-
-// Dropdown
-.grw-apperance-mode-dropdown {
-  .grw-sidebar-mode-icon svg {
-    fill: var(--secondary);
-  }
-  .grw-color-mode-icon svg {
-    fill: var(--color-global);
-  }
-  .grw-color-mode-icon-muted svg {
-    fill: var(--secondary);
-  }
-}
-
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-// form-control-focus() dropped in bootstrap v5
-// Form
-// .form-control {
-//   @include form-control-focus();
-// }
-
-// Tabs
-.nav.nav-tabs .nav-link.active {
-  color: var(--color-link);
-  background: transparent;
-
-  &:hover,
-  &:focus {
-    color: var(--color-link-hover);
-  }
-}
-
-// Pagination
-ul.pagination {
-  li.page-item.disabled {
-    button.page-link {
-      color: $gray-400;
-    }
-  }
-  li.page-item.active {
-    button.page-link {
-      color: hsl.contrast(var(--primary));
-      background-color: var(--primary);
-      &:hover,
-      &:focus {
-        color: hsl.contrast(var(--primary));
-        background-color: var(--primary);
-      }
-    }
-  }
-  li.page-item {
-    button.page-link {
-      color: var(--primary);
-      border-color: var(--secondary) !important;
-      &:hover,
-      &:active,
-      &:focus {
-        color: var(--primary);
-      }
-    }
-  }
-}
-
-//
-//== Apply to Handsontable
-//
-.handsontable {
-  color: initial;
-}
-
-//
-//== Apply to GROWI Elements
-//
-
-.grw-logo {
-  // set transition for fill
-  svg, svg * {
-    transition: fill 0.8s ease-out;
-  }
-
-  svg {
-    fill: var(--fillcolor-logo-mark);
-  }
-
-  &:hover {
-    svg {
-      .group1 {
-        fill: var.$growi-green;
-      }
-
-      .group2 {
-        fill: var.$growi-blue;
-      }
-    }
-  }
-}
-
-.grw-navbar {
-  background: var(--bgcolor-navbar);
-  .nav-item .nav-link {
-    color: var(--color-link-nabvar);
-  }
-
-  border-image: var(--border-image-navbar) !important;
-  border-image-slice: 1 !important;
-
-  .grw-app-title {
-    color: var(--fillcolor-logo-mark);
-  }
-}
-
-.grw-global-search {
-  .btn-secondary.dropdown-toggle {
-    @include hsl-button.button-variant(var(--bgcolor-search-top-dropdown), var(--bgcolor-search-top-dropdown));
-  }
-
-  // for https://youtrack.weseek.co.jp/issue/GW-2603
-  .search-typeahead {
-    background-color: hsl.alpha(var(--bgcolor-global),10%);
-  }
-  input.form-control {
-    border: none;
-  }
-}
-
-.grw-sidebar {
-  $color-resize-button: var(--color-resize-button,var(--color-global));
-  $bgcolor-resize-button: var(--bgcolor-resize-button,white);
-  $color-resize-button-hover: var(--color-resize-button-hover,var(--color-reversal));
-  $bgcolor-resize-button-hover: var(--bgcolor-resize-button-hover,#{hsl.lighten(var(--bgcolor-resize-button), 5%)});
-  // .grw-navigation-resize-button {
-  //   .hexagon-container svg {
-  //     .background {
-  //       fill: var(--bgcolor-resize-button);
-  //     }
-  //     .icon {
-  //       fill: var(--color-resize-button);
-  //     }
-  //   }
-  //   &:hover .hexagon-container svg {
-  //     .background {
-  //       fill: var(--bgcolor-resize-button-hover);
-  //     }
-  //     .icon {
-  //       fill: var(--color-resize-button-hover);
-  //     }
-  //   }
-  // }
-  div.grw-contextual-navigation {
-    > div {
-      color: var(--color-sidebar-context);
-      background-color: var(--bgcolor-sidebar-context);
-    }
-  }
-
-  .grw-sidebar-nav {
-    .btn {
-      @include hsl-button.button-variant(
-        var(--bgcolor-sidebar),
-        var(--bgcolor-sidebar),
-      );
-    }
-  }
-  .grw-sidebar-nav-primary-container {
-    .btn.active {
-      i {
-        text-shadow: $text-shadow-sidebar-nav-item-active;
-      }
-      // fukidashi
-      &:after {
-        border-right-color: var(--bgcolor-sidebar-context) !important;
-      }
-    }
-  }
-
-  .grw-sidebar-content-header {
-    .grw-btn-reload {
-      color: $color-btn-reload-in-sidebar;
-    }
-
-    .grw-recent-changes-resize-button {
-      .form-check-label::before {
-        background-color: var(--primary);
-      }
-
-      .form-check-label::after {
-        background-color: var(--bgcolor-global);
-      }
-
-      .form-check-input:not(:checked) + .form-check-label::before {
-        color: var(--bgcolor-global);
-      }
-
-      .form-check-input:checked + .form-check-label::before {
-        color: var(--bgcolor-global);
-        background-color: var(--primary);
-        border-color: var(--primary);
-      }
-      .form-check-input:checked + .form-check-label::after {
-        color: var(--bgcolor-global);
-      }
-    }
-  }
-
-  .grw-pagetree, .grw-foldertree {
-    .list-group-item {
-      .grw-pagetree-title-anchor, .grw-foldertree-title-anchor {
-        color: inherit;
-      }
-    }
-  }
-
-  .grw-pagetree-footer {
-    .h5.grw-private-legacy-pages-anchor {
-      color: inherit;
-    }
-  }
-
-  .grw-recent-changes {
-    .list-group {
-      .list-group-item {
-        background-color: transparent !important;
-
-        .icon-lock {
-          color: var(--color-link);
-        }
-
-        .grw-recent-changes-item-lower {
-          color: $gray-500;
-
-          svg {
-            fill: $gray-500;
-          }
-        }
-      }
-    }
-  }
-
-}
-
-/*
- * Icon
- */
-.editor-container .navbar-editor svg {
-  fill: var(--color-editor-icons);
-}
-
-// page preview button in link form
-.btn-page-preview svg {
-  fill: white;
-}
-
-/*
- * Modal
- */
-.modal {
-  .modal-header {
-    border-bottom-color: var(--border-color-theme);
-    .modal-title {
-      color: $color-modal-header;
-    }
-    .btn-close {
-      color: $color-modal-header;
-      opacity: 0.5;
-
-      &:hover {
-        opacity: 0.9;
-      }
-    }
-  }
-
-  .modal-content {
-    background-color: var(--bgcolor-global);
-  }
-
-  .modal-footer {
-    border-top-color: var(--border-color-theme);
-  }
-}
-
-.grw-page-accessories-modal,.grw-descendants-page-list-modal {
-  .modal-header {
-    .btn-close {
-      color: #{hsl.contrast(var(--bgcolor-global))};
-    }
-  }
-}
-
-.grw-custom-nav-tab {
-  .nav-item {
-    &:hover,
-    &:focus {
-      background-color: hsl.alpha(var(--color-link),10%);
-    }
-    .nav-link {
-      -webkit-appearance: none;
-      color: var(--color-link);
-      svg {
-        fill: var(--color-link);
-      }
-
-      // Disabled state lightens text
-      &.disabled {
-        color: $nav-link-disabled-color;
-        svg {
-          fill: $nav-link-disabled-color;
-        }
-      }
-    }
-  }
-
-  .grw-nav-slide-hr {
-    border-color: var(--color-link) !important;
-  }
-}
-
-/*
- * cards
- */
-.card.custom-card {
-  color: var(--color-global);
-  background-color: var(--bgcolor-card);
-  border-color: var(--light);
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-}
-
-.admin-bot-card {
-  .grw-botcard-title-active {
-    color: $gray-200;
-  }
-}
-
-/*
- * Form Slider
- */
-.admin-page {
-  span.slider {
-    background-color: $gray-300;
-
-    &:before {
-      background-color: white;
-    }
-  }
-
-  input:checked + .slider {
-    background-color: #007bff;
-  }
-
-  input:focus + .slider {
-    box-shadow: 0 0 1px #007bff;
-  }
-}
-
-/*
- * GROWI wiki
- */
-.wiki {
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6,
-  h7 {
-    &.blink {
-      @include mixins.blink-bgcolor(var(--bgcolor-blinked-section));
-    }
-  }
-
-  .highlighted-keyword {
-    background: linear-gradient(transparent 60%, $bgcolor-keyword-highlighted 60%);
-  }
-
-  a {
-    color: var(--color-link-wiki);
-
-    &:hover {
-      color: var(--color-link-wiki-hover);
-    }
-  }
-
-  // table with handsontable modal button
-  .editable-with-handsontable {
-    button {
-      color: var(--color-link-wiki);
-    }
-
-    button:hover {
-      color: var(--color-link-wiki-hover);
-    }
-  }
-}
-
-/*
- * GROWI page-list
- */
-.page-list {
-  // List group
-  .list-group {
-    .list-group-item {
-      background-color: var(--bgcolor-global) !important;
-      a {
-        svg {
-          fill: var(--color-global);
-        }
-
-        &:hover {
-          svg {
-            fill: var(--color-global);
-          }
-        }
-      }
-
-      .page-list-meta {
-        color: $color-page-list-group-item-meta;
-        svg {
-          fill: $color-page-list-group-item-meta;
-        }
-      }
-
-      &.list-group-item-action {
-        background-color: var(--bgcolor-list);
-        &.active {
-          border-left-color: var(--primary);
-        }
-      }
-    }
-  }
-}
-
-/*
- * GROWI Editor
- */
-.layout-root.editing {
-  background-color: hsl.darken(var(--bgcolor-global),2%);
-
-  &.builtin-editor {
-    .page-editor-editor-container {
-      border-right-color: var(--border-color-theme);
-    }
-  }
-
-  .navbar-editor {
-    background-color: var(--bgcolor-global); // same color with active tab
-    border-bottom-color: var(--border-color-theme);
-  }
-
-  .page-editor-preview-container {
-    background-color: var(--bgcolor-global);
-  }
-}
-
-
-/*
- * Preview for editing /Sidebar
- */
-.page-editor-preview-body.preview-sidebar {
-  color: var(--color-sidebar-context);
-  background-color: var(--bgcolor-sidebar-context);
-}
-
-/*
- * GROWI Grid Edit Modal
- */
-.grw-grid-edit-preview {
-  .desktop-preview,
-  .tablet-preview,
-  .mobile-preview {
-    background: var(--bgcolor-global);
-  }
-  .grid-edit-border-for-each-cols {
-    border: 2px solid var(--bgcolor-global);
-  }
-}
-
-.grid-preview-col-0 {
-  background: var.$growi-blue;
-}
-
-.grid-preview-col-1 {
-  background: var(--info);
-}
-
-.grid-preview-col-2 {
-  background: var(--success);
-}
-
-.grid-preview-col-3 {
-  background: var.$growi-green;
-}
-
-/*
- * GROWI comment
- */
-.page-comment-meta .page-comment-revision svg {
-  fill: var(--color-link);
-
-  &:hover {
-    fill: var(--color-link-hover);
-  }
-}
-
-/*
- * GROWI comment form
- */
-.page-comments-row {
-  background: var(--bgcolor-subnav);
-  .page-comment .page-comment-main,
-  .page-comment-form .comment-form-main {
-    background-color: var(--bgcolor-global);
-
-    .nav.nav-tabs {
-      > li > a.active {
-        background: transparent;
-        border-bottom: solid 1px hsl.darken(var(--bgcolor-global),4%);
-        border-bottom-color: hsl.darken(var(--bgcolor-global),4%);
-      }
-    }
-  }
-}
-
-/*
- * GROWI search result
- */
-.search-result-base {
-  .grw-search-page-nav {
-    background-color: var(--bgcolor-subnav);
-  }
-  .search-control {
-    background-color: var(--bgcolor-global);
-  }
-  .page-list {
-    .highlighted-keyword {
-      background: linear-gradient(transparent 60%, $bgcolor-keyword-highlighted 60%);
-    }
-  }
-}
-
-/*
- * react bootstrap typeahead
- */
-mark.rbt-highlight-text {
-  // Temporarily the highlight color is black
-  color: black;
-}
-
-/*
- * GROWI page content footer
- */
-.page-content-footer {
-  background-color: hsl.darken(var(--bgcolor-global),2%);
-  border-top-color: var(--border-color-theme);
-}
-
-/*
- * GROWI admin page #layoutOptions #themeOptions
- */
-.admin-page {
-  #layoutOptions {
-    .customize-layout-card {
-      &.border-active {
-        border-color: var(--color-theme-color-box);
-      }
-    }
-  }
-
-  #themeOptions {
-    .theme-option-container.active {
-      .theme-option-name {
-        color: var(--color-global);
-      }
-      a {
-        background-color: var(--color-theme-color-box);
-        border-color: var(--color-theme-color-box);
-      }
-    }
-  }
-}
-
-/*
- * HackMd
- */
-.bg-box {
-  background-color: var(--bgcolor-global);
-}
-
-/*
-  Slack Integration
-*/
-.selecting-bot-type {
-  .bot-type-disc {
-    width: 20px;
-  }
-}
-
-/*
-  In App Notification
-*/
-.grw-unopend-notification {
-  width: 7px;
-  height: 7px;
-  background-color: var(--primary);
-}
-
-/*
-Emoji picker modal
-*/
-.emoji-picker-modal {
-  background-color: transparent !important;
-}
-
-/*
-Expand / compress button bookmark list on users page
-*/
-.grw-user-page-list-m {
-  .grw-expand-compress-btn {
-    color: $body-color;
-    background-color: $body-bg;
-    &.active {
-      background-color: hsl.darken($body-bg, 12%),
-    }
-  }
-}
-
-/*
- * Questionnaire modal
- */
-.grw-questionnaire-btn-group {
-  .btn-outline-primary {
-    @include hsl-button.button-outline-variant(
-      #{hsl.lighten(var(--primary), 30%)} !important,
-      #{hsl.contrast(var(--primary))} !important,
-      var(--primary) !important,
-      #{hsl.lighten(var(--primary), 30%)} !important,
-    );
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: #{hsl.contrast(var(--primary))} !important;
-      background-color: var(--primary) !important;
-    }
-  }
-}
-
-/*
- * revision-history-diff
- */
-.revision-history-diff {
-  background-color: white;
-}

+ 2 - 2
apps/app/docker/README.md

@@ -10,8 +10,8 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`7.0.0`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.0/apps/app/docker/Dockerfile)
-* [`6.3.0`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.0/apps/app/docker/Dockerfile)
+* [`7.0.1`, `7.0`, `7`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v7.0.1/apps/app/docker/Dockerfile)
+* [`6.3.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
 * [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 

+ 0 - 6
apps/app/docker/codebuild/buildspec.yml

@@ -4,10 +4,8 @@ env:
   variables:
     DOCKER_BUILDKIT: 1
     IMAGE_TAG: ''
-    IMAGE_TAG_GHCR: ''
   secrets-manager:
     DOCKER_REGISTRY_PASSWORD: growi/official-image-builder:DOCKER_REGISTRY_PASSWORD
-    DOCKER_REGISTRY_ON_GITHUB_PAT: growi/official-image-builder:DOCKER_REGISTRY_ON_GITHUB_PAT
 
 phases:
   pre_build:
@@ -19,17 +17,13 @@ phases:
       - git-lfs pull
       # login to docker.io
       - echo ${DOCKER_REGISTRY_PASSWORD} | docker login --username wsmoogle --password-stdin
-      # login to ghcr.io
-      - echo ${DOCKER_REGISTRY_ON_GITHUB_PAT} | docker login ghcr.io --username wsmoogle --password-stdin
   build:
     commands:
       - docker build -t ${IMAGE_TAG} -f ./apps/app/docker/Dockerfile .
-      - docker tag ${IMAGE_TAG} ${IMAGE_TAG_GHCR}
 
   post_build:
     commands:
       - docker push $IMAGE_TAG
-      - docker push $IMAGE_TAG_GHCR
 
 cache:
   paths:

+ 0 - 1
apps/app/next-env.d.ts

@@ -1,6 +1,5 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
-/// <reference types="next/navigation-types/compat/navigation" />
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/basic-features/typescript for more information.

+ 2 - 2
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.2-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -243,7 +243,7 @@
     "babel-loader": "^8.2.5",
     "bootstrap": "^5.3.1",
     "connect-browser-sync": "^2.1.0",
-    "diff2html": "^3.4.35",
+    "diff2html": "^3.4.47",
     "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
     "eslint-plugin-cypress": "^2.12.1",

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

@@ -44,9 +44,11 @@
   "Error": "Error",
   "Warning": "Warning",
   "Sign in": "Sign in",
+  "Sign in with External auth": "Sign in with {{signin}}",
   "Sign up is here": "Sign up",
   "Sign in is here": "Sign in",
   "Sign up": "Sign up",
+  "or": "or",
   "Sign up with Google Account": "Sign up with Google Account",
   "Sign in with Google Account": "Sign in with Google Account",
   "Sign up with this Google Account": "Sign up with this Google Account",
@@ -108,6 +110,7 @@
   "Error occurred": "Error occurred",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
   "New Page": "New page",
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",

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

@@ -41,9 +41,11 @@
   "Error": "エラー",
   "Warning": "注意",
   "Sign in": "ログイン",
+  "Sign in with External auth": "{{signin}} でログイン",
   "Sign up is here": "新規登録はこちら",
   "Sign in is here": "ログインはこちら",
   "Sign up": "新規登録",
+  "or": "または",
   "Sign up with Google Account": "Google で登録",
   "Sign in with Google Account": "Google でログイン",
   "Sign up with this Google Account": "この Google アカウントで登録します",
@@ -107,6 +109,7 @@
   "Error occurred": "エラーが発生しました",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
+  "Input parent page path": "親ページのパスを入力",
   "New Page": "新規ページ",
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",

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

@@ -41,9 +41,11 @@
   "Error": "误差",
   "Warning": "警告",
   "Sign in": "登录",
+  "Sign in with External auth": "Sign in with {{signin}}",
   "Sign up is here": "注册",
   "Sign in is here": "登录",
   "Sign up": "注册",
+  "or": "或者",
   "Sign up with Google Account": "Sign up with Google Account",
   "Sign in with Google Account": "Sign in with Google Account",
   "Sign up with this Google Account": "Sign up with this Google Account",
@@ -113,6 +115,7 @@
   "Error occurred": "Error occurred",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
   "New Page": "新页面",
   "Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",

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

@@ -33,7 +33,7 @@ Let's increase the amount of information shared on a daily base!
 - GROWI also allows pages to be viewed by users outside the company who do not have an account
     - Let's share information with users outside your company using shared links!
 
-#### :bulb: Check [Sndbox](/Sandbox) to learn more on how to edit pages!
+#### :bulb: Check [Sandbox](/Sandbox) to learn more on how to edit pages!
 
 
 # :wrench: For Administrators - Once GROWI is created

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

@@ -33,7 +33,7 @@ GROWI は法人・個人向けの wiki | ナレッジベースツールです。
 - アカウントを保有していない社外のユーザーのページ閲覧を可能にすることも可能です
     - 「共有リンク」を活用し社外のユーザーに情報を共有しましょう
 
-#### :bulb:ページの編集方法が分からないときは [Snadbox](/Sandbox) を確認してみましょう!
+#### :bulb:ページの編集方法が分からないときは [Sandbox](/Sandbox) を確認してみましょう!
 
 
 # :wrench:管理者の方へ ~ GROWI を作成したら~

+ 2 - 2
apps/app/src/components/Admin/App/AppSetting.jsx

@@ -40,7 +40,7 @@ const AppSetting = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={adminAppContainer.state.title || ''}
+            value={adminAppContainer.state.title || ''}
             onChange={(e) => {
               adminAppContainer.changeTitle(e.target.value);
             }}
@@ -60,7 +60,7 @@ const AppSetting = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={adminAppContainer.state.confidential || ''}
+            value={adminAppContainer.state.confidential || ''}
             onChange={(e) => {
               adminAppContainer.changeConfidential(e.target.value);
             }}

+ 40 - 24
apps/app/src/components/Admin/SlackIntegration/BotTypeCard.jsx → apps/app/src/components/Admin/SlackIntegration/BotTypeCard.tsx

@@ -2,7 +2,7 @@ import React from 'react';
 
 import { SlackbotType } from '@growi/slack';
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
+import Image from 'next/image';
 
 const botDetails = {
   officialBot: {
@@ -30,25 +30,33 @@ const botDetails = {
   },
 };
 
-const BotTypeCard = (props) => {
+type BotTypeCardProps = {
+  isActive: boolean,
+  botType: string,
+  onBotTypeSelectHandler: (botType: SlackbotType) => void,
+};
+
+export const BotTypeCard = (props: BotTypeCardProps): JSX.Element => {
   const { t } = useTranslation();
 
-  const isBotTypeOfficial = props.botType === SlackbotType.OFFICIAL;
+  const { isActive, botType, onBotTypeSelectHandler } = props;
+
+  const isBotTypeOfficial = botType === SlackbotType.OFFICIAL;
 
   return (
     <div
-      className={`card admin-bot-card rounded border-radius-sm shadow ${props.isActive ? 'border-primary' : ''}`}
-      onClick={() => props.onBotTypeSelectHandler(botDetails[props.botType].botType)}
+      className={`card admin-bot-card rounded border-radius-sm shadow ${isActive ? 'border-primary' : ''}`}
+      onClick={() => onBotTypeSelectHandler(botDetails[botType].botType)}
       role="button"
-      key={props.botType}
+      key={botType}
     >
       <div>
         <h3 className={`card-header mb-0 py-3
               ${isBotTypeOfficial ? 'd-flex align-items-center justify-content-center' : 'text-center'}
-              ${props.isActive ? 'bg-primary grw-botcard-title-active' : ''}`}
+              ${isActive ? 'bg-primary grw-botcard-title-active' : ''}`}
         >
           <span className="me-2">
-            {t(`admin:slack_integration.selecting_bot_types.${botDetails[props.botType].botTypeCategory}`)}
+            {t(`admin:slack_integration.selecting_bot_types.${botDetails[botType].botTypeCategory}`)}
           </span>
 
           {/*  A recommended badge is shown on official bot card, supplementary names are shown on Custom bot cards   */}
@@ -59,40 +67,48 @@ const BotTypeCard = (props) => {
               </span>
             ) : (
               <span className="supplementary-bot-name me-2">
-                {t(`admin:slack_integration.selecting_bot_types.${botDetails[props.botType].supplementaryBotName}`)}
+                {t(`admin:slack_integration.selecting_bot_types.${botDetails[botType].supplementaryBotName}`)}
               </span>
             )}
 
-          <i className={props.isActive ? 'grw-botcard-title-active' : ''} aria-hidden="true"></i>
+          <i className={isActive ? 'grw-botcard-title-active' : ''} aria-hidden="true"></i>
         </h3>
       </div>
       <div className="card-body p-4">
         <div className="card-text">
           <div className="my-2">
-            <img
+            <Image
               className="bot-difficulty-icon d-block mx-auto mb-4"
-              src={`/images/slack-integration/slackbot-difficulty-level-${botDetails[props.botType].setUp}.svg`}
+              src={`/images/slack-integration/slackbot-difficulty-level-${botDetails[botType].setUp}.svg`}
+              alt=""
+              width={60}
+              height={60}
             />
-            <div className="d-flex justify-content-between mb-3">
+            <div className="d-flex justify-content-between mb-3 align-items-center">
               <span>{t('admin:slack_integration.selecting_bot_types.multiple_workspaces_integration')}</span>
-              <img className="bot-type-disc" src={`/images/slack-integration/${botDetails[props.botType].multiWSIntegration}.png`} alt="" />
+              <Image
+                className="bot-type-disc"
+                src={`/images/slack-integration/${botDetails[botType].multiWSIntegration}.png`}
+                alt=""
+                width={20}
+                height={20}
+              />
             </div>
-            <div className="d-flex justify-content-between">
+            <div className="d-flex justify-content-between align-items-center">
               <span>{t('admin:slack_integration.selecting_bot_types.security_control')}</span>
-              <img className="bot-type-disc" src={`/images/slack-integration/${botDetails[props.botType].securityControl}.png`} alt="" />
+              <Image
+                className="bot-type-disc"
+                src={`/images/slack-integration/${botDetails[botType].securityControl}.png`}
+                alt=""
+                width={20}
+                height={20}
+              />
             </div>
           </div>
         </div>
       </div>
     </div>
   );
-
-};
-
-BotTypeCard.propTypes = {
-  isActive: PropTypes.bool.isRequired,
-  botType: PropTypes.string.isRequired,
-  onBotTypeSelectHandler: PropTypes.func.isRequired,
 };
 
-export default BotTypeCard;
+BotTypeCard.displayName = 'BotTypeCard';

+ 19 - 23
apps/app/src/components/Admin/SlackIntegration/Bridge.jsx → apps/app/src/components/Admin/SlackIntegration/Bridge.tsx

@@ -1,19 +1,24 @@
-import React from 'react';
 
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
 import { UncontrolledTooltip } from 'reactstrap';
 
 const ProxyCircle = () => (
-  <div className="grw-bridge-proxy-circle">
-    <div className="circle position-absolute bg-primary border-light rounded-circle">
-      <p className="circle-inner text-light fw-bold d-none d-lg-inline">Proxy Server</p>
-      <p className="circle-inner grw-proxy-server-name d-block d-lg-none">Proxy Server</p>
+  <div className="grw-bridge-proxy-circle position-relative">
+    <div className="circle position-absolute m-auto z-1 bg-primary border-light rounded-circle">
+      <p className="circle-inner position-absolute text-light fw-bold d-none d-lg-inline">Proxy Server</p>
+      <p className="circle-inner position-absolute grw-proxy-server-name d-inline d-lg-none">Proxy Server</p>
     </div>
   </div>
 );
 
-const BridgeCore = (props) => {
+type BridgeCoreProps = {
+  description: string,
+  iconClass: string,
+  iconName: string,
+  hrClass: string,
+  withProxy?: boolean,
+}
+const BridgeCore = (props: BridgeCoreProps): JSX.Element => {
   const {
     description, iconClass, iconName, hrClass, withProxy,
   } = props;
@@ -44,16 +49,13 @@ const BridgeCore = (props) => {
   );
 };
 
-BridgeCore.propTypes = {
-  description: PropTypes.string.isRequired,
-  iconClass: PropTypes.string.isRequired,
-  iconName: PropTypes.string.isRequired,
-  hrClass: PropTypes.string.isRequired,
-  withProxy: PropTypes.bool,
-};
-
 
-const Bridge = (props) => {
+type BridgeProps = {
+  errorCount: number,
+  totalCount: number,
+  withProxy?: boolean,
+}
+export const Bridge = (props: BridgeProps): JSX.Element => {
   const { t } = useTranslation();
   const { errorCount, totalCount, withProxy } = props;
 
@@ -95,10 +97,4 @@ const Bridge = (props) => {
   );
 };
 
-Bridge.propTypes = {
-  errorCount: PropTypes.number.isRequired,
-  totalCount: PropTypes.number.isRequired,
-  withProxy: PropTypes.bool,
-};
-
-export default Bridge;
+Bridge.displayName = 'Bridge';

+ 0 - 57
apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.jsx

@@ -1,57 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import Bridge from './Bridge';
-
-const CustomBotWithProxyConnectionStatus = (props) => {
-  const { siteName, connectionStatuses } = props;
-
-  const connectionStatusValues = Object.values(connectionStatuses); // type: ConnectionStatus[]
-
-  const totalCount = connectionStatusValues.length;
-  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatusValues.error != null).length;
-
-  return (
-    <div className="d-flex justify-content-center my-5 bot-integration">
-
-      <div className="card rounded shadow border-0 w-50 admin-bot-card">
-        <h5 className="card-title fw-bold mt-3 ms-3">Slack</h5>
-        <div className="card-body px-5">
-          {connectionStatusValues.map((connectionStatus, i) => {
-            const workspaceName = connectionStatus.workspaceName || `Settings #${i}`;
-
-            return (
-              <div key={workspaceName} className="card slack-work-space-name-card">
-                <div className="m-2 text-center">
-                  <h5 className="fw-bold">{workspaceName}</h5>
-                  <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
-                </div>
-              </div>
-            );
-          })}
-        </div>
-      </div>
-
-      <div className="text-center w-25 mt-3">
-        <Bridge errorCount={errorCount} totalCount={totalCount} withProxy />
-      </div>
-
-      <div className="card rounded-3 shadow border-0 w-50 admin-bot-card">
-        <h5 className="card-title fw-bold mt-3 ms-3">GROWI App</h5>
-        <div className="card-body text-center">
-          <div className="mx-md-3 my-4 my-lg-5 p-2 border bg-primary text-light">
-            {siteName}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-CustomBotWithProxyConnectionStatus.propTypes = {
-  siteName: PropTypes.string.isRequired,
-  connectionStatuses: PropTypes.object.isRequired,
-};
-
-export default CustomBotWithProxyConnectionStatus;

+ 59 - 0
apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx

@@ -0,0 +1,59 @@
+import React from 'react';
+
+import type { ConnectionStatus } from '@growi/slack';
+import Image from 'next/image';
+
+import { Bridge } from './Bridge';
+
+
+type CustomBotWithProxyConnectionStatusProps = {
+  siteName: string,
+  connectionStatuses: any,
+}
+
+export const CustomBotWithProxyConnectionStatus = (props: CustomBotWithProxyConnectionStatusProps): JSX.Element => {
+  const { siteName, connectionStatuses } = props;
+
+  const connectionStatusValues: ConnectionStatus[] = Object.values(connectionStatuses);
+
+  const totalCount = connectionStatusValues.length;
+  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatus.error != null).length;
+
+  return (
+    <div className="row justify-content-center my-5 bot-integration">
+
+      <div className="card rounded shadow col-4 border-0 admin-bot-card">
+        <h5 className="card-title fw-bold mt-3 text-center">Slack</h5>
+        <div className="card-body px-5">
+          {connectionStatusValues.map((connectionStatus, i) => {
+            const workspaceName = connectionStatus.workspaceName || `Settings #${i}`;
+
+            return (
+              <div key={workspaceName} className="card slack-work-space-name-card">
+                <div className="m-2 text-center">
+                  <h5 className="fw-bold">{workspaceName}</h5>
+                  <Image width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" alt="" />
+                </div>
+              </div>
+            );
+          })}
+        </div>
+      </div>
+
+      <div className="col-3 mt-3 text-center">
+        <Bridge errorCount={errorCount} totalCount={totalCount} withProxy />
+      </div>
+
+      <div className="card rounded-3 shadow col-4 border-0 admin-bot-card">
+        <h5 className="card-title fw-bold mt-3 text-center">GROWI App</h5>
+        <div className="card-body text-center">
+          <div className="mx-md-3 my-4 my-lg-5 p-2 border bg-primary text-light">
+            {siteName}
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+CustomBotWithProxyConnectionStatus.displayName = 'CustomBotWithProxyConnectionStatus';

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

@@ -9,8 +9,8 @@ import { useAppTitle } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
 
-import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
-import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
+import { CustomBotWithProxyConnectionStatus } from './CustomBotWithProxyConnectionStatus';
+import { DeleteSlackBotSettingsModal } from './DeleteSlackBotSettingsModal';
 import { SlackAppIntegrationControl } from './SlackAppIntegrationControl';
 import WithProxyAccordions from './WithProxyAccordions';
 
@@ -93,7 +93,7 @@ const CustomBotWithProxySettings = (props) => {
     <>
       <h2 className="admin-setting-header mb-2">{t('admin:slack_integration.custom_bot_with_proxy_integration')}
         <a href={t('admin:slack_integration.docs_url.custom_bot_with_proxy')} target="_blank" rel="noopener noreferrer">
-          <span className="growi-custom-icons btn-link ms-2 fs-3">external_link</span>
+          <span className="growi-custom-icons btn-link ms-2">external_link</span>
         </a>
       </h2>
 

+ 0 - 57
apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.jsx

@@ -1,57 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import Bridge from './Bridge';
-
-const CustomBotWithoutProxyConnectionStatus = (props) => {
-  const { siteName, connectionStatuses } = props;
-
-  const connectionStatusValues = Object.values(connectionStatuses); // type: ConnectionStatus[]
-
-  const totalCount = connectionStatusValues.length;
-  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatus.error != null).length;
-
-  let workspaceName;
-  if (totalCount > 0) {
-    workspaceName = connectionStatusValues[0].workspaceName;
-  }
-
-  return (
-    <div className="d-flex justify-content-center my-5 bot-integration">
-      <div className="card rounded shadow border-0 w-50 admin-bot-card mb-0">
-        <h5 className="card-title fw-bold mt-3 ms-4">Slack</h5>
-        <div className="card-body px-4 text-center mx-md-5">
-          {totalCount > 0 ? (
-            <div className="card slack-work-space-name-card">
-              <div className="m-2 text-center">
-                <h5 className="fw-bold">
-                  {workspaceName != null ? workspaceName : 'Settings #1'}
-                </h5>
-                <img width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" />
-              </div>
-            </div>
-          ) : ''}
-        </div>
-      </div>
-
-      <div className="text-center w-25">
-        <Bridge errorCount={errorCount} totalCount={totalCount} />
-      </div>
-
-      <div className="card rounded-3 shadow border-0 w-50 admin-bot-card mb-0">
-        <h5 className="card-title fw-bold mt-3 ms-4">GROWI App</h5>
-        <div className="card-body p-4 text-center">
-          <div className="border p-2 bg-primary text-light mx-md-5">
-            {siteName}
-          </div>
-        </div>
-      </div>
-    </div>
-  );
-};
-
-CustomBotWithoutProxyConnectionStatus.propTypes = {
-  siteName: PropTypes.string.isRequired,
-  connectionStatuses: PropTypes.object.isRequired,
-};
-
-export default CustomBotWithoutProxyConnectionStatus;

+ 57 - 0
apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx

@@ -0,0 +1,57 @@
+import React from 'react';
+
+import type { ConnectionStatus } from '@growi/slack';
+import Image from 'next/image';
+
+import { Bridge } from './Bridge';
+
+type CustomBotWithoutProxyConnectionStatusProps = {
+  siteName: string,
+  connectionStatuses: any,
+}
+
+export const CustomBotWithoutProxyConnectionStatus = (props: CustomBotWithoutProxyConnectionStatusProps): JSX.Element => {
+  const { siteName, connectionStatuses } = props;
+
+  const connectionStatusValues: ConnectionStatus[] = Object.values(connectionStatuses);
+
+  const totalCount = connectionStatusValues.length;
+  const errorCount = connectionStatusValues.filter(connectionStatus => connectionStatus.error != null).length;
+  const workspaceName = connectionStatusValues[0]?.workspaceName;
+
+  return (
+    <div className="row justify-content-center my-5 bot-integration">
+      <div className="card rounded shadow col-4 border-0 admin-bot-card mb-0">
+        <h5 className="card-title fw-bold mt-3 text-center">Slack</h5>
+        <div className="card-body px-4 text-center mx-md-5">
+          {totalCount > 0 ? (
+            <div className="card slack-work-space-name-card">
+              <div className="m-2 text-center">
+                <h5 className="fw-bold">
+                  {workspaceName != null ? workspaceName : 'Settings #1'}
+                </h5>
+                <Image width={20} height={20} src="/images/slack-integration/growi-bot-kun-icon.png" alt="" />
+              </div>
+            </div>
+          ) : ''}
+        </div>
+      </div>
+
+      <div className="col-3 text-center">
+        <Bridge errorCount={errorCount} totalCount={totalCount} />
+      </div>
+
+      <div className="card rounded-3 shadow col-4 border-0 admin-bot-card mb-0">
+        <h5 className="card-title fw-bold mt-3 text-center">GROWI App</h5>
+        <div className="card-body p-4 text-center">
+          <div className="border p-2 bg-primary text-light mx-md-5">
+            {siteName}
+          </div>
+        </div>
+      </div>
+
+    </div>
+  );
+};
+
+CustomBotWithoutProxyConnectionStatus.displayName = 'CustomBotWithoutProxyConnectionStatus';

+ 2 - 2
apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
 
 import { useAppTitle } from '~/stores/context';
 
-import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
+import { CustomBotWithoutProxyConnectionStatus } from './CustomBotWithoutProxyConnectionStatus';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 
 const CustomBotWithoutProxySettings = (props) => {
@@ -24,7 +24,7 @@ const CustomBotWithoutProxySettings = (props) => {
     <>
       <h2 className="admin-setting-header">{t('admin:slack_integration.custom_bot_without_proxy_integration')}
         <a href={t('admin:slack_integration.docs_url.custom_bot_without_proxy')} target="_blank" rel="noopener noreferrer">
-          <span className="growi-custom-icons btn-link ms-2 fs-3">external_link</span>
+          <span className="growi-custom-icons btn-link ms-2">external_link</span>
         </a>
       </h2>
 

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

@@ -10,7 +10,7 @@ import Accordion from '../Common/Accordion';
 import CustomBotWithoutProxySecretTokenSection from './CustomBotWithoutProxySecretTokenSection';
 import ManageCommandsProcessWithoutProxy from './ManageCommandsProcessWithoutProxy';
 import MessageBasedOnConnection from './MessageBasedOnConnection';
-import { addLogs } from './slak-integration-util';
+import { addLogs } from './slack-integration-util';
 
 
 export const botInstallationStep = {
@@ -78,7 +78,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
         <div className="my-5 d-flex flex-column align-items-center">
           <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
             {t('admin:slack_integration.accordion.create_bot')}
-            <span className="growi-custom-icons ms-2"><small>external_link</small></span>
+            <span className="growi-custom-icons ms-2">external_link</span>
           </button>
           <a
             href={t('admin:slack_integration.docs_url.custom_bot_without_proxy_setting')}
@@ -88,7 +88,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
             <p className="text-center mt-1">
               <small>
                 {t('admin:slack_integration.accordion.how_to_create_a_bot')}
-                <span className="growi-custom-icons ms-2"><small><small>external_link</small></small></span>
+                <span className="growi-custom-icons ms-2">external_link</span>
               </small>
             </p>
           </a>

+ 21 - 19
apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.jsx → apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.tsx

@@ -1,13 +1,24 @@
 import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
 import {
   Button, Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-const DeleteSlackBotSettingsModal = React.memo((props) => {
-  const { t, onClickDeleteButton, onClose } = useTranslation();
+type DeleteSlackBotSettingsModalProps = {
+  isResetAll: boolean,
+  isOpen: boolean,
+  onClose?: () => void,
+  onClickDeleteButton?: () => void,
+}
+
+export const DeleteSlackBotSettingsModal = React.memo((props: DeleteSlackBotSettingsModalProps) => {
+
+  const { t } = useTranslation();
+
+  const {
+    isResetAll, isOpen, onClose, onClickDeleteButton,
+  } = props;
 
   const deleteSlackCredentialsHandler = useCallback(() => {
     onClickDeleteButton?.();
@@ -19,16 +30,16 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
   }, [onClose]);
 
   return (
-    <Modal isOpen={props.isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
+    <Modal isOpen={isOpen} toggle={closeButtonHandler} className="page-comment-delete-modal">
       <ModalHeader tag="h4" toggle={closeButtonHandler} className="bg-danger text-light">
         <span>
-          {props.isResetAll && (
+          {isResetAll && (
             <>
               <span className="material-symbols-outlined">delete_forever</span>
               {t('admin:slack_integration.reset_all_settings')}
             </>
           )}
-          {!props.isResetAll && (
+          {!isResetAll && (
             <>
               <span className="material-symbols-outlined">delete</span>
               {t('admin:slack_integration.delete_slackbot_settings')}
@@ -37,13 +48,13 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
         </span>
       </ModalHeader>
       <ModalBody>
-        {props.isResetAll && (
+        {isResetAll && (
           <span
             // eslint-disable-next-line react/no-danger
             dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.all_settings_of_the_bot_will_be_reset') }}
           />
         )}
-        {!props.isResetAll && (
+        {!isResetAll && (
           <span
             // eslint-disable-next-line react/no-danger
             dangerouslySetInnerHTML={{ __html: t('admin:slack_integration.slackbot_settings_notice') }}
@@ -53,13 +64,13 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
       <ModalFooter>
         <Button onClick={closeButtonHandler}>{t('Cancel')}</Button>
         <Button color="danger" onClick={deleteSlackCredentialsHandler}>
-          {props.isResetAll && (
+          {isResetAll && (
             <>
               <span className="material-symbols-outlined">delete_forever</span>
               {t('admin:slack_integration.reset')}
             </>
           )}
-          {!props.isResetAll && (
+          {!isResetAll && (
             <>
               <span className="material-symbols-outlined">delete</span>
               {t('admin:slack_integration.delete')}
@@ -72,13 +83,4 @@ const DeleteSlackBotSettingsModal = React.memo((props) => {
 
 });
 
-DeleteSlackBotSettingsModal.propTypes = {
-  isResetAll: PropTypes.bool.isRequired,
-  isOpen: PropTypes.bool.isRequired,
-  onClose: PropTypes.func,
-  onClickDeleteButton: PropTypes.func,
-};
-
 DeleteSlackBotSettingsModal.displayName = 'DeleteSlackBotSettingsModal';
-
-export default DeleteSlackBotSettingsModal;

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

@@ -11,8 +11,8 @@ import { useAppTitle } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
 
-import CustomBotWithProxyConnectionStatus from './CustomBotWithProxyConnectionStatus';
-import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
+import { CustomBotWithProxyConnectionStatus } from './CustomBotWithProxyConnectionStatus';
+import { DeleteSlackBotSettingsModal } from './DeleteSlackBotSettingsModal';
 import { SlackAppIntegrationControl } from './SlackAppIntegrationControl';
 import WithProxyAccordions from './WithProxyAccordions';
 
@@ -78,7 +78,7 @@ const OfficialBotSettings = (props) => {
       <h2 className="admin-setting-header">{t('admin:slack_integration.official_bot_integration')}
         <a href={t('admin:slack_integration.docs_url.official_bot')} target="_blank" rel="noopener noreferrer">
           <span
-            className="growi-custom-icons btn-link ms-2 fs-3"
+            className="growi-custom-icons btn-link ms-2"
             onClick={() => window.open(`${t('admin:slack_integration.docs_url.official_bot')}`, '_blank')}
           >
             external_link

+ 1 - 2
apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx

@@ -1,4 +1,3 @@
-import React, { FC } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
@@ -11,7 +10,7 @@ type Props = {
   onDeleteButtonClicked?: (slackAppIntegration: unknown) => void,
 }
 
-export const SlackAppIntegrationControl: FC<Props> = (props: Props) => {
+export const SlackAppIntegrationControl = (props: Props): JSX.Element => {
   const { t } = useTranslation();
 
   const { slackAppIntegration, onIsPrimaryChanged, onDeleteButtonClicked } = props;

+ 12 - 12
apps/app/src/components/Admin/SlackIntegration/SlackIntegration.jsx → apps/app/src/components/Admin/SlackIntegration/SlackIntegration.tsx

@@ -10,21 +10,21 @@ import {
 } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 
-import BotTypeCard from './BotTypeCard';
+import { BotTypeCard } from './BotTypeCard';
 import ConfirmBotChangeModal from './ConfirmBotChangeModal';
 import CustomBotWithProxySettings from './CustomBotWithProxySettings';
 import CustomBotWithoutProxySettings from './CustomBotWithoutProxySettings';
-import DeleteSlackBotSettingsModal from './DeleteSlackBotSettingsModal';
+import { DeleteSlackBotSettingsModal } from './DeleteSlackBotSettingsModal';
 import OfficialBotSettings from './OfficialBotSettings';
 
 
 const botTypes = Object.values(SlackbotType);
 
-const SlackIntegration = () => {
+export const SlackIntegration = (): JSX.Element => {
 
   const { t } = useTranslation();
-  const [currentBotType, setCurrentBotType] = useState(null);
-  const [selectedBotType, setSelectedBotType] = useState(null);
+  const [currentBotType, setCurrentBotType] = useState<SlackbotType | undefined>();
+  const [selectedBotType, setSelectedBotType] = useState<SlackbotType | undefined>();
   const [slackSigningSecret, setSlackSigningSecret] = useState(null);
   const [slackBotToken, setSlackBotToken] = useState(null);
   const [slackSigningSecretEnv, setSlackSigningSecretEnv] = useState('');
@@ -106,12 +106,12 @@ const SlackIntegration = () => {
     fetchSlackIntegrationData();
   }, [fetchSlackIntegrationData]);
 
-  const changeCurrentBotSettings = async(botType) => {
+  const changeCurrentBotSettings = async(botType?: SlackbotType) => {
     try {
       await apiv3Put('/slack-integration-settings/bot-type', {
         currentBotType: botType,
       });
-      setSelectedBotType(null);
+      setSelectedBotType(undefined);
       fetchSlackIntegrationData();
     }
     catch (err) {
@@ -119,7 +119,7 @@ const SlackIntegration = () => {
     }
   };
 
-  const botTypeSelectHandler = async(botType) => {
+  const botTypeSelectHandler = async(botType: SlackbotType) => {
     if (botType === currentBotType) {
       return;
     }
@@ -135,10 +135,10 @@ const SlackIntegration = () => {
   };
 
   const cancelBotChangeHandler = () => {
-    setSelectedBotType(null);
+    setSelectedBotType(undefined);
   };
 
-  let settingsComponent = null;
+  let settingsComponent = <></>;
 
   switch (currentBotType) {
     case SlackbotType.OFFICIAL:
@@ -231,7 +231,7 @@ const SlackIntegration = () => {
           </button>
         </div>
 
-        <div className="row my-5 flex-wrap-reverse justify-content-center">
+        <div className="my-5 d-flex flex-wrap-reverse justify-content-center">
           {botTypes.map((botType) => {
             return (
               <div key={botType} className="m-3">
@@ -251,4 +251,4 @@ const SlackIntegration = () => {
   );
 };
 
-export default SlackIntegration;
+SlackIntegration.displayName = 'SlackIntegration';

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

@@ -15,7 +15,7 @@ import Accordion from '../Common/Accordion';
 
 import ManageCommandsProcess from './ManageCommandsProcess';
 import MessageBasedOnConnection from './MessageBasedOnConnection';
-import { addLogs } from './slak-integration-util';
+import { addLogs } from './slack-integration-util';
 
 const logger = loggerFactory('growi:SlackIntegration:WithProxyAccordionsWrapper');
 
@@ -25,7 +25,7 @@ const BotCreateProcess = () => {
     <div className="my-5 d-flex flex-column align-items-center">
       <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://api.slack.com/apps', '_blank')}>
         {t('admin:slack_integration.accordion.create_bot')}
-        <span className="growi-custom-icons ms-2"><small>external_link</small></span>
+        <span className="growi-custom-icons ms-2">external_link</span>
       </button>
       <a
         href={t('admin:slack_integration.docs_url.custom_bot_with_proxy_setting')}
@@ -35,7 +35,7 @@ const BotCreateProcess = () => {
         <p className="text-center mt-1">
           <small>
             {t('admin:slack_integration.accordion.how_to_create_a_bot')}
-            <span className="growi-custom-icons ms-2"><small><small>external_link</small></small></span>
+            <span className="growi-custom-icons ms-2">external_link</span>
           </small>
         </p>
       </a>
@@ -49,7 +49,7 @@ const BotInstallProcessForOfficialBot = () => {
     <div className="my-5 d-flex flex-column align-items-center">
       <button type="button" className="btn btn-primary text-nowrap" onClick={() => window.open('https://slackbot-proxy.growi.org/', '_blank')}>
         {t('admin:slack_integration.accordion.install_now')}
-        <span className="growi-custom-icons ms-2"><small>external_link</small></span>
+        <span className="growi-custom-icons ms-2">external_link</span>
       </button>
       <a
         href={t('admin:slack_integration.docs_url.official_bot_setting')}
@@ -59,7 +59,7 @@ const BotInstallProcessForOfficialBot = () => {
         <p className="text-center mt-1">
           <small>
             {t('admin:slack_integration.accordion.how_to_install')}
-            <span className="growi-custom-icons ms-2"><small><small>external_link</small></small></span>
+            <span className="growi-custom-icons ms-2">external_link</span>
           </small>
         </p>
       </a>

+ 4 - 0
apps/app/src/components/Admin/SlackIntegration/slack-integration-util.ts

@@ -0,0 +1,4 @@
+export const addLogs = (log: string, newLogMessage:string, newLogCode?: string): string => {
+  const newLog = `${new Date()} - ${newLogCode ? `${newLogCode}, ` : ''}${newLogMessage}\n\n`;
+  return `${newLog}${log ?? ''}`;
+};

+ 0 - 20
apps/app/src/components/Admin/SlackIntegration/slak-integration-util.js

@@ -1,20 +0,0 @@
-const addLogs = (log, newLogMessage, newLogCode = undefined) => {
-
-  let newLog;
-  if (newLogCode == null) {
-    newLog = `${new Date()} - ${newLogMessage}\n\n`;
-  }
-  else {
-    newLog = `${new Date()} - ${newLogCode}, ${newLogMessage}\n\n`;
-  }
-
-  if (log == null) {
-    return newLog;
-  }
-  return `${newLog}${log}`;
-};
-
-export {
-  // eslint-disable-next-line import/prefer-default-export
-  addLogs,
-};

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

@@ -162,7 +162,14 @@ export const UserGroupTable: FC<Props> = ({
                 {isExternalGroup && <td>{(group as IExternalUserGroupHasId).provider}</td>}
                 {isAclEnabled
                   ? (
-                    <td><Link href={`/admin/user-group-detail/${group._id}?isExternalGroup=${isExternalGroup}`}>{group.name}</Link></td>
+                    <td>
+                      <Link
+                        className="link-opacity-75-hover"
+                        href={`/admin/user-group-detail/${group._id}?isExternalGroup=${isExternalGroup}`}
+                      >
+                        {group.name}
+                      </Link>
+                    </td>
                   )
                   : (
                     <td>{group.name}</td>

+ 4 - 4
apps/app/src/components/Admin/UserManagement.tsx

@@ -90,7 +90,7 @@ const UserManagement = (props: UserManagementProps) => {
           onChange={() => clickHandler(status)}
         />
         <label className="form-label form-check-label" htmlFor={`c_${status}`}>
-          <span className={`badge rounded-pill bg-${statusColor} d-inline-block vt mt-1`}>
+          <span className={`badge text-bg-${statusColor} d-inline-block vt mt-1`}>
             {statusLabel}
           </span>
         </label>
@@ -167,11 +167,11 @@ const UserManagement = (props: UserManagementProps) => {
 
           <div className="offset-md-1 col-md-6 my-2">
             <div>
-              {renderCheckbox('all', 'All', 'secondary')}
+              {renderCheckbox('all', 'All', 'primary')}
               {renderCheckbox('registered', 'Approval Pending', 'info')}
               {renderCheckbox('active', 'Active', 'success')}
-              {renderCheckbox('suspended', 'Suspended', 'warning text-dark')}
-              {renderCheckbox('invited', 'Invited', 'pink')}
+              {renderCheckbox('suspended', 'Suspended', 'warning')}
+              {renderCheckbox('invited', 'Invited', 'secondary')}
             </div>
             <div>
               { isNotifyCommentShow && <span className="text-warning">{t('admin:user_management.click_twice_same_checkbox')}</span> }

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

@@ -22,34 +22,34 @@ const UserTable = (props: UserTableProps) => {
   const { adminUsersContainer } = props;
 
   const getUserStatusLabel = (userStatus: number) => {
-    let additionalClassName = 'bg-info';
+    let additionalClassName = 'text-bg-info';
     let text = 'Approval Pending';
 
     switch (userStatus) {
       case 1:
-        additionalClassName = 'bg-info';
+        additionalClassName = 'text-bg-info';
         text = 'Approval Pending';
         break;
       case 2:
-        additionalClassName = 'bg-success';
+        additionalClassName = 'text-bg-success';
         text = 'Active';
         break;
       case 3:
-        additionalClassName = 'bg-warning text-dark';
+        additionalClassName = 'text-bg-warning';
         text = 'Suspended';
         break;
       case 4:
-        additionalClassName = 'bg-danger';
+        additionalClassName = 'text-bg-danger';
         text = 'Deleted';
         break;
       case 5:
-        additionalClassName = 'bg-pink';
+        additionalClassName = 'text-bg-secondary';
         text = 'Invited';
         break;
     }
 
     return (
-      <span className={`badge rounded-pill ${additionalClassName}`}>
+      <span className={`badge ${additionalClassName}`}>
         {text}
       </span>
     );
@@ -153,12 +153,12 @@ const UserTable = (props: UserTableProps) => {
                 <td>
                   {getUserStatusLabel(user.status)}
                   {(user.admin) && (
-                    <span className="badge bg-indigo rounded-pill ms-2">
+                    <span className="badge text-bg-secondary ms-2">
                       {t('admin:user_management.user_table.administrator')}
                     </span>
                   )}
                   {(user.readOnly) && (
-                    <span className="badge bg-light text-dark rounded-pill ms-2">
+                    <span className="badge text-bg-light ms-2">
                       {t('admin:user_management.user_table.read_only')}
                     </span>
                   )}

+ 10 - 0
apps/app/src/components/Bookmarks/BookmarkFolderMenu.module.scss

@@ -0,0 +1,10 @@
+.grw-bookmark-folder-menu  :global {
+  max-width: 65%;
+
+  .grw-bookmark-folder-menu-item-folder-first {
+    padding-left: 40px;
+  }
+  .grw-bookmark-folder-menu-item-folder-second {
+    padding-left: 60px;
+  }
+}

+ 7 - 9
apps/app/src/components/Bookmarks/BookmarkFolderMenu.tsx

@@ -12,6 +12,7 @@ import { useSWRMUTxPageInfo } from '~/stores/page';
 
 import { BookmarkFolderMenuItem } from './BookmarkFolderMenuItem';
 
+import styles from './BookmarkFolderMenu.module.scss';
 
 type BookmarkFolderMenuProps = {
   isOpen: boolean,
@@ -116,7 +117,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
         <DropdownItem
           toggle={false}
           onClick={onUnbookmarkHandler}
-          className="grw-bookmark-folder-menu-item text-danger"
+          className="grw-bookmark-folder-menu-item text-danger text-truncate"
         >
           <span className="material-symbols-outlined">bookmark</span>{' '}
           <span className="mx-2">
@@ -129,7 +130,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
             <DropdownItem divider />
             <div key="root">
               <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 px-4"
                 tabIndex={0}
                 role="menuitem"
                 onClick={e => onMenuItemClickHandler(e, 'root')}
@@ -144,8 +145,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
             {bookmarkFolders?.map(folder => (
               <React.Fragment key={`bookmark-folders-${folder._id}`}>
                 <div
-                  className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
-                  style={{ paddingLeft: '40px' }}
+                  className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-first list-group-item list-group-item-action"
                   tabIndex={0}
                   role="menuitem"
                   onClick={e => onMenuItemClickHandler(e, folder._id)}
@@ -159,8 +159,7 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
                 {folder.children?.map(child => (
                   <div key={child._id}>
                     <div
-                      className="dropdown-item grw-bookmark-folder-menu-item list-group-item list-group-item-action border-0 py-0"
-                      style={{ paddingLeft: '60px' }}
+                      className="dropdown-item grw-bookmark-folder-menu-item grw-bookmark-folder-menu-item-folder-second list-group-item list-group-item-action"
                       tabIndex={0}
                       role="menuitem"
                       onClick={e => onMenuItemClickHandler(e, child._id)}
@@ -185,15 +184,14 @@ export const BookmarkFolderMenu = (props: BookmarkFolderMenuProps): JSX.Element
     <UncontrolledDropdown
       isOpen={isOpen}
       onToggle={toggleHandler}
-      direction={isBookmarkFolderExists ? 'up' : 'down'}
-      className="grw-bookmark-folder-dropdown"
     >
       {children}
       <DropdownMenu
         end
         persist
         strategy="fixed"
-        className="grw-bookmark-folder-menu"
+        container="body"
+        className={`grw-bookmark-folder-menu ${styles['grw-bookmark-folder-menu']}`}
       >
         { renderBookmarkMenuItem() }
       </DropdownMenu>

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

@@ -19,7 +19,7 @@ export const BookmarkFolderMenuItem: React.FC<{
         onChange={e => e.stopPropagation()}
         onClick={e => e.stopPropagation()}
       />
-      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className="p-2 m-0 form-label">
+      <label htmlFor={`bookmark-folder-menu-item-${itemId}`} className="p-2 m-0 form-label text-truncate">
         {itemName}
       </label>
     </div>

+ 6 - 2
apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx

@@ -13,6 +13,7 @@ type PagePathHierarchicalLinkProps = {
   linkedPagePathByHtml?: LinkedPagePath,
   basePath?: string,
   isInTrash?: boolean,
+  isIconHidden?: boolean,
 
   // !!INTERNAL USE ONLY!!
   isInnerElem?: boolean,
@@ -23,16 +24,18 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
     linkedPagePath, linkedPagePathByHtml, basePath, isInTrash, isInnerElem,
   } = props;
 
+  const isIconHidden = props.isIconHidden ?? false;
+
   // eslint-disable-next-line react/prop-types
   const RootElm = useCallback(({ children }) => {
     return isInnerElem
       ? <>{children}</>
-      : <span className="text-break">{children}</span>;
+      : <span className="text-break" id="grw-page-path-hierarchical-link">{children}</span>;
   }, [isInnerElem]);
 
   // render root element
   if (linkedPagePath.isRoot) {
-    if (basePath != null) {
+    if (basePath != null || isIconHidden) {
       return <></>;
     }
 
@@ -76,6 +79,7 @@ export const PagePathHierarchicalLink = memo((props: PagePathHierarchicalLinkPro
           basePath={basePath}
           isInTrash={isInTrash || linkedPagePath.isInTrash}
           isInnerElem
+          isIconHidden={isIconHidden}
         />
       ) }
       { isSeparatorRequired && (

+ 17 - 0
apps/app/src/components/Common/PagePathNav/PagePathNav.module.scss

@@ -36,3 +36,20 @@
     @include btn-muted.colorize(bs.$orange);
   }
 }
+
+// == Colors
+.grw-former-link :global {
+  .separator {
+    opacity: 0.75;
+  }
+}
+
+.grw-former-link a {
+  --bs-link-opacity: 0.75;
+
+  &:global {
+    &:hover {
+      --bs-link-opacity: 1;
+    }
+  }
+}

+ 5 - 5
apps/app/src/components/Common/PagePathNav/PagePathNav.tsx

@@ -29,8 +29,8 @@ type Props = {
 
 const CopyDropdown = dynamic(() => import('../CopyDropdown').then(mod => mod.CopyDropdown), { ssr: false });
 
-const Separator = (): JSX.Element => {
-  return <span className={styles['grw-mx-02em']}>/</span>;
+const Separator = ({ className }: {className?: string}): JSX.Element => {
+  return <span className={`separator ${className ?? ''} ${styles['grw-mx-02em']}`}>/</span>;
 };
 
 export const PagePathNav: FC<Props> = (props: Props) => {
@@ -69,10 +69,10 @@ export const PagePathNav: FC<Props> = (props: Props) => {
     const linkedPagePathFormer = new LinkedPagePath(dPagePath.former);
     const linkedPagePathLatter = new LinkedPagePath(dPagePath.latter);
     formerLink = (
-      <div className="fs-5">
+      <>
         <PagePathHierarchicalLink linkedPagePath={linkedPagePathFormer} isInTrash={isInTrash} />
         <Separator />
-      </div>
+      </>
     );
     latterLink = (
       <PagePathHierarchicalLink linkedPagePath={linkedPagePathLatter} basePath={dPagePath.former} isInTrash={isInTrash} />
@@ -83,7 +83,7 @@ export const PagePathNav: FC<Props> = (props: Props) => {
 
   return (
     <div>
-      <span className={formerLinkClassName}>{formerLink}</span>
+      <span className={`${formerLinkClassName ?? ''} ${styles['grw-former-link']}`}>{formerLink}</span>
       <div className="d-flex align-items-center">
         <h1 className={`m-0 ${latterLinkClassName}`}>
           {latterLink}

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

@@ -1,4 +1,5 @@
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 

+ 10 - 0
apps/app/src/components/CompleteUserRegistrationForm.module.scss

@@ -0,0 +1,10 @@
+@use '~/styles/atoms/placeholders/buttons';
+
+:root {
+  .complete-user-registration-form :global {
+    .btn-register {
+      @extend %btn-nologin;
+      @extend %btn-register;
+    }
+  }
+}

+ 42 - 18
apps/app/src/components/CompleteUserRegistrationForm.tsx

@@ -11,6 +11,12 @@ import { toastError } from '../client/util/toastr';
 
 import { CompleteUserRegistration } from './CompleteUserRegistration';
 
+
+import styles from './CompleteUserRegistrationForm.module.scss';
+
+const moduleClass = styles['complete-user-registration-form'] ?? '';
+
+
 interface Props {
   email: string,
   token: string,
@@ -85,9 +91,9 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
 
   return (
     <>
-      <div className="nologin-dialog mx-auto" id="nologin-dialog">
+      <div className={`${moduleClass} nologin-dialog mx-auto rounded-4 rounded-top-0`} id="nologin-dialog">
         <div className="row mx-0">
-          <div className="col-12">
+          <div className="col-12 px-4">
 
             { (errorCode != null && errorCode === UserActivationErrorCode.TOKEN_NOT_FOUND) && (
               <p className="alert alert-danger">
@@ -111,15 +117,19 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               <input type="hidden" name="token" value={token} />
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
-                <input type="text" className="form-control" placeholder={t('Email')} disabled value={email} />
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">mail</span>
+                </span>
+                <input type="text" className="form-control rounded" placeholder={t('Email')} disabled value={email} />
               </div>
 
               <div className="input-group" id="input-group-username">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">person</span>
+                </span>
                 <input
                   type="text"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('User ID')}
                   name="username"
                   onChange={e => setUsername(e.target.value)}
@@ -129,15 +139,22 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
               {!usernameAvailable && (
                 <p className="form-text text-red">
-                  <span id="help-block-username"><span className="material-symbols-outlined">block</span>{t('installer.unavaliable_user_id')}</span>
+                  <span id="help-block-username">
+                    <span className="p-2 text-white opacity-75">
+                      <span className="material-symbols-outlined">block</span>
+                    </span>
+                    {t('installer.unavaliable_user_id')}
+                  </span>
                 </p>
               )}
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">sell</span>
+                </span>
                 <input
                   type="text"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('Name')}
                   name="name"
                   value={name}
@@ -148,10 +165,12 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
               </div>
 
               <div className="input-group">
-                <span className="input-group-text"></span><span className="material-symbols-outlined">lock</span>
+                <span className="p-2 text-white opacity-75">
+                  <span className="material-symbols-outlined">lock</span>
+                </span>
                 <input
                   type="password"
-                  className="form-control"
+                  className="form-control rounded"
                   placeholder={t('Password')}
                   name="password"
                   value={password}
@@ -161,17 +180,22 @@ const CompleteUserRegistrationForm: React.FC<Props> = (props: Props) => {
                 />
               </div>
 
-              <div className="input-group justify-content-center d-flex mt-5">
-                <button type="button" disabled={forceDisableForm || disableForm} className="btn btn-fill" id="register">
-                  <div className="eff"></div>
-                  <span className="btn-label"></span><span className="material-symbols-outlined">person_add</span>
-                  <span className="btn-label-text">{t('Create')}</span>
+              <div className="input-group justify-content-center mt-4">
+                <button
+                  type="button"
+                  disabled={forceDisableForm || disableForm}
+                  className="btn btn-secondary btn-register col-6 mx-auto d-flex"
+                >
+                  <span>
+                    <span className="material-symbols-outlined">person_add</span>
+                  </span>
+                  <span className="flex-grow-1">{t('Create')}</span>
                 </button>
               </div>
 
-              <div className="input-group mt-5 d-flex justify-content-center">
+              <div className="input-group mt-5 d-flex">
                 <a href="https://growi.org" className="link-growi-org">
-                  <span className="growi">GROWI</span>.<span className="org">ORG</span>
+                  <span className="growi">GROWI</span><span className="org">.org</span>
                 </a>
               </div>
             </form>

+ 4 - 4
apps/app/src/components/DataTransferForm.tsx

@@ -11,8 +11,8 @@ const DataTransferForm = (): JSX.Element => {
   const { transferKey, generateTransferKey } = useGenerateTransferKey();
 
   return (
-    <div data-testid="installerForm" className="p-3">
-      <p className="alert alert-success">
+    <div data-testid="installerForm" className="py-3 px-4">
+      <p className="text-white fs-5 mt-2">
         <strong>{ t('g2g_data_transfer.transfer_data_to_this_growi')}</strong>
       </p>
 
@@ -22,8 +22,8 @@ const DataTransferForm = (): JSX.Element => {
             {t('g2g_data_transfer.publish_transfer_key')}
           </button>
         </div>
-        <div className="col-md-12 mt-1">
-          <div>
+        <div className="col-md-12 mt-2">
+          <div className="d-flex">
             <input className="form-control" type="text" value={transferKey} readOnly />
             <CustomCopyToClipBoard textToBeCopied={transferKey} message="copied_to_clipboard" />
           </div>

+ 3 - 2
apps/app/src/components/DeleteBookmarkFolderModal.tsx

@@ -1,5 +1,6 @@
 
-import React, { FC } from 'react';
+import type { FC } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 import {
@@ -46,7 +47,7 @@ const DeleteBookmarkFolderModal: FC = () => {
         {t('bookmark_folder.delete_modal.modal_header_label')}
       </ModalHeader>
       <ModalBody>
-        <div className="pb-1">
+        <div className="pb-1 text-break">
           <label className="form-label">{ t('bookmark_folder.delete_modal.modal_body_description') }:</label><br />
           <FolderIcon isOpen={false} /> {deleteBookmarkFolderModalData?.bookmarkFolder?.name}
         </div>

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

@@ -75,7 +75,7 @@ export const DescendantsPageListModal = (): JSX.Element => {
         expandWindow={() => setIsWindowExpanded(true)}
         contractWindow={() => setIsWindowExpanded(false)}
       />
-      <button type="button" className="btn btn-close" onClick={close} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
     </span>
   ), [close, isWindowExpanded]);
 

+ 8 - 0
apps/app/src/components/ExpandOrContractButton.module.scss

@@ -0,0 +1,8 @@
+.btn-expand-or-contract {
+  padding: 3px;
+  opacity: .5;
+
+  &:hover {
+    opacity: .75;
+  }
+}

+ 9 - 2
apps/app/src/components/ExpandOrContractButton.tsx

@@ -1,12 +1,17 @@
 import type { FC } from 'react';
 import React from 'react';
 
+import styles from './ExpandOrContractButton.module.scss';
+
 type Props = {
   isWindowExpanded: boolean,
   contractWindow?: () => void,
   expandWindow?: () => void,
 };
 
+const moduleClass = styles['btn-expand-or-contract'] ?? '';
+
+
 const ExpandOrContractButton: FC<Props> = (props: Props) => {
   const { isWindowExpanded, contractWindow, expandWindow } = props;
 
@@ -25,10 +30,12 @@ const ExpandOrContractButton: FC<Props> = (props: Props) => {
   return (
     <button
       type="button"
-      className="btn material-symbols-outlined"
+      className={`btn ${moduleClass}`}
       onClick={isWindowExpanded ? clickContractButtonHandler : clickExpandButtonHandler}
     >
-      {isWindowExpanded ? 'close_fullscreen' : 'open_in_full'}
+      <span className="material-symbols-outlined fw-bold">
+        {isWindowExpanded ? 'close_fullscreen' : 'open_in_full'}
+      </span>
     </button>
   );
 };

+ 3 - 1
apps/app/src/components/FontFamily/use-growi-custom-icons.tsx

@@ -1,9 +1,11 @@
 import localFont from 'next/font/local';
 
-import { DefineStyle } from './types';
+import type { DefineStyle } from './types';
 
 const growiCustomIconFont = localFont({
   src: '../../../../../packages/custom-icons/dist/growi-custom-icons.woff2',
+  adjustFontFallback: false,
+  display: 'block',
 });
 
 export const useGrowiCustomIcon: DefineStyle = () => (

+ 1 - 1
apps/app/src/components/FontFamily/use-lato.tsx

@@ -1,6 +1,6 @@
 import { Lato } from 'next/font/google';
 
-import { DefineStyle } from './types';
+import type { DefineStyle } from './types';
 
 const lato = Lato({
   weight: ['400', '700'],

+ 2 - 1
apps/app/src/components/FontFamily/use-material-symbols-outlined.tsx

@@ -1,10 +1,11 @@
 import localFont from 'next/font/local';
 
-import { DefineStyle } from './types';
+import type { DefineStyle } from './types';
 
 const materialSymbolsOutlined = localFont({
   src: '../../../resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2',
   adjustFontFallback: false,
+  display: 'block',
 });
 
 export const useMaterialSymbolsOutlined: DefineStyle = () => (

+ 1 - 1
apps/app/src/components/FontFamily/use-source-han-code-jp.tsx

@@ -1,6 +1,6 @@
 import localFont from 'next/font/local';
 
-import { DefineStyle } from './types';
+import type { DefineStyle } from './types';
 
 const sourceHanCodeJPSubsetMain = localFont({
   src: '../../../resource/fonts/SourceHanCodeJP-Regular-subset-main.woff2',

+ 10 - 0
apps/app/src/components/InstallerForm.module.scss

@@ -0,0 +1,10 @@
+@use '~/styles/atoms/placeholders/buttons';
+
+:root {
+  .installer-form :global {
+    .btn-register {
+      @extend %btn-nologin;
+      @extend %btn-register;
+    }
+  }
+}

+ 34 - 21
apps/app/src/components/InstallerForm.tsx

@@ -12,6 +12,11 @@ import { apiv3Post } from '~/client/util/apiv3-client';
 import { toastError } from '~/client/util/toastr';
 
 
+import styles from './InstallerForm.module.scss';
+
+const moduleClass = styles['installer-form'] ?? '';
+
+
 const InstallerForm = memo((): JSX.Element => {
   const { t, i18n } = useTranslation();
 
@@ -88,8 +93,8 @@ const InstallerForm = memo((): JSX.Element => {
     : <span><span className="material-symbols-outlined">block</span>{ t('installer.unavaliable_user_id') }</span>;
 
   return (
-    <div data-testid="installerForm" className={`nologin-dialog p-3 mx-auto${hasErrorClass}`}>
-      <div className="row">
+    <div data-testid="installerForm" className={`${moduleClass} nologin-dialog py-3 px-4 rounded-4 rounded-top-0 mx-auto${hasErrorClass}`}>
+      <div className="row mt-3">
         <div className="col-md-12">
           <p className="alert alert-success">
             <strong>{ t('installer.create_initial_account') }</strong><br />
@@ -97,14 +102,16 @@ const InstallerForm = memo((): JSX.Element => {
           </p>
         </div>
       </div>
-      <div className="row">
-        <form role="form" id="register-form" className="col-md-12" onSubmit={submitHandler}>
+      <div className="row mt-2">
+        <form role="form" id="register-form" className="ps-1" onSubmit={submitHandler}>
           <div className="dropdown mb-3">
             <div className="input-group dropdown-with-icon">
-              <span className="input-group-text"></span><span className="material-symbols-outlined">language</span>
+              <span className="p-2 text-white opacity-75">
+                <span className="material-symbols-outlined">language</span>
+              </span>
               <button
                 type="button"
-                className="btn btn-secondary dropdown-toggle form-control text-end rounded-end"
+                className="btn btn-secondary dropdown-toggle form-control text-end rounded"
                 id="dropdownLanguage"
                 data-testid="dropdownLanguage"
                 data-bs-toggle="dropdown"
@@ -146,11 +153,13 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className={`input-group mb-3${hasErrorClass}`}>
-            <span className="input-group-text"></span><span className="material-symbols-outlined">person</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">person</span>
+            </span>
             <input
               data-testid="tiUsername"
               type="text"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('User ID')}
               name="registerForm[username]"
               // onBlur={checkUserName} // need not to check username before installation -- 2020.07.24 Yuki Takei
@@ -160,11 +169,13 @@ const InstallerForm = memo((): JSX.Element => {
           <p className="form-text">{ unavailableUserId }</p>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span><span className="material-symbols-outlined">sell</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">sell</span>
+            </span>
             <input
               data-testid="tiName"
               type="text"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Name')}
               name="registerForm[name]"
               required
@@ -172,11 +183,13 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span><span className="material-symbols-outlined">mail</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">mail</span>
+            </span>
             <input
               data-testid="tiEmail"
               type="email"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Email')}
               name="registerForm[email]"
               required
@@ -184,40 +197,40 @@ const InstallerForm = memo((): JSX.Element => {
           </div>
 
           <div className="input-group mb-3">
-            <span className="input-group-text"></span> <span className="material-symbols-outlined">lock</span>
+            <span className="p-2 text-white opacity-75">
+              <span className="material-symbols-outlined">lock</span>
+            </span>
             <input
               data-testid="tiPassword"
               type="password"
-              className="form-control"
+              className="form-control rounded"
               placeholder={t('Password')}
               name="registerForm[password]"
               required
             />
           </div>
 
-          <div className="input-group mt-4 d-flex justify-content-center">
+          <div className="input-group mt-4 justify-content-center">
             <button
               data-testid="btnSubmit"
               type="submit"
-              className="btn-fill btn btn-register"
-              id="register"
+              className="btn btn-secondary btn-register col-6 d-flex"
               disabled={isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
                   <span className="material-symbols-outlined">person_add</span>
                 )}
               </span>
-              <span className="btn-label-text">{ t('Create') }</span>
+              <span className="flex-grow-1">{ t('Create') }</span>
             </button>
           </div>
 
           <div>
             <a href="https://growi.org" className="link-growi-org">
-              <span className="growi">GROWI</span>.<span className="org">org</span>
+              <span className="growi">GROWI</span><span className="org">.org</span>
             </a>
           </div>
 

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

@@ -141,7 +141,6 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
         {/* Create Button */}
         <div className="input-group justify-content-center d-flex mt-4">
           <button type="submit" className="btn btn-fill" id="register" disabled={isLoading}>
-            <div className="eff"></div>
             <span className="btn-label">
               {isLoading ? (
                 <LoadingSpinner />
@@ -155,7 +154,7 @@ export const InvitedForm = (props: InvitedFormProps): JSX.Element => {
       </form>
       <div className="input-group mt-4 d-flex justify-content-center">
         <a href="https://growi.org" className="link-growi-org">
-          <span className="growi">GROWI</span>.<span className="org">ORG</span>
+          <span className="growi">GROWI</span><span className="org">.ORG</span>
         </a>
       </div>
     </div>

+ 9 - 8
apps/app/src/components/Layout/Admin.module.scss

@@ -95,9 +95,12 @@ $slack-work-space-name-card-border: #efc1f6;
       font-size: 0.6rem;
     }
     .admin-bot-card {
-      min-width: 280px;
+      min-width: 300px;
       max-width: 500px;
       border-radius: 8px !important;
+      .grw-botcard-title-active {
+        color: $gray-200;
+      }
     }
     .border-primary {
       border-width: 2px;
@@ -135,12 +138,12 @@ $slack-work-space-name-card-border: #efc1f6;
 
     .grw-bridge-proxy-circle {
       .circle {
-        left: 50%;
+        inset: 0;
         width: 100px;
         height: 100px;
         border: 13px solid;
-        transform: translate(-50%, -50%);
-        @include media-breakpoint-down(md) {
+
+        @include media-breakpoint-down(lg) {
           width: 50px;
           height: 50px;
           border: 8px solid;
@@ -148,13 +151,11 @@ $slack-work-space-name-card-border: #efc1f6;
       }
 
       .circle-inner {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
+        transform: translate(-50%, 25%);
       }
       .circle-inner.grw-proxy-server-name {
         margin-top: 55px;
+        transform: translate(-50%, -25%);
       }
     }
 

+ 28 - 118
apps/app/src/components/Layout/NoLoginLayout.module.scss

@@ -6,45 +6,33 @@
   height: 100vh;
 
   // layout
-  .page-wrapper {
-    display: flex;
-    align-items: center;
-    height: 100vh;
-    margin-top: 0px;
-
-    .main {
-      width: 100vw;
-
-      .nologin-header {
-        display: flex;
-        align-items: center;
-        padding-top: 30px;
-        padding-bottom: 10px;
+  .main {
+    width: 100vw;
+
+    .nologin-header {
+      padding-top: 30px;
+      padding-bottom: 10px;
+      svg {
+        fill: white;
       }
+    }
 
+    .growi-logo-type {
+      letter-spacing: .5rem;
     }
 
   }
 
   // styles
-  .nologin-header {
-    h1 {
-      font-size: 22px;
-      line-height: 1em;
-    }
-  }
-
   .alert {
     padding: 0.5em 1em 0.5em 2em;
   }
 
   .input-group {
-    margin-bottom: 10px;
+    margin-bottom: 16px;
 
     .input-group-text {
       text-align: center;
-      border: none;
-      border-radius: 0;
     }
   }
 
@@ -54,48 +42,6 @@
     }
   }
 
-  $btn-fill-colors: (
-    'login': (
-      rgba(bs.$danger, 0.4),
-      rgba(#7e4153, 0.7),
-    ),
-    'register': (
-      rgba(bs.$success, 0.4),
-      rgba(#3f7263, 0.7),
-    ),
-    'google': (
-      rgba(#24292e, 0.4),
-      bs.$gray-700,
-    ),
-    'github': (
-      rgba(lighten(black, 20%), 0.4),
-      bs.$gray-700,
-    ),
-    'facebook': (
-      rgba(#29487d, 0.4),
-      bs.$gray-700,
-    ),
-    'oidc': (
-      rgba(#24292e, 0.4),
-      bs.$gray-700,
-    ),
-    'saml': (
-      rgba(#55a79a, 0.4),
-      bs.$gray-700,
-    ),
-  );
-
-  @each $label, $colors in $btn-fill-colors {
-    .btn-fill##{$label} {
-      .btn-label {
-        background-color: nth($colors, 1);
-      }
-      .eff {
-        background-color: nth($colors, 2);
-      }
-    }
-  }
-
   // footer link text
   .link-growi-org {
     font-size: smaller;
@@ -107,6 +53,7 @@
       transition: color 0.8s;
     }
   }
+
   .nologin-header,
   .nologin-dialog {
     max-width: 480px;
@@ -138,24 +85,11 @@
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
     .nologin-header {
-      background-color: rgba(white, 0.5);
-
-      svg {
-        color: var(--bs-body-color);
-      }
-
-      .logo {
-        color: rgba(black, 0.5);
-        background-color: rgba(black, 0);
-      }
-
-      h1 {
-        color: rgba(black, 0.5);
-      }
+      background-color: rgba(white, 0.3);
     }
 
     .nologin-dialog {
-      background-color: rgba(white, 0.5);
+      background-color: rgba(white, 0.3);
       .link-switch {
         color: #1939b8;
         &:hover {
@@ -165,35 +99,30 @@
     }
 
     .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba(bs.$gray-700, 0.7);
-      }
-
       .form-control {
-        color: white;
-        background-color: rgba(bs.$gray-600, 0.7);
+        color: bs.$gray-800;
+        background-color: white;
         box-shadow: unset;
 
         &::placeholder {
-          color: darken(white, 30%);
+          color: bs.$gray-500;
         }
       }
     }
 
     .link-growi-org {
-      color: rgba(black, 0.4);
+      color: rgba(white, 0.4);
 
       &:hover,
       &.focus {
-        color: black;
+        color: white;
 
         .growi {
-          color: darken(var.$growi-green, 20%);
+          color: darken(var.$growi-blue, 10%);
         }
 
         .org {
-          color: darken(var.$growi-blue, 15%);
+          color: darken(var.$growi-green, 10%);
         }
       }
     }
@@ -206,29 +135,15 @@
     // background color
     $color-gradient: #3c465c;
     background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-      linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
-      linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
       linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
     .nologin-header {
-      background-color: rgba(black, 0.5);
-
-      svg {
-        color: var(--bs-body-color);
-      }
-
-      .logo {
-        color: rgba(white, 0.5);
-        background-color: rgba(white, 0);
-      }
-
-      h1 {
-        color: rgba(white, 0.5);
-      }
+      background-color: rgba(black, 0.3);
     }
 
     .nologin-dialog {
-      background-color: rgba(black, 0.5);
+      background-color: rgba(black, 0.3);
       .link-switch {
         color: #7b9bd5;
         &:hover {
@@ -238,18 +153,13 @@
     }
 
     .input-group {
-      .input-group-text {
-        color: darken(white, 30%);
-        background-color: rgba(bs.$gray-700, 0.7);
-      }
-
       .form-control {
         color: white;
         background-color: rgba(#505050, 0.7);
         box-shadow: unset;
 
         &::placeholder {
-          color: darken(white, 30%);
+          color: bs.$gray-500;
         }
       }
     }
@@ -262,11 +172,11 @@
         color: rgba(white, 0.7);
 
         .growi {
-          color: darken(var.$growi-green, 5%);
+          color: darken(var.$growi-blue, 5%);
         }
 
         .org {
-          color: darken(var.$growi-blue, 5%);
+          color: darken(var.$growi-green, 5%);
         }
       }
     }

+ 11 - 5
apps/app/src/components/Layout/NoLoginLayout.tsx

@@ -1,4 +1,5 @@
-import React, { ReactNode } from 'react';
+import type { ReactNode } from 'react';
+import React from 'react';
 
 import { useAppTitle } from '~/stores/context';
 
@@ -26,15 +27,20 @@ export const NoLoginLayout = ({
 
   return (
     <RawLayout className={`nologin ${commonStyles.nologin} ${classNames}`}>
-      <div className="page-wrapper flex-row">
+      <div className="d-flex align-items-center vh-100 mt-0 flex-row">
         <div className="main container-fluid">
 
           <div className="row">
 
             <div className="col-md-12 position-relative">
-              <div className="nologin-header mx-auto flex-column">
-                <GrowiLogo />
-                <h1 className="my-3">{ appTitle ?? 'GROWI' }</h1>
+              <div className="nologin-header mx-auto rounded-4 rounded-bottom-0">
+                <div className="d-flex justify-content-center align-items-center">
+                  <GrowiLogo />
+                  <h1 className="growi-logo-type text-white fs-3 my-3 ms-3">GROWI</h1>
+                </div>
+                {appTitle !== 'GROWI' ? (
+                  <h2 className="fs-4 text-center text-white">{ appTitle }</h2>
+                ) : null}
                 <div className="noLogin-form-errors px-3"></div>
               </div>
               {children}

+ 0 - 18
apps/app/src/components/LoginForm.module.scss

@@ -1,18 +0,0 @@
-.login-form :global {
-  // To adjust the behavior, this problem is not solved.
-  // See https://github.com/AaronCCWong/react-card-flip/issues/56
-  .react-card-front,
-  .react-card-back {
-    height: 0% !important;
-  }
-
-  .collapse-external-auth {
-    overflow: hidden;
-  }
-
-  .link-growi-org {
-    position: absolute;
-    bottom: 9px;
-    z-index: 3;
-  }
-}

+ 44 - 0
apps/app/src/components/LoginForm/ExternalAuthButton.tsx

@@ -0,0 +1,44 @@
+import { useCallback } from 'react';
+
+import { IExternalAuthProviderType } from '@growi/core';
+import { useTranslation } from 'next-i18next';
+
+const authIcon = {
+  [IExternalAuthProviderType.google]: <span className="growi-custom-icons align-bottom">google</span>,
+  [IExternalAuthProviderType.github]: <span className="growi-custom-icons align-bottom">github</span>,
+  [IExternalAuthProviderType.facebook]: <span className="growi-custom-icons align-bottom">facebook</span>,
+  [IExternalAuthProviderType.oidc]: <span className="growi-custom-icons align-bottom">openid</span>,
+  [IExternalAuthProviderType.saml]: <span className="material-symbols-outlined align-bottom">key</span>,
+};
+
+const authLabel = {
+  [IExternalAuthProviderType.google]: 'Google',
+  [IExternalAuthProviderType.github]: 'GitHub',
+  [IExternalAuthProviderType.facebook]: 'Facebook',
+  [IExternalAuthProviderType.oidc]: 'OIDC',
+  [IExternalAuthProviderType.saml]: 'SAML',
+};
+
+
+export const ExternalAuthButton = ({ authType }: {authType: IExternalAuthProviderType}): JSX.Element => {
+  const { t } = useTranslation();
+
+  const key = `btn-auth-${authType.toString()}`;
+  const btnClass = `btn-auth-${authType.toString()}`;
+
+  const handleLoginWithExternalAuth = useCallback(() => {
+    window.location.href = `/passport/${authType.toString()}`;
+  }, [authType]);
+
+  return (
+    <button
+      key={key}
+      type="button"
+      className={`btn btn-secondary ${btnClass} my-2 col-10 col-sm-7 mx-auto d-flex`}
+      onClick={handleLoginWithExternalAuth}
+    >
+      <span>{authIcon[authType]}</span>
+      <span className="flex-grow-1">{t('Sign in with External auth', { signin: authLabel[authType] })}</span>
+    </button>
+  );
+};

+ 104 - 0
apps/app/src/components/LoginForm/LoginForm.module.scss

@@ -0,0 +1,104 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '~/styles/atoms/placeholders/buttons';
+
+.login-form :global {
+  //
+  // deactivated in order to fix https://redmine.weseek.co.jp/issues/143531 -- 2024.04.02 Yuki Takei
+  //
+  // // To adjust the behavior, this problem is not solved.
+  // // See https://github.com/AaronCCWong/react-card-flip/issues/56
+  // .react-card-front,
+  // .react-card-back {
+  //   height: 0% !important;
+  // }
+
+  .collapse-external-auth {
+    overflow: hidden;
+  }
+
+  .link-growi-org {
+    position: absolute;
+    bottom: 9px;
+    z-index: 3;
+  }
+
+  .text-line {
+    &:before,
+    &:after {
+      flex-grow: 1;
+      height: 1px;
+      margin:0 1em;
+      content: '';
+      background: rgba(white,0.5);
+    }
+  }
+
+
+  .ldap-space {
+    padding-right: 76px;
+  }
+
+  .input-ldap {
+    position: absolute;
+    top: 4px;
+    right: 5px;
+  }
+}
+
+
+// Button colors
+:root {
+  .login-form :global {
+
+    .btn {
+      @extend %btn-nologin;
+    }
+
+    .btn-register {
+      @extend %btn-register;
+    }
+
+    .btn-login {
+      --bs-btn-bg: #{rgba(#204986, 0.6)};
+      --bs-btn-hover-bg: #{rgba(#204986, 0.8)};
+      --bs-btn-active-bg: #{rgba(#204986, 0.8)};
+    }
+
+    .btn-function {
+      --bs-btn-bg: #{rgba(bs.$gray-800, 0.8)};
+      --bs-btn-hover-bg: #{rgba(bs.$gray-800, 0.5)};
+      --bs-btn-active-bg: #{rgba(bs.$gray-800, 0.5)};
+    }
+
+    .btn-auth-google {
+      --bs-btn-bg: #{rgba(#4285F4, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#4285F4, 0.8)};
+      --bs-btn-active-bg: #{rgba(#4285F4, 0.8)};
+    }
+
+    .btn-auth-github {
+      --bs-btn-bg: #{rgba(#403D3E, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#403D3E, 0.7)};
+      --bs-btn-active-bg: #{rgba(#403D3E, 0.7)};
+    }
+
+    .btn-auth-facebook {
+      --bs-btn-bg: #{rgba(#29487d, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#29487d, 0.9)};
+      --bs-btn-active-bg: #{rgba(#29487d, 0.9)};
+    }
+
+    .btn-auth-oidc {
+      --bs-btn-bg: #{rgba(#835B1A, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#835B1A, 0.8)};
+      --bs-btn-active-bg: #{rgba(#835B1A, 0.8)};
+    }
+
+    .btn-auth-saml {
+      --bs-btn-bg: #{rgba(#138957, 0.4)};
+      --bs-btn-hover-bg: #{rgba(#138957, 0.7)};
+      --bs-btn-active-bg: #{rgba(#138957, 0.7)};
+    }
+  }
+}

+ 64 - 102
apps/app/src/components/LoginForm.tsx → apps/app/src/components/LoginForm/LoginForm.tsx

@@ -2,6 +2,7 @@ import React, {
   useState, useEffect, useCallback,
 } from 'react';
 
+import type { IExternalAuthProviderType } from '@growi/core';
 import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -14,10 +15,15 @@ import type { IErrorV3 } from '~/interfaces/errors/v3-error';
 import { RegistrationMode } from '~/interfaces/registration-mode';
 import { toArrayIfNot } from '~/utils/array-utils';
 
-import { CompleteUserRegistration } from './CompleteUserRegistration';
+import { CompleteUserRegistration } from '../CompleteUserRegistration';
+
+import { ExternalAuthButton } from './ExternalAuthButton';
 
 import styles from './LoginForm.module.scss';
 
+const moduleClass = styles['login-form'];
+
+
 type LoginFormProps = {
   username?: string,
   name?: string,
@@ -29,7 +35,7 @@ type LoginFormProps = {
   isLocalStrategySetup: boolean,
   isLdapStrategySetup: boolean,
   isLdapSetupFailed: boolean,
-  objOfIsExternalAuthEnableds?: any,
+  enabledExternalAuthType?: IExternalAuthProviderType[],
   isMailerSetup?: boolean,
   externalAccountLoginError?: IExternalAccountLoginError,
 }
@@ -40,10 +46,10 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
   const {
     isLocalStrategySetup, isLdapStrategySetup, isLdapSetupFailed, isPasswordResetEnabled,
-    isEmailAuthenticationEnabled, registrationMode, registrationWhitelist, isMailerSetup, objOfIsExternalAuthEnableds,
+    isEmailAuthenticationEnabled, registrationMode, registrationWhitelist, isMailerSetup, enabledExternalAuthType,
   } = props;
   const isLocalOrLdapStrategiesEnabled = isLocalStrategySetup || isLdapStrategySetup;
-  const isSomeExternalAuthEnabled = Object.values(objOfIsExternalAuthEnableds).some(elem => elem);
+  const isSomeExternalAuthEnabled = enabledExternalAuthType != null && enabledExternalAuthType.length > 0;
 
   // states
   const [isRegistering, setIsRegistering] = useState(false);
@@ -79,12 +85,6 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
     return t(key);
   }, [t]);
 
-  const handleLoginWithExternalAuth = useCallback((e) => {
-    const auth = e.currentTarget.id;
-
-    window.location.href = `/passport/${auth}`;
-  }, []);
-
   const resetLoginErrors = useCallback(() => {
     if (loginErrors.length === 0) return;
     setLoginErrors([]);
@@ -196,31 +196,33 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
         <form role="form" onSubmit={handleLoginWithLocalSubmit} id="login-form">
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">person</span>
             </span>
             <input
               type="text"
-              className="form-control rounded-0"
+              className={`form-control rounded ms-2 ${isLdapStrategySetup ? 'ldap-space' : ''}`}
               data-testid="tiUsernameForLogin"
               placeholder="Username or E-mail"
               onChange={(e) => { setUsernameForLogin(e.target.value) }}
               name="usernameForLogin"
             />
             {isLdapStrategySetup && (
-              <small className="input-group-text text-success">
-                <span className="material-symbols-outlined">select_check_box</span>LDAP
+              <small className="badge text-bg-success input-ldap d-flex align-items-center">
+                <span className="material-symbols-outlined">network_node</span>
+                <span className="">LDAP</span>
               </small>
             )}
+
           </div>
 
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">lock</span>
             </span>
             <input
               type="password"
-              className="form-control rounded-0"
+              className="form-control rounded ms-2"
               data-testid="tiPasswordForLogin"
               placeholder="Password"
               onChange={(e) => { setPasswordForLogin(e.target.value) }}
@@ -231,20 +233,18 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           <div className="input-group my-4">
             <button
               type="submit"
-              id="login"
-              className="btn btn-fill rounded-0 login mx-auto"
+              className="btn btn-secondary btn-login col-7 mx-auto d-flex"
               data-testid="btnSubmitForLogin"
               disabled={isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
                   <span className="material-symbols-outlined">login</span>
                 )}
               </span>
-              <span className="btn-label-text">{t('Sign in')}</span>
+              <span className="flex-grow-1">{t('Sign in')}</span>
             </button>
           </div>
         </form>
@@ -263,63 +263,24 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
   ]);
 
 
-  const renderExternalAuthInput = useCallback((auth) => {
-    const authIconNames = {
-      google: 'google',
-      github: 'github',
-      facebook: 'facebook',
-      oidc: 'openid',
-      saml: 'key',
-    };
-
-    return (
-      <div key={auth} className="col-6 my-2">
-        <button type="button" className="btn btn-fill rounded-0" id={auth} onClick={handleLoginWithExternalAuth}>
-          <div className="eff"></div>
-          <span className="btn-label">
-            <span className="growi-custom-icons align-bottom">{authIconNames[auth]}</span>
-          </span>
-          <span className="btn-label-text">{t('Sign in')}</span>
-        </button>
-        <div className="small text-end">by {auth} Account</div>
-      </div>
-    );
-  }, [handleLoginWithExternalAuth, t]);
-
   const renderExternalAuthLoginForm = useCallback(() => {
-    const { isLocalStrategySetup, isLdapStrategySetup, objOfIsExternalAuthEnableds } = props;
-    const isExternalAuthCollapsible = isLocalStrategySetup || isLdapStrategySetup;
-    const collapsibleClass = isExternalAuthCollapsible ? 'collapse collapse-external-auth' : '';
+    const { enabledExternalAuthType } = props;
+
+    if (enabledExternalAuthType == null) {
+      return <></>;
+    }
 
     return (
       <>
-        <div className="grw-external-auth-form border-top border-bottom">
-          <div id="external-auth" className={`external-auth ${collapsibleClass}`}>
-            <div className="row mt-2">
-              {Object.keys(objOfIsExternalAuthEnableds).map((auth) => {
-                if (!objOfIsExternalAuthEnableds[auth]) {
-                  return;
-                }
-                return renderExternalAuthInput(auth);
-              })}
-            </div>
-          </div>
+        <div className="text-center text-line d-flex align-items-center mb-3">
+          <p className="text-white mb-0">{t('or')}</p>
         </div>
-        <div className="text-center">
-          <button
-            type="button"
-            className="btn btn-secondary btn-external-auth-tab btn-sm rounded-0 mb-3"
-            data-bs-toggle={isExternalAuthCollapsible ? 'collapse' : ''}
-            data-bs-target="#external-auth"
-            aria-expanded="false"
-            aria-controls="external-auth"
-          >
-            External Auth
-          </button>
+        <div className="mt-2">
+          { enabledExternalAuthType.map(authType => <ExternalAuthButton authType={authType} />) }
         </div>
       </>
     );
-  }, [props, renderExternalAuthInput]);
+  }, [props, t]);
 
   const resetRegisterErrors = useCallback(() => {
     if (registerErrors.length === 0) return;
@@ -420,13 +381,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group" id="input-group-username">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">person</span>
                 </span>
                 {/* username */}
                 <input
                   type="text"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setUsernameForRegister(e.target.value) }}
                   placeholder={t('User ID')}
                   name="username"
@@ -438,13 +399,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
                 <span id="help-block-username"></span>
               </p>
               <div className="input-group">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">sell</span>
                 </span>
                 {/* name */}
                 <input
                   type="text"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setNameForRegister(e.target.value) }}
                   placeholder={t('Name')}
                   name="name"
@@ -456,14 +417,14 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           )}
 
           <div className="input-group">
-            <span className="input-group-text">
+            <span className="text-white opacity-75 d-flex align-items-center">
               <span className="material-symbols-outlined">mail</span>
             </span>
             {/* email */}
             <input
               type="email"
               disabled={!isMailerSetup && isEmailAuthenticationEnabled}
-              className="form-control rounded-0"
+              className="form-control rounded ms-2"
               onChange={(e) => { setEmailForRegister(e.target.value) }}
               placeholder={t('Email')}
               name="email"
@@ -490,13 +451,13 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           {!isEmailAuthenticationEnabled && (
             <div>
               <div className="input-group">
-                <span className="input-group-text">
+                <span className="text-white opacity-75 d-flex align-items-center">
                   <span className="material-symbols-outlined">lock</span>
                 </span>
                 {/* Password */}
                 <input
                   type="password"
-                  className="form-control rounded-0"
+                  className="form-control rounded ms-2"
                   onChange={(e) => { setPasswordForRegister(e.target.value) }}
                   placeholder={t('Password')}
                   name="password"
@@ -510,35 +471,31 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           <div className="input-group justify-content-center my-4">
             <button
               type="submit"
-              className="btn btn-fill rounded-0"
-              id="register"
+              className="btn btn-secondary btn-register d-flex col-7"
               disabled={(!isMailerSetup && isEmailAuthenticationEnabled) || isLoading}
             >
-              <div className="eff"></div>
-              <span className="btn-label">
+              <span>
                 {isLoading ? (
                   <LoadingSpinner />
                 ) : (
-                  <span className="material-symbols-outlined">login</span>
+                  <span className="material-symbols-outlined">person_add</span>
                 )}
               </span>
-              <span className="btn-label-text">{submitText}</span>
+              <span className="flex-grow-1">{submitText}</span>
             </button>
           </div>
         </form>
 
-        <div className="border-bottom"></div>
-
         <div className="row">
-          <div className="text-end col-12 mt-2 py-2">
+          <div className="text-end col-12 mb-5">
             <a
               href="#login"
-              id="login"
-              className="link-switch"
-              style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
+              className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
+              style={{ pointerEvents: isLoading ? 'none' : undefined }}
               onClick={switchForm}
             >
-              <span className="material-symbols-outlined">login</span>{t('Sign in is here')}
+              <span className="material-symbols-outlined fs-5">login</span>
+              <span className="flex-grow-1">{t('Sign in is here')}</span>
             </a>
           </div>
         </div>
@@ -554,32 +511,37 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
   }
 
   return (
-    <div className={`login-form ${styles['login-form']}`}>
-      <div className="nologin-dialog mx-auto" id="nologin-dialog" data-testid="login-form">
+    <div className={moduleClass}>
+      <div className="nologin-dialog mx-auto rounded-4 rounded-top-0" id="nologin-dialog" data-testid="login-form">
         <div className="row mx-0">
-          <div className="col-12">
+          <div className="col-12 px-md-4">
             <ReactCardFlip isFlipped={isRegistering} flipDirection="horizontal" cardZIndex="3">
               <div className="front">
                 {isLocalOrLdapStrategiesEnabled && renderLocalOrLdapLoginForm()}
                 {isSomeExternalAuthEnabled && renderExternalAuthLoginForm()}
                 {isLocalOrLdapStrategiesEnabled && isPasswordResetEnabled && (
-                  <div className="text-end mb-2">
-                    <a href="/forgot-password" className="d-block link-switch">
-                      <span className="material-symbols-outlined">vpn_key</span>{t('forgot_password.forgot_password')}
+                  <div className="mt-4">
+                    <a
+                      href="/forgot-password"
+                      className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
+                      style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
+                    >
+                      <span className="material-symbols-outlined">vpn_key</span>
+                      <span className="flex-grow-1">{t('forgot_password.forgot_password')}</span>
                     </a>
                   </div>
                 )}
                 {/* Sign up link */}
                 {isRegistrationEnabled && (
-                  <div className="text-end mb-2">
+                  <div className="mt-2 mb-5">
                     <a
                       href="#register"
-                      id="register"
-                      className="link-switch"
+                      className="btn btn-sm btn-secondary btn-function col-10 col-sm-9 mx-auto py-1 d-flex"
                       style={{ pointerEvents: isLoading ? 'none' : 'auto' }}
                       onClick={switchForm}
                     >
-                      <span className="material-symbols-outlined">check_box</span> {t('Sign up is here')}
+                      <span className="material-symbols-outlined">person_add</span>
+                      <span className="flex-grow-1">{t('Sign up is here')}</span>
                     </a>
                   </div>
                 )}
@@ -592,7 +554,7 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
           </div>
         </div>
         <a href="https://growi.org" className="link-growi-org ps-3">
-          <span className="growi">GROWI</span>.<span className="org">org</span>
+          <span className="growi">GROWI</span><span className="org">.org</span>
         </a>
       </div>
     </div>

+ 1 - 0
apps/app/src/components/LoginForm/index.ts

@@ -0,0 +1 @@
+export * from './LoginForm';

+ 2 - 2
apps/app/src/components/Me/UISettings.tsx

@@ -57,7 +57,7 @@ export const UISettings = (): JSX.Element => {
               label="Collapsed"
               additionalClasses={styles['grw-sidebar-mode-icon']}
             >
-              <span className="growi-custom-icons">sidebar-collapsed</span>
+              <span className="growi-custom-icons fs-6">sidebar-collapsed</span>
             </IconWithTooltip>
             <div className="form-check form-switch ms-1">
 
@@ -71,7 +71,7 @@ export const UISettings = (): JSX.Element => {
               <label className="form-label form-check-label" htmlFor="swSidebarMode"></label>
             </div>
             <IconWithTooltip id="iwt-sidebar-dock" label="Dock" additionalClasses={styles['grw-sidebar-mode-icon']}>
-              <span className="growi-custom-icons">sidebar-dock</span>
+              <span className="growi-custom-icons fs-6">sidebar-dock</span>
             </IconWithTooltip>
           </div>
           <div className="ms-2">

+ 1 - 0
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -295,6 +295,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
           d-flex align-items-center justify-content-end px-2 px-sm-3 px-md-4 py-1 gap-2 gap-md-4 d-print-none
         `}
         data-testid="grw-contextual-sub-nav"
+        id="grw-contextual-sub-nav"
       >
         {pageId != null && (
           <PageControls

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

@@ -75,7 +75,7 @@ export const PageAccessoriesModal = (): JSX.Element => {
         expandWindow={() => setIsWindowExpanded(true)}
         contractWindow={() => setIsWindowExpanded(false)}
       />
-      <button type="button" className="btn btn-close" onClick={close} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={close} aria-label="Close"></button>
     </span>
   ), [close, isWindowExpanded]);
 

+ 1 - 1
apps/app/src/components/PageControls/BookmarkButtons.tsx

@@ -81,7 +81,7 @@ export const BookmarkButtons: FC<Props> = (props: Props) => {
           </span>
         </DropdownToggle>
       </BookmarkFolderMenu>
-      <UncontrolledTooltip placement="top" data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
+      <UncontrolledTooltip data-testid="bookmark-button-tooltip" target="bookmark-dropdown-btn" fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 

+ 1 - 1
apps/app/src/components/PageControls/LikeButtons.tsx

@@ -54,7 +54,7 @@ const LikeButtons: FC<LikeButtonsProps> = (props: LikeButtonsProps) => {
         <span className={`material-symbols-outlined ${isLiked ? 'fill' : ''}`}>favorite</span>
       </button>
 
-      <UncontrolledTooltip data-testid="like-button-tooltip" placement="top" target="like-button" autohide={false} fade={false}>
+      <UncontrolledTooltip data-testid="like-button-tooltip" target="like-button" autohide={false} fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
 

+ 8 - 7
apps/app/src/components/PageControls/PageControls.tsx

@@ -235,7 +235,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     return wideviewMenuItemRenderer;
   }, [pageInfo, switchContentWidthClickHandler, expandContentWidth]);
 
-  if (!isIPageInfoForOperation(pageInfo)) {
+  if (!isIPageInfoForEntity(pageInfo)) {
     return <></>;
   }
 
@@ -249,6 +249,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
     MenuItemType.REVERT,
   ];
 
+  const _isIPageInfoForOperation = isIPageInfoForOperation(pageInfo);
   const isViewMode = editorMode === EditorMode.View;
 
   return (
@@ -256,18 +257,18 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
       { isDeviceLargerThanMd && (
         <SearchButton />
       )}
-      {revisionId != null && !isViewMode && (
+      {revisionId != null && !isViewMode && _isIPageInfoForOperation && (
         <Tags
           onClickEditTagsButton={onClickEditTagsButton}
         />
       )}
-      {revisionId != null && (
+      {revisionId != null && _isIPageInfoForOperation && (
         <SubscribeButton
           status={pageInfo.subscriptionStatus}
           onClick={subscribeClickhandler}
         />
       )}
-      {revisionId != null && (
+      {revisionId != null && _isIPageInfoForOperation && (
         <LikeButtons
           onLikeClicked={likeClickhandler}
           sumOfLikers={sumOfLikers}
@@ -275,7 +276,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
           likers={likers}
         />
       )}
-      {revisionId != null && (
+      {revisionId != null && _isIPageInfoForOperation && (
         <BookmarkButtons
           pageId={pageId}
           isBookmarked={pageInfo.isBookmarked}
@@ -289,7 +290,7 @@ const PageControlsSubstance = (props: PageControlsSubstanceProps): JSX.Element =
           disabled={disableSeenUserInfoPopover}
         />
       ) }
-      { showPageControlDropdown && (
+      { showPageControlDropdown && _isIPageInfoForOperation && (
         <PageItemControl
           alignEnd
           pageId={pageId}
@@ -337,7 +338,7 @@ export const PageControls = memo((props: PageControlsProps): JSX.Element => {
     return <></>;
   }
 
-  if (!isIPageInfoForOperation(pageInfo)) {
+  if (!isIPageInfoForEntity(pageInfo)) {
     return <></>;
   }
 

+ 1 - 2
apps/app/src/components/PageControls/SeenUserInfo.tsx

@@ -2,7 +2,6 @@ import type { FC } from 'react';
 import React, { useState } from 'react';
 
 import type { IUser } from '@growi/core';
-import { FootstampIcon } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 import { UncontrolledTooltip, Popover, PopoverBody } from 'reactstrap';
 
@@ -40,7 +39,7 @@ const SeenUserInfo: FC<Props> = (props: Props) => {
           </div>
         </PopoverBody>
       </Popover>
-      <UncontrolledTooltip data-testid="seen-user-info-tooltip" placement="top" target="btn-seen-user" fade={false}>
+      <UncontrolledTooltip data-testid="seen-user-info-tooltip" target="btn-seen-user" fade={false}>
         {t('tooltip.footprints')}
       </UncontrolledTooltip>
     </div>

+ 3 - 2
apps/app/src/components/PageControls/SubscribeButton.tsx

@@ -1,4 +1,5 @@
-import React, { FC, useCallback } from 'react';
+import type { FC } from 'react';
+import React, { useCallback } from 'react';
 
 import { SubscriptionStatusType } from '@growi/core';
 import { useTranslation } from 'next-i18next';
@@ -41,7 +42,7 @@ const SubscribeButton: FC<Props> = (props: Props) => {
         </span>
       </button>
 
-      <UncontrolledTooltip data-testid="subscribe-button-tooltip" placement="top" target="subscribe-button" fade={false}>
+      <UncontrolledTooltip data-testid="subscribe-button-tooltip" target="subscribe-button" fade={false}>
         {t(getTooltipMessage())}
       </UncontrolledTooltip>
     </>

+ 1 - 0
apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss

@@ -5,6 +5,7 @@
 @include mixins.editing() {
   .grw-editor-navbar-bottom :global {
     .grw-grant-selector {
+      max-width: 250px;
       .material-symbols-outlined  {
         padding-bottom: 2px;
         font-size: 19px;

+ 11 - 11
apps/app/src/components/PageEditor/HandsontableModal.tsx

@@ -442,7 +442,7 @@ export const HandsontableModal = (): JSX.Element => {
         contractWindow={contractWindow}
         expandWindow={expandWindow}
       />
-      <button type="button" className="btn btn-close" onClick={cancel} aria-label="Close"></button>
+      <button type="button" className="btn btn-close ms-2" onClick={cancel} aria-label="Close"></button>
     </span>
   );
 
@@ -457,14 +457,14 @@ export const HandsontableModal = (): JSX.Element => {
       className={`handsontable-modal ${isWindowExpanded && 'grw-modal-expanded'}`}
       onOpened={handleModalOpen}
     >
-      <ModalHeader tag="h4" toggle={cancel} close={closeButton} className="bg-primary text-light">
+      <ModalHeader tag="h4" toggle={cancel} close={closeButton}>
         {t('handsontable_modal.title')}
       </ModalHeader>
       <ModalBody className="p-0 d-flex flex-column">
-        <div className="grw-hot-modal-navbar px-4 py-3 border-bottom">
+        <div className="grw-hot-modal-navbar p-3">
           <button
             type="button"
-            className="me-4 data-import-button btn btn-secondary"
+            className="me-4 data-import-button btn btn-outline-neutral-secondary"
             data-bs-toggle="collapse"
             data-bs-target="#collapseDataImport"
             aria-expanded={isDataImportAreaExpanded}
@@ -474,23 +474,23 @@ export const HandsontableModal = (): JSX.Element => {
             <span className="material-symbols-outlined">{isDataImportAreaExpanded ? 'expand_less' : 'expand_more'}</span>
           </button>
           <div role="group" className="btn-group">
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('l') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('l') }}>
               <span className="material-symbols-outlined">format_align_left</span>
             </button>
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('c') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('c') }}>
               <span className="material-symbols-outlined">format_align_center</span>
             </button>
-            <button type="button" className="btn btn-secondary" onClick={() => { alignButtonHandler('r') }}>
+            <button type="button" className="btn btn-outline-neutral-secondary" onClick={() => { alignButtonHandler('r') }}>
               <span className="material-symbols-outlined">format_align_right</span>
             </button>
           </div>
           <Collapse isOpen={isDataImportAreaExpanded}>
-            <div className="mt-4">
+            <div className="py-3 border-bottom">
               <MarkdownTableDataImportForm onCancel={toggleDataImportArea} onImport={importData} />
             </div>
           </Collapse>
         </div>
-        <div ref={c => setHotTableContainer(c)} className="m-4 hot-table-container">
+        <div ref={c => setHotTableContainer(c)} className="hot-table-container px-3">
           <HotTable
             ref={c => setHotTable(c)}
             data={markdownTable.table}
@@ -507,9 +507,9 @@ export const HandsontableModal = (): JSX.Element => {
         </div>
       </ModalBody>
       <ModalFooter className="grw-modal-footer">
-        <button type="button" className="btn btn-danger" onClick={reset}>{t('commons:Reset')}</button>
+        <button type="button" className="btn btn-outline-danger" onClick={reset}>{t('commons:Reset')}</button>
         <div className="ms-auto">
-          <button type="button" className="me-2 btn btn-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
+          <button type="button" className="me-2 btn btn-outline-neutral-secondary" onClick={cancel}>{t('handsontable_modal.cancel')}</button>
           <button type="button" className="btn btn-primary" onClick={save}>{t('handsontable_modal.done')}</button>
         </div>
       </ModalFooter>

+ 3 - 3
apps/app/src/components/PageEditor/MarkdownTableDataImportForm.tsx

@@ -66,7 +66,7 @@ export const MarkdownTableDataImportForm = (props: MarkdownTableDataImportFormPr
           <option value="html">HTML</option>
         </select>
       </div>
-      <div>
+      <div className="mt-2">
         <label htmlFor="data-import-form-type-textarea" className="form-label">{t('import_data')}</label>
         <textarea
           id="data-import-form-type-textarea"
@@ -88,8 +88,8 @@ export const MarkdownTableDataImportForm = (props: MarkdownTableDataImportFormPr
           />
         </div>
       </Collapse>
-      <div className="d-flex justify-content-end">
-        <Button color="secondary me-2" onClick={onCancel}>{t('cancel')}</Button>
+      <div className="mt-3 d-flex justify-content-end">
+        <Button color="outline-neutral-secondary me-2" onClick={onCancel}>{t('cancel')}</Button>
         <Button color="primary" onClick={importButtonHandler}>{t('import')}</Button>
       </div>
     </form>

+ 2 - 3
apps/app/src/components/PageHeader/PageHeader.tsx

@@ -17,13 +17,12 @@ export const PageHeader: FC = () => {
   }
 
   return (
-    <div className={moduleClass}>
+    <div className={`${moduleClass} w-100`}>
       <PagePathHeader
         currentPage={currentPage}
       />
-      <div className="row mt-1">
+      <div className="mt-1">
         <PageTitleHeader
-          className="col"
           currentPage={currentPage}
         />
       </div>

+ 4 - 3
apps/app/src/components/PageHeader/PagePathHeader.module.scss

@@ -1,10 +1,11 @@
 .page-path-header :global {
+  max-width: calc(100vw - 650px);
   input {
     min-width: 20px;
     min-height: unset;
-    padding-top: 0;
-    padding-bottom: 0;
-    line-height: 1em;
+    padding-top: 2px;
+    padding-bottom: 2px;
+    line-height: 1.2em;
   }
 
   .page-path-header-buttons {

+ 45 - 28
apps/app/src/components/PageHeader/PagePathHeader.tsx

@@ -1,4 +1,6 @@
-import { useState, useEffect, useCallback } from 'react';
+import {
+  useState, useCallback, memo,
+} from 'react';
 import type { FC } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
@@ -21,12 +23,13 @@ const moduleClass = styles['page-path-header'];
 
 
 type Props = {
-  currentPage: IPagePopulatedToShowRevision
+  currentPage: IPagePopulatedToShowRevision,
+  className?: string,
 }
 
-export const PagePathHeader: FC<Props> = (props) => {
+export const PagePathHeader: FC<Props> = memo((props: Props) => {
   const { t } = useTranslation();
-  const { currentPage } = props;
+  const { currentPage, className } = props;
 
   const dPagePath = new DevidedPagePath(currentPage.path, true);
   const parentPagePath = dPagePath.former;
@@ -37,6 +40,8 @@ export const PagePathHeader: FC<Props> = (props) => {
   const [isHover, setHover] = useState(false);
   const [editingParentPagePath, setEditingParentPagePath] = useState(parentPagePath);
 
+  // const [isIconHidden, setIsIconHidden] = useState(false);
+
   const { data: PageSelectModalData, open: openPageSelectModal } = usePageSelectModal();
   const isOpened = PageSelectModalData?.isOpened ?? false;
 
@@ -71,22 +76,25 @@ export const PagePathHeader: FC<Props> = (props) => {
     setRenameInputShown(true);
   }, [parentPagePath]);
 
-  const clickOutSideHandler = useCallback((e) => {
-    const container = document.getElementById('page-path-header');
-
-    if (container && !container.contains(e.target)) {
-      setRenameInputShown(false);
-    }
-  }, []);
-
-  useEffect(() => {
-    document.addEventListener('click', clickOutSideHandler);
-
-    return () => {
-      document.removeEventListener('click', clickOutSideHandler);
-    };
-  }, [clickOutSideHandler]);
-
+  // TODO: https://redmine.weseek.co.jp/issues/141062
+  // Truncate left side and don't use getElementById
+  //
+  // useEffect(() => {
+  //   const areaElem = document.getElementById('grw-page-path-header-container');
+  //   const linkElem = document.getElementById('grw-page-path-hierarchical-link');
+
+  //   const areaElemWidth = areaElem?.offsetWidth;
+  //   const linkElemWidth = linkElem?.offsetWidth;
+
+  //   if (areaElemWidth && linkElemWidth) {
+  //     setIsIconHidden(linkElemWidth > areaElemWidth);
+  //   }
+  //   else {
+  //     setIsIconHidden(false);
+  //   }
+  // }, [currentPage]);
+  //
+  // const styles: CSSProperties | undefined = isIconHidden ? { direction: 'rtl' } : undefined;
 
   if (dPagePath.isRoot) {
     return <></>;
@@ -95,27 +103,36 @@ export const PagePathHeader: FC<Props> = (props) => {
   return (
     <div
       id="page-path-header"
-      className={`d-flex ${moduleClass} small`}
+      className={`d-flex ${moduleClass} ${className ?? ''} small position-relative ms-2`}
       onMouseEnter={() => setHover(true)}
       onMouseLeave={() => setHover(false)}
     >
-      <div className="me-2">
+      <div
+        id="grw-page-path-header-container"
+        className="me-2 d-inline-block overflow-hidden"
+      >
         { isRenameInputShown && (
-          <div className="position-absolute">
+          <div className="position-absolute w-100">
             <ClosableTextInput
-              useAutosizeInput
               value={editingParentPagePath}
-              placeholder={t('Input page name')}
+              placeholder={t('Input parent page path')}
               inputClassName="form-control-sm"
               onPressEnter={onPressEnter}
               onPressEscape={onPressEscape}
               onChange={onInputChange}
               validationTarget={ValidationTarget.PAGE}
+              onClickOutside={onPressEscape}
             />
           </div>
         ) }
-        <div className={`${isRenameInputShown ? 'invisible' : ''}`}>
-          <PagePathHierarchicalLink linkedPagePath={linkedPagePath} />
+        <div
+          className={`${isRenameInputShown ? 'invisible' : ''} text-truncate`}
+          // style={styles}
+        >
+          <PagePathHierarchicalLink
+            linkedPagePath={linkedPagePath}
+            // isIconHidden={isIconHidden}
+          />
         </div>
       </div>
 
@@ -140,4 +157,4 @@ export const PagePathHeader: FC<Props> = (props) => {
       {isOpened && <PageSelectModal />}
     </div>
   );
-};
+});

+ 12 - 1
apps/app/src/components/PageHeader/PageTitleHeader.module.scss

@@ -1,9 +1,20 @@
 .page-title-header :global {
+  max-width: calc(100vw - 650px);
   input {
     min-width: 20px;
     min-height: unset;
     padding: 0 0.5rem;
     line-height: 1em;
-    transform: translateX(-0.55rem) translateY(-0.05rem);
+    transform: translateX(0.05rem) translateY(0.05rem);
+  }
+}
+
+.page-title-header-border-color {
+  --bs-border-color: transparent;
+
+  &:global {
+    &:hover {
+      --bs-border-color: var(--bs-primary-border-subtle);
+    }
   }
 }

+ 21 - 7
apps/app/src/components/PageHeader/PageTitleHeader.tsx

@@ -6,6 +6,7 @@ import nodePath from 'path';
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
 import { pathUtils } from '@growi/core/dist/utils';
+import { isMovablePage } from '@growi/core/dist/utils/page-path-utils';
 import { useTranslation } from 'next-i18next';
 
 import { ValidationTarget } from '~/client/util/input-validator';
@@ -17,7 +18,8 @@ import { usePagePathRenameHandler } from '../PageEditor/page-path-rename-utils';
 
 import styles from './PageTitleHeader.module.scss';
 
-const moduleClass = styles['page-title-header'];
+const moduleClass = styles['page-title-header'] ?? '';
+const borderColorClass = styles['page-title-header-border-color'] ?? '';
 
 type Props = {
   currentPage: IPagePopulatedToShowRevision,
@@ -30,6 +32,8 @@ export const PageTitleHeader: FC<Props> = (props) => {
 
   const currentPagePath = currentPage.path;
 
+  const isMovable = isMovablePage(currentPagePath);
+
   const dPagePath = new DevidedPagePath(currentPage.path, true);
   const pageTitle = dPagePath.latter;
 
@@ -72,9 +76,13 @@ export const PageTitleHeader: FC<Props> = (props) => {
   }, [currentPagePath]);
 
   const onClickPageTitle = useCallback(() => {
+    if (!isMovable) {
+      return;
+    }
+
     setEditedPagePath(currentPagePath);
     setRenameInputShown(true);
-  }, [currentPagePath]);
+  }, [currentPagePath, isMovable]);
 
   useEffect(() => {
     if (isNewlyCreatedPage) {
@@ -83,10 +91,10 @@ export const PageTitleHeader: FC<Props> = (props) => {
   }, [currentPage._id, isNewlyCreatedPage]);
 
   return (
-    <div className={`d-flex align-items-center ${moduleClass} ${props.className ?? ''}`}>
-      <div className="me-1">
+    <div className={`d-flex ${moduleClass} ${props.className ?? ''} position-relative`}>
+      <div className="me-1 d-inline-block overflow-hidden">
         { isRenameInputShown && (
-          <div className="position-absolute">
+          <div className="position-absolute w-100">
             <ClosableTextInput
               useAutosizeInput
               value={isNewlyCreatedPage ? '' : editedPageTitle}
@@ -100,7 +108,13 @@ export const PageTitleHeader: FC<Props> = (props) => {
             />
           </div>
         ) }
-        <h1 className={`mb-0 fs-4 ${isRenameInputShown ? 'invisible' : ''}`} onClick={onClickPageTitle}>
+        <h1
+          className={`mb-0 px-2 fs-4
+            ${isRenameInputShown ? 'invisible' : ''} text-truncate
+            ${isMovable ? 'border border-2 rounded-2' : ''} ${borderColorClass}
+          `}
+          onClick={onClickPageTitle}
+        >
           {pageTitle}
         </h1>
       </div>
@@ -114,7 +128,7 @@ export const PageTitleHeader: FC<Props> = (props) => {
           pageId={currentPage._id}
           pagePath={currentPage.path}
           dropdownToggleId={`copydropdown-${currentPage._id}`}
-          dropdownToggleClassName="ms-2 p-1"
+          dropdownToggleClassName="p-1"
         >
           <span className="material-symbols-outlined fs-6">content_paste</span>
         </CopyDropdown>

+ 14 - 12
apps/app/src/components/PageHistory/PageRevisionTable.tsx

@@ -56,6 +56,7 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
 
   useEffect(() => {
     if (revisions != null) {
+      // when both source and target are specified
       if (sourceRevisionId != null && targetRevisionId != null) {
         const sourceRevision = revisions.filter(revision => revision._id === sourceRevisionId)[0];
         const targetRevision = revisions.filter(revision => revision._id === targetRevisionId)[0];
@@ -63,11 +64,10 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
         setTargetRevision(targetRevision);
       }
       else {
-        const latestRevision = revisions != null ? revisions[0] : null;
-        if (latestRevision != null) {
-          setSourceRevision(latestRevision);
-          setTargetRevision(latestRevision);
-        }
+        const latestRevision = revisions != null ? revisions[0] : undefined;
+        const previousRevision = revisions.length >= 2 ? revisions[1] : latestRevision;
+        setTargetRevision(latestRevision);
+        setSourceRevision(previousRevision);
       }
     }
   }, [revisions, sourceRevisionId, targetRevisionId]);
@@ -210,13 +210,15 @@ export const PageRevisionTable = (props: PageRevisionTableProps): JSX.Element =>
       </table>
 
       {sourceRevision != null && targetRevision != null && (
-        <RevisionComparer
-          sourceRevision={sourceRevision}
-          targetRevision={targetRevision}
-          currentPageId={currentPageId}
-          currentPagePath={currentPagePath}
-          onClose={onClose}
-        />
+        <div className="mt-5">
+          <RevisionComparer
+            sourceRevision={sourceRevision}
+            targetRevision={targetRevision}
+            currentPageId={currentPageId}
+            currentPagePath={currentPagePath}
+            onClose={onClose}
+          />
+        </div>
       )
       }
     </>

+ 2 - 21
apps/app/src/components/PageHistory/RevisionDiff.module.scss

@@ -1,28 +1,9 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
 .revision-diff-container :global {
-  .comparison-header {
-    height: 34px;
-    background-color: #ffffff;
-    border: 1px solid bs.$gray-300;
-    .comparison-source-wrapper {
-      height: 26px;
-      margin-right: 1px;
-      border-right: 1px solid bs.$gray-300;
-      .comparison-source {
-        color: bs.$gray-500;
-      }
-    }
-    .comparison-target-wrapper {
-      height: 26px;
-      .comparison-target {
-        color: bs.$gray-500;
-      }
-    }
+  .link-created-at {
+    text-decoration-line: underline;
   }
 
   .revision-history-diff {
-    color: bs.$gray-900;
     table-layout: fixed;
 
     // revision-history

+ 48 - 26
apps/app/src/components/PageHistory/RevisionDiff.tsx

@@ -1,14 +1,17 @@
-import React from 'react';
+import { useMemo } from 'react';
 
 import type { IRevisionHasPageId } from '@growi/core';
 import { returnPathForURL } from '@growi/core/dist/utils/path-utils';
 import { createPatch } from 'diff';
 import type { Diff2HtmlConfig } from 'diff2html';
 import { html } from 'diff2html';
+import { ColorSchemeType } from 'diff2html/lib/types';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 import urljoin from 'url-join';
 
+import { Themes, useNextThemes } from '~/stores/use-next-themes';
+
 import UserDate from '../User/UserDate';
 
 import styles from './RevisionDiff.module.scss';
@@ -31,6 +34,19 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
     currentRevision, previousRevision, revisionDiffOpened, currentPageId, currentPagePath, onClose,
   } = props;
 
+  const { theme } = useNextThemes();
+
+  const colorScheme: ColorSchemeType = useMemo(() => {
+    switch (theme) {
+      case Themes.DARK:
+        return ColorSchemeType.DARK;
+      case Themes.LIGHT:
+        return ColorSchemeType.LIGHT;
+      default:
+        return ColorSchemeType.AUTO;
+    }
+  }, [theme]);
+
   const previousText = (currentRevision._id === previousRevision._id) ? '' : previousRevision.body;
 
   const patch = createPatch(
@@ -42,6 +58,7 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
   const option: Diff2HtmlConfig = {
     outputFormat: 'side-by-side',
     drawFileList: false,
+    colorScheme,
   };
 
   const diffViewHTML = revisionDiffOpened ? html(patch, option) : '';
@@ -50,34 +67,39 @@ export const RevisionDiff = (props: RevisioinDiffProps): JSX.Element => {
 
   return (
     <div className={`${styles['revision-diff-container']}`}>
-      <div className="comparison-header">
-        <div className="container pt-1 pe-0">
-          <div className="row">
-            <div className="col comparison-source-wrapper pt-1 px-0">
-              <span className="comparison-source pe-3">{t('page_history.comparing_source')}</span><UserDate dateTime={previousRevision.createdAt} />
-              <Link
-                href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${previousRevision._id}`)}
-                className="ms-3"
-                onClick={onClose}
-                prefetch={false}
-              >
-                <span className="material-symbols-outlined">login</span>
-              </Link>
-            </div>
-            <div className="col comparison-target-wrapper pt-1">
-              <span className="comparison-target pe-3">{t('page_history.comparing_target')}</span><UserDate dateTime={currentRevision.createdAt} />
-              <Link
-                href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${currentRevision._id}`)}
-                className="ms-3"
-                onClick={onClose}
-                prefetch={false}
-              >
-                <span className="material-symbols-outlined">login</span>
-              </Link>
-            </div>
+      <div className="container">
+        <div className="row mt-2">
+          <div className="col px-0 py-2">
+            <span className="fw-bold">{t('page_history.comparing_source')}</span>
+            <Link
+              href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${previousRevision._id}`)}
+              className="small ms-2
+                link-created-at
+                link-secondary link-opacity-75 link-opacity-100-hover
+                link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
+              onClick={onClose}
+              prefetch={false}
+            >
+              <UserDate dateTime={previousRevision.createdAt} />
+            </Link>
+          </div>
+          <div className="col px-0 py-2">
+            <span className="fw-bold">{t('page_history.comparing_target')}</span>
+            <Link
+              href={urljoin(returnPathForURL(currentPagePath, currentPageId), `?revisionId=${currentRevision._id}`)}
+              className="small ms-2
+                link-created-at
+                link-secondary link-opacity-75 link-opacity-100-hover
+                link-offset-2 link-underline-opacity-25 link-underline-opacity-100-hover"
+              onClick={onClose}
+              prefetch={false}
+            >
+              <UserDate dateTime={currentRevision.createdAt} />
+            </Link>
           </div>
         </div>
       </div>
+      {/* eslint-disable-next-line react/no-danger */}
       <div className="revision-history-diff pb-1" dangerouslySetInnerHTML={diffView} />
     </div>
   );

+ 0 - 5
apps/app/src/components/PageStatusAlert.module.scss

@@ -15,11 +15,6 @@
     }
     .grw-card-btn-container {
       text-align: center;
-
-      .btn {
-        // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-        // @include bs.button-size(bs.$btn-padding-y-lg, bs.$btn-padding-x-lg, bs.$btn-font-size-lg, bs.$btn-line-height-lg, bs.$btn-border-radius-lg);
-      }
     }
   }
 

+ 22 - 7
apps/app/src/components/ReactMarkdownComponents/LightBox.tsx

@@ -1,19 +1,34 @@
-import React, { useState } from 'react';
+import type { DetailedHTMLProps, ImgHTMLAttributes } from 'react';
+import React, { useMemo, useState } from 'react';
 
 import FsLightbox from 'fslightbox-react';
+import { createPortal } from 'react-dom';
 
-export const LightBox = (props) => {
+type Props = DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>
+
+export const LightBox = (props: Props): JSX.Element => {
   const [toggler, setToggler] = useState(false);
-  const { node, ...rest } = props;
+  const { alt, ...rest } = props;
 
-  return (
-    <>
-      <img {...rest} onClick={() => setToggler(!toggler)} />
+  const lightboxPortal = useMemo(() => {
+    return createPortal(
       <FsLightbox
         toggler={toggler}
         sources={[props.src]}
+        alt={alt}
         type="image"
-      />
+        exitFullscreenOnClose
+      />,
+      document.body,
+    );
+  }, [alt, props.src, toggler]);
+
+  return (
+    <>
+      {/* eslint-disable-next-line @next/next/no-img-element */}
+      <img alt={alt} {...rest} onClick={() => setToggler(!toggler)} />
+
+      {lightboxPortal}
     </>
   );
 };

+ 8 - 0
apps/app/src/components/RevisionComparer/RevisionComparer.module.scss

@@ -1,3 +1,7 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '@growi/ui/scss/atoms/btn-muted';
+
 .revision-compare :global {
   .revision-compare-container {
     min-height: 100px;
@@ -13,6 +17,10 @@
   }
 
   .grw-copy-dropdown {
+    .btn.btn-copy {
+      @include btn-muted.colorize(bs.$gray-500);
+    }
+
     .dropdown-menu {
       min-width: 310px;
 

+ 26 - 26
apps/app/src/components/RevisionComparer/RevisionComparer.tsx

@@ -66,33 +66,33 @@ export const RevisionComparer = (props: RevisionComparerProps): JSX.Element => {
     <div className={`${styles['revision-compare']} revision-compare`}>
       <div className="d-flex">
         <h4 className="align-self-center">{ t('page_history.comparing_revisions') }</h4>
-        <Dropdown
-          className="grw-copy-dropdown align-self-center ms-auto"
-          isOpen={dropdownOpen}
-          toggle={() => toggleDropdown()}
-        >
-          <DropdownToggle
-            caret
-            className="d-block text-muted bg-transparent btn-copy border-0 py-0"
+
+        { !isNodiff && (
+          <Dropdown
+            className="grw-copy-dropdown align-self-center ms-auto"
+            isOpen={dropdownOpen}
+            toggle={() => toggleDropdown()}
           >
-            <span className="material-symbols-outlined">content_paste</span>
-          </DropdownToggle>
-          <DropdownMenu strategy="fixed" end>
-            {/* Page path URL */}
-            <CopyToClipboard text={generateURL(currentPagePath)}>
-              <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Page URL', { ns: 'commons' })} contents={generateURL(currentPagePath)} />
-              </DropdownItem>
-            </CopyToClipboard>
-            {/* Permanent Link URL */}
-            <CopyToClipboard text={generateURL(currentPageId)}>
-              <DropdownItem className="px-3">
-                <DropdownItemContents title={t('copy_to_clipboard.Permanent link', { ns: 'commons' })} contents={generateURL(currentPageId)} />
-              </DropdownItem>
-            </CopyToClipboard>
-            <DropdownItem divider className="my-0"></DropdownItem>
-          </DropdownMenu>
-        </Dropdown>
+            <DropdownToggle className="btn-copy">
+              <span className="material-symbols-outlined">content_paste</span>
+            </DropdownToggle>
+            <DropdownMenu strategy="fixed" end>
+              {/* Page path URL */}
+              <CopyToClipboard text={generateURL(currentPagePath)}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Page URL', { ns: 'commons' })} contents={generateURL(currentPagePath)} />
+                </DropdownItem>
+              </CopyToClipboard>
+              {/* Permanent Link URL */}
+              <CopyToClipboard text={generateURL(currentPageId)}>
+                <DropdownItem className="px-3">
+                  <DropdownItemContents title={t('copy_to_clipboard.Permanent link', { ns: 'commons' })} contents={generateURL(currentPageId)} />
+                </DropdownItem>
+              </CopyToClipboard>
+              <DropdownItem divider className="my-0"></DropdownItem>
+            </DropdownMenu>
+          </Dropdown>
+        ) }
       </div>
 
       <div className={`revision-compare-container ${isNodiff ? 'nodiff' : ''}`}>

Неке датотеке нису приказане због велике количине промена