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

Merge branch 'dev/7.0.x' into feat/126536-install-yjs-libraries

ryoji-s 2 лет назад
Родитель
Сommit
38c1b373e6
100 измененных файлов с 782 добавлено и 699 удалено
  1. 1 0
      .devcontainer/Dockerfile
  2. 1 1
      .devcontainer/devcontainer.json
  3. 1 1
      .github/release-drafter.yml
  4. 1 1
      .github/workflows/auto-labeling.yml
  5. 5 2
      .github/workflows/ci-app.yml
  6. 3 0
      .github/workflows/ci-slackbot-proxy.yml
  7. 1 0
      .github/workflows/release-slackbot-proxy.yml
  8. 49 53
      .github/workflows/release.yml
  9. 1 1
      .github/workflows/reusable-app-build-image.yml
  10. 5 3
      .github/workflows/reusable-app-prod.yml
  11. 1 0
      .github/workflows/reusable-app-reg-suit.yml
  12. 35 11
      CHANGELOG.md
  13. 3 4
      README.md
  14. 3 4
      README_JP.md
  15. 1 0
      _obsolete/packages/.eslintignore
  16. 0 0
      _obsolete/packages/hackmd/.eslintignore
  17. 0 0
      _obsolete/packages/hackmd/.gitignore
  18. 0 0
      _obsolete/packages/hackmd/package.json
  19. 0 0
      _obsolete/packages/hackmd/src/hackmd-agent.js
  20. 0 0
      _obsolete/packages/hackmd/src/hackmd-styles.ts
  21. 0 0
      _obsolete/packages/hackmd/src/index.ts
  22. 0 0
      _obsolete/packages/hackmd/src/style.scss
  23. 0 0
      _obsolete/packages/hackmd/tsconfig.json
  24. 0 0
      _obsolete/packages/hackmd/vite.config.js
  25. 0 3
      apps/app/.env.development
  26. 0 0
      apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts
  27. 0 0
      apps/app/_obsolete/src/components/PageEditorByHackmd.tsx
  28. 0 0
      apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx
  29. 0 0
      apps/app/_obsolete/src/interfaces/hackmd.ts
  30. 0 0
      apps/app/_obsolete/src/server/routes/hackmd.js
  31. 0 0
      apps/app/_obsolete/src/stores/hackmd.ts
  32. 0 0
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  33. 0 0
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  34. 0 0
      apps/app/_obsolete/src/styles/theme/_hsl-functions.scss
  35. 0 0
      apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss
  36. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss
  37. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss
  38. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss
  39. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss
  40. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss
  41. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss
  42. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss
  43. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  44. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  45. 0 0
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  46. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss
  47. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss
  48. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss
  49. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss
  50. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss
  51. 1 1
      apps/app/bin/github-actions/update-readme.sh
  52. 1 0
      apps/app/docker/Dockerfile
  53. 3 3
      apps/app/docker/README.md
  54. 1 6
      apps/app/package.json
  55. 6 5
      apps/app/public/static/locales/en_US/admin.json
  56. 2 22
      apps/app/public/static/locales/en_US/translation.json
  57. 8 6
      apps/app/public/static/locales/ja_JP/admin.json
  58. 2 22
      apps/app/public/static/locales/ja_JP/translation.json
  59. 6 5
      apps/app/public/static/locales/zh_CN/admin.json
  60. 2 22
      apps/app/public/static/locales/zh_CN/translation.json
  61. 21 2
      apps/app/src/client/services/AdminCustomizeContainer.js
  62. 0 19
      apps/app/src/client/services/page-operation.ts
  63. 2 4
      apps/app/src/client/services/side-effects/page-updated.ts
  64. 13 15
      apps/app/src/components/Admin/Common/Accordion.jsx
  65. 4 0
      apps/app/src/components/Admin/Customize/Customize.jsx
  66. 0 28
      apps/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  67. 70 0
      apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx
  68. 0 13
      apps/app/src/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx
  69. 14 18
      apps/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  70. 6 6
      apps/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettingsAccordion.jsx
  71. 12 12
      apps/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  72. 1 2
      apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx
  73. 4 2
      apps/app/src/components/Bookmarks/BookmarkFolderTree.module.scss
  74. 0 15
      apps/app/src/components/Icons/TriangleIcon.tsx
  75. 1 1
      apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx
  76. 26 0
      apps/app/src/components/ItemsTree/Item.module.scss
  77. 1 1
      apps/app/src/components/ItemsTree/ItemNode.ts
  78. 11 2
      apps/app/src/components/ItemsTree/ItemsTree.module.scss
  79. 7 9
      apps/app/src/components/ItemsTree/ItemsTree.tsx
  80. 2 2
      apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx
  81. 2 0
      apps/app/src/components/ItemsTree/index.ts
  82. 1 0
      apps/app/src/components/Layout/BasicLayout.tsx
  83. 35 58
      apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  84. 0 4
      apps/app/src/components/Navbar/GrowiSubNavigation.module.scss
  85. 2 22
      apps/app/src/components/Navbar/GrowiSubNavigation.tsx
  86. 0 127
      apps/app/src/components/Navbar/PageEditorModeManager.jsx
  87. 50 24
      apps/app/src/components/Navbar/PageEditorModeManager.module.scss
  88. 103 0
      apps/app/src/components/Navbar/PageEditorModeManager.tsx
  89. 0 2
      apps/app/src/components/Page/DisplaySwitcher.tsx
  90. 1 0
      apps/app/src/components/PageEditor/Editor.tsx
  91. 3 4
      apps/app/src/components/PageEditor/EditorNavbarBottom.tsx
  92. 58 58
      apps/app/src/components/PageEditor/PageEditor.tsx
  93. 0 1
      apps/app/src/components/PagePresentationModal.module.scss
  94. 65 0
      apps/app/src/components/PageSelectModal/PageSelectModal.tsx
  95. 30 0
      apps/app/src/components/PageSelectModal/TreeItemForModal.tsx
  96. 0 0
      apps/app/src/components/PageSideContents/PageSideContents.module.scss
  97. 71 7
      apps/app/src/components/PageSideContents/PageSideContents.tsx
  98. 1 0
      apps/app/src/components/PageSideContents/index.ts
  99. 19 61
      apps/app/src/components/PageStatusAlert.tsx
  100. 3 4
      apps/app/src/components/PageTags/PageTags.tsx

+ 1 - 0
.devcontainer/Dockerfile

@@ -51,6 +51,7 @@ RUN apt-get update \
 ENV DEBIAN_FRONTEND=dialog
 
 RUN yarn global add turbo
+RUN yarn global add node-gyp
 
 # Uncomment to default to non-root user
 # USER $USER_UID

+ 1 - 1
.devcontainer/devcontainer.json

@@ -34,7 +34,7 @@
   // "shutdownAction": "none",
 
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "yarn global add turbo && yarn install",
+  "postCreateCommand": "yarn global add turbo node-gyp && yarn install",
 
   // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
   "remoteUser": "node"

+ 1 - 1
.github/release-drafter.yml

@@ -7,7 +7,7 @@ filter-by-commitish: true
 categories:
   - title: 'BREAKING CHANGES'
     labels:
-      - 'type/reaking'
+      - 'type/breaking'
   - title: '💎 Features'
     labels:
       - 'type/feature'

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

@@ -37,7 +37,7 @@ jobs:
         !startsWith( github.head_ref, 'dependabot/' ))
 
     steps:
-      - uses: amannn/action-semantic-pull-request@v5.0.2
+      - uses: amannn/action-semantic-pull-request@v5
         with:
           types: |
             feat

+ 5 - 2
.github/workflows/ci-app.yml

@@ -61,6 +61,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: Lint
@@ -95,7 +96,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
           - 27017/tcp
 
@@ -132,6 +133,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: Test
@@ -176,7 +178,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
           - 27017/tcp
 
@@ -214,6 +216,7 @@ jobs:
       - name: Install dependencies
         run: |
           yarn global add turbo
+          yarn global add node-gyp
           yarn --frozen-lockfile
 
       - name: turbo run dev:ci

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

@@ -63,6 +63,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Lint
@@ -137,6 +138,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: yarn dev:ci
@@ -220,6 +222,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Restore dist

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

@@ -109,6 +109,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Bump versions for next RC

+ 49 - 53
.github/workflows/release.yml

@@ -31,6 +31,7 @@ jobs:
     - name: Install dependencies
       run: |
         yarn global add turbo
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Bump versions
@@ -75,56 +76,6 @@ jobs:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
 
-  create-pr-for-next-rc:
-    needs: create-github-release
-
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v3
-      with:
-        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
-
-    - uses: actions/setup-node@v3
-      with:
-        node-version: '18'
-        cache: 'yarn'
-        cache-dependency-path: '**/yarn.lock'
-
-    - name: Install dependencies
-      run: |
-        yarn global add turbo
-        yarn --frozen-lockfile
-
-    - name: Bump versions for next RC
-      run: |
-        turbo run version --filter=@growi/app -- --prepatch
-        turbo run version --filter=@growi/slackbot-proxy -- --prepatch
-        yarn upgrade --scope=@growi
-
-    - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.2.0
-      id: package-json
-
-    - name: Commit
-      uses: github-actions-x/commit@v2.9
-      with:
-        github-token: ${{ secrets.GITHUB_TOKEN }}
-        push-branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
-        commit-message: 'Bump version'
-        name: GitHub Action
-
-    - name: Create PR
-      uses: repo-sync/pull-request@v2
-      with:
-        source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
-        destination_branch: ${{ github.head_ref }}
-        pr_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
-        pr_label: flag/exclude-from-changelog,type/prepare-next-version
-        pr_body: "[skip ci] An automated PR generated by create-pr-for-next-rc"
-        github_token: ${{ secrets.GITHUB_TOKEN }}
-
-
   determine-tags:
     needs: create-github-release
     runs-on: ubuntu-latest
@@ -226,7 +177,52 @@ jobs:
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
         created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}'
 
-    - name: Check whether workspace is clean
+
+  create-pr-for-next-rc:
+    needs: [create-github-release, publish-image, publish-image-ghcr]
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
+
+    - uses: actions/setup-node@v3
+      with:
+        node-version: '18'
+        cache: 'yarn'
+        cache-dependency-path: '**/yarn.lock'
+
+    - name: Install dependencies
+      run: |
+        yarn global add turbo
+        yarn global add node-gyp
+        yarn --frozen-lockfile
+
+    - name: Bump versions for next RC
       run: |
-        STATUS=`git status --porcelain`
-        if [ -z "$STATUS" ]; then exit 0; else exit 1; fi
+        turbo run version --filter=@growi/app -- --prepatch
+        turbo run version --filter=@growi/slackbot-proxy -- --prepatch
+        yarn upgrade --scope=@growi
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
+
+    - name: Commit
+      uses: github-actions-x/commit@v2.9
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        push-branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        commit-message: 'Bump version'
+        name: GitHub Action
+
+    - name: Create PR
+      uses: repo-sync/pull-request@v2
+      with:
+        source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
+        destination_branch: ${{ github.head_ref }}
+        pr_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
+        pr_label: flag/exclude-from-changelog,type/prepare-next-version
+        pr_body: "[skip ci] An automated PR generated by create-pr-for-next-rc"
+        github_token: ${{ secrets.GITHUB_TOKEN }}

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

@@ -36,7 +36,7 @@ jobs:
     - uses: actions/checkout@v3
 
     - name: Configure AWS Credentials
-      uses: aws-actions/configure-aws-credentials@v2
+      uses: aws-actions/configure-aws-credentials@v4
       with:
         aws-region: ap-northeast-1
         role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}

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

@@ -56,6 +56,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Restore dist
@@ -126,7 +127,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
         - 27017/tcp
       elasticsearch:
@@ -214,7 +215,7 @@ jobs:
 
     services:
       mongodb:
-        image: mongo:4.4
+        image: mongo:6.0
         ports:
         - 27017/tcp
       elasticsearch:
@@ -267,6 +268,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
         yarn cypress install
 
@@ -303,7 +305,7 @@ jobs:
         cat config/ci/.env.local.for-auto-install-with-allowing-guest >> .env.production.local
 
     - name: Cypress Run
-      uses: cypress-io/github-action@v5
+      uses: cypress-io/github-action@v6
       with:
         browser: chromium
         working-directory: ./apps/app

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

@@ -82,6 +82,7 @@ jobs:
 
     - name: Install dependencies
       run: |
+        yarn global add node-gyp
         yarn --frozen-lockfile
 
     - name: Download screenshots taken by cypress

+ 35 - 11
CHANGELOG.md

@@ -1,27 +1,32 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.2.0...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.2.1...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
-## [v6.1.15](https://github.com/weseek/growi/compare/v6.1.14...v6.1.15) - 2023-09-11
+## [v6.2.1](https://github.com/weseek/growi/compare/v6.2.0...v6.2.1) - 2023-10-03
 
-### 🚀 Improvement
+### BREAKING CHANGES
 
-- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (for v6.1.x) (#8057) @yuki-takei
+- support: Omit promster (#8105) @yuki-takei
 
-## [v6.1.14](https://github.com/weseek/growi/compare/v6.1.13...v6.1.14) - 2023-08-22
+### 🚀 Improvement
+
+- imprv: Download a markdown file using the page name as the file name (#8061) @soumaeda
+- imprv: Admin customize presentation form (#8083) @meiri-k
+- imprv: i18n for marp settings (#8110) @moekumasaka
+- imprv: i18n "Create /Sidebar page" label (#8085) @yuki-takei
 
 ### 🐛 Bug Fixes
 
-- fix: Add option to lightbox (6.1.x) (#8003) @yuki-takei
+- fix: Marp is enabled incorrectly problem (#8100) @reiji-h
+- fix: Do not work img tag if use style property 62x (#8092) @jam411
 
-## [v6.1.13](https://github.com/weseek/growi/compare/v6.1.12...v6.1.13) - 2023-08-17
-
-### 🐛 Bug Fixes
+### 🧰 Maintenance
 
-- fix: Do not work img tag if use style property (#7988) @jam411
-- fix: "Searching..." label appearing unnecessarily (#7990) @yuki-takei
+- 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
 
 ## [v6.2.0](https://github.com/weseek/growi/compare/v6.1.12...v6.2.0) - 2023-09-14
 
@@ -58,6 +63,25 @@
 - support: Improve build settings (#7919) @yuki-takei
 - support: Url to join to the slack team (#8073) @WNomunomu
 
+## [v6.1.15](https://github.com/weseek/growi/compare/v6.1.14...v6.1.15) - 2023-09-11
+
+### 🚀 Improvement
+
+- imprv: Add CSP style-src for Safari and Content-Disposition of attachment (for v6.1.x) (#8057) @yuki-takei
+
+## [v6.1.14](https://github.com/weseek/growi/compare/v6.1.13...v6.1.14) - 2023-08-22
+
+### 🐛 Bug Fixes
+
+- fix: Add option to lightbox (6.1.x) (#8003) @yuki-takei
+
+## [v6.1.13](https://github.com/weseek/growi/compare/v6.1.12...v6.1.13) - 2023-08-17
+
+### 🐛 Bug Fixes
+
+- fix: Do not work img tag if use style property (#7988) @jam411
+- fix: "Searching..." label appearing unnecessarily (#7990) @yuki-takei
+
 ## [v6.1.12](https://github.com/weseek/growi/compare/v6.1.11...v6.1.12) - 2023-08-14
 
 ### 🐛 Bug Fixes

+ 3 - 4
README.md

@@ -38,8 +38,7 @@
 
 - **Features**
   - Create hierarchical pages with markdown -> [Try GROWI on the demo site](https://docs.growi.org/en/guide/getting-started/try_growi.html)
-  - Simultaneously edit with multiple people by [HackMD(CodiMD)](https://hackmd.io/) integration
-    - [GROWI Docs: HackMD(CodiMD) Integration](https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html)
+  - Simultaneously edit with multiple people
   - Support Authentication with LDAP / Active Directory, OAuth
   - SSO(Single Sign On) with SAML
   - Slack/Mattermost, IFTTT Integration
@@ -84,12 +83,12 @@ See [GROWI Docs: Environment Variables](https://docs.growi.org/en/admin-guide/ad
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)
-- MongoDB 4.x
+- MongoDB 4.4 or above
 
 ### Optional Dependencies
 
 - Redis 3.x
-- ElasticSearch 6.x or 7.x (needed when using Full-text search)
+- ElasticSearch 7.x or 8.x (needed when using Full-text search)
   - **CAUTION: Following plugins are required**
     - [Japanese (kuromoji) Analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)
     - [ICU Analysis Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html)

+ 3 - 4
README_JP.md

@@ -37,8 +37,7 @@
 
 - **主な機能**
   - マークダウンを使用してページを階層構造で作成することが可能です。 -> [デモサイトで GROWI を体験する](https://docs.growi.org/ja/guide/getting-started/try_growi.html)。
-  - [HackMD(CodiMd)](https://hackmd.io/) と連携することで同時多人数編集が可能です。
-    - [GROWI Docs: HackMD(CodiMD) 連携](https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html)
+  - 同時多人数編集が可能です。
   - LDAP / Active Direcotry , OAuth 認証をサポートしています。
   - SAML を用いた Single Sign On が可能です。
   - Slack / Mattermost, IFTTT と連携することが可能です。
@@ -83,12 +82,12 @@ Crowi からの移行は **[こちら](https://docs.growi.org/en/admin-guide/mig
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)
-- MongoDB 4.x
+- MongoDB 4.4 以上
 
 ### オプションの依存関係
 
 - Redis 3.x
-- ElasticSearch 6.x or 7.x (needed when using Full-text search)
+- ElasticSearch 7.x or 8.x (needed when using Full-text search)
   - **注意: 次のプラグインが必要です**
     - [Japanese (kuromoji) Analysis plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-kuromoji.html)
     - [ICU Analysis Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/current/analysis-icu.html)

+ 1 - 0
_obsolete/packages/.eslintignore

@@ -0,0 +1 @@
+**/*

+ 0 - 0
packages/hackmd/.eslintignore → _obsolete/packages/hackmd/.eslintignore


+ 0 - 0
packages/hackmd/.gitignore → _obsolete/packages/hackmd/.gitignore


+ 0 - 0
packages/hackmd/package.json → _obsolete/packages/hackmd/package.json


+ 0 - 0
packages/hackmd/src/hackmd-agent.js → _obsolete/packages/hackmd/src/hackmd-agent.js


+ 0 - 0
packages/hackmd/src/hackmd-styles.ts → _obsolete/packages/hackmd/src/hackmd-styles.ts


+ 0 - 0
packages/hackmd/src/index.ts → _obsolete/packages/hackmd/src/index.ts


+ 0 - 0
packages/hackmd/src/style.scss → _obsolete/packages/hackmd/src/style.scss


+ 0 - 0
packages/hackmd/tsconfig.json → _obsolete/packages/hackmd/tsconfig.json


+ 0 - 0
packages/hackmd/vite.config.js → _obsolete/packages/hackmd/vite.config.js


+ 0 - 3
apps/app/.env.development

@@ -13,8 +13,6 @@ MONGO_URI="mongodb://mongo:27017/growi"
 ELASTICSEARCH_URI="http://elasticsearch:9200/growi"
 ELASTICSEARCH_REQUEST_TIMEOUT=15000
 ELASTICSEARCH_REJECT_UNAUTHORIZED=true
-HACKMD_URI="http://localhost:3010"
-HACKMD_URI_FOR_SERVER="http://hackmd:3000"
 OGP_URI="http://ogp:8088"
 QUESTIONNAIRE_SERVER_ORIGIN="http://host.docker.internal:3003"
 # DRAWIO_URI="http://localhost:8080/?offline=1&https=0"
@@ -23,7 +21,6 @@ QUESTIONNAIRE_SERVER_ORIGIN="http://host.docker.internal:3003"
 # USER_UPPER_LIMIT=0
 # DEV_HTTPS=true
 # FORCE_WIKI_MODE=private
-# PROMSTER_ENABLED=true
 # SLACKBOT_WITHOUT_PROXY_SIGNING_SECRET=''
 # SLACKBOT_WITHOUT_PROXY_BOT_TOKEN=''
 # GROWI_CLOUD_URI='http://growi.cloud'

+ 0 - 0
apps/app/src/client/services/side-effects/hackmd-draft-updated.ts → apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts


+ 0 - 0
apps/app/src/components/PageEditorByHackmd.tsx → apps/app/_obsolete/src/components/PageEditorByHackmd.tsx


+ 0 - 0
apps/app/src/components/PageEditorByHackmd/HackmdEditor.jsx → apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx


+ 0 - 0
apps/app/src/interfaces/hackmd.ts → apps/app/_obsolete/src/interfaces/hackmd.ts


+ 0 - 0
apps/app/src/server/routes/hackmd.js → apps/app/_obsolete/src/server/routes/hackmd.js


+ 0 - 0
apps/app/src/stores/hackmd.ts → apps/app/_obsolete/src/stores/hackmd.ts


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


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


+ 0 - 0
apps/app/src/styles/theme/_hsl-functions.scss → apps/app/_obsolete/src/styles/theme/_hsl-functions.scss


+ 0 - 0
apps/app/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss → apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-border-colors.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-buttons.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-colors.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-dropdown.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-nav.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-tables.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-text.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss


+ 0 - 0
apps/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss → apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss


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


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


+ 0 - 0
apps/app/src/styles/theme/mixins/_count-badge.scss → apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss


+ 0 - 0
apps/app/src/styles/theme/mixins/_hsl-badge.scss → apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss


+ 0 - 0
apps/app/src/styles/theme/mixins/_hsl-button.scss → apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss


+ 0 - 0
apps/app/src/styles/theme/mixins/_list-group.scss → apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss


+ 0 - 0
apps/app/src/styles/theme/mixins/_page-editor-mode-manager.scss → apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss


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

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

+ 1 - 0
apps/app/docker/Dockerfile

@@ -34,6 +34,7 @@ COPY --from=base ${optDir}/out/yarn.lock ./yarn.lock
 
 # setup (with network-timeout = 1 hour)
 RUN yarn config set network-timeout 3600000
+RUN yarn global add node-gyp
 RUN yarn --frozen-lockfile
 
 # make artifacts

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

@@ -11,8 +11,8 @@ 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.2.0`, `6.2`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.0/apps/app/docker/Dockerfile)
-* [`6.1.0`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.8/apps/app/docker/Dockerfile)
+* [`6.2.1`, `6.2`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.1/apps/app/docker/Dockerfile)
+* [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 
 
 What is GROWI?
@@ -30,7 +30,7 @@ Requirements
 
 ### Optional Dependencies
 
-* ElasticSearch (>= 6.6)
+* ElasticSearch (>= 7.17)
     * Japanese (kuromoji) Analysis plugin
     * ICU Analysis Plugin
 

+ 1 - 6
apps/app/package.json

@@ -52,7 +52,6 @@
   "// comments for dependencies": {
     "escape-string-regexp": "5.0.0 or above exports only ESM",
     "string-width": "5.0.0 or above exports only ESM.",
-    "prom-client": "!!DO NOT REMOVE!! A peer dependency of @promster.",
     "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker"
   },
   "dependencies": {
@@ -65,7 +64,6 @@
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@growi/core": "link:../../packages/core",
-    "@growi/hackmd": "link:../../packages/hackmd",
     "@growi/pluginkit": "link:../../packages/pluginkit",
     "@growi/preset-templates": "link:../../packages/preset-templates",
     "@growi/preset-themes": "link:../../packages/preset-themes",
@@ -74,8 +72,6 @@
     "@growi/remark-growi-directive": "link:../../packages/remark-growi-directive",
     "@growi/remark-lsx": "link:../../packages/remark-lsx",
     "@growi/slack": "link:../../packages/slack",
-    "@promster/express": "^7.0.6",
-    "@promster/server": "^7.0.8",
     "@slack/web-api": "^6.2.4",
     "@slack/webhook": "^6.0.0",
     "@types/jest": "^29.5.2",
@@ -154,7 +150,6 @@
     "passport-ldapauth": "^3.0.1",
     "passport-local": "^1.0.0",
     "passport-saml": "^3.2.0",
-    "prom-client": "^14.1.1",
     "qs": "^6.11.1",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
@@ -245,9 +240,9 @@
     "mongodb-memory-server": "^8.12.2",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",
-    "penpal": "^4.0.0",
     "plantuml-encoder": "^1.2.5",
     "prettier": "^1.19.1",
+    "pretty-bytes": "^6.1.1",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",

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

@@ -480,7 +480,10 @@
       "show_all_reply_comments": "Show all reply comments",
       "show_all_reply_comments_desc": "When the setting value is off, comments other than the latest two are omitted.",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
-      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range.",
+      "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
+    },
+      "presentation": "Presentation",
+    "presentation_options": {
       "enable_marp": "Enable Marp ",
       "enable_marp_desc": "Marp can be used in presentation preview. This option may make you vulnerable to XSS.",
       "marp_official_site": "The Marp Official Site",
@@ -499,6 +502,7 @@
     "write_css": "You can write CSS that is applied to whole system.",
     "ctrl_space": "Ctrl+Space to autocomplete",
     "custom_script": "Custom script",
+    "custom_presentation": "Custom presentation",
     "write_java": "You can write Javascript that is applied to whole system.",
     "reflect_change": "You need to reload the page to reflect the change.",
     "custom_logo" : "Custom Logo",
@@ -553,10 +557,6 @@
           "initialize_meta_datas": {
             "label": "Initialize page's like, read users and comment count",
             "desc": "Recommended <span class=\"text-danger\">NOT</span> to check this when users will also be restored."
-          },
-          "initialize_hackmd_related_datas": {
-            "label": "Initialize HackMD related data",
-            "desc": "Recommended to check this unless there is important drafts on HackMD."
           }
         },
         "revisions": {
@@ -912,6 +912,7 @@
     "USER_API_TOKEN_UPDATE": "API Token update",
     "USER_EDITOR_SETTINGS_UPDATE": "Editor settings update",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "In-App Notification settings update",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "User registration request for ID/Password authentication",
     "PAGE_VIEW": "Page view",
     "PAGE_USER_HOME_VIEW": "Page view (User home)",
     "PAGE_FORBIDDEN": "Page view (Fobidden page)",

+ 2 - 22
apps/app/public/static/locales/en_US/translation.json

@@ -66,6 +66,7 @@
   "No users have liked this yet.": "No users have liked this yet.",
   "No users have bookmarked yet": "No users have bookmarked yet",
   "Create Archive Page": "Create Archive Page",
+  "Create Sidebar Page": "Create <strong>/Sidebar</strong> Page",
   "File type": "File type",
   "Target page": "Target page",
   "Include Attachment File": "Include Attachment File",
@@ -501,27 +502,6 @@
     "insert_image": "inserts an image",
     "open_sandbox": "Open Sandbox"
   },
-  "hackmd": {
-    "hack_md": "HackMD",
-    "not_set_up": "HackMD is not set up.",
-    "used_for_not_found": "Can not use HackMD to a page that does not exist.",
-    "start_to_edit": "Start to edit with HackMD",
-    "clone_page_content": "Click to clone page content and start to edit.",
-    "unsaved_draft": "HackMD has unsaved draft.",
-    "draft_outdated": "DRAFT MAY BE OUTDATED",
-    "based_on_revision": "The current draft on HackMD is based on",
-    "view_outdated_draft": "View the outdated draft on HackMD",
-    "resume_to_edit": "Resume to edit with HackMD",
-    "discard_changes": "Discard changes of HackMD",
-    "integration_failed": "HackMD Integration failed",
-    "fail_to_connect": "GROWI client failed to connect to GROWI agent for HackMD.",
-    "check_configuration": "Check your configuration following <a href='https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html'>the manual</a>.",
-    "not_initialized": "HackmdEditor component has not initialized",
-    "someone_editing": "Someone editing this page on HackMD",
-    "this_page_has_draft": "This page has a draft on HackMD",
-    "need_to_associate_with_growi_to_use_hackmd_refer_to_this": "To use HackMD for simultaneous multi-person editing, need to associate HackMD with GROWI.Please refer to <a href='https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html'>here</a>.",
-    "need_to_make_page": "To use HackMD, please make a new page from the <a href='#edit'>built-in editor.</a>"
-  },
   "slack_notification": {
     "popover_title": "Slack Notification",
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
@@ -736,7 +716,7 @@
         "isForbidden": "Authority not allowed to view",
         "currentPageGrantLabel": "Authorization for this page: ",
         "parentPageGrantLabel": "Authority of parent page: ",
-        "docLink": "For more information on modifying permissions, please refer to <a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちらのリンク</a>"
+        "docLink": "For more information on modifying permissions, please refer to <a href='https://docs.growi.org/en/guide/features/authority.html#permissions-for-subordinate-pages'>こちらのリンク</a>"
       },
       "radio_btn": {
         "restrected": "Only those who know the link",

+ 8 - 6
apps/app/public/static/locales/ja_JP/admin.json

@@ -488,7 +488,11 @@
       "show_all_reply_comments": "返信コメントを全て表示する",
       "show_all_reply_comments_desc": "OFFの場合、最新2件のコメント以外が省略されます。",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
-      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。",
+      "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
+
+    },
+    "presentation":"プレゼンテーション",
+    "presentation_options":{
       "enable_marp": "Marp を有効化する",
       "enable_marp_desc": "プレゼンテーション表示に Marp を利用できるようになります。ただし、XSS に対して脆弱になる恐れがあります。",
       "marp_official_site": "参考:Marp 公式サイト",
@@ -507,6 +511,7 @@
     "write_css": " システム全体に適用されるCSSを記述できます。",
     "ctrl_space": "Ctrl+Space でコード補完",
     "custom_script": "カスタムスクリプト",
+    "custom_presentation":"プレゼンテーション",
     "write_java": "システム全体に適用されるJavaScriptを記述できます。",
     "reflect_change": "変更の反映はページの更新が必要です。",
     "custom_logo": "カスタムロゴ",
@@ -515,7 +520,7 @@
     "current_logo": "現在のロゴ",
     "upload_new_logo": "新しいロゴをアップロードする",
     "delete_logo": "ロゴを削除"
-  },
+   },
   "importer_management": {
     "import_data": "データインポート",
     "article": "記事",
@@ -561,10 +566,6 @@
           "initialize_meta_datas": {
             "label": "「いいね」「閲覧したユーザー」「コメント数」を初期化する",
             "desc": "users を同時に復元しない場合、このオプションは<span class=\"text-danger\">非推奨</span>です。"
-          },
-          "initialize_hackmd_related_datas": {
-            "label": "HackMD 関連データを初期化する",
-            "desc": "HackMD に重要な下書きデータがない限りはこのオプションをチェックすることを推奨します。"
           }
         },
         "revisions": {
@@ -920,6 +921,7 @@
     "USER_API_TOKEN_UPDATE": "API トークンの更新",
     "USER_EDITOR_SETTINGS_UPDATE": "エディター設定の更新",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "アプリ内通知設定の更新",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "ID/Password 認証のユーザー登録リクエスト",
     "PAGE_VIEW": "ページ閲覧",
     "PAGE_USER_HOME_VIEW": "ページ閲覧(ユーザーホーム)",
     "PAGE_FORBIDDEN": "ページ閲覧(fobiddenページ)",

+ 2 - 22
apps/app/public/static/locales/ja_JP/translation.json

@@ -62,6 +62,7 @@
   "No users have liked this yet": "いいねをしているユーザーはいません",
   "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "Create Archive Page": "アーカイブページの作成",
+  "Create Sidebar Page": "<strong>/Sidebar</strong> ページを作成する",
   "Target page": "対象ページ",
   "File type": "ファイル形式",
   "Include Attachment File": "添付ファイルも含める",
@@ -534,27 +535,6 @@
     "insert_image": "で画像を挿入できます",
     "open_sandbox": "Sandbox を開く"
   },
-  "hackmd":{
-    "hack_md": "HackMD",
-    "not_set_up": "HackMD はセットアップされていません",
-    "used_for_not_found": "HackMD は新しいページの作成には利用できません",
-    "start_to_edit": "HackMD を開始する",
-    "clone_page_content": "ページを複製して編集を開始します",
-    "unsaved_draft": "HackMD のドラフトが保存されていません",
-    "draft_outdated": "ドラフトは古くなっている可能性があります",
-    "based_on_revision": "現在のドラフトは次の revision に基づいています",
-    "view_outdated_draft": "HackMD で古いドラフトを表示する",
-    "resume_to_edit": "HackMD で編集を再開する",
-    "discard_changes": "HackMD の変更を破棄する",
-    "integration_failed": "HackMD の統合に失敗しました",
-    "fail_to_connect": "GROWI クライアントが HackMD の GROWI agent に接続できませんでした。",
-    "check_configuration": "<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちらのマニュアル</a>から設定を確認してください",
-    "not_initialized": "HackMD コンポーネントは初期化されていません",
-    "someone_editing": "このページは、HackMD で編集されています。",
-    "this_page_has_draft": "このページは、HackMD のドラフトがあります。",
-    "need_to_associate_with_growi_to_use_hackmd_refer_to_this": "HackMD を利用して同時多人数編集を行うには、HackMD と GROWI を連携する必要があります。<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちら</a>を参照してください。",
-    "need_to_make_page": "HackMD を利用するためには、<a href='#edit'>ビルトインエディタ</a>で新しいページを作成してください。"
-  },
   "slack_notification": {
     "popover_title": "Slack 通知",
     "popover_desc": "チャンネル名を入れてください。カンマ区切りのリストを入力することで複数のチャンネルに通知することができます。"
@@ -769,7 +749,7 @@
         "isForbidden": "権限の閲覧が許可されていません",
         "currentPageGrantLabel": "このページの権限: ",
         "parentPageGrantLabel": "親のページの権限: ",
-        "docLink": "権限の修正についての詳細は<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>こちらのリンク</a>を参照してください"
+        "docLink": "権限の修正についての詳細は<a href='https://docs.growi.org/ja/guide/features/authority.html#%E9%85%8D%E4%B8%8B%E3%83%98%E3%82%9A%E3%83%BC%E3%82%B7%E3%82%99%E3%81%AB%E8%A8%AD%E5%AE%9A%E3%81%A6%E3%82%99%E3%81%8D%E3%82%8B%E6%A8%A9%E9%99%90'>こちらのリンク</a>を参照してください"
       },
       "radio_btn": {
         "restrected": "リンクを知っている人のみ",

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

@@ -488,7 +488,10 @@
       "show_all_reply_comments": "显示所有回复评论",
       "show_all_reply_comments_desc": "当设置值为“关”时,将忽略最近两个之外的注释。",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
-      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。",
+      "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
+    },
+      "presentation": "表达",
+      "presentation_options": {
       "enable_marp": "启用 Marp",
       "enable_marp_desc": "Marp 可在演示视图中使用。该选项可能会使您受到 XSS 的攻击。",
       "marp_official_site": "参考资料:Marp 官方网站",
@@ -507,6 +510,7 @@
     "write_css": "您可以编写应用于整个系统的CSS。",
     "ctrl_space": "Ctrl+Space 自动完成",
     "custom_script": "定制纸条",
+    "custom_presentation":"表达",
     "write_java": "您可以编写应用于整个系统的Javascript。",
     "reflect_change": "您需要重新加载页面以反映更改。",
     "custom_logo": "自定义徽标",
@@ -561,10 +565,6 @@
           "initialize_meta_datas": {
             "label": "Initialize page's like, read users and comment count",
             "desc": "Recommended <span class=\"text-danger\">NOT</span> to check this when users will also be restored."
-          },
-          "initialize_hackmd_related_datas": {
-            "label": "Initialize HackMD related data",
-            "desc": "Recommended to check this unless there is important drafts on HackMD."
           }
         },
         "revisions": {
@@ -920,6 +920,7 @@
     "USER_API_TOKEN_UPDATE": "API 令牌更新",
     "USER_EDITOR_SETTINGS_UPDATE": "编辑器设置更新",
     "USER_IN_APP_NOTIFICATION_SETTINGS_UPDATE": "应用内通知设置更新",
+    "USER_REGISTRATION_APPROVAL_REQUEST": "用户注册 ID/密码验证请求",
     "PAGE_VIEW": "页面浏览量",
     "PAGE_USER_HOME_VIEW": "页面浏览量(用户主页)",
     "PAGE_FORBIDDEN": "页面浏览量(禁止页面)",

+ 2 - 22
apps/app/public/static/locales/zh_CN/translation.json

@@ -62,6 +62,7 @@
   "No users have liked this yet": "还没有用户喜欢这个",
   "No users have bookmarked yet": "还没有用户加入书签",
   "Create Archive Page": "创建归档页",
+  "Create Sidebar Page": "创建 <strong>/Sidebar</strong> 页面",
   "File type": "文件类型",
   "Target page": "目标页面",
   "Include Attachment File": "包含附件",
@@ -488,27 +489,6 @@
 		"insert_image": "插入图像",
 		"open_sandbox": "开放式沙箱"
 	},
-	"hackmd": {
-    "hack_md": "HackMD",
-    "not_set_up": "HackMD is not set up.",
-    "used_for_not_found": "Can not use HackMD to a page that does not exist.",
-		"start_to_edit": "Start to edit with HackMD",
-		"clone_page_content": "Click to clone page content and start to edit.",
-		"unsaved_draft": "HackMD has unsaved draft.",
-		"draft_outdated": "DRAFT MAY BE OUTDATED",
-		"based_on_revision": "The current draft on HackMD is based on",
-		"view_outdated_draft": "View the outdated draft on HackMD",
-		"resume_to_edit": "Resume to edit with HackMD",
-		"discard_changes": "Discard changes of HackMD",
-		"integration_failed": "HackMD Integration failed",
-		"fail_to_connect": "GROWI client failed to connect to GROWI agent for HackMD.",
-		"check_configuration": "Check your configuration following <a href='https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html'>the manual</a>.",
-		"not_initialized": "HackmdEditor component has not initialized",
-		"someone_editing": "Someone editing this page on HackMD",
-    "this_page_has_draft": "This page has a draft on HackMD",
-    "need_to_associate_with_growi_to_use_hackmd_refer_to_this": "若要使用HackMD的多人同时编辑功能,请先关联HackMD和GROWI。详情请参考<a href='https://docs.growi.org/en/admin-guide/admin-cookbook/integrate-with-hackmd.html'>这里</a>。",
-    "need_to_make_page": "To use HackMD, please make a new page from the <a href='#edit'>built-in editor.</a>"
-  },
   "slack_notification": {
     "popover_title": "Slack Notification",
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
@@ -739,7 +719,7 @@
         "isForbidden": "无权查看的机构",
         "currentPageGrantLabel": "本页的权限: ",
         "parentPageGrantLabel": "父页的权限: ",
-        "docLink": "关于修改授权的更多信息,请参见此<a href='https://docs.growi.org/ja/admin-guide/admin-cookbook/integrate-with-hackmd.html'>此链接</a>"
+        "docLink": "关于修改授权的更多信息,请参见此<a href='https://docs.growi.org/en/guide/features/authority.html#permissions-for-subordinate-pages'>此链接</a>"
       },
       "radio_btn": {
         "restrected": "只有那些知道链接的人",

+ 21 - 2
apps/app/src/client/services/AdminCustomizeContainer.js

@@ -1,3 +1,4 @@
+/* eslint-disable lines-between-class-members */
 import { isServer } from '@growi/core/dist/utils';
 import { Container } from 'unstated';
 
@@ -154,7 +155,7 @@ export default class AdminCustomizeContainer extends Container {
   /**
    * Switch isEnabledMarp
    */
-  switchIsEnabledMarp() {
+  switchIsEnabledMarp(inputValue) {
     this.setState({ isEnabledMarp: !this.state.isEnabledMarp });
   }
 
@@ -203,7 +204,6 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: this.state.isEnabledStaleNotification,
         isAllReplyShown: this.state.isAllReplyShown,
         isSearchScopeChildrenAsDefault: this.state.isSearchScopeChildrenAsDefault,
-        isEnabledMarp: this.state.isEnabledMarp,
       });
       const { customizedParams } = response.data;
       this.setState({
@@ -216,6 +216,25 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizedParams.isEnabledStaleNotification,
         isAllReplyShown: customizedParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizedParams.isSearchScopeChildrenAsDefault,
+      });
+    }
+    catch (err) {
+      logger.error(err);
+      throw new Error('Failed to update data');
+    }
+  }
+  /**
+   * Update presentation
+   * @memberOf AdminCustomizeContainer
+   */
+  async updateCustomizePresentation() {
+    try {
+      const response = await apiv3Put('/customize-setting/presentation', {
+        isEnabledMarp: this.state.isEnabledMarp,
+      });
+
+      const { customizedParams } = response.data;
+      this.setState({
         isEnabledMarp: customizedParams.isEnabledMarp,
       });
     }

+ 0 - 19
apps/app/src/client/services/page-operation.ts

@@ -135,23 +135,6 @@ export const useSaveOrUpdate = (): SaveOrUpdateFunction => {
     const { path, pageId, revisionId } = pageInfo;
 
     const options: OptionsToSave = Object.assign({}, optionsToSave);
-    /*
-    * Note: variable "markdown" will be received from params
-    * please delete the following code after implemating HackMD editor function
-    */
-    // let markdown;
-    // if (editorMode === EditorMode.HackMD) {
-    // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-    // markdown = await pageEditorByHackmd.getMarkdown();
-    // // set option to sync
-    // options.isSyncRevisionToHackmd = true;
-    // revisionId = this.state.revisionIdHackmdSynced;
-    // }
-    // else {
-    // const pageEditor = this.appContainer.getComponentInstance('PageEditor');
-    // const pageEditor = getComponentInstance('PageEditor');
-    // markdown = pageEditor.getMarkdown();
-    // }
 
     let res;
     if (pageId == null || revisionId == null) {
@@ -209,8 +192,6 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
       remoteRevisionBody: updatedPage.revision.body,
       remoteRevisionLastUpdateUser: updatedPage.lastUpdateUser,
       remoteRevisionLastUpdatedAt: updatedPage.updatedAt,
-      revisionIdHackmdSynced: updatedPage.revisionHackmdSynced?.toString(),
-      hasDraftOnHackmd: updatedPage.hasDraftOnHackmd,
     };
 
     setRemoteLatestPageData(remoterevisionData);

+ 2 - 4
apps/app/src/client/services/side-effects/page-updated.ts

@@ -2,7 +2,7 @@ import { useCallback, useEffect } from 'react';
 
 import { SocketEventName } from '~/interfaces/websocket';
 import { useCurrentPageId } from '~/stores/page';
-import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
+import { useSetRemoteLatestPageData, type RemoteRevisionData } from '~/stores/remote-latest-page';
 import { useGlobalSocket } from '~/stores/websocket';
 
 export const usePageUpdatedEffect = (): void => {
@@ -15,13 +15,11 @@ export const usePageUpdatedEffect = (): void => {
   const setLatestRemotePageData = useCallback((data) => {
     const { s2cMessagePageUpdated } = data;
 
-    const remoteData = {
+    const remoteData: RemoteRevisionData = {
       remoteRevisionId: s2cMessagePageUpdated.revisionId,
       remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
       remoteRevisionLastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
       remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
-      revisionIdHackmdSynced: s2cMessagePageUpdated.revisionIdHackmdSynced,
-      hasDraftOnHackmd: s2cMessagePageUpdated.hasDraftOnHackmd,
     };
 
     if (currentPageId != null && currentPageId === s2cMessagePageUpdated.pageId) {

+ 13 - 15
apps/app/src/components/Admin/Common/Accordion.jsx

@@ -4,25 +4,23 @@ import PropTypes from 'prop-types';
 import { Collapse } from 'reactstrap';
 
 
-// TODO: use new accordion component
-// https://redmine.weseek.co.jp/issues/129222
 const Accordion = (props) => {
   const [isOpen, setIsOpen] = useState(props.isOpenDefault);
   return (
-    <div className="card border-0 rounded-3 mb-0">
-      <div
-        className="card-header fw-normal py-3 d-flex justify-content-between"
-        role="button"
-        onClick={() => setIsOpen(prevState => !prevState)}
-      >
-        <p className="mb-0">{props.title}</p>
-        {isOpen
-          ? <i className="fa fa-chevron-up" />
-          : <i className="fa fa-chevron-down" />
-        }
-      </div>
+    <div className="accordion-item">
+      <p className="accordion-header" id="headingOne">
+        <button
+          className={`accordion-button ${isOpen ? '' : 'collapsed'}`}
+          type="button"
+          data-bs-toggle="collapse"
+          aria-expanded="true"
+          onClick={() => setIsOpen(prevState => !prevState)}
+        >
+          {props.title}
+        </button>
+      </p>
       <Collapse isOpen={isOpen}>
-        <div className="card-body">
+        <div className="accordion-body">
           {props.children}
         </div>
       </Collapse>

+ 4 - 0
apps/app/src/components/Admin/Customize/Customize.jsx

@@ -15,6 +15,7 @@ import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeNoscriptSetting from './CustomizeNoscriptSetting';
+import CustomizePresentationSetting from './CustomizePresentationSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeSidebarSetting from './CustomizeSidebarSetting';
 import CustomizeThemeSetting from './CustomizeThemeSetting';
@@ -58,6 +59,9 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeFunctionSetting />
       </div>
+      <div className="mb-5">
+        <CustomizePresentationSetting />
+      </div>
       <div className="mb-5">
         <CustomizeTitle />
       </div>

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

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

+ 70 - 0
apps/app/src/components/Admin/Customize/CustomizePresentationSetting.tsx

@@ -0,0 +1,70 @@
+import React, { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
+import { toastSuccess, toastError } from '~/client/util/toastr';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+import CustomizePresentationOption from './CustomizeFunctionOption';
+
+type Props = {
+  adminCustomizeContainer: AdminCustomizeContainer
+}
+
+const CustomizePresentationSetting = (props: Props): JSX.Element => {
+  const { adminCustomizeContainer } = props;
+
+  console.log(adminCustomizeContainer);
+
+  const { t } = useTranslation();
+  const onClickSubmit = useCallback(async() => {
+    try {
+      await adminCustomizeContainer.updateCustomizePresentation();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.presentation'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [adminCustomizeContainer]);
+  return (
+    <React.Fragment>
+      <h2 className="admin-setting-header">{t('admin:customize_settings.custom_presentation')}</h2>
+      <div className="form-group row">
+        <div className="offset-md-3 col-md-6 text-left">
+          <CustomizePresentationOption
+            optionId="isEnabledMarp"
+            label={t('admin:customize_settings.presentation_options.enable_marp')}
+            isChecked={adminCustomizeContainer?.state.isEnabledMarp || false}
+            onChecked={() => { adminCustomizeContainer.switchIsEnabledMarp() }}
+          >
+            <p className="form-text text-muted">
+              {t('admin:customize_settings.presentation_options.enable_marp_desc')}
+              <br></br>
+              <a
+                href={`${t('admin:customize_settings.presentation_options.marp_official_site_link')}`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >{`${t('admin:customize_settings.presentation_options.marp_official_site')}`}
+              </a>
+              <br></br>
+              <a
+                href={`${t('admin:customize_settings.presentation_options.marp_in_gorwi_link')}`}
+                target="_blank"
+                rel="noopener noreferrer"
+              >{`${t('admin:customize_settings.presenattion_options.marp_in_growi')}`}
+              </a>
+            </p>
+          </CustomizePresentationOption>
+        </div>
+      </div>
+
+      <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+    </React.Fragment>
+  );
+};
+const CustomizePresentationSettingWrapper = withUnstatedContainers(CustomizePresentationSetting, [AdminCustomizeContainer]);
+
+export default CustomizePresentationSettingWrapper;

+ 0 - 13
apps/app/src/components/Admin/ImportData/GrowiArchive/ImportCollectionConfigurationModal.jsx

@@ -140,19 +140,6 @@ class ImportCollectionConfigurationModal extends React.Component {
             <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_meta_datas.desc`) }} />
           </label>
         </div>
-        <div className="form-check form-check-warning">
-          <input
-            id="cbOpt6"
-            type="checkbox"
-            className="form-check-input"
-            checked={option.initHackmdDatas || false} // add ' || false' to avoid uncontrolled input warning
-            onChange={() => this.changeHandler({ initHackmdDatas: !option.initHackmdDatas })}
-          />
-          <label htmlFor="cbOpt6" className="form-label form-check-label">
-            {t(`${translationBase}.initialize_hackmd_related_datas.label`)}
-            <p className="form-text text-muted mt-0" dangerouslySetInnerHTML={{ __html: t(`${translationBase}.initialize_hackmd_related_datas.desc`) }} />
-          </label>
-        </div>
       </>
     );
     /* eslint-enable react/no-unescaped-entities */

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

@@ -474,25 +474,21 @@ pWVdnzS1VCO8fKsJ7YYIr+JmHvseph3kFUOI5RqkCcMZlKUv83aUThsTHw==
                           Apache Lucene - Query Parser Syntax <i className="icon-share-alt"></i>
                         </a>.
                       </p>
-                      {/* TODO: use new accordion component */}
-                      {/* https://redmine.weseek.co.jp/issues/129222 */}
-                      <div className="accordion" id="accordionExample">
-                        <div className="card">
-                          <div className="card-header p-1">
-                            <h2 className="mb-0">
-                              <button
-                                className="btn btn-link text-start"
-                                type="button"
-                                onClick={() => this.setState({ isHelpOpened: !this.state.isHelpOpened })}
-                                aria-expanded="true"
-                                aria-controls="ablchelp"
-                              >
-                                <i className={`icon-fw ${this.state.isHelpOpened ? 'icon-arrow-down' : 'icon-arrow-right'} small`}></i> Show more...
-                              </button>
-                            </h2>
-                          </div>
+                      <div className="accordion" id="accordionId">
+                        <div className="accordion-item p-1">
+                          <h2 className="accordion-header">
+                            <button
+                              className="btn btn-link text-start"
+                              type="button"
+                              onClick={() => this.setState({ isHelpOpened: !this.state.isHelpOpened })}
+                              aria-expanded="true"
+                              aria-controls="ablchelp"
+                            >
+                              <i className={`icon-fw ${this.state.isHelpOpened ? 'icon-arrow-down' : 'icon-arrow-right'} small`}></i> Show more...
+                            </button>
+                          </h2>
                           <Collapse isOpen={this.state.isHelpOpened}>
-                            <div className="card-body">
+                            <div className="accordion-body">
                               <p dangerouslySetInnerHTML={{ __html: t('security_settings.SAML.attr_based_login_control_rule_help') }} />
                               <p dangerouslySetInnerHTML={{ __html: t('security_settings.SAML.attr_based_login_control_rule_example1') }} />
                               <p dangerouslySetInnerHTML={{ __html: t('security_settings.SAML.attr_based_login_control_rule_example2') }} />

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

@@ -70,10 +70,10 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
   );
 
   return (
-    <div className="card border-0 rounded-3 shadow overflow-hidden">
+    <div className="accordion">
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CREATE_BOT)}
-        title={<><span className="me-2">①</span>{t('admin:slack_integration.accordion.create_bot')}</>}
+        title={<><span className="me-3">1</span>{t('admin:slack_integration.accordion.create_bot')}</>}
       >
         <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')}>
@@ -96,7 +96,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       </Accordion>
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.INSTALL_BOT)}
-        title={<><span className="me-2">②</span>{t('admin:slack_integration.accordion.install_bot_to_slack')}</>}
+        title={<><span className="me-3">2</span>{t('admin:slack_integration.accordion.install_bot_to_slack')}</>}
       >
         <div className="container w-75 py-5">
           <p>1. {t('admin:slack_integration.accordion.select_install_your_app')}</p>
@@ -115,7 +115,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.REGISTER_SLACK_CONFIGURATION)}
         // eslint-disable-next-line max-len
-        title={<><span className="me-2">③</span>{t('admin:slack_integration.accordion.register_secret_and_token')}{isEnterdSecretAndToken && <i className="ms-3 text-success fa fa-check"></i>}</>}
+        title={<><span className="me-3">3</span>{t('admin:slack_integration.accordion.register_secret_and_token')}{isEnterdSecretAndToken && <i className="ms-3 text-success fa fa-check"></i>}</>}
       >
         <CustomBotWithoutProxySecretTokenSection
           onUpdatedSecretToken={props.onUpdatedSecretToken}
@@ -128,7 +128,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         // eslint-disable-next-line max-len
-        title={<><span className="me-2">④</span>{t('admin:slack_integration.accordion.manage_permission')}</>}
+        title={<><span className="me-3">4</span>{t('admin:slack_integration.accordion.manage_permission')}</>}
       >
         <ManageCommandsProcessWithoutProxy
           commandPermission={commandPermission}
@@ -138,7 +138,7 @@ const CustomBotWithoutProxySettingsAccordion = (props) => {
       <Accordion
         defaultIsActive={defaultOpenAccordionKeys.has(botInstallationStep.CONNECTION_TEST)}
         // eslint-disable-next-line max-len
-        title={<><span className="me-2">⑤</span>{t('admin:slack_integration.accordion.test_connection')}{isLatestConnectionSuccess && <i className="ms-3 text-success fa fa-check"></i>}</>}
+        title={<><span className="me-3">5</span>{t('admin:slack_integration.accordion.test_connection')}{isLatestConnectionSuccess && <i className="ms-3 text-success fa fa-check"></i>}</>}
       >
         <p className="text-center m-4">{t('admin:slack_integration.accordion.test_connection_by_pressing_button')}</p>
         <p className="text-center text-warning">

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

@@ -301,11 +301,11 @@ const WithProxyAccordions = (props) => {
 
 
   const officialBotIntegrationProcedure = {
-    '①': {
+    1: {
       title: 'install_bot_to_slack',
       content: <BotInstallProcessForOfficialBot />,
     },
-    '②': {
+    2: {
       title: 'register_for_growi_official_bot_proxy_service',
       content: <GeneratingTokensAndRegisteringProxyServiceProcess
         growiUrl={siteUrl}
@@ -315,7 +315,7 @@ const WithProxyAccordions = (props) => {
         onUpdateTokens={props.onUpdateTokens}
       />,
     },
-    '③': {
+    3: {
       title: 'manage_permission',
       content: <ManageCommandsProcess
         slackAppIntegrationId={props.slackAppIntegrationId}
@@ -324,7 +324,7 @@ const WithProxyAccordions = (props) => {
         permissionsForSlackEventActions={props.permissionsForSlackEventActions}
       />,
     },
-    '④': {
+    4: {
       title: 'test_connection',
       content: <TestProcess
         slackAppIntegrationId={props.slackAppIntegrationId}
@@ -336,15 +336,15 @@ const WithProxyAccordions = (props) => {
   };
 
   const CustomBotIntegrationProcedure = {
-    '①': {
+    1: {
       title: 'create_bot',
       content: <BotCreateProcess />,
     },
-    '②': {
+    2: {
       title: 'install_bot_to_slack',
       content: <BotInstallProcessForCustomBotWithProxy />,
     },
-    '③': {
+    3: {
       title: 'register_for_growi_custom_bot_proxy',
       content: <GeneratingTokensAndRegisteringProxyServiceProcess
         growiUrl={siteUrl}
@@ -354,11 +354,11 @@ const WithProxyAccordions = (props) => {
         onUpdateTokens={props.onUpdateTokens}
       />,
     },
-    '④': {
+    4: {
       title: 'set_proxy_url_on_growi',
       content: <RegisteringProxyUrlProcess />,
     },
-    '⑤': {
+    5: {
       title: 'manage_permission',
       content: <ManageCommandsProcess
         slackAppIntegrationId={props.slackAppIntegrationId}
@@ -367,7 +367,7 @@ const WithProxyAccordions = (props) => {
         permissionsForSlackEventActions={props.permissionsForSlackEventActions}
       />,
     },
-    '⑥': {
+    6: {
       title: 'test_connection',
       content: <TestProcess
         slackAppIntegrationId={props.slackAppIntegrationId}
@@ -382,14 +382,14 @@ const WithProxyAccordions = (props) => {
 
   return (
     <div
-      className="card border-0 rounded-3 shadow overflow-hidden"
+      className="accordion"
     >
       {Object.entries(integrationProcedureMapping).map(([key, value]) => {
         return (
           <Accordion
             title={(
               <>
-                <span className="me-2">{key}</span>
+                <span className="me-3">{key}</span>
                 {t(`admin:slack_integration.accordion.${value.title}`)}
                 {value.title === 'test_connection' && isLatestConnectionSuccess && <i className="ms-3 text-success fa fa-check"></i>}
               </>

+ 1 - 2
apps/app/src/components/Bookmarks/BookmarkFolderItem.tsx

@@ -10,7 +10,6 @@ import {
 } from '~/client/util/bookmark-utils';
 import { toastError } from '~/client/util/toastr';
 import { FolderIcon } from '~/components/Icons/FolderIcon';
-import { TriangleIcon } from '~/components/Icons/TriangleIcon';
 import {
   BookmarkFolderItems, DragItemDataType, DragItemType, DRAG_ITEM_TYPE,
 } from '~/interfaces/bookmark-info';
@@ -234,7 +233,7 @@ export const BookmarkFolderItem: FC<BookmarkFolderItemProps> = (props: BookmarkF
                 onClick={loadChildFolder}
               >
                 <div className="d-flex justify-content-center">
-                  <TriangleIcon />
+                  <span className="material-icons-round">arrow_right</span>
                 </div>
               </button>
             )}

+ 4 - 2
apps/app/src/components/Bookmarks/BookmarkFolderTree.module.scss

@@ -40,7 +40,7 @@ $grw-bookmark-item-padding-left: 35px;
     }
 
     .grw-foldertree-triangle-btn {
-      background-color: transparent;
+      border: 0;
       transition: all 0.2s ease-out;
       transform: rotate(0deg);
 
@@ -58,7 +58,9 @@ $grw-bookmark-item-padding-left: 35px;
 
   .grw-foldertree-item-container {
     .grw-triangle-container {
-      min-width: 35px;
+      // TODO: ignore width frickering
+      // https://redmine.weseek.co.jp/issues/130828
+      // min-width: 35px;
       height: 40px;
     }
 

+ 0 - 15
apps/app/src/components/Icons/TriangleIcon.tsx

@@ -1,15 +0,0 @@
-import React from 'react';
-
-export const TriangleIcon = (): JSX.Element => (
-  <svg
-    xmlns="http://www.w3.org/2000/svg"
-    width="12"
-    height="12"
-    viewBox="0 0 12 12"
-  >
-    <g transform="translate(18194 -6790)">
-      <rect width="12" height="12" transform="translate(-18194 6790)" fill="none" />
-      <path d="M5.2,1.067a1,1,0,0,1,1.6,0l4,5.333A1,1,0,0,1,10,8H2a1,1,0,0,1-.8-1.6Z" transform="translate(-18183 6790) rotate(90)" />
-    </g>
-  </svg>
-);

+ 1 - 1
apps/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -83,7 +83,7 @@ export const InAppNotificationDropdown = (): JSX.Element => {
 
   return (
     <Dropdown className="notification-wrapper grw-notification-dropdown" isOpen={isOpen} toggle={toggleDropdownHandler} direction="end">
-      <DropdownToggle className="px-3 nav-link border-0 bg-transparent" innerRef={buttonRef}>
+      <DropdownToggle className="px-3" color="primary" innerRef={buttonRef}>
         <i className="icon-bell" /> {badge}
       </DropdownToggle>
       <DropdownMenu end>

+ 26 - 0
apps/app/src/components/ItemsTree/Item.module.scss

@@ -0,0 +1,26 @@
+@use '@growi/core/scss/bootstrap/init' as bs;
+
+// TODO: relocate following styles into PageTreeItem.mdoule.scss after refactoring
+// https://redmine.weseek.co.jp/issues/127544
+@include bs.color-mode(light) {
+  .pagetree-item :global {
+    .list-group-item-action {
+      .btn-page-item-control {
+        --bs-btn-bg: transparent;
+        --bs-btn-hover-bg: var(--grw-primary-200);
+        --bs-btn-active-bg: var(--grw-primary-300);
+      }
+    }
+  }
+}
+@include bs.color-mode(dark) {
+  .pagetree-item :global {
+    .list-group-item-action {
+      .btn-page-item-control {
+        --bs-btn-bg: transparent;
+        --bs-btn-hover-bg: var(--grw-primary-600);
+        --bs-btn-active-bg: var(--grw-primary-700);
+      }
+    }
+  }
+}

+ 1 - 1
apps/app/src/components/Sidebar/PageTree/ItemNode.ts → apps/app/src/components/ItemsTree/ItemNode.ts

@@ -1,4 +1,4 @@
-import { IPageForItem } from '../../../interfaces/page';
+import { IPageForItem } from '../../interfaces/page';
 
 export class ItemNode {
 

+ 11 - 2
apps/app/src/components/Sidebar/PageTree/ItemsTree.module.scss → apps/app/src/components/ItemsTree/ItemsTree.module.scss

@@ -41,7 +41,7 @@ $grw-pagetree-item-container-height: 40px;
       }
 
       .grw-pagetree-triangle-btn {
-        background-color: transparent;
+        border: 0;
         transition: all 0.2s ease-out;
         transform: rotate(0deg);
 
@@ -67,7 +67,9 @@ $grw-pagetree-item-container-height: 40px;
 
     .grw-pagetree-item-container {
       .grw-triangle-container {
-        min-width: 35px;
+        // TODO: ignore width frickering
+        // https://redmine.weseek.co.jp/issues/130828
+        // min-width: 35px;
         height: $grw-pagetree-item-container-height;
       }
     }
@@ -151,3 +153,10 @@ $grw-pagetree-item-container-height: 40px;
     }
   }
 }
+
+
+.grw-pagetree :global {
+  .grw-pagetree-triangle-btn {
+    --btn-color: var(--bs-tertiary-color);
+  }
+}

+ 7 - 9
apps/app/src/components/Sidebar/PageTree/ItemsTree.tsx → apps/app/src/components/ItemsTree/ItemsTree.tsx

@@ -25,10 +25,9 @@ import { usePageTreeDescCountMap, useSidebarScrollerRef } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import loggerFactory from '~/utils/logger';
 
+import { ItemNode, SimpleItemProps } from '../TreeItem';
 
-import Item from './Item';
-import { ItemNode } from './ItemNode';
-import PageTreeContentSkeleton from './PageTreeContentSkeleton';
+import ItemsTreeContentSkeleton from './ItemsTreeContentSkeleton';
 
 import styles from './ItemsTree.module.scss';
 
@@ -93,14 +92,15 @@ type ItemsTreeProps = {
   targetPath: string
   targetPathOrId?: Nullable<string>
   targetAndAncestorsData?: TargetAndAncestors
+  CustomTreeItem: React.FunctionComponent<SimpleItemProps>
 }
 
 /*
  * ItemsTree
  */
-const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
+export const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   const {
-    targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions, isReadOnlyUser,
+    targetPath, targetPathOrId, targetAndAncestorsData, isEnableActions, isReadOnlyUser, CustomTreeItem,
   } = props;
 
   const { t } = useTranslation();
@@ -272,7 +272,7 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
   if (initialItemNode != null) {
     return (
       <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group py-3`} ref={rootElemRef}>
-        <Item
+        <CustomTreeItem
           key={initialItemNode.page.path}
           targetPathOrId={targetPathOrId}
           itemNode={initialItemNode}
@@ -287,7 +287,5 @@ const ItemsTree = (props: ItemsTreeProps): JSX.Element => {
     );
   }
 
-  return <PageTreeContentSkeleton />;
+  return <ItemsTreeContentSkeleton />;
 };
-
-export default ItemsTree;

+ 2 - 2
apps/app/src/components/Sidebar/PageTree/PageTreeContentSkeleton.tsx → apps/app/src/components/ItemsTree/ItemsTreeContentSkeleton.tsx

@@ -4,7 +4,7 @@ import { Skeleton } from '~/components/Skeleton';
 
 import styles from './ItemsTree.module.scss';
 
-const PageTreeContentSkeleton = (): JSX.Element => {
+const ItemsTreeContentSkeleton = (): JSX.Element => {
 
   return (
     <ul className={`grw-pagetree ${styles['grw-pagetree']} list-group py-3`}>
@@ -15,4 +15,4 @@ const PageTreeContentSkeleton = (): JSX.Element => {
   );
 };
 
-export default PageTreeContentSkeleton;
+export default ItemsTreeContentSkeleton;

+ 2 - 0
apps/app/src/components/ItemsTree/index.ts

@@ -0,0 +1,2 @@
+export { ItemNode } from './ItemNode';
+export * from './ItemsTree';

+ 1 - 0
apps/app/src/components/Layout/BasicLayout.tsx

@@ -30,6 +30,7 @@ type Props = {
   className?: string
 }
 
+
 export const BasicLayout = ({ children, className }: Props): JSX.Element => {
   return (
     <RawLayout className={className ?? ''}>

+ 35 - 58
apps/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,4 +1,4 @@
-import React, { useState, useEffect, useCallback } from 'react';
+import React, { useState, useCallback } from 'react';
 
 import { isPopulated } from '@growi/core';
 import type {
@@ -11,25 +11,22 @@ import dynamic from 'next/dynamic';
 import { useRouter } from 'next/router';
 import { DropdownItem } from 'reactstrap';
 
-import { exportAsMarkdown, updateContentWidth, useUpdateStateAfterSave } from '~/client/services/page-operation';
-import { apiPost } from '~/client/util/apiv1-client';
-import { toastSuccess, toastError } from '~/client/util/toastr';
+import { exportAsMarkdown, updateContentWidth } from '~/client/services/page-operation';
 import { OnDuplicatedFunction, OnRenamedFunction, OnDeletedFunction } from '~/interfaces/ui';
 import {
   useCurrentPathname,
   useCurrentUser, useIsGuestUser, useIsReadOnlyUser, useIsSharedUser, useShareLinkId, useIsContainerFluid, useIsIdenticalPath,
 } from '~/stores/context';
-import { usePageTagsForEditors } from '~/stores/editor';
 import {
   usePageAccessoriesModal, PageAccessoriesModalContents, IPageForPageDuplicateModal,
   usePageDuplicateModal, usePageRenameModal, usePageDeleteModal, usePagePresentationModal,
 } from '~/stores/modal';
 import {
-  useSWRMUTxCurrentPage, useSWRxTagsInfo, useCurrentPageId, useIsNotFound, useTemplateTagData, useSWRxPageInfo,
+  useSWRMUTxCurrentPage, useCurrentPageId, useIsNotFound, useSWRxPageInfo,
 } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
 import {
-  EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
+  EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement,
   useIsAbleToChangeEditorMode, useIsAbleToShowPageAuthors,
 } from '~/stores/ui';
 
@@ -52,7 +49,7 @@ const AuthorInfoSkeleton = () => <Skeleton additionalClass={`${AuthorInfoStyles[
 
 
 const PageEditorModeManager = dynamic(
-  () => import('./PageEditorModeManager'),
+  () => import('./PageEditorModeManager').then(mod => mod.PageEditorModeManager),
   { ssr: false, loading: () => <Skeleton additionalClass={`${PageEditorModeManagerStyles['grw-page-editor-mode-manager-skeleton']}`} /> },
 );
 // TODO: If enable skeleton, we get hydration error when create a page from PageCreateModal
@@ -217,38 +214,39 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const { data: isContainerFluid } = useIsContainerFluid();
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
-  const { data: isAbleToShowTagLabel } = useIsAbleToShowTagLabel();
   const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
   const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
 
-  const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRxTagsInfo(currentPage?._id);
-
+  // TODO: implement tags for editor
+  // refs: https://redmine.weseek.co.jp/issues/132125
   // eslint-disable-next-line max-len
-  const { data: tagsForEditors, mutate: mutatePageTagsForEditors, sync: syncPageTagsForEditors } = usePageTagsForEditors(!isSharedPage ? currentPage?._id : undefined);
+  // const { data: tagsForEditors, mutate: mutatePageTagsForEditors, sync: syncPageTagsForEditors } = usePageTagsForEditors(!isSharedPage ? currentPage?._id : undefined);
+  // const { data: templateTagData } = useTemplateTagData();
 
   const { open: openDuplicateModal } = usePageDuplicateModal();
   const { open: openRenameModal } = usePageRenameModal();
   const { open: openDeleteModal } = usePageDeleteModal();
-  const { data: templateTagData } = useTemplateTagData();
   const { mutate: mutatePageInfo } = useSWRxPageInfo(pageId);
 
-  const updateStateAfterSave = useUpdateStateAfterSave(pageId);
-
   const path = currentPage?.path ?? currentPathname;
 
-  useEffect(() => {
-    // Run only when tagsInfoData has been updated
-    if (templateTagData == null) {
-      syncPageTagsForEditors();
-    }
-    // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [tagsInfoData?.tags]);
-
-  useEffect(() => {
-    if (pageId === null && templateTagData != null) {
-      mutatePageTagsForEditors(templateTagData);
-    }
-  }, [pageId, mutatePageTagsForEditors, templateTagData, mutateSWRTagsInfo]);
+  // TODO: implement tags for editor
+  // refs: https://redmine.weseek.co.jp/issues/132125
+  // useEffect(() => {
+  //   // Run only when tagsInfoData has been updated
+  //   if (templateTagData == null) {
+  //     syncPageTagsForEditors();
+  //   }
+  //   // eslint-disable-next-line react-hooks/exhaustive-deps
+  // }, [tagsInfoData?.tags]);
+
+  // TODO: implement tags for editor
+  // refs: https://redmine.weseek.co.jp/issues/132125
+  // useEffect(() => {
+  //   if (pageId === null && templateTagData != null) {
+  //     mutatePageTagsForEditors(templateTagData);
+  //   }
+  // }, [pageId, mutatePageTagsForEditors, templateTagData, mutateSWRTagsInfo]);
 
   const [isPageTemplateModalShown, setIsPageTempleteModalShown] = useState(false);
 
@@ -257,30 +255,13 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   const isViewMode = editorMode === EditorMode.View;
 
 
-  const tagsUpdatedHandlerForViewMode = useCallback(async(newTags: string[]) => {
-    if (currentPage == null) {
-      return;
-    }
-
-    const { _id: pageId, revision: revisionId } = currentPage;
-    try {
-      await apiPost('/tags.update', { pageId, revisionId, tags: newTags });
-
-      updateStateAfterSave?.();
-
-      toastSuccess('updated tags successfully');
-    }
-    catch (err) {
-      toastError(err);
-    }
-
-  }, [currentPage, updateStateAfterSave]);
-
-  const tagsUpdatedHandlerForEditMode = useCallback((newTags: string[]): void => {
-    // It will not be reflected in the DB until the page is refreshed
-    mutatePageTagsForEditors(newTags);
-    return;
-  }, [mutatePageTagsForEditors]);
+  // TODO: implement tags for editor
+  // refs: https://redmine.weseek.co.jp/issues/132125
+  // const tagsUpdatedHandlerForEditMode = useCallback((newTags: string[]): void => {
+  //   // It will not be reflected in the DB until the page is refreshed
+  //   mutatePageTagsForEditors(newTags);
+  //   return;
+  // }, [mutatePageTagsForEditors]);
 
   const duplicateItemClickedHandler = useCallback(async(page: IPageForPageDuplicateModal) => {
     const duplicatedHandler: OnDuplicatedFunction = (fromPath, toPath) => {
@@ -395,9 +376,9 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
             )}
             {isAbleToChangeEditorMode && (
               <PageEditorModeManager
-                onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
-                isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
                 editorMode={editorMode}
+                isBtnDisabled={!!isGuestUser || !!isReadOnlyUser}
+                onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
               />
             )}
           </div>
@@ -440,12 +421,8 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
       pagePath={pagePath}
       pageId={currentPage?._id}
       showDrawerToggler={isDrawerMode}
-      showTagLabel={isAbleToShowTagLabel}
-      isTagLabelsDisabled={!!isGuestUser || !!isReadOnlyUser}
       isDrawerMode={isDrawerMode}
       isCompactMode={isCompactMode}
-      tags={isViewMode ? tagsInfoData?.tags : tagsForEditors}
-      tagsUpdatedHandler={isViewMode ? tagsUpdatedHandlerForViewMode : tagsUpdatedHandlerForEditMode}
       rightComponent={RightComponent}
       additionalClasses={['container-fluid']}
     />

+ 0 - 4
apps/app/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -33,10 +33,6 @@
       line-height: 1.4em;
     }
 
-    .grw-taglabels-container {
-      margin-bottom: 0.5rem;
-    }
-
     .grw-page-path-nav {
       .separator {
         margin-right: 0.2em;

+ 2 - 22
apps/app/src/components/Navbar/GrowiSubNavigation.tsx

@@ -1,12 +1,9 @@
 import React from 'react';
 
-import dynamic from 'next/dynamic';
-
 import {
   EditorMode, useEditorMode,
 } from '~/stores/ui';
 
-import { TagLabelsSkeleton } from '../Page/TagLabels';
 import PagePathNav from '../PagePathNav';
 
 import DrawerToggler from './DrawerToggler';
@@ -15,23 +12,15 @@ import DrawerToggler from './DrawerToggler';
 import styles from './GrowiSubNavigation.module.scss';
 
 
-const TagLabels = dynamic(() => import('../Page/TagLabels').then(mod => mod.TagLabels), {
-  ssr: false,
-  loading: TagLabelsSkeleton,
-});
-
-
 export type GrowiSubNavigationProps = {
   pagePath?: string,
   pageId?: string,
   isNotFound?: boolean,
   showDrawerToggler?: boolean,
-  showTagLabel?: boolean,
   isTagLabelsDisabled?: boolean,
   isDrawerMode?: boolean,
   isCompactMode?: boolean,
   tags?: string[],
-  tagsUpdatedHandler?: (newTags: string[]) => Promise<void> | void,
   rightComponent?: React.FunctionComponent,
   additionalClasses?: string[],
 }
@@ -42,9 +31,8 @@ export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element
 
   const {
     pageId, pagePath,
-    showDrawerToggler, showTagLabel,
-    isTagLabelsDisabled, isDrawerMode, isCompactMode,
-    tags, tagsUpdatedHandler,
+    showDrawerToggler,
+    isDrawerMode, isCompactMode,
     rightComponent: RightComponent,
     additionalClasses = [],
   } = props;
@@ -67,14 +55,6 @@ export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element
           </div>
         ) }
         <div className="grw-path-nav-container">
-          { (showTagLabel && !isCompactMode) && (
-            <div className="grw-taglabels-container">
-              { tags != null
-                ? <TagLabels tags={tags} isTagLabelsDisabled={isTagLabelsDisabled ?? false} tagsUpdateInvoked={tagsUpdatedHandler} />
-                : <TagLabelsSkeleton />
-              }
-            </div>
-          ) }
           { pagePath != null && (
             <PagePathNav pageId={pageId} pagePath={pagePath} isSingleLineMode={isEditorMode} isCompactMode={isCompactMode} />
           ) }

+ 0 - 127
apps/app/src/components/Navbar/PageEditorModeManager.jsx

@@ -1,127 +0,0 @@
-import React, { useCallback } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import { useIsAdmin, useHackmdUri } from '~/stores/context';
-import { EditorMode, useIsDeviceSmallerThanMd } from '~/stores/ui';
-
-import styles from './PageEditorModeManager.module.scss';
-
-/* eslint-disable react/prop-types */
-const PageEditorModeButtonWrapper = React.memo(({
-  editorMode, isBtnDisabled, onClick, targetMode, icon, label, id,
-}) => {
-  const classNames = [`btn btn-outline-primary ${targetMode}-button px-1`];
-  if (editorMode === targetMode) {
-    classNames.push('active');
-  }
-  if (isBtnDisabled) {
-    classNames.push('disabled');
-  }
-
-  return (
-    <button
-      type="button"
-      className={classNames.join(' ')}
-      onClick={() => { onClick(targetMode) }}
-      id={id}
-      data-testid={`${targetMode}-button`}
-    >
-      <span className="d-flex flex-column flex-md-row justify-content-center">
-        <span className="grw-page-editor-mode-manager-icon me-md-1">{icon}</span>
-        <span className="grw-page-editor-mode-manager-label">{label}</span>
-      </span>
-    </button>
-  );
-});
-/* eslint-enable react/prop-types */
-
-PageEditorModeButtonWrapper.displayName = 'PageEditorModeButtonWrapper';
-
-function PageEditorModeManager(props) {
-  const {
-    editorMode, onPageEditorModeButtonClicked, isBtnDisabled,
-  } = props;
-
-  const { t } = useTranslation();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-  const { data: hackmdUri } = useHackmdUri();
-
-  const { data: isAdmin } = useIsAdmin();
-  const isHackmdEnabled = hackmdUri != null;
-  const showHackmdBtn = isHackmdEnabled || isAdmin;
-
-  const pageEditorModeButtonClickedHandler = useCallback((viewType) => {
-    if (isBtnDisabled) {
-      return;
-    }
-    if (onPageEditorModeButtonClicked != null) {
-      onPageEditorModeButtonClicked(viewType);
-    }
-  }, [isBtnDisabled, onPageEditorModeButtonClicked]);
-
-  return (
-    <>
-      <div
-        className={`btn-group grw-page-editor-mode-manager ${styles['grw-page-editor-mode-manager']}`}
-        role="group"
-        aria-label="page-editor-mode-manager"
-        id="grw-page-editor-mode-manager"
-      >
-        {(!isDeviceSmallerThanMd || editorMode !== EditorMode.View) && (
-          <PageEditorModeButtonWrapper
-            editorMode={editorMode}
-            isBtnDisabled={isBtnDisabled}
-            onClick={pageEditorModeButtonClickedHandler}
-            targetMode={EditorMode.View}
-            icon={<i className="icon-control-play" />}
-            label={t('view')}
-          />
-        )}
-        {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && (
-          <PageEditorModeButtonWrapper
-            editorMode={editorMode}
-            isBtnDisabled={isBtnDisabled}
-            onClick={pageEditorModeButtonClickedHandler}
-            targetMode={EditorMode.Editor}
-            icon={<i className="icon-note" />}
-            label={t('Edit')}
-          />
-        )}
-        {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && showHackmdBtn && (
-          <>
-            <PageEditorModeButtonWrapper
-              editorMode={editorMode}
-              isBtnDisabled={isBtnDisabled || !isHackmdEnabled}
-              onClick={isHackmdEnabled ? pageEditorModeButtonClickedHandler : undefined}
-              targetMode={EditorMode.HackMD}
-              icon={<i className="fa fa-file-text-o" />}
-              label={t('hackmd.hack_md')}
-              id="grw-page-editor-mode-manager-hackmd-button"
-            />
-            { !isHackmdEnabled && (
-              <UncontrolledTooltip placement="top" target="grw-page-editor-mode-manager-hackmd-button" fade={false}>
-                {t('hackmd.not_set_up')}
-              </UncontrolledTooltip>
-            )}
-          </>
-        )}
-      </div>
-    </>
-  );
-
-}
-
-PageEditorModeManager.propTypes = {
-  onPageEditorModeButtonClicked: PropTypes.func,
-  isBtnDisabled: PropTypes.bool,
-  editorMode: PropTypes.string,
-};
-
-PageEditorModeManager.defaultProps = {
-  isBtnDisabled: false,
-};
-
-export default PageEditorModeManager;

+ 50 - 24
apps/app/src/components/Navbar/PageEditorModeManager.module.scss

@@ -2,41 +2,67 @@
 @use '@growi/core/scss/bootstrap/init' as bs;
 @use '~/styles/mixins';
 
-$btn-line-height: 1.2rem;
-
 .grw-page-editor-mode-manager :global {
   .btn {
+    --bs-btn-font-size: 13px;
+    --bs-btn-border-width: 2px;
+
     width: 70px;
+
     white-space: nowrap;
 
     @include mixins.border-vertical('before', 70%, 1, true);
-
-    &.view-button,
-    &.edit-button {
-      line-height: $btn-line-height;
-      .grw-page-editor-mode-manager-icon {
-        @include bs.media-breakpoint-down(sm) {
-          font-size: 1.2rem;
-        }
-      }
-    }
-    &.hackmd-button {
-      line-height: $btn-line-height;
-      .grw-page-editor-mode-manager-icon {
-        @include bs.media-breakpoint-down(sm) {
-          font-size: 1.2rem;
-        }
-      }
-      .grw-page-editor-mode-manager-label {
-        font-size: 12px;
-        letter-spacing: -0.6px;
+    .grw-page-editor-mode-manager-icon {
+      @include bs.media-breakpoint-down(sm) {
+        font-size: 16px;
       }
     }
   }
 }
 
 .grw-page-editor-mode-manager-skeleton :global {
+  width: 139px;
+  height: calc(var(--bs-btn-line-height) + bs.$btn-padding-y*2 + bs.$btn-border-width*2);
+}
 
-  width: 213px;
-  height: calc($btn-line-height + bs.$btn-padding-y*2 + bs.$btn-border-width*2);
+// == Colors
+@include bs.color-mode(light) {
+  .grw-page-editor-mode-manager :global {
+    .btn-outline-primary {
+      $color: var(--grw-page-editor-mode-manager-btn-color, var(--grw-primary-700));
+      $bg: var(--grw-page-editor-mode-manager-btn-bg, var(--grw-primary-100));
+      $bg-rgb: var(--grw-page-editor-mode-manager-btn-bg-rgb, var(--grw-primary-100-rgb));
+
+      --bs-btn-color: #{$color};
+      --bs-btn-border-color: #{$bg};
+      --bs-btn-hover-color: #{$color};
+      --bs-btn-hover-bg: rgba(#{$bg-rgb}, 0.5);
+      --bs-btn-hover-border-color: #{$bg};
+      --bs-btn-active-color: #{$color};
+      --bs-btn-active-bg: #{$bg};
+      --bs-btn-active-border-color: #{$bg};
+      --bs-btn-disabled-color: $color;
+      --bs-btn-disabled-border-color: #{$bg};
+    }
+  }
+}
+@include bs.color-mode(dark) {
+  .grw-page-editor-mode-manager :global {
+    .btn-outline-primary {
+      $color: var(--grw-page-editor-mode-manager-btn-color, var(--grw-primary-300));
+      $bg: var(--grw-page-editor-mode-manager-btn-bg, var(--grw-primary-800));
+      $bg-rgb: var(--grw-page-editor-mode-manager-btn-bg-rgb, var(--grw-primary-800-rgb));
+
+      --bs-btn-color: #{$color};
+      --bs-btn-border-color: #{$bg};
+      --bs-btn-hover-color: #{$color};
+      --bs-btn-hover-bg: rgba(#{$bg-rgb}, 0.5);
+      --bs-btn-hover-border-color: #{$bg};
+      --bs-btn-active-color: #{$color};
+      --bs-btn-active-bg: #{$bg};
+      --bs-btn-active-border-color: #{$bg};
+      --bs-btn-disabled-color: $color;
+      --bs-btn-disabled-border-color: #{$bg};
+    }
+  }
 }

+ 103 - 0
apps/app/src/components/Navbar/PageEditorModeManager.tsx

@@ -0,0 +1,103 @@
+import React, { type ReactNode, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { EditorMode, useIsDeviceSmallerThanMd } from '~/stores/ui';
+
+import styles from './PageEditorModeManager.module.scss';
+
+
+type PageEditorModeButtonProps = {
+  currentEditorMode: EditorMode,
+  editorMode: EditorMode,
+  icon: ReactNode,
+  label: ReactNode,
+  isBtnDisabled?: boolean,
+  onClick?: (mode: EditorMode) => void,
+}
+const PageEditorModeButton = React.memo((props: PageEditorModeButtonProps) => {
+  const {
+    currentEditorMode, isBtnDisabled, editorMode, icon, label, onClick,
+  } = props;
+
+  const classNames = ['btn btn-outline-primary px-1'];
+  if (currentEditorMode === editorMode) {
+    classNames.push('active');
+  }
+  if (isBtnDisabled) {
+    classNames.push('disabled');
+  }
+
+  return (
+    <button
+      type="button"
+      className={classNames.join(' ')}
+      onClick={() => onClick?.(editorMode)}
+      data-testid={`${editorMode}-button`}
+    >
+      <span className="d-flex flex-column flex-md-row justify-content-center">
+        <span className="grw-page-editor-mode-manager-icon me-md-1">{icon}</span>
+        <span className="grw-page-editor-mode-manager-label">{label}</span>
+      </span>
+    </button>
+  );
+});
+
+type Props = {
+  editorMode: EditorMode | undefined,
+  onPageEditorModeButtonClicked?: (editorMode: EditorMode) => void,
+  isBtnDisabled?: boolean,
+}
+
+export const PageEditorModeManager = (props: Props): JSX.Element => {
+  const {
+    editorMode = EditorMode.View,
+    isBtnDisabled,
+    onPageEditorModeButtonClicked,
+  } = props;
+
+  const { t } = useTranslation();
+  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
+
+  const pageEditorModeButtonClickedHandler = useCallback((viewType) => {
+    if (isBtnDisabled ?? false) {
+      return;
+    }
+    if (onPageEditorModeButtonClicked != null) {
+      onPageEditorModeButtonClicked(viewType);
+    }
+  }, [isBtnDisabled, onPageEditorModeButtonClicked]);
+
+  return (
+    <>
+      <div
+        className={`btn-group grw-page-editor-mode-manager ${styles['grw-page-editor-mode-manager']}`}
+        role="group"
+        aria-label="page-editor-mode-manager"
+        id="grw-page-editor-mode-manager"
+      >
+        {(!isDeviceSmallerThanMd || editorMode !== EditorMode.View) && (
+          <PageEditorModeButton
+            currentEditorMode={editorMode}
+            editorMode={EditorMode.View}
+            isBtnDisabled={isBtnDisabled}
+            onClick={pageEditorModeButtonClickedHandler}
+            icon={<i className="icon-control-play" />}
+            label={t('view')}
+          />
+        )}
+        {(!isDeviceSmallerThanMd || editorMode === EditorMode.View) && (
+          <PageEditorModeButton
+            currentEditorMode={editorMode}
+            editorMode={EditorMode.Editor}
+            isBtnDisabled={isBtnDisabled}
+            onClick={pageEditorModeButtonClickedHandler}
+            icon={<i className="icon-note" />}
+            label={t('Edit')}
+          />
+        )}
+      </div>
+    </>
+  );
+
+};

+ 0 - 2
apps/app/src/components/Page/DisplaySwitcher.tsx

@@ -3,7 +3,6 @@ import React from 'react';
 import dynamic from 'next/dynamic';
 
 
-import { useHackmdDraftUpdatedEffect } from '~/client/services/side-effects/hackmd-draft-updated';
 import { useHashChangedEffect } from '~/client/services/side-effects/hash-changed';
 import { usePageUpdatedEffect } from '~/client/services/side-effects/page-updated';
 import { useIsEditable } from '~/stores/context';
@@ -28,7 +27,6 @@ export const DisplaySwitcher = (props: Props): JSX.Element => {
 
   usePageUpdatedEffect();
   useHashChangedEffect();
-  useHackmdDraftUpdatedEffect();
 
   const isViewMode = editorMode === EditorMode.View;
 

+ 1 - 0
apps/app/src/components/PageEditor/Editor.tsx

@@ -24,6 +24,7 @@ import { Cheatsheet } from './Cheatsheet';
 import pasteHelper from './PasteHelper';
 import TextAreaEditor from './TextAreaEditor';
 
+
 import styles from './Editor.module.scss';
 
 export type EditorPropsType = {

+ 3 - 4
apps/app/src/components/PageEditor/EditorNavbarBottom.tsx

@@ -9,7 +9,7 @@ import { useIsSlackConfigured } from '~/stores/context';
 import { useSWRxSlackChannels, useIsSlackEnabled } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 import {
-  EditorMode, useDrawerOpened, useEditorMode, useIsDeviceSmallerThanMd,
+  useDrawerOpened, useEditorMode, useIsDeviceSmallerThanMd,
 } from '~/stores/ui';
 
 
@@ -76,8 +76,7 @@ const EditorNavbarBottom = (): JSX.Element => {
     </div>
   );
 
-  const isOptionsSelectorEnabled = editorMode !== EditorMode.HackMD;
-  const isCollapsedOptionsSelectorEnabled = isOptionsSelectorEnabled && isDeviceSmallerThanMd;
+  const isCollapsedOptionsSelectorEnabled = isDeviceSmallerThanMd;
 
   return (
     <div className={`${isCollapsedOptionsSelectorEnabled ? 'fixed-bottom' : ''} `}>
@@ -103,7 +102,7 @@ const EditorNavbarBottom = (): JSX.Element => {
       <div className={`flex-expand-horiz align-items-center border-top px-2 px-md-3 ${additionalClasses.join(' ')}`}>
         <form>
           { isDeviceSmallerThanMd && renderDrawerButton() }
-          { isOptionsSelectorEnabled && !isDeviceSmallerThanMd && <OptionsSelector /> }
+          { !isDeviceSmallerThanMd && <OptionsSelector /> }
         </form>
         <form className="flex-nowrap ms-auto">
           {/* Responsive Design for the SlackNotification */}

+ 58 - 58
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -296,68 +296,67 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   }, [isNotFound, mutateEditorMode, router, save, t, updateStateAfterSave]);
 
 
-  /**
-   * the upload event handler
-   * @param {any} file
-   */
-  const uploadHandler = useCallback(async(file) => {
-    try {
-      // eslint-disable-next-line @typescript-eslint/no-explicit-any
-      let res: any = await apiGet('/attachments.limit', {
-        fileSize: file.size,
-      });
-
-      if (!res.isUploadable) {
-        throw new Error(res.errorMessage);
+  // the upload event handler
+  const uploadHandler = useCallback((files: File[]) => {
+    files.forEach(async(file) => {
+      try {
+        // eslint-disable-next-line @typescript-eslint/no-explicit-any
+        const resLimit: any = await apiGet('/attachments.limit', {
+          fileSize: file.size,
+        });
+
+        if (!resLimit.isUploadable) {
+          throw new Error(resLimit.errorMessage);
+        }
+
+        const formData = new FormData();
+        formData.append('file', file);
+        if (currentPagePath != null) {
+          formData.append('path', currentPagePath);
+        }
+        if (pageId != null) {
+          formData.append('page_id', pageId);
+        }
+        if (pageId == null) {
+          formData.append('page_body', codeMirrorEditor?.getDoc() ?? '');
+        }
+
+        const resAdd: any = await apiPostForm('/attachments.add', formData);
+        const attachment = resAdd.attachment;
+        const fileName = attachment.originalName;
+
+        let insertText = `[${fileName}](${attachment.filePathProxied})\n`;
+        // when image
+        if (attachment.fileFormat.startsWith('image/')) {
+          // modify to "![fileName](url)" syntax
+          insertText = `!${insertText}`;
+        }
+        // TODO: implement
+        // refs: https://redmine.weseek.co.jp/issues/126528
+        // editorRef.current.insertText(insertText);
+        codeMirrorEditor?.insertText(insertText);
+
+        // when if created newly
+        // Not using 'mutateGrant' to inherit the grant of the parent page
+        if (resAdd.pageCreated) {
+          logger.info('Page is created', resAdd.page._id);
+          mutateIsLatestRevision(true);
+          setCreatedPageRevisionIdWithAttachment(resAdd.page.revision);
+          await mutateCurrentPageId(resAdd.page._id);
+          await mutateCurrentPage();
+        }
       }
-
-      const formData = new FormData();
-      // const { pageId, path } = pageContainer.state;
-      formData.append('file', file);
-      if (currentPagePath != null) {
-        formData.append('path', currentPagePath);
-      }
-      if (pageId != null) {
-        formData.append('page_id', pageId);
+      catch (e) {
+        logger.error('failed to upload', e);
+        toastError(e);
       }
-      if (pageId == null) {
-        formData.append('page_body', codeMirrorEditor?.getDoc() ?? '');
+      finally {
+        // TODO: implement
+        // refs: https://redmine.weseek.co.jp/issues/126528
+        // editorRef.current.terminateUploadingState();
       }
+    });
 
-      res = await apiPostForm('/attachments.add', formData);
-      const attachment = res.attachment;
-      const fileName = attachment.originalName;
-
-      let insertText = `[${fileName}](${attachment.filePathProxied})`;
-      // when image
-      if (attachment.fileFormat.startsWith('image/')) {
-        // modify to "![fileName](url)" syntax
-        insertText = `!${insertText}`;
-      }
-      // TODO: implement
-      // refs: https://redmine.weseek.co.jp/issues/126528
-      // editorRef.current.insertText(insertText);
-
-      // when if created newly
-      // Not using 'mutateGrant' to inherit the grant of the parent page
-      if (res.pageCreated) {
-        logger.info('Page is created', res.page._id);
-        globalEmitter.emit('resetInitializedHackMdStatus');
-        mutateIsLatestRevision(true);
-        setCreatedPageRevisionIdWithAttachment(res.page.revision);
-        await mutateCurrentPageId(res.page._id);
-        await mutateCurrentPage();
-      }
-    }
-    catch (e) {
-      logger.error('failed to upload', e);
-      toastError(e);
-    }
-    finally {
-      // TODO: implement
-      // refs: https://redmine.weseek.co.jp/issues/126528
-      // editorRef.current.terminateUploadingState();
-    }
   }, [codeMirrorEditor, currentPagePath, mutateCurrentPage, mutateCurrentPageId, mutateIsLatestRevision, pageId]);
 
 
@@ -568,6 +567,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
         <CodeMirrorEditorMain
           onChange={markdownChangedHandler}
           onSave={saveWithShortcut}
+          onUpload={uploadHandler}
           indentSize={currentIndentSize ?? defaultIndentSize}
           pageId={pageId}
           userName={user?.name}

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

@@ -21,7 +21,6 @@
     width: 3rem;
     height: 3rem;
     font-size: 1.5rem;
-    color: var(--color-global);
     opacity: 0.3;
   }
 

+ 65 - 0
apps/app/src/components/PageSelectModal/PageSelectModal.tsx

@@ -0,0 +1,65 @@
+import React from 'react';
+
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter, Button,
+} from 'reactstrap';
+
+import { useTargetAndAncestors, useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
+import { usePageSelectModal } from '~/stores/modal';
+import { useCurrentPagePath, useCurrentPageId } from '~/stores/page';
+
+import { ItemsTree } from '../ItemsTree';
+
+import { TreeItemForModal } from './TreeItemForModal';
+
+
+export const PageSelectModal = () => {
+  const {
+    data: PageSelectModalData,
+    close: closeModal,
+  } = usePageSelectModal();
+
+  const isOpened = PageSelectModalData?.isOpened ?? false;
+
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isReadOnlyUser } = useIsReadOnlyUser();
+  const { data: currentPath } = useCurrentPagePath();
+  const { data: targetId } = useCurrentPageId();
+  const { data: targetAndAncestorsData } = useTargetAndAncestors();
+
+  const targetPathOrId = targetId || currentPath;
+
+  if (isGuestUser == null) {
+    return null;
+  }
+
+  const path = currentPath || '/';
+
+  return (
+    <Modal
+      isOpen={isOpened}
+      toggle={() => closeModal()}
+      centered
+    >
+      <ModalHeader toggle={() => closeModal()}>modal</ModalHeader>
+      <ModalBody>
+        <ItemsTree
+          CustomTreeItem={TreeItemForModal}
+          isEnableActions={!isGuestUser}
+          isReadOnlyUser={!!isReadOnlyUser}
+          targetPath={path}
+          targetPathOrId={targetPathOrId}
+          targetAndAncestorsData={targetAndAncestorsData}
+        />
+      </ModalBody>
+      <ModalFooter>
+        <Button color="primary">
+          Do Something
+        </Button>{' '}
+        <Button color="secondary">
+          Cancel
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+};

+ 30 - 0
apps/app/src/components/PageSelectModal/TreeItemForModal.tsx

@@ -0,0 +1,30 @@
+import React, { FC } from 'react';
+
+import {
+  SimpleItem, SimpleItemProps, SimpleItemTool, useNewPageInput,
+} from '../TreeItem';
+
+type Optional = 'itemRef' | 'itemClass' | 'mainClassName';
+type PageTreeItemProps = Omit<SimpleItemProps, Optional> & {key};
+
+export const TreeItemForModal: FC<PageTreeItemProps> = (props) => {
+
+  const { NewPageInputWrapper, NewPageCreateButtonWrapper } = useNewPageInput();
+
+  return (
+    <SimpleItem
+      key={props.key}
+      targetPathOrId={props.targetPathOrId}
+      itemNode={props.itemNode}
+      isOpen
+      isEnableActions={props.isEnableActions}
+      isReadOnlyUser={props.isReadOnlyUser}
+      onRenamed={props.onRenamed}
+      onClickDuplicateMenuItem={props.onClickDuplicateMenuItem}
+      onClickDeleteMenuItem={props.onClickDeleteMenuItem}
+      customNextComponents={[NewPageInputWrapper]}
+      itemClass={TreeItemForModal}
+      customEndComponents={[SimpleItemTool, NewPageCreateButtonWrapper]}
+    />
+  );
+};

+ 0 - 0
apps/app/src/components/PageSideContents.module.scss → apps/app/src/components/PageSideContents/PageSideContents.module.scss


+ 71 - 7
apps/app/src/components/PageSideContents.tsx → apps/app/src/components/PageSideContents/PageSideContents.tsx

@@ -1,17 +1,24 @@
-import React from 'react';
+import React, { useCallback } from 'react';
 
-import type { IPageHasId, IPageInfoForOperation } from '@growi/core';
+import { getIdForRef, type IPageHasId, type IPageInfoForOperation } from '@growi/core';
 import { pagePathUtils } from '@growi/core/dist/utils';
 import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
 import { Link } from 'react-scroll';
 
+import { useUpdateStateAfterSave } from '~/client/services/page-operation';
+import { apiPost } from '~/client/util/apiv1-client';
+import { toastSuccess, toastError } from '~/client/util/toastr';
+import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
-import { useSWRxPageInfo } from '~/stores/page';
+import { useSWRxPageInfo, useSWRxTagsInfo } from '~/stores/page';
+import { useIsAbleToShowTagLabel } from '~/stores/ui';
 
-import CountBadge from './Common/CountBadge';
-import { ContentLinkButtons } from './ContentLinkButtons';
-import PageListIcon from './Icons/PageListIcon';
-import TableOfContents from './TableOfContents';
+import CountBadge from '../Common/CountBadge';
+import { ContentLinkButtons } from '../ContentLinkButtons';
+import PageListIcon from '../Icons/PageListIcon';
+import { PageTagsSkeleton } from '../PageTags';
+import TableOfContents from '../TableOfContents';
 
 import styles from './PageSideContents.module.scss';
 
@@ -19,6 +26,59 @@ import styles from './PageSideContents.module.scss';
 const { isTopPage, isUsersHomepage, isTrashPage } = pagePathUtils;
 
 
+const PageTags = dynamic(() => import('../PageTags').then(mod => mod.PageTags), {
+  ssr: false,
+  loading: PageTagsSkeleton,
+});
+
+
+type TagsProps = {
+  pageId: string,
+  revisionId: string,
+}
+
+const Tags = (props: TagsProps): JSX.Element => {
+  const { pageId, revisionId } = props;
+
+  const { data: tagsInfoData } = useSWRxTagsInfo(pageId);
+
+  const { data: showTagLabel } = useIsAbleToShowTagLabel();
+  const { data: isGuestUser } = useIsGuestUser();
+  const { data: isReadOnlyUser } = useIsReadOnlyUser();
+
+  const updateStateAfterSave = useUpdateStateAfterSave(pageId);
+
+  const tagsUpdatedHandler = useCallback(async(newTags: string[]) => {
+    try {
+      await apiPost('/tags.update', { pageId, revisionId, tags: newTags });
+
+      updateStateAfterSave?.();
+
+      toastSuccess('updated tags successfully');
+    }
+    catch (err) {
+      toastError(err);
+    }
+
+  }, [pageId, revisionId, updateStateAfterSave]);
+
+  if (!showTagLabel) {
+    return <></>;
+  }
+
+  const isTagLabelsDisabled = !!isGuestUser || !!isReadOnlyUser;
+
+  return (
+    <div className="grw-taglabels-container">
+      { tagsInfoData?.tags != null
+        ? <PageTags tags={tagsInfoData.tags} isTagLabelsDisabled={isTagLabelsDisabled ?? false} tagsUpdateInvoked={tagsUpdatedHandler} />
+        : <PageTagsSkeleton />
+      }
+    </div>
+  );
+};
+
+
 export type PageSideContentsProps = {
   page: IPageHasId,
   isSharedUser?: boolean,
@@ -38,8 +98,12 @@ export const PageSideContents = (props: PageSideContentsProps): JSX.Element => {
   const isUsersHomepagePath = isUsersHomepage(pagePath);
   const isTrash = isTrashPage(pagePath);
 
+
   return (
     <>
+      {/* Tags */}
+      <Tags pageId={page._id} revisionId={getIdForRef(page.revision)} />
+
       {/* Page list */}
       <div className={`grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
         {!isSharedUser && (

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

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

+ 19 - 61
apps/app/src/components/PageStatusAlert.tsx

@@ -5,13 +5,9 @@ import * as ReactDOMServer from 'react-dom/server';
 
 import { useIsGuestUser, useIsReadOnlyUser } from '~/stores/context';
 import { useEditingMarkdown, useIsConflict } from '~/stores/editor';
-import {
-  useHasDraftOnHackmd, useIsHackmdDraftUpdatingInRealtime, useRevisionIdHackmdSynced,
-} from '~/stores/hackmd';
 import { useConflictDiffModal } from '~/stores/modal';
 import { useSWRMUTxCurrentPage, useSWRxCurrentPage } from '~/stores/page';
 import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
-import { EditorMode, useEditorMode } from '~/stores/ui';
 
 import { Username } from './User/Username';
 
@@ -26,17 +22,13 @@ type AlertComponentContents = {
 export const PageStatusAlert = (): JSX.Element => {
 
   const { t } = useTranslation();
-  const { data: isHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
-  const { data: hasDraftOnHackmd } = useHasDraftOnHackmd();
   const { data: isConflict } = useIsConflict();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { open: openConflictDiffModal } = useConflictDiffModal();
-  const { mutate: mutateEditorMode } = useEditorMode();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isReadOnlyUser } = useIsReadOnlyUser();
 
   // store remote latest page data
-  const { data: revisionIdHackmdSynced } = useRevisionIdHackmdSynced();
   const { data: remoteRevisionId } = useRemoteRevisionId();
   const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
@@ -53,37 +45,23 @@ export const PageStatusAlert = (): JSX.Element => {
     openConflictDiffModal();
   }, [openConflictDiffModal]);
 
-  const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
-    return {
-      additionalClasses: ['bg-success', 'd-hackmd-none'],
-      label:
-  <>
-    <i className="icon-fw icon-people"></i>
-    {t('hackmd.someone_editing')}
-  </>,
-      btn:
-  <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
-    <i className="fa fa-fw fa-file-text-o me-1"></i>
-    Open HackMD Editor
-  </a>,
-    };
-  }, [t]);
-
-  const getContentsForDraftExistsAlert = useCallback((): AlertComponentContents => {
-    return {
-      additionalClasses: ['bg-success', 'd-hackmd-none'],
-      label:
-  <>
-    <i className="icon-fw icon-pencil"></i>
-    {t('hackmd.this_page_has_draft')}
-  </>,
-      btn:
-  <button type="button" onClick={() => mutateEditorMode(EditorMode.HackMD)} className="btn btn-outline-white">
-    <i className="fa fa-fw fa-file-text-o me-1"></i>
-    Open HackMD Editor
-  </button>,
-    };
-  }, [mutateEditorMode, t]);
+  // TODO: re-impl for builtin editor
+  //
+  // const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
+  //   return {
+  //     additionalClasses: ['bg-success', 'd-hackmd-none'],
+  //     label:
+  // <>
+  //   <i className="icon-fw icon-people"></i>
+  //   {t('hackmd.someone_editing')}
+  // </>,
+  //     btn:
+  // <a href="#hackmd" key="btnOpenHackmdSomeoneEditing" className="btn btn-outline-white">
+  //   <i className="fa fa-fw fa-file-text-o me-1"></i>
+  //   Open HackMD Editor
+  // </a>,
+  //   };
+  // }, [t]);
 
   const getContentsForUpdatedAlert = useCallback((): AlertComponentContents => {
 
@@ -123,37 +101,17 @@ export const PageStatusAlert = (): JSX.Element => {
 
   const alertComponentContents = useMemo(() => {
     const isRevisionOutdated = revision?._id !== remoteRevisionId;
-    const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
 
     // 'revision?._id' and 'remoteRevisionId' are can not be undefined
     if (revision?._id == null || remoteRevisionId == null) { return }
 
     // when remote revision is newer than both
-    if (isHackmdDocumentOutdated && isRevisionOutdated) {
+    if (isRevisionOutdated) {
       return getContentsForUpdatedAlert();
     }
 
-    // when someone editing with HackMD
-    if (isHackmdDraftUpdatingInRealtime) {
-      return getContentsForSomeoneEditingAlert();
-    }
-
-    // when the draft of HackMD is newest
-    if (hasDraftOnHackmd) {
-      return getContentsForDraftExistsAlert();
-    }
-
     return null;
-  }, [
-    revision?._id,
-    remoteRevisionId,
-    revisionIdHackmdSynced,
-    isHackmdDraftUpdatingInRealtime,
-    hasDraftOnHackmd,
-    getContentsForUpdatedAlert,
-    getContentsForSomeoneEditingAlert,
-    getContentsForDraftExistsAlert,
-  ]);
+  }, [revision?._id, remoteRevisionId, getContentsForUpdatedAlert]);
 
   if (!!isGuestUser || !!isReadOnlyUser || alertComponentContents == null) { return <></> }
 

+ 3 - 4
apps/app/src/components/Page/TagLabels.tsx → apps/app/src/components/PageTags/PageTags.tsx

@@ -13,11 +13,11 @@ type Props = {
   tagsUpdateInvoked?: (tags: string[]) => Promise<void> | void,
 }
 
-export const TagLabelsSkeleton = (): JSX.Element => {
+export const PageTagsSkeleton = (): JSX.Element => {
   return <Skeleton additionalClass={`${styles['grw-tag-labels-skeleton']} py-1`} />;
 };
 
-export const TagLabels:FC<Props> = (props: Props) => {
+export const PageTags:FC<Props> = (props: Props) => {
   const { tags, isTagLabelsDisabled, tagsUpdateInvoked } = props;
 
   const [isTagEditModalShown, setIsTagEditModalShown] = useState(false);
@@ -31,13 +31,12 @@ export const TagLabels:FC<Props> = (props: Props) => {
   };
 
   if (tags == null) {
-    return <TagLabelsSkeleton />;
+    return <PageTagsSkeleton />;
   }
 
   return (
     <>
       <div className={`${styles['grw-tag-labels']} grw-tag-labels d-flex align-items-center`} data-testid="grw-tag-labels">
-        <i className="tag-icon icon-tag me-2" />
         <RenderTagLabels
           tags={tags}
           openEditorModal={openEditorModal}

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