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

Merge pull request #8616 from weseek/dev/7.0.x

support: Merge dev/7.0.x into master
Yuki Takei 2 лет назад
Родитель
Сommit
2c132ec77e
100 измененных файлов с 3736 добавлено и 3843 удалено
  1. 4 2
      .devcontainer/Dockerfile
  2. 2 1
      .devcontainer/devcontainer.json
  3. 2 36
      .devcontainer/docker-compose.yml
  4. 81 0
      .github/workflows-archived/release-rc-v7.yml
  5. 10 10
      .github/workflows/ci-app-prod.yml
  6. 21 21
      .github/workflows/ci-app.yml
  7. 3 3
      .github/workflows/ci-slackbot-proxy.yml
  8. 1 1
      .github/workflows/list-unhealthy-branches.yml
  9. 1 1
      .github/workflows/release-slackbot-proxy.yml
  10. 2 2
      .github/workflows/release.yml
  11. 12 9
      .github/workflows/reusable-app-prod.yml
  12. 2 2
      .github/workflows/reusable-app-reg-suit.yml
  13. 4 4
      .mergify.yml
  14. 2 3
      README.md
  15. 2 3
      README_JP.md
  16. 0 2
      apps/app/.env.development
  17. 0 2
      apps/app/.eslintignore
  18. 4 4
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  19. 1 1
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss
  20. 0 0
      apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts
  21. 344 0
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  22. 22 24
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  23. 672 0
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  24. 21 19
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  25. 0 0
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  26. 51 68
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  27. 0 159
      apps/app/bin/cdn/cdn-resources-downloader.ts
  28. 0 33
      apps/app/bin/download-cdn-resources.ts
  29. 1 1
      apps/app/bin/github-actions/update-readme.sh
  30. 0 1
      apps/app/config/logger/config.dev.js
  31. 11 4
      apps/app/config/next-i18next.config.js
  32. 4 4
      apps/app/docker/Dockerfile
  33. 3 1
      apps/app/docker/README.md
  34. 1 1
      apps/app/next.config.js
  35. 34 32
      apps/app/package.json
  36. 3 0
      apps/app/public/images/icons/slack/slack-logo-background.svg
  37. 3 0
      apps/app/public/images/icons/slack/slack-logo-dark-background.svg
  38. BIN
      apps/app/public/images/icons/sublime.png
  39. BIN
      apps/app/public/images/icons/vscode.png
  40. 15 16
      apps/app/public/static/locales/en_US/admin.json
  41. 19 0
      apps/app/public/static/locales/en_US/commons.json
  42. 116 94
      apps/app/public/static/locales/en_US/translation.json
  43. 15 16
      apps/app/public/static/locales/ja_JP/admin.json
  44. 19 0
      apps/app/public/static/locales/ja_JP/commons.json
  45. 114 92
      apps/app/public/static/locales/ja_JP/translation.json
  46. 15 16
      apps/app/public/static/locales/zh_CN/admin.json
  47. 19 0
      apps/app/public/static/locales/zh_CN/commons.json
  48. 391 386
      apps/app/public/static/locales/zh_CN/translation.json
  49. 0 221
      apps/app/resource/cdn-manifests.js
  50. 3 0
      apps/app/resource/fonts/MaterialSymbolsOutlined-opsz,wght,FILL@20..48,300,0..1.woff2
  51. 0 253
      apps/app/resource/locales/en_US/sandbox-bootstrap4.md
  52. 169 0
      apps/app/resource/locales/en_US/sandbox-bootstrap5.md
  53. 7 10
      apps/app/resource/locales/en_US/sandbox-diagrams.md
  54. 110 391
      apps/app/resource/locales/en_US/sandbox.md
  55. 46 59
      apps/app/resource/locales/en_US/welcome.md
  56. 0 253
      apps/app/resource/locales/ja_JP/sandbox-bootstrap4.md
  57. 258 0
      apps/app/resource/locales/ja_JP/sandbox-bootstrap5.md
  58. 12 3
      apps/app/resource/locales/ja_JP/sandbox-diagrams.md
  59. 4 4
      apps/app/resource/locales/ja_JP/sandbox-math.md
  60. 192 329
      apps/app/resource/locales/ja_JP/sandbox.md
  61. 44 53
      apps/app/resource/locales/ja_JP/welcome.md
  62. 0 253
      apps/app/resource/locales/zh_CN/sandbox-bootstrap4.md
  63. 169 0
      apps/app/resource/locales/zh_CN/sandbox-bootstrap5.md
  64. 7 10
      apps/app/resource/locales/zh_CN/sandbox-diagrams.md
  65. 110 389
      apps/app/resource/locales/zh_CN/sandbox.md
  66. 46 59
      apps/app/resource/locales/zh_CN/welcome.md
  67. 1 1
      apps/app/src/client/models/MarkdownTable.js
  68. 10 0
      apps/app/src/client/services/AdminGeneralSecurityContainer.js
  69. 2 0
      apps/app/src/client/services/create-page/index.ts
  70. 110 0
      apps/app/src/client/services/create-page/use-create-page-and-transit.tsx
  71. 40 0
      apps/app/src/client/services/create-page/use-create-template-page.ts
  72. 21 10
      apps/app/src/client/services/layout.ts
  73. 32 92
      apps/app/src/client/services/page-operation.ts
  74. 56 36
      apps/app/src/client/services/side-effects/drawio-modal-launcher-for-view.ts
  75. 0 33
      apps/app/src/client/services/side-effects/hackmd-draft-updated.ts
  76. 62 39
      apps/app/src/client/services/side-effects/handsontable-modal-launcher-for-view.ts
  77. 34 14
      apps/app/src/client/services/side-effects/page-updated.ts
  78. 22 0
      apps/app/src/client/services/update-page/conflict.tsx
  79. 9 0
      apps/app/src/client/services/update-page/index.ts
  80. 18 0
      apps/app/src/client/services/use-toastr-on-error.tsx
  81. 5 10
      apps/app/src/client/services/user-ui-settings.ts
  82. 1 1
      apps/app/src/client/util/apiv1-client.ts
  83. 1 1
      apps/app/src/client/util/apiv3-client.ts
  84. 2 2
      apps/app/src/client/util/bookmark-utils.ts
  85. 0 51
      apps/app/src/client/util/codemirror/autorefresh.ext.js
  86. 0 47
      apps/app/src/client/util/codemirror/drawio-fold.ext.js
  87. 0 19
      apps/app/src/client/util/codemirror/gfm-growi.mode.js
  88. 0 41
      apps/app/src/client/util/codemirror/update-display-util.ext.js
  89. 3 3
      apps/app/src/components/Admin/AdminHome/AdminHome.jsx
  90. 24 24
      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. 4 4
      apps/app/src/components/Admin/App/ConfirmModal.tsx
  94. 10 10
      apps/app/src/components/Admin/App/FileUploadSetting.tsx
  95. 3 3
      apps/app/src/components/Admin/App/GcsSetting.tsx
  96. 9 9
      apps/app/src/components/Admin/App/MailSetting.tsx
  97. 2 2
      apps/app/src/components/Admin/App/MaintenanceMode.tsx
  98. 2 2
      apps/app/src/components/Admin/App/MaskedInput.tsx
  99. 11 10
      apps/app/src/components/Admin/App/QuestionnaireSettings.tsx
  100. 4 4
      apps/app/src/components/Admin/App/SesSetting.tsx

+ 4 - 2
.devcontainer/Dockerfile

@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
 #-------------------------------------------------------------------------------------------------------------
 
-FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-18
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-20
 
 # The node image includes a non-root user with sudo access. Use the
 # "remoteUser" property in devcontainer.json to use it. On Linux, update
@@ -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:

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

@@ -0,0 +1,81 @@
+name: Release Docker Images for RC (for dev/7.0.x)
+
+on:
+  push:
+    branches:
+      - dev/7.0.x
+
+
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
+
+  determine-tags:
+    runs-on: ubuntu-latest
+
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
+
+    - name: Docker meta for docker.io
+      uses: docker/metadata-action@v4
+      id: meta
+      with:
+        images: docker.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
+
+    - name: Docker meta for ghcr.io
+      uses: docker/metadata-action@v4
+      id: meta-ghcr
+      with:
+        images: ghcr.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
+
+
+  build-image-rc:
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image-rc:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+  publish-image-rc-ghcr:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
+      registry: ghcr.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+

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

@@ -48,19 +48,19 @@ concurrency:
 
 jobs:
 
-  test-prod-node16:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+  test-prod-node18:
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     with:
-      node-version: 16.x
+      node-version: 18.x
       skip-cypress: true
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
-  test-prod-node18:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+  test-prod-node20:
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@dev/7.0.x
     with:
-      node-version: 18.x
+      node-version: 20.x
       skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
@@ -68,15 +68,15 @@ jobs:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
-  run-reg-suit-node18:
-    needs: [test-prod-node18]
+  run-reg-suit-node20:
+    needs: [test-prod-node20]
 
-    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()
 
     with:
-      node-version: 18.x
+      node-version: 20.x
       skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
     secrets:

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

@@ -27,7 +27,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     steps:
       - uses: actions/checkout@v3
@@ -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:
@@ -92,7 +92,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mongodb:
@@ -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:
@@ -174,7 +174,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mongodb:
@@ -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') }}

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

@@ -29,7 +29,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     steps:
     - uses: actions/checkout@v3
@@ -94,7 +94,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mysql:
@@ -179,7 +179,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mysql:

+ 1 - 1
.github/workflows/list-unhealthy-branches.yml

@@ -16,7 +16,7 @@ jobs:
 
     - uses: actions/setup-node@v3
       with:
-        node-version: '16'
+        node-version: '18'
 
     - name: List branches
       id: list-branches

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

@@ -102,7 +102,7 @@ jobs:
 
     - uses: actions/setup-node@v3
       with:
-        node-version: '16'
+        node-version: '18'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
 

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

@@ -24,7 +24,7 @@ jobs:
 
     - uses: actions/setup-node@v3
       with:
-        node-version: '18'
+        node-version: '20'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
 
@@ -189,7 +189,7 @@ jobs:
 
     - uses: actions/setup-node@v3
       with:
-        node-version: '18'
+        node-version: '20'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
 

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

+ 4 - 4
.mergify.yml

@@ -3,11 +3,11 @@ pull_request_rules:
     conditions:
       - author = dependabot[bot]
       - '#approved-reviews-by >= 1'
-      - check-success = "lint (18.x)"
-      - check-success = "test (18.x)"
-      - check-success = "launch-dev (18.x)"
-      - check-success = "test-prod-node16 / launch-prod"
+      - check-success = "lint (20.x)"
+      - check-success = "test (20.x)"
+      - check-success = "launch-dev (20.x)"
       - check-success = "test-prod-node18 / launch-prod"
+      - check-success = "test-prod-node20 / launch-prod"
     actions:
       merge:
         method: merge

+ 2 - 3
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
@@ -80,7 +79,7 @@ See [GROWI Docs: Environment Variables](https://docs.growi.org/en/admin-guide/ad
 
 ## Dependencies
 
-- Node.js v16.x or v18.x
+- Node.js v18.x or v20.x
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)

+ 2 - 3
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 と連携することが可能です。
@@ -79,7 +78,7 @@ Crowi からの移行は **[こちら](https://docs.growi.org/en/admin-guide/mig
 
 ## 依存関係
 
-- Node.js v16.x or v18.x
+- Node.js v18.x or v20.x
 - npm 6.x
 - yarn
 - [Turborepo](https://turbo.build/repo)

+ 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

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

@@ -727,7 +727,7 @@ class CodeMirrorEditor extends AbstractEditor {
   renderCheatsheetModalButton() {
     return (
       <button type="button" className="btn-link gfm-cheatsheet-modal-link small" onClick={() => { this.markdownHelpButtonClickedHandler() }}>
-        <i className="icon-question" /> Markdown
+        <span className="material-symbols-outlined">help</span> Markdown
       </button>
     );
   }
@@ -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


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

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

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

@@ -10,10 +10,8 @@ import { useUserUISettings } from '~/client/services/user-ui-settings';
 import { usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser } from '~/stores/ui';
 import { Themes, useNextThemes } from '~/stores/use-next-themes';
 
-import MoonIcon from '../Icons/MoonIcon';
 import SidebarDockIcon from '../Icons/SidebarDockIcon';
 import SidebarDrawerIcon from '../Icons/SidebarDrawerIcon';
-import SunIcon from '../Icons/SunIcon';
 
 type AppearanceModeDropdownProps = {
   isAuthenticated: boolean,
@@ -75,20 +73,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 +99,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 +127,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 />
+                  <span className="material-symbols-outlined">light_mode</span>
                   </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 />
+                  <span className="material-symbols-outlined">dark_mode</span>
                   </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 +168,7 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
       </div>
 
-    </>
+    </div>
   );
 
 };

+ 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/_reboot-toastr-colors.scss → apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss


+ 51 - 68
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,19 +395,13 @@ ul.pagination {
 /*
  * cards
  */
-.card.well {
+.card.custom-card {
   color: var(--color-global);
   background-color: var(--bgcolor-card);
   border-color: var(--light);
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
 }
 
-.admin-bot-card {
-  .grw-botcard-title-active {
-    color: $gray-200;
-  }
-}
-
 /*
  * Form Slider
  */
@@ -481,7 +476,7 @@ ul.pagination {
           fill: var(--color-global);
         }
 
-        @include hover() {
+        &:hover {
           svg {
             fill: var(--color-global);
           }
@@ -661,18 +656,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 - 159
apps/app/bin/cdn/cdn-resources-downloader.ts

@@ -1,159 +0,0 @@
-import path from 'path';
-import { URL } from 'url';
-import urljoin from 'url-join';
-import { Transform } from 'stream';
-import replaceStream from 'replacestream';
-
-import { cdnLocalScriptRoot, cdnLocalStyleRoot, cdnLocalStyleWebRoot } from '^/config/cdn';
-import * as cdnManifests from '^/resource/cdn-manifests';
-
-import { CdnResource, CdnManifest } from '~/interfaces/cdn';
-import loggerFactory from '~/utils/logger';
-import { downloadTo } from '~/utils/download';
-
-const logger = loggerFactory('growi:service:CdnResourcesDownloader');
-
-export default class CdnResourcesDownloader {
-
-  async downloadAndWriteAll(): Promise<any> {
-    const cdnScriptResources: CdnResource[] = cdnManifests.js.map((manifest: CdnManifest) => {
-      return { manifest, outDir: cdnLocalScriptRoot };
-    });
-
-    const cdnStyleResources: CdnResource[] = cdnManifests.style.map((manifest) => {
-      return { manifest, outDir: cdnLocalStyleRoot };
-    });
-
-    const dlStylesOptions = {
-      replaceUrl: {
-        webroot: cdnLocalStyleWebRoot,
-      },
-    };
-
-    return Promise.all([
-      this.downloadScripts(cdnScriptResources),
-      this.downloadStyles(cdnStyleResources, dlStylesOptions),
-    ]);
-  }
-
-  /**
-   * Download script files from CDN
-   * @param cdnResources JavaScript resource data
-   * @param options
-   */
-  private async downloadScripts(cdnResources: CdnResource[], options?: any): Promise<any> {
-    logger.debug('Downloading scripts', cdnResources);
-
-    const opts = Object.assign({}, options);
-    const ext = opts.ext || 'js';
-
-    const promises = cdnResources.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing CdnResource '${manifest.name}'`);
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        `${manifest.name}.${ext}`,
-      );
-    });
-
-    return Promise.all(promises);
-  }
-
-  /**
-   * Download style sheet file from CDN
-   *  Assets in CSS is also downloaded
-   * @param cdnResources CSS resource data
-   * @param options
-   */
-  private async downloadStyles(cdnResources: CdnResource[], options?: any): Promise<any> {
-    logger.debug('Downloading styles', cdnResources);
-
-    const opts = Object.assign({}, options);
-    const ext = opts.ext || 'css';
-
-    // styles
-    const assetsResourcesStore: CdnResource[] = [];
-    const promisesForStyle = cdnResources.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing CdnResource '${manifest.name}'`);
-
-      let urlReplacer: Transform|null = null;
-
-      // generate replaceStream instance
-      if (opts.replaceUrl != null) {
-        urlReplacer = this.generateReplaceUrlInCssStream(cdnResource, assetsResourcesStore, opts.replaceUrl.webroot);
-      }
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        `${manifest.name}.${ext}`,
-        urlReplacer,
-      );
-    });
-
-    // wait until all styles are downloaded
-    await Promise.all(promisesForStyle);
-
-    logger.debug('Downloading assets', assetsResourcesStore);
-
-    // assets in css
-    const promisesForAssets = assetsResourcesStore.map((cdnResource) => {
-      const { manifest } = cdnResource;
-
-      logger.info(`Processing assts in css '${manifest.name}'`);
-
-      return downloadTo(
-        manifest.url,
-        cdnResource.outDir,
-        manifest.name,
-      );
-    });
-
-    return Promise.all(promisesForAssets);
-  }
-
-  /**
-   * Generate replaceStream instance to replace 'url(..)'
-   *
-   * e.g.
-   *  Before  : url(../images/logo.svg)
-   *  After   : url(/path/to/webroot/${cdnResources.name}/logo.svg)
-   *
-   * @param cdnResource CSS resource data
-   * @param assetsResourcesStore An array to store CdnResource that is detected by 'url()' in CSS
-   * @param webroot
-   */
-  private generateReplaceUrlInCssStream(cdnResource: CdnResource, assetsResourcesStore: CdnResource[], webroot: string): Transform {
-    return replaceStream(
-      /url\((?!['"]?data:)["']?(.+?)["']?\)/g, // https://regex101.com/r/Sds38A/3
-      (match, url) => {
-        // generate URL Object
-        const parsedUrl = url.startsWith('http')
-          ? new URL(url) // when url is fqcn
-          : new URL(url, cdnResource.manifest.url); // when url is relative
-        const basename = path.basename(parsedUrl.pathname);
-
-        logger.debug(`${cdnResource.manifest.name} has ${parsedUrl.toString()}`);
-
-        // add assets metadata to download later
-        const replacedCdnResource = {
-          manifest: {
-            name: basename,
-            url: parsedUrl.toString(),
-          },
-          outDir: path.join(cdnResource.outDir, cdnResource.manifest.name),
-        };
-        assetsResourcesStore.push(replacedCdnResource);
-
-        const replaceUrl = urljoin(webroot, cdnResource.manifest.name, basename);
-        return `url(${replaceUrl})`;
-      },
-    );
-  }
-
-}

+ 0 - 33
apps/app/bin/download-cdn-resources.ts

@@ -1,33 +0,0 @@
-/**
- * the tool for download CDN resources and save as file
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-import { envUtils } from '@growi/core/dist/utils';
-
-import loggerFactory from '../src/utils/logger';
-
-import CdnResourcesDownloader from './cdn/cdn-resources-downloader';
-
-const logger = loggerFactory('growi:bin:download-cdn-resources');
-
-// check env var
-const noCdn: boolean = envUtils.toBoolean(process.env.NO_CDN);
-if (!noCdn) {
-  logger.info('Using CDN. No resources are downloaded.');
-  // exit
-  process.exit(0);
-}
-
-logger.info('This is NO_CDN mode. Start to download resources.');
-
-
-const downloader = new CdnResourcesDownloader();
-
-downloader.downloadAndWriteAll()
-  .then(() => {
-    logger.info('Download is completed successfully');
-  })
-  .catch((err) => {
-    logger.error(err);
-  });

+ 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

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

@@ -25,7 +25,6 @@ module.exports = {
   // 'growi:lib:importer': 'debug',
   // 'growi:routes:page': 'debug',
   'growi-plugin:*': 'debug',
-  // 'growi:InterceptorManager': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
   'growi:service:g2g-transfer': 'debug',
   'growi:service:questionnaire': 'debug',

+ 11 - 4
apps/app/config/next-i18next.config.js

@@ -1,12 +1,14 @@
+const isDev = process.env.NODE_ENV === 'development';
+
 const path = require('path');
 
 const { AllLang, Lang } = require('@growi/core');
 const { isServer } = require('@growi/core/dist/utils');
-const I18nextChainedBackend = require('i18next-chained-backend').default;
-const I18NextHttpBackend = require('i18next-http-backend');
+const I18nextChainedBackend = isDev ? require('i18next-chained-backend').default : undefined;
+const I18NextHttpBackend = require('i18next-http-backend').default;
 const I18NextLocalStorageBackend = require('i18next-localstorage-backend').default;
 
-const isDev = process.env.NODE_ENV === 'development';
+const HMRPlugin = isDev ? require('i18next-hmr/plugin').HMRPlugin : undefined;
 
 module.exports = {
   defaultLang: Lang.en_US,
@@ -17,7 +19,12 @@ module.exports = {
   defaultNS: 'translation',
   localePath: path.resolve('./public/static/locales'),
   serializeConfig: false,
-  use: isServer() ? [] : [I18nextChainedBackend],
+  // eslint-disable-next-line no-nested-ternary
+  use: isDev
+    ? isServer()
+      ? [new HMRPlugin({ webpack: { server: true } })]
+      : [I18nextChainedBackend, new HMRPlugin({ webpack: { client: true } })]
+    : [],
   backend: {
     backends: isServer() ? [] : [I18NextLocalStorageBackend, I18NextHttpBackend],
     backendOptions: [

+ 4 - 4
apps/app/docker/Dockerfile

@@ -4,7 +4,7 @@
 ##
 ## base
 ##
-FROM node:18-slim AS base
+FROM node:20-slim AS base
 
 ENV optDir /opt
 
@@ -18,7 +18,7 @@ RUN turbo prune --scope=@growi/app --docker
 ##
 ## deps-resolver
 ##
-FROM node:18-slim AS deps-resolver
+FROM node:20-slim AS deps-resolver
 
 ENV optDir /opt
 
@@ -62,7 +62,7 @@ RUN tar -cf node_modules.tar \
 ##
 ## builder
 ##
-FROM node:18-slim AS builder
+FROM node:20-slim AS builder
 
 ENV optDir /opt
 
@@ -107,7 +107,7 @@ RUN tar -cf packages.tar \
 ##
 ## release
 ##
-FROM node:18-slim
+FROM node:20-slim
 LABEL maintainer Yuki Takei <yuki@weseek.co.jp>
 
 ENV NODE_ENV production

+ 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.2`, `6.3`, `6` (Dockerfile)](https://github.com/weseek/growi/blob/v6.3.2/apps/app/docker/Dockerfile)
 * [`6.2.4`, `6.2` (Dockerfile)](https://github.com/weseek/growi/blob/v6.2.4/apps/app/docker/Dockerfile)
+* [`6.1.15`, `6.1` (Dockerfile)](https://github.com/weseek/growi/blob/v6.1.15/apps/app/docker/Dockerfile)
 
 
 What is GROWI?

+ 1 - 1
apps/app/next.config.js

@@ -120,7 +120,7 @@ module.exports = async(phase, { defaultConfig }) => {
 
       // setup i18next-hmr
       if (!options.isServer && options.dev) {
-        const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+        const { I18NextHMRPlugin } = require('i18next-hmr/webpack');
         config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
       }
 

+ 34 - 32
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.3.3-RC.0",
+  "version": "7.0.0-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -55,14 +55,15 @@
     "@aws-skd/*": "fix version above 3.186.0 that is required by mongodb@4.16.0",
     "@keycloak/keycloak-admin-client": "19.0.0 or above exports only ESM.",
     "escape-string-regexp": "5.0.0 or above exports only ESM",
-    "string-width": "5.0.0 or above exports only ESM.",
-    "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker"
+    "next-themes": "0.3.0 causes a type error: https://github.com/pacocoursey/next-themes/issues/122",
+    "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker",
+    "string-width": "5.0.0 or above exports only ESM."
   },
   "dependencies": {
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@aws-sdk/client-s3": "3.454.0",
     "@aws-sdk/s3-request-presigner": "3.454.0",
-    "@azure/identity": "^3.3.2",
+    "@azure/identity": "^4.0.1",
     "@azure/storage-blob": "^12.16.0",
     "@browser-bunyan/console-formatted-stream": "^1.8.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
@@ -70,7 +71,7 @@
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@growi/core": "link:../../packages/core",
-    "@growi/hackmd": "link:../../packages/hackmd",
+    "@growi/custom-icons": "link:../../packages/custom-icons",
     "@growi/pluginkit": "link:../../packages/pluginkit",
     "@growi/preset-templates": "link:../../packages/preset-templates",
     "@growi/preset-themes": "link:../../packages/preset-themes",
@@ -101,7 +102,7 @@
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
     "csurf": "^1.11.0",
-    "csv-to-markdown-table": "^1.1.0",
+    "csv-to-markdown-table": "^1.4.1",
     "date-fns": "^2.23.0",
     "dayjs": "^1.11.7",
     "detect-indent": "^7.0.0",
@@ -123,15 +124,15 @@
     "hast-util-select": "^5.0.5",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
-    "i18next": "^22.4.10",
-    "i18next-chained-backend": "^4.0.0",
-    "i18next-http-backend": "^2.0.0",
-    "i18next-localstorage-backend": "^4.0.0",
+    "i18next": "^23.10.1",
+    "i18next-chained-backend": "^4.6.2",
+    "i18next-http-backend": "^2.5.0",
+    "i18next-localstorage-backend": "^4.2.0",
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "ldapjs": "^3.0.2",
     "lucene-query-parser": "^1.2.0",
-    "markdown-table": "^1.1.1",
+    "markdown-table": "^3.0.3",
     "md5": "^2.2.1",
     "mermaid": "^10.1.0",
     "method-override": "^3.0.0",
@@ -144,8 +145,8 @@
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "mustache": "^4.2.0",
-    "next": "^13.3.0",
-    "next-i18next": "^13.2.1",
+    "next": "^14.1.3",
+    "next-i18next": "^15.2.0",
     "next-superjson": "^0.0.4",
     "next-themes": "^0.2.1",
     "nocache": "^3.0.1",
@@ -163,7 +164,7 @@
     "qs": "^6.11.1",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
-    "react-bootstrap-typeahead": "^5.2.2",
+    "react-bootstrap-typeahead": "^6.3.2",
     "react-card-flip": "^1.0.10",
     "react-datepicker": "^4.7.0",
     "react-disable": "^0.1.1",
@@ -171,15 +172,16 @@
     "react-dnd-html5-backend": "^14.1.0",
     "react-dom": "^18.2.0",
     "react-error-boundary": "^3.1.4",
-    "react-i18next": "^12.2.0",
+    "react-i18next": "^14.1.0",
     "react-image-crop": "^8.3.0",
     "react-markdown": "^8.0.7",
     "react-multiline-clamp": "^2.0.0",
     "react-scroll": "^1.8.7",
+    "react-stickynode": "^4.1.0",
     "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",
@@ -195,12 +197,12 @@
     "remark-toc": "^8.0.1",
     "remark-wiki-link": "^1.0.4",
     "sanitize-filename": "^1.6.3",
-    "socket.io": "^4.2.0",
+    "socket.io": "^4.7.2",
     "stream-to-promise": "^3.0.0",
     "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",
@@ -210,18 +212,21 @@
     "usehooks-ts": "^2.6.0",
     "validator": "^13.7.0",
     "ws": "^8.3.0",
-    "xss": "^1.0.14"
+    "xss": "^1.0.14",
+    "y-mongodb-provider": "^0.1.7",
+    "y-socket.io": "^1.1.0",
+    "yjs": "^13.6.12"
   },
   "// comments for defDependencies": {
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
     "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",
-    "@icon/themify-icons": "1.0.1-alpha.3",
-    "@next/bundle-analyzer": "^13.2.3",
+    "@next/bundle-analyzer": "^14.1.3",
     "@swc-node/jest": "^1.6.2",
     "@swc/jest": "^0.2.24",
     "@testing-library/react": "^14.1.2",
@@ -229,47 +234,44 @@
     "@types/express": "^4.17.11",
     "@types/jest": "^29.5.2",
     "@types/react-scroll": "^1.8.4",
+    "@types/throttle-debounce": "^5.0.1",
     "@types/unzip-stream": "^0.3.4",
+    "@types/url-join": "^4.0.2",
     "@vitejs/plugin-react": "^4.2.1",
     "@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",
+    "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
-    "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-jest": "^26.5.3",
     "eslint-plugin-regex": "^1.8.0",
-    "font-awesome": "^4.7.0",
     "fslightbox-react": "^1.7.6",
     "handsontable": "=6.2.2",
     "happy-dom": "^13.2.0",
-    "i18next-hmr": "^1.11.0",
+    "i18next-hmr": "^3.0.4",
     "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-dropzone": "^14.2.3",
     "react-hotkeys": "^2.0.0",
+    "react-input-autosize": "^3.0.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "sass": "^1.53.0",
-    "simple-line-icons": "^2.5.5",
     "simple-load-script": "^1.0.2",
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.2.0",

+ 3 - 0
apps/app/public/images/icons/slack/slack-logo-background.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
+  <circle fill="white" cx="300" cy="300" r="300" />
+</svg>

+ 3 - 0
apps/app/public/images/icons/slack/slack-logo-dark-background.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 600">
+  <circle fill="#370f38" cx="300" cy="300" r="300" />
+</svg>

BIN
apps/app/public/images/icons/sublime.png


BIN
apps/app/public/images/icons/vscode.png


+ 15 - 16
apps/app/public/static/locales/en_US/admin.json

@@ -38,10 +38,12 @@
     "page_delete": "Page Delete",
     "page_delete_completely": "Page Delete Completely",
     "other_options": "Other options",
-    "deletion_explain": "Restricts users who can trash the selected single page.",
-    "complete_deletion_explain": "Restricts users who can completely delete  selected single page.",
-    "recursive_deletion_explain": "Restricts users who can trash pages including descendants.",
-    "recursive_complete_deletion_explain": "Restricts users who can completely delete pages including descendants.",
+    "deletion_explanation": "Restricts users who can trash the selected single page.",
+    "complete_deletion_explanation": "Restricts users who can completely delete  selected single page.",
+    "recursive_deletion_explanation": "Restricts users who can trash pages including descendants.",
+    "recursive_complete_deletion_explanation": "Restricts users who can completely delete pages including descendants.",
+    "is_all_group_membership_required_for_page_complete_deletion": "Users other than admin and page author are required to belong to all groups that are granted page access",
+    "is_all_group_membership_required_for_page_complete_deletion_explanation": "Effective when page access settings is set to \"Only specific groups\".",
     "inherit": "Inherit(Use the same setting as for a single page)",
     "admin_only": "Admin only",
     "admin_and_author": "Admin and author",
@@ -496,8 +498,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 +567,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 +746,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":"<span className='material-symbols-outlined me-2'>settings</span><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.",
@@ -836,9 +834,10 @@
       "dropdown_desc": "Choose an action for private pages",
       "select_group": "Select a group",
       "no_groups": "No groups to select",
-      "publish_pages": "Publish all",
+      "publish_pages": "Publish pages that are publishable",
       "delete_pages": "Delete all",
-      "transfer_pages": "Transfer to another group"
+      "transfer_pages": "Transfer to another group",
+      "option_explanation": "A \"publishable\" page is a page visible only to the group you want to delete. Pages that can be viewed by other groups will not be published."
     },
     "update_parent_confirm_modal": {
       "header": "The parent of the group will be changed",
@@ -861,12 +860,12 @@
     "return": "Return",
     "clear": "Clear",
     "activity_expiration_date": "Audit Log expiration date",
-    "activity_expiration_date_explain": "Created Audit Log are automatically deleted after the number of seconds set in the environment variable from the creation time",
+    "activity_expiration_date_explanation": "Created Audit Log are automatically deleted after the number of seconds set in the environment variable from the creation time",
     "fixed_by_env_var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",
     "available_action_list": "Search / View All Available Actions",
-    "available_action_list_explain": "List of actions that can be searched/viewed in the current settings",
+    "available_action_list_explanation": "List of actions that can be searched/viewed in the current settings",
     "action_list": "Action List",
-    "disable_mode_explain": "Audit log is currently disabled. To enable it, set the environment variable <code>AUDIT_LOG_ENABLED</code> to true.",
+    "disable_mode_explanation": "Audit log is currently disabled. To enable it, set the environment variable <code>AUDIT_LOG_ENABLED</code> to true.",
     "docs_url": {
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }

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

@@ -42,6 +42,12 @@
     }
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "Search in all",
+    "only_children_of_this_tree": "Only children of this tree",
+    "exact_mutch": "Exact match"
+  },
+
   "share_links": {
     "Share Link": "Share Link",
     "Page Path": "Page Path",
@@ -68,6 +74,19 @@
     "feedback": "Feedback"
   },
 
+  "create_page_dropdown": {
+    "new_page": "Create New Page",
+    "todays": {
+      "desc": "Create today's memo",
+      "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",

+ 116 - 94
apps/app/public/static/locales/en_US/translation.json

@@ -10,12 +10,12 @@
   "Duplicate": "Duplicate",
   "PathRecovery": "Path recovery",
   "Copy": "Copy",
-  "preview":"Preview",
-  "desktop":"Desktop",
-  "phone":"Smartphone",
-  "tablet":"Tablet",
+  "preview": "Preview",
+  "desktop": "Desktop",
+  "phone": "Smartphone",
+  "tablet": "Tablet",
   "Click to copy": "Click to copy",
-  "Rename" : "Rename",
+  "Rename": "Rename",
   "Move/Rename": "Move/Rename",
   "Redirected": "Redirected",
   "Unlinked": "Unlinked",
@@ -44,9 +44,11 @@
   "Error": "Error",
   "Warning": "Warning",
   "Sign in": "Sign in",
+  "Sign in with External auth": "Sign in with {{signin}}",
   "Sign up is here": "Sign up",
   "Sign in is here": "Sign in",
   "Sign up": "Sign up",
+  "or": "or",
   "Sign up with Google Account": "Sign up with Google Account",
   "Sign in with Google Account": "Sign in with Google Account",
   "Sign up with this Google Account": "Sign up with this Google Account",
@@ -106,15 +108,14 @@
   "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)",
+  "Input parent page path": "Input parent page path",
   "New Page": "New page",
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
+  "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <span className='growi-custom-icons'>external_link</span> ",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
   "Basic Settings": "Basic Settings",
@@ -146,9 +147,10 @@
   "wide_view": "Wide View",
   "Recent Changes": "Recent Changes",
   "Page Tree": "Page Tree",
-  "original_path":"Original path",
-  "new_path":"New path",
-  "duplicated_path":"Duplicated path",
+  "In-App Notification": "Notifications",
+  "original_path": "Original path",
+  "new_path": "New path",
+  "duplicated_path": "Duplicated path",
   "Link sharing is disabled": "Link sharing is disabled",
   "successfully_saved_the_page": "Successfully saved the page",
   "you_can_not_create_page_with_this_name": "You can not create page with this name",
@@ -214,10 +216,10 @@
   "Password": "Password",
   "Password Settings": "Password settings",
   "personal_settings": {
-  "disassociate_external_account": "Disassociate External Account",
-  "disassociate_external_account_desc": "Are you sure to disassociate the <strong>{{providerType}}</strong> account <strong>{{accountId}}</strong>?",
-  "set_new_password": "Set new Password",
-  "update_password": "Update password",
+    "disassociate_external_account": "Disassociate External Account",
+    "disassociate_external_account_desc": "Are you sure to disassociate the <strong>{{providerType}}</strong> account <strong>{{accountId}}</strong>?",
+    "set_new_password": "Set new Password",
+    "update_password": "Update password",
     "current_password": "Current password",
     "new_password": "New password",
     "new_password_confirm": "Re-enter new password",
@@ -227,7 +229,7 @@
     "Shere this page link to public": "Shere this page link to public",
     "share_link_list": "Share link list",
     "share_link_management": "Share Link Management",
-    "delete_all_share_links":"Delete all share links",
+    "delete_all_share_links": "Delete all share links",
     "expire": "Expiration",
     "Days": "Days",
     "Custom": "Custom",
@@ -235,8 +237,8 @@
     "enter_desc": "Enter description",
     "Unlimited": "unlimited",
     "Issue": "Issue",
-    "share_settings" :"Share settings",
-    "Invalid_Number_of_Date" : "You entered invalid value",
+    "share_settings": "Share settings",
+    "Invalid_Number_of_Date": "You entered invalid value",
     "link_sharing_is_disabled": "Link sharing is disabled"
   },
   "API Settings": "API settings",
@@ -251,6 +253,21 @@
       "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."
+    }
+  },
+  "color_mode_settings": {
+    "light": "Light",
+    "dark": "Dark",
+    "system": "System",
+    "settings": "Color mode settings",
+    "description": "Select whether to display in light mode, dark mode, or a system-specific display.<br>Only supported themes can be switched."
+  },
   "editor_settings": {
     "editor_settings": "Editor Settings"
   },
@@ -293,10 +310,15 @@
       "stale": "More than {{count}} year has passed since last update.",
       "stale_plural": "More than {{count}} years has passed since last update.",
       "expiration": "This share link will expire at <strong>{{expiredAt}}</strong>.",
-      "no_deadline":"This page has no expiration date"
+      "no_deadline": "This page has no expiration date"
     }
   },
   "page_edit": {
+    "input_channels": "Slack channel name...",
+    "theme": "Theme",
+    "keymap": "Keymap",
+    "indent": "Indent",
+    "editor_config": "Editor Config",
     "Show active line": "Show active line",
     "auto_format_table": "Auto format table",
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
@@ -314,7 +336,8 @@
     "already_exists": "Page with the path already exists.",
     "outdated": "Page is updated someone and now outdated.",
     "user_not_admin": "Only admin user can delete",
-    "single_deletion_empty_pages": "Empty pages cannot be single deleted"
+    "single_deletion_empty_pages": "Empty pages cannot be single deleted",
+    "complete_deletion_not_allowed_for_user": "You are not allowed to delete this page completely"
   },
   "page_history": {
     "revision_list": "Revision list",
@@ -322,8 +345,8 @@
     "comparing_source": "Source",
     "comparing_target": "Target",
     "comparing_revisions": "Comparing the difference",
-    "compare_latest":"Compare latest revision",
-    "compare_previous":"Compare previous revision"
+    "compare_latest": "Compare latest revision",
+    "compare_previous": "Compare previous revision"
   },
   "modal_rename": {
     "label": {
@@ -361,7 +384,7 @@
   "deleted_pages_completely": "{{path}} has been deleted completely",
   "renamed_pages": "{{path}} has been renamed",
   "empty_trash": "The trash has been emptied",
-  "modal_empty":{
+  "modal_empty": {
     "empty_the_trash": "Empty The Trash",
     "empty_the_trash_button": "Empty The Trash",
     "not_deletable_notice": "Some pages cannot be removed due to lack of permission.",
@@ -375,10 +398,12 @@
       "Current page name": "Current page name",
       "Recursively": "Recursively",
       "Duplicate without exist path": "Duplicate without exist path",
-      "Same page already exists": "Same page already exists"
+      "Same page already exists": "Same page already exists",
+      "Only duplicate user related pages": "Only duplicate pages you can access"
     },
     "help": {
-      "recursive": "Duplicate children of under this path recursively"
+      "recursive": "Duplicate children of under this path recursively",
+      "only_inherit_user_related_groups": "If the page privilege is set to \"Only inside the group\", groups you do not belong to will lose access to the duplicated page"
     }
   },
   "duplicated_pages": "{{fromPath}} has been duplicated",
@@ -416,13 +441,13 @@
     }
   },
   "modal_resolve_conflict": {
+    "conflicts_with_new_body_on_server_side": "Conflict with new body on server side. Please select or edit the page body to resolve the conflict.",
     "file_conflicting_with_newer_remote": "This file is conflicting with newer remote file",
     "resolve_conflict_message": "Please select page body",
     "resolve_conflict": "Resolve Conflict",
-    "resolve_and_save" : "Resolve and save",
-    "select_revision" : "Select {{revision}}",
+    "resolve_and_save": "Resolve and save",
+    "select_revision": "Select {{revision}}",
     "requested_revision": "mine",
-    "origin_revision": "origin",
     "latest_revision": "theirs",
     "selected_editable_revision": "Selected Page Body (Editable)"
   },
@@ -449,7 +474,7 @@
     "issue_share_link": "Succeeded to issue new share link",
     "remove_share_link": "Succeeded to remove {{count}} share links",
     "switch_disable_link_sharing_success": "Succeeded to update share link setting",
-    "failed_to_reset_password":"Failed to reset password",
+    "failed_to_reset_password": "Failed to reset password",
     "save_succeeded": "Saved successfully"
   },
   "template": {
@@ -466,7 +491,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 +527,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."
@@ -536,12 +540,13 @@
     "check_all": "Check all",
     "deletion_modal_header": "Delete page",
     "delete_completely": "Delete completely",
-    "include_certain_path" : "Include {{pathToInclude}} path ",
-    "delete_all_selected_page" : "Delete All",
-    "currently_not_implemented":"This is not currently implemented",
-    "search_again" : "Search again",
-    "number_of_list_to_display" : "Display",
-    "page_number_unit" : "pages",
+    "include_certain_path": "Include {{pathToInclude}} path ",
+    "delete_all_selected_page": "Delete All",
+    "currently_not_implemented": "This is not currently implemented",
+    "search_again": "Search again",
+    "number_of_list_to_display": "Display",
+    "page_number_unit": "pages",
+    "hit_number_unit": "hit",
     "sort_axis": {
       "relationScore": "Sort by relevance",
       "createdAt": "Creation date",
@@ -557,7 +562,7 @@
     "alert_desc1": "On this page, you can select pages with the checkbox and batch convert to the new v5 compatible format from the \"Bulk operation\" button at the top of the screen.",
     "nopages_title": "Congratulations. Ready to use GROWI v5!",
     "nopages_desc1": "Now all the pages you can manage seem to be in v5 compatible format.",
-    "detail_info": "See the detail information from <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>Upgrading GROWI to v5.0.x <i class='icon-share-alt'></i></a>.",
+    "detail_info": "See the detail information from <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>Upgrading GROWI to v5.0.x <span className='growi-custom-icons'>external_link</span></a>.",
     "modal": {
       "title": "Convert to new v5 compatible format",
       "converting_pages": "Converting pages",
@@ -588,7 +593,7 @@
     "sign_in_error": "Login error",
     "registration_successful": "Registration successful. Please wait for administrator approval.",
     "Setup": "Setup",
-    "enabled_ldap_has_configuration_problem":"LDAP is enabled but the configuration has something wrong.",
+    "enabled_ldap_has_configuration_problem": "LDAP is enabled but the configuration has something wrong.",
     "set_env_var_for_logs": "(Please set the environment variables <code>DEBUG=crowi:service:PassportService</code> to get the logs)"
   },
   "invited": {
@@ -615,21 +620,21 @@
     "aws_sttings_required": "AWS settings required to use this function. Please ask the administrator.",
     "application_already_installed": "Application already installed.",
     "email_address_could_not_be_used": "This email address could not be used. (Make sure the allowed email address)",
-    "user_id_is_not_available":"This User ID is not available.",
-    "username_should_not_be_null":"Username should not be null. Please check Authentication Mechanism Settings on admin page",
-    "email_address_is_already_registered":"This email address is already registered.",
-    "can_not_register_maximum_number_of_users":"Can not register more than the maximum number of users.",
-    "email_settings_is_not_setup":"E-mail settings is not set up. Please ask the administrator.",
+    "user_id_is_not_available": "This User ID is not available.",
+    "username_should_not_be_null": "Username should not be null. Please check Authentication Mechanism Settings on admin page",
+    "email_address_is_already_registered": "This email address is already registered.",
+    "can_not_register_maximum_number_of_users": "Can not register more than the maximum number of users.",
+    "email_settings_is_not_setup": "E-mail settings is not set up. Please ask the administrator.",
     "email_authentication_is_not_enabled": "Email authentication is not enabled. Please ask the administrator.",
-    "failed_to_register":"Failed to register.",
-    "successfully_created":"The user {{username}} is successfully created.",
-    "can_not_activate_maximum_number_of_users":"Can not activate more than the maximum number of users.",
-    "failed_to_activate":"Failed to activate.",
-    "unable_to_use_this_user":"Unable to use this user.",
-    "complete_to_install1":"Complete to Install GROWI ! Please login as admin account.",
-    "complete_to_install2":"Complete to Install GROWI ! Please check each settings on this page first.",
-    "failed_to_create_admin_user":"Failed to create admin user. {{errMessage}}",
-    "successfully_send_email_auth":"We sent an email to {{email}}. Please click the URL in the email and complete the registration.",
+    "failed_to_register": "Failed to register.",
+    "successfully_created": "The user {{username}} is successfully created.",
+    "can_not_activate_maximum_number_of_users": "Can not activate more than the maximum number of users.",
+    "failed_to_activate": "Failed to activate.",
+    "unable_to_use_this_user": "Unable to use this user.",
+    "complete_to_install1": "Complete to Install GROWI ! Please login as admin account.",
+    "complete_to_install2": "Complete to Install GROWI ! Please check each settings on this page first.",
+    "failed_to_create_admin_user": "Failed to create admin user. {{errMessage}}",
+    "successfully_send_email_auth": "We sent an email to {{email}}. Please click the URL in the email and complete the registration.",
     "incorrect_token_or_expired_url": "The token is incorrect or the URL has expired.",
     "user_already_logged_in": "You cannot create a new account when you are logged in.",
     "registration_closed": "You are not authorized to create a new account.",
@@ -644,22 +649,22 @@
     "Username or E-mail has invalid characters": "Username or E-mail has invalid characters.",
     "Password minimum character should be more than 6 characters": "Password minimum character should be more than 6 characters.",
     "user_not_found": "User not found.",
-    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>DuplicatedUsernameException occured</strong></p><p class='mb-0'> Your {{ failedProviderForDuplicatedUsernameException }} authentication was succeeded, but a new user could not be created. See the issue <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>DuplicatedUsernameException occured</strong></p><p class='mb-0'> Your {{ failedProviderForDuplicatedUsernameException }} authentication was succeeded, but a new user could not be created. See the issue <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
   },
-  "grid_edit":{
-    "create_bootstrap_4_grid":"Create Bootstrap 4 Grid",
+  "grid_edit": {
+    "create_bootstrap_4_grid": "Create Bootstrap 4 Grid",
     "grid_settings": "Grid Settings",
-    "grid_pattern":"Grid Pattern",
-    "division":"Divisions",
-    "smart_no":"Smartphone / No Break",
-    "break_point":"Break point by display size"
+    "grid_pattern": "Grid Pattern",
+    "division": "Divisions",
+    "smart_no": "Smartphone / No Break",
+    "break_point": "Break point by display size"
   },
-  "validation":{
+  "validation": {
     "aws_region": "For the region, enter the AWS region name. ex):us-east-1",
-    "aws_custom_endpoint":"For the custom endpoint, specify the URL that starts with http(s)://. Also, the trailing slash is not required.",
-    "failed_to_send_a_test_email":"Failed to send a test email using SMTP. Please check your settings."
+    "aws_custom_endpoint": "For the custom endpoint, specify the URL that starts with http(s)://. Also, the trailing slash is not required.",
+    "failed_to_send_a_test_email": "Failed to send a test email using SMTP. Please check your settings."
   },
-  "forgot_password":{
+  "forgot_password": {
     "forgot_password": "Forgot Password?",
     "send": "Send",
     "return_to_login": "Return to login",
@@ -676,7 +681,7 @@
     "password_and_confirm_password_does_not_match": "Password and confirm password does not match",
     "please_enable_mailer_alert": "The password reset feature is disabled because email setup has not been completed. Please ask administrator to complete the email setup."
   },
-  "emoji" :{
+  "emoji": {
     "title": "Pick an Emoji",
     "search": "Search",
     "clear": "Clear",
@@ -706,7 +711,7 @@
       "6": "Dark Skin Tone"
     }
   },
-  "maintenance_mode":{
+  "maintenance_mode": {
     "maintenance_mode": "Maintenance Mode",
     "growi_is_under_maintenance": "GROWI is under maintenance. Please wait until it ends.",
     "admin_page": "Admin Page",
@@ -718,10 +723,10 @@
     "you_cannot_move_this_page_now": "You cannot move this page now",
     "something_went_wrong_with_moving_page": "Something went wrong with moving page"
   },
-  "duplicated_page_alert" : {
+  "duplicated_page_alert": {
     "same_page_name_exists": "Same page name exits as「{{pageName}}」",
-    "same_page_name_exists_at_path" : "Same page name as {{pageName}} exists at {{path}} ",
-    "select_page_to_see" : "Select a page to see"
+    "same_page_name_exists_at_path": "Same page name as {{pageName}} exists at {{path}} ",
+    "select_page_to_see": "Select a page to see"
   },
   "user_group": {
     "select_group": "Select group",
@@ -737,7 +742,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",
@@ -768,15 +773,15 @@
       }
     }
   },
-  "page_operation":{
+  "page_operation": {
     "paths_recovered": "Paths recovered successfully",
-    "path_recovery_failed":"Path recovery failed"
+    "path_recovery_failed": "Path recovery failed"
   },
   "footer": {
     "bookmarks": "Bookmarks",
     "recently_created": "Recently Created"
   },
-  "bookmark_folder":{
+  "bookmark_folder": {
     "bookmark_folder": "bookmark folder",
     "bookmark": "bookmark",
     "delete_modal": {
@@ -794,7 +799,7 @@
     "root": "root (default)"
   },
   "v5_page_migration": {
-    "page_tree_not_avaliable" : "Page tree feature is not available yet.",
+    "page_tree_not_avaliable": "Page tree feature is not available yet.",
     "go_to_settings": "Go to settings to enable the feature"
   },
   "questionnaire": {
@@ -826,5 +831,22 @@
   },
   "rich_attachment": {
     "attachment_not_be_found": "The attachment could not be found"
+  },
+  "page_select_modal": {
+    "select_page_location": "Select page location"
+  },
+  "wip_page": {
+    "save_as_wip": "Save as WIP (still being written)",
+    "success_save_as_wip": "Successfully saved as a WIP page",
+    "fail_save_as_wip": "Failed to save as a WIP page",
+    "alert": "This page is still being written",
+    "publish_page": "Publish page",
+    "success_publish_page": "Page has been published",
+    "fail_publish_page": "Failed to publish the Page"
+  },
+  "sidebar_header": {
+    "show_wip_page": "Show WIP",
+    "size_s": "Size: S",
+    "size_l": "Size: L"
   }
 }

+ 15 - 16
apps/app/public/static/locales/ja_JP/admin.json

@@ -47,10 +47,12 @@
     "page_delete": "ゴミ箱に入れる",
     "page_delete_completely": "完全に削除する",
     "other_options": "その他のオプション",
-    "deletion_explain": "ページをゴミ箱に入れることができるユーザーを制限します。",
-    "complete_deletion_explain": "ページを完全削除することができるユーザーを制限します。",
-    "recursive_deletion_explain": "子孫を含めたページをゴミ箱に入れることができるユーザーを制限します。",
-    "recursive_complete_deletion_explain": "子孫を含めたページを完全削除することができるユーザーを制限します。",
+    "deletion_explanation": "ページをゴミ箱に入れることができるユーザーを制限します。",
+    "complete_deletion_explanation": "ページを完全削除することができるユーザーを制限します。",
+    "recursive_deletion_explanation": "子孫を含めたページをゴミ箱に入れることができるユーザーを制限します。",
+    "recursive_complete_deletion_explanation": "子孫を含めたページを完全削除することができるユーザーを制限します。",
+    "is_all_group_membership_required_for_page_complete_deletion": "管理者とページ作者以外はページに対する権限を持つ全てのグループに所属している必要がある",
+    "is_all_group_membership_required_for_page_complete_deletion_explanation": "ページの権限設定が「特定のグループのみ」の場合有効になります。",
     "inherit": "単体のみと同じ",
     "admin_only": "管理者のみ可能",
     "admin_and_author": "管理者とページ作者が可能",
@@ -506,8 +508,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 +577,6 @@
           "initialize_meta_datas": {
             "label": "「いいね」「閲覧したユーザー」「コメント数」を初期化する",
             "desc": "users を同時に復元しない場合、このオプションは<span class=\"text-danger\">非推奨</span>です。"
-          },
-          "initialize_hackmd_related_datas": {
-            "label": "HackMD 関連データを初期化する",
-            "desc": "HackMD に重要な下書きデータがない限りはこのオプションをチェックすることを推奨します。"
           }
         },
         "revisions": {
@@ -758,7 +756,7 @@
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
       "description2": "初回のログイン時に使用する仮パスワードが生成されます。",
       "invite_thru_email": "招待メールを送信する",
-      "mail_setting_link": "<i class='icon-settings mr-2'></i><a href='/admin/app'>メールの設定</a>",
+      "mail_setting_link": "<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>メールの設定</a>",
       "valid_email": "メールアドレスを入力してください。",
       "temporary_password": "作成したユーザーは仮パスワードが設定されています。",
       "send_new_password": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
@@ -846,9 +844,10 @@
       "dropdown_desc": "削除するグループの限定公開ページの処理を選択してください",
       "select_group": "グループを選択してください",
       "no_groups": "グループがありません",
-      "publish_pages": "全て公開する",
+      "publish_pages": "公開可能なページを公開する",
       "delete_pages": "全て削除する",
-      "transfer_pages": "全て他のグループに移譲する"
+      "transfer_pages": "全て他のグループに移譲する",
+      "option_explanation": "「公開可能なページ」とは、削除するグループにのみ限定公開されているページを指します。他のグループも閲覧可能なページは公開対象となりません。"
     },
     "update_parent_confirm_modal": {
       "header": "グループの親が変更されます",
@@ -871,12 +870,12 @@
     "return": "戻る",
     "clear": "クリア",
     "activity_expiration_date": "監査ログの有効期限",
-    "activity_expiration_date_explain": "作成された監査ログは、作成時間から環境変数に設定した秒数後に自動的に削除されます",
+    "activity_expiration_date_explanation": "作成された監査ログは、作成時間から環境変数に設定した秒数後に自動的に削除されます",
     "fixed_by_env_var": "環境変数により固定されています <code>{{key}}={{value}}</code>.",
     "available_action_list": "検索 / 表示 可能なアクション一覧",
-    "available_action_list_explain": "現在の設定で検索 / 表示 可能なアクション一覧です",
+    "available_action_list_explanation": "現在の設定で検索 / 表示 可能なアクション一覧です",
     "action_list": "アクション一覧",
-    "disable_mode_explain": "現在、監査ログは無効になっています。有効にする場合は環境変数 <code>AUDIT_LOG_ENABLED</code> を true に設定してください。",
+    "disable_mode_explanation": "現在、監査ログは無効になっています。有効にする場合は環境変数 <code>AUDIT_LOG_ENABLED</code> を true に設定してください。",
     "docs_url": {
       "log_type": "https://docs.growi.org/ja/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }

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

@@ -44,6 +44,12 @@
     }
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "全てのページ",
+    "only_children_of_this_tree": "この階層下の子ページのみ",
+    "exact_mutch": "キーワードに完全一致した文字を含むページのみ"
+  },
+
   "share_links": {
     "Share Link": "共有用リンク",
     "Page Path": "ページパス",
@@ -70,6 +76,19 @@
     "feedback": "ご意見・ご要望"
   },
 
+  "create_page_dropdown": {
+    "new_page": "新規ページ作成",
+    "todays": {
+      "desc": "今日のメモを作成",
+      "memo": "メモ"
+    },
+    "template": {
+      "desc": "テンプレートページの作成/編集",
+      "children": "同一階層テンプレート",
+      "descendants": "下位層テンプレート"
+    }
+  },
+
   "copy_to_clipboard": {
     "Copy to clipboard": "クリップボードにコピー",
     "Page path": "ページ名",

+ 114 - 92
apps/app/public/static/locales/ja_JP/translation.json

@@ -10,10 +10,10 @@
   "Duplicate": "複製",
   "PathRecovery": "パスを修復",
   "Copy": "コピー",
-  "preview":"プレビュー",
-  "desktop":"パソコン",
-  "phone":"スマホ",
-  "tablet":"タブレット",
+  "preview": "プレビュー",
+  "desktop": "パソコン",
+  "phone": "スマホ",
+  "tablet": "タブレット",
   "Click to copy": "クリックでコピー",
   "Rename": "名前変更",
   "Move/Rename": "移動/名前変更",
@@ -41,9 +41,11 @@
   "Error": "エラー",
   "Warning": "注意",
   "Sign in": "ログイン",
+  "Sign in with External auth": "{{signin}} でログイン",
   "Sign up is here": "新規登録はこちら",
   "Sign in is here": "ログインはこちら",
   "Sign up": "新規登録",
+  "or": "または",
   "Sign up with Google Account": "Google で登録",
   "Sign in with Google Account": "Google でログイン",
   "Sign up with this Google Account": "この Google アカウントで登録します",
@@ -105,15 +107,14 @@
   "Disclose E-mail": "メールアドレスの公開",
   "page exists": "このページはすでに存在しています",
   "Error occurred": "エラーが発生しました",
-  "Create today's": "今日の◯◯を作成",
-  "Memo": "メモ",
   "Input page name": "ページ名を入力",
   "Input page name (optional)": "ページ名を入力(空欄OK)",
+  "Input parent page path": "親ページのパスを入力",
   "New Page": "新規ページ",
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
-  "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><i class='icon-share-alt'></i>を参照ください。",
+  "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><span className='growi-custom-icons'>external_link</span>を参照ください。",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
   "Basic Settings": "基本設定",
@@ -147,9 +148,10 @@
   "wide_view": "ワイドビュー",
   "Recent Changes": "最新の変更",
   "Page Tree": "ページツリー",
-  "original_path":"元のパス",
-  "new_path":"新しいパス",
-  "duplicated_path":"重複したパス",
+  "In-App Notification": "通知",
+  "original_path": "元のパス",
+  "new_path": "新しいパス",
+  "duplicated_path": "重複したパス",
   "Link sharing is disabled": "リンクのシェアは無効化されています",
   "successfully_saved_the_page": "ページが正常に保存されました",
   "you_can_not_create_page_with_this_name": "この名前でページを作成することはできません",
@@ -214,7 +216,7 @@
   },
   "Password": "パスワード",
   "Password Settings": "パスワード設定",
-  "personal_settings":{
+  "personal_settings": {
     "disassociate_external_account": "External Account の連携解除",
     "disassociate_external_account_desc": "<strong>{{providerType}}</strong> プロバイダーの <strong>{{accountId}}</strong> アカウントを連携解除します",
     "set_new_password": "パスワードを新規に設定",
@@ -228,7 +230,7 @@
     "Shere this page link to public": "外部に共有するリンクを発行する",
     "share_link_list": "共有リンクリスト",
     "share_link_management": "共有リンク管理",
-    "delete_all_share_links":"全ての共有リンクを削除します",
+    "delete_all_share_links": "全ての共有リンクを削除します",
     "expire": "有効期限",
     "Days": "日間",
     "Custom": "カスタム",
@@ -236,8 +238,8 @@
     "enter_desc": "概要を入力",
     "Unlimited": "無期限",
     "Issue": "発行",
-    "share_settings" :"共有設定",
-    "Invalid_Number_of_Date" : "有効期限の日数には整数を入力してください",
+    "share_settings": "共有設定",
+    "Invalid_Number_of_Date": "有効期限の日数には整数を入力してください",
     "link_sharing_is_disabled": "リンクのシェアは無効化されています"
   },
   "API Settings": "API設定",
@@ -252,6 +254,21 @@
       "page_create": "ページを作成した時にそのページをサブスクライブします。"
     }
   },
+  "ui_settings": {
+    "ui_settings": "UI設定",
+    "side_bar_mode": {
+      "settings": "サイドバーモードの設定",
+      "side_bar_mode_setting": "サイドバーのモードを設定する",
+      "description": "画面幅が大きい場合に、サイドバーを常時開いた状態にするかどうかを設定できます。画面幅が小さい場合はサイドバーは常に閉じた状態となります。"
+    }
+  },
+  "color_mode_settings": {
+    "light": "ライト",
+    "dark": "ダーク",
+    "system": "システム",
+    "settings": "カラーモードの設定",
+    "description": "ライトモードかダークモード、もしくはシステム合わせた表示をするか選択します。<br>対応したテーマのみ切り替えることができます。"
+  },
   "editor_settings": {
     "editor_settings": "エディター設定",
     "common_settings": {
@@ -267,7 +284,7 @@
       "no_zero_width_spaces": "ゼロ幅スペースを許可しません。",
       "period_in_list_item": "リストアイテムのピリオドの有無をチェックします。",
       "use_si_units": "SI単位系以外の使用を禁止します。"
-      },
+    },
     "japanese_settings": {
       "japanese_settings": "日本語設定",
       "ja_hiragana_keishikimeishi": "漢字よりひらがなで書かれた読みやすい形式名詞をチェックします。",
@@ -321,7 +338,7 @@
     "notice": {
       "version": "これは最新のバージョンではありません。",
       "redirected": "リダイレクト元 >>",
-      "redirected_period":"",
+      "redirected_period": "",
       "unlinked": "このページへのリダイレクトは削除されました。",
       "restricted": "このページの閲覧は制限されています",
       "stale": "このページは最終更新日から{{count}}年以上が経過しています。",
@@ -330,6 +347,11 @@
     }
   },
   "page_edit": {
+    "input_channels": "チャンネル名",
+    "theme": "テーマ",
+    "keymap": "キーマップ",
+    "indent": "インデント",
+    "editor_config": "エディタ設定",
     "Show active line": "アクティブ行をハイライト",
     "auto_format_table": "表の自動整形",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
@@ -347,7 +369,8 @@
     "already_exists": "そのパスを持つページは既に存在しています。",
     "outdated": "ページが他のユーザーによって更新されました。",
     "user_not_admin": "権限のあるユーザーのみが削除できます",
-    "single_deletion_empty_pages": "空ページの単体削除はできません"
+    "single_deletion_empty_pages": "空ページの単体削除はできません",
+    "complete_deletion_not_allowed_for_user": "ページを完全に削除する権限がありません"
   },
   "page_history": {
     "revision_list": "更新履歴",
@@ -355,8 +378,8 @@
     "comparing_source": "ソース",
     "comparing_target": "ターゲット",
     "comparing_revisions": "差分を比較する",
-    "compare_latest":"最新と比較",
-    "compare_previous":"1つ前のバージョンと比較"
+    "compare_latest": "最新と比較",
+    "compare_previous": "1つ前のバージョンと比較"
   },
   "modal_rename": {
     "label": {
@@ -394,7 +417,7 @@
   "deleted_pages_completely": "{{path}} を完全に削除しました",
   "renamed_pages": "{{path}} を移動/名前変更しました",
   "empty_trash": "ゴミ箱を空にしました",
-  "modal_empty":{
+  "modal_empty": {
     "empty_the_trash": "ゴミ箱を空にする",
     "empty_the_trash_button": "空にする",
     "not_deletable_notice": "権限がないため、いくつかのページは削除できません",
@@ -408,10 +431,12 @@
       "Current page name": "現在のページ名",
       "Recursively": "再帰的に複製",
       "Duplicate without exist path": "存在するパス以外を複製する",
-      "Same page already exists": "同じページがすでに存在します"
+      "Same page already exists": "同じページがすでに存在します",
+      "Only duplicate user related pages": "自分が閲覧可能なページのみを複製する"
     },
     "help": {
-      "recursive": "配下のページも複製します"
+      "recursive": "配下のページも複製します",
+      "only_inherit_user_related_groups": "閲覧権限が「特定グループのみ」で設定されている場合、複製されたページを閲覧可能なグループ一覧から、自分が所属していないものは取り除かれます"
     }
   },
   "duplicated_pages": "{{fromPath}} を複製しました",
@@ -449,13 +474,13 @@
     }
   },
   "modal_resolve_conflict": {
+    "conflicts_with_new_body_on_server_side": "サーバー側の新しい本文と衝突します。ページ本文を選択または編集して衝突を解消してください。",
     "file_conflicting_with_newer_remote": "サーバー側の新しいファイルと衝突します。",
     "resolve_conflict_message": "ページ本文を選んでください",
     "resolve_conflict": "衝突を解消",
-    "resolve_and_save" : "解消し保存する",
-    "select_revision" : "{{revision}}にする",
+    "resolve_and_save": "解消し保存する",
+    "select_revision": "{{revision}}にする",
     "requested_revision": "送信された本文",
-    "origin_revision": "送信する前の本文",
     "latest_revision": "最新の本文",
     "selected_editable_revision": "保存するページ本文(編集可能)"
   },
@@ -482,7 +507,7 @@
     "issue_share_link": "共有リンクを作成しました",
     "remove_share_link": "共有リンクを{{count}}件削除しました",
     "switch_disable_link_sharing_success": "共有リンクの設定を変更しました",
-    "failed_to_reset_password":"パスワードのリセットに失敗しました",
+    "failed_to_reset_password": "パスワードのリセットに失敗しました",
     "save_succeeded": "保存に成功しました"
   },
   "template": {
@@ -499,7 +524,7 @@
       "label": "同一階層テンプレート",
       "desc": "テンプレートページが存在する階層にのみ適用されます"
     },
-    "decendants": {
+    "descendants": {
       "label": "下位層テンプレート",
       "desc": "テンプレートページが存在する下位層のすべてのページに適用されます"
     }
@@ -535,27 +560,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": "チャンネル名を入れてください。カンマ区切りのリストを入力することで複数のチャンネルに通知することができます。"
@@ -570,11 +574,12 @@
     "deletion_modal_header": "以下のページを削除",
     "delete_completely": "完全に削除する",
     "include_certain_path": "{{pathToInclude}}下を含む ",
-    "delete_all_selected_page" : "一括削除",
-    "currently_not_implemented":"現在未実装の機能です",
-    "search_again" : "再検索",
-    "number_of_list_to_display" : "表示件数",
-    "page_number_unit" : "件",
+    "delete_all_selected_page": "一括削除",
+    "currently_not_implemented": "現在未実装の機能です",
+    "search_again": "再検索",
+    "number_of_list_to_display": "表示件数",
+    "page_number_unit": "件",
+    "hit_number_unit": "件",
     "sort_axis": {
       "relationScore": "関連度順",
       "createdAt": "作成日時",
@@ -590,7 +595,7 @@
     "alert_desc1": "このページでは、チェックボックスでページを選択し、画面上部の「一括操作」ボタンから新しい v5 互換形式に一括変換できます。",
     "nopages_title": "おめでとうございます。GROWI v5 を使う準備が完了しました!",
     "nopages_desc1": "今あなたが管理可能なページはすべて v5 互換形式になっているようです。",
-    "detail_info": "詳しくは <a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>GROWI v5.0.x へのアップグレード  <i class='icon-share-alt'></i></a> を参照ください。",
+    "detail_info": "詳しくは <a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html' target='_blank' class='alert-link'>GROWI v5.0.x へのアップグレード <span className='growi-custom-icons'>external_link</span></a> を参照ください。",
     "modal": {
       "title": "新しい v5 互換形式への変換",
       "converting_pages": "以下のページを変換します",
@@ -621,7 +626,7 @@
     "sign_in_error": "ログインエラー",
     "registration_successful": "登録が完了しました。管理者の承認をお待ちください。",
     "Setup": "セットアップ",
-    "enabled_ldap_has_configuration_problem":"LDAPは有効ですが、設定に問題があります。",
+    "enabled_ldap_has_configuration_problem": "LDAPは有効ですが、設定に問題があります。",
     "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"
   },
   "invited": {
@@ -647,23 +652,23 @@
     "sign_in_failure": "ログインに失敗しました。",
     "aws_sttings_required": "この機能にはAWS設定が必要です。管理者に訪ねて下さい。",
     "application_already_installed": "アプリケーションのインストールが完了しました。",
-    "email_address_could_not_be_used":"このメールアドレスは使用できません。(許可されたメールアドレスを確認してください。)",
-    "user_id_is_not_available":"このユーザーIDは使用できません。",
-    "username_should_not_be_null":"Username が null になっています 管理画面の認証機構設定にて設定の確認をしてください",
-    "email_address_is_already_registered":"このメールアドレスは既に登録されています。",
-    "can_not_register_maximum_number_of_users":"ユーザー数が上限を超えたため登録できません。",
-    "email_settings_is_not_setup":"E-mail 設定が完了していません。管理者に問い合わせてください。",
+    "email_address_could_not_be_used": "このメールアドレスは使用できません。(許可されたメールアドレスを確認してください。)",
+    "user_id_is_not_available": "このユーザーIDは使用できません。",
+    "username_should_not_be_null": "Username が null になっています 管理画面の認証機構設定にて設定の確認をしてください",
+    "email_address_is_already_registered": "このメールアドレスは既に登録されています。",
+    "can_not_register_maximum_number_of_users": "ユーザー数が上限を超えたため登録できません。",
+    "email_settings_is_not_setup": "E-mail 設定が完了していません。管理者に問い合わせてください。",
     "email_authentication_is_not_enabled": "メール認証が有効になっていません。管理者に問い合わせてください。",
-    "failed_to_register":"登録に失敗しました。",
-    "successfully_created":"{{username}} が作成されました。",
-    "can_not_activate_maximum_number_of_users":"ユーザーが上限に達したためアクティベートできません。",
-    "failed_to_activate":"アクティベートに失敗しました。",
-    "unable_to_use_this_user":"利用できないユーザーIDです。",
-    "complete_to_install1":"GROWI のインストールが完了しました!管理者アカウントでログインしてください。",
-    "complete_to_install2":"GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。",
-    "failed_to_create_admin_user":"管理ユーザーの作成に失敗しました。{{errMessage}}",
-    "successfully_send_email_auth":"{{email}} にメールを送信しました。添付されたURLをクリックし、本登録を完了させてください",
-    "incorrect_token_or_expired_url":"トークンが正しくないか、URLの有効期限が切れています。",
+    "failed_to_register": "登録に失敗しました。",
+    "successfully_created": "{{username}} が作成されました。",
+    "can_not_activate_maximum_number_of_users": "ユーザーが上限に達したためアクティベートできません。",
+    "failed_to_activate": "アクティベートに失敗しました。",
+    "unable_to_use_this_user": "利用できないユーザーIDです。",
+    "complete_to_install1": "GROWI のインストールが完了しました!管理者アカウントでログインしてください。",
+    "complete_to_install2": "GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。",
+    "failed_to_create_admin_user": "管理ユーザーの作成に失敗しました。{{errMessage}}",
+    "successfully_send_email_auth": "{{email}} にメールを送信しました。添付されたURLをクリックし、本登録を完了させてください",
+    "incorrect_token_or_expired_url": "トークンが正しくないか、URLの有効期限が切れています。",
     "user_already_logged_in": "ログイン中のため、新規アカウントを作成できませんでした。",
     "registration_closed": "新しいアカウントを作成する権限がありません。",
     "Username has invalid characters": "ユーザー名に不正な文字が含まれています.",
@@ -677,22 +682,22 @@
     "Username or E-mail has invalid characters": "ユーザー名または、メールアドレスに無効な文字があります",
     "Password minimum character should be more than 6 characters": "パスワードの最小文字数は6文字以上です",
     "user_not_found": "ユーザーが見つかりません",
-    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>エラー: DuplicatedUsernameException</strong></p><p class='mb-0'> {{ failedProviderForDuplicatedUsernameException }} 認証は成功しましたが、新しいユーザーを作成できませんでした。詳しくは<a href='https://github.com/weseek/growi/issues/193'>こちら: #193</a>.</p>"
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>エラー: DuplicatedUsernameException</strong></p><p class='mb-0'> {{ failedProviderForDuplicatedUsernameException }} 認証は成功しましたが、新しいユーザーを作成できませんでした。詳しくは<a href='https://github.com/weseek/growi/issues/193'>こちら: #193</a>.</p>"
   },
-  "grid_edit":{
-    "create_bootstrap_4_grid":"Bootstrap 4 グリッドを作成",
+  "grid_edit": {
+    "create_bootstrap_4_grid": "Bootstrap 4 グリッドを作成",
     "grid_settings": "グリッド設定",
-    "grid_pattern":"グリッド パターン",
-    "division":"分割",
-    "smart_no":"スマホ / 分割なし",
-    "break_point":"画面サイズより分割"
+    "grid_pattern": "グリッド パターン",
+    "division": "分割",
+    "smart_no": "スマホ / 分割なし",
+    "break_point": "画面サイズより分割"
   },
-  "validation":{
+  "validation": {
     "aws_region": "リージョンには、AWSリージョン名を入力してください。例: ap-northeast-1",
     "aws_custom_endpoint": "カスタムエンドポイントは、http(s)://で始まるURLを指定してください。また、末尾の/は不要です。",
-    "failed_to_send_a_test_email":"SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。"
+    "failed_to_send_a_test_email": "SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。"
   },
-  "forgot_password":{
+  "forgot_password": {
     "forgot_password": "パスワードをお忘れですか?",
     "send": "送信",
     "return_to_login": "ログイン画面に戻る",
@@ -705,11 +710,11 @@
     "email_is_required": "メールを入力してください",
     "success_to_send_email": "メールを送信しました",
     "feature_is_unavailable": "この機能を利用することはできません。",
-    "incorrect_token_or_expired_url":"トークンが正しくないか、URLの有効期限が切れています。 以下のリンクからパスワードリセットリクエストを再送信してください。",
+    "incorrect_token_or_expired_url": "トークンが正しくないか、URLの有効期限が切れています。 以下のリンクからパスワードリセットリクエストを再送信してください。",
     "password_and_confirm_password_does_not_match": "パスワードと確認パスワードが一致しません",
     "please_enable_mailer_alert": "メール設定が完了していないため、パスワード再設定機能が無効になっています。メール設定を完了させるよう管理者に依頼してください。"
   },
-  "emoji" :{
+  "emoji": {
     "title": "絵文字を選択",
     "search": "探す",
     "clear": "リセット",
@@ -739,7 +744,7 @@
       "6": "肌の色が濃い"
     }
   },
-  "maintenance_mode":{
+  "maintenance_mode": {
     "maintenance_mode": "メンテナンスモード",
     "growi_is_under_maintenance": "GROWI はメンテナンス中です。終了するまでお待ちください",
     "admin_page": "管理画面へ",
@@ -751,10 +756,10 @@
     "you_cannot_move_this_page_now": "現在、このページを移動することはできません",
     "something_went_wrong_with_moving_page": "ページの移動に問題が発生しました"
   },
-  "duplicated_page_alert" : {
+  "duplicated_page_alert": {
     "same_page_name_exists": "ページ名 「{{pageName}}」が重複しています",
-    "same_page_name_exists_at_path" : "”{{path}}” において ”{{pageName}}”というページは複数存在しています。",
-    "select_page_to_see" : "以下から遷移するページを選択してください。"
+    "same_page_name_exists_at_path": "”{{path}}” において ”{{pageName}}”というページは複数存在しています。",
+    "select_page_to_see": "以下から遷移するページを選択してください。"
   },
   "user_group": {
     "select_group": "グループを選ぶ",
@@ -770,7 +775,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": "リンクを知っている人のみ",
@@ -801,15 +806,15 @@
       }
     }
   },
-  "page_operation":{
+  "page_operation": {
     "paths_recovered": "パスを修復しました",
-    "path_recovery_failed":"パスを修復できませんでした"
+    "path_recovery_failed": "パスを修復できませんでした"
   },
   "footer": {
     "bookmarks": "ブックマーク",
     "recently_created": "最近作成したページ"
   },
-  "bookmark_folder":{
+  "bookmark_folder": {
     "bookmark_folder": "ブックマークフォルダ",
     "bookmark": "ブックマーク",
     "delete_modal": {
@@ -827,7 +832,7 @@
     "root": "root (default)"
   },
   "v5_page_migration": {
-    "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
+    "page_tree_not_avaliable": "Page Tree 機能は現在使用できません。",
     "go_to_settings": "設定する"
   },
   "questionnaire": {
@@ -859,5 +864,22 @@
   },
   "rich_attachment": {
     "attachment_not_be_found": "アタッチメントが見つかりません"
+  },
+  "page_select_modal": {
+    "select_page_location": "ページの場所を選択"
+  },
+  "wip_page": {
+    "save_as_wip": "WIP (執筆途中) として保存",
+    "success_save_as_wip": "WIP ページとして保存しました",
+    "fail_save_as_wip": "WIP ページとして保存できませんでした",
+    "alert": "このページは執筆途中です",
+    "publish_page": "WIP を解除",
+    "success_publish_page": "WIP を解除しました",
+    "fail_publish_page": "WIP を解除できませんでした"
+  },
+  "sidebar_header": {
+    "show_wip_page": "WIP を表示",
+    "size_s": "サイズ: S",
+    "size_l": "サイズ: L"
   }
 }

+ 15 - 16
apps/app/public/static/locales/zh_CN/admin.json

@@ -47,10 +47,12 @@
     "page_delete": "删除",
     "page_delete_completely": "彻底删除",
     "other_options": "其他选项",
-    "deletion_explain": "限制用户对选定的单一页面进行垃圾处理。",
-    "complete_deletion_explain": "限制可以完全删除所选单页的用户。",
-    "recursive_deletion_explain": "限制用户可以捣毁包括子孙在内的页面。",
-    "recursive_complete_deletion_explain": "限制可以完全删除页面的用户,包括子孙。",
+    "deletion_explanation": "限制用户对选定的单一页面进行垃圾处理。",
+    "complete_deletion_explanation": "限制可以完全删除所选单页的用户。",
+    "recursive_deletion_explanation": "限制用户可以捣毁包括子孙在内的页面。",
+    "recursive_complete_deletion_explanation": "限制可以完全删除页面的用户,包括子孙。",
+    "is_all_group_membership_required_for_page_complete_deletion": "除管理员和页面作者之外的用户必须属于被授予页面访问权限的所有组",
+    "is_all_group_membership_required_for_page_complete_deletion_explanation": "如果页面权限设置为\"仅限特定群体\",则会启用此功能。",
     "inherit": "继承(使用与单页相同的设置)。",
 		"admin_only": "仅管理员",
 		"admin_and_author": "管理员|作者",
@@ -505,8 +507,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 +576,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 +754,7 @@
       "emails": "电子邮件",
       "description1": "通过电子邮件地址临时发布新用户。",
       "description2": "将为首次登录生成一个临时密码。",
-      "mail_setting_link": "<i class='icon-settings mr-2'></i><a href='/admin/app'>Email settings</a>",
+      "mail_setting_link": "<span className='material-symbols-outlined me-2'>settings</span><a href='/admin/app'>Email settings</a>",
       "valid_email": "需要有效的电子邮件地址",
       "invite_thru_email": "发送邀请电子邮件",
       "temporary_password": "创建的用户具有临时密码",
@@ -845,9 +843,10 @@
       "dropdown_desc": "为私人页选择操作",
       "select_group": "选择组",
       "no_groups": "没有可选择的组",
-      "publish_pages": "全部发布",
+      "publish_pages": "发布可以发布的页面",
       "delete_pages": "全部删除",
-      "transfer_pages": "转移到另一组"
+      "transfer_pages": "转移到另一组",
+      "option_explanation": "\"可发布页面\"是指仅对您要删除的群组可见的页面。其他群组可以查看的页面将不会被发布。"
     },
     "update_parent_confirm_modal": {
       "header": "该组的父组被改变",
@@ -870,12 +869,12 @@
     "return": "返回",
     "clear": "清除",
     "activity_expiration_date": "审计日志的到期日",
-    "activity_expiration_date_explain": "创建的审计日志会在环境变量中设置的从创建时间算起的秒数后自动删除",
+    "activity_expiration_date_explanation": "创建的审计日志会在环境变量中设置的从创建时间算起的秒数后自动删除",
     "fixed_by_env_var": "这是由env var 修复的 <code>{{key}}={{value}}</code>.",
     "available_action_list": "搜索/查看 所有可用的行动",
-    "available_action_list_explain": "在当前配置中可以搜索/查看的行动列表",
+    "available_action_list_explanation": "在当前配置中可以搜索/查看的行动列表",
     "action_list": "行动清单",
-    "disable_mode_explain": "审计日志当前已禁用。 要启用它,请将环境变量 <code>AUDIT_LOG_ENABLED</code> 设置为 true。",
+    "disable_mode_explanation": "审计日志当前已禁用。 要启用它,请将环境变量 <code>AUDIT_LOG_ENABLED</code> 设置为 true。",
     "docs_url": {
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }

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

@@ -45,6 +45,12 @@
 		}
   },
 
+  "search_method_menu_item": {
+    "search_in_all": "所有页面",
+    "only_children_of_this_tree": "当前分支以下内容",
+    "exact_mutch": "完全匹配"
+  },
+
   "share_links": {
     "Share Link": "Share Link",
     "Page Path": "Page Path",
@@ -71,6 +77,19 @@
     "feedback": "意见和要求"
   },
 
+  "create_page_dropdown": {
+    "new_page": "新页面",
+    "todays": {
+      "desc": "Create today's memo",
+      "memo": "memo"
+    },
+    "template": {
+      "desc": "创建/编辑模板页",
+      "children": "子模板",
+      "descendants": "子代模板"
+    }
+  },
+
 	"copy_to_clipboard": {
 		"Copy to clipboard": "复制到剪贴板",
 		"Page path": "页面路径",

Разница между файлами не показана из-за своего большого размера
+ 391 - 386
apps/app/public/static/locales/zh_CN/translation.json


+ 0 - 221
apps/app/resource/cdn-manifests.js

@@ -1,221 +0,0 @@
-module.exports = {
-  js: [
-    {
-      name: 'basis',
-      // eslint-disable-next-line max-len
-      url: 'https://cdn.jsdelivr.net/combine/npm/jquery@3.4.0,npm/popper.js@1.15.0,npm/bootstrap@4.5.0/dist/js/bootstrap.min.js,npm/scrollpos-styler@0.7.1,npm/jquery-slimscroll@1.3.8/jquery.slimscroll.min.js',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight',
-      url: 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/highlight.min.js',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight-addons',
-      url: 'https://cdn.jsdelivr.net/combine/'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/dockerfile.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/go.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/gradle.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/json.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/less.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/plaintext.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/scss.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/typescript.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/swift.min.js,'
-        + 'gh/highlightjs/cdn-release@9.13.0/build/languages/kotlin.min.js,'
-        + 'npm/highlightjs-line-numbers.js@2.6.0/dist/highlightjs-line-numbers.min.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'mathjax',
-      url: 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'drawio-viewer',
-      url: 'https://jgraph.github.io/drawio/src/main/webapp/js/viewer.min.js',
-      args: {
-        async: true,
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-dialog',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/addon/dialog/dialog.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-vim',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/vim.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-emacs',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/emacs.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-keymap-sublime',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/keymap/sublime.min.js',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'redoc-standalone',
-      url: 'https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js',
-      args: {
-        integrity: '',
-      },
-    },
-  ],
-  style: [
-    {
-      name: 'lato',
-      url: 'https://fonts.googleapis.com/css?family=Lato:400,700',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'Press Start 2P',
-      url: 'https://fonts.googleapis.com/css?family=Press+Start+2P',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'font-awesome',
-      url: 'https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'themify-icons',
-      url: 'https://cdn.jsdelivr.net/npm/cd-themify-icons@0.0.1/index.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'simple-line-icons',
-      url: 'https://cdn.jsdelivr.net/npm/simple-line-icons@2.4.1/css/simple-line-icons.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'material-icons',
-      url: 'https://cdn.jsdelivr.net/npm/material-icons@0.3.1/iconfont/material-icons.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-
-    {
-      name: 'animate.css',
-      url: 'https://cdn.jsdelivr.net/npm/animate.css@3.7.2/animate.min.css',
-      groups: ['basis'],
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'highlight-theme-github',
-      url: 'https://cdn.jsdelivr.net/npm/highlight.js@9.13.0/styles/github.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-dialog',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/addon/dialog/dialog.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-eclipse',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/eclipse.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-elegant',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/elegant.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-neo',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/neo.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-mdn-like',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/mdn-like.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-material',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/material.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-dracula',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/dracula.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-monokai',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/monokai.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-    {
-      name: 'codemirror-theme-twilight',
-      url: 'https://cdn.jsdelivr.net/npm/codemirror@5.64.0/theme/twilight.min.css',
-      args: {
-        integrity: '',
-      },
-    },
-  ],
-};

+ 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

+ 0 - 253
apps/app/resource/locales/en_US/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

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

@@ -0,0 +1,169 @@
+# 1. Badges
+
+<span class="badge text-bg-primary">primary</span>  
+
+<span class="badge text-bg-secondary">secondary</span>  
+
+<span class="badge text-bg-success">success</span>  
+
+<span class="badge text-bg-danger">danger</span>  
+
+<span class="badge text-bg-warning">warning</span>  
+
+<span class="badge text-bg-info">info</span>  
+
+<span class="badge text-bg-light">light</span>  
+
+<span class="badge text-bg-dark">dark</span>  
+
+
+# 2. Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert.
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert.
+</div>
+
+<div class="alert alert-success" role="alert">
+  This is a success alert.
+</div>
+
+<div class="alert alert-danger" role="alert">
+  This is a danger alert.
+</div>
+
+<div class="alert alert-warning" role="alert">
+  This is a warning alert.
+</div>
+
+<div class="alert alert-info" role="alert">
+  This is a info alert.
+</div>
+
+<div class="alert alert-light" role="alert">
+  This is a light alert.
+</div>
+
+<div class="alert alert-dark" role="alert">
+  This is a dark alert.
+</div>
+
+
+# 3. Cards
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+
+# 4. Colors
+## Contextual colors
+<p class="text-primary">Look, I'm in a well!</p>
+<p class="text-warning">Look, I'm in a well!</p>
+<p class="text-danger">Look, I'm in a well!</p>
+
+## Contextual backgrounds
+<p class="text-danger bg-primary">Look, I'm in a well!</p>
+<p class="text-primary bg-warning">Look, I'm in a well!</p>
+<p class="text-warning bg-danger">Look, I'm in a well!</p>
+
+
+# 5. Collapse
+## Displaying content
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  Show content
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- Content you want to display
+  - Content you want to display
+      
+  </div>
+</div>
+
+## Hiding content
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  Hide content
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- Content you want to hide
+  - Content you want to hide
+
+  </div>
+</div>
+
+
+# Official docs
+- [Click here for Badges details](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [Click here for Alerts details](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [Click here for Cards details](https://getbootstrap.jp/docs/5.3/components/card/)
+- [Click here for Colors details](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [Click here for Collapse details](https://getbootstrap.jp/docs/5.3/components/collapse/)

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

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(former Draw.io)
+# :pencil: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -15,7 +15,7 @@ See [diagrams.net](https://diagrams.net)
 ```
 
 
-## AWS diagram
+## AWS configuration diagram
 
 ``` drawio
 3Zhdb5swFIZ/TS4XYRswuUzSr0mtVqmtejkZOASvgJHtfO3Xz+YjgdJqiaa1SbnBvD7G9vv4IJsRmeeba8nK9E7EkI2wE29G5GKEMfa9wNyssq0VhFyvVhaSx422Fx74b2hEp1GXPAbVC9RCZJqXfTESRQGR7mlMSrHuhyUi6/dasgUMhIeIZUP1mcc6rdUA071+A3yRtj0jf1LX5KwNbmaiUhaLdUcilyMyl0LoupRv5pBZ91pf6nZX79TuBiah0G80eFIgf4S/rCfYyVhowFRBI+xFIi9FYZthr3WvVaqYGxZy2+xRsugFpCndPN7dmtu0LJtuMxZBaswE2Te4HR7ezXA3cqW3ravGi9IW883CrpsxWyt3nIuQ24BZwrNsLjIhq2CSJOBHkdGVluIFOjUxnYSOY2pWIDU30G7tPO+F4pqLwsSEQmuRmwCmynp1JHwDZoizerS2HWzeNRR1JnENIgcttyakafANuQ3aZnG7Ph37vk8d6pAgoBO3rl131k3TIO0smVZjjZGLXUd7mKbQ2Ng+dvD+M+6n7xatUqDVgXTJ8XQVGZLFlJqEeYtsUl2fRRb7Y+QEJCCIBhQ5ExL0OBPHOReyqsre6VKnRjM+Vu4dxtg9nnEkFgXXYgh6ThFBV6cHmgRj10XUo9jByA1c90vk8/TeJvQ107Bm2wNpe8fTZiX/uWg6GRD3psSZeadH/C+p7RNvTAhxzaedUuoFwbkgf34w4i3Lw5gdSNw/nnhWvf9nsiyimtWBH/TCjPSzgCP/FXH3SwC/YJqFTMGBsOnxsONtwXIRh0PK1q/Z5PRymzgni3qwfW86X7FsCS113KcSLeXKWnNhd7hQxFN7nNlnk1GuuO2yqo+ZSqtg9BYXPwogTHYuQzw49Lzy2AxELGUEnc28OXgxuQA93AF2SEjIzB5j1X/7EdYNfJqcuU/uB/nUnpfP1ijvo4xC52SUNzTK/yij8DkZ5Q+Nov/HKPO4/2lT1XX+fZHLPw==
@@ -27,8 +27,7 @@ See [diagrams.net](https://diagrams.net)
 
 See [PlantUML](http://plantuml.com/).
 
-## Sequence diagram
-
+## Sequence Diagram
 ``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
@@ -63,7 +62,6 @@ deactivate A
 
 
 ## Class diagram
-
 ``` plantuml
 @startuml
 
@@ -155,7 +153,7 @@ State3 --> [*] : Aborted
 
 # :pencil: Mermaid
 
-## Pie chart diagram
+## Pie graph
 
 ```mermaid
 %%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
@@ -167,7 +165,7 @@ pie showData
     "Iron" :  5
 ```
 
-## Gantt diagram
+## Gantt chart
 
 ```mermaid
 gantt
@@ -181,7 +179,7 @@ gantt
     another task      : 24d
 ```
 
-## Gitgraph diagram
+## Git tree diagram
 
 ```mermaid
 %%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
@@ -202,14 +200,13 @@ gitGraph
   commit
 ```
 
-## Mindmap diagram
+## Mind map
 
 ```mermaid
 mindmap
   root((mindmap))
     Origins
       Long history
-      ::icon(fa fa-book)
       Popularisation
         British popular psychology author Tony Buzan
     Research

+ 110 - 391
apps/app/resource/locales/en_US/sandbox.md

@@ -1,439 +1,158 @@
-# :memo: Table of Contents
+# What is Sandbox?
+- In this page, you will find tips that help you to master GROWI 
+- Feel free to enrich the content of your pages with the references under this hierarchy
 
-Add `ToC` after some `#` signs.
-`Table of Contents` or `Table-of-Contents` is also OK.
 
-```
-# ToC
-```
-
-## ToC
-
-# :memo: Block Elements
+# :closed_book:Headings & Paragraphs
+- By inserting headings and paragraphs, you can make the text on the page easier to read
 
 ## Headers
-
-Add one `#` per level at the start of the line
+- Add `#` before the heading text to create a heading 
+    - Depending on the number of `#`, the typeface size of headings would be different shown in the View screen 
+    - Check the View screen on the right side to understand the effect of headings
+- The number of `#` will decide the hierarchy level and help you to organize the contents
 
 ```
-# Header 1
-## Header 2
-### Header 3
-#### Header 4
-##### Header 5
-###### Header 6
+# First-level heading
+## Second-level heading
+### Third-level heading
+#### Forth-level heading
+##### Fifth-level heading
+###### Sixth-level heading
 ```
 
-### Header 3
+## Break
+- Insert two half-width spaces at the end of the sentence you want to break
+    - You can also change this in the Setting to break the line without half-width spaces
+        - Change the line break setting in the `Markdown Settings` sector of the admin page
 
-#### Header 4
+#### Without line break
+Paragraph 1
+Paragraph 2
 
-##### Header 5
+#### With line break
+Paragraph 1  
+Paragraph 2
 
-###### Header 6
+## Block
+- Paragraphs can be created by inserting a blank table in the text
+- Passage can be broken into sentences and make them easier to read
 
-## Block paragraph
+#### Without paragraph
+Paragraph 1  
+Paragraph 2
 
-Paragraphs are created by inserting a newline character
-A paragraph can be created by pressing Enter at the end of the previous paragraph.
+#### With paragraph
+Paragraph 1  
 
-```
-paragraph1
-(Blank line)
-paragraph2
-```
+Paragraph 2
 
-paragraph1
 
-paragraph2
+# :green_book: Styling Text
+- Various styles can be applied to enrich the textual expression of a sentence
+    - These styles also can be easily applied by selecting the toolbar icon at the bottom of the Edit screen
 
-## Br new line
+## Italic
+- Enclose the text with an asterisk `*` or an underscore `_`.
 
-Add two spaces before break.
-***This behavior can be modified in the options menu.***
+#### Examples
+- This sentence indicates emphasis with *Italic*
+- This sentence indicates emphasis with _Italic_ 
 
-```
-foo
-bar(two spaces)
-baz
-```
+## Bold
+- Enclose the text with two asterisks `*` or two underscores `_`
 
-foo
-bar
-baz
+#### Example
+- This sentence indicates emphasis with **Bold** 
+- This sentence indicates emphasis with __Bold__
 
-## Blockquotes
-
-Add one `>` per level at the start of the line
-
-```
-> quote
-> quote
->> nested quotes
-```
-
-> quote
-> quote
->> nested quotes
+## Italic & Bold
+- Enclose the text with three asterisks `*` or three underscores `_`
 
-## Code
+#### Example
+- This sentence indicates emphasis with ***Italic & Bold***
+- This sentence indicates emphasis witH ___Italic & Bold___
 
-Wrap code with three back quotes or tildes.
 
-```
-print 'foo'
-```
+# :orange_book: Insert Lists
+## Bulleted List
+- Insert a bulleted list by starting a line with a hyphen `-`, a plus `+`, or an asterisk `*`
 
-### Syntax highlight and file name
+#### Example
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+        - This sentence is present in the bulleted list
+- This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
 
-- corresponding [highlight.js Demo](https://highlightjs.org/static/demo/) of common category
+## Numbered List
+- `Number.` at the beginning of a line to insert a numbered list
+- Numbered list and bulleted list can also be combined for use
 
+#### Example
+1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+    1. This sentence is present in the numbered list
+        - This sentence is present in the bulleted list 
+1. This sentence is present in the bulleted list
+    - This sentence is present in the bulleted list
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+## Task List
+- Insert an unchecked checkbox list by writing `[] `
+    - Check the checkbox by writing `[x]`
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
+#### Example
+- [ ] Task 1
+    - [x] Task 1-1
+    - [ ] Task 1-2
+- [x] Task 2
 
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
+# :blue_book: Others
+## Blockquotes
+- Use quoted expressions by putting `>` at the beginning of the paragraph
+    - Multiple quotations can be expressed by using a sequence of `>` characters
+- Lists and other elements can be used together within the blockquotes
 
-### Inline code
+#### Example
+> - Quotation
+> - Quotation
+>> Multiple quotations need to insert more `>`
 
-Words wrapped by `` `back quotes` `` will be formatted as inline code.
+## Code
+- It is possible to express the code by adding it in three `` ` ``
 
+#### Example
 ```
-This is `Inline Code`.
-```
-
-This is  `Inline Code`.
-
-## Pre-arranged text
+Add codes here  
+Line breaks and paragraphs can be reflected in the code
 
-Code blocks should be preceded by four spaces or one tab.
-
-```
-    class Foo
-        def foo
-            print 'foo'
-        end
-    end
+- List also can be used in code
+    - List also can be used in code
 ```
 
-    class Foo
-        def foo
-            print 'foo'
-        end
-    end
+## Inline Code
+- Enclose words in `` ` `` to make inline code
 
-## Horizontal Line
+#### Example
+Here is the `inline code` 
 
-Write three underscores `_`, or asterisks`*`.
+## Horizontal lines
+- Insert the horizontal line with three or more consecutive asterisks `*` or underscores `_`
 
-```
+#### Example
+Below is a horizontal line
 ***
-___
----
-```
 
-***
+Below is a horizontal line
 ___
----
-
-
-
-# :memo: Typography
-
-## Strong Text
-
-### Italic
-
-To italicize text, add one asterisk or underscores before and after a word or phrase.
-
-```
-This is *Italic* .
-This is _Italic_ .
-```
-
-This is *Italic* .
-This is _Italic_ .
-
-### Bold
-
-To make text bold, add two asterisks or underscores before and after a word or phrase.
-
-```
-This is **bold**.
-This is __bold__.
-```
-
-This is **bold**.
-This is __bold__.
-
-### Bold + Italic
-
-To bold and italicize text, add three asterisks or underscores before and after a word or phrase.
-
-```
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-```
-
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-
-# :memo: Images
-
-You can insert `<img>` tag using `![description](URL)`.
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-```
-
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-The size of the image can be set by using an HTML image tag
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-```
-
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
-
-## Markdown standard
-
-You can create links using `[Display text](URL)`.
-
-```
-[Google](https://www.google.co.jp/)
-```
-
-[Google](https://www.google.co.jp/)
-
-## Pukiwiki like linker
-
-This is the most flexible linker.
-Both the page description and link address can be displayed on the page.
-
-```
-[[./Bootstrap4]]
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-```
-
-[[./Bootstrap4]]  
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-
-# :memo: Lists
-
-## Ul Bulleted list
-
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items.
-Items can be nested using indentation.
-
-```
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-```
-
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-
-## Ol Numbered List
-
-To create an ordered list, add line items with numbers followed by periods.
-The numbers don’t have to be in numerical order, but the list should start with the number one.
-
-```
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-```
-
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-
-
-## Check list
-
-```
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-```
-
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-
-
-# :memo: Table
-
-## Markdown Standard
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
-```
-
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
-
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV with header
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## CSV
-
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-## CSV with header
-
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-
-# :memo: Footnote
-
-You can write a reference [^1] to a footnote.
-
-Long footnotes can be written as [^longnote].
-
-[^1]: A_reference_to_the_first_footnote.
-
-[^longnote]: An_example_of_writing_a_footnote_in_multiple_blocks.
-
-    Subsequent paragraphs are indented and belong to the previous footnote.
-
-
-# :memo: Emoji
-
-:smiley: :smile: :laughing: :innocent: :drooling_face:
-
-:family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
-
-:+1: :-1: :open_hands: :raised_hands: :point_right:
-
-:apple: :green_apple: :strawberry: :cake: :hamburger:
-
-:basketball: :football: :baseball: :volleyball: :8ball:
-
-:hearts: :broken_heart: :heartbeat: :heartpulse: :heart_decoration:
 
-:watch: :gear: :gem: :wrench: :email:
 
+# :ledger: More Applications
+- [Bootstrap5](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: More..
+- [Diagrams](/Sandbox/Diagrams)
 
-- Want to attach Bootstrap4 Tags?
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- Want to draw Diagrams?
-    - :arrow_right: [/Sandbox/Diagrams]
-- Want to write Math Formulas?
-    - :arrow_right: [/Sandbox/Math]
+- [Math](/Sandbox/Math)

+ 46 - 59
apps/app/resource/locales/en_US/welcome.md

@@ -1,64 +1,51 @@
-# :tada: Welcome to GROWI
+# :tada: Welcome to GROWI 
 
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
+GROWI is an internal wiki & knowledge base tool for corporations and individuals.  
+With GROWI, members can easily share and edit information in a company, university seminar, or circle.
 
-GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
-Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.
+Casually writing down the information you know and editing it together can **reduce tacit knowledge within the team**.  
+Let's increase the amount of information shared on a daily base!
 
-We can easily write what we know and edit it together, we can **simplify the tacit knowledge (knowledge which is hard to explain with words) in our team**.  
-Let's increase the information exchange everyday.
-
-### :beginner: How to create a page easily 
-
-- Start from "**Create**" button on the upper right, or the **Pencil Icon** on the lower right.
-    - The page title can be edited again later, don't worry about the title.
-        - On title input field, it's possible to create the page's hierarchy with half-width `/` (slash).
-        - (Example)Try entering `/category1/category2/page-title-we-want-to-create`.
-- We can create a bullet point by adding `-`  at the beginning of the line.
-- We can also copy and paste, drag and drop attachments such as images, PDF, Word/Excel/PowerPoint, etc.
-- Once we finished, press the "**Update**" button to publish the page.
-    - We can also save it by `Ctrl(⌘) + S`.
-
-For more information: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">
-    Tips
-  </div>
-  <div class="card-body">
-    <ul>
-      <li>Ctrl(⌘) + "/" to show quick help.</li>
-      <li>We can write HTML with <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a>.</li>
-    </ul>
-  </div>
+<div class="alert alert-primary" role="alert">
+※Feel free to edit and use this page as the top page of the wiki.
 </div>
 
-# :anchor: For administrator <small>〜After you construct the site〜</small>
-
-### :arrow_right: Do you will use a Wiki with more than one person?
-- :heavy_check_mark: Let's invite some members.
-    - [Add/invite new members to the Wiki](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
-### :arrow_right: Work with Slack to receive page and comment notifications.
-- :heavy_check_mark:  [Slack integration](https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#overview)
-### :arrow_right: Are you switching from another system?
-- :heavy_check_mark: It's possible to import data from other GROWI, esa.io, Qiita:Team.
-    -  [Import Data](https://docs.growi.org/en/admin-guide/management-cookbook/import.html)
-
-For more information: [Admin Guide](https://docs.growi.org/en/admin-guide/)
-
-
-# Content List Example
-
-We can display the content list using a table and `$lsx`.
-
-| All page list (First 15 pages)      | [/Sandbox] List of subordinate pages |
-| ----------------------------------- | ------------------------------------ |
-| $lsx(/,num=15)                      | $lsx(/Sandbox)                       |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
-
-We welcome newcomers joining our slack channel to help improve GROWI.
-In addition to discussing development, we are also happy to answer your questions when you join.
+# :beginner: What can you do with GROWI?
+## 1. Knowledge Management: Create pages to store information and knowledge
+- How to create and edit pages?
+    - You can create a new page from the "Pencil Icon" in the upper left corner of the screen
+    - You can edit a page you have already created by clicking "Edit" in the upper right corner of the screen
+- How to manage pages?
+    - GROWI manages pages in a **hierarchical** structure
+        - Example: ` /page A/page B/page C ` 
+    - Apart from Hierarchy, pages can also be managed with Tags
+
+## 2. Information Retrieval: Search information in various ways
+- Keyword search
+- Search using various sidebars
+    - Search by Page Tree
+    - Search by Latest Changes
+    - Search by Tag, and more...
+
+## 3. Information Sharing: Easy to share both internally and externally
+- You can send the URL and permalink of the page to your company members
+    - User Groups can be used to manage viewing privileges among members of the company
+- GROWI also allows pages to be viewed by users outside the company who do not have an account
+    - Let's share information with users outside your company using shared links!
+
+#### :bulb: Check [Sandbox](/Sandbox) to learn more on how to edit pages!
+
+
+# :wrench: For Administrators - Once GROWI is created
+
+### :arrow_right: Wanna use GROWI with multiple people?
+- :heavy_check_mark: Invite your members!
+    - [Add or invite new members to GROWI](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
+
+### :arrow_right: Not satisfied with the current look of GROWI?
+- :heavy_check_mark: No worry! Let's customize the theme of GROWI!
+    - [Customizing GROWI Themes](/admin/customize)
+
+### :arrow_right: GROWI security settings are not completed?
+- :heavy_check_mark: Come to update your GROWI security settings!
+    - [Update GROWI security settings](/admin/security)

+ 0 - 253
apps/app/resource/locales/ja_JP/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

+ 258 - 0
apps/app/resource/locales/ja_JP/sandbox-bootstrap5.md

@@ -0,0 +1,258 @@
+# Bootstrap について
+- GROWI では [Bootstrap](https://getbootstrap.jp/docs/5.3/getting-started/introduction/)(Bootstrap5)を活用して文章やテキストの装飾をすることが可能です
+- 以下にて紹介する代表的な Bootstrap をそのまま引用し活用していただくことが可能です
+
+# 1. バッジ(Badges)
+
+<span class="badge text-bg-primary">テキスト</span>  
+
+<span class="badge text-bg-secondary">テキスト</span>  
+
+<span class="badge text-bg-success">テキスト</span>  
+
+<span class="badge text-bg-danger">テキスト</span>  
+
+<span class="badge text-bg-warning">テキスト</span>  
+
+<span class="badge text-bg-info">テキスト</span>  
+
+<span class="badge text-bg-light">テキスト</span>  
+
+<span class="badge text-bg-dark">テキスト</span>  
+
+---
+
+#### 活用例
+
+- 入社してすぐにやることリスト
+    1. 自己紹介文を記載してください <span class="badge text-bg-danger">必須</span>  
+    2. 振込先口座情報を記載してください <span class="badge text-bg-danger">必須</span>  
+    3. SNS アカウントを記載してください <span class="badge text-bg-secondary">任意</span>  
+
+---
+
+
+
+
+# 2. アラート(Alerts)
+
+<div class="alert alert-primary" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-success" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-danger" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-warning" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-info" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-light" role="alert">
+  テキストが入ります
+</div>
+
+<div class="alert alert-dark" role="alert">
+  テキストが入ります
+</div>
+
+---
+
+#### 活用例
+
+<div class="alert alert-danger" role="alert">
+  ※こちらの情報はチーム長以上の役職のメンバー以外は編集しないでください※
+</div>
+
+---
+
+
+
+
+# 3. カード(Cards)
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">見出しが入ります</div>
+  <div class="card-body">
+    <h5 class="card-title">小見出しが入ります</h5>
+    <p class="card-text">テキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
+  </div>
+</div>
+
+---
+
+#### 活用例
+
+<div class="card text-bg-warning mb-3" style="max-width: 40rem;">
+  <div class="card-header">一口コラム</div>
+  <div class="card-body">
+    <h5 class="card-title">日本で最初のカレーライスのレシピとは?</h5>
+    <p class="card-text">日本で初めてカレーライスの調理法が紹介されたのは、1872年(明治5年)に出版された「西洋料理指南」という本でした。</p>
+    <p class="card-text">使用する食材として「ネギ・ショウガ・ニンニク・バター・エビ・タイ・鶏・小麦粉・カレー粉」などが挙げられています。</p>
+  </div>
+</div>
+
+---
+
+
+
+
+# 4. カラー(Colors)
+## テキストカラー
+<p class="text-primary">テキストはこちら</p>
+<p class="text-warning">テキストはこちら</p>
+<p class="text-danger">テキストはこちら</p>
+
+## 背景カラー
+<p class="bg-primary">テキストはこちら</p>
+<p class="bg-warning">テキストはこちら</p>
+<p class="bg-danger">テキストはこちら</p>
+
+## テキスト&背景カラー
+<p class="text-danger bg-primary">テキストはこちら</p>
+<p class="text-primary bg-warning">テキストはこちら</p>
+<p class="text-warning bg-danger">テキストはこちら</p>
+
+---
+
+#### 活用例
+
+- <p class="text-danger">プロジェクトにアサインされる場合はスタートアップを完了させておきましょう</p>
+- <p class="bg-warning">分からないことがあればまとめて質問しましょう</p>
+
+---
+
+
+
+
+# 5. コラプス(Collapse)
+- コラプスはコンテンツの 表示 / 非表示 の切り替えの際に活用できます
+
+## コンテンツの表示
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  コンテンツを表示する
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- 表示させたいコンテンツの内容が入ります
+  - 表示させたいコンテンツの内容が入ります
+      
+  </div>
+</div>
+
+
+## コンテンツの非表示
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  コンテンツを非表示にする
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- 非表示にさせたいコンテンツの内容が入ります
+  - 非表示にさせたいコンテンツの内容が入ります
+
+  </div>
+</div>
+
+
+#### 活用例
+<a class="btn btn-warning text-white" data-bs-toggle="collapse" href="#collapse-3">
+  最終順位を確認する!
+</a>
+
+<div class="collapse" id="collapse-3">
+  <div class="card card-body">
+
+##### 優勝者は **Bさん** です!!
+
+| 対象者 | 点数 | 順位 |
+| ------ | ---- | ---- |
+| Aさん  | 80pt | 2位  |
+| Bさん  | 95pt | 1位  |
+| Cさん  | 70pt | 3位  |
+      
+  </div>
+</div>
+
+
+
+
+
+
+# 公式ドキュメント
+- [バッジの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/badge/)
+- [アラートの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/alerts/)
+- [カード詳細はこちら](https://getbootstrap.jp/docs/5.3/components/card/)
+- [カラーの詳細はこちら](https://getbootstrap.jp/docs/5.3/utilities/colors/)
+- [コラプスの詳細はこちら](https://getbootstrap.jp/docs/5.3/components/collapse/)

Разница между файлами не показана из-за своего большого размера
+ 12 - 3
apps/app/resource/locales/ja_JP/sandbox-diagrams.md


+ 4 - 4
apps/app/resource/locales/ja_JP/sandbox-math.md

@@ -1,10 +1,10 @@
-# :pencil: Math
-
-See [KaTeX](https://katex.org/).
+# Math について
+- GROWI では  [MathJax](https://www.mathjax.org/) を活用して文章内に数式を挿入することが可能です
+- 以下にて紹介する代表的な MathJax の記法をそのまま引用し活用することが可能です
 
 ## Inline Formula
 
-When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are
+When $a \ne 0$, there are two solutions to $ax^2 + bx + c = 0$ and they are  
   $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$
 
 ## The Lorenz Equations

+ 192 - 329
apps/app/resource/locales/ja_JP/sandbox.md

@@ -1,19 +1,16 @@
-# :memo: 目次
+# Sandbox(サンドボックス)とは
+- この階層下では、GROWI をより便利に活用するための活用術や活用ヒントを掲載しています
+- この階層下のページ内容を組織内で自由に書き換えて GROWI の理解度を深めるために活用しましょう!
 
-いくつかの `#` 記号に続けて `ToC` という文字列を記述します。  
-`Table of Contents` または `Table-of-Contents` でも構いません。
 
-```
-# ToC
-```
-
-## ToC
+# :memo:見出しや段落
+- 見出しや段落を挿入することで、ページ内の文章にメリハリがつき読みやすい文章を作成することが可能です
 
-# :memo: Block Elements
-
-## Headers 見出し
-
-先頭に`#`をレベルの数だけ記述します。
+## 見出し(Headers)
+- 行頭に `#` をレベルの数だけ記述することで見出しを作成することが可能です
+    - 各見出しに応じて View 画面に表示される際のデザインも異なります
+    - 各見出しに応じて View 画面右側に表示される目次が生成されます
+- このページ内にもたくさんの見出しが活用されており、`#` の数に応じて内容をグルーピングすることで可能です
 
 ```
 # 見出し1
@@ -24,382 +21,253 @@
 ###### 見出し6
 ```
 
-### 見出し3
-
-#### 見出し4
-
-##### 見出し5
-
-###### 見出し6
+## 改行(Br)
+- 改行したい文章の行末に半角スペースを2つ挿入することで改行をすることができます
+    - こちらの挙動は、設定画面から半角スペースなしで改行が反映されるように設定を変更することが可能です
+        - 「マークダウン設定_Line Break設定(/admin/markdown)」から変更が可能です
 
-## Block 段落
+#### 改行がない場合
+文章 1 の内容が入ります
+文章 2 の内容が入ります
 
-空白行を挟むことで段落となります。
+#### 改行がある場合
+文章 1 の内容が入ります  
+文章 2 の内容が入ります
 
-```
-段落1
-(空行)
-段落2
-```
+## 段落(Block)
+- 文章内で空白表を挿入することで段落を作成することが可能です
+- 段落を作成することで文章の節目を作成し読みやすい文章を作成することができます
 
-段落1
+#### 段落がない場合
+文章 1 の内容が入ります  
+文章 2 の内容が入ります
 
-段落2
+#### 段落がある場合
+文章 1 の内容が入ります  
 
-## Br 改行
+文章 2 の内容が入ります
 
-改行の前に半角スペース``を2つ記述します。
-***この挙動は、オプションで変更可能です***
 
-```
-hoge
-fuga(スペース2つ)
-piyo
-```
+# :memo:文字の強調
+- 各種記述方法を適用させることで文内の文字の表現を豊かにすることが可能です
+    - これらの表現は Edit 画面下部のツールバーから該当のアイコンを選択することで簡単に適用させることも可能です
 
-hoge
-fuga
-piyo
+## 斜体(Italic)
+- アスタリスク `*` もしくはアンダースコア `_` 1つで該当の文字列を囲みます
 
-## Blockquotes 引用
+#### 活用例
+- この文章は *斜体が適用* されます  
+- この文章は _斜体が適用_ されます
 
-先頭に`>`を記述します。ネストは`>`を多重に記述します。
+## 太字(Bold)
+- アスタリスク `*` もしくはアンダースコア `_` 2つで該当の文字列を囲みます
 
-```
-> 引用
-> 引用
->> 多重引用
-```
+#### 活用例
+- この文章は **強調が適用** されます  
+- この文章は __強調が適用__ されます
 
-> 引用
-> 引用
->> 多重引用
+## 斜体 & 太字(Italic & Bold)
+- アスタリスク `*` もしくはアンダースコア `_` 3つで該当の文字列を囲みます
 
-## Code コード
+#### 活用例
+- この文章は ***斜体 & 太字が適用*** されます  
+- この文章は ___斜体 & 太字が適用___ されます
 
-`` `バッククオート` `` 3つ、あるいはチルダ`~`3つで囲みます。
 
-```
-print 'hoge'
-```
+# :memo:リストの挿入
+## 箇条書きリスト
+- ハイフン `-`、プラス `+`、アスタリスク `*` を行頭に記述することで、箇条書きのリストを挿入することでができます
+    - タブを活用することで前の行のリストに紐づくリストを挿入することも可能です
 
-### シンタックスハイライトとファイル名
-
-- [highlight.js Demo](https://highlightjs.org/static/demo/) の common カテゴリ内の言語に対応しています
+#### 活用例
+- この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています
+        - この文章は箇条書きリストで表現しています
+        - この文章は箇条書きリストで表現しています
+- この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています
 
+## 番号付きリスト
+- `番号.` を行頭に記述することで、番号付きのリストを挿入することができます
+    - タブを活用することで前の行のリストに紐づくリストを挿入することも可能です
+- 番号付きリストと箇条書きリストを組み合わせて活用することも可能です
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
-
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
-
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
-
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
+#### 活用例
+1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+    1. この文章は番号付きリストで表現しています
+        - この文章は箇条書きリストで表現しています 
+1. この文章は箇条書きリストで表現しています
+    - この文章は箇条書きリストで表現しています  
 
-### インラインコード
-
-`` `バッククオート` `` で単語を囲むとインラインコードになります。
-
-```
-これは `インラインコード`です。
-```
-
-これは `インラインコード`です。
-
-## pre 整形済みテキスト
-
-半角スペース4個もしくはタブで、コードブロックをpre表示できます
-
-```
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-```
-
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-
-## Hr 水平線
-
-アンダースコア`_` 、アスタリスク`*`を3つ以上連続して記述します。
-
-```
-***
-___
----
-```
-
-***
-___
----
-
-
-
-# :memo: Typography
-
-## 強調
-
-### em
-
-アスタリスク`*`もしくはアンダースコア`_`1個で文字列を囲みます。
+## タスクリスト
+- `[] ` を記述することでリストに対して未チェックのチェックボックスを挿入することができます
+    - `[x] ` を記述することでチェック済みのチェックボックスを挿入することができます
 
-```
-これは *イタリック* です
-これは _イタリック_ です
-```
+#### 活用例
+- [ ] タスク 1
+    - [x] タスク 1-1
+    - [ ] タスク 1-2
+- [x] タスク2
 
-これは *イタリック* です
-これは _イタリック_ です
 
-### strong
+# :memo:表の挿入
+## Markdown 標準
+- Markdown で記載できる標準的な形式の表です
 
-アスタリスク`*`もしくはアンダースコア`_`2個で文字列を囲みます。
+#### 活用例
+| 左揃え               |               右揃え |        中央揃え        |
+| :------------------- | -------------------: | :--------------------: |
+| この列は             |             この列は |        この列は        |
+| 左揃えで表示されます | 右揃えで表示されます | 中央揃えで表示されます |
 
+## TSV
+#### 活用例
+``` tsv
+10:00	集合
+10:20	移動
 ```
-これは **ボールド** です
-これは __ボールド__ です
-```
-
-これは **ボールド** です
-これは __ボールド__ です
 
-### em + strong
-
-アスタリスク`*`もしくはアンダースコア`_`3個で文字列を囲みます。
-
-```
-これは ***イタリック&ボールド*** です
-これは ___イタリック&ボールド___ です
+## TSV(ヘッダー付き)
+#### 活用例
+``` tsv-h
+時間	行動
+10:00	集合
+10:20	移動
 ```
 
-これは ***イタリック&ボールド*** です
-これは ___イタリック&ボールド___ です
-
-# :memo: Images
-
-`![Alt文字列](URL)` で`<img>`タグを挿入できます。
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
+## CSV
+#### 活用例
+``` csv
+11:00,MTG
+12:00,昼食
 ```
 
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-画像の大きさなどの指定をする場合はimgタグを使用します。
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
+## CSV(ヘッダー付き)
+#### 活用例
+``` csv-h
+時間,行動
+11:00,MTG
+12:00,昼食
 ```
 
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
 
+# :memo:リンクの挿入
 ## Markdown 標準
+- Markdown で記載できる標準的な形式のリンクです
+- `[表示されるテキスト](リンク先のURL)`でリンクに変換されます
 
-`[表示テキスト](URL)`でリンクに変換されます。
-
-```
-[Google](https://www.google.co.jp/)
-```
-
+#### 活用例
 [Google](https://www.google.co.jp/)
 
 ## Pukiwiki like linker
+- もっとも柔軟なリンクの形式です
+- 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます
 
-最も柔軟な Linker です。
-記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
+#### 活用例
+Bootstrap によるページの装飾方法の記述方法は [[こちらをご確認ください>./Bootstrap5]]
 
-```
-[[./Bootstrap4]]
-Bootstrap4のExampleは[[こちら>./Bootstrap4]]
-```
 
-[[./Bootstrap4]]  
-Bootstrap4のExampleは[[こちら>./Bootstrap4]]
+# :memo:画像の挿入
+## 画像(Images)の挿入
+- `![Alt文字列](URL)` で`<img>`タグを挿入できます
 
-# :memo: Lists
+#### 活用例
+![Minion](https://octodex.github.com/images/minion.png)
 
-## Ul 箇条書きリスト
+## 画像のサイズ指定
+- 画像の大きさなどを指定する場合はimgタグを使用します
 
-ハイフン`-`、プラス`+`、アスタリスク`*`のいずれかを先頭に記述します。
-ネストはタブで表現します。
+#### 活用例
+<img src="https://octodex.github.com/images/dojocat.jpg" width="500px">
 
-```
-- リスト1
-    - リスト1_1
-        - リスト1_1_1
-        - リスト1_1_2
-    - リスト1_2
-- リスト2
-- リスト3
-```
 
-- リスト1
-    - リスト1_1
-        - リスト1_1_1
-        - リスト1_1_2
-    - リスト1_2
-- リスト2
-- リスト3
+# :memo:コンテンツやページの表示
+## 目次(ToC)
+- いくつかの `#` 記号に続けて `ToC` を記述することでページ内に目次を生成することができます
+    - `ToC` は `Table of Contents` または `Table-of-Contents` でも適用されます
+- 生成される目次は、ページ内で `ToC` を記述した以降の部分の目次となります
 
-## Ol 番号付きリスト
+#### 活用例
+##### ToC
 
-`番号.`を先頭に記述します。ネストはタブで表現します。
-番号は自動的に採番されるため、すべての行を1.と記述するのがお勧めです。
+## 配下ページの表示(lsx)
+- ページ内に `$lsx()` を記述することで配下に作成されているページを表示することができます
+- 各種オプションを指定することで表示される配下ページを操作することができます
+    - lsx の詳細は [GROWI 公式ドキュメント](https://docs.growi.org/ja/guide/features/lsx.html) をご確認ください
 
-```
-1. 番号付きリスト1
-    1. 番号付きリスト1-1
-    1. 番号付きリスト1-2
-1. 番号付きリスト2
-1. 番号付きリスト3
-```
+#### 活用例
+$lsx()
 
-1. 番号付きリスト1
-    1. 番号付きリスト1-1
-    1. 番号付きリスト1-2
-1. 番号付きリスト2
-1. 番号付きリスト3
+# :memo:その他の基本的な表現
+## 引用(Blockquotes)
+- 行頭に `>` を記述することで引用表現をすることが可能です
+    - 多重引用の際は `>` を複数個連続で記述することで表現が可能です
+- 引用内でリストなどの要素を併用することも可能です
 
+#### 活用例
+> - 引用する文章が入ります
+> - 引用する文章が入ります
+>> 多重引用したい文章の場合は複数個の挿入が必要です
 
-## タスクリスト
+## コード(Code)
+- `` ` `` 3つで囲むことでコードの表現をすることが可能です
 
+#### 活用例
 ```
-- [ ] タスク 1
-    - [x] タスク 1.1
-    - [ ] タスク 1.2
-- [x] タスク2
-```
-
-- [ ] タスク 1
-    - [x] タスク 1.1
-    - [ ] タスク 1.2
-- [x] タスク2
-
-
-# :memo: Table
+コードが入ります  
+改行や段落をコード内で反映させることが可能です
 
-## Markdown 標準
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
+- リストもコード内での表現が可能です
+    - リストもコード内での表現が可能です
 ```
 
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
+## インラインコード
+- `` ` `` で単語を囲むとインラインコードになります
 
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
+#### 活用例
+こちらは `インラインコード` です
 
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV (ヘッダー付き)
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
+## シンタックスハイライトとファイル名
+- [highlight.js Demo](https://highlightjs.org/static/demo/) の common カテゴリ内の言語に対応しています
 
-## CSV
+#### 活用例 
+```javascript:mersenne-twister.js
+function MersenneTwister(seed) {
+  if (arguments.length == 0) {
+    seed = new Date().getTime();
+  }
 
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
+  this._mt = new Array(624);
+  this.setSeed(seed);
+}
 ```
-~~~
 
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
+## pre 整形済みテキスト
+- 半角スペース4個もしくはタブで、コードブロックを pre 表示できます
 
-## CSV (ヘッダー付き)
+#### 活用例
+    class Hoge
+        def hoge
+            print 'hoge'
+        end
+    end
 
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
+## 水平線(Hr)
+- アスタリスク `*` もしくはアンダースコア `_` を3つ以上連続して記述することで水平線を挿入できます
 
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
+#### 活用例
+以下に水平線が挿入されます
+***
 
+以下に水平線が挿入されます
+___
 
-# :memo: Footnote
+## 脚注(Footnote)
+- 脚注 `[^1]` と脚注への参照 `[^1]:` を作成することができます
 
+#### 活用例
 脚注への参照[^1]を書くことができます。
 
 長い脚注は[^longnote]のように書くことができます。
@@ -410,9 +278,7 @@ Content Cell,Content Cell
 
     後続の段落はインデントされて、前の脚注に属します。
 
-
-# :memo: Emoji
-
+## 絵文字(Emoji)
 :smiley: :smile: :laughing: :innocent: :drooling_face:
 
 :family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
@@ -428,12 +294,9 @@ Content Cell,Content Cell
 :watch: :gear: :gem: :wrench: :email:
 
 
+# :memo:さらに応用的な表現
+- [ページの装飾方法(Bootstrap5)](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: 更に…
+- [図形の表現方法(Diagrams)](/Sandbox/Diagrams)
 
-- Bootstrap4 のタグを使う
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- 図表を書く
-    - :arrow_right: [/Sandbox/Diagrams]
-- 数式を書く
-    - :arrow_right: [/Sandbox/Math]
+- [数式の表現方法(Math)](/Sandbox/Math)

+ 44 - 53
apps/app/resource/locales/ja_JP/welcome.md

@@ -1,60 +1,51 @@
 # :tada: GROWI へようこそ
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
-GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
+GROWI は法人・個人向けの wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。
 
 知っている情報をカジュアルに書き出しみんなで編集することで、**チーム内での暗黙知を減らす**ことができます。  
-当たり前に共有される情報を日々増やしていきましょう。
-
-### :beginner: 簡単なページの作り方
-
-- 右上の "**作成**"ボタンまたは右下の**鉛筆アイコン**のボタンからページを書き始めることができます
-    - ページタイトルは後から変更できますので、適当に入力しても大丈夫です
-        - タイトル入力欄では、半角の `/` (スラッシュ) でページ階層を作れます
-        - (例)`/カテゴリ1/カテゴリ2/作りたいページタイトル` のように入力してみてください
-- `- ` を行頭につけると、この文章のような箇条書きを書くことができます
-- 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
-- 書けたら "**更新**" ボタンを押してページを公開しましょう
-    - `Ctrl(⌘) + S` でも保存できます
-
-さらに詳しくはこちら: [ページを作成する](https://docs.growi.org/ja/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">Tips</div>
-  <div class="card-body"><ul>
-    <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
-    <li>HTML/CSS の記述には、<a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a> を利用できます</li>
-  </ul></div>
-</div>
-
-
-# :anchor: 管理者の方へ <small>〜Wikiを作ったら〜</small>
-
-### :arrow_right: 複数人でWikiを使いますか?
-- :heavy_check_mark: メンバーを招待しましょう
-    - [Wikiに新しいメンバーを追加・招待する](https://docs.growi.org/ja/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
-### :arrow_right: Slackと連携してページやコメントの通知を受け取りましょう
-- :heavy_check_mark:  [Slack連携](https://docs.growi.org/ja/admin-guide/management-cookbook/slack-integration/#%E6%A6%82%E8%A6%81)
-### :arrow_right: 他のシステムからの乗り換えですか?
-- :heavy_check_mark: 他の GROWI、esa. io、Qiita:Team のデータをインポートすることが出来ます
-    -  [データのインポート](https://docs.growi.org/ja/admin-guide/management-cookbook/import.html)
-
-さらに詳しくはこちら: [管理者ガイド](https://docs.growi.org/ja/admin-guide/)
-
+当たり前に共有される情報を日々増やしていきましょう!
 
-# コンテンツリストアップ例
-
-テーブルと `$lsx` を使ってコンテンツリストを表示できます。
-
-| 全てのページリスト (First 15 pages) | [/Sandbox] 配下ページ一覧 |
-| ----------------------------------- | ------------------------- |
-| $lsx(/,num=15)                      | $lsx(/Sandbox)            |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
+<div class="alert alert-primary" role="alert">
+※こちらのページは wiki の TOP ページとして自由に編集してご活用ください
+</div>
 
-GROWI をより良いものにするために、是非 Slack に参加してください。  
-開発に関する議論を行っている他、導入時の質問等も受け付けています。
+# :beginner: GROWI でできること
+## 1. **【情報の蓄積】** ページを作成し情報やナレッジの蓄積ができます
+- ページの作成と編集方法 
+    - 画面左上の「鉛筆アイコン」から新規でページを作成することが可能です
+    - 作成済みのページは画面右上の「Edit」からページを編集することが可能です
+- ページの管理方法
+    - GROWI ではページを「階層構造」で管理します
+        - ` /ページa/ページb/ページc ` のような構造
+    - 「階層構造」とは別に「タグ」でもページを管理することが可能です
+
+## 2. **【情報の検索】** 情報やナレッジは様々な方法で検索できます
+- キーワード検索
+- 各種サイドバーを活用した検索
+    - 「ページツリー」からの検索
+    - 「最新の変更」からの検索
+    - 「タグ」からの検索 など…
+
+## 3. **【情報の共有】** 情報やナレッジは社内外を問わず簡単に共有可能です
+- 社内のメンバーに対してはページの URL やパーマリンクを送ることで共有が可能です
+    - 社内のメンバー内でも「ユーザーグループ」を活用することで閲覧権限の管理をすることが可能です
+- アカウントを保有していない社外のユーザーのページ閲覧を可能にすることも可能です
+    - 「共有リンク」を活用し社外のユーザーに情報を共有しましょう
+
+#### :bulb:ページの編集方法が分からないときは [Sandbox](/Sandbox) を確認してみましょう!
+
+
+# :wrench:管理者の方へ ~ GROWI を作成したら~
+
+### :arrow_right: 複数人で GROWI を使いますか?
+- :heavy_check_mark: メンバーを招待しましょう!
+    - [GROWI に新しいメンバーを追加・招待する](https://docs.growi.org/ja/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
+
+### :arrow_right: GROWI の見た目はこのままで満足ですか?
+- :heavy_check_mark: GROWI の見た目をカスタイマイズしましょう!
+    - [GROWI のテーマをカスタイマイズする](/admin/customize)
+
+### :arrow_right: GROWI のセキュリティ設定は完了していますか?
+- :heavy_check_mark: GROWI のセキュリティ設定を更新しましょう!
+    - [GROWI のセキュリティ設定を更新する](/admin/security)

+ 0 - 253
apps/app/resource/locales/zh_CN/sandbox-bootstrap4.md

@@ -1,253 +0,0 @@
-# Labels
-
-<span class="badge badge-primary">Primary</span>
-<span class="badge badge-secondary">Secondary</span>
-<span class="badge badge-success">Success</span>
-<span class="badge badge-info">Info</span>
-<span class="badge badge-warning">Warning</span>
-<span class="badge badge-danger">Danger</span>
-<span class="badge badge-light text-dark">Light</span>
-<span class="badge badge-dark">Dark</span>
-
-<span class="badge badge-blue">Blue</span>
-<span class="badge badge-indigo">Indigo</span>
-<span class="badge badge-purple">Purple</span>
-<span class="badge badge-pink">Pink</span>
-<span class="badge badge-red">Red</span>
-<span class="badge badge-orange">Orange</span>
-<span class="badge badge-yellow">Yellow</span>
-<span class="badge badge-green">Green</span>
-<span class="badge badge-teal">Teal</span>
-<span class="badge badge-cyan">Cyan</span>
-
-
-# Alerts
-
-<div class="alert alert-primary" role="alert">
-  This is a primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-secondary" role="alert">
-  This is a secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-success" role="alert">
-  This is a success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-danger" role="alert">
-  This is a danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-warning" role="alert">
-  This is a warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-info" role="alert">
-  This is a info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-light text-dark" role="alert">
-  This is a light alert with <a href="#" class="alert-link text-dark">an example link</a>. Give it a click if you like.
-</div>
-<div class="alert alert-dark" role="alert">
-  This is a dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.
-</div>
-
-# Cards
-
-<div class="d-flex">
-
-<div class="mr-3">
-<div class="card text-white bg-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card bg-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card text-white bg-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-<div>
-<div class="card border-primary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-primary">
-    <h5 class="card-title">Primary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-secondary mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-secondary">
-    <h5 class="card-title">Secondary card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-success mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-success">
-    <h5 class="card-title">Success card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-danger mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-danger">
-    <h5 class="card-title">Danger card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-warning mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-warning">
-    <h5 class="card-title">Warning card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-info mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-info">
-    <h5 class="card-title">Info card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-light mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body">
-    <h5 class="card-title">Light card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-<div class="card border-dark mb-3" style="max-width: 18rem;">
-  <div class="card-header">Header</div>
-  <div class="card-body text-dark">
-    <h5 class="card-title">Dark card title</h5>
-    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
-  </div>
-</div>
-</div>
-
-</div>
-
-# Wells
-
-## Default well
-
-<div class="card card-body">Look, I'm in a well! </div>
-
-## Optional classes
-
-<div class="card card-body bg-primary text-light p-2">Look, I'm in a well! </div>
-
-# Typography
-
-## Lead body copy
-
-<p class="lead">Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus.</p>
-
-## Marked text
-
-You can use the mark tag to <mark>highlight</mark> text.
-
-## Small text
-
-<small>This line of text is meant to be treated as fine print.</small>
-
-## Alignment classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-left">Left aligned text.</p>
-    <p class="text-center">Center aligned text.</p>
-    <p class="text-right">Right aligned text.</p>
-    <p class="text-justify">Justified text.</p>
-    <p class="text-nowrap">No wrap text.</p>
-  </div>
-</div>
-
-## Transformation classes
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-lowercase">Lowercased text.</p>
-    <p class="text-uppercase">Uppercased text.</p>
-    <p class="text-capitalize">Capitalized text.</p>
-  </div>
-</div>
-
-
-# Helper classes
-
-## Contextual colors
-
-<div class="card">
-  <div class="card-body">
-    <p class="text-muted">Fusce dapibus, tellus ac cursus commodo, tortor mauris nibh.</p>
-    <p class="text-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="text-secondary">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="text-dark">Ut vel lorem aliquet, rhoncus libero at, condimentum mi. Fusce pellentesque quam nec magna maximus porta.</p>
-    <p class="text-primary">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="text-success">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="text-info">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="text-warning">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="text-danger">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>
-
-## Contextual backgrounds
-
-<div class="card">
-  <div class="card-body">
-    <p class="bg-light">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
-    <p class="bg-secondary text-white">Sed luctus venenatis tellus, in aliquam ligula scelerisque eget.</p>
-    <p class="bg-dark text-white">Ut vel lorem aliquet, rhoncus libero at, condimentum mi.</p>
-    <p class="bg-primary text-white">Nullam id dolor id nibh ultricies vehicula ut id elit.</p>
-    <p class="bg-success text-white">Duis mollis, est non commodo luctus, nisi erat porttitor ligula.</p>
-    <p class="bg-info text-white">Maecenas sed diam eget risus varius blandit sit amet non magna.</p>
-    <p class="bg-warning text-white">Etiam porta sem malesuada magna mollis euismod.</p>
-    <p class="bg-danger text-white">Donec ullamcorper nulla non metus auctor fringilla.</p>
-  </div>
-</div>

+ 169 - 0
apps/app/resource/locales/zh_CN/sandbox-bootstrap5.md

@@ -0,0 +1,169 @@
+# 1. Badges
+
+<span class="badge text-bg-primary">primary</span>  
+
+<span class="badge text-bg-secondary">secondary</span>  
+
+<span class="badge text-bg-success">success</span>  
+
+<span class="badge text-bg-danger">danger</span>  
+
+<span class="badge text-bg-warning">warning</span>  
+
+<span class="badge text-bg-info">info</span>  
+
+<span class="badge text-bg-light">light</span>  
+
+<span class="badge text-bg-dark">dark</span>  
+
+
+# 2. Alerts
+
+<div class="alert alert-primary" role="alert">
+  This is a primary alert.
+</div>
+
+<div class="alert alert-secondary" role="alert">
+  This is a secondary alert.
+</div>
+
+<div class="alert alert-success" role="alert">
+  This is a success alert.
+</div>
+
+<div class="alert alert-danger" role="alert">
+  This is a danger alert.
+</div>
+
+<div class="alert alert-warning" role="alert">
+  This is a warning alert.
+</div>
+
+<div class="alert alert-info" role="alert">
+  This is a info alert.
+</div>
+
+<div class="alert alert-light" role="alert">
+  This is a light alert.
+</div>
+
+<div class="alert alert-dark" role="alert">
+  This is a dark alert.
+</div>
+
+
+# 3. Cards
+
+<div class="card text-bg-primary mb-3" style="max-width: 50rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Primary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-secondary mb-3" style="max-width: 45rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Secondary card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-success mb-3" style="max-width: 40rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Success card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-danger mb-3" style="max-width: 35rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Danger card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-warning mb-3" style="max-width: 30rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Warning card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-info mb-3" style="max-width: 25rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Info card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-light mb-3" style="max-width: 20rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Light card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+<div class="card text-bg-dark mb-3" style="max-width: 15rem;">
+  <div class="card-header">Header</div>
+  <div class="card-body">
+    <h5 class="card-title">Dark card title</h5>
+    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
+  </div>
+</div>
+
+
+# 4. Colors
+## 背景颜色
+<p class="text-primary">Look, I'm in a well!</p>
+<p class="text-warning">Look, I'm in a well!</p>
+<p class="text-danger">Look, I'm in a well!</p>
+
+## 背景情况
+<p class="text-danger bg-primary">Look, I'm in a well!</p>
+<p class="text-primary bg-warning">Look, I'm in a well!</p>
+<p class="text-warning bg-danger">Look, I'm in a well!</p>
+
+
+# 5. Collapse
+## 显示内容
+<a class="btn btn-primary text-white" data-bs-toggle="collapse" href="#collapse-1">
+  Show content
+</a>
+
+<div class="collapse" id="collapse-1">
+  <div class="card card-body">
+
+- Content you want to display
+  - Content you want to display
+      
+  </div>
+</div>
+
+## 隐藏内容
+<a class="btn btn-secondary text-white" data-bs-toggle="collapse" href="#collapse-2">
+  Hide content
+</a>
+
+<div class="collapse show" id="collapse-2">
+  <div class="card card-body">
+
+- Content you want to hide
+  - Content you want to hide
+
+  </div>
+</div>
+
+
+# 官方文件
+- [点击此处了解徽章详情](https://getbootstrap.com/docs/5.3/components/badge/)
+- [单击此处了解警报详情](https://getbootstrap.com/docs/5.3/components/alerts/)
+- [点击此处了解贺卡详情](https://getbootstrap.com/docs/5.3/components/card/)
+- [点击此处了解颜色详情](https://getbootstrap.com/docs/5.3/utilities/colors/)
+- [点击此处查看折叠详情](https://getbootstrap.com/docs/5.3/components/collapse/)

+ 7 - 10
apps/app/resource/locales/zh_CN/sandbox-diagrams.md

@@ -1,4 +1,4 @@
-# :pencil: diagrams.net(former Draw.io)
+# :pencil: diagrams.net(Draw.io)
 
 See [diagrams.net](https://diagrams.net)
 
@@ -15,7 +15,7 @@ See [diagrams.net](https://diagrams.net)
 ```
 
 
-## AWS diagram
+## AWS configuration diagram
 
 ``` drawio
 3Zhdb5swFIZ/TS4XYRswuUzSr0mtVqmtejkZOASvgJHtfO3Xz+YjgdJqiaa1SbnBvD7G9vv4IJsRmeeba8nK9E7EkI2wE29G5GKEMfa9wNyssq0VhFyvVhaSx422Fx74b2hEp1GXPAbVC9RCZJqXfTESRQGR7mlMSrHuhyUi6/dasgUMhIeIZUP1mcc6rdUA071+A3yRtj0jf1LX5KwNbmaiUhaLdUcilyMyl0LoupRv5pBZ91pf6nZX79TuBiah0G80eFIgf4S/rCfYyVhowFRBI+xFIi9FYZthr3WvVaqYGxZy2+xRsugFpCndPN7dmtu0LJtuMxZBaswE2Te4HR7ezXA3cqW3ravGi9IW883CrpsxWyt3nIuQ24BZwrNsLjIhq2CSJOBHkdGVluIFOjUxnYSOY2pWIDU30G7tPO+F4pqLwsSEQmuRmwCmynp1JHwDZoizerS2HWzeNRR1JnENIgcttyakafANuQ3aZnG7Ph37vk8d6pAgoBO3rl131k3TIO0smVZjjZGLXUd7mKbQ2Ng+dvD+M+6n7xatUqDVgXTJ8XQVGZLFlJqEeYtsUl2fRRb7Y+QEJCCIBhQ5ExL0OBPHOReyqsre6VKnRjM+Vu4dxtg9nnEkFgXXYgh6ThFBV6cHmgRj10XUo9jByA1c90vk8/TeJvQ107Bm2wNpe8fTZiX/uWg6GRD3psSZeadH/C+p7RNvTAhxzaedUuoFwbkgf34w4i3Lw5gdSNw/nnhWvf9nsiyimtWBH/TCjPSzgCP/FXH3SwC/YJqFTMGBsOnxsONtwXIRh0PK1q/Z5PRymzgni3qwfW86X7FsCS113KcSLeXKWnNhd7hQxFN7nNlnk1GuuO2yqo+ZSqtg9BYXPwogTHYuQzw49Lzy2AxELGUEnc28OXgxuQA93AF2SEjIzB5j1X/7EdYNfJqcuU/uB/nUnpfP1ijvo4xC52SUNzTK/yij8DkZ5Q+Nov/HKPO4/2lT1XX+fZHLPw==
@@ -27,8 +27,7 @@ See [diagrams.net](https://diagrams.net)
 
 See [PlantUML](http://plantuml.com/).
 
-## Sequence diagram
-
+## Sequence Diagram
 ``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
@@ -63,7 +62,6 @@ deactivate A
 
 
 ## Class diagram
-
 ``` plantuml
 @startuml
 
@@ -155,7 +153,7 @@ State3 --> [*] : Aborted
 
 # :pencil: Mermaid
 
-## Pie chart diagram
+## Pie graph
 
 ```mermaid
 %%{init: {"pie": {"textPosition": 0.5}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
@@ -167,7 +165,7 @@ pie showData
     "Iron" :  5
 ```
 
-## Gantt diagram
+## Gantt chart
 
 ```mermaid
 gantt
@@ -181,7 +179,7 @@ gantt
     another task      : 24d
 ```
 
-## Gitgraph diagram
+## Git tree diagram
 
 ```mermaid
 %%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'rotateCommitLabel': true}} }%%
@@ -202,14 +200,13 @@ gitGraph
   commit
 ```
 
-## Mindmap diagram
+## Mind map
 
 ```mermaid
 mindmap
   root((mindmap))
     Origins
       Long history
-      ::icon(fa fa-book)
       Popularisation
         British popular psychology author Tony Buzan
     Research

+ 110 - 389
apps/app/resource/locales/zh_CN/sandbox.md

@@ -1,439 +1,160 @@
-# :memo: Table of Contents
+# 什么是沙盒?
+- 在本页中,您可以找到帮助您掌握 GROWI 的技巧。
+- 您可以在此层级下的参考资料中丰富您的网页内容
 
-Add `ToC` after some `#` signs.
-`Table of Contents` or `Table-of-Contents` is also OK.
 
-```
-# ToC
-```
-
-## ToC
-
-# :memo: Block Elements
-
-## Headers
+# :closed_book:标题和段落
+- 通过插入标题和段落,可以使页面上的文字更易于阅读
 
-Add one `#` per level at the start of the line
+## 标题
+- 在标题文字前添加 `#` 以创建标题 
+    - 在 "视图 "屏幕中,标题的字体大小会因 "#"的数量而异 
+    - 查看右侧的 "视图 "屏幕,了解标题的效果
+- `#`的数量将决定层次结构的级别,并帮助您组织内容
 
 ```
-# Header 1
-## Header 2
-### Header 3
-#### Header 4
-##### Header 5
-###### Header 6
+# 一级标题
+## 二级标题
+### 三级标题
+#### 第四级标题
+##### 第五级标题
+###### 第六级标题
 ```
 
-### Header 3
+### 断句
+- 在要换行的句子末尾插入两个半宽空格
+    - 您也可以在 "设置 "中进行更改,使换行不使用半宽空格
+        - 在管理页面的 "Markdown 设置 "部分更改换行设置
 
-#### Header 4
+#### 无换行
+段落 1
+第 2 段
 
-##### Header 5
+#### 有换行符
+段落 1  
+第 2 段
 
-###### Header 6
+## 段落
+- 在文本中插入空白表格即可创建段落
+- 可将段落分成若干句子,使其更易于阅读
 
-## Block paragraph
+#### 无段落
+段落 1  
+第 2 段
 
-Pararaphs are created by inserting a newline character
-A paragraph can be created by pressing Enter at the end of the previous paragraph.
+#### 有段落
+第 1 段  
 
-```
-paragraph1
-(Blank line)
-paragraph2
-```
+第 2 段
 
-paragraph1
 
-paragraph2
+# :green_book: 文本样式
+- 可以使用各种样式来丰富句子的文字表达方式
+    - 选择 "编辑 "屏幕底部的工具栏图标,也可以轻松应用这些样式
 
-## Br new line
+##斜体
+- 用星号`*`或下划线`_`括住文本。
 
-Add two spaces before break.
-***This behaviour can be modified in the options menu.***
+#### 示例
+- 这句话用*斜体*表示强调
+- 这句话用 _Italic_ 表示强调 
 
-```
-hoge
-fuga(two spaces)
-piyo
-```
+## 粗体
+- 用两个星号`*`或两个下划线`_`括住文本。
 
-hoge
-fuga
-piyo
+#### 示例
+- 这句话用 ** 粗体** 表示强调 
+- 这句话用__粗体__表示强调
 
-## Blockquotes
+## 斜体和粗体
+- 用三个星号`*`或三个下划线`_`括起来
 
-Add one `>` per level at the start of the line
+#### 示例
+- 本句用***斜体和粗体***表示强调
+- 本句用____斜体和粗体____表示强调
 
-```
-> quote
-> quote
->> nested quotes
-```
 
-> quote
-> quote
->> nested quotes
+# :orange_book: 插入列表
+## 缩略图列表
+- 用连字符 `-`、加号 `+` 或星号 `*` 开头一行,插入一个项目符号列表
 
-## Code
+#### 示例
+- 这句话出现在项目符号列表中
+    - 这句话出现在项目符号列表中
+        - 这句话出现在项目符号列表中
+        - 这句话出现在项目符号列表中
+- 此句出现在项目符号列表中
+    - 此句子出现在项目符号列表中
 
-Wrap code with three back quotes or tildes.
+## 编号列表
+- 在行首添加 `Number.` 以插入编号列表
+- 编号列表和项目符号列表也可合并使用
 
-```
-print 'hoge'
-```
+#### 示例
+1. 编号列表中有这样一句话
+    1. 编号列表中包含这句话
+    1. 该句子出现在编号表中
+    1. 此句出现在编号列表中
+        - 此句出现在项目符号列表中 
+1. 此句出现在项目符号列表中
+    - 此句出现在项目符号列表中
 
-### Syntax highlight and file name
+##任务列表
+- 通过书写 `[] ` 插入未选中复选框列表
+    - 通过书写 `[x]` 选中复选框
 
-- corresponding [highlight.js Demo](https://highlightjs.org/static/demo/) of common category
+#### 示例
+- [ ] 任务 1
+    - [x] 任务 1-1
+    - [ ] 任务 1-2
+- [x] 任务 2
 
 
-~~~
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+# :blue_book: 其他
+### 引号
+- 在段落开头加上`>`,使用引号表达式
+    - 使用`>`字符序列可表达多个引号
+- 列表和其他元素可在方括号内一起使用
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
-```
-~~~
+#### 示例
+> - 引号
+> - 引号
+>> 多个引号需要插入更多的 `>` 字符
 
-```javascript:mersenne-twister.js
-function MersenneTwister(seed) {
-  if (arguments.length == 0) {
-    seed = new Date().getTime();
-  }
+## 代码
+- 可以通过将代码添加到三个 `` `` `` 中来表达代码
 
-  this._mt = new Array(624);
-  this.setSeed(seed);
-}
+#### 示例
 ```
+在此处添加代码  
+代码中可以体现换行和段落
 
-### Inline code
-
-Words wrapped by `` `back quotes` `` will be formatted as inline code.
-
-```
-This is `Inline Code`.
+- 代码中也可使用列表
+    - 也可在代码中使用列表
 ```
 
-This is  `Inline Code`.
+## 内联代码
 
-## Pre-arranged text
 
-Code blocks should be preceded by four spaces or one tab.
 
-```
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
-```
 
-    class Hoge
-        def hoge
-            print 'hoge'
-        end
-    end
+#### 示例
+以下是内联代码 
 
-## Horizontal Line
+## 水平线
+- 用三个或三个以上连续的星号`*`或下划线`_`插入水平线
 
-Write three underscores `_`, or asterisks`*`.
-
-```
+#### 示例
+以下是水平线
 ***
-___
----
-```
 
-***
+下面是水平线
 ___
----
-
-
-
-# :memo: Typography
-
-## Strong Text
-
-### Italic
-
-To italicize text, add One asterisk or underscores before and after a word or phrase.
-
-```
-This is *Italic* .
-This is _Italic_ .
-```
-
-This is *Italic* .
-This is _Italic_ .
-
-### Bold
-
-To bold text, add two asterisks or underscores before and after a word or phrase.
-
-```
-This is **bold**.
-This is __bold__.
-```
-
-This is **bold**.
-This is __bold__.
-
-### Bold + Italic
-
-To bold and italicize text, add three asterisks or underscores before and after a word or phrase.
-
-```
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-```
-
-This is ***Italic & Bold***.
-This is ___Italic & Bold___.
-
-# :memo: Images
-
-You can insert `<img>` tag using `![description](URL)`.
-
-```markdown
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-```
-
-![Minion](https://octodex.github.com/images/minion.png)
-![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat")
-
-The size of the image can be set by using an HTML image tag
-
-```html
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-```
-
-<img src="https://octodex.github.com/images/dojocat.jpg" width="200px">
-
-
-# :memo: Link
-
-## Markdown standard
-
-You can create links using `[Display text](URL)`.
-
-```
-[Google](https://www.google.co.jp/)
-```
-
-[Google](https://www.google.co.jp/)
-
-## Pukiwiki like linker
-
-This is the most flexible linker.
-Both the page description and link address can be displayed on the page.
-
-```
-[[./Bootstrap4]]
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-```
-
-[[./Bootstrap4]]  
-Example of Bootstrap4 is [[here>./Bootstrap4]]
-
-# :memo: Lists
-
-## Ul Bulleted list
-
-To create an unordered list, add dashes (-), asterisks (*), or plus signs (+) in front of line items. 
-Items can be nested using indentation.
-
-```
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-```
-
-- List1
-    - List1_1
-        - List1_1_1
-        - List1_1_2
-    - List1_2
-- List2
-- List3
-
-## Ol Numbered List
-
-To create an ordered list, add line items with numbers followed by periods. 
-The numbers don’t have to be in numerical order, but the list should start with the number one.
-
-```
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-```
-
-1. Number list 1
-    1. Number list 1-1
-    1. Number list 1-2
-1. Number list 2
-1. Number list 3
-
-
-## Check list
-
-```
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-```
-
-- [ ] Task 1
-    - [x] Task 1.1
-    - [ ] Task 1.2
-- [x] Task2
-
-
-# :memo: Table
-
-## Markdown Standard
-
-```markdown
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-OR
-
-Left align | Right align | Center align
-:--|--:|:-:
-This       | This        | This
-column     | column      | column
-will       | will        | will
-be         | be          | be
-left       | right       | center
-aligned    | aligned     | aligned
-```
-
-| Left align | Right align | Center align |
-|:-----------|------------:|:------------:|
-| This       | This        | This         |
-| column     | column      | column       |
-| will       | will        | will         |
-| be         | be          | be           |
-| left       | right       | center       |
-| aligned    | aligned     | aligned      |
-
-## TSV
-
-~~~
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## TSV with header
-
-~~~
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-~~~
-
-``` tsv-h
-First Header	Second Header
-Content Cell	Content Cell
-Content Cell	Content Cell
-```
-
-## CSV
-
-~~~
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-## CSV with header
-
-~~~
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-~~~
-
-``` csv-h
-First Header,Second Header
-Content Cell,Content Cell
-Content Cell,Content Cell
-```
-
-
-# :memo: Footnote
-
-You can write a reference [^1] to a footnote.
-
-Long footnotes can be written as [^longnote].
-
-[^1]: A_reference_to_the_first_footnote.
-
-[^longnote]: An_example_of_writing_a_footnote_in_multiple_blocks.
-
-    Subsequent paragraphs are indented and belong to the previous footnote.
-
-
-# :memo: Emoji
-
-:smiley: :smile: :laughing: :innocent: :drooling_face:
-
-:family: :man-boy: :man-girl: :man-girl-girl: :woman-girl-girl:
-
-:+1: :-1: :open_hands: :raised_hands: :point_right:
-
-:apple: :green_apple: :strawberry: :cake: :hamburger:
-
-:basketball: :football: :baseball: :volleyball: :8ball:
-
-:hearts: :broken_heart: :heartbeat: :heartpulse: :heart_decoration:
 
-:watch: :gear: :gem: :wrench: :email:
 
+# :ledger: 更多应用
+- [Bootstrap5](/Sandbox/Bootstrap5)
 
-# :heavy_plus_sign: More..
+- [Diagrams](/Sandbox/Diagrams)
 
-- Try to attach Bootstrap4 Tags?
-    - :arrow_right: [/Sandbox/Bootstrap4]
-- Try to draw Diagrams?
-    - :arrow_right: [/Sandbox/Diagrams]
-- Try to write Math Formulas?
-    - :arrow_right: [/Sandbox/Math]
+- [Math](/Sandbox/Math)

+ 46 - 59
apps/app/resource/locales/zh_CN/welcome.md

@@ -1,64 +1,51 @@
-# :tada: 欢迎来到GROWI
+# :tada: 欢迎访问 GROWI
 
-[![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
+GROWI 是面向企业和个人的维基知识库工具。  
+知识信息可以在公司、大学实验室和俱乐部中轻松共享,任何人都可以编辑创建的页面。  
 
-GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
-公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。
+随手写下你所知道的信息,然后一起编辑,可以减少团队中的隐性知识。  
+理所当然地增加每天共享的信息量!
 
-我们可以很容易地写下我们知道的东西,并一起编辑,我们可以**简化我们团队中的隐性知识(难以用语言解释的知识)**。 
-让我们每天增加信息交流。
-
-### :beginner: 如何轻松制作一个页面 
-
-- 从右上方的 "**创建**"按钮,或右下方的**铅笔图标开始。
-    - 页面标题以后可以再编辑,不用担心标题的问题。
-        - 在标题输入栏,可以用半宽的`/`(斜线)创建页面的层次。
-        - 例子)尝试输入`/category1/category2/page-title-we-want-to-create`。
-- 我们可以通过在行首添加`-`来创建一个要点。
-- 我们还可以复制和粘贴,拖放附件,如图片、PDF、Word/Excel/PowerPoint等。
-- 一旦我们完成了,按 "**更新**"按钮来发布页面。
-    - 我们也可以通过`Ctrl(⌘) + S`来保存。
-
-了解更多信息: [Create page](https://docs.growi.org/en/guide/features/create_page.html)
-
-<div class="mt-4 card border-primary">
-  <div class="card-header bg-primary text-light">
-    Tips
-  </div>
-  <div class="card-body">
-    <ul>
-      <li>Ctrl(⌘) + "/" 显示快速帮助。</li>
-      <li>你可以用 <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4编写HTML</a>.</li>
-    </ul>
-  </div>
+<div class="alert alert-primary" role="alert">
+※此页面可自由编辑,并作为维基的首页使用
 </div>
 
-# :anchor: 对于管理员来说 <small>〜如果你创建了一个Wiki〜</small>
-
-### :arrow_right: 你会和多个人一起使用Wiki吗?
-- :heavy_check_mark: 让我们邀请一些成员。
-    - [Add/invite new members to the Wiki](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#temporary-issuance-of-a-new-user)
-### :arrow_right: 与Slack合作,接收页面和评论通知。
-- :heavy_check_mark:  [Slack integration](https://docs.growi.org/en/admin-guide/management-cookbook/slack-integration/#overview)
-### :arrow_right: 你是否从另一个系统转换?
-- :heavy_check_mark: 可以从其他GROWI, esa.io, Qiita:Team导入数据。
-    -  [Import Data](https://docs.growi.org/en/admin-guide/management-cookbook/import.html)
-
-了解更多信息: [Admin Guide](https://docs.growi.org/en/admin-guide/)
-
-
-# 内容列表示例
-
-你可以用一个表格和`$lsx`来显示内容列表。
-
-| 所有页面列表(前15页)      | [/Sandbox] 下级页面列表 |
-| ---------------------------| ------------------------|
-| $lsx(/,num=15)             | $lsx(/Sandbox)          |
-
-# Slack
-
-<a href="https://communityinviter.com/apps/wsgrowi/invite/">join our Slack team</a>
-
-我们欢迎新人加入我们的slack频道,帮助改善Growi。
-除了讨论发展问题,我们也很乐意在你加入时回答你的问题。
+# :beginner: 使用 GROWI 可以做什么
+## 1. 可以创建页面来存储信息和知识。
+- 如何创建和编辑页面
+    - 可以通过屏幕左上角的 "铅笔图标 "创建新页面。
+    - 点击屏幕右上角的 "编辑 "可编辑已创建的页面。
+- 如何管理页面
+    - GROWI 采用 "分层结构 "管理页面。
+        - ` /page a/page b/page c ` 结构类似。
+    - 除了 "层次结构",还可以通过 "标签 "来管理页面。
+
+## 2. 检索信息和知识的方式有以下几种。
+- 关键词搜索
+- 使用各种侧边栏进行搜索。
+    - 按 "页面树 "搜索。
+    - 按 "最新更改 "搜索。
+    - 按 "标签 "搜索 例如...
+
+## 3. 信息和知识可以很容易地在内部和外部共享。
+- 内部成员可通过发送页面的 URL 或 URL 链接获得通知。
+    - 使用 "用户组 "可以管理公司成员内部的查看权限。
+- 还可以允许未持有账户的外部用户查看页面。
+    - 使用 "共享链接 "与公司以外的用户共享信息。
+
+#### :bulb:如果不确定如何编辑页面,请选中 [Sandbox](/Sandbox)!
+
+
+# :wrench:针对管理员 - GROWI 创建后
+
+### :arrow_right: 您是否与多人一起使用 GROWI?
+- :heavy_check_mark: 邀请您的会员
+    - [添加和邀请新成员加入 GROWI](https://docs.growi.org/en/admin-guide/management-cookbook/user-management.html#%E6%96%B0%E8%A6%8F%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E3%81%AE%E4%BB%AE%E7%99%BA%E8%A1%8C)
+
+### :arrow_right: 您对 GROWI 现在的样子满意吗?
+- :heavy_check_mark: 定制 GROWI 的外观和感觉!
+    - [定制 GROWI 主题](/admin/customize)
+
+### :arrow_right: GROWI 安全配置是否已完成?
+- :heavy_check_mark: 更新您的 GROWI 安全设置!
+    - [更新 GROWI 安全设置](/admin/security)

+ 1 - 1
apps/app/src/client/models/MarkdownTable.js

@@ -1,5 +1,5 @@
 import csvToMarkdown from 'csv-to-markdown-table';
-import markdownTable from 'markdown-table';
+import { markdownTable } from 'markdown-table';
 import stringWidth from 'string-width';
 
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83

+ 10 - 0
apps/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -32,6 +32,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       currentPageRecursiveDeletionAuthority: PageRecursiveDeleteConfigValue.Inherit,
       currentPageCompleteDeletionAuthority: PageSingleDeleteCompConfigValue.AdminOnly,
       currentPageRecursiveCompleteDeletionAuthority: PageRecursiveDeleteCompConfigValue.Inherit,
+      isAllGroupMembershipRequiredForPageCompleteDeletion: true,
       previousPageRecursiveDeletionAuthority: null,
       previousPageRecursiveCompleteDeletionAuthority: null,
       expandOtherOptionsForDeletion: false,
@@ -73,6 +74,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       currentPageCompleteDeletionAuthority: generalSetting.pageCompleteDeletionAuthority,
       currentPageRecursiveDeletionAuthority: generalSetting.pageRecursiveDeletionAuthority,
       currentPageRecursiveCompleteDeletionAuthority: generalSetting.pageRecursiveCompleteDeletionAuthority,
+      isAllGroupMembershipRequiredForPageCompleteDeletion: generalSetting.isAllGroupMembershipRequiredForPageCompleteDeletion,
       isShowRestrictedByOwner: !generalSetting.hideRestrictedByOwner,
       isShowRestrictedByGroup: !generalSetting.hideRestrictedByGroup,
       isUsersHomepageDeletionEnabled: generalSetting.isUsersHomepageDeletionEnabled,
@@ -154,6 +156,13 @@ export default class AdminGeneralSecurityContainer extends Container {
     this.setState({ currentPageRecursiveCompleteDeletionAuthority: val });
   }
 
+  /**
+   * Switch isAllGroupMembershipRequiredForPageCompleteDeletion
+   */
+  switchIsAllGroupMembershipRequiredForPageCompleteDeletion() {
+    this.setState({ isAllGroupMembershipRequiredForPageCompleteDeletion: !this.state.isAllGroupMembershipRequiredForPageCompleteDeletion });
+  }
+
   /**
    * Change previousPageRecursiveDeletionAuthority
    */
@@ -225,6 +234,7 @@ export default class AdminGeneralSecurityContainer extends Container {
       pageCompleteDeletionAuthority: this.state.currentPageCompleteDeletionAuthority,
       pageRecursiveDeletionAuthority: this.state.currentPageRecursiveDeletionAuthority,
       pageRecursiveCompleteDeletionAuthority: this.state.currentPageRecursiveCompleteDeletionAuthority,
+      isAllGroupMembershipRequiredForPageCompleteDeletion: this.state.isAllGroupMembershipRequiredForPageCompleteDeletion,
       hideRestrictedByGroup: !this.state.isShowRestrictedByGroup,
       hideRestrictedByOwner: !this.state.isShowRestrictedByOwner,
       isUsersHomepageDeletionEnabled: this.state.isUsersHomepageDeletionEnabled,

+ 2 - 0
apps/app/src/client/services/create-page/index.ts

@@ -0,0 +1,2 @@
+export * from './use-create-page-and-transit';
+export * from './use-create-template-page';

+ 110 - 0
apps/app/src/client/services/create-page/use-create-page-and-transit.tsx

@@ -0,0 +1,110 @@
+import { useCallback, useState } from 'react';
+
+import { useRouter } from 'next/router';
+
+import { createPage, exist } from '~/client/services/page-operation';
+import type { IApiv3PageCreateParams } from '~/interfaces/apiv3';
+import { useCurrentPagePath } from '~/stores/page';
+import { EditorMode, useEditorMode } from '~/stores/ui';
+import loggerFactory from '~/utils/logger';
+
+const logger = loggerFactory('growi:Navbar:GrowiContextualSubNavigation');
+
+/**
+ * Invoked when creation and transition has finished
+ */
+type OnCreated = () => void;
+/**
+ * Invoked when either creation or transition has aborted
+ */
+type OnAborted = () => void;
+/**
+ * Always invoked after processing is terminated
+ */
+type OnTerminated = () => void;
+
+type CreatePageAndTransitOpts = {
+  shouldCheckPageExists?: boolean,
+  onCreationStart?: OnCreated,
+  onCreated?: OnCreated,
+  onAborted?: OnAborted,
+  onTerminated?: OnTerminated,
+}
+
+type CreatePageAndTransit = (
+  params: IApiv3PageCreateParams,
+  opts?: CreatePageAndTransitOpts,
+) => Promise<void>;
+
+type UseCreatePageAndTransit = () => {
+  isCreating: boolean,
+  createAndTransit: CreatePageAndTransit,
+};
+
+export const useCreatePageAndTransit: UseCreatePageAndTransit = () => {
+
+  const router = useRouter();
+
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { mutate: mutateEditorMode } = useEditorMode();
+
+  const [isCreating, setCreating] = useState(false);
+
+  const createAndTransit: CreatePageAndTransit = useCallback(async(params, opts = {}) => {
+    const {
+      shouldCheckPageExists,
+      onCreationStart, onCreated, onAborted, onTerminated,
+    } = opts;
+
+    // check the page existence
+    if (shouldCheckPageExists && params.path != null) {
+      const pagePath = params.path;
+
+      try {
+        const { isExist } = await exist(pagePath);
+
+        if (isExist) {
+          // routing
+          if (pagePath !== currentPagePath) {
+            await router.push(`${pagePath}#edit`);
+          }
+          mutateEditorMode(EditorMode.Editor);
+          onAborted?.();
+          return;
+        }
+      }
+      catch (err) {
+        throw err;
+      }
+      finally {
+        onTerminated?.();
+      }
+    }
+
+    // create and transit
+    try {
+      setCreating(true);
+      onCreationStart?.();
+
+      const response = await createPage(params);
+
+      await router.push(`/${response.page._id}#edit`);
+      mutateEditorMode(EditorMode.Editor);
+
+      onCreated?.();
+    }
+    catch (err) {
+      throw err;
+    }
+    finally {
+      onTerminated?.();
+      setCreating(false);
+    }
+
+  }, [currentPagePath, mutateEditorMode, router]);
+
+  return {
+    isCreating,
+    createAndTransit,
+  };
+};

+ 40 - 0
apps/app/src/client/services/create-page/use-create-template-page.ts

@@ -0,0 +1,40 @@
+import { useCallback } from 'react';
+
+import { Origin } from '@growi/core';
+import { isCreatablePage } from '@growi/core/dist/utils/page-path-utils';
+import { normalizePath } from '@growi/core/dist/utils/path-utils';
+
+import type { LabelType } from '~/interfaces/template';
+import { useCurrentPagePath } from '~/stores/page';
+
+
+import { useCreatePageAndTransit } from './use-create-page-and-transit';
+
+type UseCreateTemplatePage = () => {
+  isCreatable: boolean,
+  isCreating: boolean,
+  createTemplate?: (label: LabelType) => Promise<void>,
+}
+
+export const useCreateTemplatePage: UseCreateTemplatePage = () => {
+
+  const { data: currentPagePath, isLoading: isLoadingPagePath } = useCurrentPagePath();
+
+  const { isCreating, createAndTransit } = useCreatePageAndTransit();
+  const isCreatable = currentPagePath != null && isCreatablePage(normalizePath(`${currentPagePath}/_template`));
+
+  const createTemplate = useCallback(async(label: LabelType) => {
+    if (isLoadingPagePath || !isCreatable) return;
+
+    return createAndTransit(
+      { path: normalizePath(`${currentPagePath}/${label}`), wip: false, origin: Origin.View },
+      { shouldCheckPageExists: true },
+    );
+  }, [currentPagePath, isCreatable, isLoadingPagePath, createAndTransit]);
+
+  return {
+    isCreatable,
+    isCreating,
+    createTemplate: isCreatable ? createTemplate : undefined,
+  };
+};

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

+ 32 - 92
apps/app/src/client/services/page-operation.ts

@@ -1,20 +1,24 @@
 import { useCallback } from 'react';
 
-import { SubscriptionStatusType, type Nullable } from '@growi/core';
+import type { IPageHasId } from '@growi/core';
+import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 
-import { OptionsToSave } from '~/interfaces/page-operation';
-import { useEditingMarkdown, useIsEnabledUnsavedWarning, usePageTagsForEditors } from '~/stores/editor';
+import type {
+  IApiv3PageCreateParams, IApiv3PageCreateResponse, IApiv3PageUpdateParams, IApiv3PageUpdateResponse,
+} from '~/interfaces/apiv3';
+import { useEditingMarkdown, usePageTagsForEditors } from '~/stores/editor';
 import { useCurrentPageId, useSWRMUTxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
 import { apiPost } from '../util/apiv1-client';
-import { apiv3Post, apiv3Put } from '../util/apiv3-client';
+import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { toastError } from '../util/toastr';
 
 const logger = loggerFactory('growi:services:page-operation');
 
+
 export const toggleSubscribe = async(pageId: string, currentStatus: SubscriptionStatusType | undefined): Promise<void> => {
   try {
     const newStatus = currentStatus === SubscriptionStatusType.SUBSCRIBE
@@ -87,91 +91,9 @@ export const resumeRenameOperation = async(pageId: string): Promise<void> => {
   await apiv3Post('/pages/resume-rename', { pageId });
 };
 
-// TODO: define return type
-const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
-  // clone
-  const params = Object.assign(tmpParams, {
-    path: pagePath,
-    body: markdown,
-  });
-
-  const res = await apiv3Post('/pages/', params);
-  const { page, tags, revision } = res.data;
-
-  return { page, tags, revision };
-};
-
-// TODO: define return type
-const updatePage = async(pageId: string, revisionId: string, markdown: string, tmpParams: OptionsToSave) => {
-  // clone
-  const params = Object.assign(tmpParams, {
-    page_id: pageId,
-    revision_id: revisionId,
-    body: markdown,
-  });
-
-  const res: any = await apiPost('/pages.update', params);
-  if (!res.ok) {
-    throw new Error(res.error);
-  }
-  return res;
-};
-
-type PageInfo= {
-  path: string,
-  pageId: Nullable<string>,
-  revisionId: Nullable<string>,
-}
-
-type SaveOrUpdateFunction = (markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) => any;
-
-// TODO: define return type
-export const useSaveOrUpdate = (): SaveOrUpdateFunction => {
-  /* eslint-disable react-hooks/rules-of-hooks */
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
-  /* eslint-enable react-hooks/rules-of-hooks */
-
-  return useCallback(async(markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) => {
-    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) {
-      res = await createPage(path, markdown, options);
-    }
-    else {
-      if (revisionId == null) {
-        const msg = '\'revisionId\' is required to update page';
-        throw new Error(msg);
-      }
-      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 });
-
-    return res;
-  }, [mutateIsEnabledUnsavedWarning]);
+export const createPage = async(params: IApiv3PageCreateParams): Promise<IApiv3PageCreateResponse> => {
+  const res = await apiv3Post<IApiv3PageCreateResponse>('/page', params);
+  return res.data;
 };
 
 export type UpdateStateAfterSaveOption = {
@@ -198,7 +120,7 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
     await mutateCurrentPageId(pageId);
     const updatedPage = await mutateCurrentPage();
 
-    if (updatedPage == null) { return }
+    if (updatedPage == null || updatedPage.revision == null) { return }
 
     // supress to mutate only when updated from built-in editor
     // and see: https://github.com/weseek/growi/pull/7118
@@ -212,8 +134,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 +145,23 @@ export const useUpdateStateAfterSave = (pageId: string|undefined|null, opts?: Up
 export const unlink = async(path: string): Promise<void> => {
   await apiPost('/pages.unlink', { path });
 };
+
+
+interface PageExistResponse {
+  isExist: boolean,
+}
+
+export const exist = async(path: string): Promise<PageExistResponse> => {
+  const res = await apiv3Get<PageExistResponse>('/page/exist', { path });
+  return res.data;
+};
+
+export const publish = async(pageId: string): Promise<IPageHasId> => {
+  const res = await apiv3Put(`/page/${pageId}/publish`);
+  return res.data;
+};
+
+export const unpublish = async(pageId: string): Promise<IPageHasId> => {
+  const res = await apiv3Put(`/page/${pageId}/unpublish`);
+  return res.data;
+};

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

@@ -1,15 +1,16 @@
 import { useCallback, useEffect } from 'react';
 
-import EventEmitter from 'events';
+import type EventEmitter from 'events';
 
+import { Origin } from '@growi/core';
 import type { DrawioEditByViewerProps } from '@growi/remark-drawio';
 
-import { useSaveOrUpdate } from '~/client/services/page-operation';
-import mdu from '~/components/PageEditor/MarkdownDrawioUtil';
-import type { OptionsToSave } from '~/interfaces/page-operation';
+import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
+import { replaceDrawioInMarkdown } from '~/components/Page/markdown-drawio-util-for-view';
 import { useShareLinkId } from '~/stores/context';
-import { useDrawioModal } from '~/stores/modal';
-import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
+import { useConflictDiffModal, useDrawioModal } from '~/stores/modal';
+import { useSWRxCurrentPage } from '~/stores/page';
+import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
 
@@ -30,51 +31,70 @@ 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 { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModal();
 
-  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
-    if (currentPage == null || tagsInfo == null || shareLinkId != null) {
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
+
+  // eslint-disable-next-line max-len
+  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
     }
 
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = mdu.replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
-
-    const grantUserGroupIds = currentPage.grantedGroups.map((g) => {
-      return {
-        type: g.type,
-        item: g.item._id,
-      };
-    });
-
-    const optionsToSave: OptionsToSave = {
-      isSlackEnabled: false,
-      slackChannels: '',
-      grant: currentPage.grant,
-      grantUserGroupIds,
-      pageTags: tagsInfo.tags,
-    };
-
     try {
-      const currentRevisionId = currentPage.revision._id;
-      await saveOrUpdate(
-        newMarkdown,
-        { pageId: currentPage._id, path: currentPage.path, revisionId: currentRevisionId },
-        optionsToSave,
-      );
-
+      await _updatePage({
+        pageId: currentPage._id,
+        revisionId,
+        body: newMarkdown,
+        origin: Origin.View,
+      });
+
+      closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
     catch (error) {
+      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
+      if (remoteRevidsionData != null) {
+        onConflict(remoteRevidsionData, newMarkdown);
+      }
+
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, saveOrUpdate, shareLinkId, tagsInfo]);
+  }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
+
+  // eslint-disable-next-line max-len
+  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    return async(newMarkdown: string) => {
+      await updatePage(revisionId, newMarkdown, onConflict);
+    };
+  }, [updatePage]);
+
+  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+    setRemoteLatestPageData(remoteRevidsionData);
+
+    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
+    if (resolveConflictHandler == null) {
+      return;
+    }
+
+    openConflictDiffModal(newMarkdown, resolveConflictHandler);
+  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+
+  const saveByDrawioModal = useCallback(async(drawioMxFile: string, bol: number, eol: number) => {
+    if (currentPage == null || currentPage.revision == null) {
+      return;
+    }
+
+    const currentRevisionId = currentPage.revision._id;
+    const currentMarkdown = currentPage.revision.body;
+    const newMarkdown = replaceDrawioInMarkdown(drawioMxFile, currentMarkdown, bol, eol);
 
+    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+  }, [currentPage, onConflictHandler, updatePage]);
 
   // set handler to open DrawioModal
   useEffect(() => {

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

@@ -1,33 +0,0 @@
-import { useCallback, useEffect } from 'react';
-
-import { SocketEventName } from '~/interfaces/websocket';
-import { useIsHackmdDraftUpdatingInRealtime } from '~/stores/hackmd';
-import { useCurrentPageId } from '~/stores/page';
-import { useGlobalSocket } from '~/stores/websocket';
-
-export const useHackmdDraftUpdatedEffect = (): void => {
-
-  const { data: currentPageId } = useCurrentPageId();
-  const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
-
-  const { data: socket } = useGlobalSocket();
-
-  const setIsHackmdDraftUpdatingInRealtime = useCallback((data) => {
-    const { s2cMessagePageUpdated } = data;
-    if (s2cMessagePageUpdated.pageId === currentPageId) {
-      mutateIsHackmdDraftUpdatingInRealtime(true);
-    }
-  }, [currentPageId, mutateIsHackmdDraftUpdatingInRealtime]);
-
-  // listen socket for hackmd saved
-  useEffect(() => {
-
-    if (socket == null) { return }
-
-    socket.on(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
-
-    return () => {
-      socket.off(SocketEventName.EditingWithHackmd, setIsHackmdDraftUpdatingInRealtime);
-    };
-  }, [setIsHackmdDraftUpdatingInRealtime, socket]);
-};

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

@@ -1,14 +1,16 @@
 import { useCallback, useEffect } from 'react';
 
-import EventEmitter from 'events';
+import type EventEmitter from 'events';
 
-import MarkdownTable from '~/client/models/MarkdownTable';
-import { useSaveOrUpdate } from '~/client/services/page-operation';
-import mtu from '~/components/PageEditor/MarkdownTableUtil';
-import type { OptionsToSave } from '~/interfaces/page-operation';
+import { Origin } from '@growi/core';
+
+import type MarkdownTable from '~/client/models/MarkdownTable';
+import { extractRemoteRevisionDataFromErrorObj, updatePage as _updatePage } from '~/client/services/update-page';
+import { getMarkdownTableFromLine, replaceMarkdownTableInMarkdown } from '~/components/Page/markdown-table-util-for-view';
 import { useShareLinkId } from '~/stores/context';
-import { useHandsontableModal } from '~/stores/modal';
-import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
+import { useHandsontableModal, useConflictDiffModal } from '~/stores/modal';
+import { useSWRxCurrentPage } from '~/stores/page';
+import { type RemoteRevisionData, useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
 import loggerFactory from '~/utils/logger';
 
 
@@ -29,51 +31,70 @@ 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 { open: openConflictDiffModal, close: closeConflictDiffModal } = useConflictDiffModal();
 
-  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
-    if (currentPage == null || tagsInfo == null || shareLinkId != null) {
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
+
+  // eslint-disable-next-line max-len
+  const updatePage = useCallback(async(revisionId:string, newMarkdown: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    if (currentPage == null || currentPage.revision == null || shareLinkId != null) {
       return;
     }
 
-    const currentMarkdown = currentPage.revision.body;
-    const newMarkdown = mtu.replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
-
-    const grantUserGroupIds = currentPage.grantedGroups.map((g) => {
-      return {
-        type: g.type,
-        item: g.item._id,
-      };
-    });
-
-    const optionsToSave: OptionsToSave = {
-      isSlackEnabled: false,
-      slackChannels: '',
-      grant: currentPage.grant,
-      grantUserGroupIds,
-      pageTags: tagsInfo.tags,
-    };
-
     try {
-      const currentRevisionId = currentPage.revision._id;
-      await saveOrUpdate(
-        newMarkdown,
-        { pageId: currentPage._id, path: currentPage.path, revisionId: currentRevisionId },
-        optionsToSave,
-      );
-
+      await _updatePage({
+        pageId: currentPage._id,
+        revisionId,
+        body: newMarkdown,
+        origin: Origin.View,
+      });
+
+      closeConflictDiffModal();
       opts?.onSaveSuccess?.();
     }
     catch (error) {
+      const remoteRevidsionData = extractRemoteRevisionDataFromErrorObj(error);
+      if (remoteRevidsionData != null) {
+        onConflict?.(remoteRevidsionData, newMarkdown);
+      }
+
       logger.error('failed to save', error);
       opts?.onSaveError?.(error);
     }
-  }, [currentPage, opts, saveOrUpdate, shareLinkId, tagsInfo]);
+  }, [closeConflictDiffModal, currentPage, opts, shareLinkId]);
+
+  // eslint-disable-next-line max-len
+  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: (conflictData: RemoteRevisionData, newMarkdown: string) => void) => {
+    return async(newMarkdown: string) => {
+      await updatePage(revisionId, newMarkdown, onConflict);
+    };
+  }, [updatePage]);
+
+  const onConflictHandler = useCallback((remoteRevidsionData: RemoteRevisionData, newMarkdown: string) => {
+    setRemoteLatestPageData(remoteRevidsionData);
+
+    const resolveConflictHandler = generateResolveConflictHandler(remoteRevidsionData.remoteRevisionId, onConflictHandler);
+    if (resolveConflictHandler == null) {
+      return;
+    }
 
+    openConflictDiffModal(newMarkdown, resolveConflictHandler);
+  }, [generateResolveConflictHandler, openConflictDiffModal, setRemoteLatestPageData]);
+
+  const saveByHandsontableModal = useCallback(async(table: MarkdownTable, bol: number, eol: number) => {
+    if (currentPage == null || currentPage.revision == null) {
+      return;
+    }
+
+    const currentRevisionId = currentPage.revision._id;
+    const currentMarkdown = currentPage.revision.body;
+    const newMarkdown = replaceMarkdownTableInMarkdown(table, currentMarkdown, bol, eol);
+
+    await updatePage(currentRevisionId, newMarkdown, onConflictHandler);
+  }, [currentPage, onConflictHandler, updatePage]);
 
   // set handler to open HandsonTableModal
   useEffect(() => {
@@ -82,9 +103,11 @@ export const useHandsontableModalLauncherForView = (opts?: {
     }
 
     const handler = (bol: number, eol: number) => {
+      if (currentPage.revision == null) return;
+
       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);
 

+ 34 - 14
apps/app/src/client/services/side-effects/page-updated.ts

@@ -1,45 +1,65 @@
 import { useCallback, useEffect } from 'react';
 
+import { useGlobalSocket } from '@growi/core/dist/swr';
+
 import { SocketEventName } from '~/interfaces/websocket';
-import { useCurrentPageId } from '~/stores/page';
-import { useSetRemoteLatestPageData } from '~/stores/remote-latest-page';
-import { useGlobalSocket } from '~/stores/websocket';
+import { usePageStatusAlert } from '~/stores/alert';
+import { useSWRxCurrentPage, useSWRMUTxCurrentPage } from '~/stores/page';
+import { useSetRemoteLatestPageData, type RemoteRevisionData } from '~/stores/remote-latest-page';
+import { useEditorMode, EditorMode } from '~/stores/ui';
+
 
 export const usePageUpdatedEffect = (): void => {
 
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
   const { data: socket } = useGlobalSocket();
-  const { data: currentPageId } = useCurrentPageId();
+  const { data: editorMode } = useEditorMode();
+  const { data: currentPage } = useSWRxCurrentPage();
+  const { trigger: mutateCurrentPage } = useSWRMUTxCurrentPage();
+  const { open: openPageStatusAlert, close: closePageStatusAlert } = usePageStatusAlert();
 
-  const setLatestRemotePageData = useCallback((data) => {
+  const remotePageDataUpdateHandler = useCallback((data) => {
+    // Set remote page 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) {
+    if (currentPage?._id != null && currentPage._id === s2cMessagePageUpdated.pageId) {
       setRemoteLatestPageData(remoteData);
-    }
 
-  }, [currentPageId, setRemoteLatestPageData]);
+      // Open PageStatusAlert
+      const currentRevisionId = currentPage?.revision?._id;
+      const remoteRevisionId = s2cMessagePageUpdated.revisionId;
+      const isRevisionOutdated = (currentRevisionId != null || remoteRevisionId != null) && currentRevisionId !== remoteRevisionId;
+
+      // !!CAUTION!! Timing of calling openPageStatusAlert may clash with components/PageEditor/conflict.tsx
+      if (isRevisionOutdated && editorMode === EditorMode.View) {
+        openPageStatusAlert({ hideEditorMode: EditorMode.Editor, onRefleshPage: mutateCurrentPage });
+      }
+
+      // Clear cache
+      if (!isRevisionOutdated) {
+        closePageStatusAlert();
+      }
+    }
+  }, [currentPage?._id, currentPage?.revision?._id, editorMode, mutateCurrentPage, openPageStatusAlert, closePageStatusAlert, setRemoteLatestPageData]);
 
   // listen socket for someone updating this page
   useEffect(() => {
 
     if (socket == null) { return }
 
-    socket.on(SocketEventName.PageUpdated, setLatestRemotePageData);
+    socket.on(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
 
     return () => {
-      socket.off(SocketEventName.PageUpdated, setLatestRemotePageData);
+      socket.off(SocketEventName.PageUpdated, remotePageDataUpdateHandler);
     };
 
-  }, [setLatestRemotePageData, socket]);
+  }, [remotePageDataUpdateHandler, socket]);
 };

+ 22 - 0
apps/app/src/client/services/update-page/conflict.tsx

@@ -0,0 +1,22 @@
+import type { ErrorV3 } from '@growi/core/dist/models';
+
+import { PageUpdateErrorCode } from '~/interfaces/apiv3';
+import { type RemoteRevisionData } from '~/stores/remote-latest-page';
+
+export const extractRemoteRevisionDataFromErrorObj = (errors: Array<ErrorV3>): RemoteRevisionData | undefined => {
+  for (const error of errors) {
+    if (error.code === PageUpdateErrorCode.CONFLICT) {
+
+      const latestRevision = error.args.returnLatestRevision;
+
+      const remoteRevidsionData = {
+        remoteRevisionId: latestRevision.revisionId,
+        remoteRevisionBody: latestRevision.revisionBody,
+        remoteRevisionLastUpdateUser: latestRevision.user,
+        remoteRevisionLastUpdatedAt: latestRevision.createdAt,
+      };
+
+      return remoteRevidsionData;
+    }
+  }
+};

+ 9 - 0
apps/app/src/client/services/update-page/index.ts

@@ -0,0 +1,9 @@
+import { apiv3Put } from '~/client/util/apiv3-client';
+import type { IApiv3PageUpdateParams, IApiv3PageUpdateResponse } from '~/interfaces/apiv3';
+
+export * from './conflict';
+
+export const updatePage = async(params: IApiv3PageUpdateParams): Promise<IApiv3PageUpdateResponse> => {
+  const res = await apiv3Put<IApiv3PageUpdateResponse>('/page', params);
+  return res.data;
+};

+ 18 - 0
apps/app/src/client/services/use-toastr-on-error.tsx

@@ -0,0 +1,18 @@
+import { useCallback } from 'react';
+
+import { useTranslation } from 'react-i18next';
+
+import { toastError } from '~/client/util/toastr';
+
+export const useToastrOnError = <P, R>(method?: (param?: P) => Promise<R|undefined>): (param?: P) => Promise<R|undefined> => {
+  const { t } = useTranslation('commons');
+
+  return useCallback(async(param) => {
+    try {
+      return await method?.(param);
+    }
+    catch (err) {
+      toastError(t('toaster.create_failed', { target: 'a page' }));
+    }
+  }, [method, t]);
+};

+ 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
 

+ 2 - 2
apps/app/src/client/util/bookmark-utils.ts

@@ -1,6 +1,6 @@
 import type { IRevision, Ref } from '@growi/core';
 
-import { BookmarkFolderItems } from '~/interfaces/bookmark-info';
+import type { BookmarkFolderItems } from '~/interfaces/bookmark-info';
 
 import { apiv3Delete, apiv3Post, apiv3Put } from './apiv3-client';
 
@@ -31,7 +31,7 @@ export const deleteBookmarkFolder = async(bookmarkFolderId: string): Promise<voi
 };
 
 // Rename page from bookmark item control
-export const renamePage = async(pageId: string, revisionId: Ref<IRevision>, newPagePath: string): Promise<void> => {
+export const renamePage = async(pageId: string, revisionId: Ref<IRevision> | undefined, newPagePath: string): Promise<void> => {
   await apiv3Put('/pages/rename', { pageId, revisionId, newPagePath });
 };
 

+ 0 - 51
apps/app/src/client/util/codemirror/autorefresh.ext.js

@@ -1,51 +0,0 @@
-/**
- * extends codemirror/addon/display/autorefresh
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- * @see https://codemirror.net/addon/display/autorefresh.js
- * @see https://github.com/scniro/react-codemirror2/issues/83#issuecomment-398825212
- */
-/* eslint-disable */
-
-// CodeMirror, copyright (c) by Marijn Haverbeke and others
-// Distributed under an MIT license: http://codemirror.net/LICENSE
-
-(function(mod) {
-  mod(require("codemirror"));
-})(function(CodeMirror) {
-  "use strict"
-
-  CodeMirror.defineOption("autoRefresh", false, function(cm, val) {
-    if (cm.state.autoRefresh) {
-      stopListening(cm, cm.state.autoRefresh)
-      cm.state.autoRefresh = null
-    }
-    if (val && (val.force || cm.display.wrapper.offsetHeight == 0))
-      startListening(cm, cm.state.autoRefresh = {delay: val.delay || 250})
-  })
-
-  function startListening(cm, state) {
-    function check() {
-      if (cm.display.wrapper.offsetHeight) {
-        stopListening(cm, state)
-        if (cm.display.lastWrapHeight != cm.display.wrapper.clientHeight)
-          cm.refresh()
-      } else {
-        state.timeout = setTimeout(check, state.delay)
-      }
-    }
-    state.timeout = setTimeout(check, state.delay)
-    state.hurry = function() {
-      clearTimeout(state.timeout)
-      state.timeout = setTimeout(check, 50)
-    }
-    CodeMirror.on(window, "mouseup", state.hurry)
-    CodeMirror.on(window, "keyup", state.hurry)
-  }
-
-  function stopListening(_cm, state) {
-    clearTimeout(state.timeout)
-    CodeMirror.off(window, "mouseup", state.hurry)
-    CodeMirror.off(window, "keyup", state.hurry)
-  }
-});

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

@@ -1,47 +0,0 @@
-/* eslint-disable */
-
-import mdu from '../../../components/PageEditor/MarkdownDrawioUtil.js';
-
-(function(mod) {
-  mod(require("codemirror"));
-})(function(CodeMirror) {
-  "use strict"
-
-  CodeMirror.registerGlobalHelper('fold', 'drawio', function (mode, cm) {
-    return true;
-  }, function(cm, start) {
-    function isBeginningOfDrawio(lineNo) {
-      let line = cm.getLine(lineNo);
-      let match = mdu.lineBeginPartOfDrawioRE.exec(line);
-      if (match) {
-        return true;
-      }
-      return false;
-    }
-    function isEndOfDrawio(lineNo) {
-      let line = cm.getLine(lineNo);
-      let match = mdu.lineEndPartOfDrawioRE.exec(line);
-      if (match) {
-        return true;
-      }
-      return false;
-    }
-
-    let drawio = isBeginningOfDrawio(start.line);
-    if (drawio === false) { return; }
-
-    let lastLine = cm.lastLine();
-    let end = start.line;
-    while(end < lastLine) {
-      end += 1;
-      if (isEndOfDrawio(end)) {
-        break;
-      }
-    }
-
-    return {
-      from: CodeMirror.Pos(start.line, cm.getLine(start.line).length),
-      to: CodeMirror.Pos(end, cm.getLine(end).length)
-    };
-  });
-});

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

@@ -1,19 +0,0 @@
-// https://discuss.codemirror.net/t/cm-header-margin-padding-height/75/5
-window.CodeMirror.defineMode('gfm-growi', (cmConfig, modeCfg) => {
-  // based on Markdown (GitHub-flavour) mode
-  // https://codemirror.net/doc/manual.html#option_mode
-  // https://codemirror.net/mode/index.html
-  modeCfg.name = 'gfm';
-  modeCfg.highlightFormatting = true;
-  const mode = window.CodeMirror.getMode(cmConfig, modeCfg);
-
-  const origToken = mode.token;
-  mode.token = function(stream, state) {
-    let classes = origToken(stream, state) || '';
-    // https://regex101.com/r/Fep0w2/1
-    classes = classes.replace(/(^| )header(\S*)/g, '$1header$2 line-grw-cm-header-line');
-    return /^\s*$/.test(classes) ? null : classes;
-  };
-
-  return mode;
-});

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

@@ -1,41 +0,0 @@
-import { sawCollapsedSpans } from 'codemirror/src/line/saw_special_spans';
-import { getLine } from 'codemirror/src/line/utils_line';
-import { heightAtLine, visualLineEndNo, visualLineNo } from 'codemirror/src/line/spans';
-import { DisplayUpdate } from 'codemirror/src/display/update_display';
-import { adjustView } from 'codemirror/src/display/view_tracking';
-
-class UpdateDisplayUtil {
-
-  /**
-   * Transplant 'updateDisplayIfNeeded' method to fix weseek/growi#703
-   *
-   * @see https://github.com/weseek/growi/issues/703
-   * @see https://github.com/codemirror/CodeMirror/blob/5.42.0/src/display/update_display.js#L125
-   *
-   * @param {CodeMirror} cm
-   */
-  static forceUpdateViewOffset(cm) {
-    const doc = cm.doc;
-    const display = cm.display;
-
-    const update = new DisplayUpdate(cm, cm.getViewport());
-
-    // Compute a suitable new viewport (from & to)
-    const end = doc.first + doc.size;
-    let from = Math.max(update.visible.from - cm.options.viewportMargin, doc.first);
-    let to = Math.min(end, update.visible.to + cm.options.viewportMargin);
-    if (display.viewFrom < from && from - display.viewFrom < 20) from = Math.max(doc.first, display.viewFrom);
-    if (display.viewTo > to && display.viewTo - to < 20) to = Math.min(end, display.viewTo);
-    if (sawCollapsedSpans) {
-      from = visualLineNo(cm.doc, from);
-      to = visualLineEndNo(cm.doc, to);
-    }
-    adjustView(cm, from, to);
-
-    display.viewOffset = heightAtLine(getLine(doc, display.viewFrom));
-  }
-
-}
-
-
-export default UpdateDisplayUtil;

+ 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>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">link</span>
               <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>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">link</span>
               <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>

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

@@ -34,13 +34,13 @@ 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"
             type="text"
-            defaultValue={adminAppContainer.state.title || ''}
+            value={adminAppContainer.state.title || ''}
             onChange={(e) => {
               adminAppContainer.changeTitle(e.target.value);
             }}
@@ -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>
@@ -60,7 +60,7 @@ const AppSetting = (props) => {
           <input
             className="form-control"
             type="text"
-            defaultValue={adminAppContainer.state.confidential || ''}
+            value={adminAppContainer.state.confidential || ''}
             onChange={(e) => {
               adminAppContainer.changeConfidential(e.target.value);
             }}
@@ -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>
+              <span className="material-symbols-outlined ms-1" aria-hidden="true">expand_more</span>
               <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">

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

@@ -30,9 +30,9 @@ 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" />
+        <span className="material-symbols-outlined">help</span>
         {t('Warning')}
       </ModalHeader>
       <ModalBody>
@@ -44,7 +44,7 @@ export const ConfirmModal: FC<ConfirmModalProps> = (props: ConfirmModalProps) =>
               <br />
               <span className="text-warning">
                 <>
-                  <i className="icon-exclamation icon-fw"></i>
+                  <span className="material-symbols-outlined">error</span>
                   {props.supplymentaryMessage}
                 </>
               </span>
@@ -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')}

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

@@ -30,43 +30,43 @@ 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 />
         <span className="text-danger">
-          <i className="ti ti-unlink"></i>
+          <span className="material-symbols-outlined">link_off</span>
           {t('admin:app_setting.change_setting')}
         </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">
-            <i className="icon-exclamation icon-fw">
-            </i><b>FIXED</b><br />
+          <p className="alert alert-warning mt-2 text-start offset-3 col-6">
+            <span className="material-symbols-outlined">help</span>
+            <b>FIXED</b><br />
             {/* eslint-disable-next-line react/no-danger */}
             <b dangerouslySetInnerHTML={{ __html: t('admin:app_setting.fixed_by_env_var', { fileUploadType: props.envFileUploadType }) }} />
           </p>

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

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

@@ -47,10 +47,10 @@ const MailSetting = (props: Props) => {
   return (
     <React.Fragment>
       {!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="alert alert-danger"><span className="material-symbols-outlined">error</span> {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>
           )}

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

@@ -54,12 +54,12 @@ export const MaintenanceMode: FC = () => {
         onConfirm={onConfirmHandler}
         onCancel={() => closeModal()}
       />
-      <p className="card well">
+      <p className="card custom-card">
         {t('admin:maintenance_mode.description')}
         <br />
         <br />
         <span className="text-warning">
-          <i className="icon-exclamation icon-fw"></i>
+          <span className="material-symbols-outlined">error</span>
           {t('admin:maintenance_mode.supplymentary_message_to_start')}
         </span>
       </p>

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

@@ -33,9 +33,9 @@ export default function MaskedInput(props: Props): JSX.Element {
       />
       <span onClick={togglePassword} className={styles.PasswordReveal}>
         {passwordShown ? (
-          <i className="fa fa-eye" />
+          <span className="material-symbols-outlined">visibility</span>
         ) : (
-          <i className="fa fa-eye-slash" />
+          <span className="material-symbols-outlined">visibility_off</span>
         )}
       </span>
     </div>

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

@@ -2,6 +2,7 @@ import {
   useState, useCallback, useEffect,
 } from 'react';
 
+import { LoadingSpinner } from '@growi/ui/dist/components';
 import { useTranslation } from 'next-i18next';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
@@ -49,13 +50,13 @@ 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"><span className="material-symbols-outlined">info</span>{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>
+              {t('app_setting.learn_more')} <span className="material-symbols-outlined">share</span>
             </a>
           </div>
           {t('app_setting.other_info_will_be_sent')}<br />
@@ -65,38 +66,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" />
+          <LoadingSpinner className="me-1 fs-3" />
         </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">

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