Przeglądaj źródła

Merge branch 'feat/multiple-group-grant-for-page' into feat/136137-136537-page-grant-update-for-multiple-group-grant

Futa Arai 2 lat temu
rodzic
commit
8614528ba8
100 zmienionych plików z 1438 dodań i 669 usunięć
  1. 3 1
      .devcontainer/Dockerfile
  2. 2 1
      .devcontainer/devcontainer.json
  3. 2 36
      .devcontainer/docker-compose.yml
  4. 3 3
      .github/workflows/ci-app-prod.yml
  5. 18 18
      .github/workflows/ci-app.yml
  6. 12 9
      .github/workflows/reusable-app-prod.yml
  7. 2 2
      .github/workflows/reusable-app-reg-suit.yml
  8. 1 2
      README.md
  9. 1 2
      README_JP.md
  10. 1 0
      _obsolete/packages/.eslintignore
  11. 0 0
      _obsolete/packages/hackmd/.eslintignore
  12. 0 0
      _obsolete/packages/hackmd/.gitignore
  13. 1 1
      _obsolete/packages/hackmd/package.json
  14. 0 0
      _obsolete/packages/hackmd/src/hackmd-agent.js
  15. 0 0
      _obsolete/packages/hackmd/src/hackmd-styles.ts
  16. 0 0
      _obsolete/packages/hackmd/src/index.ts
  17. 0 0
      _obsolete/packages/hackmd/src/style.scss
  18. 0 0
      _obsolete/packages/hackmd/tsconfig.json
  19. 0 0
      _obsolete/packages/hackmd/vite.config.js
  20. 0 2
      apps/app/.env.development
  21. 0 2
      apps/app/.eslintignore
  22. 0 0
      apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts
  23. 0 0
      apps/app/_obsolete/src/client/util/codemirror/autorefresh.ext.js
  24. 0 0
      apps/app/_obsolete/src/client/util/codemirror/drawio-fold.ext.js
  25. 0 0
      apps/app/_obsolete/src/client/util/codemirror/gfm-growi.mode.js
  26. 0 0
      apps/app/_obsolete/src/client/util/codemirror/update-display-util.ext.js
  27. 0 19
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss
  28. 5 49
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx
  29. 1 72
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss
  30. 55 0
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx
  31. 1 1
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss
  32. 0 0
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  33. 3 3
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  34. 1 1
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss
  35. 0 0
      apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts
  36. 24 22
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  37. 0 0
      apps/app/_obsolete/src/components/PageEditor/EmojiPicker.tsx
  38. 0 0
      apps/app/_obsolete/src/components/PageEditor/EmojiPickerHelper.ts
  39. 15 13
      apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js
  40. 4 4
      apps/app/_obsolete/src/components/PageEditorByHackmd.tsx
  41. 0 0
      apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx
  42. 20 20
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  43. 0 0
      apps/app/_obsolete/src/components/UncontrolledCodeMirror.tsx
  44. 0 0
      apps/app/_obsolete/src/interfaces/hackmd.ts
  45. 0 0
      apps/app/_obsolete/src/server/routes/hackmd.js
  46. 0 0
      apps/app/_obsolete/src/stores/hackmd.ts
  47. 170 0
      apps/app/_obsolete/src/styles/_override.scss
  48. 672 0
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  49. 21 19
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  50. 0 0
      apps/app/_obsolete/src/styles/theme/_hsl-functions.scss
  51. 16 14
      apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss
  52. 2 2
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss
  53. 1 1
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss
  54. 2 2
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss
  55. 1 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss
  56. 1 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss
  57. 2 2
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss
  58. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss
  59. 28 26
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  60. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  61. 51 62
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  62. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss
  63. 6 4
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss
  64. 8 4
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss
  65. 3 2
      apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss
  66. 0 0
      apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss
  67. 1 1
      apps/app/bin/github-actions/update-readme.sh
  68. 3 1
      apps/app/docker/README.md
  69. 9 10
      apps/app/package.json
  70. 3 7
      apps/app/public/static/locales/en_US/admin.json
  71. 13 0
      apps/app/public/static/locales/en_US/commons.json
  72. 10 25
      apps/app/public/static/locales/en_US/translation.json
  73. 3 7
      apps/app/public/static/locales/ja_JP/admin.json
  74. 13 0
      apps/app/public/static/locales/ja_JP/commons.json
  75. 10 25
      apps/app/public/static/locales/ja_JP/translation.json
  76. 3 7
      apps/app/public/static/locales/zh_CN/admin.json
  77. 13 0
      apps/app/public/static/locales/zh_CN/commons.json
  78. 10 25
      apps/app/public/static/locales/zh_CN/translation.json
  79. 3 0
      apps/app/resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2
  80. 21 10
      apps/app/src/client/services/layout.ts
  81. 19 25
      apps/app/src/client/services/page-operation.ts
  82. 4 5
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  83. 8 9
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  84. 2 4
      apps/app/src/client/services/side-effects/page-updated.ts
  85. 49 0
      apps/app/src/client/services/use-on-template-button-clicked.ts
  86. 5 10
      apps/app/src/client/services/user-ui-settings.ts
  87. 1 1
      apps/app/src/client/util/apiv1-client.ts
  88. 1 1
      apps/app/src/client/util/apiv3-client.ts
  89. 3 3
      apps/app/src/components/Admin/AdminHome/AdminHome.jsx
  90. 22 22
      apps/app/src/components/Admin/App/AppSetting.jsx
  91. 1 1
      apps/app/src/components/Admin/App/AppSettingsPageContents.tsx
  92. 13 13
      apps/app/src/components/Admin/App/AwsSetting.tsx
  93. 2 2
      apps/app/src/components/Admin/App/ConfirmModal.tsx
  94. 7 7
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  95. 3 3
      apps/app/src/components/Admin/App/GcsSetting.tsx
  96. 8 8
      apps/app/src/components/Admin/App/MailSetting.tsx
  97. 1 1
      apps/app/src/components/Admin/App/MaintenanceMode.tsx
  98. 9 9
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  99. 4 4
      apps/app/src/components/Admin/App/SesSetting.tsx
  100. 2 2
      apps/app/src/components/Admin/App/SiteUrlSetting.tsx

+ 3 - 1
.devcontainer/Dockerfile

@@ -37,7 +37,9 @@ RUN sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable
 RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
 
 RUN apt-get update \
-    && apt-get -y install --no-install-recommends git-lfs \
+    && apt-get -y install --no-install-recommends \
+      git-lfs \
+      iputils-ping net-tools dnsutils \
 
     # Uncomment below lines to install Chromium
     # --- works only on AMD64 ---

+ 2 - 1
.devcontainer/devcontainer.json

@@ -19,6 +19,7 @@
     "eamodio.gitlens",
     "github.vscode-pull-request-github",
     "cschleiden.vscode-github-actions",
+    "mongodb.mongodb-vscode",
     "msjsdiag.debugger-for-chrome",
     "firefox-devtools.vscode-firefox-debug",
     "editorconfig.editorconfig",
@@ -34,7 +35,7 @@
   // "shutdownAction": "none",
 
   // Use 'postCreateCommand' to run commands after the container is created.
-  "postCreateCommand": "yarn global add turbo node-gyp && yarn install",
+  "postCreateCommand": "git-lfs pull & turbo run bootstrap",
 
   // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
   "remoteUser": "node"

+ 2 - 36
.devcontainer/docker-compose.yml

@@ -31,17 +31,10 @@ services:
     image: mongo:6.0
     restart: unless-stopped
     ports:
-      - 27017:27017
+      - 27017
     volumes:
       - /data/db
 
-  ogp:
-    image: ghcr.io/weseek/growi-unique-ogp:latest
-    ports:
-      - 8088:8088
-    restart: unless-stopped
-    tty: true
-
   # This container requires '../../growi-docker-compose' repository
   #   cloned from https://github.com/weseek/growi-docker-compose.git
   elasticsearch:
@@ -52,7 +45,7 @@ services:
         - version=8.7.0
     restart: unless-stopped
     ports:
-      - 9200:9200
+      - 9200
     environment:
       - bootstrap.memory_lock=true
       - "ES_JAVA_OPTS=-Xms256m -Xmx256m"
@@ -65,33 +58,6 @@ services:
       - /usr/share/elasticsearch/data
       - ../../growi-docker-compose/elasticsearch/v8/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml
 
-  #need to adjust kibana version based on elasticsearch version (use same version as elasticsearch version)
-  kibana:
-    image: docker.elastic.co/kibana/kibana:8.7.0
-    restart: unless-stopped
-    environment:
-      ELASTICSEARCH_HOSTS: 'http://elasticsearch:9200'
-    ports:
-      - 5601:5601
-    depends_on:
-      - elasticsearch
-
-  # This container requires '../../growi-docker-compose' repository
-  #   cloned from https://github.com/weseek/growi-docker-compose.git
-  hackmd:
-    build:
-      context: ../../growi-docker-compose/hackmd
-    restart: unless-stopped
-    environment:
-      - GROWI_URI=http://localhost:3000
-      # define 'storage' option value
-      # see https://github.com/sequelize/cli/blob/7160d0/src/helpers/config-helper.js#L192
-      - CMD_DB_URL=sqlite://dummyhost/hackmd/sqlite/codimd.db
-      - CMD_CSP_ENABLE=false
-    ports:
-      - 3010:3000
-    volumes:
-      - /files/sqlite
 volumes:
   node_modules:
   node_modules_app:

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

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

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

@@ -43,10 +43,10 @@ jobs:
         with:
           path: |
             **/node_modules
-          key: node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
+          key: node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
           restore-keys: |
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Restore dist
         uses: actions/cache/restore@v3
@@ -54,9 +54,9 @@ jobs:
           path: |
             **/.turbo
             **/dist
-          key: dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
           restore-keys: |
-            dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-
+            dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Install dependencies
         run: |
@@ -84,7 +84,7 @@ jobs:
           path: |
             **/.turbo
             **/dist
-          key: dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
 
 
   test:
@@ -115,10 +115,10 @@ jobs:
         with:
           path: |
             **/node_modules
-          key: node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
+          key: node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
           restore-keys: |
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Restore dist
         uses: actions/cache/restore@v3
@@ -126,9 +126,9 @@ jobs:
           path: |
             **/.turbo
             **/dist
-          key: dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
           restore-keys: |
-            dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-
+            dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Install dependencies
         run: |
@@ -166,7 +166,7 @@ jobs:
           path: |
             **/.turbo
             **/dist
-          key: dist-app-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-ci-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
 
 
   launch-dev:
@@ -197,10 +197,10 @@ jobs:
         with:
           path: |
             **/node_modules
-          key: node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
+          key: node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('apps/app/package.json') }}
           restore-keys: |
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
+            node_modules-7.x-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Restore dist
         uses: actions/cache/restore@v3
@@ -209,9 +209,9 @@ jobs:
             **/.turbo
             **/dist
             ${{ github.workspace }}/apps/app/.next
-          key: dist-app-dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
           restore-keys: |
-            dist-app-dev-${{ runner.OS }}-node${{ matrix.node-version }}-
+            dist-app-7.x-dev-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: Install dependencies
         run: |
@@ -244,4 +244,4 @@ jobs:
             **/.turbo
             **/dist
             ${{ github.workspace }}/apps/app/.next
-          key: dist-app-dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}
+          key: dist-app-7.x-dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('node_modules/.cache/turbo/*-meta.json') }}

+ 12 - 9
.github/workflows/reusable-app-prod.yml

@@ -27,6 +27,9 @@ jobs:
 
     steps:
     - uses: actions/checkout@v3
+      with:
+        # retrieve local font files
+        lfs: true
 
     - uses: actions/setup-node@v3
       with:
@@ -50,9 +53,9 @@ jobs:
       with:
         path: |
           **/node_modules
-        key: node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
+        key: node_modules-app-7.x-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
-          node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
+          node_modules-app-7.x-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
 
     - name: Install dependencies
       run: |
@@ -67,10 +70,10 @@ jobs:
           **/.turbo
           **/dist
           ${{ github.workspace }}/apps/app/.next
-        key: dist-app-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ github.ref_name }}-${{ github.sha }}
+        key: dist-app-7.x-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ github.ref_name }}-${{ github.sha }}
         restore-keys: |
-          dist-app-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ github.ref_name }}-
-          dist-app-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
+          dist-app-7.x-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ github.ref_name }}-
+          dist-app-7.x-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
 
     - name: Build
       working-directory: ./apps/app
@@ -162,9 +165,9 @@ jobs:
       with:
         path: |
           **/node_modules
-        key: node_modules-app-launch-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
+        key: node_modules-app-7.x-launch-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
-          node_modules-app-launch-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
+          node_modules-app-7.x-launch-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
 
     - name: Install dependencies
       run: |
@@ -253,9 +256,9 @@ jobs:
       with:
         path: |
           **/node_modules
-        key: node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
+        key: node_modules-app-7.x-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
-          node_modules-app-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
+          node_modules-app-7.x-build-prod-${{ runner.OS }}-node${{ inputs.node-version }}-
 
     - name: Cache/Restore Cypress files
       uses: actions/cache@v3

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

@@ -76,9 +76,9 @@ jobs:
       with:
         path: |
           **/node_modules
-        key: node_modules-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
+        key: node_modules-7.x-${{ runner.OS }}-node${{ inputs.node-version }}-${{ hashFiles('**/yarn.lock') }}
         restore-keys: |
-          node_modules-${{ runner.OS }}-node${{ inputs.node-version }}-
+          node_modules-7.x-${{ runner.OS }}-node${{ inputs.node-version }}-
 
     - name: Install dependencies
       run: |

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

+ 1 - 2
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 と連携することが可能です。

+ 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


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

@@ -1,6 +1,6 @@
 {
   "name": "@growi/hackmd",
-  "version": "6.3.0-RC.0",
+  "version": "7.0.0-RC.0",
   "description": "GROWI js and css files to use hackmd",
   "license": "MIT",
   "type": "module",

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

+ 0 - 2
apps/app/.eslintignore

@@ -2,8 +2,6 @@
 /dist/**
 /transpiled/**
 /public/**
-/src/client/legacy/thirdparty-js/**
-/src/client/util/reveal/plugins/markdown.js
 /src/linter-checker/**
 /tmp/**
 /next-env.d.ts

+ 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/client/util/codemirror/autorefresh.ext.js → apps/app/_obsolete/src/client/util/codemirror/autorefresh.ext.js


+ 0 - 0
apps/app/src/client/util/codemirror/drawio-fold.ext.js → apps/app/_obsolete/src/client/util/codemirror/drawio-fold.ext.js


+ 0 - 0
apps/app/src/client/util/codemirror/gfm-growi.mode.js → apps/app/_obsolete/src/client/util/codemirror/gfm-growi.mode.js


+ 0 - 0
apps/app/src/client/util/codemirror/update-display-util.ext.js → apps/app/_obsolete/src/client/util/codemirror/update-display-util.ext.js


+ 0 - 19
apps/app/src/components/Navbar/GrowiNavbar.module.scss → apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss

@@ -4,14 +4,6 @@
 
 .grw-navbar :global {
 
-  .grw-logo {
-    svg {
-      width: var.$grw-logo-width;
-      height: var.$grw-navbar-height;
-      padding: (var.$grw-logo-width - var.$grw-logomark-width) / 2;
-    }
-  }
-
   .confidential {
     font-weight: bold;
   }
@@ -70,17 +62,6 @@
     background: rgba(0, 0, 0, 0.2);
   }
 
-  .grw-apperance-mode-dropdown,
-  .grw-personal-dropdown {
-    .dropdown-menu {
-      min-width: 15rem;
-
-      .grw-icon-container svg {
-        width: 18px;
-        height: 18px;
-      }
-    }
-  }
   .grw-email-sm {
     font-size: 0.75em;
   }

+ 5 - 49
apps/app/src/components/Navbar/GrowiNavbar.tsx → apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx

@@ -4,28 +4,21 @@ import React, {
 
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
-import Link from 'next/link';
 import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 
 import {
-  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useIsDefaultLogo,
+  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useIsDeviceSmallerThanMd } from '~/stores/ui';
 
-import GrowiLogo from '../Icons/GrowiLogo';
 
 import { GlobalSearchProps } from './GlobalSearch';
 
 import styles from './GrowiNavbar.module.scss';
 
-const PersonalDropdown = dynamic(() => import('./PersonalDropdown'), { ssr: false });
-const InAppNotificationDropdown = dynamic(() => import('../InAppNotification/InAppNotificationDropdown')
-  .then(mod => mod.InAppNotificationDropdown), { ssr: false });
-const AppearanceModeDropdown = dynamic(() => import('./AppearanceModeDropdown').then(mod => mod.AppearanceModeDropdown), { ssr: false });
-
 const NavbarRight = memo((): JSX.Element => {
   const { t } = useTranslation();
 
@@ -44,10 +37,6 @@ const NavbarRight = memo((): JSX.Element => {
   const authenticatedNavItem = useMemo(() => {
     return (
       <>
-        <li className="nav-item">
-          <InAppNotificationDropdown />
-        </li>
-
         {!isReadOnlyUser
           && (
             <li className="nav-item d-none d-md-block">
@@ -58,34 +47,23 @@ const NavbarRight = memo((): JSX.Element => {
                 data-testid="newPageBtn"
                 onClick={() => openCreateModal(currentPagePath || '')}
               >
-                <i className="icon-pencil mr-2"></i>
+                <i className="icon-pencil me-2"></i>
                 <span className="d-none d-lg-block">{ t('commons:New') }</span>
               </button>
             </li>
           )
         }
-
-        <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
-        </li>
-
-        <li className="grw-personal-dropdown nav-item dropdown dropdown-toggle dropdown-toggle-no-caret" data-testid="grw-personal-dropdown">
-          <PersonalDropdown />
-        </li>
       </>
     );
-  }, [isReadOnlyUser, t, isAuthenticated, openCreateModal, currentPagePath]);
+  }, [isReadOnlyUser, t, openCreateModal, currentPagePath]);
 
   const notAuthenticatedNavItem = useMemo(() => {
     return (
       <>
-        <li className="grw-apperance-mode-dropdown nav-item dropdown">
-          <AppearanceModeDropdown isAuthenticated={isAuthenticated} />
-        </li>
         <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>
       </>
     );
-  }, [isAuthenticated]);
+  }, []);
 
   return (
     <>
@@ -123,21 +101,6 @@ const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX
 });
 Confidential.displayName = 'Confidential';
 
-interface NavbarLogoProps {
-  isDefaultLogo?: boolean
-}
-
-const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
-  const { isDefaultLogo } = props;
-
-  return isDefaultLogo
-    ? <GrowiLogo />
-    // eslint-disable-next-line @next/next/no-img-element
-    : (<img src="/attachment/brand-logo" alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
-});
-
-GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
-
 type Props = {
   isGlobalSearchHidden?: boolean
 }
@@ -153,23 +116,16 @@ export const GrowiNavbar = (props: Props): JSX.Element => {
   const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isSearchPage } = useIsSearchPage();
-  const { data: isDefaultLogo } = useIsDefaultLogo();
 
   return (
     <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
-      {/* Brand Logo  */}
-      <div className="navbar-brand mr-0">
-        <Link href="/" className="grw-logo d-block">
-          <GrowiNavbarLogo isDefaultLogo={isDefaultLogo} />
-        </Link>
-      </div>
 
       <div className="grw-app-title d-none d-md-block">
         {appTitle}
       </div>
 
       {/* Navbar Right  */}
-      <ul className="navbar-nav ml-auto">
+      <ul className="navbar-nav ms-auto">
         <NavbarRight />
         <Confidential confidential={confidential} />
       </ul>

+ 1 - 72
apps/app/src/components/Navbar/GrowiSubNavigation.module.scss → apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -1,5 +1,5 @@
 @use '~/styles/variables' as var;
-@use '~/styles/bootstrap/init' as bs;
+@use '@growi/core/scss/bootstrap/init' as bs;
 @use '~/styles/mixins';
 
 %subnav-buttons-height {
@@ -22,28 +22,11 @@
       min-height: var.$grw-subnav-min-height-md;
     }
 
-    .grw-drawer-toggler {
-      width: 50px;
-      height: 50px;
-      font-size: 24px;
-    }
-
     h1 {
       @include mixins.variable-font-size(32px);
       line-height: 1.4em;
     }
 
-    .grw-taglabels-container {
-      margin-bottom: 0.5rem;
-    }
-
-    .grw-page-path-nav {
-      .separator {
-        margin-right: 0.2em;
-        margin-left: 0.2em;
-      }
-    }
-
     .btn-copy {
       &:not(:hover):not(:active) {
         background-color: transparent !important;
@@ -51,19 +34,6 @@
       opacity: 0.5;
     }
 
-    .btn-edit-tags {
-      opacity: 0.5;
-
-      &.no-tags {
-        opacity: 0.7;
-      }
-    }
-
-    .btn-skeleton {
-      @extend %subnav-buttons-height;
-      width: 100%;
-    }
-
     .btn-subscribe {
       @extend %subnav-buttons-height;
       font-size: 20px;
@@ -129,46 +99,5 @@
         opacity: unset;
       }
     }
-
-    /*
-     * Compact Mode
-     */
-    &.grw-subnav-compact {
-      min-height: 70px;
-
-      @include bs.media-breakpoint-up(md) {
-        min-height: 90px;
-      }
-
-      .btn-skeleton {
-        @extend %compact-subnav-buttons-height;
-        width: 100%;
-      }
-
-      .btn-like,
-      .btn-bookmark,
-      .btn-subscribe {
-        width: 32px;
-        @extend %compact-subnav-buttons-height;
-        padding: 4px;
-        font-size: 16px;
-      }
-      .btn-seen-user {
-        width: 48px;
-        @extend %compact-subnav-buttons-height;
-        padding: 4px;
-        font-size: 16px;
-
-        svg {
-          width: 16px;
-          height: 16px;
-        }
-      }
-      .btn-page-item-control {
-        width: 32px;
-        @extend %compact-subnav-buttons-height;
-        font-size: 12px;
-      }
-    }
   }
 }

+ 55 - 0
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx

@@ -0,0 +1,55 @@
+import React from 'react';
+
+import {
+  EditorMode, useEditorMode,
+} from '~/stores/ui';
+
+import PagePathNav from '../PagePathNav';
+
+
+import styles from './GrowiSubNavigation.module.scss';
+
+
+export type GrowiSubNavigationProps = {
+  pagePath?: string,
+  pageId?: string,
+  isNotFound?: boolean,
+  isTagLabelsDisabled?: boolean,
+  tags?: string[],
+  rightComponent?: React.FunctionComponent,
+  additionalClasses?: string[],
+}
+
+export const GrowiSubNavigation = (props: GrowiSubNavigationProps): JSX.Element => {
+
+  const { data: editorMode } = useEditorMode();
+
+  const {
+    pageId, pagePath,
+    rightComponent: RightComponent,
+    additionalClasses = [],
+  } = props;
+
+  const isViewMode = editorMode === EditorMode.View;
+  const isEditorMode = !isViewMode;
+
+  return (
+    <div className={`
+      grw-subnav ${styles['grw-subnav']} d-flex align-items-center justify-content-between
+      ${additionalClasses.join(' ')}`}
+    >
+      {/* Left side */}
+      <div className="d-flex grw-subnav-start-side">
+        <div className="grw-path-nav-container">
+          { pagePath != null && (
+            <PagePathNav pageId={pageId} pagePath={pagePath} isSingleLineMode={isEditorMode} />
+          ) }
+        </div>
+      </div>
+      {/* Right side. */}
+      { RightComponent && (
+        <RightComponent />
+      ) }
+    </div>
+  );
+};

+ 1 - 1
apps/app/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss → apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss

@@ -1,5 +1,5 @@
 @use '~/styles/variables' as var;
-@use '~/styles/bootstrap/init' as bs;
+@use '@growi/core/scss/bootstrap/init' as bs;
 
 /*
  * Fixed ver

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


+ 3 - 3
apps/app/src/components/PageEditor/CodeMirrorEditor.jsx → apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -739,7 +739,7 @@ class CodeMirrorEditor extends AbstractEditor {
       <div className="overlay overlay-gfm-cheatsheet mt-1 p-3">
         { this.state.isSimpleCheatsheetShown
           ? (
-            <div className="text-right">
+            <div className="text-end">
               {cheatsheetModalButton}
               <div className="mb-2 d-none d-md-block">
                 <SimpleCheatsheet />
@@ -747,7 +747,7 @@ class CodeMirrorEditor extends AbstractEditor {
             </div>
           )
           : (
-            <div className="mr-4 mb-2">
+            <div className="me-4 mb-2">
               {cheatsheetModalButton}
             </div>
           )
@@ -760,7 +760,7 @@ class CodeMirrorEditor extends AbstractEditor {
     const { emojiSearchText } = this.state;
     return this.state.isEmojiPickerShown
       ? (
-        <div className="text-left">
+        <div className="text-start">
           <div className="mb-2 d-none d-md-block">
             <EmojiPicker
               onClose={() => this.setState({ isEmojiPickerShown: false })}

+ 1 - 1
apps/app/src/components/PageEditor/CodeMirrorEditor.module.scss → apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss

@@ -1,4 +1,4 @@
-@use '~/styles/bootstrap/init' as bs;
+@use '@growi/core/scss/bootstrap/init' as bs;
 
 .grw-codemirror-editor :global {
   @import '~codemirror/lib/codemirror';

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


+ 24 - 22
apps/app/src/components/PageEditor/ConflictDiffModal.tsx → apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx

@@ -130,18 +130,19 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
 
   }, [afterResolvedHandler, close, currentPagePath, currentPathname, optionsToSave, pageId, remoteRevisionId, saveOrUpdate, setRemoteLatestPageData]);
 
-  const resizeAndCloseButtons = useMemo(() => (
-    <div className="d-flex flex-nowrap">
-      <ExpandOrContractButton
-        isWindowExpanded={isModalExpanded}
-        expandWindow={() => setIsModalExpanded(true)}
-        contractWindow={() => setIsModalExpanded(false)}
-      />
-      <button type="button" className="close text-white" onClick={close} aria-label="Close">
-        <span aria-hidden="true">&times;</span>
-      </button>
-    </div>
-  ), [isModalExpanded, close]);
+  // TODO: No longer support custom close icon in bootstrap v5
+  // const resizeAndCloseButtons = useMemo(() => (
+  //   <div className="d-flex flex-nowrap">
+  //     <ExpandOrContractButton
+  //       isWindowExpanded={isModalExpanded}
+  //       expandWindow={() => setIsModalExpanded(true)}
+  //       contractWindow={() => setIsModalExpanded(false)}
+  //     />
+  //     <button type="button" className="close text-white" onClick={close} aria-label="Close">
+  //       <span aria-hidden="true">&times;</span>
+  //     </button>
+  //   </div>
+  // ), [isModalExpanded, close]);
 
   const isOpen = props.isOpen ?? false;
 
@@ -153,7 +154,8 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
       className={`${isModalExpanded ? ' grw-modal-expanded' : ''}`}
       size="xl"
     >
-      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3" close={resizeAndCloseButtons}>
+      {/* <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3" close={resizeAndCloseButtons}> */}
+      <ModalHeader tag="h4" toggle={onClose} className="bg-primary text-light align-items-center py-3">
         <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
       </ModalHeader>
       <ModalBody className="mx-4 my-1">
@@ -161,39 +163,39 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
         && (
           <div className="row">
             <div className="col-12 text-center mt-2 mb-4">
-              <h2 className="font-weight-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
+              <h2 className="fw-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
             </div>
             <div className="col-4">
-              <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
+              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
               <div className="d-flex align-items-center my-3">
                 <div>
                   <UserPicture user={request.user} size="lg" noLink noTooltip />
                 </div>
-                <div className="ml-3 text-muted">
+                <div className="ms-3 text-muted">
                   <p className="my-0">updated by {request.user.username}</p>
                   <p className="my-0">{request.createdAt}</p>
                 </div>
               </div>
             </div>
             <div className="col-4">
-              <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.origin_revision')}</h3>
+              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.origin_revision')}</h3>
               <div className="d-flex align-items-center my-3">
                 <div>
                   <UserPicture user={origin.user} size="lg" noLink noTooltip />
                 </div>
-                <div className="ml-3 text-muted">
+                <div className="ms-3 text-muted">
                   <p className="my-0">updated by {origin.user.username}</p>
                   <p className="my-0">{origin.createdAt}</p>
                 </div>
               </div>
             </div>
             <div className="col-4">
-              <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
+              <h3 className="fw-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
               <div className="d-flex align-items-center my-3">
                 <div>
                   <UserPicture user={latest.user} size="lg" noLink noTooltip />
                 </div>
-                <div className="ml-3 text-muted">
+                <div className="ms-3 text-muted">
                   <p className="my-0">updated by {latest.user.username}</p>
                   <p className="my-0">{latest.createdAt}</p>
                 </div>
@@ -247,7 +249,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
             </div>
             <div className="col-12">
               <div className="border border-dark">
-                <h3 className="font-weight-bold my-2 mx-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
+                <h3 className="fw-bold my-2 mx-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
                 <UncontrolledCodeMirror
                   ref={uncontrolledRef}
                   value={resolvedRevision}
@@ -270,7 +272,7 @@ const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element =
         </button>
         <button
           type="button"
-          className="btn btn-primary ml-3"
+          className="btn btn-primary ms-3"
           onClick={onResolveConflict}
           disabled={!isRevisionselected}
         >

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


+ 0 - 0
apps/app/src/components/PageEditor/EmojiPickerHelper.ts → apps/app/_obsolete/src/components/PageEditor/EmojiPickerHelper.ts


+ 15 - 13
apps/app/src/components/PageEditor/MarkdownTableInterceptor.js → apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js

@@ -2,7 +2,10 @@ import { BasicInterceptor } from '@growi/core/dist/utils';
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 
-import mtu from './MarkdownTableUtil';
+import {
+  getStrFromBot, addRowToMarkdownTable, getStrToEot, isEndOfLine, mergeMarkdownTable, replaceFocusedMarkdownTableWithEditor,
+  isInTable, emptyLineOfTableRE,
+} from '../../../../src/components/PageEditor/markdown-table-util-for-editor';
 
 /**
  * Interceptor for markdown table
@@ -27,24 +30,24 @@ export default class MarkdownTableInterceptor extends BasicInterceptor {
 
   addRow(cm) {
     // get lines all of table from current position to beginning of table
-    const strFromBot = mtu.getStrFromBot(cm);
+    const strFromBot = getStrFromBot(cm);
     let table = MarkdownTable.fromMarkdownString(strFromBot);
 
-    mtu.addRowToMarkdownTable(table);
+    addRowToMarkdownTable(table);
 
-    const strToEot = mtu.getStrToEot(cm);
+    const strToEot = getStrToEot(cm);
     const tableBottom = MarkdownTable.fromMarkdownString(strToEot);
     if (tableBottom.table.length > 0) {
-      table = mtu.mergeMarkdownTable([table, tableBottom]);
+      table = mergeMarkdownTable([table, tableBottom]);
     }
 
-    mtu.replaceMarkdownTableWithReformed(cm, table);
+    replaceFocusedMarkdownTableWithEditor(cm, table);
   }
 
   reformTable(cm) {
-    const tableStr = mtu.getStrFromBot(cm) + mtu.getStrToEot(cm);
+    const tableStr = getStrFromBot(cm) + getStrToEot(cm);
     const table = MarkdownTable.fromMarkdownString(tableStr);
-    mtu.replaceMarkdownTableWithReformed(cm, table);
+    replaceFocusedMarkdownTableWithEditor(cm, table);
   }
 
   removeRow(editor) {
@@ -67,16 +70,15 @@ export default class MarkdownTableInterceptor extends BasicInterceptor {
 
     const cm = editor.getCodeMirror();
 
-    const isInTable = mtu.isInTable(cm);
-    const isLastRow = mtu.getStrToEot(cm) === editor.getStrToEol();
+    const isLastRow = getStrToEot(cm) === editor.getStrToEol();
 
-    if (isInTable) {
+    if (isInTable(cm)) {
       // at EOL in the table
-      if (mtu.isEndOfLine(cm)) {
+      if (isEndOfLine(cm)) {
         this.addRow(cm);
       }
       // last empty row
-      else if (isLastRow && mtu.emptyLineOfTableRE.test(editor.getStrFromBol() + editor.getStrToEol())) {
+      else if (isLastRow && emptyLineOfTableRE.test(editor.getStrFromBol() + editor.getStrToEol())) {
         this.removeRow(editor);
       }
       else {

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

@@ -361,7 +361,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
       content = (
         <div className="text-center">
           <p className="hackmd-status-label">
-            <i className="fa fa-file-text mr-2" />
+            <i className="fa fa-file-text me-2" />
             { t('hackmd.used_for_not_found') }
           </p>
           {/* eslint-disable-next-line react/no-danger */}
@@ -382,12 +382,12 @@ export const PageEditorByHackmd = (): JSX.Element => {
 
           { isHackmdDocumentOutdated && (
             <div className="card border-warning">
-              <div className="card-header bg-warning"><i className="icon-fw icon-info"></i> {t('hackmd.draft_outdated')}</div>
+              <div className="card-header bg-warning text-dark"><i className="icon-fw icon-info"></i> {t('hackmd.draft_outdated')}</div>
               <div className="card-body text-center">
                 {t('hackmd.based_on_revision')}&nbsp;
                 { pageData != null && (
                   <Link href={urljoin(returnPathForURL(pageData.path, pageData._id), `?revisionId=${revisionIdHackmdSynced}`)} prefetch={false}>
-                    <span className="badge badge-secondary">{revisionIdHackmdSynced?.substr(-8)}</span>
+                    <span className="badge bg-primary">{revisionIdHackmdSynced?.substr(-8)}</span>
                   </Link>
                 )}
                 <div className="text-center mt-3">
@@ -506,7 +506,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
           <div className="bg-box p-5 text-center">
             <h2 className="text-warning"><i className="icon-fw icon-exclamation"></i> {t('hackmd.integration_failed')}</h2>
             <h4>{errorMessage}</h4>
-            <p className="card well text-danger">
+            <p className="card custom-card text-danger">
               {errorReason}
             </p>
             {/* eslint-disable-next-line react/no-danger */}

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


+ 20 - 20
apps/app/src/components/Navbar/AppearanceModeDropdown.tsx → apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx

@@ -75,20 +75,20 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
       <>
         <h6 className="dropdown-header">{t(isEditMode ? 'personal_dropdown.sidebar_mode_editor' : 'personal_dropdown.sidebar_mode')}</h6>
         <form className="px-4">
-          <div className="form-row justify-content-center">
-            <div className="form-group col-auto mb-0 d-flex align-items-center">
+          <div className="justify-content-center">
+            <div className="col-auto mb-0 d-flex align-items-center">
               <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-drawer' : 'iwt-sidebar-drawer'} label="Drawer" additionalClasses="grw-sidebar-mode-icon">
                 <SidebarDrawerIcon />
               </IconWithTooltip>
-              <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
+              <div className="form-check form-switch form-check-secondary ms-2">
                 <input
                   id={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}
-                  className="custom-control-input"
+                  className="form-check-input"
                   type="checkbox"
                   checked={isEditMode ? !isPreferDrawerModeOnEdit : !isPreferDrawerMode}
                   onChange={e => preferDrawerModeSwitchModifiedHandler(!e.target.checked, isEditMode)}
                 />
-                <label className="custom-control-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
+                <label className="form-label form-check-label" htmlFor={isEditMode ? 'swSidebarModeOnEditor' : 'swSidebarMode'}></label>
               </div>
               <IconWithTooltip id={isEditMode ? 'iwt-sidebar-editor-dock' : 'iwt-sidebar-dock'} label="Dock" additionalClasses="grw-sidebar-mode-icon">
                 <SidebarDockIcon />
@@ -101,16 +101,16 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
   }, [isPreferDrawerMode, isPreferDrawerModeOnEdit, preferDrawerModeSwitchModifiedHandler, t]);
 
   return (
-    <>
+    <div className="dropend">
       {/* setting button */}
       {/* remove .dropdown-toggle for hide caret */}
       {/* See https://stackoverflow.com/a/44577512/13183572 */}
-      <button className="bg-transparent border-0 nav-link" type="button" data-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
-        <i className="icon-settings"></i>
+      <button className="btn btn-primary" type="button" data-bs-toggle="dropdown" ref={buttonRef} aria-haspopup="true">
+        <span className="material-symbols-outlined">settings</span>
       </button>
 
       {/* dropdown */}
-      <div className="dropdown-menu dropdown-menu-right">
+      <div className="dropdown-menu">
 
         {/* sidebar mode */}
         {renderSidebarModeSwitch(false)}
@@ -129,38 +129,38 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
             {dropdownDivider}
             <h6 className="dropdown-header">{t('personal_dropdown.color_mode')}</h6>
             <form className="px-4">
-              <div className="form-row justify-content-center">
-                <div className="form-group col-auto d-flex align-items-center">
+              <div className="justify-content-center">
+                <div className="col-auto d-flex align-items-center">
                   <IconWithTooltip id="iwt-light" label="Light" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
                     <SunIcon />
                   </IconWithTooltip>
-                  <div className="custom-control custom-switch custom-checkbox-secondary ml-2">
+                  <div className="form-check form-switch form-check-secondary ms-2">
                     <input
                       id="swUserPreference"
-                      className="custom-control-input"
+                      className="form-check-input"
                       type="checkbox"
                       checked={isDarkMode}
                       disabled={useOsSettings}
                       onChange={e => userPreferenceSwitchModifiedHandler(e.target.checked)}
                     />
-                    <label className="custom-control-label" htmlFor="swUserPreference"></label>
+                    <label className="form-label form-check-label" htmlFor="swUserPreference"></label>
                   </div>
                   <IconWithTooltip id="iwt-dark" label="Dark" additionalClasses={useOsSettings ? 'grw-color-mode-icon-muted' : 'grw-color-mode-icon'}>
                     <MoonIcon />
                   </IconWithTooltip>
                 </div>
               </div>
-              <div className="form-row">
-                <div className="form-group col-auto">
-                  <div className="custom-control custom-checkbox">
+              <div>
+                <div className="col-auto">
+                  <div className="form-check">
                     <input
                       id="cbFollowOs"
-                      className="custom-control-input"
+                      className="form-check-input"
                       type="checkbox"
                       checked={useOsSettings}
                       onChange={e => followOsCheckboxModifiedHandler(e.target.checked)}
                     />
-                    <label className="custom-control-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
+                    <label className="form-label form-check-label text-nowrap" htmlFor="cbFollowOs">{t('personal_dropdown.use_os_settings')}</label>
                   </div>
                 </div>
               </div>
@@ -170,7 +170,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
       </div>
 
-    </>
+    </div>
   );
 
 };

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


+ 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


+ 170 - 0
apps/app/_obsolete/src/styles/_override.scss

@@ -0,0 +1,170 @@
+// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+
+// * {
+//   outline: none !important;
+// }
+
+// .container,
+// .container-sm,
+// .container-md,
+// .container-lg,
+// .container-xl,
+// .container-fluid {
+//   @include media-breakpoint-down(xs) {
+//     padding-right: 10px;
+//     padding-left: 10px;
+//   }
+//   @include media-breakpoint-up(md) {
+//     padding-right: 30px;
+//     padding-left: 30px;
+//   }
+// }
+
+// h1 {
+//   font-size: 36px;
+//   line-height: 48px;
+// }
+
+// h2 {
+//   font-size: 24px;
+//   line-height: 36px;
+// }
+
+// h3 {
+//   font-size: 21px;
+//   line-height: 30px;
+// }
+
+// h4 {
+//   font-size: 18px;
+//   line-height: 22px;
+// }
+
+// h5 {
+//   font-size: 16px;
+//   line-height: 18px;
+// }
+
+// h6 {
+//   font-size: 12px;
+//   line-height: 18px;
+// }
+
+// // Navs
+// .nav-tabs {
+//   .nav-item {
+//     margin-right: 0.15rem;
+//     a.active {
+//       cursor: default;
+//     }
+//   }
+// }
+
+// // Custom Control
+// .form-check {
+//   .form-check-input,
+//   .form-check-input + .form-check-label {
+//     cursor: pointer;
+//   }
+// }
+
+// // card (substitute panel of bootstrap3)
+// .card {
+//   margin-bottom: 20px;
+// }
+
+// .card-header {
+//   font-weight: 700;
+//   text-transform: none;
+// }
+
+// .card-header:first-child {
+// }
+
+// .card.custom-card {
+//   min-height: 20px;
+//   padding: $card-spacer-y $card-spacer-x;
+// }
+
+// // Dropdowns
+// .dropdown-toggle {
+//   &.btn.disabled {
+//     pointer-events: auto;
+//     cursor: not-allowed;
+//   }
+
+//   // hide caret
+//   &.dropdown-toggle-no-caret::after {
+//     content: none;
+//   }
+// }
+
+// //Modals
+// .modal-open {
+//   width: 100%;
+//   padding-right: 0 !important;
+// }
+
+// .modal-content {
+//   box-shadow: 0 0.3rem 1rem rgba(0, 0, 0, 0.1);
+// }
+
+// .modal-header {
+//   border-bottom: 1px solid #e5e5e5;
+// }
+
+// .modal-footer {
+//   border-top: 1px solid #e5e5e5;
+// }
+
+// // When fading in the modal, animate it to slide down
+// .modal.fade .modal-dialog {
+//   @include transition($modal-transition);
+//   transform: $modal-fade-transform;
+// }
+
+// .modal.show .modal-dialog {
+//   transform: $modal-show-transform;
+// }
+
+// // When trying to close, animate focus to scale
+// .modal.modal-static .modal-dialog {
+//   transform: $modal-scale-transform;
+// }
+
+// // col-form-label (substitute for control-label of bootstrap3)
+// .col-form-label {
+//   text-align: right;
+// }
+
+// // label
+// label {
+//   // add with-no-font-weight class in case you do not want to apply font-weight 700 to label
+//   :not(.with-no-font-weight) {
+//     font-weight: 700;
+//   }
+// }
+
+// // disabled button (reproduction from bootstrap3.)
+// // see https://cccabinet.jpn.org/bootstrap4/components/buttons#disabled-state
+// .btn.disabled,
+// .btn[disabled],
+// fieldset[disabled] .btn {
+//   cursor: not-allowed;
+// }
+
+// // progress bar
+// .progress {
+//   margin-bottom: 18px;
+//   overflow: hidden;
+// }
+
+// .text-break {
+//   word-break: break-word;
+//   overflow-wrap: break-word;
+// }
+
+// // prevent tooltip flickering (flashing) on hover
+// .tooltip {
+//   pointer-events: none;
+// }

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

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

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

@@ -1,12 +1,13 @@
+@use '@growi/core/scss/bootstrap/init' as *;
+
 @use '../variables' as var;
-@use '../bootstrap/init' as *;
 @use '../atoms/mixins/buttons' as mixins-buttons;
 @use './mixins/count-badge';
 @use './mixins/hsl-button';
 @use './hsl-functions' as hsl;
 
 // determine optional variables
-:root[data-theme='light'] {
+:root[data-bs-theme='light'] {
   $color-list: var(--color-list,var(--color-global));
   $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
   $color-list-hover: var(--color-list-hover,var(--color-global));
@@ -49,12 +50,13 @@
   $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 
   @import './mixins/list-group';
-  @import './reboot-bootstrap-text';
-  @import './reboot-bootstrap-border-colors';
-  @import './reboot-bootstrap-tables';
-  @import './reboot-bootstrap-theme-colors';
-  @import 'hsl-reboot-bootstrap-theme-colors';
-  @import './reboot-bootstrap-dropdown';
+  // TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+  // @import './reboot-bootstrap-text';
+  // @import './reboot-bootstrap-border-colors';
+  // @import './reboot-bootstrap-tables';
+  // @import './reboot-bootstrap-theme-colors';
+  // @import 'hsl-reboot-bootstrap-theme-colors';
+  // @import './reboot-bootstrap-dropdown';
 
   // List Group
   @include override-list-group-item(
@@ -117,7 +119,7 @@
       background-color: rgba(white, 0.5);
       .link-switch {
         color: #1939b8;
-        @include hover() {
+        &:hover {
           color: lighten(#1939b8,20%);
         }
       }
@@ -178,9 +180,9 @@
   /*
   * GROWI subnavigation
   */
-  .grw-subnav {
-    background-color: var(--bgcolor-subnav);
-  }
+  // .grw-subnav {
+  //   background-color: var(--bgcolor-subnav);
+  // }
 
   .grw-subnav-fixed-container .grw-subnav {
     background-color: hsl.alpha(var(--bgcolor-subnav),85%);
@@ -205,9 +207,9 @@
   /**
    * GROWI PagePathHierarchicalLink
    */
-  .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
-    color: $gray-600;
-  }
+  // .grw-page-path-text-muted-container .grw-page-path-hierarchical-link a {
+  //   color: $gray-600;
+  // }
   /*
   * GROWI Sidebar
   */
@@ -264,7 +266,7 @@
     --gray-500-hs: 210,13%;
     --gray-500-l: 61%;
     @include hsl-button.button-outline-variant(var(--gray-500), var(--primary), #{hsl.lighten(var(--primary), 52%)}, transparent);
-    @include hover() {
+    &:hover {
       background-color: hsl.lighten(var(--primary), 58%);
     }
     &:not(:disabled):not(.disabled):active,
@@ -356,7 +358,7 @@
   * GROWI Editor
   */
   .grw-editor-navbar-bottom {
-    background-color: $gray-50;
+    background-color: $gray-100;
 
     #slack-mark-white {
       display: none;
@@ -401,7 +403,7 @@
       background: white;
     }
 
-    .custom-control-label {
+    .form-check-label {
       &::before {
         background-color: $gray-200;
         border-color: transparent;
@@ -411,7 +413,7 @@
         background-image: url(/images/icons/slack/slack-logo-off.svg);
       }
     }
-    .custom-control-input:checked ~ .custom-control-label {
+    .form-check-input:checked ~ .form-check-label {
       &::before {
         background-color: lighten($color-slack, 60%);
       }

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


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

@@ -21,18 +21,20 @@ $hsl-colors: (
   }
 }
 
-@each $color, $value in $hsl-colors {
-  .text-#{$color} {
-    color: $value !important;
-    @if $emphasized-link-hover-darken-percentage != 0 {
-      a {
-        @include hover-focus() {
-          color: hsl.darken($value, $emphasized-link-hover-darken-percentage) !important;
-        }
-      }
-    }
-  }
-}
+// TODO: hover-focus() dropped in bootstrap v5
+// https://redmine.weseek.co.jp/issues/128307
+// @each $color, $value in $hsl-colors {
+//   .text-#{$color} {
+//     color: $value !important;
+//     @if $emphasized-link-hover-darken-percentage != 0 {
+//       a {
+//         @include hover-focus() {
+//           color: hsl.darken($value, $emphasized-link-hover-darken-percentage) !important;
+//         }
+//       }
+//     }
+//   }
+// }
 
 @each $color, $value in $hsl-colors {
   .btn-#{$color} {
@@ -96,11 +98,11 @@ $hsl-colors: (
 }
 
 @each $color, $value in $hsl-colors {
-  .badge-#{$color} {
+  .bg-#{$color} {
     @include hsl-badge.badge-variant($value);
   }
 
-  a.badge-#{$color}  {
+  a.bg-#{$color}  {
     @include hsl-badge.badge-variant($value);
   }
 }

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

@@ -12,7 +12,7 @@
   border-top: $border-width solid $border-color !important;
 }
 
-.border-right {
+.border-end {
   border-right: $border-width solid $border-color !important;
 }
 
@@ -20,7 +20,7 @@
   border-bottom: $border-width solid $border-color !important;
 }
 
-.border-left {
+.border-start {
   border-left: $border-width solid $border-color !important;
 }
 

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

@@ -4,7 +4,7 @@
     fill: var(--color-link);
   }
 
-  @include hover() {
+  &:hover {
     color: var(--color-link-hover);
     svg {
       fill: var(--color-link-hover);

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

@@ -34,7 +34,7 @@ a {
     fill: var(--color-link);
   }
 
-  @include hover() {
+  &:hover {
     color: var(--color-link-hover);
     text-decoration: $link-hover-decoration;
 
@@ -53,7 +53,7 @@ a {
 //   color: inherit;
 //   text-decoration: none;
 
-//   @include hover() {
+//   &:hover {
 //     color: inherit;
 //     text-decoration: none;
 //   }

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

@@ -15,6 +15,7 @@
     fill: $color-dropdown-link;
   }
 
+  // TODO: hover-focus() dropped in bootstrap v5
   @include hover-focus() {
     color: $color-dropdown-link;
     svg {

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

@@ -26,6 +26,7 @@
     border: $nav-tabs-border-width solid transparent;
     @include border-top-radius($nav-tabs-border-radius);
 
+    // TODO: hover-focus() dropped in bootstrap v5
     @include hover-focus() {
       border-color: $nav-tabs-link-hover-border-color;
     }

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

@@ -36,7 +36,7 @@
 
 .table-hover {
   tbody tr {
-    @include hover() {
+    &:hover {
       color: $color-table-hover;
       background-color: $bgcolor-table-hover;
     }
@@ -65,7 +65,7 @@
 
   &.table-hover {
     tbody tr {
-      @include hover() {
+      &:hover {
         color: $table-dark-hover-color;
         background-color: $table-dark-hover-bg;
       }

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


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

@@ -47,24 +47,24 @@
 }
 
 @each $theme-color, $color in $theme-colors {
-  .custom-checkbox-#{$theme-color} {
-    .custom-control-label::before {
+  .form-check-#{$theme-color} {
+    .form-check-label::before {
       border-color: $input-border-color;
       transition: 0.3s ease-in-out;
     }
-    .custom-control-input:checked + .custom-control-label::before {
+    .form-check-input:checked + .form-check-label::before {
       background-color: $color;
       border-color: $color;
     }
-    .custom-control-input:checked + .custom-control-label::after {
+    .form-check-input:checked + .form-check-label::after {
       color: var(--bgcolor-global);
     }
-    .custom-control-input:not(:disabled):active ~ .custom-control-label::before {
+    .form-check-input:not(:disabled):active ~ .form-check-label::before {
       color: var(--bgcolor-global);
       background-color: $color;
       border-color: $color;
     }
-    .custom-control-input:focus:not(:checked) ~ .custom-control-label::before {
+    .form-check-input:focus:not(:checked) ~ .form-check-label::before {
       color: var(--bgcolor-global);
       background-color: var(--bgcolor-global);
       border-color: $input-focus-border-color;
@@ -72,30 +72,32 @@
   }
 }
 
-@each $color, $value in $theme-colors {
-  .alert-#{$color} {
-    @include alert-variant(
-      theme-color-level($color, $alert-bg-level),
-      theme-color-level($color, $alert-border-level),
-      theme-color-level($color, $alert-color-level)
-    );
-  }
-  // Alert link
-  :root, .wiki {
-    .alert.alert-#{$color} {
-      a,
-      a:hover {
-        color: theme-color-level($color, $alert-color-level - 2);
-      }
-    }
-  }
-}
+// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+// theme-color-level() dropped in bootstrap v5
+// @each $color, $value in $theme-colors {
+//   .alert-#{$color} {
+//     @include alert-variant(
+//       theme-color-level($color, $alert-bg-level),
+//       theme-color-level($color, $alert-border-level),
+//       theme-color-level($color, $alert-color-level)
+//     );
+//   }
+//   // Alert link
+//   :root, .wiki {
+//     .alert.alert-#{$color} {
+//       a,
+//       a:hover {
+//         color: theme-color-level($color, $alert-color-level - 2);
+//       }
+//     }
+//   }
+// }
 
 @each $color, $value in $theme-colors {
-  .badge-#{$color} {
+  .bg-#{$color} {
     @include badge-variant($value);
   }
-  a.badge-#{$color} {
+  a.bg-#{$color} {
     @include badge-variant($value);
   }
 }

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


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

@@ -1,5 +1,6 @@
+@use '@growi/core/scss/bootstrap/init' as *;
+
 @use '../variables' as var;
-@use '../bootstrap/init' as *;
 @use '../mixins';
 @use '../atoms/mixins/code';
 @use './mixins/hsl-button';
@@ -41,12 +42,13 @@ $nav-tabs-link-active-bg: var(--bgcolor-global);
 $nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
 $theme-colors: map-merge($theme-colors, ( primary: $primary ));
 
-@import 'reboot-bootstrap-buttons';
-@import 'reboot-bootstrap-colors';
-@import 'reboot-bootstrap-theme-colors';
-@import 'hsl-reboot-bootstrap-theme-colors';
-@import 'reboot-bootstrap-nav';
-@import 'reboot-toastr-colors';
+// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+// @import 'reboot-bootstrap-buttons';
+// @import 'reboot-bootstrap-colors';
+// @import 'reboot-bootstrap-theme-colors';
+// @import 'hsl-reboot-bootstrap-theme-colors';
+// @import 'reboot-bootstrap-nav';
+// @import 'reboot-toastr-colors';
 
 // determine variables with bootstrap function (These variables can be used after importing bootstrap above)
 $color-modal-header: var(--color-modal-header,#{hsl.contrast(var(--primary))});
@@ -63,15 +65,17 @@ code:not([class^='language-']) {
 //== Apply to Bootstrap Elements
 //
 
+// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+// theme-color-level() dropped in bootstrap v5
 // Alert link
-@each $color, $value in $theme-colors {
-  .alert.alert-#{$color} {
-    a,
-    a:hover {
-      color: theme-color-level($color, $alert-color-level - 2);
-    }
-  }
-}
+// @each $color, $value in $theme-colors {
+//   .alert.alert-#{$color} {
+//     a,
+//     a:hover {
+//       color: theme-color-level($color, $alert-color-level - 2);
+//     }
+//   }
+// }
 
 // Dropdown
 .grw-apperance-mode-dropdown {
@@ -86,10 +90,12 @@ code:not([class^='language-']) {
   }
 }
 
+// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
+// form-control-focus() dropped in bootstrap v5
 // Form
-.form-control {
-  @include form-control-focus();
-}
+// .form-control {
+//   @include form-control-focus();
+// }
 
 // Tabs
 .nav.nav-tabs .nav-link.active {
@@ -200,29 +206,24 @@ ul.pagination {
   $bgcolor-resize-button: var(--bgcolor-resize-button,white);
   $color-resize-button-hover: var(--color-resize-button-hover,var(--color-reversal));
   $bgcolor-resize-button-hover: var(--bgcolor-resize-button-hover,#{hsl.lighten(var(--bgcolor-resize-button), 5%)});
-  .grw-navigation-resize-button {
-    .hexagon-container svg {
-      .background {
-        fill: var(--bgcolor-resize-button);
-      }
-      .icon {
-        fill: var(--color-resize-button);
-      }
-    }
-    &:hover .hexagon-container svg {
-      .background {
-        fill: var(--bgcolor-resize-button-hover);
-      }
-      .icon {
-        fill: var(--color-resize-button-hover);
-      }
-    }
-  }
-  div.grw-global-navigation {
-    > div {
-      background-color: var(--bgcolor-sidebar);
-    }
-  }
+  // .grw-navigation-resize-button {
+  //   .hexagon-container svg {
+  //     .background {
+  //       fill: var(--bgcolor-resize-button);
+  //     }
+  //     .icon {
+  //       fill: var(--color-resize-button);
+  //     }
+  //   }
+  //   &:hover .hexagon-container svg {
+  //     .background {
+  //       fill: var(--bgcolor-resize-button-hover);
+  //     }
+  //     .icon {
+  //       fill: var(--color-resize-button-hover);
+  //     }
+  //   }
+  // }
   div.grw-contextual-navigation {
     > div {
       color: var(--color-sidebar-context);
@@ -256,24 +257,24 @@ ul.pagination {
     }
 
     .grw-recent-changes-resize-button {
-      .custom-control-label::before {
+      .form-check-label::before {
         background-color: var(--primary);
       }
 
-      .custom-control-label::after {
+      .form-check-label::after {
         background-color: var(--bgcolor-global);
       }
 
-      .custom-control-input:not(:checked) + .custom-control-label::before {
+      .form-check-input:not(:checked) + .form-check-label::before {
         color: var(--bgcolor-global);
       }
 
-      .custom-control-input:checked + .custom-control-label::before {
+      .form-check-input:checked + .form-check-label::before {
         color: var(--bgcolor-global);
         background-color: var(--primary);
         border-color: var(--primary);
       }
-      .custom-control-input:checked + .custom-control-label::after {
+      .form-check-input:checked + .form-check-label::after {
         color: var(--bgcolor-global);
       }
     }
@@ -336,7 +337,7 @@ ul.pagination {
     .modal-title {
       color: $color-modal-header;
     }
-    .close {
+    .btn-close {
       color: $color-modal-header;
       opacity: 0.5;
 
@@ -357,7 +358,7 @@ ul.pagination {
 
 .grw-page-accessories-modal,.grw-descendants-page-list-modal {
   .modal-header {
-    .close {
+    .btn-close {
       color: #{hsl.contrast(var(--bgcolor-global))};
     }
   }
@@ -394,7 +395,7 @@ ul.pagination {
 /*
  * cards
  */
-.card.well {
+.card.custom-card {
   color: var(--color-global);
   background-color: var(--bgcolor-card);
   border-color: var(--light);
@@ -481,7 +482,7 @@ ul.pagination {
           fill: var(--color-global);
         }
 
-        @include hover() {
+        &:hover {
           svg {
             fill: var(--color-global);
           }
@@ -661,18 +662,6 @@ mark.rbt-highlight-text {
   background-color: var(--bgcolor-global);
 }
 
-.grw-fab {
-  .btn-create-page {
-    svg {
-      fill: hsl.contrast(var(--primary));
-    }
-  }
-
-  .btn-scroll-to-top {
-    fill: $gray-900;
-  }
-}
-
 /*
   Slack Integration
 */

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


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

@@ -7,10 +7,12 @@
   background-color: $bg;
 
   @at-root a#{&} {
-    @include bs.hover-focus() {
-      color: hsl.contrast($bg);
-      background-color: hsl.darken($bg, 10%);
-    }
+    // TODO: hover-focus() dropped in bootstrap v5
+    // https://redmine.weseek.co.jp/issues/128307
+    // @include bs.hover-focus() {
+    //   color: hsl.contrast($bg);
+    //   background-color: hsl.darken($bg, 10%);
+    // }
 
     &:focus,
     &.focus {

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

@@ -1,4 +1,4 @@
-@use '../../bootstrap/init' as bs;
+@use '@growi/core/scss/bootstrap/init' as bs;
 @use '../hsl-functions' as hsl;
 
 // @mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) {
@@ -11,7 +11,7 @@
   border-color: $border;
   // @include box-shadow($btn-box-shadow);
 
-  @include bs.hover() {
+  &:hover {
     color: hsl.contrast($background);
     @include bs.gradient-bg($hover-background);
     border-color: $hover-border;
@@ -22,6 +22,8 @@
     color: hsl.contrast($background);
     @include bs.gradient-bg($hover-background);
     border-color: $hover-border;
+    // TODO: color-yiq() to color-contrast()
+    // https://redmine.weseek.co.jp/issues/128307
     // @if $enable-shadows {
     //   @include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
     // } @else {
@@ -54,6 +56,8 @@
   //   }
   //   border-color: $active-border;
 
+  // TODO: color-yiq() to color-contrast()
+  // https://redmine.weseek.co.jp/issues/128307
   //   &:focus {
   //     // @if $enable-shadows and $btn-active-box-shadow != none {
   //     //   @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
@@ -70,7 +74,7 @@
   color: $color;
   border-color: $color;
 
-  @include bs.hover() {
+  &:hover {
     color: $color-hover;
     background-color: $active-background;
     border-color: $active-border;
@@ -111,7 +115,7 @@
     fill: hsl.contrast($background);
   }
 
-  @include bs.hover() {
+  &:hover {
     svg {
       fill: hsl.contrast($background);
     }

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

@@ -1,4 +1,5 @@
-@use '../../bootstrap/init' as bs;
+@use '@growi/core/scss/bootstrap/init' as bs;
+
 @use '../../mixins';
 @use './count-badge';
 
@@ -38,7 +39,7 @@
     .btn.btn-page-item-control {
       color: $btn-color;
       background-color: transparent;
-      @include bs.hover() {
+      &:hover {
         color: $btn-color-hover;
         background-color: $btn-bgcolor-hover;
       }

+ 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/^\([*] \[\`\)[^\`]\+\(\`, \`6\.2\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/apps\/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

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

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

+ 9 - 10
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.3.0-RC.0",
+  "version": "7.0.0-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -69,7 +69,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",
@@ -176,9 +175,9 @@
     "react-multiline-clamp": "^2.0.0",
     "react-scroll": "^1.8.7",
     "react-syntax-highlighter": "^15.5.0",
-    "react-toastify": "^9.1.1",
+    "react-toastify": "^9.1.3",
     "react-use-ripple": "^1.5.2",
-    "reactstrap": "^8.10.1",
+    "reactstrap": "^9.2.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "rehype-katex": "^6.0.2",
@@ -199,7 +198,7 @@
     "string-width": "=4.2.2",
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
-    "swr": "^2.0.3",
+    "swr": "^2.2.2",
     "throttle-debounce": "^5.0.0",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
@@ -216,6 +215,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
+    "@growi/editor": "link:../../packages/editor",
     "@growi/presentation": "link:../../packages/presentation",
     "@growi/ui": "link:../../packages/ui",
     "@handsontable/react": "=2.1.0",
@@ -226,11 +226,12 @@
     "@types/express": "^4.17.11",
     "@types/jest": "^29.5.2",
     "@types/react-scroll": "^1.8.4",
+    "@types/throttle-debounce": "^5.0.1",
+    "@types/url-join": "^4.0.2",
     "@vitest/coverage-v8": "^0.34.6",
     "autoprefixer": "^9.0.0",
     "babel-loader": "^8.2.5",
-    "bootstrap": "^4.6.1",
-    "codemirror": "^5.64.0",
+    "bootstrap": "^5.3.1",
     "connect-browser-sync": "^2.1.0",
     "diff2html": "^3.4.35",
     "eazy-logger": "^3.1.0",
@@ -245,21 +246,19 @@
     "jest": "^29.5.0",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
-    "jquery": "^3.7.0",
     "load-css-file": "^1.0.0",
     "material-icons": "^1.11.3",
     "mongodb-memory-server-core": "^9.1.1",
     "morgan": "^1.10.0",
     "null-loader": "^4.0.1",
-    "penpal": "^4.0.0",
     "plantuml-encoder": "^1.2.5",
-    "popper.js": "^1.16.1",
     "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",
     "react-hotkeys": "^2.0.0",
+    "react-stickynode": "^4.1.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",

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

@@ -496,8 +496,8 @@
       "enable_marp_desc": "Marp can be used in presentation preview. This option may make you vulnerable to XSS.",
       "marp_official_site": "The Marp Official Site",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "GROWI Docs - Create slides for a presentation",
-      "presentation_docs_link": "https://docs.growi.org/en/guide/features/presentation.html"
+      "marp_in_growi" : "GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "Custom title",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
@@ -565,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": {
@@ -748,7 +744,7 @@
       "description1":"Temporarily issue new users by email addresses.",
       "description2":"A temporary password will be generated for the first login.",
       "invite_thru_email": "Send invitation email",
-      "mail_setting_link":"<i class='icon-settings mr-2'></i><a href='/admin/app'>Email settings</a>",
+      "mail_setting_link":"<i class='icon-settings me-2'></i><a href='/admin/app'>Email settings</a>",
       "valid_email": "Valid email address is required",
       "temporary_password": "The created user has a temporary password",
       "send_new_password": "Please send the new password to the user.",

+ 13 - 0
apps/app/public/static/locales/en_US/commons.json

@@ -68,6 +68,19 @@
     "feedback": "Feedback"
   },
 
+  "create_page_dropdown": {
+    "new_page": "Create New Page",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "Create/Edit template page",
+      "children": "Template for children",
+      "descendants": "Template for descendants"
+    }
+  },
+
   "copy_to_clipboard": {
     "Copy to clipboard": "Copy to clipboard",
     "Page path": "Page path",

+ 10 - 25
apps/app/public/static/locales/en_US/translation.json

@@ -106,8 +106,6 @@
   "Disclose E-mail": "Disclose E-mail",
   "page exists": "this page already exists",
   "Error occurred": "Error occurred",
-  "Create today's": "Create today's ...",
-  "Memo": "memo",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
   "New Page": "New page",
@@ -251,6 +249,14 @@
       "page_create": "Subscribe to the page when you create it."
     }
   },
+  "ui_settings": {
+    "ui_settings": "UI Settings",
+    "side_bar_mode": {
+      "settings": "Sidebar mode settings",
+      "side_bar_mode_setting": "Set the sidebar mode",
+      "description": "You can set whether or not the sidebar will always be open when the screen width is large. If the screen width is small, the sidebar will always be closed."
+    }
+  },
   "editor_settings": {
     "editor_settings": "Editor Settings"
   },
@@ -466,7 +472,7 @@
       "label": "Template for children",
       "desc": "Applies only to the same level pages which the template exists"
     },
-    "decendants": {
+    "descendants": {
       "label": "Template for descendants",
       "desc": "Applies to all decendant pages"
     }
@@ -502,27 +508,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."
@@ -737,7 +722,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",

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

@@ -506,8 +506,8 @@
       "enable_marp_desc": "プレゼンテーション表示に Marp を利用できるようになります。ただし、XSS に対して脆弱になる恐れがあります。",
       "marp_official_site": "参考:Marp 公式サイト",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "参考:GROWI Docs - プレゼンテーション機能を使う",
-      "presentation_docs_link": "https://docs.growi.org/ja/guide/features/presentation.html"
+      "marp_in_growi" : "参考:GROWI Docs - Marp でスライドを作成する",
+      "marp_in_growi_link": "https://docs.growi.org/ja/guide/features/marp.html"
     },
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
@@ -575,10 +575,6 @@
           "initialize_meta_datas": {
             "label": "「いいね」「閲覧したユーザー」「コメント数」を初期化する",
             "desc": "users を同時に復元しない場合、このオプションは<span class=\"text-danger\">非推奨</span>です。"
-          },
-          "initialize_hackmd_related_datas": {
-            "label": "HackMD 関連データを初期化する",
-            "desc": "HackMD に重要な下書きデータがない限りはこのオプションをチェックすることを推奨します。"
           }
         },
         "revisions": {
@@ -758,7 +754,7 @@
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
       "description2": "初回のログイン時に使用する仮パスワードが生成されます。",
       "invite_thru_email": "招待メールを送信する",
-      "mail_setting_link": "<i class='icon-settings mr-2'></i><a href='/admin/app'>メールの設定</a>",
+      "mail_setting_link": "<i class='icon-settings me-2'></i><a href='/admin/app'>メールの設定</a>",
       "valid_email": "メールアドレスを入力してください。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",

+ 13 - 0
apps/app/public/static/locales/ja_JP/commons.json

@@ -70,6 +70,19 @@
     "feedback": "ご意見・ご要望"
   },
 
+  "create_page_dropdown": {
+    "new_page": "新規ページ作成",
+    "todays": {
+      "desc": "今日の◯◯を作成",
+      "memo": "メモ"
+    },
+    "template": {
+      "desc": "テンプレートページの作成/編集",
+      "children": "同一階層テンプレート",
+      "descendants": "下位層テンプレート"
+    }
+  },
+
   "copy_to_clipboard": {
     "Copy to clipboard": "クリップボードにコピー",
     "Page path": "ページ名",

+ 10 - 25
apps/app/public/static/locales/ja_JP/translation.json

@@ -105,8 +105,6 @@
   "Disclose E-mail": "メールアドレスの公開",
   "page exists": "このページはすでに存在しています",
   "Error occurred": "エラーが発生しました",
-  "Create today's": "今日の◯◯を作成",
-  "Memo": "メモ",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "New Page": "新規ページ",
@@ -252,6 +250,14 @@
       "page_create": "ページを作成した時にそのページをサブスクライブします。"
     }
   },
+  "ui_settings": {
+    "ui_settings": "UI設定",
+    "side_bar_mode": {
+      "settings": "サイドバーモードの設定",
+      "side_bar_mode_setting": "サイドバーのモードを設定する",
+      "description": "画面幅が大きい場合に、サイドバーを常時開いた状態にするかどうかを設定できます。画面幅が小さい場合はサイドバーは常に閉じた状態となります。"
+    }
+  },
   "editor_settings": {
     "editor_settings": "エディター設定",
     "common_settings": {
@@ -499,7 +505,7 @@
       "label": "同一階層テンプレート",
       "desc": "テンプレートページが存在する階層にのみ適用されます"
     },
-    "decendants": {
+    "descendants": {
       "label": "下位層テンプレート",
       "desc": "テンプレートページが存在する下位層のすべてのページに適用されます"
     }
@@ -535,27 +541,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": "チャンネル名を入れてください。カンマ区切りのリストを入力することで複数のチャンネルに通知することができます。"
@@ -770,7 +755,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": "リンクを知っている人のみ",

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

@@ -505,8 +505,8 @@
       "enable_marp_desc": "Marp 可在演示视图中使用。该选项可能会使您受到 XSS 的攻击。",
       "marp_official_site": "参考资料:Marp 官方网站",
       "marp_official_site_link": "https://marp.app",
-      "presentation_docs" : "参考资料:GROWI Docs - Create slides for a presentation",
-      "presentation_docs_link": "https://docs.growi.org/en/guide/features/presentation.html"
+      "marp_in_growi" : "参考资料:GROWI Docs - Create slide using Marp",
+      "marp_in_growi_link": "https://docs.growi.org/en/guide/features/marp.html"
     },
     "custom_title": "自定义标题",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
@@ -574,10 +574,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": {
@@ -756,7 +752,7 @@
       "emails": "电子邮件",
       "description1": "通过电子邮件地址临时发布新用户。",
       "description2": "将为首次登录生成一个临时密码。",
-      "mail_setting_link": "<i class='icon-settings mr-2'></i><a href='/admin/app'>Email settings</a>",
+      "mail_setting_link": "<i class='icon-settings me-2'></i><a href='/admin/app'>Email settings</a>",
       "valid_email": "需要有效的电子邮件地址",
       "invite_thru_email": "发送邀请电子邮件",
       "temporary_password": "创建的用户具有临时密码",

+ 13 - 0
apps/app/public/static/locales/zh_CN/commons.json

@@ -71,6 +71,19 @@
     "feedback": "意见和要求"
   },
 
+  "create_page_dropdown": {
+    "new_page": "新页面",
+    "todays": {
+      "desc": "Create today's ...",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "创建/编辑模板页",
+      "children": "子模板",
+      "descendants": "子代模板"
+    }
+  },
+
 	"copy_to_clipboard": {
 		"Copy to clipboard": "复制到剪贴板",
 		"Page path": "页面路径",

+ 10 - 25
apps/app/public/static/locales/zh_CN/translation.json

@@ -111,8 +111,6 @@
 	"Disclose E-mail": "显示邮箱",
 	"page exists": "页面已存在",
 	"Error occurred": "Error occurred",
-	"Create today's": "Create today's ...",
-	"Memo": "memo",
 	"Input page name": "Input page name",
 	"Input page name (optional)": "Input page name (optional)",
 	"New Page": "新页面",
@@ -242,6 +240,14 @@
       "page_create": "创建页面时订阅页面。"
     }
   },
+  "ui_settings": {
+    "ui_settings": "用户界面设置",
+    "side_bar_mode": {
+      "settings": "侧边栏模式设置",
+      "side_bar_mode_setting": "设置侧边栏模式",
+      "description": "您可以设置当屏幕宽度较大时,侧边栏是否始终打开。 如果屏幕宽度较小,侧边栏将始终关闭。"
+    }
+  },
   "editor_settings": {
     "editor_settings": "编辑器设置"
   },
@@ -453,7 +459,7 @@
 			"label": "子模板",
 			"desc": "仅应用于模板存在的同一级别页"
 		},
-		"decendants": {
+		"descendants": {
 			"label": "子代模板",
 			"desc": "适用于所有分散页"
 		}
@@ -489,27 +495,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."
@@ -740,7 +725,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": "只有那些知道链接的人",

+ 3 - 0
apps/app/resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b607eb2ff757a116a1bf6bfac3702b38c8d5b2d20caa36654c31e8116c299ee7
+size 868836

+ 21 - 10
apps/app/src/client/services/layout.ts

@@ -1,7 +1,6 @@
 import type { IPage } from '@growi/core';
 
 import { useIsContainerFluid } from '~/stores/context';
-import { useSWRxCurrentPage } from '~/stores/page';
 import { useEditorMode } from '~/stores/ui';
 
 export const useEditorModeClassName = (): string => {
@@ -10,17 +9,29 @@ export const useEditorModeClassName = (): string => {
   return `${getClassNamesByEditorMode().join(' ') ?? ''}`;
 };
 
-export const useCurrentGrowiLayoutFluidClassName = (initialPage?: IPage): string => {
-  const { data: currentPage } = useSWRxCurrentPage();
-
+const useDetermineExpandContent = (expandContentWidth?: boolean | null): boolean => {
   const { data: dataIsContainerFluid } = useIsContainerFluid();
 
-  const page = currentPage ?? initialPage;
-  const isContainerFluidEachPage = page == null || !('expandContentWidth' in page)
-    ? null
-    : page.expandContentWidth;
   const isContainerFluidDefault = dataIsContainerFluid;
-  const isContainerFluid = isContainerFluidEachPage ?? isContainerFluidDefault;
+  return expandContentWidth ?? isContainerFluidDefault ?? false;
+};
+
+export const useShouldExpandContent = (data?: IPage | boolean | null): boolean => {
+  const expandContentWidth = (() => {
+    // when data is null
+    if (data == null) {
+      return null;
+    }
+    // when data is boolean
+    if (data === true || data === false) {
+      return data;
+    }
+    // when IPage does not have expandContentWidth
+    if (!('expandContentWidth' in data)) {
+      return null;
+    }
+    return data.expandContentWidth;
+  })();
 
-  return isContainerFluid ? 'growi-layout-fluid' : '';
+  return useDetermineExpandContent(expandContentWidth);
 };

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

@@ -9,7 +9,7 @@ import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stor
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
-import { apiPost } from '../util/apiv1-client';
+import { apiGet, apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { toastError } from '../util/toastr';
 
@@ -88,7 +88,7 @@ export const resumeRenameOperation = async(pageId: string): Promise<void> => {
 };
 
 // TODO: define return type
-const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
+export const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
   // clone
   const params = Object.assign(tmpParams, {
     path: pagePath,
@@ -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) {
@@ -165,10 +148,7 @@ export const useSaveOrUpdate = (): SaveOrUpdateFunction => {
       res = await updatePage(pageId, revisionId, markdown, options);
     }
 
-    // The updateFn should be a promise or asynchronous function to handle the remote mutation
-    // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
-    // Moreover, `async() => false` does not work since it's too fast to be calculated.
-    await mutateIsEnabledUnsavedWarning(new Promise(r => setTimeout(() => r(false), 10)), { optimisticData: () => false });
+    mutateIsEnabledUnsavedWarning(false);
 
     return res;
   }, [mutateIsEnabledUnsavedWarning]);
@@ -212,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);
@@ -225,3 +203,19 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
 export const unlink = async(path: string): Promise<void> => {
   await apiPost('/pages.unlink', { path });
 };
+
+
+interface PageExistRequest {
+  pagePaths: string;
+}
+
+interface PageExistResponse {
+  pages: Record<string, boolean>;
+  ok: boolean
+}
+
+export const exist = async(pagePaths: string): Promise<PageExistResponse> => {
+  const request: PageExistRequest = { pagePaths };
+  const res = await apiGet<PageExistResponse>('/pages.exist', request);
+  return res;
+};

+ 4 - 5
apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts

@@ -30,14 +30,13 @@ export const useDrawioModalLauncherForView = (opts?: {
   const { data: shareLinkId } = useShareLinkId();
 
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: tagsInfo } = useSWRxTagsInfo(currentPage?._id);
 
   const { open: openDrawioModal } = useDrawioModal();
 
   const saveOrUpdate = useSaveOrUpdate();
 
   const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || tagsInfo == null || shareLinkId != null) {
+    if (currentPage == null || shareLinkId != null) {
       return;
     }
 
@@ -55,8 +54,8 @@ export const useDrawioModalLauncherForView = (opts?: {
       isSlackEnabled: false,
       slackChannels: '',
       grant: currentPage.grant,
-      grantUserGroupIds,
-      pageTags: tagsInfo.tags,
+      // grantUserGroupIds,
+      // pageTags: tagsInfo.tags,
     };
 
     try {
@@ -73,7 +72,7 @@ export const useDrawioModalLauncherForView = (opts?: {
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, saveOrUpdate, shareLinkId, tagsInfo]);
+  }, [currentPage, opts, saveOrUpdate, shareLinkId]);
 
 
   // set handler to open DrawioModal

+ 8 - 9
apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts

@@ -4,7 +4,7 @@ import EventEmitter from 'events';
 
 import MarkdownTable from '~/client/models/MarkdownTable';
 import { useSaveOrUpdate } from '~/client/services/page-operation';
-import mtu from '~/components/PageEditor/MarkdownTableUtil';
+import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/PageEditor/markdown-table-util-for-view';
 import type { OptionsToSave } from '~/interfaces/page-operation';
 import { useShareLinkId } from '~/stores/context';
 import { useHandsontableModal } from '~/stores/modal';
@@ -29,19 +29,18 @@ export const useHandsontableModalLauncherForView = (opts?: {
   const { data: shareLinkId } = useShareLinkId();
 
   const { data: currentPage } = useSWRxCurrentPage();
-  const { data: tagsInfo } = useSWRxTagsInfo(currentPage?._id);
 
   const { open: openHandsontableModal } = useHandsontableModal();
 
   const saveOrUpdate = useSaveOrUpdate();
 
   const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || tagsInfo == null || shareLinkId != null) {
+    if (currentPage == null || shareLinkId != null) {
       return;
     }
 
     const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
+    const newMarkdown = replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
 
     const grantUserGroupIds = currentPage.grantedGroups.map((g) => {
       return {
@@ -54,8 +53,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
       isSlackEnabled: false,
       slackChannels: '',
       grant: currentPage.grant,
-      grantUserGroupIds,
-      pageTags: tagsInfo.tags,
+      // grantUserGroupIds,
+      // pageTags: tagsInfo.tags,
     };
 
     try {
@@ -72,7 +71,7 @@ export const useHandsontableModalLauncherForView = (opts?: {
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, saveOrUpdate, shareLinkId, tagsInfo]);
+  }, [currentPage, opts, saveOrUpdate, shareLinkId]);
 
 
   // set handler to open HandsonTableModal
@@ -83,8 +82,8 @@ export const useHandsontableModalLauncherForView = (opts?: {
 
     const handler = (bol: number, eol: number) => {
       const markdown = currentPage.revision.body;
-      const currentMarkdownTable = mtu.getMarkdownTableFromLine(markdown, bol, eol);
-      openHandsontableModal(currentMarkdownTable, undefined, false, table => saveByHandsontableModal(table, bol, eol));
+      const currentMarkdownTable = getMarkdownTableFromLine(markdown, bol, eol);
+      openHandsontableModal(currentMarkdownTable, false, table => saveByHandsontableModal(table, bol, eol));
     };
     globalEmitter.on('launchHandsonTableModal', handler);
 

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

+ 49 - 0
apps/app/src/client/services/use-on-template-button-clicked.ts

@@ -0,0 +1,49 @@
+import { useCallback, useState } from 'react';
+
+import { useRouter } from 'next/router';
+
+import { createPage, exist } from '~/client/services/page-operation';
+import { LabelType } from '~/interfaces/template';
+
+export const useOnTemplateButtonClicked = (
+    currentPagePath?: string,
+): {
+  onClickHandler: (label: LabelType) => Promise<void>,
+  isPageCreating: boolean
+} => {
+  const router = useRouter();
+  const [isPageCreating, setIsPageCreating] = useState(false);
+
+  const onClickHandler = useCallback(async(label: LabelType) => {
+    try {
+      setIsPageCreating(true);
+
+      const path = currentPagePath == null || currentPagePath === '/'
+        ? `/${label}`
+        : `${currentPagePath}/${label}`;
+
+      const params = {
+        isSlackEnabled: false,
+        slackChannels: '',
+        grant: 4,
+      // grant: currentPage?.grant || 1,
+      // grantUserGroupId: currentPage?.grantedGroup?._id,
+      };
+
+      const res = await exist(JSON.stringify([path]));
+      if (!res.pages[path]) {
+        await createPage(path, '', params);
+      }
+
+      router.push(`${path}#edit`);
+    }
+    catch (err) {
+      throw err;
+    }
+    finally {
+      setIsPageCreating(false);
+    }
+  }, [currentPagePath, router]);
+
+  return { onClickHandler, isPageCreating };
+};

+ 5 - 10
apps/app/src/client/services/user-ui-settings.ts

@@ -17,22 +17,17 @@ const _putUserUISettingsInBulk = (): Promise<AxiosResponse<IUserUISettings>> =>
 
 const _putUserUISettingsInBulkDebounced = debounce(1500, _putUserUISettingsInBulk);
 
-type ScheduleToPutFunction = (settings: Partial<IUserUISettings>) => Promise<AxiosResponse<IUserUISettings>>;
-const scheduleToPut: ScheduleToPutFunction = (settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
+export const scheduleToPut = (settings: Partial<IUserUISettings>): void => {
   settingsForBulk = {
     ...settingsForBulk,
     ...settings,
   };
 
-  return _putUserUISettingsInBulkDebounced();
+  _putUserUISettingsInBulkDebounced();
 };
 
-type UserUISettingsUtil = {
-  scheduleToPut: ScheduleToPutFunction | (() => void),
-}
-export const useUserUISettings = (): UserUISettingsUtil => {
+export const updateUserUISettings = async(settings: Partial<IUserUISettings>): Promise<AxiosResponse<IUserUISettings>> => {
+  const result = await apiv3Put<IUserUISettings>('/user-ui-settings', { settings });
 
-  return {
-    scheduleToPut,
-  };
+  return result;
 };

+ 1 - 1
apps/app/src/client/util/apiv1-client.ts

@@ -1,4 +1,4 @@
-import * as urljoin from 'url-join';
+import urljoin from 'url-join';
 
 import axios from '~/utils/axios';
 

+ 1 - 1
apps/app/src/client/util/apiv3-client.ts

@@ -1,6 +1,6 @@
 // eslint-disable-next-line no-restricted-imports
 import { AxiosResponse } from 'axios';
-import * as urljoin from 'url-join';
+import urljoin from 'url-join';
 
 // eslint-disable-next-line no-restricted-imports
 

+ 3 - 3
apps/app/src/components/Admin/AdminHome/AdminHome.jsx

@@ -52,7 +52,7 @@ const AdminHome = (props) => {
             </p>
             <hr />
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
-              <i className="fa fa-link ml-1" aria-hidden="true"></i>
+              <i className="fa fa-link ms-1" aria-hidden="true"></i>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
             </a>
           </div>
@@ -65,7 +65,7 @@ const AdminHome = (props) => {
           <div className={`alert ${migrationStatus.isV5Compatible == null ? 'alert-warning' : 'alert-info'}`}>
             {t('admin:v5_page_migration.migration_desc')}
             <a className="btn-link" href="/admin/app" rel="noopener noreferrer">
-              <i className="fa fa-link ml-1" aria-hidden="true"></i>
+              <i className="fa fa-link ms-1" aria-hidden="true"></i>
               <strong>{t('admin:v5_page_migration.upgrade_to_v5')}</strong>
             </a>
           </div>
@@ -115,7 +115,7 @@ const AdminHome = (props) => {
               {t('admin:admin_top:copy_prefilled_host_information:done')}
             </Tooltip>
             {/* eslint-disable-next-line react/no-danger */}
-            <span className="ml-2" dangerouslySetInnerHTML={{ __html: t('admin:admin_top:submit_bug_report') }} />
+            <span className="ms-2" dangerouslySetInnerHTML={{ __html: t('admin:admin_top:submit_bug_report') }} />
           </div>
         </div>
       </div>

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

@@ -34,8 +34,8 @@ const AppSetting = (props) => {
 
   return (
     <React.Fragment>
-      <div className="form-group row">
-        <label className="text-left text-md-right col-md-3 col-form-label">{t('admin:app_setting.site_name')}</label>
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">{t('admin:app_setting.site_name')}</label>
         <div className="col-md-6">
           <input
             className="form-control"
@@ -50,9 +50,9 @@ const AppSetting = (props) => {
         </div>
       </div>
 
-      <div className="row form-group mb-5">
+      <div className="row mb-5">
         <label
-          className="text-left text-md-right col-md-3 col-form-label"
+          className="text-start text-md-end col-md-3 col-form-label"
         >
           {t('admin:app_setting.confidential_name')}
         </label>
@@ -70,9 +70,9 @@ const AppSetting = (props) => {
         </div>
       </div>
 
-      <div className="row form-group mb-5">
+      <div className="row mb-5">
         <label
-          className="text-left text-md-right col-md-3 col-form-label"
+          className="text-start text-md-end col-md-3 col-form-label"
         >
           {t('admin:app_setting.default_language')}
         </label>
@@ -83,11 +83,11 @@ const AppSetting = (props) => {
               const fixedT = i18n.getFixedT(locale, 'admin');
 
               return (
-                <div key={locale} className="custom-control custom-radio custom-control-inline">
+                <div key={locale} className="form-check form-check-inline">
                   <input
                     type="radio"
                     id={`radioLang${locale}`}
-                    className="custom-control-input"
+                    className="form-check-input"
                     name="globalLang"
                     value={locale}
                     checked={adminAppContainer.state.globalLang === locale}
@@ -95,7 +95,7 @@ const AppSetting = (props) => {
                       adminAppContainer.changeGlobalLang(e.target.value);
                     }}
                   />
-                  <label className="custom-control-label" htmlFor={`radioLang${locale}`}>{fixedT('meta.display_name')}</label>
+                  <label className="form-label form-check-label" htmlFor={`radioLang${locale}`}>{fixedT('meta.display_name')}</label>
                 </div>
               );
             })
@@ -103,53 +103,53 @@ const AppSetting = (props) => {
         </div>
       </div>
 
-      <div className="row form-group mb-5">
+      <div className="row mb-5">
         <label
-          className="text-left text-md-right col-md-3 col-form-label"
+          className="text-start text-md-end col-md-3 col-form-label"
         >
           {t('admin:app_setting.default_mail_visibility')}
         </label>
         <div className="col-md-6 py-2">
 
-          <div className="custom-control custom-radio custom-control-inline">
+          <div className="form-check form-check-inline">
             <input
               type="radio"
               id="radio-email-show"
-              className="custom-control-input"
+              className="form-check-input"
               name="mailVisibility"
               checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
             />
-            <label className="custom-control-label" htmlFor="radio-email-show">{t('commons:Show')}</label>
+            <label className="form-label form-check-label" htmlFor="radio-email-show">{t('commons:Show')}</label>
           </div>
 
-          <div className="custom-control custom-radio custom-control-inline">
+          <div className="form-check form-check-inline">
             <input
               type="radio"
               id="radio-email-hide"
-              className="custom-control-input"
+              className="form-check-input"
               name="mailVisibility"
               checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
             />
-            <label className="custom-control-label" htmlFor="radio-email-hide">{t('commons:Hide')}</label>
+            <label className="form-label form-check-label" htmlFor="radio-email-hide">{t('commons:Hide')}</label>
           </div>
 
         </div>
       </div>
 
-      <div className="row form-group mb-5">
+      <div className="row mb-5">
         <label
-          className="text-left text-md-right col-md-3 col-form-label"
+          className="text-start text-md-end col-md-3 col-form-label"
         >
           {/* {t('admin:app_setting.file_uploading')} */}
         </label>
         <div className="col-md-6">
-          <div className="custom-control custom-checkbox custom-checkbox-info">
+          <div className="form-check form-check-info">
             <input
               type="checkbox"
               id="cbFileUpload"
-              className="custom-control-input"
+              className="form-check-input"
               name="fileUpload"
               checked={adminAppContainer.state.fileUpload}
               onChange={(e) => {
@@ -157,7 +157,7 @@ const AppSetting = (props) => {
               }}
             />
             <label
-              className="custom-control-label"
+              className="form-label form-check-label"
               htmlFor="cbFileUpload"
             >
               {t('admin:app_setting.enable_files_except_image')}

+ 1 - 1
apps/app/src/components/Admin/App/AppSettingsPageContents.tsx

@@ -62,7 +62,7 @@ const AppSettingsPageContents = (props: Props) => {
             </p>
             <hr />
             <a className="btn-link" href="#maintenance-mode" rel="noopener noreferrer">
-              <i className="fa fa-fw fa-arrow-down ml-1" aria-hidden="true"></i>
+              <i className="fa fa-fw fa-arrow-down ms-1" aria-hidden="true"></i>
               <strong>{t('admin:maintenance_mode.end_maintenance_mode')}</strong>
             </a>
           </div>

+ 13 - 13
apps/app/src/components/Admin/App/AwsSetting.tsx

@@ -21,8 +21,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
   return (
     <>
 
-      <div className="row form-group my-3">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row my-3">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_delivery_method')}
         </label>
 
@@ -32,7 +32,7 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
               className="btn btn-outline-secondary dropdown-toggle"
               type="button"
               id="ddS3ReferenceFileWithRelayMode"
-              data-toggle="dropdown"
+              data-bs-toggle="dropdown"
               aria-haspopup="true"
               aria-expanded="true"
             >
@@ -65,8 +65,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
         </div>
       </div>
 
-      <div className="row form-group">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.region')}
         </label>
         <div className="col-md-6">
@@ -81,8 +81,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
         </div>
       </div>
 
-      <div className="row form-group">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.custom_endpoint')}
         </label>
         <div className="col-md-6">
@@ -99,8 +99,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
         </div>
       </div>
 
-      <div className="row form-group">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.bucket_name')}
         </label>
         <div className="col-md-6">
@@ -116,8 +116,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
         </div>
       </div>
 
-      <div className="row form-group">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           Access key ID
         </label>
         <div className="col-md-6">
@@ -132,8 +132,8 @@ export const AwsSettingMolecule = (props: AwsSettingMoleculeProps): JSX.Element
         </div>
       </div>
 
-      <div className="row form-group">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           Secret access key
         </label>
         <div className="col-md-6">

+ 2 - 2
apps/app/src/components/Admin/App/ConfirmModal.tsx

@@ -30,7 +30,7 @@ export const ConfirmModal: FC<ConfirmModalProps> = (props: ConfirmModalProps) =>
   };
 
   return (
-    <Modal isOpen={props.isModalOpen} toggle={onCancel} className="">
+    <Modal isOpen={props.isModalOpen} toggle={onCancel}>
       <ModalHeader tag="h4" toggle={onCancel} className="bg-danger">
         <i className="icon-fw icon-question" />
         {t('Warning')}
@@ -62,7 +62,7 @@ export const ConfirmModal: FC<ConfirmModalProps> = (props: ConfirmModalProps) =>
         </button>
         <button
           type="button"
-          className="btn btn-outline-primary ml-3"
+          className="btn btn-outline-primary ms-3"
           onClick={onConfirm}
         >
           {props.confirmButtonTitle ?? t('Confirm')}

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

@@ -30,7 +30,7 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
 
   return (
     <>
-      <p className="card well my-3">
+      <p className="card custom-card my-3">
         {t('admin:app_setting.file_upload')}
         <br />
         <br />
@@ -40,31 +40,31 @@ export const FileUploadSettingMolecule = React.memo((props: FileUploadSettingMol
         </span>
       </p>
 
-      <div className="row form-group mb-3">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row mb-3">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_upload_method')}
         </label>
 
         <div className="col-md-6 py-2">
           {fileUploadTypes.map((type) => {
             return (
-              <div key={type} className="custom-control custom-radio custom-control-inline">
+              <div key={type} className="form-check form-check-inline">
                 <input
                   type="radio"
-                  className="custom-control-input"
+                  className="form-check-input"
                   name="file-upload-type"
                   id={`file-upload-type-radio-${type}`}
                   checked={props.fileUploadType === type}
                   disabled={props.isFixedFileUploadByEnvVar}
                   onChange={(e) => { props?.onChangeFileUploadType(e, type) }}
                 />
-                <label className="custom-control-label" htmlFor={`file-upload-type-radio-${type}`}>{t(`admin:app_setting.${type}_label`)}</label>
+                <label className="form-label form-check-label" htmlFor={`file-upload-type-radio-${type}`}>{t(`admin:app_setting.${type}_label`)}</label>
               </div>
             );
           })}
         </div>
         {props.isFixedFileUploadByEnvVar && (
-          <p className="alert alert-warning mt-2 text-left offset-3 col-6">
+          <p className="alert alert-warning mt-2 text-start offset-3 col-6">
             <i className="icon-exclamation icon-fw">
             </i><b>FIXED</b><br />
             {/* eslint-disable-next-line react/no-danger */}

+ 3 - 3
apps/app/src/components/Admin/App/GcsSetting.tsx

@@ -32,8 +32,8 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
   return (
     <>
 
-      <div className="row form-group my-3">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row my-3">
+        <label className="text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.file_delivery_method')}
         </label>
 
@@ -43,7 +43,7 @@ export const GcsSettingMolecule = (props: GcsSettingMoleculeProps): JSX.Element
               className="btn btn-outline-secondary dropdown-toggle"
               type="button"
               id="ddGcsReferenceFileWithRelayMode"
-              data-toggle="dropdown"
+              data-bs-toggle="dropdown"
               aria-haspopup="true"
               aria-expanded="true"
             >

+ 8 - 8
apps/app/src/components/Admin/App/MailSetting.tsx

@@ -49,8 +49,8 @@ const MailSetting = (props: Props) => {
       {!adminAppContainer.state.isMailerSetup && (
         <div className="alert alert-danger"><i className="icon-exclamation"></i> {t('admin:app_setting.mailer_is_not_set_up')}</div>
       )}
-      <div className="row form-group mb-5">
-        <label className="col-md-3 col-form-label text-right">{t('admin:app_setting.from_e-mail_address')}</label>
+      <div className="row mb-5">
+        <label className="col-md-3 col-form-label text-end">{t('admin:app_setting.from_e-mail_address')}</label>
         <div className="col-md-6">
           <input
             className="form-control"
@@ -62,17 +62,17 @@ const MailSetting = (props: Props) => {
         </div>
       </div>
 
-      <div className="row form-group mb-5">
-        <label className="text-left text-md-right col-md-3 col-form-label">
+      <div className="row mb-5">
+        <label className="form-label text-start text-md-end col-md-3 col-form-label">
           {t('admin:app_setting.transmission_method')}
         </label>
         <div className="col-md-6 py-2">
           {transmissionMethods.map((method) => {
             return (
-              <div key={method} className="custom-control custom-radio custom-control-inline">
+              <div key={method} className="form-check form-check-inline">
                 <input
                   type="radio"
-                  className="custom-control-input"
+                  className="form-check-input"
                   name="transmission-method"
                   id={`transmission-method-radio-${method}`}
                   checked={adminAppContainer.state.transmissionMethod === method}
@@ -80,7 +80,7 @@ const MailSetting = (props: Props) => {
                     adminAppContainer.changeTransmissionMethod(method);
                   }}
                 />
-                <label className="custom-control-label" htmlFor={`transmission-method-radio-${method}`}>{t(`admin:app_setting.${method}_label`)}</label>
+                <label className="form-label form-check-label" htmlFor={`transmission-method-radio-${method}`}>{t(`admin:app_setting.${method}_label`)}</label>
               </div>
             );
           })}
@@ -96,7 +96,7 @@ const MailSetting = (props: Props) => {
             { t('Update') }
           </button>
           {adminAppContainer.state.transmissionMethod === 'smtp' && (
-            <button type="button" className="btn btn-secondary ml-4" onClick={sendTestEmailHandler}>
+            <button type="button" className="btn btn-secondary ms-4" onClick={sendTestEmailHandler}>
               {t('admin:app_setting.send_test_email')}
             </button>
           )}

+ 1 - 1
apps/app/src/components/Admin/App/MaintenanceMode.tsx

@@ -54,7 +54,7 @@ export const MaintenanceMode: FC = () => {
         onConfirm={onConfirmHandler}
         onCancel={() => closeModal()}
       />
-      <p className="card well">
+      <p className="card custom-card">
         {t('admin:maintenance_mode.description')}
         <br />
         <br />

+ 9 - 9
apps/app/src/components/Admin/App/QuestionnaireSettings.tsx

@@ -49,11 +49,11 @@ const QuestionnaireSettings = (): JSX.Element => {
 
   return (
     <div id="questionnaire-settings" className="mb-5">
-      <p className="card well">
+      <p className="card custom-card">
         <div className="mb-4">{t('app_setting.questionnaire_settings_explanation')}</div>
         <span>
           <div className="mb-2">
-            <span className="text-info mr-2"><i className="icon-info icon-fw"></i>{t('app_setting.about_data_sent')}</span>
+            <span className="text-info me-2"><i className="icon-info icon-fw"></i>{t('app_setting.about_data_sent')}</span>
             <a href={t('app_setting.docs_link')} rel="noreferrer" target="_blank" className="d-inline">
               {t('app_setting.learn_more')} <i className="icon-share-alt"></i>
             </a>
@@ -65,38 +65,38 @@ const QuestionnaireSettings = (): JSX.Element => {
 
       {isLoading && (
         <div className="text-muted text-center mb-5">
-          <i className="fa fa-2x fa-spinner fa-pulse mr-1" />
+          <i className="fa fa-2x fa-spinner fa-pulse me-1" />
         </div>
       )}
 
       {!isLoading && (
         <>
           <div className="row my-3">
-            <div className="custom-control custom-switch custom-checkbox-info col-md-5 offset-md-5">
+            <div className="form-check form-switch form-check-info col-md-5 offset-md-5">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="form-check-input"
                 id="isQuestionnaireEnabled"
                 checked={isQuestionnaireEnabled}
                 onChange={onChangeIsQuestionnaireEnabledHandler}
               />
-              <label className="custom-control-label" htmlFor="isQuestionnaireEnabled">
+              <label className="form-label form-check-label" htmlFor="isQuestionnaireEnabled">
                 {t('app_setting.enable_questionnaire')}
               </label>
             </div>
           </div>
 
           <div className="row my-4">
-            <div className="custom-control custom-checkbox custom-checkbox-info col-md-5 offset-md-5">
+            <div className="form-check form-check-info col-md-5 offset-md-5">
               <input
                 type="checkbox"
-                className="custom-control-input"
+                className="form-check-input"
                 id="isAppSiteUrlHashed"
                 checked={isAppSiteUrlHashed}
                 onChange={onChangeisAppSiteUrlHashedHandler}
                 disabled={!isQuestionnaireEnabled}
               />
-              <label className="custom-control-label" htmlFor="isAppSiteUrlHashed">
+              <label className="form-label form-check-label" htmlFor="isAppSiteUrlHashed">
                 {t('app_setting.anonymize_app_site_url')}
               </label>
               <p className="form-text text-muted small">

+ 4 - 4
apps/app/src/components/Admin/App/SesSetting.tsx

@@ -16,8 +16,8 @@ const SmtpSetting = (props: Props) => {
     <React.Fragment>
       <div id="mail-smtp" className="tab-pane active mt-5">
 
-        <div className="row form-group">
-          <label className="text-left text-md-right col-md-3 col-form-label">
+        <div className="row">
+          <label className="text-start text-md-end col-md-3 col-form-label">
             Access key ID
           </label>
           <div className="col-md-6">
@@ -32,8 +32,8 @@ const SmtpSetting = (props: Props) => {
           </div>
         </div>
 
-        <div className="row form-group">
-          <label className="text-left text-md-right col-md-3 col-form-label">
+        <div className="row">
+          <label className="text-start text-md-end col-md-3 col-form-label">
             Secret access key
           </label>
           <div className="col-md-6">

+ 2 - 2
apps/app/src/components/Admin/App/SiteUrlSetting.tsx

@@ -35,7 +35,7 @@ const SiteUrlSetting = (props: Props) => {
 
   return (
     <React.Fragment>
-      <p className="card well">{t('site_url.desc')}</p>
+      <p className="card custom-card">{t('site_url.desc')}</p>
       {!adminAppContainer.state.isSetSiteUrl
           && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('site_url.warn')}</p>)}
 
@@ -53,7 +53,7 @@ const SiteUrlSetting = (props: Props) => {
         </div>
       ) }
 
-      <div className="row form-group">
+      <div className="row">
         <div className="col-md-9 offset-md-3">
           <table className="table settings-table">
             <colgroup>

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików