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

Merge branch 'master' into fix/141824-143080-correct-update-bookmark-button-clicked

ryoji-s 2 лет назад
Родитель
Сommit
c9c4de9614
100 измененных файлов с 966 добавлено и 770 удалено
  1. 0 1
      .github/dependabot.yml
  2. 0 0
      .github/workflows-archived/release-rc-v7.yml
  3. 1 1
      .github/workflows/auto-approve.yml
  4. 3 3
      .github/workflows/ci-app-prod.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. 0 1
      .github/workflows/reusable-app-build-image.yml
  9. 126 1
      CHANGELOG.md
  10. 0 6
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  11. 1 1
      apps/app/docker/README.md
  12. 1 6
      apps/app/docker/codebuild/buildspec.yml
  13. 0 1
      apps/app/next-env.d.ts
  14. 1 1
      apps/app/package.json
  15. 3 0
      apps/app/public/static/locales/en_US/translation.json
  16. 3 0
      apps/app/public/static/locales/ja_JP/translation.json
  17. 3 0
      apps/app/public/static/locales/zh_CN/translation.json
  18. 10 10
      apps/app/resource/locales/en_US/sandbox-bootstrap5.md
  19. 1 1
      apps/app/resource/locales/en_US/welcome.md
  20. 2 2
      apps/app/resource/locales/ja_JP/sandbox.md
  21. 1 1
      apps/app/resource/locales/ja_JP/welcome.md
  22. 2 2
      apps/app/src/components/Admin/App/AppSetting.jsx
  23. 3 3
      apps/app/src/components/Admin/Security/SecurityManagementContents.jsx
  24. 40 24
      apps/app/src/components/Admin/SlackIntegration/BotTypeCard.tsx
  25. 19 23
      apps/app/src/components/Admin/SlackIntegration/Bridge.tsx
  26. 0 57
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.jsx
  27. 59 0
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxyConnectionStatus.tsx
  28. 3 3
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  29. 0 57
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.jsx
  30. 57 0
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxyConnectionStatus.tsx
  31. 2 2
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  32. 3 3
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  33. 21 19
      apps/app/src/components/Admin/SlackIntegration/DeleteSlackBotSettingsModal.tsx
  34. 3 3
      apps/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx
  35. 1 2
      apps/app/src/components/Admin/SlackIntegration/SlackAppIntegrationControl.tsx
  36. 12 12
      apps/app/src/components/Admin/SlackIntegration/SlackIntegration.tsx
  37. 5 5
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  38. 4 0
      apps/app/src/components/Admin/SlackIntegration/slack-integration-util.ts
  39. 0 20
      apps/app/src/components/Admin/SlackIntegration/slak-integration-util.js
  40. 8 1
      apps/app/src/components/Admin/UserGroup/UserGroupTable.tsx
  41. 6 2
      apps/app/src/components/Common/PagePathHierarchicalLink/PagePathHierarchicalLink.tsx
  42. 10 0
      apps/app/src/components/CompleteUserRegistrationForm.module.scss
  43. 42 18
      apps/app/src/components/CompleteUserRegistrationForm.tsx
  44. 4 4
      apps/app/src/components/DataTransferForm.tsx
  45. 10 0
      apps/app/src/components/InstallerForm.module.scss
  46. 34 21
      apps/app/src/components/InstallerForm.tsx
  47. 1 2
      apps/app/src/components/InvitedForm.tsx
  48. 9 8
      apps/app/src/components/Layout/Admin.module.scss
  49. 28 118
      apps/app/src/components/Layout/NoLoginLayout.module.scss
  50. 11 5
      apps/app/src/components/Layout/NoLoginLayout.tsx
  51. 83 0
      apps/app/src/components/LoginForm.module.scss
  52. 80 85
      apps/app/src/components/LoginForm.tsx
  53. 7 7
      apps/app/src/components/Me/AssociateModal.tsx
  54. 2 2
      apps/app/src/components/Me/UISettings.tsx
  55. 1 0
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  56. 8 7
      apps/app/src/components/PageControls/PageControls.tsx
  57. 1 1
      apps/app/src/components/PageEditor/EditorNavbar/EditorNavbar.tsx
  58. 1 0
      apps/app/src/components/PageEditor/EditorNavbarBottom.module.scss
  59. 6 2
      apps/app/src/components/PageEditor/PageEditorReadOnly.tsx
  60. 1 1
      apps/app/src/components/PageHeader/PageHeader.tsx
  61. 1 0
      apps/app/src/components/PageHeader/PagePathHeader.module.scss
  62. 43 27
      apps/app/src/components/PageHeader/PagePathHeader.tsx
  63. 1 0
      apps/app/src/components/PageHeader/PageTitleHeader.module.scss
  64. 4 5
      apps/app/src/components/PageHeader/PageTitleHeader.tsx
  65. 26 8
      apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx
  66. 4 0
      apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss
  67. 17 2
      apps/app/src/components/Sidebar/AppTitle/AppTitle.tsx
  68. 5 4
      apps/app/src/pages/admin/slack-integration.page.tsx
  69. 4 3
      apps/app/src/pages/installer.page.tsx
  70. 6 1
      apps/app/src/pages/login/index.module.scss
  71. 7 0
      apps/app/src/server/crowi/index.js
  72. 9 1
      apps/app/src/server/routes/apiv3/page/update-page.ts
  73. 3 6
      apps/app/src/server/service/socket-io.js
  74. 35 5
      apps/app/src/server/service/yjs-connection-manager.ts
  75. 1 1
      apps/app/src/styles/_fonts.scss
  76. 0 64
      apps/app/src/styles/atoms/_buttons.scss
  77. 14 0
      apps/app/src/styles/atoms/placeholders/_buttons.scss
  78. 0 1
      apps/app/src/styles/style-app.scss
  79. 1 1
      apps/slackbot-proxy/package.json
  80. 1 1
      package.json
  81. 1 1
      packages/core/package.json
  82. 1 0
      packages/custom-icons/svg/facebook.svg
  83. 1 0
      packages/custom-icons/svg/github.svg
  84. 1 0
      packages/custom-icons/svg/google.svg
  85. 1 0
      packages/custom-icons/svg/openid.svg
  86. 1 0
      packages/custom-icons/svg/slack.svg
  87. 1 1
      packages/editor/package.json
  88. 2 1
      packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx
  89. 2 0
      packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx
  90. 17 15
      packages/editor/src/services/paste-util/paste-markdown-util.ts
  91. 1 1
      packages/presentation/package.json
  92. 1 1
      packages/preset-templates/package.json
  93. 1 1
      packages/preset-themes/package.json
  94. 1 1
      packages/remark-attachment-refs/package.json
  95. 1 1
      packages/remark-drawio/package.json
  96. 1 1
      packages/remark-growi-directive/package.json
  97. 1 1
      packages/remark-lsx/package.json
  98. 1 1
      packages/slack/package.json
  99. 1 0
      packages/slack/src/interfaces/index.ts
  100. 1 1
      packages/ui/package.json

+ 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
 

+ 0 - 0
.github/workflows/release-rc-v7.yml → .github/workflows-archived/release-rc-v7.yml


+ 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()
 

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

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

@@ -52,5 +52,4 @@ jobs:
         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 }}" }
         ]'

+ 126 - 1
CHANGELOG.md

@@ -1,9 +1,134 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.3.1...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v7.0.0...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [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 - 6
apps/app/_obsolete/src/styles/theme/apply-colors.scss

@@ -402,12 +402,6 @@ ul.pagination {
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
 }
 
-.admin-bot-card {
-  .grw-botcard-title-active {
-    color: $gray-200;
-  }
-}
-
 /*
  * Form Slider
  */

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

@@ -11,7 +11,7 @@ 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)
+* [`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)
 

+ 1 - 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,14 @@ 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}
+      - docker tag ${IMAGE_TAG}
 
   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.

+ 1 - 1
apps/app/package.json

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

+ 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的兼容性",

+ 10 - 10
apps/app/resource/locales/en_US/sandbox-bootstrap5.md

@@ -120,19 +120,19 @@
 
 
 # 4. Colors
-## 背景颜色
+## Contextual colors
 <p class="text-primary">Look, I'm in a well!</p>
 <p class="text-warning">Look, I'm in a well!</p>
 <p class="text-danger">Look, I'm in a well!</p>
 
-## 背景情况
+## Contextual backgrounds
 <p class="text-danger bg-primary">Look, I'm in a well!</p>
 <p class="text-primary bg-warning">Look, I'm in a well!</p>
 <p class="text-warning bg-danger">Look, I'm in a well!</p>
 
 
 # 5. Collapse
-## 显示内容
+## Displaying content
 <a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
   Show content
 </a>
@@ -146,7 +146,7 @@
   </div>
 </div>
 
-## 隐藏内容
+## Hiding content
 <a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
   Hide content
 </a>
@@ -161,9 +161,9 @@
 </div>
 
 
-# 官方文件
-- [点击此处了解徽章详情](https://getbootstrap.com/docs/5.3/components/badge/)
-- [单击此处了解警报详情](https://getbootstrap.com/docs/5.3/components/alerts/)
-- [点击此处了解贺卡详情](https://getbootstrap.com/docs/5.3/components/card/)
-- [点击此处了解颜色详情](https://getbootstrap.com/docs/5.3/utilities/colors/)
-- [点击此处查看折叠详情](https://getbootstrap.com/docs/5.3/components/collapse/)
+# Official docs
+- [Click here for Badges details](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [Click here for Alerts details](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [Click here for Cards details](https://getbootstrap.jp/docs/5.3/components/card/)
+- [Click here for Colors details](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [Click here for Collapse details](https://getbootstrap.jp/docs/5.3/components/collapse/)

+ 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

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

@@ -166,7 +166,7 @@
 - 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます
 
 #### 活用例
-Bootstrap によるページの装飾方法の記述方法は [[こちらをご確認ください>./1. ページの装飾方法(Bootstrap)]]
+Bootstrap によるページの装飾方法の記述方法は [[こちらをご確認ください>./Bootstrap5]]
 
 
 # :memo:画像の挿入
@@ -297,6 +297,6 @@ ___
 # :memo:さらに応用的な表現
 - [ページの装飾方法(Bootstrap5)](/Sandbox/Bootstrap5)
 
-- [図形の表現方法(Daigrams)](/Sandbox/Daigrams)
+- [図形の表現方法(Diagrams)](/Sandbox/Diagrams)
 
 - [数式の表現方法(Math)](/Sandbox/Math)

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

+ 3 - 3
apps/app/src/components/Admin/Security/SecurityManagementContents.jsx

@@ -46,15 +46,15 @@ const SecurityManagementContents = () => {
         i18n: 'OIDC',
       },
       passport_google: {
-        Icon: () => <i className="fa fa-google" />,
+        Icon: () => <span className="growi-custom-icons align-bottom">google</span>,
         i18n: 'Google',
       },
       passport_github: {
-        Icon: () => <i className="fa fa-github" />,
+        Icon: () => <span className="growi-custom-icons align-bottom">github</span>,
         i18n: 'GitHub',
       },
       // passport_facebook: {
-      //   Icon: () => <i className="fa fa-facebook" />,
+      //   Icon: () => <span className="growi-custom-icons align-bottom">facebook</span>,
       //   i18n: '(TBD) Facebook',
       // },
     };

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

+ 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 && (

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

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

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

@@ -1,3 +1,7 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+@use '~/styles/atoms/placeholders/buttons';
+
 .login-form :global {
   // To adjust the behavior, this problem is not solved.
   // See https://github.com/AaronCCWong/react-card-flip/issues/56
@@ -15,4 +19,83 @@
     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)};
+    }
+  }
 }

+ 80 - 85
apps/app/src/components/LoginForm.tsx

@@ -18,6 +18,8 @@ import { CompleteUserRegistration } from './CompleteUserRegistration';
 
 import styles from './LoginForm.module.scss';
 
+const moduleClass = styles['login-form'];
+
 type LoginFormProps = {
   username?: string,
   name?: string,
@@ -196,31 +198,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 +235,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>
@@ -264,62 +266,54 @@ export const LoginForm = (props: LoginFormProps): JSX.Element => {
 
 
   const renderExternalAuthInput = useCallback((auth) => {
-    const authIconNames = {
-      google: 'google',
-      github: 'github',
-      facebook: 'facebook',
-      oidc: 'openid',
-      saml: 'key',
+    const authIcon = {
+      google: <span className="growi-custom-icons align-bottom">google</span>,
+      github: <span className="growi-custom-icons align-bottom">github</span>,
+      facebook: <span className="growi-custom-icons align-bottom">facebook</span>,
+      oidc: <span className="growi-custom-icons align-bottom">openid</span>,
+      saml: <span className="material-symbols-outlined align-bottom">key</span>,
+    };
+    const authBtn = `btn-auth-${auth}`;
+    const signin = {
+      google: 'Google',
+      github: 'GitHub',
+      facebook: 'Facebook',
+      oidc: 'OIDC',
+      saml: 'SAML',
     };
 
     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">
-            <i className={`fa fa-${authIconNames[auth]}`}></i>
-          </span>
-          <span className="btn-label-text">{t('Sign in')}</span>
-        </button>
-        <div className="small text-end">by {auth} Account</div>
-      </div>
+      <button
+        key={`btn-auth-${auth}`}
+        type="button"
+        className={`btn btn-secondary ${authBtn} my-2 col-10 col-sm-7 mx-auto d-flex`}
+        onClick={handleLoginWithExternalAuth}
+      >
+        <span>{authIcon[auth]}</span>
+        <span className="flex-grow-1">{t('Sign in with External auth', { signin: signin[auth] })}</span>
+      </button>
     );
   }, [handleLoginWithExternalAuth, t]);
 
   const renderExternalAuthLoginForm = useCallback(() => {
-    const { isLocalStrategySetup, isLdapStrategySetup, objOfIsExternalAuthEnableds } = props;
-    const isExternalAuthCollapsible = isLocalStrategySetup || isLdapStrategySetup;
-    const collapsibleClass = isExternalAuthCollapsible ? 'collapse collapse-external-auth' : '';
+    const { objOfIsExternalAuthEnableds } = props;
 
     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">
+          {Object.keys(objOfIsExternalAuthEnableds).map((auth) => {
+            if (!objOfIsExternalAuthEnableds[auth]) {
+              return;
+            }
+            return renderExternalAuthInput(auth);
+          })}
         </div>
       </>
     );
-  }, [props, renderExternalAuthInput]);
+  }, [props, t, renderExternalAuthInput]);
 
   const resetRegisterErrors = useCallback(() => {
     if (registerErrors.length === 0) return;
@@ -420,13 +414,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 +432,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 +450,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 +484,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 +504,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 +544,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 +587,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>

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

@@ -63,28 +63,28 @@ const AssociateModal = (props: Props): JSX.Element => {
         <div>
           <Nav tabs className="mb-2">
             <NavLink
-              className={activeTab === 1 ? 'active' : ''}
+              className={`${activeTab === 1 ? 'active' : ''} d-flex gap-1 align-items-center`}
               onClick={() => setActiveTab(1)}
             >
               <span className="material-symbols-outlined fs-5">network_node</span> LDAP
             </NavLink>
             <NavLink
-              className={activeTab === 2 ? 'active' : ''}
+              className={`${activeTab === 2 ? 'active' : ''} d-flex gap-1 align-items-center`}
               onClick={() => setActiveTab(2)}
             >
-              <i className="fa fa-github"></i> (TBD) GitHub
+              <span className="growi-custom-icons">github</span> (TBD) GitHub
             </NavLink>
             <NavLink
-              className={activeTab === 3 ? 'active' : ''}
+              className={`${activeTab === 3 ? 'active' : ''} d-flex gap-1 align-items-center`}
               onClick={() => setActiveTab(3)}
             >
-              <i className="fa fa-google"></i> (TBD) Google OAuth
+              <span className="growi-custom-icons">google</span> (TBD) Google OAuth
             </NavLink>
             {/* <NavLink
-              className={activeTab === 4 ? 'active' : ''}
+              className={`${activeTab === 4 ? 'active' : ''} d-flex gap-1 align-items-center`}
               onClick={() => setActiveTab(4)}
             >
-              <i className="fa fa-facebook"></i> (TBD) Facebook
+              <span className="growi-custom-icons">facebook</span> (TBD) Facebook
             </NavLink> */}
           </Nav>
           <TabContent activeTab={activeTab}>

+ 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

+ 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 - 1
apps/app/src/components/PageEditor/EditorNavbar/EditorNavbar.tsx

@@ -12,7 +12,7 @@ export const EditorNavbar = (): JSX.Element => {
   const { data: editingUsers } = useEditingUsers();
 
   return (
-    <div className={`${moduleClass} d-flex justify-content-between px-4 py-1`}>
+    <div className={`${moduleClass} d-flex justify-content-between px-4 py-1 ms-3`}>
       <PageHeader />
       <EditingUserList
         userList={editingUsers?.userList ?? []}

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

+ 6 - 2
apps/app/src/components/PageEditor/PageEditorReadOnly.tsx

@@ -11,7 +11,11 @@ import { EditorNavbar } from './EditorNavbar';
 import Preview from './Preview';
 import { useScrollSync } from './ScrollSyncHelper';
 
-export const PageEditorReadOnly = react.memo((): JSX.Element => {
+type Props = {
+  visibility?: boolean,
+}
+
+export const PageEditorReadOnly = react.memo(({ visibility }: Props): JSX.Element => {
   const previewRef = useRef<HTMLDivElement>(null);
 
   const { data: currentPage } = useSWRxCurrentPage();
@@ -30,7 +34,7 @@ export const PageEditorReadOnly = react.memo((): JSX.Element => {
   }
 
   return (
-    <div id="page-editor" className="flex-expand-vert">
+    <div id="page-editor" className={`flex-expand-vert ${visibility ? '' : 'd-none'}`}>
       <EditorNavbar />
 
       <div className="flex-expand-horiz">

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

@@ -17,7 +17,7 @@ export const PageHeader: FC = () => {
   }
 
   return (
-    <div className={moduleClass}>
+    <div className={`${moduleClass} w-100`}>
       <PagePathHeader
         currentPage={currentPage}
       />

+ 1 - 0
apps/app/src/components/PageHeader/PagePathHeader.module.scss

@@ -1,4 +1,5 @@
 .page-path-header :global {
+  max-width: calc(100vw - 650px);
   input {
     min-width: 20px;
     min-height: unset;

+ 43 - 27
apps/app/src/components/PageHeader/PagePathHeader.tsx

@@ -1,5 +1,7 @@
-import { useState, useEffect, useCallback } from 'react';
-import type { FC } from 'react';
+import {
+  useState, useEffect, useCallback, memo, useMemo,
+} from 'react';
+import type { CSSProperties, FC } from 'react';
 
 import type { IPagePopulatedToShowRevision } from '@growi/core';
 import { DevidedPagePath } from '@growi/core/dist/models';
@@ -24,7 +26,7 @@ type Props = {
   currentPage: IPagePopulatedToShowRevision
 }
 
-export const PagePathHeader: FC<Props> = (props) => {
+export const PagePathHeader: FC<Props> = memo((props: Props) => {
   const { t } = useTranslation();
   const { currentPage } = props;
 
@@ -37,6 +39,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 +75,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 +102,36 @@ export const PagePathHeader: FC<Props> = (props) => {
   return (
     <div
       id="page-path-header"
-      className={`d-flex ${moduleClass} small`}
+      className={`d-flex ${moduleClass} small position-relative`}
       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 +156,4 @@ export const PagePathHeader: FC<Props> = (props) => {
       {isOpened && <PageSelectModal />}
     </div>
   );
-};
+});

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

@@ -1,4 +1,5 @@
 .page-title-header :global {
+  max-width: calc(100vw - 650px);
   input {
     min-width: 20px;
     min-height: unset;

+ 4 - 5
apps/app/src/components/PageHeader/PageTitleHeader.tsx

@@ -70,12 +70,11 @@ export const PageTitleHeader: FC<Props> = (props) => {
 
 
   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={editedPageTitle}
               placeholder={t('Input page name')}
               inputClassName="fs-4"
@@ -87,7 +86,7 @@ export const PageTitleHeader: FC<Props> = (props) => {
             />
           </div>
         ) }
-        <h1 className={`mb-0 fs-4 ${isRenameInputShown ? 'invisible' : ''}`} onClick={onClickPageTitle}>
+        <h1 className={`mb-0 fs-4 ${isRenameInputShown ? 'invisible' : ''} text-truncate`} onClick={onClickPageTitle}>
           {pageTitle}
         </h1>
       </div>

+ 26 - 8
apps/app/src/components/SavePageControls/GrantSelector/GrantSelector.tsx

@@ -144,11 +144,12 @@ export const GrantSelector = (props: Props): JSX.Element => {
           <span className="label">
             {userRelatedGrantedGroups.length > 1
               ? (
+              // substring for group name truncate
                 <span>
-                  {`${userRelatedGrantedGroups[0].name}... `}
-                  <span className="badge badge-purple">+{userRelatedGrantedGroups.length - 1}</span>
+                  {`${userRelatedGrantedGroups[0].name.substring(0, 30)}, ... `}
+                  <span className="badge bg-primary">+{userRelatedGrantedGroups.length - 1}</span>
                 </span>
-              ) : userRelatedGrantedGroups[0].name}
+              ) : userRelatedGrantedGroups[0].name.substring(0, 30)}
           </span>
         </span>
       );
@@ -162,7 +163,12 @@ export const GrantSelector = (props: Props): JSX.Element => {
     return (
       <div className="grw-grant-selector mb-0" data-testid="grw-grant-selector">
         <UncontrolledDropdown direction={openInModal ? 'down' : 'up'} size="sm">
-          <DropdownToggle color={dropdownToggleBtnColor} caret className="w-100 d-flex justify-content-between align-items-center" disabled={disabled}>
+          <DropdownToggle
+            color={dropdownToggleBtnColor}
+            caret
+            className="w-100 text-truncate d-flex justify-content-between align-items-center"
+            disabled={disabled}
+          >
             {dropdownToggleLabelElm}
           </DropdownToggle>
           <DropdownMenu container={openInModal ? '' : 'body'}>
@@ -209,13 +215,13 @@ export const GrantSelector = (props: Props): JSX.Element => {
 
           return (
             <button
-              className={`btn btn-outline-primary w-100 d-flex justify-content-start mb-3 align-items-center p-3 ${activeClass}`}
+              className={`btn btn-outline-primary d-flex justify-content-start mb-3 mx-4 align-items-center p-3 ${activeClass}`}
               type="button"
               key={group.item._id}
               onClick={() => groupListItemClickHandler(group)}
             >
-              <span className="align-middle"><input type="checkbox" checked={groupIsGranted} /></span>
-              <h5 className="d-inline-block ms-3">{group.item.name}</h5>
+              <input type="checkbox" checked={groupIsGranted} />
+              <p className="ms-3 mb-0">{group.item.name}</p>
               {group.type === GroupType.externalUserGroup && <span className="ms-2 badge badge-pill badge-info">{group.item.provider}</span>}
               {/* TODO: Replace <div className="small">(TBD) List group members</div> */}
             </button>
@@ -227,6 +233,18 @@ export const GrantSelector = (props: Props): JSX.Element => {
 
   }, [currentUser?.admin, groupListItemClickHandler, myUserGroups, shouldFetch, t, userRelatedGrantedGroups]);
 
+  const renderModalCloseButton = useCallback(() => {
+    return (
+      <button
+        type="button"
+        className="btn border-0 text-muted"
+        onClick={() => setIsSelectGroupModalShown(false)}
+      >
+        <span className="material-symbols-outlined">close</span>
+      </button>
+    );
+  }, [setIsSelectGroupModalShown]);
+
   return (
     <>
       { renderGrantSelector() }
@@ -238,7 +256,7 @@ export const GrantSelector = (props: Props): JSX.Element => {
           toggle={() => setIsSelectGroupModalShown(false)}
           centered
         >
-          <ModalHeader tag="h4" toggle={() => setIsSelectGroupModalShown(false)} className="bg-purple text-muted">
+          <ModalHeader tag="p" toggle={() => setIsSelectGroupModalShown(false)} className="fs-5 text-muted fw-bold pb-2" close={renderModalCloseButton()}>
             {t('user_group.select_group')}
           </ModalHeader>
           <ModalBody>

+ 4 - 0
apps/app/src/components/Sidebar/AppTitle/AppTitle.module.scss

@@ -22,6 +22,10 @@
       padding: (($height - $logomark-height) / 2) (($width - $logomark-width) / 2);
     }
   }
+
+  .confidential-tooltip {
+    max-width: 180px;
+  }
 }
 
 

+ 17 - 2
apps/app/src/components/Sidebar/AppTitle/AppTitle.tsx

@@ -1,8 +1,9 @@
 import React, { memo } from 'react';
 
 import Link from 'next/link';
+import { UncontrolledTooltip } from 'reactstrap';
 
-import { useAppTitle, useIsDefaultLogo } from '~/stores/context';
+import { useAppTitle, useConfidential, useIsDefaultLogo } from '~/stores/context';
 
 import { SidebarBrandLogo } from '../SidebarBrandLogo';
 
@@ -19,6 +20,7 @@ const AppTitleSubstance = memo((props: Props): JSX.Element => {
 
   const { data: isDefaultLogo } = useIsDefaultLogo();
   const { data: appTitle } = useAppTitle();
+  const { data: confidential } = useConfidential();
 
   return (
     <div className={`${styles['grw-app-title']} ${className} d-flex d-edit-none`}>
@@ -27,12 +29,25 @@ const AppTitleSubstance = memo((props: Props): JSX.Element => {
         <SidebarBrandLogo isDefaultLogo={isDefaultLogo} />
       </Link>
       <div className="flex-grow-1 d-flex align-items-center justify-content-between gap-3 overflow-hidden">
-        <div className="grw-site-name text-truncate">
+        <div id="grw-site-name" className="grw-site-name text-truncate">
           <Link href="/" className="fs-4">
             {appTitle}
           </Link>
         </div>
       </div>
+      {!(confidential == null || confidential === '')
+      && (
+        <UncontrolledTooltip
+          className="d-none d-sm-block confidential-tooltip"
+          innerClassName="text-start"
+          data-testid="confidential-tooltip"
+          placement="top"
+          target="grw-site-name"
+          fade={false}
+        >
+          {confidential}
+        </UncontrolledTooltip>
+      )}
     </div>
   );
 });

+ 5 - 4
apps/app/src/pages/admin/slack-integration.page.tsx

@@ -1,19 +1,20 @@
-import {
+import type {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
-import { CrowiRequest } from '~/interfaces/crowi-request';
-import { CommonProps, generateCustomTitle } from '~/pages/utils/commons';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
+import type { CommonProps } from '~/pages/utils/commons';
+import { generateCustomTitle } from '~/pages/utils/commons';
 import { useCurrentUser, useSiteUrl } from '~/stores/context';
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
 
 const AdminLayout = dynamic(() => import('~/components/Layout/AdminLayout'), { ssr: false });
-const SlackIntegration = dynamic(() => import('~/components/Admin/SlackIntegration/SlackIntegration'), { ssr: false });
+const SlackIntegration = dynamic(() => import('~/components/Admin/SlackIntegration/SlackIntegration').then(mod => mod.SlackIntegration), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/Admin/ForbiddenPage').then(mod => mod.ForbiddenPage), { ssr: false });
 
 

+ 4 - 3
apps/app/src/pages/installer.page.tsx

@@ -42,12 +42,13 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
   const navTabMapping = useMemo(() => {
     return {
       user_infomation: {
-        Icon: () => <span className="material-symbols-outlined me-1">person</span>,
+        Icon: () => <span className="material-symbols-outlined me-2">person</span>,
         Content: InstallerForm,
         i18n: t('installer.tab'),
       },
       external_accounts: {
-        Icon: () => <span className="growi-custom-icons">external_link</span>,
+        // TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015
+        Icon: () => <span className="growi-custom-icons me-2">external_link</span>,
         Content: DataTransferForm,
         i18n: tCommons('g2g_data_transfer.tab'),
       },
@@ -68,7 +69,7 @@ const InstallerPage: NextPage<Props> = (props: Props) => {
       <Head>
         <title>{title}</title>
       </Head>
-      <div id="installer-form-container" className="nologin-dialog mx-auto">
+      <div id="installer-form-container" className="nologin-dialog mx-auto rounded-4 rounded-top-0">
         <CustomNavAndContents navTabMapping={navTabMapping} tabContentClasses={['p-0']} />
       </div>
     </NoLoginLayout>

+ 6 - 1
apps/app/src/pages/login/index.module.scss

@@ -1,9 +1,14 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
 // layout
 .login-page :global {
 
   .nologin-header,
   .nologin-dialog {
-    width: 320px;
+    width: 420px;
+    @include bs.media-breakpoint-down(sm) {
+      width: 320px;
+    }
   }
 
 }

+ 7 - 0
apps/app/src/server/crowi/index.js

@@ -37,8 +37,10 @@ import SearchService from '../service/search';
 import { SlackIntegrationService } from '../service/slack-integration';
 import UserGroupService from '../service/user-group';
 import { UserNotificationService } from '../service/user-notification';
+import { instantiateYjsConnectionManager } from '../service/yjs-connection-manager';
 import { getMongoUri, mongoOptions } from '../util/mongoose-utils';
 
+
 const logger = loggerFactory('growi:crowi');
 const httpErrorHandler = require('../middlewares/http-error-handler');
 
@@ -475,9 +477,14 @@ Crowi.prototype.start = async function() {
 
   // setup terminus
   this.setupTerminus(httpServer);
+
   // attach to socket.io
   this.socketIoService.attachServer(httpServer);
 
+  // Initialization YjsConnectionManager
+  instantiateYjsConnectionManager(this.socketIoService.io);
+  this.socketIoService.setupYjsConnection();
+
   // listen
   const serverListening = httpServer.listen(this.port, () => {
     logger.info(`[${this.node_env}] Express server is listening on port ${this.port}`);

+ 9 - 1
apps/app/src/server/routes/apiv3/page/update-page.ts

@@ -1,4 +1,4 @@
-import { allOrigin } from '@growi/core';
+import { Origin, allOrigin } from '@growi/core';
 import type {
   IPage, IRevisionHasId, IUserHasId,
 } from '@growi/core';
@@ -20,6 +20,7 @@ import {
 import type { PageDocument, PageModel } from '~/server/models/page';
 import { configManager } from '~/server/service/config-manager';
 import { preNotifyService } from '~/server/service/pre-notify';
+import { getYjsConnectionManager } from '~/server/service/yjs-connection-manager';
 import Xss from '~/services/xss';
 import XssOption from '~/services/xss/xssOption';
 import loggerFactory from '~/utils/logger';
@@ -79,6 +80,13 @@ export const updatePageHandlersFactory: UpdatePageHandlersFactory = (crowi) => {
 
 
   async function postAction(req: UpdatePageRequest, res: ApiV3Response, updatedPage: PageDocument) {
+    // Reflect the updates in ydoc
+    const origin = req.body.origin;
+    if (origin === Origin.View || origin === undefined) {
+      const yjsConnectionManager = getYjsConnectionManager();
+      await yjsConnectionManager.handleYDocUpdate(req.body.pageId, req.body.body);
+    }
+
     // persist activity
     const parameters = {
       targetModel: SupportedTargetModel.MODEL_PAGE,

+ 3 - 6
apps/app/src/server/service/socket-io.js

@@ -5,7 +5,7 @@ import loggerFactory from '~/utils/logger';
 
 import { RoomPrefix, getRoomNameWithId } from '../util/socket-io-helpers';
 
-import YjsConnectionManager from './yjs-connection-manager';
+import { getYjsConnectionManager } from './yjs-connection-manager';
 
 const expressSession = require('express-session');
 const passport = require('passport');
@@ -37,9 +37,6 @@ class SocketIoService {
     });
     this.io.attach(server);
 
-    // create the YjsConnectionManager instance
-    this.yjsConnectionManager = new YjsConnectionManager(this.io);
-
     // create namespace for admin
     this.adminNamespace = this.io.of('/admin');
 
@@ -54,7 +51,6 @@ class SocketIoService {
 
     await this.setupLoginedUserRoomsJoinOnConnection();
     await this.setupDefaultSocketJoinRoomsEventHandler();
-    await this.setupYjsConnection();
   }
 
   getDefaultSocket() {
@@ -160,10 +156,11 @@ class SocketIoService {
   }
 
   setupYjsConnection() {
+    const yjsConnectionManager = getYjsConnectionManager();
     this.io.on('connection', (socket) => {
       socket.on(GlobalSocketEventName.YDocSync, async({ pageId, initialValue }) => {
         try {
-          await this.yjsConnectionManager.handleYDocSync(pageId, initialValue);
+          await yjsConnectionManager.handleYDocSync(pageId, initialValue);
         }
         catch (error) {
           logger.warn(error.message);

+ 35 - 5
apps/app/src/server/service/yjs-connection-manager.ts

@@ -5,16 +5,18 @@ import * as Y from 'yjs';
 
 import { getMongoUri } from '../util/mongoose-utils';
 
-export const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
-export const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
+const MONGODB_PERSISTENCE_COLLECTION_NAME = 'yjs-writings';
+const MONGODB_PERSISTENCE_FLUSH_SIZE = 100;
 
 class YjsConnectionManager {
 
+  private static instance: YjsConnectionManager;
+
   private ysocketio: YSocketIO;
 
   private mdb: MongodbPersistence;
 
-  constructor(io: Server) {
+  private constructor(io: Server) {
     this.ysocketio = new YSocketIO(io);
     this.ysocketio.initialize();
 
@@ -22,8 +24,19 @@ class YjsConnectionManager {
       collectionName: MONGODB_PERSISTENCE_COLLECTION_NAME,
       flushSize: MONGODB_PERSISTENCE_FLUSH_SIZE,
     });
+  }
+
+  public static getInstance(io?: Server) {
+    if (this.instance != null) {
+      return this.instance;
+    }
+
+    if (io == null) {
+      throw new Error("'io' is required if initialize YjsConnectionManager");
+    }
 
-    this.getCurrentYdoc = this.getCurrentYdoc.bind(this);
+    this.instance = new YjsConnectionManager(io);
+    return this.instance;
   }
 
   public async handleYDocSync(pageId: string, initialValue: string): Promise<void> {
@@ -60,6 +73,16 @@ class YjsConnectionManager {
     persistedYdoc.destroy();
   }
 
+  public async handleYDocUpdate(pageId: string, newValue: string): Promise<void> {
+    // TODO: https://redmine.weseek.co.jp/issues/132775
+    // It's necessary to confirm that the user is not editing the target page in the Editor
+    const currentYdoc = this.getCurrentYdoc(pageId);
+    const currentMarkdownLength = currentYdoc.getText('codemirror').length;
+    currentYdoc.getText('codemirror').delete(0, currentMarkdownLength);
+    currentYdoc.getText('codemirror').insert(0, newValue);
+    Y.encodeStateAsUpdate(currentYdoc);
+  }
+
   private getCurrentYdoc(pageId: string): Y.Doc {
     const currentYdoc = this.ysocketio.documents.get(`yjs/${pageId}`);
     if (currentYdoc == null) {
@@ -70,4 +93,11 @@ class YjsConnectionManager {
 
 }
 
-export default YjsConnectionManager;
+export const instantiateYjsConnectionManager = (io: Server): YjsConnectionManager => {
+  return YjsConnectionManager.getInstance(io);
+};
+
+// export the singleton instance
+export const getYjsConnectionManager = (): YjsConnectionManager => {
+  return YjsConnectionManager.getInstance();
+};

+ 1 - 1
apps/app/src/styles/_fonts.scss

@@ -25,7 +25,7 @@
 
 .growi-custom-icons {
   font-family: var(--grw-font-family-custom-icon);
-  font-size: 16px;
+  font-size: 0.8em;
   font-style: normal;
   -webkit-font-smoothing: auto;
   -moz-osx-font-smoothing: auto;

+ 0 - 64
apps/app/src/styles/atoms/_buttons.scss

@@ -1,64 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use '../mixins';
-
-// fill button style
-.btn.btn-fill {
-  position: relative;
-  display: flex;
-  justify-content: space-between;
-  min-width: 130px;
-  padding: 0px;
-  overflow: hidden;
-  color: white;
-  text-align: center;
-  background-color: rgba(lighten(black, 15%), 0.5);
-  border: none;
-
-  &:not(:disabled) {
-    cursor: pointer;
-  }
-
-  .btn-label {
-    position: relative;
-    z-index: 1;
-    padding: 9px 15px;
-    color: white;
-    text-decoration: none;
-  }
-
-  .btn-label-text {
-    position: relative;
-    z-index: 1;
-    margin: auto;
-    color: white;
-    text-align: center;
-    text-decoration: none;
-  }
-
-  // effect
-  .eff {
-    position: absolute;
-    top: -50px;
-    left: 0px;
-    z-index: 0;
-    width: 100%;
-    height: 100%;
-    transition: all 0.5s ease;
-  }
-
-  &:hover {
-    .eff {
-      top: 0;
-    }
-  }
-}
-
-// define disabled button w/o pointer-events, see _override-bootstrap.scss
-.btn.disabled,
-.btn[disabled],
-fieldset[disabled] .btn {
-  &.grw-pointer-events-none {
-    pointer-events: none;
-  }
-}

+ 14 - 0
apps/app/src/styles/atoms/placeholders/_buttons.scss

@@ -0,0 +1,14 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+%btn-nologin {
+  transition: 0.8s ease;
+  --bs-btn-border-color: #{rgba(white, 0.1)};
+  --bs-btn-hover-border-color: #{rgba(white, 0.1)};
+  --bs-btn-active-border-color: #{rgba(white, 0.1)};
+}
+
+%btn-register {
+  --bs-btn-bg: #{rgba(bs.$success, 0.4)};
+  --bs-btn-hover-bg: #{rgba(bs.$success, 0.8)};
+  --bs-btn-active-bg: #{rgba(bs.$success, 0.8)};
+}

+ 0 - 1
apps/app/src/styles/style-app.scss

@@ -4,7 +4,6 @@
 @import 'mixins';
 
 // atoms
-@import 'atoms/buttons';
 @import 'atoms/custom_control';
 @import 'atoms/code';
 @import 'atoms/tag';

+ 1 - 1
apps/slackbot-proxy/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slackbot-proxy",
-  "version": "7.0.0-slackbot-proxy.0",
+  "version": "7.0.1-slackbot-proxy.0",
   "license": "MIT",
   "scripts": {
     "build": "yarn tsc && tsc-alias -p tsconfig.build.json",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 1 - 0
packages/custom-icons/svg/facebook.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}</style></defs><g transform="translate(-372 -420)"><path class="a" d="M20,10A10,10,0,1,0,7.584,19.706v-6.65H5.522V10H7.584V8.683c0-3.4,1.54-4.981,4.882-4.981a11.026,11.026,0,0,1,2.174.248v2.77c-.236-.025-.646-.037-1.155-.037-1.64,0-2.273.621-2.273,2.236V10h3.266l-.561,3.056H11.211v6.871A10,10,0,0,0,20,10" transform="translate(374 422)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/github.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a,.b{fill:none;}.a{fill-rule:evenodd;}</style></defs><g transform="translate(-207 -420)"><path class="a" d="M10.008,0A10.093,10.093,0,0,0,6.843,19.648c.5.1.679-.217.679-.484,0-.234-.016-1.035-.016-1.87-2.784.6-3.364-1.2-3.364-1.2a2.576,2.576,0,0,0-1.11-1.469c-.911-.618.066-.618.066-.618A2.1,2.1,0,0,1,4.64,15.04a2.12,2.12,0,0,0,2.916.835,2.148,2.148,0,0,1,.63-1.352c-2.22-.234-4.557-1.1-4.557-4.975a3.947,3.947,0,0,1,1.027-2.7,3.667,3.667,0,0,1,.1-2.671s.845-.267,2.75,1.035a9.541,9.541,0,0,1,5,0c1.906-1.3,2.751-1.035,2.751-1.035a3.667,3.667,0,0,1,.1,2.671,3.872,3.872,0,0,1,1.027,2.7c0,3.873-2.336,4.724-4.573,4.975a2.4,2.4,0,0,1,.679,1.87c0,1.352-.016,2.437-.016,2.771,0,.267.182.584.679.484A10.093,10.093,0,0,0,10.008,0Z" transform="translate(209 422)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/google.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}</style></defs><g transform="translate(-291 -420)"><path class="a" d="M19.418,8.182H10V12.05h5.382a4.6,4.6,0,0,1-2,3.018A6.026,6.026,0,1,1,10,3.977a5.4,5.4,0,0,1,3.822,1.5l2.869-2.868A9.611,9.611,0,0,0,10,0a10,10,0,1,0,0,20,9.544,9.544,0,0,0,6.618-2.423,9.75,9.75,0,0,0,2.982-7.35,11.507,11.507,0,0,0-.182-2.045" transform="translate(293.2 422)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/openid.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}</style></defs><g transform="translate(-451 -420)"><path class="a" d="M12.121-1.607h0V-18.75L9.08-17.268V-2.089c-3.446-.433-6.058-2.321-6.058-4.6,0-2.156,2.344-3.964,5.536-4.522v-1.92C3.683-12.536,0-9.879,0-6.687,0-3.37,3.951-.638,9.085-.179ZM20-7.9l-.411-4.353-1.562.884a13.494,13.494,0,0,0-5.371-1.754v1.92A9.539,9.539,0,0,1,15.781-10.1l-1.643.924Z" transform="translate(453 441.465)"/></g></svg>

+ 1 - 0
packages/custom-icons/svg/slack.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><defs><style>.a{fill:none;}</style></defs><g transform="translate(-131 -420)"><g transform="translate(133 422)"><path class="a" d="M4.2,66.8a2.1,2.1,0,1,1-2.1-2.1H4.2Z" transform="translate(0 -54.162)"/><path class="a" d="M32.3,66.8a2.1,2.1,0,1,1,4.2,0v5.261a2.1,2.1,0,1,1-4.2,0V66.8" transform="translate(-27.039 -54.162)"/><path class="a" d="M34.4,4.2a2.1,2.1,0,1,1,2.1-2.1V4.2Z" transform="translate(-27.039)"/><path class="a" d="M7.362,32.3a2.1,2.1,0,1,1,0,4.2H2.1a2.1,2.1,0,1,1,0-4.2H7.362" transform="translate(0 -27.039)"/><path class="a" d="M97,34.4a2.1,2.1,0,1,1,2.1,2.1H97Z" transform="translate(-81.202 -27.039)"/><path class="a" d="M68.9,7.362a2.1,2.1,0,1,1-4.2,0V2.1a2.1,2.1,0,1,1,4.2,0Z" transform="translate(-54.162)"/><path class="a" d="M66.8,97a2.1,2.1,0,1,1-2.1,2.1V97Z" transform="translate(-54.162 -81.201)"/><path class="a" d="M66.8,68.9a2.1,2.1,0,1,1,0-4.2h5.261a2.1,2.1,0,1,1,0,4.2Z" transform="translate(-54.162 -54.162)"/></g></g></svg>

+ 1 - 1
packages/editor/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/editor",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "license": "MIT",
   "type": "module",
   "module": "dist/index.js",

+ 2 - 1
packages/editor/src/components/CodeMirrorEditor/Toolbar/DiagramButton.tsx

@@ -14,7 +14,8 @@ export const DiagramButton = (props: Props): JSX.Element => {
   }, [editorKey, openDrawioModal]);
   return (
     <button type="button" className="btn btn-toolbar-button" onClick={onClickDiagramButton}>
-      <span className="growi-custom-icons">drawer_io</span>
+      {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
+      <span className="growi-custom-icons fs-6">drawer_io</span>
     </button>
   );
 };

+ 2 - 0
packages/editor/src/components/CodeMirrorEditor/Toolbar/TextFormatTools.tsx

@@ -69,6 +69,7 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
             <span className="material-symbols-outlined fs-5">format_strikethrough</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('#', true)}>
+            {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
             <span className="growi-custom-icons">header</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertMarkdownElements('`', '`')}>
@@ -81,6 +82,7 @@ export const TextFormatTools = (props: TextFormatToolsType): JSX.Element => {
             <span className="material-symbols-outlined fs-5">format_list_numbered</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('>')}>
+            {/* TODO: chack and fix font-size. see: https://redmine.weseek.co.jp/issues/143015 */}
             <span className="growi-custom-icons">format_quote</span>
           </button>
           <button type="button" className="btn btn-toolbar-button" onClick={() => onClickInsertPrefix('- [ ]')}>

+ 17 - 15
packages/editor/src/services/paste-util/paste-markdown-util.ts

@@ -20,28 +20,30 @@ export const adjustPasteData = (strFromBol: string, text: string): string => {
 
   let adjusted = text;
 
-  if (text.match(indentAndMarkRE)) {
-    const matchResult = strFromBol.match(indentAndMarkRE);
-    const indent = matchResult ? matchResult[1] : '';
+  if (indentAndMarkOnlyRE.test(strFromBol)) {
+    if (text.match(indentAndMarkRE)) {
+      const matchResult = strFromBol.match(indentAndMarkRE);
+      const indent = matchResult ? matchResult[1] : '';
 
-    const lines = text.match(/[^\r\n]+/g);
+      const lines = text.match(/[^\r\n]+/g);
 
-    const replacedLines = lines?.map((line, index) => {
+      const replacedLines = lines?.map((line, index) => {
 
-      if (index === 0 && strFromBol.match(indentAndMarkOnlyRE)) {
-        return line.replace(indentAndMarkRE, '');
-      }
+        if (index === 0 && strFromBol.match(indentAndMarkOnlyRE)) {
+          return line.replace(indentAndMarkRE, '');
+        }
 
-      return indent + line;
-    });
+        return indent + line;
+      });
 
-    adjusted = replacedLines ? replacedLines.join('\n') : '';
-  }
+      adjusted = replacedLines ? replacedLines.join('\n') : '';
+    }
 
-  else if (strFromBol.match(indentAndMarkRE)) {
-    const replacedText = text.replace(/(\r\n|\r|\n)/g, `$1${strFromBol}`);
+    else {
+      const replacedText = text.replace(/(\r\n|\r|\n)/g, `$1${strFromBol}`);
 
-    adjusted = replacedText;
+      adjusted = replacedText;
+    }
   }
 
   return adjusted;

+ 1 - 1
packages/presentation/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/presentation",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "GROWI plugin for presentation",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/preset-templates/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/preset-templates",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "scripts": {
     "test": "vitest run",
     "version": "yarn version --no-git-tag-version --preid=RC"

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "license": "MIT",
   "main": "dist/libs/preset-themes.umd.js",
   "module": "dist/libs/preset-themes.mjs",

+ 1 - 1
packages/remark-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-attachment-refs",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-drawio/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-drawio",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "remark plugin to draw diagrams with draw.io (diagrams.net)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-growi-directive/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-growi-directive",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "remark plugin to support GROWI plugin (forked from remark-directive@2.0.1)",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/remark-lsx/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/remark-lsx",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "GROWI plugin to list pages",
   "license": "MIT",
   "keywords": [

+ 1 - 1
packages/slack/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/slack",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "license": "MIT",
   "type": "module",
   "main": "dist/index.cjs",

+ 1 - 0
packages/slack/src/interfaces/index.ts

@@ -1,4 +1,5 @@
 export * from './channel';
+export * from './connection-status';
 export * from './growi-command-processor';
 export * from './growi-interaction-processor';
 export * from './growi-event-processor';

+ 1 - 1
packages/ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/ui",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.1-RC.0",
   "description": "GROWI UI Libraries",
   "license": "MIT",
   "keywords": [

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