Browse Source

Merge branch 'master' into fix/cypress-test

Shun Miyazawa 2 years ago
parent
commit
b69738968f
100 changed files with 1489 additions and 9321 deletions
  1. 1 1
      .devcontainer/Dockerfile
  2. 0 1
      .github/dependabot.yml
  3. 1 1
      .github/workflows/auto-approve.yml
  4. 10 10
      .github/workflows/ci-app-prod.yml
  5. 3 3
      .github/workflows/ci-app.yml
  6. 3 3
      .github/workflows/ci-slackbot-proxy.yml
  7. 1 1
      .github/workflows/list-unhealthy-branches.yml
  8. 4 5
      .github/workflows/release-rc-scheduled.yml
  9. 0 23
      .github/workflows/release-rc.yml
  10. 2 9
      .github/workflows/release-slackbot-proxy.yml
  11. 4 29
      .github/workflows/release.yml
  12. 1 2
      .github/workflows/reusable-app-build-image.yml
  13. 4 4
      .mergify.yml
  14. 182 1
      CHANGELOG.md
  15. 1 1
      README.md
  16. 1 1
      README_JP.md
  17. 0 1
      _obsolete/packages/.eslintignore
  18. 0 1
      _obsolete/packages/hackmd/.eslintignore
  19. 0 1
      _obsolete/packages/hackmd/.gitignore
  20. 0 25
      _obsolete/packages/hackmd/package.json
  21. 0 152
      _obsolete/packages/hackmd/src/hackmd-agent.js
  22. 0 41
      _obsolete/packages/hackmd/src/hackmd-styles.ts
  23. 0 16
      _obsolete/packages/hackmd/src/index.ts
  24. 0 21
      _obsolete/packages/hackmd/src/style.scss
  25. 0 14
      _obsolete/packages/hackmd/tsconfig.json
  26. 0 30
      _obsolete/packages/hackmd/vite.config.js
  27. 0 1
      apps/app/.eslintignore
  28. 1 0
      apps/app/.eslintrc.js
  29. 0 33
      apps/app/_obsolete/src/client/services/side-effects/hackmd-draft-updated.ts
  30. 0 51
      apps/app/_obsolete/src/client/util/codemirror/autorefresh.ext.js
  31. 0 47
      apps/app/_obsolete/src/client/util/codemirror/drawio-fold.ext.js
  32. 0 19
      apps/app/_obsolete/src/client/util/codemirror/gfm-growi.mode.js
  33. 0 41
      apps/app/_obsolete/src/client/util/codemirror/update-display-util.ext.js
  34. 0 118
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.module.scss
  35. 0 141
      apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx
  36. 0 103
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.module.scss
  37. 0 55
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigation.tsx
  38. 0 41
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss
  39. 0 97
      apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  40. 0 1191
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.jsx
  41. 0 126
      apps/app/_obsolete/src/components/PageEditor/CodeMirrorEditor.module.scss
  42. 0 65
      apps/app/_obsolete/src/components/PageEditor/CommentMentionHelper.ts
  43. 0 344
      apps/app/_obsolete/src/components/PageEditor/ConflictDiffModal.tsx
  44. 0 62
      apps/app/_obsolete/src/components/PageEditor/EmojiPicker.tsx
  45. 0 116
      apps/app/_obsolete/src/components/PageEditor/EmojiPickerHelper.ts
  46. 0 95
      apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js
  47. 0 521
      apps/app/_obsolete/src/components/PageEditorByHackmd.tsx
  48. 0 115
      apps/app/_obsolete/src/components/PageEditorByHackmd/HackmdEditor.jsx
  49. 0 176
      apps/app/_obsolete/src/components/Sidebar/AppearanceModeDropdown.tsx
  50. 0 101
      apps/app/_obsolete/src/components/UncontrolledCodeMirror.tsx
  51. 0 16
      apps/app/_obsolete/src/interfaces/hackmd.ts
  52. 0 346
      apps/app/_obsolete/src/server/routes/hackmd.js
  53. 0 22
      apps/app/_obsolete/src/stores/hackmd.ts
  54. 0 170
      apps/app/_obsolete/src/styles/_override.scss
  55. 0 672
      apps/app/_obsolete/src/styles/theme/_apply-colors-dark.scss
  56. 0 536
      apps/app/_obsolete/src/styles/theme/_apply-colors-light.scss
  57. 0 32
      apps/app/_obsolete/src/styles/theme/_hsl-functions.scss
  58. 0 108
      apps/app/_obsolete/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss
  59. 0 29
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-border-colors.scss
  60. 0 22
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-buttons.scss
  61. 0 60
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-colors.scss
  62. 0 38
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-dropdown.scss
  63. 0 52
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-nav.scss
  64. 0 74
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-tables.scss
  65. 0 3
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-text.scss
  66. 0 103
      apps/app/_obsolete/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  67. 0 22
      apps/app/_obsolete/src/styles/theme/_reboot-toastr-colors.scss
  68. 0 727
      apps/app/_obsolete/src/styles/theme/apply-colors.scss
  69. 0 9
      apps/app/_obsolete/src/styles/theme/mixins/_count-badge.scss
  70. 0 23
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-badge.scss
  71. 0 146
      apps/app/_obsolete/src/styles/theme/mixins/_hsl-button.scss
  72. 0 72
      apps/app/_obsolete/src/styles/theme/mixins/_list-group.scss
  73. 0 22
      apps/app/_obsolete/src/styles/theme/mixins/_page-editor-mode-manager.scss
  74. 0 159
      apps/app/bin/cdn/cdn-resources-downloader.ts
  75. 0 33
      apps/app/bin/download-cdn-resources.ts
  76. 0 1
      apps/app/config/logger/config.dev.js
  77. 11 4
      apps/app/config/next-i18next.config.js
  78. 4 4
      apps/app/docker/Dockerfile
  79. 2 2
      apps/app/docker/README.md
  80. 0 6
      apps/app/docker/codebuild/buildspec.yml
  81. 1 1
      apps/app/next.config.js
  82. 33 28
      apps/app/package.json
  83. 3 0
      apps/app/public/images/icons/slack/slack-logo-background.svg
  84. 3 0
      apps/app/public/images/icons/slack/slack-logo-dark-background.svg
  85. BIN
      apps/app/public/images/icons/sublime.png
  86. BIN
      apps/app/public/images/icons/vscode.png
  87. 13 10
      apps/app/public/static/locales/en_US/admin.json
  88. 95 69
      apps/app/public/static/locales/en_US/translation.json
  89. 13 10
      apps/app/public/static/locales/ja_JP/admin.json
  90. 93 67
      apps/app/public/static/locales/ja_JP/translation.json
  91. 13 10
      apps/app/public/static/locales/zh_CN/admin.json
  92. 396 370
      apps/app/public/static/locales/zh_CN/translation.json
  93. 0 221
      apps/app/resource/cdn-manifests.js
  94. 0 253
      apps/app/resource/locales/en_US/sandbox-bootstrap4.md
  95. 169 0
      apps/app/resource/locales/en_US/sandbox-bootstrap5.md
  96. 7 10
      apps/app/resource/locales/en_US/sandbox-diagrams.md
  97. 110 391
      apps/app/resource/locales/en_US/sandbox.md
  98. 46 59
      apps/app/resource/locales/en_US/welcome.md
  99. 0 253
      apps/app/resource/locales/ja_JP/sandbox-bootstrap4.md
  100. 258 0
      apps/app/resource/locales/ja_JP/sandbox-bootstrap5.md

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

+ 0 - 1
.github/dependabot.yml

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

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

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

+ 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@dev/7.0.x
+  test-prod-node18:
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     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@dev/7.0.x
+  test-prod-node20:
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     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@dev/7.0.x
+    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
 
     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:

+ 3 - 3
.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
@@ -92,7 +92,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mongodb:
@@ -174,7 +174,7 @@ jobs:
 
     strategy:
       matrix:
-        node-version: [18.x]
+        node-version: [20.x]
 
     services:
       mongodb:

+ 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

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

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

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

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

+ 2 - 9
.github/workflows/release-slackbot-proxy.yml

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

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

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

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

+ 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

+ 182 - 1
CHANGELOG.md

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

+ 1 - 1
README.md

@@ -79,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)

+ 1 - 1
README_JP.md

@@ -78,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 - 1
_obsolete/packages/.eslintignore

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

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

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

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

@@ -1 +0,0 @@
-/dist

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

@@ -1,25 +0,0 @@
-{
-  "name": "@growi/hackmd",
-  "version": "7.0.0-RC.0",
-  "description": "GROWI js and css files to use hackmd",
-  "license": "MIT",
-  "type": "module",
-  "main": "dist/index.cjs",
-  "module": "dist/index.js",
-  "types": "dist/index.d.ts",
-  "scripts": {
-    "build": "vite build",
-    "clean": "shx rm -rf dist",
-    "dev": "vite build --mode dev",
-    "watch": "yarn dev -w --emptyOutDir=false",
-    "lint:js": "yarn eslint **/*.{js,ts}",
-    "lint:typecheck": "tsc",
-    "lint": "npm-run-all -p lint:*",
-    "version": "yarn version --no-git-tag-version --preid=RC"
-  },
-  "dependencies": {},
-  "devDependencies": {
-    "penpal": "^4.0.0",
-    "throttle-debounce": "^5.0.0"
-  }
-}

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

@@ -1,152 +0,0 @@
-/**
- * GROWI agent for HackMD
- *
- * This file will be transpiled as a single JS
- *  and should be load from HackMD head via 'routes/hackmd.js' route
- *
- * USAGE:
- *  <script src="${hostname of GROWI}/_hackmd/load-agent"></script>
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-import connectToParent from 'penpal/lib/connectToParent';
-import { debounce } from 'throttle-debounce';
-
-const DEBUG_PENPAL = false;
-
-/* eslint-disable no-console  */
-
-const allowedOrigin = '<%= origin %>'; // will be replaced by ejs
-
-
-/**
- * return the value of CodeMirror
- */
-function getValueOfCodemirror() {
-  // get CodeMirror instance
-  const editor = window.editor;
-  return editor.doc.getValue();
-}
-
-/**
- * set the specified document to CodeMirror
- * @param {string} value
- */
-function setValueToCodemirror(value) {
-  // get CodeMirror instance
-  const editor = window.editor;
-  editor.doc.setValue(value);
-}
-
-/**
- * set the specified document to CodeMirror on window loaded
- * @param {string} value
- */
-function setValueToCodemirrorOnInit(newValue) {
-  if (window.cmClient != null) {
-    setValueToCodemirror(newValue);
-    return;
-  }
-
-  const intervalId = setInterval(() => {
-    if (window.cmClient != null) {
-      clearInterval(intervalId);
-      setValueToCodemirror(newValue);
-    }
-  }, 250);
-
-}
-
-/**
- * postMessage to GROWI to notify body changes
- * @param {string} body
- */
-function postParentToNotifyBodyChanges(body) {
-  window.growi.notifyBodyChanges(body);
-}
-// generate debounced function
-const debouncedPostParentToNotifyBodyChanges = debounce(800, postParentToNotifyBodyChanges);
-
-/**
- * postMessage to GROWI to save with shortcut
- * @param {string} document
- */
-function postParentToSaveWithShortcut(document) {
-  window.growi.saveWithShortcut(document);
-}
-
-function addEventListenersToCodemirror() {
-  // get CodeMirror instance
-  const codemirror = window.CodeMirror;
-  // get CodeMirror editor instance
-  const editor = window.editor;
-
-  // e.g. 404 not found
-  if (codemirror == null || editor == null) {
-    return;
-  }
-
-  // == change event
-  editor.on('change', (cm, change) => {
-    if (change.origin === 'ignoreHistory') {
-      // do nothing because this operation triggered by other user
-      return;
-    }
-    debouncedPostParentToNotifyBodyChanges(cm.doc.getValue());
-  });
-
-  // == save event
-  // Reset save commands and Cmd-S/Ctrl-S shortcuts that initialized by HackMD
-  codemirror.commands.save = function(cm) {
-    postParentToSaveWithShortcut(cm.doc.getValue());
-  };
-  delete editor.options.extraKeys['Cmd-S'];
-  delete editor.options.extraKeys['Ctrl-S'];
-}
-
-function connectToParentWithPenpal() {
-  const connection = connectToParent({
-    parentOrigin: allowedOrigin,
-    // Methods child is exposing to parent
-    methods: {
-      getValue() {
-        return getValueOfCodemirror();
-      },
-      setValue(newValue) {
-        setValueToCodemirror(newValue);
-      },
-      setValueOnInit(newValue) {
-        setValueToCodemirrorOnInit(newValue);
-      },
-    },
-    debug: DEBUG_PENPAL,
-  });
-  connection.promise
-    .then((parent) => {
-      window.growi = parent;
-    })
-    .catch((err) => {
-      console.log(err);
-    });
-}
-
-/**
- * main
- */
-(function() {
-  // check HackMD is in iframe
-  if (window === window.parent) {
-    console.log('[GROWI] Loading agent for HackMD is not processed because currently not in iframe');
-    return;
-  }
-
-  console.log('[HackMD] Loading GROWI agent for HackMD...');
-
-  window.addEventListener('load', () => {
-    addEventListenersToCodemirror();
-  });
-
-  connectToParentWithPenpal();
-
-  console.log('[HackMD] GROWI agent for HackMD has successfully loaded.');
-}());

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

@@ -1,41 +0,0 @@
-/**
- * GROWI styles loader for HackMD
- *
- * This file will be transpiled as a single JS
- *  and should be load from HackMD head via 'routes/hackmd.js' route
- *
- * USAGE:
- *  <script src="${hostname of GROWI}/_hackmd/load-styles"></script>
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-
-/* eslint-disable no-console  */
-
-const styles = '<%= styles %>'; // will be replaced by ejs
-
-/**
- * Insert link tag to load style file
- */
-function insertStyle() {
-  const element = document.createElement('style');
-  element.appendChild(document.createTextNode(unescape(styles)));
-  document.getElementsByTagName('head')[0].appendChild(element);
-}
-
-/**
- * main
- */
-(function() {
-  // check HackMD is in iframe
-  if (window === window.parent) {
-    console.log('[GROWI] Loading styles for HackMD is not processed because currently not in iframe');
-    return;
-  }
-
-  console.log('[HackMD] Loading GROWI styles for HackMD...');
-
-  insertStyle();
-
-  console.log('[HackMD] GROWI styles for HackMD has successfully loaded.');
-}());

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

@@ -1,16 +0,0 @@
-import fs from 'node:fs';
-import path from 'node:path';
-
-const isProduction = process.env.NODE_ENV === 'production';
-const dirPath = isProduction ? '.' : '../dist';
-const stylesJSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/hackmd-styles.js`));
-const agentJSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/hackmd-agent.js`));
-const stylesCSSFile = fs.readFileSync(path.resolve(__dirname, `${dirPath}/style.css`));
-
-// export to app as string
-const hackmdFiles = {
-  stylesJS: stylesJSFile.toString(),
-  agentJS: agentJSFile.toString(),
-  stylesCSS: stylesCSSFile.toString().replace(/(\r\n|\n|\r)/gm, ''), // https://stackoverflow.com/questions/10805125/how-to-remove-all-line-breaks-from-a-string
-};
-export default hackmdFiles;

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

@@ -1,21 +0,0 @@
-.navbar-header {
-  .navbar-brand {
-    display: none;
-  }
-}
-
-.navbar-form {
-  margin-left: 15px;
-}
-
-.navbar-right {
-  .ui-new, .ui-publish {
-    display: none;
-  }
-}
-
-.CodeMirror pre.CodeMirror-line {
-  font-family: Osaka-Mono, 'MS Gothic', Monaco, Menlo, Consolas, 'Courier New', monospace;
-  font-size: 14px;
-  line-height: 20px;
-}

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

@@ -1,14 +0,0 @@
-{
-  "$schema": "http://json.schemastore.org/tsconfig",
-  "extends": "../../tsconfig.base.json",
-  "compilerOptions": {
-    "isolatedModules": false,
-
-    "baseUrl": ".",
-    "paths": {
-    }
-  },
-  "include": [
-    "src"
-  ]
-}

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

@@ -1,30 +0,0 @@
-import { defineConfig } from 'vite';
-import dts from 'vite-plugin-dts';
-
-
-// https://vitejs.dev/config/
-export default defineConfig({
-  plugins: [
-    dts({ copyDtsFiles: true }),
-  ],
-  build: {
-    outDir: 'dist',
-    lib: {
-      entry: [
-        'src/index.ts',
-        'src/hackmd-styles.ts',
-        'src/hackmd-agent.js',
-        'src/style.scss',
-      ],
-      name: 'hackmd-libs',
-      formats: ['es', 'cjs'],
-    },
-    rollupOptions: {
-      external: [
-        'node:fs',
-        'node:path',
-      ],
-    },
-    sourcemap: true,
-  },
-});

+ 0 - 1
apps/app/.eslintignore

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

+ 1 - 0
apps/app/.eslintrc.js

@@ -27,6 +27,7 @@ module.exports = {
       },
     ]],
     '@typescript-eslint/no-var-requires': 'off',
+    '@typescript-eslint/consistent-type-imports': 'warn',
 
     // set 'warn' temporarily -- 2021.08.02 Yuki Takei
     '@typescript-eslint/no-use-before-define': ['warn'],

+ 0 - 33
apps/app/_obsolete/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]);
-};

+ 0 - 51
apps/app/_obsolete/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/_obsolete/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/_obsolete/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/_obsolete/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;

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

@@ -1,118 +0,0 @@
-@use '~/styles/variables' as var;
-@use '~/styles/bootstrap/init' as bs;
-@use '~/styles/mixins';
-
-.grw-navbar :global {
-
-  .confidential {
-    font-weight: bold;
-  }
-
-}
-
-.grw-navbar :global {
-  top: #{-1 * var.$grw-navbar-height} !important;
-
-  z-index: var.$grw-navbar-z-index !important;
-  max-height: var.$grw-navbar-height + var.$grw-navbar-border-width;
-  border-top: 0;
-  border-right: 0;
-  border-bottom: var.$grw-navbar-border-width solid;
-  border-left: 0;
-
-  .grw-app-title {
-    @include mixins.variable-font-size(24px);
-  }
-
-  .grw-navbar-search {
-    position: absolute;
-    left: 50%;
-    transform: translate(-50%, 0%);
-  }
-
-  .nav-link,
-  .nav-item.confidential {
-    display: flex;
-    align-items: center;
-    min-height: var.$grw-navbar-height;
-    padding: 0 1rem;
-  }
-
-  .nav-link {
-    &:hover {
-      background: rgba(0, 0, 0, 0.1);
-    }
-
-    &:focus {
-      background: rgba(0, 0, 0, 0);
-    }
-  }
-  .nav-item.confidential {
-    :not(i) {
-      @include mixins.variable-font-size(14px);
-    }
-
-    @include bs.media-breakpoint-only(md) {
-      max-width: 100px;
-    }
-
-    max-width: 120px;
-    max-height: var.$grw-navbar-height;
-    overflow: hidden;
-    background: rgba(0, 0, 0, 0.2);
-  }
-
-  .grw-email-sm {
-    font-size: 0.75em;
-  }
-
-  .grw-notification-dropdown {
-    .dropdown-menu {
-      max-width: 70vw;
-    }
-  }
-}
-
-// layout for GlobalSearch
-.grw-navbar :global {
-  .grw-global-search-container {
-    // centering on navbar
-    top: var.$grw-navbar-height / 2;
-    left: 50vw;
-    z-index: bs.$zindex-fixed + 1;
-    transform: translate(-50%, -50%);
-
-    .rbt-input.form-control {
-      width: 200px;
-      transition: 0.3s ease-out;
-
-      // focus
-      &.focus {
-        width: 300px;
-      }
-
-      @include bs.media-breakpoint-up(md) {
-        width: 300px;
-      }
-      @include bs.media-breakpoint-up(lg) {
-        // focus
-        &.focus {
-          width: 400px;
-        }
-      }
-      @include bs.media-breakpoint-up(xl) {
-        width: 350px;
-        // focus
-        &.focus {
-          width: 450px;
-        }
-      }
-    }
-  }
-}
-
-.grw-notification-badge {
-  position: absolute;
-  top: 6px;
-  right: 3.5px;
-}

+ 0 - 141
apps/app/_obsolete/src/components/Navbar/GrowiNavbar.tsx

@@ -1,141 +0,0 @@
-import React, {
-  FC, memo, useMemo, useRef,
-} from 'react';
-
-import { useTranslation } from 'next-i18next';
-import dynamic from 'next/dynamic';
-import { useRipple } from 'react-use-ripple';
-import { UncontrolledTooltip } from 'reactstrap';
-
-import {
-  useIsSearchPage, useIsGuestUser, useIsReadOnlyUser, useIsSearchServiceConfigured, useAppTitle, useConfidential,
-} from '~/stores/context';
-import { usePageCreateModal } from '~/stores/modal';
-import { useCurrentPagePath } from '~/stores/page';
-import { useIsDeviceSmallerThanMd } from '~/stores/ui';
-
-
-import { GlobalSearchProps } from './GlobalSearch';
-
-import styles from './GrowiNavbar.module.scss';
-
-const NavbarRight = memo((): JSX.Element => {
-  const { t } = useTranslation();
-
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: isGuestUser } = useIsGuestUser();
-  const { data: isReadOnlyUser } = useIsReadOnlyUser();
-
-  // ripple
-  const newButtonRef = useRef(null);
-  useRipple(newButtonRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
-
-  const { open: openCreateModal } = usePageCreateModal();
-
-  const isAuthenticated = isGuestUser === false;
-
-  const authenticatedNavItem = useMemo(() => {
-    return (
-      <>
-        {!isReadOnlyUser
-          && (
-            <li className="nav-item d-none d-md-block">
-              <button
-                className="px-md-3 nav-link btn-create-page border-0 bg-transparent"
-                type="button"
-                ref={newButtonRef}
-                data-testid="newPageBtn"
-                onClick={() => openCreateModal(currentPagePath || '')}
-              >
-                <span className="material-symbols-outlined">edit</span>
-                <span className="d-none d-lg-block">{ t('commons:New') }</span>
-              </button>
-            </li>
-          )
-        }
-      </>
-    );
-  }, [isReadOnlyUser, t, openCreateModal, currentPagePath]);
-
-  const notAuthenticatedNavItem = useMemo(() => {
-    return (
-      <>
-        <li id="login-user" className="nav-item"><a className="nav-link" href="/login">Login</a></li>
-      </>
-    );
-  }, []);
-
-  return (
-    <>
-      {isAuthenticated ? authenticatedNavItem : notAuthenticatedNavItem}
-    </>
-  );
-});
-NavbarRight.displayName = 'NavbarRight';
-
-type ConfidentialProps = {
-  confidential?: string,
-}
-const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX.Element => {
-  const { confidential } = props;
-
-  if (confidential == null || confidential.length === 0) {
-    return <></>;
-  }
-
-  return (
-    <li className="nav-item confidential text-light">
-      <i id="confidentialTooltip"></i><span className="material-symbols-outlined d-md-none">info</span>
-      <span className="d-none d-md-inline">
-        {confidential}
-      </span>
-      <UncontrolledTooltip
-        placement="bottom"
-        target="confidentialTooltip"
-        className="d-md-none"
-      >
-        {confidential}
-      </UncontrolledTooltip>
-    </li>
-  );
-});
-Confidential.displayName = 'Confidential';
-
-type Props = {
-  isGlobalSearchHidden?: boolean
-}
-
-export const GrowiNavbar = (props: Props): JSX.Element => {
-
-  const { isGlobalSearchHidden } = props;
-
-  const GlobalSearch = dynamic<GlobalSearchProps>(() => import('./GlobalSearch').then(mod => mod.GlobalSearch), { ssr: false });
-
-  const { data: appTitle } = useAppTitle();
-  const { data: confidential } = useConfidential();
-  const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
-  const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
-  const { data: isSearchPage } = useIsSearchPage();
-
-  return (
-    <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
-
-      <div className="grw-app-title d-none d-md-block">
-        {appTitle}
-      </div>
-
-      {/* Navbar Right  */}
-      <ul className="navbar-nav ms-auto">
-        <NavbarRight />
-        <Confidential confidential={confidential} />
-      </ul>
-
-      <div className="grw-global-search-container position-absolute">
-        { !isGlobalSearchHidden && isSearchServiceConfigured && !isDeviceSmallerThanMd && !isSearchPage && (
-          <GlobalSearch />
-        ) }
-      </div>
-    </nav>
-  );
-
-};

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

@@ -1,103 +0,0 @@
-@use '~/styles/variables' as var;
-@use '@growi/core/scss/bootstrap/init' as bs;
-@use '~/styles/mixins';
-
-%subnav-buttons-height {
-  height: 40px;
-}
-
-%compact-subnav-buttons-height {
-  height: 32px;
-}
-
-// https://github.com/css-modules/css-modules/issues/295#issuecomment-404873976
-// workaround to use '&' in global scope
-.grw-subnav {
-  :global {
-    min-height: var.$grw-subnav-min-height;
-    padding-top: 8px;
-    padding-bottom: 8px;
-
-    @include bs.media-breakpoint-up(md) {
-      min-height: var.$grw-subnav-min-height-md;
-    }
-
-    h1 {
-      @include mixins.variable-font-size(32px);
-      line-height: 1.4em;
-    }
-
-    .btn-copy {
-      &:not(:hover):not(:active) {
-        background-color: transparent !important;
-      }
-      opacity: 0.5;
-    }
-
-    .btn-subscribe {
-      @extend %subnav-buttons-height;
-      font-size: 20px;
-    }
-
-    .btn-like,
-    .btn-bookmark,
-    .btn-seen-user {
-      @extend %subnav-buttons-height;
-      padding-right: 6px;
-      padding-left: 8px;
-      font-size: 20px;
-      svg {
-        width: 20px;
-        height: 20px;
-      }
-    }
-    .total-likes,
-    .total-bookmarks {
-      display: flex;
-      align-items: flex-end;
-      padding-right: 8px;
-      padding-left: 6px;
-      font-size: 14px;
-      font-weight: bs.$font-weight-bold;
-    }
-    .seen-user-count {
-      padding-right: 6px;
-      padding-left: 6px;
-      font-size: 14px;
-      font-weight: bs.$font-weight-bold;
-      vertical-align: bottom;
-    }
-
-    .btn-page-item-control {
-      height: 40px;
-      font-size: 16px;
-    }
-
-    .user-list-popover {
-      max-width: 200px;
-
-      .user-list-content {
-        direction: rtl;
-
-        .liker-user-count,
-        .seen-user-count {
-          font-size: 12px;
-          font-weight: bolder;
-        }
-      }
-      .cls-1 {
-        isolation: isolate;
-      }
-    }
-  }
-
-  &:global {
-    &:hover {
-      .btn-copy,
-      .btn-edit-tags {
-        // change button opacity
-        opacity: unset;
-      }
-    }
-  }
-}

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

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

+ 0 - 41
apps/app/_obsolete/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss

@@ -1,41 +0,0 @@
-@use '~/styles/variables' as var;
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-/*
- * Fixed ver
- */
-$easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
-
-.grw-subnav-fixed-container {
-  top: var.$grw-navbar-border-width;
-  z-index: bs.$zindex-sticky - 5;
-}
-
-/*
- * Switching show/hide
- */
-.grw-subnav-switcher {
-  :global {
-    .grw-subnav-fixed-container {
-      transition: transform 150ms $easeInOutCubic;
-    }
-
-    /*
-    * shadow
-    */
-    .grw-subnav-append-shadow-container {
-      .grw-subnav {
-        box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
-      }
-    }
-  }
-
-  &:global {
-    &.grw-subnav-switcher-hidden {
-      .grw-subnav-fixed-container {
-        transition: unset;
-        transform: translateY(-100%);
-      }
-    }
-  }
-}

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

@@ -1,97 +0,0 @@
-import React, {
-  useState, useRef, useEffect, useCallback,
-} from 'react';
-
-import { debounce } from 'throttle-debounce';
-
-import { useSticky } from '~/client/services/side-effects/use-sticky';
-import { useSWRxCurrentPage } from '~/stores/page';
-import { useSidebarCollapsed } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
-
-import styles from './GrowiSubNavigationSwitcher.module.scss';
-
-const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
-
-export type GrowiSubNavigationSwitcherProps = {
-  isLinkSharingDisabled: boolean,
-}
-
-/**
- * GrowiSubNavigation
- *
- * needs:
- *   #grw-subnav-fixed-container element
- *   #grw-subnav-sticky-trigger element
- */
-export const GrowiSubNavigationSwitcher = (props: GrowiSubNavigationSwitcherProps): JSX.Element => {
-  const { isLinkSharingDisabled } = props;
-
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: isSidebarCollapsed } = useSidebarCollapsed();
-
-  const [width, setWidth] = useState<number>(0);
-
-  // use more specific type HTMLDivElement for avoid assertion error.
-  // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
-  const fixedContainerRef = useRef<HTMLDivElement>(null);
-  const clientWidth = fixedContainerRef.current?.parentElement?.clientWidth;
-
-  // Get sticky status
-  const isSticky = useSticky('#grw-subnav-sticky-trigger');
-
-  // Do not use clientWidth as useCallback deps, resizing events will not work in production builds.
-  const initWidth = useCallback(() => {
-    if (fixedContainerRef.current != null && fixedContainerRef.current.parentElement != null) {
-      // get parent elements width
-      const { clientWidth } = fixedContainerRef.current.parentElement;
-      setWidth(clientWidth);
-    }
-  }, []);
-
-  // setup effect by resizing event
-  useEffect(() => {
-    const resizeHandler = debounce(100, initWidth);
-    window.addEventListener('resize', resizeHandler);
-
-    // return clean up handler
-    return () => {
-      window.removeEventListener('resize', resizeHandler);
-    };
-  }, [initWidth]);
-
-  // update width when sidebar collapsing changed
-  useEffect(() => {
-    if (isSidebarCollapsed != null) {
-      setTimeout(initWidth, 300);
-    }
-  }, [isSidebarCollapsed, initWidth]);
-
-  /*
-   * initialize width.
-   * Since width is not recalculated at production build first rendering,
-   * make initWidth execution dependent on clientWidth.
-   */
-  useEffect(() => {
-    if (clientWidth != null) initWidth();
-  }, [initWidth, clientWidth]);
-
-  if (currentPage == null) {
-    return <></>;
-  }
-
-  return (
-    <div className={`${styles['grw-subnav-switcher']} ${isSticky ? '' : 'grw-subnav-switcher-hidden'}`} data-testid="grw-subnav-switcher">
-      <div
-        id="grw-subnav-fixed-container"
-        className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
-        ref={fixedContainerRef}
-        style={{ width }}
-      >
-        <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled={isLinkSharingDisabled} />
-      </div>
-    </div>
-  );
-};

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

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

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

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

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

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

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

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

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

@@ -1,62 +0,0 @@
-import React, { FC, useCallback } from 'react';
-
-import { Picker } from 'emoji-mart';
-import { Modal } from 'reactstrap';
-
-import { useNextThemes } from '~/stores/use-next-themes';
-
-import EmojiPickerHelper, { getEmojiTranslation } from './EmojiPickerHelper';
-
-
-import 'emoji-mart/css/emoji-mart.css';
-
-
-type Props = {
-  onClose: () => void,
-  onSelected: (emoji: string) => void,
-  emojiSearchText: string,
-  emojiPickerHelper: EmojiPickerHelper,
-  isOpen: boolean
-}
-
-const EmojiPicker: FC<Props> = (props: Props) => {
-
-  const {
-    onClose, onSelected, emojiSearchText, emojiPickerHelper, isOpen,
-  } = props;
-
-  const { resolvedTheme } = useNextThemes();
-
-  // Set search emoji input and trigger search
-  const searchEmoji = useCallback(() => {
-    const input = window.document.querySelector('[id^="emoji-mart-search"]') as HTMLInputElement;
-    const valueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')?.set;
-    valueSetter?.call(input, emojiSearchText);
-    const event = new Event('input', { bubbles: true });
-    input.dispatchEvent(event);
-    input.focus();
-  }, [emojiSearchText]);
-
-  const selectEmoji = useCallback((emoji) => {
-    onSelected(emoji);
-    onClose();
-  }, [onClose, onSelected]);
-
-
-  const translation = getEmojiTranslation();
-
-  return (
-    <Modal isOpen={isOpen} toggle={onClose} onOpened={searchEmoji} backdropClassName="emoji-picker-modal" fade={false}>
-      <Picker
-        onSelect={selectEmoji}
-        i18n={translation}
-        title={translation.title}
-        emojiTooltip
-        style={emojiPickerHelper.setStyle()}
-        theme={resolvedTheme}
-      />
-    </Modal>
-  );
-};
-
-export default EmojiPicker;

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

@@ -1,116 +0,0 @@
-import { CSSProperties } from 'react';
-
-import { Position } from 'codemirror';
-import i18n from 'i18next';
-
-// https://regex101.com/r/x5LbOZ/1
-const EMOJI_PATTERN = new RegExp(/^:[a-z0-9-+_]+$/);
-
-export default class EmojiPickerHelper {
-
-  editor;
-
-  pattern: string;
-
-  constructor(editor) {
-    this.editor = editor;
-  }
-
-  setStyle = (): CSSProperties => {
-    const offset = 20;
-    const emojiPickerHeight = 420;
-    const cursorPos = this.editor.cursorCoords(true);
-    const editorPos = this.editor.getWrapperElement().getBoundingClientRect();
-    // Emoji Picker bottom position exceed editor's bottom position
-    if (cursorPos.bottom + emojiPickerHeight > editorPos.bottom) {
-      return {
-        top: editorPos.bottom - emojiPickerHeight,
-        left: cursorPos.left + offset,
-        position: 'fixed',
-      };
-    }
-    return {
-      top: cursorPos.top + offset,
-      left: cursorPos.left + offset,
-      position: 'fixed',
-    };
-  };
-
-  shouldModeTurnOn = (char: string): Position | null | undefined => {
-    if (char !== ':') {
-      return null;
-    }
-
-    const currentPos = this.editor.getCursor();
-    const sc = this.editor.getSearchCursor(':', currentPos, { multiline: false });
-    if (sc.findPrevious()) {
-      return sc.pos.from;
-    }
-  };
-
-  shouldOpen = (startPos: Position): boolean => {
-    const currentPos = this.editor.getCursor();
-    const rangeStr = this.editor.getRange(startPos, currentPos);
-
-    return EMOJI_PATTERN.test(rangeStr);
-  };
-
-  getInitialSearchingText = (startPos: Position): void => {
-    const currentPos = this.editor.getCursor();
-    const rangeStr = this.editor.getRange(startPos, currentPos);
-
-    return rangeStr.slice(1); // return without the heading ':'
-  };
-
-  addEmoji = (emoji: { colons: string }, startPosToReplace: Position|null): void => {
-    const currentPos = this.editor.getCursor();
-
-    const from = startPosToReplace ?? currentPos;
-    const to = currentPos;
-
-    const doc = this.editor.getDoc();
-    doc.replaceRange(`${emoji.colons} `, from, to);
-    this.editor.focus();
-    this.editor.refresh();
-  };
-
-}
-
-export const getEmojiTranslation = () => {
-
-  const categories = {};
-  [
-    'search',
-    'recent',
-    'smileys',
-    'people',
-    'nature',
-    'foods',
-    'activity',
-    'places',
-    'objects',
-    'symbols',
-    'flags',
-    'custom',
-  ].forEach((category) => {
-    categories[category] = i18n.t(`emoji.categories.${category}`);
-  });
-
-  const skintones = {};
-  (Array.from(Array(6).keys())).forEach((tone) => {
-    skintones[tone + 1] = i18n.t(`emoji.skintones.${tone + 1}`);
-  });
-
-  const translation = {
-    search: i18n.t('emoji.search'),
-    clear: i18n.t('emoji.clear'),
-    notfound: i18n.t('emoji.notfound'),
-    skintext: i18n.t('emoji.skintext'),
-    categories,
-    categorieslabel: i18n.t('emoji.categorieslabel'),
-    skintones,
-    title: i18n.t('emoji.title'),
-  };
-
-  return translation;
-};

+ 0 - 95
apps/app/_obsolete/src/components/PageEditor/MarkdownTableInterceptor.js

@@ -1,95 +0,0 @@
-import { BasicInterceptor } from '@growi/core/dist/utils';
-
-import MarkdownTable from '~/client/models/MarkdownTable';
-
-import {
-  getStrFromBot, addRowToMarkdownTable, getStrToEot, isEndOfLine, mergeMarkdownTable, replaceFocusedMarkdownTableWithEditor,
-  isInTable, emptyLineOfTableRE,
-} from '../../../../src/components/PageEditor/markdown-table-util-for-editor';
-
-/**
- * Interceptor for markdown table
- */
-export default class MarkdownTableInterceptor extends BasicInterceptor {
-
-  /**
-   * @inheritdoc
-   */
-  isInterceptWhen(contextName) {
-    return (
-      contextName === 'preHandleEnter'
-    );
-  }
-
-  /**
-   * return boolean value whether processable parallel
-   */
-  isProcessableParallel() {
-    return false;
-  }
-
-  addRow(cm) {
-    // get lines all of table from current position to beginning of table
-    const strFromBot = getStrFromBot(cm);
-    let table = MarkdownTable.fromMarkdownString(strFromBot);
-
-    addRowToMarkdownTable(table);
-
-    const strToEot = getStrToEot(cm);
-    const tableBottom = MarkdownTable.fromMarkdownString(strToEot);
-    if (tableBottom.table.length > 0) {
-      table = mergeMarkdownTable([table, tableBottom]);
-    }
-
-    replaceFocusedMarkdownTableWithEditor(cm, table);
-  }
-
-  reformTable(cm) {
-    const tableStr = getStrFromBot(cm) + getStrToEot(cm);
-    const table = MarkdownTable.fromMarkdownString(tableStr);
-    replaceFocusedMarkdownTableWithEditor(cm, table);
-  }
-
-  removeRow(editor) {
-    editor.replaceLine('\n');
-  }
-
-  /**
-   * @inheritdoc
-   */
-  async process(contextName, ...args) {
-    const context = Object.assign(args[0]); // clone
-    const editor = context.editor; // AbstractEditor instance
-    // "autoFormatMarkdownTable" may be undefined, so it is compared to true and converted to bool.
-    const noIntercept = (context.autoFormatMarkdownTable === false);
-
-    // do nothing if editor is not a CodeMirrorEditor or no intercept
-    if (editor == null || editor.getCodeMirror() == null || noIntercept) {
-      return context;
-    }
-
-    const cm = editor.getCodeMirror();
-
-    const isLastRow = getStrToEot(cm) === editor.getStrToEol();
-
-    if (isInTable(cm)) {
-      // at EOL in the table
-      if (isEndOfLine(cm)) {
-        this.addRow(cm);
-      }
-      // last empty row
-      else if (isLastRow && emptyLineOfTableRE.test(editor.getStrFromBol() + editor.getStrToEol())) {
-        this.removeRow(editor);
-      }
-      else {
-        this.reformTable(cm);
-      }
-
-      // report to manager that handling was done
-      context.handlers.push(this.className);
-      return context;
-    }
-
-  }
-
-}

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

@@ -1,521 +0,0 @@
-import React, {
-  useCallback, useRef, useState, useEffect, useMemo,
-} from 'react';
-
-import EventEmitter from 'events';
-
-import { pathUtils } from '@growi/core/dist/utils';
-import Link from 'next/link';
-import { useRouter } from 'next/router';
-import { useTranslation } from 'react-i18next';
-import urljoin from 'url-join';
-
-import { useUpdateStateAfterSave, useSaveOrUpdate } from '~/client/services/page-operation';
-import { apiPost } from '~/client/util/apiv1-client';
-import { toastError, toastSuccess } from '~/client/util/toastr';
-import { IResHackmdIntegrated, IResHackmdDiscard } from '~/interfaces/hackmd';
-import { OptionsToSave } from '~/interfaces/page-operation';
-import {
-  useCurrentPathname, useHackmdUri,
-} from '~/stores/context';
-import {
-  useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning, useWaitingSaveProcessing,
-} from '~/stores/editor';
-import {
-  usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useIsHackmdDraftUpdatingInRealtime,
-} from '~/stores/hackmd';
-import {
-  useCurrentPagePath, useSWRMUTxCurrentPage, useSWRxCurrentPage, useSWRxTagsInfo, useCurrentPageId, useIsNotFound,
-} from '~/stores/page';
-import { mutatePageTree } from '~/stores/page-listing';
-import { useRemoteRevisionId } from '~/stores/remote-latest-page';
-import {
-  EditorMode,
-  useEditorMode, useSelectedGrant,
-} from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
-
-const logger = loggerFactory('growi:PageEditorByHackmd');
-
-
-declare global {
-  // eslint-disable-next-line vars-on-top, no-var
-  var globalEmitter: EventEmitter;
-}
-
-
-type HackEditorRef = {
-  getValue: () => Promise<string>
-};
-
-export const PageEditorByHackmd = (): JSX.Element => {
-
-  const { t } = useTranslation();
-  const router = useRouter();
-
-  const { data: isNotFound } = useIsNotFound();
-  const { mutate: mutateWaitingSaveProcessing } = useWaitingSaveProcessing();
-  const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
-  const { data: currentPagePath } = useCurrentPagePath();
-  const { data: currentPathname } = useCurrentPathname();
-  const { data: isSlackEnabled } = useIsSlackEnabled();
-  const { data: pageId } = useCurrentPageId();
-  const { data: pageTags } = usePageTagsForEditors(pageId);
-  const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
-  const { data: grantData } = useSelectedGrant();
-  const { data: hackmdUri } = useHackmdUri();
-  const saveOrUpdate = useSaveOrUpdate();
-
-  const { returnPathForURL } = pathUtils;
-
-  // pageData
-  const { data: pageData } = useSWRxCurrentPage();
-  const { trigger: mutatePageData } = useSWRMUTxCurrentPage();
-  const revision = pageData?.revision;
-
-  const [isInitialized, setIsInitialized] = useState(false);
-  const [isInitializing, setIsInitializing] = useState(false);
-  // for error
-  const [hasError, setHasError] = useState(false);
-  const [errorMessage, setErrorMessage] = useState('');
-  const [errorReason, setErrorReason] = useState('');
-
-  // state from pageContainer
-  const { data: pageIdOnHackmd, mutate: mutatePageIdOnHackmd } = usePageIdOnHackmd();
-  const { data: hasDraftOnHackmd, mutate: mutateHasDraftOnHackmd } = useHasDraftOnHackmd();
-  const { data: revisionIdHackmdSynced, mutate: mutateRevisionIdHackmdSynced } = useRevisionIdHackmdSynced();
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
-  const { data: isHackmdDraftUpdatingInRealtime, mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
-  const { data: remoteRevisionId, mutate: mutateRemoteRevisionId } = useRemoteRevisionId();
-
-  const updateStateAfterSave = useUpdateStateAfterSave(pageId);
-
-  const hackmdEditorRef = useRef<HackEditorRef>(null);
-
-  const optionsToSave = useMemo((): OptionsToSave | undefined => {
-    if (grantData == null) {
-      return;
-    }
-    const grantedGroups = grantData.grantedGroups?.map((group) => {
-      return { item: group.id, type: group.type };
-    });
-    const optionsToSave = {
-      isSlackEnabled: isSlackEnabled ?? false,
-      slackChannels: '', // set in save method by opts in SavePageControlls.tsx
-      grant: grantData.grant,
-      pageTags: pageTags ?? [],
-      grantUserGroupIds: grantedGroups,
-    };
-    return optionsToSave;
-  }, [grantData, isSlackEnabled, pageTags]);
-
-  const saveAndReturnToViewHandler = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}) => {
-    if (editorMode !== EditorMode.HackMD) { return }
-
-    try {
-      if (currentPathname == null || revision == null || hackmdEditorRef.current == null || revisionIdHackmdSynced == null || optionsToSave == null) {
-        throw new Error('Some materials to save are invalid');
-      }
-
-      mutateWaitingSaveProcessing(true);
-
-      const options = Object.assign(optionsToSave, opts, { isSyncRevisionToHackmd: true });
-
-      const markdown = await hackmdEditorRef.current.getValue();
-
-      const { page } = await saveOrUpdate(markdown, { pageId, path: currentPagePath || currentPathname, revisionId: revisionIdHackmdSynced }, options);
-
-      if (page == null) {
-        return;
-      }
-      if (isNotFound) {
-        await router.push(`/${page._id}`);
-      }
-      else {
-        updateStateAfterSave?.();
-        mutateIsHackmdDraftUpdatingInRealtime(false);
-
-        // to sync revision id with page tree: https://github.com/weseek/growi/pull/7227
-        mutatePageTree();
-      }
-      setIsInitialized(false);
-      mutateEditorMode(EditorMode.View);
-    }
-    catch (error) {
-      logger.error('failed to save', error);
-      toastError(error.message);
-    }
-    finally {
-      mutateWaitingSaveProcessing(false);
-    }
-
-  // eslint-disable-next-line max-len
-  }, [
-    pageId, currentPagePath, isNotFound, router,
-    editorMode, currentPathname, revision, revisionIdHackmdSynced, optionsToSave,
-    saveOrUpdate, mutateEditorMode, updateStateAfterSave, mutateIsHackmdDraftUpdatingInRealtime, mutateWaitingSaveProcessing,
-  ]);
-
-  // set handler to save and reload Page
-  useEffect(() => {
-    globalEmitter.on('saveAndReturnToView', saveAndReturnToViewHandler);
-
-    return function cleanup() {
-      globalEmitter.removeListener('saveAndReturnToView', saveAndReturnToViewHandler);
-    };
-  }, [saveAndReturnToViewHandler]);
-
-  const resetInitializedStatusHandler = useCallback(() => {
-    setIsInitialized(false);
-  }, []);
-
-
-  // set handler to save and reload Page
-  useEffect(() => {
-    globalEmitter.on('resetInitializedHackMdStatus', resetInitializedStatusHandler);
-
-    return function cleanup() {
-      globalEmitter.removeListener('resetInitializedHackMdStatus', resetInitializedStatusHandler);
-    };
-  }, [resetInitializedStatusHandler]);
-
-  useEffect(() => {
-    // for page translation: https://github.com/weseek/growi/pull/7100
-    setIsInitialized(false);
-  }, [pageId]);
-
-
-  const isResume = useCallback(() => {
-    const isPageExistsOnHackmd = (pageIdOnHackmd != null);
-    return (isPageExistsOnHackmd && hasDraftOnHackmd) || isHackmdDraftUpdatingInRealtime;
-  }, [hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime, pageIdOnHackmd]);
-
-  const startToEdit = useCallback(async() => {
-
-    if (hackmdUri == null) {
-      // do nothing
-      return;
-    }
-
-    setIsInitialized(false);
-    setIsInitializing(true);
-
-    try {
-      const res = await apiPost<IResHackmdIntegrated>('/hackmd.integrate', { pageId });
-
-      if (!res.ok) {
-        throw new Error(res.error);
-      }
-
-      mutatePageIdOnHackmd(res.pageIdOnHackmd);
-      mutateRevisionIdHackmdSynced(res.revisionIdHackmdSynced);
-    }
-    catch (err) {
-      toastError(err.message);
-
-      setHasError(true);
-      setErrorMessage('GROWI server failed to connect to HackMD.');
-      setErrorReason(err.toString());
-    }
-
-    setIsInitialized(true);
-    setIsInitializing(false);
-  }, [pageId, hackmdUri, mutatePageIdOnHackmd, mutateRevisionIdHackmdSynced]);
-
-  /**
-   * Start to edit w/o any api request
-   */
-  const resumeToEdit = useCallback(() => {
-    setIsInitialized(true);
-  }, []);
-
-  const discardChanges = useCallback(async() => {
-
-    if (pageId == null) { return }
-
-    try {
-      const res = await apiPost<IResHackmdDiscard>('/hackmd.discard', { pageId });
-
-      if (!res.ok) {
-        throw new Error(res.error);
-      }
-
-      mutateIsHackmdDraftUpdatingInRealtime(false);
-      mutateHasDraftOnHackmd(false);
-      mutatePageIdOnHackmd(res.pageIdOnHackmd);
-      mutateRemoteRevisionId(res.revisionIdHackmdSynced);
-      mutateRevisionIdHackmdSynced(res.revisionIdHackmdSynced);
-
-
-    }
-    catch (err) {
-      logger.error(err);
-      toastError(err.message);
-    }
-  }, [mutateIsHackmdDraftUpdatingInRealtime, mutateHasDraftOnHackmd, mutatePageIdOnHackmd, mutateRevisionIdHackmdSynced, mutateRemoteRevisionId, pageId]);
-
-  /**
-   * save and update state of containers
-   * @param {string} markdown
-   */
-  const onSaveWithShortcut = useCallback(async(markdown) => {
-    try {
-      mutateWaitingSaveProcessing(true);
-
-      const currentPagePathOrPathname = currentPagePath || currentPathname;
-      if (
-        pageId == null || revisionIdHackmdSynced == null || currentPagePathOrPathname == null || optionsToSave == null
-      ) { throw new Error('Some materials to save are invalid') }
-
-      const options = Object.assign(optionsToSave, { isSyncRevisionToHackmd: true });
-
-      const res = await saveOrUpdate(markdown, { pageId, path: currentPagePathOrPathname, revisionId: revisionIdHackmdSynced }, options);
-
-      // update pageData
-      mutatePageData(res);
-
-      // set updated data
-      updateStateAfterSave?.();
-      mutateTagsInfo();
-
-      // to sync revision id with page tree: https://github.com/weseek/growi/pull/7227
-      mutatePageTree();
-
-      mutateIsEnabledUnsavedWarning(false);
-
-      logger.debug('success to save');
-
-      toastSuccess(t('successfully_saved_the_page'));
-    }
-    catch (error) {
-      logger.error('failed to save', error);
-      toastError(error.message);
-    }
-    finally {
-      mutateWaitingSaveProcessing(false);
-    }
-
-  // eslint-disable-next-line max-len
-  }, [
-    currentPagePath, currentPathname, pageId, revisionIdHackmdSynced, optionsToSave,
-    saveOrUpdate,
-    mutateWaitingSaveProcessing, mutatePageData, updateStateAfterSave, mutateTagsInfo, mutateIsEnabledUnsavedWarning, t,
-  ]);
-
-  /**
-   * onChange event of HackmdEditor handler
-   */
-  const hackmdEditorChangeHandler = useCallback(async(body) => {
-
-    if (hackmdUri == null || pageId == null) {
-      // do nothing
-      return;
-    }
-
-    if (revision?.body === body) {
-      return;
-    }
-
-    mutateIsEnabledUnsavedWarning(true);
-
-    try {
-      await apiPost('/hackmd.saveOnHackmd', { pageId });
-    }
-    catch (err) {
-      logger.error(err);
-    }
-  }, [hackmdUri, pageId, revision?.body, mutateIsEnabledUnsavedWarning]);
-
-  const penpalErrorOccuredHandler = useCallback((error) => {
-    toastError(error.message);
-
-    setHasError(true);
-    setErrorMessage(t('hackmd.fail_to_connect'));
-    setErrorReason(error.toString());
-  }, [t]);
-
-  const renderPreInitContent = useCallback(() => {
-    const isPageNotFound = pageId == null;
-
-    let content;
-
-    /*
-     * HackMD is not setup
-     */
-    if (hackmdUri == null) {
-      content = (
-        <div>
-          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> { t('hackmd.not_set_up')}</p>
-          {/* eslint-disable-next-line react/no-danger */}
-          <p dangerouslySetInnerHTML={{ __html: t('hackmd.need_to_associate_with_growi_to_use_hackmd_refer_to_this') }} />
-        </div>
-      );
-    }
-
-    /*
-    * used HackMD from NotFound Page
-    */
-    else if (isPageNotFound) {
-      content = (
-        <div className="text-center">
-          <p className="hackmd-status-label">
-            <span className="material-symbols-outlined">description</span>
-            { t('hackmd.used_for_not_found') }
-          </p>
-          {/* eslint-disable-next-line react/no-danger */}
-          <p dangerouslySetInnerHTML={{ __html: t('hackmd.need_to_make_page') }} />
-        </div>
-      );
-    }
-    /*
-     * Resume to edit or discard changes
-     */
-    else if (isResume()) {
-      const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
-
-      content = (
-        <div>
-          <p className="text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
-          <p className="text-center"><strong>{t('hackmd.unsaved_draft')}</strong></p>
-
-          { isHackmdDocumentOutdated && (
-            <div className="card border-warning">
-              <div className="card-header bg-warning text-dark"><span className="material-symbols-outlined">info</span> {t('hackmd.draft_outdated')}</div>
-              <div className="card-body text-center">
-                {t('hackmd.based_on_revision')}&nbsp;
-                { pageData != null && (
-                  <Link href={urljoin(returnPathForURL(pageData.path, pageData._id), `?revisionId=${revisionIdHackmdSynced}`)} prefetch={false}>
-                    <span className="badge bg-primary">{revisionIdHackmdSynced?.substr(-8)}</span>
-                  </Link>
-                )}
-                <div className="text-center mt-3">
-                  <button
-                    className="btn btn-link btn-view-outdated-draft p-0"
-                    type="button"
-                    disabled={isInitializing}
-                    onClick={resumeToEdit}
-                  >
-                    {t('hackmd.view_outdated_draft')}
-                  </button>
-                </div>
-              </div>
-            </div>
-          ) }
-
-          { !isHackmdDocumentOutdated && (
-            <div className="text-center hackmd-resume-button-container mb-3">
-              <button
-                className="btn btn-success btn-lg waves-effect waves-light"
-                type="button"
-                disabled={isInitializing}
-                onClick={resumeToEdit}
-              >
-                <span className="btn-label"></span><span className="material-symbols-outlined">skip_next</span>
-                <span className="btn-text">{t('hackmd.resume_to_edit')}</span>
-              </button>
-            </div>
-          ) }
-
-          <div className="text-center hackmd-discard-button-container mb-3">
-            <button
-              className="btn btn-outline-secondary btn-lg waves-effect waves-light"
-              type="button"
-              onClick={discardChanges}
-            >
-              <span className="btn-label"></span><span className="material-symbols-outlined">play_arrow</span>
-              <span className="btn-text">{t('hackmd.discard_changes')}</span>
-            </button>
-          </div>
-
-        </div>
-      );
-    }
-    /*
-     * Start to edit
-     */
-    else {
-      const isRevisionOutdated = revision?._id !== remoteRevisionId;
-
-      content = (
-        <div>
-          <p className="text-muted text-center hackmd-status-label"><span className="material-symbols-outlined">description</span> HackMD is READY!</p>
-          <div className="text-center hackmd-start-button-container mb-3">
-            <button
-              className="btn btn-info btn-lg waves-effect waves-light"
-              type="button"
-              disabled={isRevisionOutdated || isInitializing}
-              onClick={startToEdit}
-            >
-              <span className="btn-label"></span><span className="material-symbols-outlined">send</span>
-              {t('hackmd.start_to_edit')}
-            </button>
-          </div>
-          <p className="text-center">{t('hackmd.clone_page_content')}</p>
-        </div>
-      );
-    }
-
-    return (
-      <div className="hackmd-preinit d-flex justify-content-center align-items-center">
-        {content}
-      </div>
-    );
-  // eslint-disable-next-line max-len
-  }, [pageId, hackmdUri, isResume, t, revisionIdHackmdSynced, remoteRevisionId, pageData, returnPathForURL, isInitializing, resumeToEdit, discardChanges, revision?._id, startToEdit]);
-
-  if (editorMode == null || revision == null) {
-    return <></>;
-  }
-
-  let content;
-
-  // TODO: typescriptize
-  // using any because ref cann't used between FC and class conponent with type safe
-  const AnyEditor = HackmdEditor as any;
-
-  if (isInitialized && hackmdUri != null) {
-    content = (
-      <AnyEditor
-        ref={hackmdEditorRef}
-        hackmdUri={hackmdUri}
-        pageIdOnHackmd={pageIdOnHackmd}
-        initializationMarkdown={isResume() ? null : revision.body}
-        onChange={hackmdEditorChangeHandler}
-        onSaveWithShortcut={(document) => {
-          onSaveWithShortcut(document);
-        }}
-        onPenpalErrorOccured={penpalErrorOccuredHandler}
-      >
-      </AnyEditor>
-    );
-  }
-  else {
-    content = renderPreInitContent();
-  }
-
-
-  return (
-    <div className="position-relative">
-
-      {content}
-
-      { hasError && (
-        <div className="hackmd-error position-absolute d-flex flex-column justify-content-center align-items-center">
-          <div className="bg-box p-5 text-center">
-            <h2 className="text-warning"><span className="material-symbols-outlined">error</span> {t('hackmd.integration_failed')}</h2>
-            <h4>{errorMessage}</h4>
-            <p className="card custom-card text-danger">
-              {errorReason}
-            </p>
-            {/* eslint-disable-next-line react/no-danger */}
-            <p dangerouslySetInnerHTML={{ __html: t('hackmd.check_configuration') }} />
-          </div>
-        </div>
-      ) }
-
-    </div>
-  );
-
-};

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

@@ -1,115 +0,0 @@
-import React from 'react';
-
-import connectToChild from 'penpal/lib/connectToChild';
-import PropTypes from 'prop-types';
-
-import loggerFactory from '~/utils/logger';
-
-
-const DEBUG_PENPAL = false;
-
-const logger = loggerFactory('growi:HackmdEditor');
-
-export default class HackmdEditor extends React.PureComponent {
-
-  constructor(props) {
-    super(props);
-
-    this.hackmd = null;
-
-    this.initHackmdWithPenpal = this.initHackmdWithPenpal.bind(this);
-
-    this.notifyBodyChangesHandler = this.notifyBodyChangesHandler.bind(this);
-    this.saveWithShortcutHandler = this.saveWithShortcutHandler.bind(this);
-  }
-
-  componentDidMount() {
-    // append iframe with penpal
-    this.initHackmdWithPenpal();
-  }
-
-  async initHackmdWithPenpal() {
-    const shouldInit = document.getElementById('iframe-hackmd') != null;
-    if (shouldInit) {
-      return;
-    }
-
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const _this = this; // for in methods scope
-    const iframe = document.createElement('iframe');
-    iframe.src = `${this.props.hackmdUri}/${this.props.pageIdOnHackmd}?both`;
-    iframe.id = 'iframe-hackmd';
-    this.iframeContainer.appendChild(iframe);
-
-    const connection = connectToChild({
-      iframe,
-      methods: { // expose methods to HackMD
-        notifyBodyChanges(document) {
-          _this.notifyBodyChangesHandler(document);
-        },
-        saveWithShortcut(document) {
-          _this.saveWithShortcutHandler(document);
-        },
-      },
-      timeout: 15000,
-      debug: DEBUG_PENPAL,
-    });
-
-    try {
-      const child = await connection.promise;
-      this.hackmd = child;
-      if (this.props.initializationMarkdown != null) {
-        child.setValueOnInit(this.props.initializationMarkdown);
-      }
-    }
-    catch (err) {
-      logger.error(err);
-
-      if (this.props.onPenpalErrorOccured != null) {
-        this.props.onPenpalErrorOccured(err);
-      }
-    }
-  }
-
-  /**
-   * return markdown document of HackMD
-   * @return {Promise<string>}
-   */
-  getValue() {
-    return this.hackmd.getValue();
-  }
-
-  setValue(newValue) {
-    this.hackmd.setValue(newValue);
-  }
-
-  notifyBodyChangesHandler(body) {
-    // dispatch onChange() when there is difference from 'initializationMarkdown' props
-    if (this.props.onChange != null && body !== this.props.initializationMarkdown) {
-      this.props.onChange(body);
-    }
-  }
-
-  saveWithShortcutHandler(document) {
-    if (this.props.onSaveWithShortcut != null) {
-      this.props.onSaveWithShortcut(document);
-    }
-  }
-
-  render() {
-    return (
-      // will be rendered in componentDidMount
-      <div id="iframe-hackmd-container" ref={(c) => { this.iframeContainer = c }}></div>
-    );
-  }
-
-}
-
-HackmdEditor.propTypes = {
-  hackmdUri: PropTypes.string.isRequired,
-  pageIdOnHackmd: PropTypes.string.isRequired,
-  initializationMarkdown: PropTypes.string,
-  onChange: PropTypes.func,
-  onSaveWithShortcut: PropTypes.func,
-  onPenpalErrorOccured: PropTypes.func,
-};

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

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

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

@@ -1,101 +0,0 @@
-import React, {
-  useCallback, useRef, MutableRefObject,
-} from 'react';
-
-import codemirror, { commands, Editor } from 'codemirror';
-import { type ICodeMirror, UnControlled as CodeMirror } from 'react-codemirror2';
-
-declare global {
-  // eslint-disable-next-line vars-on-top, no-var
-  var CodeMirror: ICodeMirror;
-}
-
-// set save handler
-// CommandActions in @types/codemirror does not include 'save' but actualy exists
-// https://codemirror.net/5/doc/manual.html#commands
-(commands as any).save = (instance) => {
-  if (instance.codeMirrorEditor != null) {
-    instance.codeMirrorEditor.dispatchSave();
-  }
-};
-
-window.CodeMirror = codemirror;
-require('codemirror/addon/display/placeholder');
-require('~/client/util/codemirror/gfm-growi.mode');
-
-export interface UncontrolledCodeMirrorProps extends ICodeMirror {
-  value: string;
-  isGfmMode?: boolean;
-  lineNumbers?: boolean;
-  onSave?: () => Promise<void>;
-  onCtrlEnter?: (event: Event) => void;
-  onPasteFiles?: (editor: any, event: Event) => void;
-  onScrollCursorIntoView?: (editor: any, event: Event) => void;
-}
-
-export const UncontrolledCodeMirror = React.forwardRef<CodeMirror|null, UncontrolledCodeMirrorProps>((props, forwardedRef): JSX.Element => {
-
-  const {
-    value, lineNumbers, options,
-    onPasteFiles, onScrollCursorIntoView,
-    ...rest
-  } = props;
-
-  const editorRef = useRef<Editor>();
-
-  const wrapperRef = useRef<CodeMirror|null>();
-
-  const editorDidMountHandler = useCallback((editor: Editor): void => {
-    editorRef.current = editor;
-    if (onPasteFiles != null) {
-      editor.on('paste', onPasteFiles);
-    }
-    if (onScrollCursorIntoView != null) {
-      editor.on('scrollCursorIntoView', onScrollCursorIntoView);
-    }
-  }, [onPasteFiles, onScrollCursorIntoView]);
-
-  const editorWillUnmountHandler = useCallback((): void => {
-    // workaround to fix editor duplicating by https://github.com/scniro/react-codemirror2/issues/284#issuecomment-1155928554
-    if (editorRef.current != null) {
-      (editorRef.current as any).display.wrapper.remove();
-    }
-    if (wrapperRef.current != null) {
-      (wrapperRef.current as any).hydrated = false;
-    }
-  }, []);
-
-  // default true
-  const isGfmMode = rest.isGfmMode ?? true;
-
-  return (
-    <CodeMirror
-      ref={(elem) => {
-        // register to wrapperRef
-        wrapperRef.current = elem;
-        // register to forwardedRef
-        if (forwardedRef != null) {
-          if (typeof forwardedRef === 'function') {
-            forwardedRef(elem);
-          }
-          else {
-            (forwardedRef as MutableRefObject<CodeMirror|null>).current = elem;
-          }
-        }
-      }}
-      value={value}
-      options={{
-        lineNumbers: lineNumbers ?? true,
-        mode: isGfmMode ? 'gfm-growi' : undefined,
-        tabSize: 4,
-        ...options,
-      }}
-      editorDidMount={editorDidMountHandler}
-      editorWillUnmount={editorWillUnmountHandler}
-      {...rest}
-    />
-  );
-
-});
-
-UncontrolledCodeMirror.displayName = 'UncontrolledCodeMirror';

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

@@ -1,16 +0,0 @@
-
-export interface IResHackmdIntegrated {
-  ok: boolean,
-  error?: any,
-  pageIdOnHackmd?: string,
-  revisionIdHackmdSynced?: string,
-  hasDraftOnHackmd?: string
-}
-
-export interface IResHackmdDiscard {
-  ok: boolean,
-  error?: any,
-  pageIdOnHackmd?: string,
-  revisionIdHackmdSynced?: string,
-  hasDraftOnHackmd?: string
-}

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

@@ -1,346 +0,0 @@
-import * as hackmdFiles from '@growi/hackmd';
-
-import loggerFactory from '~/utils/logger';
-
-/* eslint-disable no-use-before-define */
-
-const logger = loggerFactory('growi:routes:hackmd');
-
-const axios = require('axios');
-const ejs = require('ejs');
-
-const ApiResponse = require('../util/apiResponse');
-
-/**
- * @swagger
- *
- *  components:
- *    schemas:
- *      Hackmd:
- *        description: Hackmd
- *        type: object
- *        properties:
- *          pageIdOnHackmd:
- *            type: string
- *            description: page ID on HackMD
- *            example: qLnodHLxT6C3hVEVczvbDQ
- *          revisionIdHackmdSynced:
- *            $ref: '#/components/schemas/Revision/properties/_id'
- *          hasDraftOnHackmd:
- *            type: boolean
- *            description: has draft on HackMD
- *            example: false
- */
-module.exports = function(crowi, app) {
-  const Page = crowi.models.Page;
-  const pageEvent = crowi.event('page');
-
-  /**
-   * GET /_hackmd/load-agent
-   *
-   * loadAgent action
-   * This should be access from HackMD and send agent script
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const loadAgent = function(req, res) {
-
-    const origin = crowi.appService.getSiteUrl();
-
-    // generate definitions to replace
-    const definitions = {
-      origin,
-    };
-
-    // inject origin to script
-    const script = ejs.render(hackmdFiles.agentJS, definitions);
-
-    res.set('Content-Type', 'application/javascript');
-    res.send(script);
-  };
-
-  /**
-   * GET /_hackmd/load-styles
-   *
-   * loadStyles action
-   * This should be access from HackMD and send script to insert styles
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const loadStyles = function(req, res) {
-
-    // generate definitions to replace
-    const definitions = {
-      styles: hackmdFiles.stylesCSS,
-    };
-    // inject styles to script
-    const script = ejs.render(hackmdFiles.stylesJS, definitions);
-
-    res.set('Content-Type', 'application/javascript');
-    res.send(script);
-  };
-
-  const validateForApi = async function(req, res, next) {
-    // validate process.env.HACKMD_URI
-    const hackmdUri = process.env.HACKMD_URI;
-    if (hackmdUri == null) {
-      return res.json(ApiResponse.error('HackMD for GROWI has not been setup'));
-    }
-    // validate pageId
-    const pageId = req.body.pageId;
-    if (pageId == null) {
-      return res.json(ApiResponse.error('pageId required'));
-    }
-    // validate page
-    const page = await Page.findOne({ _id: pageId });
-    if (page == null) {
-      return res.json(ApiResponse.error(`Page('${pageId}') does not exist`));
-    }
-
-    req.page = page;
-    next();
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.integrate:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: integrateHackmd
-   *        summary: /hackmd.integrate
-   *        description: Integrate hackmd
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to integrate HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    pageIdOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/pageIdOnHackmd'
-   *                    revisionIdHackmdSynced:
-   *                      $ref: '#/components/schemas/Hackmd/properties/revisionIdHackmdSynced'
-   *                    hasDraftOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/hasDraftOnHackmd'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.integrate
-   *
-   * Create page on HackMD and start to integrate
-   * @param {object} req
-   * @param {object} res
-   */
-  const integrate = async function(req, res) {
-    const hackmdUri = process.env.HACKMD_URI_FOR_SERVER || process.env.HACKMD_URI;
-    let page = req.page;
-
-    const hackmdPageUri = (page.pageIdOnHackmd != null)
-      ? `${hackmdUri}/${page.pageIdOnHackmd}`
-      : `${hackmdUri}/new`;
-
-    let hackmdResponse;
-    try {
-      // check if page is found or created in HackMD
-      hackmdResponse = await axios.get(hackmdPageUri, {
-        maxRedirects: 0,
-        // validate HTTP status is 200 or 302 or 404
-        validateStatus: (status) => {
-          return status === 200 || status === 302 || status === 404;
-        },
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error(err));
-    }
-
-    const { status, headers } = hackmdResponse;
-
-    // validate HackMD/CodiMD/HedgeDoc specific header
-    if (headers['codimd-version'] == null && headers['hackmd-version'] == null && headers['hedgedoc-version'] == null) {
-      const message = 'Connecting to a non-HackMD server.';
-      logger.error(message);
-      return res.json(ApiResponse.error(message));
-    }
-
-    try {
-      // when page is not found
-      if (status === 404) {
-        // reset registered data
-        page = await Page.registerHackmdPage(page, undefined);
-        // re-invoke
-        return integrate(req, res);
-      }
-
-      // when redirect
-      if (status === 302) {
-        // extract page id on HackMD
-        const pathnameOnHackmd = new URL(headers.location, hackmdUri).pathname; // e.g. '/NC7bSRraT1CQO1TO7wjCPw'
-        const pageIdOnHackmd = pathnameOnHackmd.substr(1); //                      strip the head '/'
-
-        page = await Page.registerHackmdPage(page, pageIdOnHackmd);
-      }
-      // when page is found
-      else {
-        page = await Page.syncRevisionToHackmd(page);
-      }
-
-      const data = {
-        pageIdOnHackmd: page.pageIdOnHackmd,
-        revisionIdHackmdSynced: page.revisionHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      return res.json(ApiResponse.success(data));
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('Integration with HackMD process failed'));
-    }
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.discard:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: discardHackmd
-   *        summary: /hackmd.discard
-   *        description: Discard hackmd
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to integrate HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *                    pageIdOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/pageIdOnHackmd'
-   *                    revisionIdHackmdSynced:
-   *                      $ref: '#/components/schemas/Hackmd/properties/revisionIdHackmdSynced'
-   *                    hasDraftOnHackmd:
-   *                      $ref: '#/components/schemas/Hackmd/properties/hasDraftOnHackmd'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.discard
-   *
-   * Create page on HackMD and start to integrate
-   * @param {object} req
-   * @param {object} res
-   */
-  const discard = async function(req, res) {
-    let page = req.page;
-
-    try {
-      page = await Page.syncRevisionToHackmd(page);
-
-      const data = {
-        pageIdOnHackmd: page.pageIdOnHackmd,
-        revisionIdHackmdSynced: page.revisionHackmdSynced,
-        hasDraftOnHackmd: page.hasDraftOnHackmd,
-      };
-      return res.json(ApiResponse.success(data));
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('discard process failed'));
-    }
-  };
-
-  /**
-   * @swagger
-   *
-   *    /hackmd.saveOnHackmd:
-   *      post:
-   *        tags: [Hackmd]
-   *        operationId: saveOnHackmd
-   *        summary: /hackmd.saveOnHackmd
-   *        description: Receive when save operation triggered on HackMD
-   *        requestBody:
-   *          content:
-   *            application/json:
-   *              schema:
-   *                properties:
-   *                  pageId:
-   *                    $ref: '#/components/schemas/Page/properties/_id'
-   *                  page:
-   *                    $ref: '#/components/schemas/Hackmd'
-   *        responses:
-   *          200:
-   *            description: Succeeded to receive when save operation triggered on HackMD.
-   *            content:
-   *              application/json:
-   *                schema:
-   *                  properties:
-   *                    ok:
-   *                      $ref: '#/components/schemas/V1Response/properties/ok'
-   *          403:
-   *            $ref: '#/components/responses/403'
-   *          500:
-   *            $ref: '#/components/responses/500'
-   */
-  /**
-   * POST /_api/hackmd.saveOnHackmd
-   *
-   * receive when save operation triggered on HackMD
-   * !! This will be invoked many time from many people !!
-   *
-   * @param {object} req
-   * @param {object} res
-   */
-  const saveOnHackmd = async function(req, res) {
-    const { page, user } = req;
-
-    try {
-      await Page.updateHasDraftOnHackmd(page, true);
-      pageEvent.emit('saveOnHackmd', page, user);
-      return res.json(ApiResponse.success());
-    }
-    catch (err) {
-      logger.error(err);
-      return res.json(ApiResponse.error('saveOnHackmd process failed'));
-    }
-  };
-
-  return {
-    loadAgent,
-    loadStyles,
-    validateForApi,
-    integrate,
-    discard,
-    saveOnHackmd,
-  };
-};

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

@@ -1,22 +0,0 @@
-import { SWRResponse } from 'swr';
-
-import { useStaticSWR } from './use-static-swr';
-
-type Nullable<T> = T | null;
-
-export const usePageIdOnHackmd = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('pageIdOnHackmd', initialData);
-};
-
-
-export const useHasDraftOnHackmd = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('hasDraftOnHackmd', initialData);
-};
-
-export const useRevisionIdHackmdSynced = (initialData?: Nullable<any>): SWRResponse<Nullable<any>, Error> => {
-  return useStaticSWR<Nullable<any>, Error>('revisionIdHackmdSynced', initialData);
-};
-
-export const useIsHackmdDraftUpdatingInRealtime = (initialData?: Nullable<boolean>): SWRResponse<Nullable<boolean>, Error> => {
-  return useStaticSWR<Nullable<boolean>, Error>('isHackmdDraftUpdatingInRealtime', initialData);
-};

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

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

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

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

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

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

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

@@ -1,32 +0,0 @@
-@use 'bootstrap/scss/functions' as bs;
-
-@function contrast($color, $darken-degrees: 0%, $alpha-degrees: 100%) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  // @return hsl($color-hs, clamp(10%, calc((100% - $color-l - 51% ) * 1000), 95%));
-  @return hsla($color-hs, clamp(10%, calc((100% - $color-l - $darken-degrees - 51% ) * 1000), 95%), $alpha-degrees);
-}
-
-@function lighten($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsl($color-hs, calc($color-l + $degrees));
-}
-@function darken($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsl($color-hs, calc($color-l - $degrees));
-}
-@function alpha($color, $degrees) {
-  $color: bs.str-replace($color, 'var(');
-  $color: bs.str-replace($color, ')');
-  $color-hs: var(#{$color+'-hs'});
-  $color-l: var(#{$color+'-l'});
-  @return hsla($color-hs,$color-l,$degrees);
-}

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

@@ -1,108 +0,0 @@
-@use '../bootstrap/init' as *;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-@use './mixins/hsl-button';
-@use './mixins/hsl-badge';
-@use './hsl-functions' as hsl;
-
-$hsl-colors: (
-  'primary': var(--primary),
-  'secondary': var(--secondary)
-);
-
-@each $color, $value in $hsl-colors {
-  .bg-#{$color} {
-    background-color: $value !important;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .border-#{$color} {
-    border-color: $value !important;
-  }
-}
-
-// TODO: hover-focus() dropped in bootstrap v5
-// https://redmine.weseek.co.jp/issues/128307
-// @each $color, $value in $hsl-colors {
-//   .text-#{$color} {
-//     color: $value !important;
-//     @if $emphasized-link-hover-darken-percentage != 0 {
-//       a {
-//         @include hover-focus() {
-//           color: hsl.darken($value, $emphasized-link-hover-darken-percentage) !important;
-//         }
-//       }
-//     }
-//   }
-// }
-
-@each $color, $value in $hsl-colors {
-  .btn-#{$color} {
-    @include hsl-button.button-variant($value, $value);
-    @include hsl-button.button-svg-icon-variant($value, $value);
-    box-shadow: none !important;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .btn-outline-#{$color} {
-    @include hsl-button.button-outline-variant($value, $value, hsl.alpha($value, 10%), $value);
-    @include mixins-buttons.button-outline-svg-icon-variant($value, $value);
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: $value;
-      background-color: hsl.alpha($value, 10%);
-      border-color: $value;
-    }
-
-    box-shadow: none !important;
-  }
-
-  // separate style: https://github.com/weseek/growi/pull/6804
-  .show > .btn-outline-#{$color}.dropdown-toggle {
-    color: $value;
-    background-color: hsl.alpha($value, 10%);
-    border-color: $value;
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .alert-#{$color} {
-    $alert-color: rgba(white,90%);
-    $alert-bg-color: hsl.darken($value, calc($alert-bg-level + 0.95) * $theme-color-interval);
-    $alert-border-color: hsl.darken($value, $alert-border-level * $theme-color-interval);
-
-    color: $alert-color;
-    @include gradient-bg($alert-bg-color);
-    border-color: $alert-border-color;
-
-    hr {
-      border-top-color: hsl.darken($value, calc($alert-border-level + 5) * $theme-color-interval);
-    }
-
-    .alert-link {
-      color: hsl.contrast($value);
-    }
-  }
-
-  // Alert link
-  :root, .wiki {
-    .alert.alert-primary {
-      a,
-      a:hover {
-        color: hsl.darken($value, calc($alert-bg-level - 7.7) * $theme-color-interval);
-      }
-    }
-  }
-}
-
-@each $color, $value in $hsl-colors {
-  .bg-#{$color} {
-    @include hsl-badge.badge-variant($value);
-  }
-
-  a.bg-#{$color}  {
-    @include hsl-badge.badge-variant($value);
-  }
-}

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

@@ -1,29 +0,0 @@
-@use '../bootstrap/init' as *;
-
-//
-// Border
-//
-
-.border {
-  border: $border-width solid $border-color !important;
-}
-
-.border-top {
-  border-top: $border-width solid $border-color !important;
-}
-
-.border-end {
-  border-right: $border-width solid $border-color !important;
-}
-
-.border-bottom {
-  border-bottom: $border-width solid $border-color !important;
-}
-
-.border-start {
-  border-left: $border-width solid $border-color !important;
-}
-
-.border-info {
-  border-color: $info !important;
-}

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

@@ -1,22 +0,0 @@
-.btn-link {
-  color: var(--color-link);
-  svg {
-    fill: var(--color-link);
-  }
-
-  &:hover {
-    color: var(--color-link-hover);
-    svg {
-      fill: var(--color-link-hover);
-    }
-  }
-
-  &:disabled,
-  &.disabled {
-    color: var(--color-link-disabled, #{$gray-500});
-    svg {
-      fill: var(--color-link-disabled, #{$gray-500});
-    }
-  }
-}
-

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

@@ -1,60 +0,0 @@
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_reboot.scss
-//
-//
-
-// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix
-
-// Body
-//
-// 1. Remove the margin in all browsers.
-// 2. As a best practice, apply a default `background-color`.
-// 3. Set an explicit initial text-align value so that we can later use
-//    the `inherit` value on things like `<th>` elements.
-
-html, body {
-  color: var(--color-global);
-  background-color: var(--bgcolor-global); // 2
-
-  svg {
-    fill: var(--color-global);
-  }
-}
-
-// Links
-
-a {
-  color: var(--color-link);
-  text-decoration: $link-decoration;
-  background-color: transparent; // Remove the gray background on active links in IE 10.
-
-  svg {
-    fill: var(--color-link);
-  }
-
-  &:hover {
-    color: var(--color-link-hover);
-    text-decoration: $link-hover-decoration;
-
-    svg {
-      fill: var(--color-link-hover);
-    }
-  }
-}
-
-// And undo these styles for placeholder links/named anchors (without href).
-// It would be more straightforward to just use a[href] in previous block, but that
-// causes specificity issues in many other styles that are too complex to fix.
-// See https://github.com/twbs/bootstrap/issues/19402
-
-// a:not([href]) {
-//   color: inherit;
-//   text-decoration: none;
-
-//   &:hover {
-//     color: inherit;
-//     text-decoration: none;
-//   }
-// }

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

@@ -1,38 +0,0 @@
-@use '../bootstrap/init' as *;
-
-.dropdown-menu {
-  color: $color-dropdown;
-  svg {
-    fill: $color-dropdown;
-  }
-
-  background-color: $bgcolor-dropdown;
-}
-
-.dropdown-item {
-  color: $color-dropdown-link;
-  svg {
-    fill: $color-dropdown-link;
-  }
-
-  // TODO: hover-focus() dropped in bootstrap v5
-  @include hover-focus() {
-    color: $color-dropdown-link;
-    svg {
-      fill: $color-dropdown-link-hover;
-    }
-
-    @include gradient-bg($bgcolor-dropdown-link-hover);
-  }
-
-  &:active,
-  &.active,
-  &:active:hover,
-  &.active:hover {
-    color: $color-dropdown-link-active;
-    background-color:  $bgcolor-dropdown-link-active;
-    svg {
-      fill: $color-dropdown-link-active;
-    }
-  }
-}

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

@@ -1,52 +0,0 @@
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_nav.scss
-//
-//
-
-.nav-link {
-  // Disabled state lightens text
-  &.disabled {
-    color: $nav-link-disabled-color;
-    svg {
-      fill: $nav-link-disabled-color;
-    }
-  }
-}
-
-//
-// Tabs
-//
-
-.nav-tabs {
-  border-bottom: $nav-tabs-border-width solid $nav-tabs-border-color;
-
-  .nav-link {
-    border: $nav-tabs-border-width solid transparent;
-    @include border-top-radius($nav-tabs-border-radius);
-
-    // TODO: hover-focus() dropped in bootstrap v5
-    @include hover-focus() {
-      border-color: $nav-tabs-link-hover-border-color;
-    }
-
-    &.disabled {
-      color: $nav-link-disabled-color;
-      background-color: transparent;
-      border-color: transparent;
-    }
-  }
-
-  .nav-link.active,
-  .nav-item.show .nav-link {
-    color: $nav-tabs-link-active-color;
-    background-color: $nav-tabs-link-active-bg;
-    border-color: $nav-tabs-link-active-border-color;
-  }
-
-  .dropdown-menu {
-    // Remove the top rounded corners here since there is a hard edge above the menu
-    @include border-top-radius(0);
-  }
-}

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

@@ -1,74 +0,0 @@
-@use '../bootstrap/init' as *;
-
-//
-//
-// Apply partially
-//   https://github.com/twbs/bootstrap/blob/v4.5.0/scss/_tables.scss
-//
-//
-
-.table {
-  color: $color-table;
-  background-color: $bgcolor-table; // Reset for nesting within parents with `background-color`.
-
-  th,
-  td {
-    border-top-color: $border-color-table;
-  }
-
-  thead th {
-    border-bottom-color: $border-color-table;
-  }
-
-  tbody + tbody {
-    border-top-color: $border-color-table;
-  }
-}
-
-.table-bordered {
-  border-color: $border-color-table;
-
-  th,
-  td {
-    border-color: $border-color-table;
-  }
-}
-
-.table-hover {
-  tbody tr {
-    &:hover {
-      color: $color-table-hover;
-      background-color: $bgcolor-table-hover;
-    }
-  }
-}
-
-.table-dark {
-  color: $table-dark-color;
-  background-color: $table-dark-bg;
-
-  th,
-  td,
-  thead th {
-    border-color: $table-dark-border-color;
-  }
-
-  &.table-bordered {
-    border: 0;
-  }
-
-  &.table-striped {
-    tbody tr:nth-of-type(#{$table-striped-order}) {
-      background-color: $table-dark-accent-bg;
-    }
-  }
-
-  &.table-hover {
-    tbody tr {
-      &:hover {
-        color: $table-dark-hover-color;
-        background-color: $table-dark-hover-bg;
-      }
-    }
-  }
-}

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

@@ -1,3 +0,0 @@
-.text-muted {
-  color: $text-muted !important;
-}

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

@@ -1,103 +0,0 @@
-@use '../bootstrap/init' as *;
-@use '../atoms/mixins/buttons' as mixins-buttons;
-
-@each $color, $value in $theme-colors {
-  @include bg-variant('.bg-#{$color}', $value);
-}
-
-@each $color, $value in $theme-colors {
-  .border-#{$color} {
-    border-color: $value;
-  }
-}
-
-@each $color, $value in $theme-colors {
-  @include text-emphasis-variant('.text-#{$color}', $value, true);
-}
-
-@each $color, $value in $theme-colors {
-  .btn-#{$color} {
-    @include button-variant($value, $value);
-    @include mixins-buttons.button-svg-icon-variant($value, $value);
-    box-shadow: none !important;
-  }
-}
-
-@each $color, $value in $theme-colors {
-  .btn-outline-#{$color} {
-    @include button-outline-variant($value, $color-hover: $value, $active-background: rgba($value, 0.1), $active-border: $value);
-    @include mixins-buttons.button-outline-svg-icon-variant($value, $color-hover: $value);
-
-    &:not(:disabled):not(.disabled):active,
-    &:not(:disabled):not(.disabled).active {
-      color: $value;
-      background-color: rgba($value, 0.1);
-      border-color: $value;
-    }
-
-    box-shadow: none !important;
-  }
-
-  // separate style: https://github.com/weseek/growi/pull/6804
-  .show > .btn-outline-#{$color}.dropdown-toggle {
-    color: $value;
-    background-color: rgba($value, 0.1);
-    border-color: $value;
-  }
-}
-
-@each $theme-color, $color in $theme-colors {
-  .form-check-#{$theme-color} {
-    .form-check-label::before {
-      border-color: $input-border-color;
-      transition: 0.3s ease-in-out;
-    }
-    .form-check-input:checked + .form-check-label::before {
-      background-color: $color;
-      border-color: $color;
-    }
-    .form-check-input:checked + .form-check-label::after {
-      color: var(--bgcolor-global);
-    }
-    .form-check-input:not(:disabled):active ~ .form-check-label::before {
-      color: var(--bgcolor-global);
-      background-color: $color;
-      border-color: $color;
-    }
-    .form-check-input:focus:not(:checked) ~ .form-check-label::before {
-      color: var(--bgcolor-global);
-      background-color: var(--bgcolor-global);
-      border-color: $input-focus-border-color;
-    }
-  }
-}
-
-// TODO: activate (https://redmine.weseek.co.jp/issues/128307)
-// theme-color-level() dropped in bootstrap v5
-// @each $color, $value in $theme-colors {
-//   .alert-#{$color} {
-//     @include alert-variant(
-//       theme-color-level($color, $alert-bg-level),
-//       theme-color-level($color, $alert-border-level),
-//       theme-color-level($color, $alert-color-level)
-//     );
-//   }
-//   // Alert link
-//   :root, .wiki {
-//     .alert.alert-#{$color} {
-//       a,
-//       a:hover {
-//         color: theme-color-level($color, $alert-color-level - 2);
-//       }
-//     }
-//   }
-// }
-
-@each $color, $value in $theme-colors {
-  .bg-#{$color} {
-    @include badge-variant($value);
-  }
-  a.bg-#{$color} {
-    @include badge-variant($value);
-  }
-}

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

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

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

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

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

@@ -1,9 +0,0 @@
-@mixin count-badge($color, $bg-color, $min-width: initial) {
-  min-width: $min-width;
-  padding: 0.1rem 0.5rem;
-  font-family: var(--font-family-monospace);
-  font-size: 12px;
-  font-weight: 500;
-  color: $color;
-  background-color: $bg-color;
-}

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

@@ -1,23 +0,0 @@
-@use '../../bootstrap/init' as bs;
-@use '../hsl-functions' as hsl;
-
-// @mixin badge-variant($bg) {
-@mixin badge-variant($bg) {
-  color: hsl.contrast($bg);
-  background-color: $bg;
-
-  @at-root a#{&} {
-    // TODO: hover-focus() dropped in bootstrap v5
-    // https://redmine.weseek.co.jp/issues/128307
-    // @include bs.hover-focus() {
-    //   color: hsl.contrast($bg);
-    //   background-color: hsl.darken($bg, 10%);
-    // }
-
-    &:focus,
-    &.focus {
-      outline: 0;
-      // box-shadow: 0 0 0 $badge-focus-width hsl.alpha($bg, 50%);
-    }
-  }
-}

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

@@ -1,146 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-@use '../hsl-functions' as hsl;
-
-// @mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) {
-@mixin button-variant($background, $border, $hover-background-darken-degrees: 7.5%, $hover-border-darken-degrees: 10%, $active-background-darken-degrees: 10%, $active-border-darken-degrees: 12.5%) {
-  $hover-background: hsl.darken($background, $hover-background-darken-degrees);  // DO NOT ... twice
-  $hover-border: hsl.darken($border, $hover-border-darken-degrees);  // DO NOT ... twice
-
-  color: hsl.contrast($background);
-  @include bs.gradient-bg($background);
-  border-color: $border;
-  // @include box-shadow($btn-box-shadow);
-
-  &:hover {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($hover-background);
-    border-color: $hover-border;
-  }
-
-  &:focus,
-  &.focus {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($hover-background);
-    border-color: $hover-border;
-    // TODO: color-yiq() to color-contrast()
-    // https://redmine.weseek.co.jp/issues/128307
-    // @if $enable-shadows {
-    //   @include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
-    // } @else {
-    //   // Avoid using mixin so we can pass custom focus shadow properly
-    //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
-    // }
-  }
-
-  // // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    color: hsl.contrast($background);
-    @include bs.gradient-bg($background);
-    border-color: $border;
-    // Remove CSS gradients if they're enabled
-    @if bs.$enable-gradients {
-      background-image: none;
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    color: hsl.contrast($background);
-    background-color: $hover-background;
-    border-color: $hover-border;
-  }
-  //   @if $enable-gradients {
-  //     background-image: none; // Remove the gradient for the pressed/active state
-  //   }
-  //   border-color: $active-border;
-
-  // TODO: color-yiq() to color-contrast()
-  // https://redmine.weseek.co.jp/issues/128307
-  //   &:focus {
-  //     // @if $enable-shadows and $btn-active-box-shadow != none {
-  //     //   @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
-  //     // } @else {
-  //     //   // Avoid using mixin so we can pass custom focus shadow properly
-  //     //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
-  //     // }
-  //   }
-  // }
-}
-
-// @mixin button-outline-variant($color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) {
-@mixin button-outline-variant($color, $color-hover: hsl.contrast($color), $active-background: $color, $active-border: $color) {
-  color: $color;
-  border-color: $color;
-
-  &:hover {
-    color: $color-hover;
-    background-color: $active-background;
-    border-color: $active-border;
-  }
-
-  // &:focus,
-  // &.focus {
-  //   box-shadow: 0 0 0 bs.$btn-focus-width hsl.alpha($color,50%);
-  // }
-
-  &.disabled,
-  &:disabled {
-    color: $color;
-    background-color: transparent;
-  }
-
-  // &:not(:disabled):not(.disabled):active,
-  // &:not(:disabled):not(.disabled).active,
-  // .show > &.dropdown-toggle {
-  //   color: hsl.contrast($active-background);
-  //   background-color: $active-background;
-  //   border-color: $active-border;
-
-  // &:focus {
-  //   @if $enable-shadows and $btn-active-box-shadow != none {
-  //     @include bs.box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width hsl.alpha($color,50%));
-  //   } @else {
-  //     // Avoid using mixin so we can pass custom focus shadow properly
-  //     box-shadow: 0 0 0 $btn-focus-width hsl.alpha($color,50%);
-  //   }
-  // }
-  // }
-}
-
-// @mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
-@mixin button-svg-icon-variant($background, $hover-background-darken-degrees: 7.5%, $active-background-darken-degrees: 10%) {
-  svg {
-    fill: hsl.contrast($background);
-  }
-
-  &:hover {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  &:focus,
-  &.focus {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  // Disabled comes first so active can properly restyle
-  &.disabled,
-  &:disabled {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active,
-  .show > &.dropdown-toggle {
-    svg {
-      fill: hsl.contrast($background);
-    }
-  }
-}

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

@@ -1,72 +0,0 @@
-@use '@growi/core/scss/bootstrap/init' as bs;
-
-@use '../../mixins';
-@use './count-badge';
-
-@mixin override-list-group-item($color, $bgcolor, $color-hover: $color, $bgcolor-hover: $bgcolor, $color-active: $color, $bgcolor-active: $bgcolor) {
-  .list-group {
-    .list-group-item {
-      color: $color;
-      background-color: $bgcolor;
-      border-color: $border-color-global;
-
-      &.list-group-item-action {
-        &:hover {
-          background-color: $bgcolor-hover;
-        }
-        &.active {
-          color: $color-active;
-          background-color: $bgcolor-active;
-        }
-      }
-    }
-  }
-}
-
-@mixin override-list-group-item-for-pagetree($color, $bgcolor-hover, $bgcolor-active, $btn-color, $btn-color-hover, $btn-bgcolor-hover, $btn-bgcolor-active) {
-  .grw-pagetree-is-over {
-    background: $bgcolor-hover;
-  }
-  .list-group-item {
-    color: $color;
-    background-color: transparent;
-    border-color: $border-color-global;
-
-    .grw-count-badge {
-      @include count-badge.count-badge($btn-color, $bgcolor-hover, 28px);
-    }
-
-    .btn.btn-page-item-control {
-      color: $btn-color;
-      background-color: transparent;
-      &:hover {
-        color: $btn-color-hover;
-        background-color: $btn-bgcolor-hover;
-      }
-      &:not(:disabled):not(.disabled):active,
-      &:not(:disabled):not(.disabled).active {
-        color: $btn-color-hover;
-        background-color: $btn-bgcolor-active;
-      }
-    }
-
-    &.grw-pagetree-current-page-item {
-      background: $bgcolor-hover;
-    }
-
-    &.list-group-item-action {
-      &:hover {
-        background-color: $bgcolor-hover;
-      }
-      &:active {
-        background-color: $bgcolor-active;
-      }
-    }
-    .grw-pagetree-title-anchor,
-    .grw-foldertree-title-anchor {
-      .grw-sidebar-text-muted {
-        // color: rgba(desaturate($color, 50%), 0.6);
-      }
-    }
-  }
-}

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

@@ -1,22 +0,0 @@
-@mixin btn-page-editor-mode-manager($textColor, $borderColor, $bgColorHoverAndActive, $bgColor: white) {
-  color: $textColor;
-  background-color: $bgColor;
-  border-color: $borderColor;
-
-  &:not(:first-child) {
-    &::before {
-      border-left-color: $borderColor;
-    }
-  }
-
-  &:hover,
-  &:active,
-  &.active {
-    color: $textColor;
-    background-color: $bgColorHoverAndActive;
-    border-color: $borderColor;
-    &::after {
-      border-color: $bgColorHoverAndActive;
-    }
-  }
-}

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

+ 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

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

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

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

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

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

+ 33 - 28
apps/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "7.0.0-RC.0",
+  "version": "7.0.2-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -35,11 +35,12 @@
     "prelint:swagger2openapi": "yarn openapi:v3",
     "test": "run-p test:*",
     "test:jest": "cross-env NODE_ENV=test NODE_OPTIONS=\"--max-old-space-size=4096\" jest --logHeapUsage",
-    "test:vitest": "run-p vitest:run vitest:run:integ",
+    "test:vitest": "run-p vitest:run vitest:run:integ vitest:run:components",
     "jest:run": "cross-env NODE_ENV=test jest --passWithNoTests -- ",
     "reg:run": "reg-suit run",
     "vitest:run": "vitest run config src --coverage",
     "vitest:run:integ": "vitest run -c vitest.config.integ.ts src --coverage",
+    "vitest:run:components": "vitest run -c vitest.config.components.ts src --coverage",
     "previtest:run:integ": "vitest run -c test-with-vite/download-mongo-binary/vitest.config.ts test-with-vite/download-mongo-binary",
     "//// misc": "",
     "console": "yarn cross-env NODE_ENV=development yarn ts-node --experimental-repl-await src/server/console.js",
@@ -54,22 +55,23 @@
     "@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",
     "@elastic/elasticsearch8": "npm:@elastic/elasticsearch@^8.7.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/custom-icons": "link:../../packages/custom-icons",
     "@growi/core": "link:../../packages/core",
+    "@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",
@@ -100,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",
@@ -122,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",
@@ -143,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",
@@ -162,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",
@@ -170,7 +172,7 @@
     "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",
@@ -195,7 +197,7 @@
     "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",
@@ -206,12 +208,14 @@
     "universal-bunyan": "^0.9.2",
     "unstated": "^2.1.1",
     "unzip-stream": "^0.3.1",
-    "unzipper": "^0.10.5",
     "url-join": "^4.0.0",
     "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.",
@@ -222,32 +226,33 @@
     "@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",
+    "@testing-library/user-event": "^14.5.2",
     "@types/express": "^4.17.11",
     "@types/jest": "^29.5.2",
     "@types/react-scroll": "^1.8.4",
     "@types/throttle-debounce": "^5.0.1",
-    "@types/url-join": "^4.0.2",
     "@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": "^5.3.1",
     "connect-browser-sync": "^2.1.0",
-    "diff2html": "^3.4.35",
+    "diff2html": "^3.4.47",
     "downshift": "^8.2.3",
     "eazy-logger": "^3.1.0",
-    "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",
-    "i18next-hmr": "^1.11.0",
+    "happy-dom": "^13.2.0",
+    "i18next-hmr": "^3.0.4",
     "jest": "^29.5.0",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
@@ -261,12 +266,12 @@
     "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


+ 13 - 10
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",
@@ -744,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 me-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.",
@@ -832,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",
@@ -857,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"
     }

+ 95 - 69
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",
@@ -108,11 +110,12 @@
   "Error occurred": "Error occurred",
   "Input page name": "Input page name",
   "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
   "New Page": "New page",
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "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",
@@ -145,9 +148,9 @@
   "Recent Changes": "Recent Changes",
   "Page Tree": "Page Tree",
   "In-App Notification": "Notifications",
-  "original_path":"Original path",
-  "new_path":"New path",
-  "duplicated_path":"Duplicated path",
+  "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",
@@ -213,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",
@@ -226,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",
@@ -234,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",
@@ -307,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",
@@ -328,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",
@@ -336,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": {
@@ -375,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.",
@@ -389,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",
@@ -430,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)"
   },
@@ -463,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": {
@@ -529,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",
@@ -550,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",
@@ -581,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": {
@@ -608,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.",
@@ -637,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",
@@ -669,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",
@@ -699,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",
@@ -711,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",
@@ -761,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": {
@@ -787,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": {
@@ -822,5 +834,19 @@
   },
   "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"
   }
 }

+ 13 - 10
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": "管理者とページ作者が可能",
@@ -754,7 +756,7 @@
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
       "description2": "初回のログイン時に使用する仮パスワードが生成されます。",
       "invite_thru_email": "招待メールを送信する",
-      "mail_setting_link": "<i class='icon-settings me-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": "新規発行したパスワードを、対象ユーザーへ連絡してください。",
@@ -842,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": "グループの親が変更されます",
@@ -867,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"
     }

+ 93 - 67
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 アカウントで登録します",
@@ -107,11 +109,12 @@
   "Error occurred": "エラーが発生しました",
   "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": "基本設定",
@@ -146,9 +149,9 @@
   "Recent Changes": "最新の変更",
   "Page Tree": "ページツリー",
   "In-App Notification": "通知",
-  "original_path":"元のパス",
-  "new_path":"新しいパス",
-  "duplicated_path":"重複したパス",
+  "original_path": "元のパス",
+  "new_path": "新しいパス",
+  "duplicated_path": "重複したパス",
   "Link sharing is disabled": "リンクのシェアは無効化されています",
   "successfully_saved_the_page": "ページが正常に保存されました",
   "you_can_not_create_page_with_this_name": "この名前でページを作成することはできません",
@@ -213,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": "パスワードを新規に設定",
@@ -227,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": "カスタム",
@@ -235,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設定",
@@ -281,7 +284,7 @@
       "no_zero_width_spaces": "ゼロ幅スペースを許可しません。",
       "period_in_list_item": "リストアイテムのピリオドの有無をチェックします。",
       "use_si_units": "SI単位系以外の使用を禁止します。"
-      },
+    },
     "japanese_settings": {
       "japanese_settings": "日本語設定",
       "ja_hiragana_keishikimeishi": "漢字よりひらがなで書かれた読みやすい形式名詞をチェックします。",
@@ -335,7 +338,7 @@
     "notice": {
       "version": "これは最新のバージョンではありません。",
       "redirected": "リダイレクト元 >>",
-      "redirected_period":"",
+      "redirected_period": "",
       "unlinked": "このページへのリダイレクトは削除されました。",
       "restricted": "このページの閲覧は制限されています",
       "stale": "このページは最終更新日から{{count}}年以上が経過しています。",
@@ -344,6 +347,11 @@
     }
   },
   "page_edit": {
+    "input_channels": "チャンネル名",
+    "theme": "テーマ",
+    "keymap": "キーマップ",
+    "indent": "インデント",
+    "editor_config": "エディタ設定",
     "Show active line": "アクティブ行をハイライト",
     "auto_format_table": "表の自動整形",
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
@@ -361,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": "更新履歴",
@@ -369,8 +378,8 @@
     "comparing_source": "ソース",
     "comparing_target": "ターゲット",
     "comparing_revisions": "差分を比較する",
-    "compare_latest":"最新と比較",
-    "compare_previous":"1つ前のバージョンと比較"
+    "compare_latest": "最新と比較",
+    "compare_previous": "1つ前のバージョンと比較"
   },
   "modal_rename": {
     "label": {
@@ -408,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": "権限がないため、いくつかのページは削除できません",
@@ -422,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}} を複製しました",
@@ -463,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": "保存するページ本文(編集可能)"
   },
@@ -496,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": {
@@ -563,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": "作成日時",
@@ -583,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": "以下のページを変換します",
@@ -614,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": {
@@ -640,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": "ユーザー名に不正な文字が含まれています.",
@@ -670,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": "ログイン画面に戻る",
@@ -698,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": "リセット",
@@ -732,7 +744,7 @@
       "6": "肌の色が濃い"
     }
   },
-  "maintenance_mode":{
+  "maintenance_mode": {
     "maintenance_mode": "メンテナンスモード",
     "growi_is_under_maintenance": "GROWI はメンテナンス中です。終了するまでお待ちください",
     "admin_page": "管理画面へ",
@@ -744,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": "グループを選ぶ",
@@ -794,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": {
@@ -820,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": {
@@ -855,5 +867,19 @@
   },
   "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"
   }
 }

+ 13 - 10
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": "管理员|作者",
@@ -752,7 +754,7 @@
       "emails": "电子邮件",
       "description1": "通过电子邮件地址临时发布新用户。",
       "description2": "将为首次登录生成一个临时密码。",
-      "mail_setting_link": "<i class='icon-settings me-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": "创建的用户具有临时密码",
@@ -841,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": "该组的父组被改变",
@@ -866,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"
     }

+ 396 - 370
apps/app/public/static/locales/zh_CN/translation.json

@@ -4,59 +4,61 @@
   },
   "Help": "帮助",
   "view": "View",
-	"Edit": "编辑",
-	"Delete": "删除",
-	"delete_all": "删除所有",
-	"Duplicate": "复制",
+  "Edit": "编辑",
+  "Delete": "删除",
+  "delete_all": "删除所有",
+  "Duplicate": "复制",
   "PathRecovery": "路径恢复",
-	"Copy": "复制",
-  "preview":"预览",
-  "desktop":"电脑",
-  "phone":"手机",
-  "tablet":"平板",
-	"Login": "登录",
-	"Click to copy": "点击复制",
+  "Copy": "复制",
+  "preview": "预览",
+  "desktop": "电脑",
+  "phone": "手机",
+  "tablet": "平板",
+  "Login": "登录",
+  "Click to copy": "点击复制",
   "Rename": "重命名",
-	"Move/Rename": "移动/重命名",
-	"Redirected": "重定向",
-	"Unlinked": "Unlinked",
+  "Move/Rename": "移动/重命名",
+  "Redirected": "重定向",
+  "Unlinked": "Unlinked",
   "unlink_redirection": "取消链接重定向",
   "Done": "Done",
   "Cancel": "取消",
-	"Create": "创建",
+  "Create": "创建",
   "Description": "描述",
-	"Admin": "管理",
-	"administrator": "管理员",
-	"Tags": "Tags",
+  "Admin": "管理",
+  "administrator": "管理员",
+  "Tags": "Tags",
   "Close": "Close",
-	"Shortcuts": "快捷方式",
+  "Shortcuts": "快捷方式",
   "CustomSidebar": "Custom Sidebar",
-	"eg": "e.g.",
-	"add": "添加",
-	"Undo": "撤销",
-	"account_id": "用户Id",
-	"Initialize": "初始化",
+  "eg": "e.g.",
+  "add": "添加",
+  "Undo": "撤销",
+  "account_id": "用户Id",
+  "Initialize": "初始化",
   "Update": "更新",
-	"Update Page": "更新本页",
-	"Error": "误差",
-	"Warning": "警告",
+  "Update Page": "更新本页",
+  "Error": "误差",
+  "Warning": "警告",
   "Sign in": "登录",
-	"Sign up is here": "注册",
-	"Sign in is here": "登录",
-	"Sign up": "注册",
-	"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",
-	"Example": "例如",
-	"Taro Yamada": "John Doe",
+  "Sign in with External auth": "Sign in with {{signin}}",
+  "Sign up is here": "注册",
+  "Sign in is here": "登录",
+  "Sign up": "注册",
+  "or": "或者",
+  "Sign up with Google Account": "Sign up with Google Account",
+  "Sign in with Google Account": "Sign in with Google Account",
+  "Sign up with this Google Account": "Sign up with this Google Account",
+  "Example": "例如",
+  "Taro Yamada": "John Doe",
   "Select": "请选择",
   "Required": "必需的",
-	"List View": "列表",
-	"Timeline View": "时间线",
+  "List View": "列表",
+  "Timeline View": "时间线",
   "History": "历史",
   "attachment_data": "Attachment Data",
   "No_attachments_yet": "暂无附件",
-	"Presentation Mode": "演示文稿",
+  "Presentation Mode": "演示文稿",
   "Not available for guest": "不提供给客人",
   "Not available in this version": "此版本中不提供",
   "No users have liked this yet": "还没有用户喜欢这个",
@@ -73,76 +75,77 @@
   "Specify Hierarchy": "指定层级",
   "Submitted the request to create the archive": "提交创建归档请求",
   "username": "用户名",
-	"Created": "创建",
-	"Last updated": "上次更新",
-	"Share": "分享",
+  "Created": "创建",
+  "Last updated": "上次更新",
+  "Share": "分享",
   "Share Link": "分享链接",
-	"Markdown Link": "Markdown链接",
-	"Create/Edit Template": "创建/编辑 模板页面",
-	"Unportalize": "未启动",
-	"Go to this version": "查看此版本",
-	"View diff": "查看差异",
-	"No diff": "无差异",
-	"User ID": "用户ID",
-	"Home": "首页",
-	"My Drafts": "My Drafts",
-	"User Settings": "用户设置",
-	"User Information": "用户信息",
+  "Markdown Link": "Markdown链接",
+  "Create/Edit Template": "创建/编辑 模板页面",
+  "Unportalize": "未启动",
+  "Go to this version": "查看此版本",
+  "View diff": "查看差异",
+  "No diff": "无差异",
+  "User ID": "用户ID",
+  "Home": "首页",
+  "My Drafts": "My Drafts",
+  "User Settings": "用户设置",
+  "User Information": "用户信息",
   "User Activation": "用户激活",
-	"Basic Info": "基础信息",
-	"Name": "姓名",
-	"Email": "邮箱",
-	"Language": "语言",
-	"English": "英语",
-	"Japanese": "日语",
-	"Chinese": "简体中文",
-	"Set Profile Image": "头像",
-	"Upload Image": "上传图片",
-	"Current Image": "当前图片",
-	"Delete Image": "删除图片",
-	"Delete this image?": "删除图片?",
-	"Updated": "更新",
-	"Upload new image": "上传新图像",
-	"Connected": "Connected",
-	"Show": "显示",
-	"Hide": "隐藏",
+  "Basic Info": "基础信息",
+  "Name": "姓名",
+  "Email": "邮箱",
+  "Language": "语言",
+  "English": "英语",
+  "Japanese": "日语",
+  "Chinese": "简体中文",
+  "Set Profile Image": "头像",
+  "Upload Image": "上传图片",
+  "Current Image": "当前图片",
+  "Delete Image": "删除图片",
+  "Delete this image?": "删除图片?",
+  "Updated": "更新",
+  "Upload new image": "上传新图像",
+  "Connected": "Connected",
+  "Show": "显示",
+  "Hide": "隐藏",
   "Loading": "加载...",
-	"Reset": "重置",
-	"Disclose E-mail": "显示邮箱",
-	"page exists": "页面已存在",
-	"Error occurred": "Error occurred",
-	"Input page name": "Input page name",
-	"Input page name (optional)": "Input page name (optional)",
-	"New Page": "新页面",
-	"Create under": "Create page under below:",
+  "Reset": "重置",
+  "Disclose E-mail": "显示邮箱",
+  "page exists": "页面已存在",
+  "Error occurred": "Error occurred",
+  "Input page name": "Input page name",
+  "Input page name (optional)": "Input page name (optional)",
+  "Input parent page path": "Input parent page path",
+  "New Page": "新页面",
+  "Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "更多详情请见<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": "更多详情请见<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> ",
 	"Markdown Settings": "Markdown设置",
 	"external_account_management": "外部账户管理",
   "UserGroup": "用户组",
   "ChildUserGroup": "儿童用户组",
-	"Basic Settings": "基础设置",
-	"The contents entered here will be shown in the header etc": "此处输入的内容将显示在标题等中",
-	"Public": "公共",
-	"Anyone with the link": "任何人",
-	"Specified users only": "仅指定用户",
-	"Only me": "只有我",
+  "Basic Settings": "基础设置",
+  "The contents entered here will be shown in the header etc": "此处输入的内容将显示在标题等中",
+  "Public": "公共",
+  "Anyone with the link": "任何人",
+  "Specified users only": "仅指定用户",
+  "Only me": "只有我",
   "Only inside the group": "仅组内",
   "page_list": "Page List",
-	"Reselect the group": "重新选择组",
-	"Shareable link": "可分享链接",
-	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
-	"Add tags for this page": "添加标签",
+  "Reselect the group": "重新选择组",
+  "Shareable link": "可分享链接",
+  "The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
+  "Add tags for this page": "添加标签",
   "tag_list": "标签列表",
   "popular_tags": "流行标签",
   "Check All tags": "检查所有标签",
-	"You have no tag, You can set tags on pages": "你没有标签,可以在页面上设置标签",
-	"Show latest": "显示最新",
-	"Load latest": "家在最新",
-	"edited this page": "edited this page.",
-	"List Drafts": "草稿",
-	"Deleted Pages": "已删除页",
+  "You have no tag, You can set tags on pages": "你没有标签,可以在页面上设置标签",
+  "Show latest": "显示最新",
+  "Load latest": "家在最新",
+  "edited this page": "edited this page.",
+  "List Drafts": "草稿",
+  "Deleted Pages": "已删除页",
   "Disassociate": "解除关联",
   "No bookmarks yet": "暂无书签",
   "add_bookmark": "添加到书签",
@@ -151,9 +154,9 @@
   "Recent Changes": "最新修改",
   "Page Tree": "页面树",
   "In-App Notification": "通知",
-  "original_path":"Original path",
-  "new_path":"New path",
-  "duplicated_path":"Duplicated path",
+  "original_path": "Original path",
+  "new_path": "New path",
+  "duplicated_path": "Duplicated path",
   "Link sharing is disabled": "你不允许分享该链接",
   "successfully_saved_the_page": "成功地保存了该页面",
   "you_can_not_create_page_with_this_name": "您无法使用此名称创建页面",
@@ -161,10 +164,10 @@
   "Confirm": "确定",
   "Successfully requested": "进程成功接受",
   "copied_to_clipboard": "它已复制到剪贴板。",
-	"form_validation": {
-		"error_message": "有些值不正确",
-		"required": "%s 是必需的",
-		"invalid_syntax": "%s的语法无效。",
+  "form_validation": {
+    "error_message": "有些值不正确",
+    "required": "%s 是必需的",
+    "invalid_syntax": "%s的语法无效。",
     "title_required": "标题是必需的。",
     "field_required": "{{target}} 是必需的"
   },
@@ -177,63 +180,63 @@
   "custom_navigation": {
     "no_pages_under_this_page": "There are no pages under this page."
   },
-	"installer": {
+  "installer": {
     "tab": "创建账户",
     "title": "安装",
-		"setup": "安装",
-		"create_initial_account": "创建初始用户",
-		"initial_account_will_be_administrator_automatically": "初始帐户将自动成为管理员。",
-		"unavaliable_user_id": "用户ID不可用",
+    "setup": "安装",
+    "create_initial_account": "创建初始用户",
+    "initial_account_will_be_administrator_automatically": "初始帐户将自动成为管理员。",
+    "unavaliable_user_id": "用户ID不可用",
     "failed_to_install": "GROWI安装失败。请再试一次。",
     "failed_to_login_after_install": "安装后登录失败。重定向到登录表格..."
-	},
-	"breaking_changes": {
-		"v346_using_basic_auth": "当前使用的基本身份验证在不久的将来将不再可用。从%s中删除设置"
-	},
-	"page_register": {
+  },
+  "breaking_changes": {
+    "v346_using_basic_auth": "当前使用的基本身份验证在不久的将来将不再可用。从%s中删除设置"
+  },
+  "page_register": {
     "send_email": "发电子邮件",
-		"notice": {
-			"restricted": "需要管理员批准。",
-			"restricted_defail": "一旦管理员批准您的注册,您就可以访问此wiki。"
-		},
-		"form_help": {
-			"email": "您必须有下面列出的电子邮件地址才能注册此wiki。",
-			"password": "密码长度必须至少为8个字符。",
-			"user_id": "您创建的网页的URL将包含您的用户ID。您的用户ID可以由字母、数字和一些符号组成。"
-		}
-	},
-	"Settings": "设置",
-	"page_me": {
-		"form_help": {
-			"profile_image1": "图像上传设置未完成。",
-			"profile_image2": "设置AWS或启用本地上传。"
-		}
-	},
-	"page_me_apitoken": {
+    "notice": {
+      "restricted": "需要管理员批准。",
+      "restricted_defail": "一旦管理员批准您的注册,您就可以访问此wiki。"
+    },
+    "form_help": {
+      "email": "您必须有下面列出的电子邮件地址才能注册此wiki。",
+      "password": "密码长度必须至少为8个字符。",
+      "user_id": "您创建的网页的URL将包含您的用户ID。您的用户ID可以由字母、数字和一些符号组成。"
+    }
+  },
+  "Settings": "设置",
+  "page_me": {
+    "form_help": {
+      "profile_image1": "图像上传设置未完成。",
+      "profile_image2": "设置AWS或启用本地上传。"
+    }
+  },
+  "page_me_apitoken": {
     "api_token": "API Token",
-		"notice": {
-			"apitoken_issued": "API token 未发布。",
-			"update_token1": "您可以更新以生成新的API令牌。",
-			"update_token2": "您需要更新任何现有进程中的API令牌。"
-		}
-	},
-	"Password": "密码",
-	"Password Settings": "密码设置",
-	"personal_settings": {
-		"disassociate_external_account": "解除与外部帐户的关联",
-		"disassociate_external_account_desc": "是否确实要解除与<strong>{{providerType}}</strong>帐户<strong>{{providerType}}</strong> 的关联?",
-		"set_new_password": "设置新密码",
-		"update_password": "更新密码",
-		"current_password": "当前密码",
-		"new_password": "新密码",
-		"new_password_confirm": "重复新密码",
-		"password_is_not_set": "密码未设置"
-	},
-	"API Settings": "API设置",
+    "notice": {
+      "apitoken_issued": "API token 未发布。",
+      "update_token1": "您可以更新以生成新的API令牌。",
+      "update_token2": "您需要更新任何现有进程中的API令牌。"
+    }
+  },
+  "Password": "密码",
+  "Password Settings": "密码设置",
+  "personal_settings": {
+    "disassociate_external_account": "解除与外部帐户的关联",
+    "disassociate_external_account_desc": "是否确实要解除与<strong>{{providerType}}</strong>帐户<strong>{{providerType}}</strong> 的关联?",
+    "set_new_password": "设置新密码",
+    "update_password": "更新密码",
+    "current_password": "当前密码",
+    "new_password": "新密码",
+    "new_password_confirm": "重复新密码",
+    "password_is_not_set": "密码未设置"
+  },
+  "API Settings": "API设置",
   "Other Settings": "其他设置",
-	"API Token Settings": "API token 设置",
-	"Current API Token": "当前 API token",
-	"Update API Token": "更新 API token",
+  "API Token Settings": "API token 设置",
+  "Current API Token": "当前 API token",
+  "Update API Token": "更新 API token",
   "in_app_notification_settings": {
     "in_app_notification_settings": "在应用程序通知设置",
     "subscribe_settings": "自动订阅(接收通知)页面的设置",
@@ -259,48 +262,53 @@
   "editor_settings": {
     "editor_settings": "编辑器设置"
   },
-	"search_help": {
-		"title": "搜索帮助",
-		"and": {
-			"syntax help": "用空格分隔",
-			"desc": "在标题或正文中同时包含{{word1}、{{word2}的搜索页"
-		},
-		"exclude": {
-			"desc": "排除标题或正文中包含{{word}的页"
-		},
-		"phrase": {
-			"syntax help": "用双引号括起来",
-			"desc": "包含短语“{{phrase}”的搜索页"
-		},
-		"prefix": {
-			"desc": "只搜索标题以{{path}开头的页"
-		},
-		"exclude_prefix": {
-			"desc": "排除标题以{{path}开头的页"
-		},
-		"tag": {
-			"desc": "搜索带有{{tag}标记的页面"
-		},
-		"exclude_tag": {
-			"desc": "排除带有{{tag}标记的页"
-		}
-	},
-	"search": {
-		"search page bodies": "按[回车]键进行全文搜索"
-	},
-	"page_page": {
-		"notice": {
-			"version": "这不是当前版本。",
-			"redirected": "您将从",
+  "search_help": {
+    "title": "搜索帮助",
+    "and": {
+      "syntax help": "用空格分隔",
+      "desc": "在标题或正文中同时包含{{word1}、{{word2}的搜索页"
+    },
+    "exclude": {
+      "desc": "排除标题或正文中包含{{word}的页"
+    },
+    "phrase": {
+      "syntax help": "用双引号括起来",
+      "desc": "包含短语“{{phrase}”的搜索页"
+    },
+    "prefix": {
+      "desc": "只搜索标题以{{path}开头的页"
+    },
+    "exclude_prefix": {
+      "desc": "排除标题以{{path}开头的页"
+    },
+    "tag": {
+      "desc": "搜索带有{{tag}标记的页面"
+    },
+    "exclude_tag": {
+      "desc": "排除带有{{tag}标记的页"
+    }
+  },
+  "search": {
+    "search page bodies": "按[回车]键进行全文搜索"
+  },
+  "page_page": {
+    "notice": {
+      "version": "这不是当前版本。",
+      "redirected": "您将从",
       "redirected_period": "",
-			"unlinked": "将网页重定向到此网页已被删除。",
-			"restricted": "访问此页受到限制",
-			"stale": "自上次更新以来,已超过{{count}年。",
+      "unlinked": "将网页重定向到此网页已被删除。",
+      "restricted": "访问此页受到限制",
+      "stale": "自上次更新以来,已超过{{count}年。",
       "stale_plural": "自上次更新以来已过去{{count}年以上。",
       "no_deadline": "This page has no expiration date"
 		}
 	},
 	"page_edit": {
+    "input_channels": "频道名",
+    "theme": "主题",
+    "keymap": "键表",
+    "indent": "缩进",
+    "editor_config": "编辑器配置",
 		"Show active line": "显示活动行",
 		"auto_format_table": "自动格式化表格",
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
@@ -313,12 +321,13 @@
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",
     "no_user_found": "未找到用户名"
   },
-	"page_api_error": {
-		"notfound_or_forbidden": "未找到或禁止原始页。",
-		"already_exists": "具有该路径的页面已存在",
-		"outdated": "页面已被某人更新,现在已过时。",
-		"user_not_admin": "仅管理员用户可以删除",
-    "single_deletion_empty_pages": "空的页面不能被单一删除"
+  "page_api_error": {
+    "notfound_or_forbidden": "未找到或禁止原始页。",
+    "already_exists": "具有该路径的页面已存在",
+    "outdated": "页面已被某人更新,现在已过时。",
+    "user_not_admin": "仅管理员用户可以删除",
+    "single_deletion_empty_pages": "空的页面不能被单一删除",
+    "complete_deletion_not_allowed_for_user": "您无权永久删除该页面"
   },
   "page_history": {
     "revision_list": "修订清单",
@@ -326,11 +335,11 @@
     "comparing_source": "源头",
     "comparing_target": "目标",
     "comparing_revisions": "比较两者的区别",
-    "compare_latest":"比較最新版本",
-    "compare_previous":"比較以前的版本"
+    "compare_latest": "比較最新版本",
+    "compare_previous": "比較以前的版本"
   },
-	"modal_rename": {
-		"label": {
+  "modal_rename": {
+    "label": {
       "Move/Rename page": "页面 移动/重命名",
       "New page name": "新建页面名称",
       "Failed to get subordinated pages": "Failed to get subordinated pages",
@@ -341,91 +350,93 @@
       "Other options": "其他选项",
       "Do not update metadata": "不更新元数据",
       "Redirect": "重定向"
-		},
-		"help": {
+    },
+    "help": {
       "redirect": "Redirect to new page if someone accesses <code>%s</code>",
       "metadata": "Remains last update user and updated date as is",
       "recursive": "Move/Rename children of under <code>%s</code> recursively"
-		}
-	},
-	"Put Back": "Put back",
+    }
+  },
+  "Put Back": "Put back",
   "Delete Completely": "Delete completely",
   "page_has_been_reverted": "{{path}} 已还原",
-	"modal_delete": {
-		"delete_page": "Delete page",
-		"deleting_page": "Deleting page",
-		"delete_recursively": "Delete child pages recursively.",
-		"delete_completely": "Delete completely",
-		"delete_completely_restriction": "You don't have the authority to delete pages completely.",
-		"recursively": "Delete children of <code>%s</code> recursively.",
-		"completely": "Delete completely instead of putting it into trash."
+  "modal_delete": {
+    "delete_page": "Delete page",
+    "deleting_page": "Deleting page",
+    "delete_recursively": "Delete child pages recursively.",
+    "delete_completely": "Delete completely",
+    "delete_completely_restriction": "You don't have the authority to delete pages completely.",
+    "recursively": "Delete children of <code>%s</code> recursively.",
+    "completely": "Delete completely instead of putting it into trash."
   },
   "deleted_page": "移到了垃圾箱。",
   "deleted_pages": "将 {{path}} 放入垃圾箱",
   "deleted_pages_completely": "{{path}} 已被完全删除",
   "renamed_pages": "移动/重命名 {{path}}",
   "empty_trash": "清空垃圾",
-	"modal_empty": {
-		"empty_the_trash": "清空垃圾",
+  "modal_empty": {
+    "empty_the_trash": "清空垃圾",
     "empty_the_trash_button": "清空垃圾",
     "not_deletable_notice": "由于缺乏权限,一些页面不能被删除",
-		"notice": "完全删除的页面是不可恢复的。"
-	},
-	"modal_duplicate": {
-		"label": {
-			"Duplicate page": "Duplicate page",
+    "notice": "完全删除的页面是不可恢复的。"
+  },
+  "modal_duplicate": {
+    "label": {
+      "Duplicate page": "Duplicate page",
       "New page name": "New page name",
       "Failed to get subordinated pages": "Failed to get subordinated pages",
-			"Current page name": "Current page name",
+      "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}} 已重复",
-	"modal_putback": {
-		"label": {
-			"Put Back Page": "Put back page",
-			"recursively": "Put back recursively"
-		},
-		"help": {
-			"recursively": "Put back children of under <code>%s</code> recursively"
-		}
-	},
-	"modal_shortcuts": {
-		"global": {
-			"title": "全局快捷方式",
-			"Open/Close shortcut help": "打开/关闭快捷方式帮助",
-			"Edit Page": "编辑页面",
-			"Create Page": "创建页面",
+  "modal_putback": {
+    "label": {
+      "Put Back Page": "Put back page",
+      "recursively": "Put back recursively"
+    },
+    "help": {
+      "recursively": "Put back children of under <code>%s</code> recursively"
+    }
+  },
+  "modal_shortcuts": {
+    "global": {
+      "title": "全局快捷方式",
+      "Open/Close shortcut help": "打开/关闭快捷方式帮助",
+      "Edit Page": "编辑页面",
+      "Create Page": "创建页面",
       "Search": "搜索",
-			"Show Contributors": "显示参与者",
-			"Konami Code": "Konami Code",
-			"konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
-		},
-		"editor": {
-			"title": "编辑器快捷方式",
-			"Indent": "缩进",
-			"Outdent": "回退缩进",
-			"Save Page": "保存页面",
-			"Delete Line": "删除行"
-		},
-		"commentform": {
-			"title": "注释窗体快捷方式",
-			"Post": "提交"
-		}
-	},
+      "Show Contributors": "显示参与者",
+      "Konami Code": "Konami Code",
+      "konami_code_url": "https://en.wikipedia.org/wiki/Konami_Code"
+    },
+    "editor": {
+      "title": "编辑器快捷方式",
+      "Indent": "缩进",
+      "Outdent": "回退缩进",
+      "Save Page": "保存页面",
+      "Delete Line": "删除行"
+    },
+    "commentform": {
+      "title": "注释窗体快捷方式",
+      "Post": "提交"
+    }
+  },
   "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": "选定的可编辑页面正文"
   },
@@ -445,64 +456,64 @@
     "preview": "Preview",
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
-	"toaster": {
+  "toaster": {
     "file_upload_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",
     "switch_disable_link_sharing_success": "成功更新分享链接设置",
-    "failed_to_reset_password":"Failed to reset password",
+    "failed_to_reset_password": "Failed to reset password",
     "save_succeeded": "已成功保存",
     "issue_share_link": "Succeeded to issue new share link"
   },
-	"template": {
-		"modal_label": {
+  "template": {
+    "modal_label": {
       "Select template": "选择模板",
-			"Create/Edit Template Page": "创建/编辑模板页",
-			"Create template under": "在下面创建模板页"
-		},
-		"option_label": {
-			"create/edit": "创建/编辑模板页。",
-			"select": "选择模板页面类型"
-		},
-		"children": {
-			"label": "子模板",
-			"desc": "仅应用于模板存在的同一级别页"
-		},
-		"descendants": {
-			"label": "子代模板",
-			"desc": "适用于所有分散页"
-		}
-	},
-	"sandbox": {
-		"header": "标题",
-		"header_x": "标题{{index}",
-		"block": "段落",
-		"block_detail": "写一段",
-		"empty_line": "空行",
-		"line_break": "换行符",
-		"line_break_detail": "(2空格)换行",
-		"typography": "排版",
-		"italics": "斜体",
-		"bold": "加粗",
-		"italic_bold": "斜体加粗",
-		"strikethrough": "删除线",
-		"link": "链接",
-		"code_highlight": "代码突出显示",
-		"list": "列表",
-		"unordered_list_x": "无序列表{{index}}",
-		"ordered_list_x": "有序列表{{index}}",
-		"task": "任务",
-		"task_checked": "选中的",
-		"task_unchecked": "未选中的",
-		"quote": "引用",
-		"quote1": "你可以写",
-		"quote2": "多行引用",
-		"quote_nested": "嵌套引用",
-		"table": "表格",
-		"image": "图片",
-		"alt_text": "Alt文本",
-		"insert_image": "插入图像",
-		"open_sandbox": "开放式沙箱"
-	},
+      "Create/Edit Template Page": "创建/编辑模板页",
+      "Create template under": "在下面创建模板页"
+    },
+    "option_label": {
+      "create/edit": "创建/编辑模板页。",
+      "select": "选择模板页面类型"
+    },
+    "children": {
+      "label": "子模板",
+      "desc": "仅应用于模板存在的同一级别页"
+    },
+    "descendants": {
+      "label": "子代模板",
+      "desc": "适用于所有分散页"
+    }
+  },
+  "sandbox": {
+    "header": "标题",
+    "header_x": "标题{{index}",
+    "block": "段落",
+    "block_detail": "写一段",
+    "empty_line": "空行",
+    "line_break": "换行符",
+    "line_break_detail": "(2空格)换行",
+    "typography": "排版",
+    "italics": "斜体",
+    "bold": "加粗",
+    "italic_bold": "斜体加粗",
+    "strikethrough": "删除线",
+    "link": "链接",
+    "code_highlight": "代码突出显示",
+    "list": "列表",
+    "unordered_list_x": "无序列表{{index}}",
+    "ordered_list_x": "有序列表{{index}}",
+    "task": "任务",
+    "task_checked": "选中的",
+    "task_unchecked": "未选中的",
+    "quote": "引用",
+    "quote1": "你可以写",
+    "quote2": "多行引用",
+    "quote_nested": "嵌套引用",
+    "table": "表格",
+    "image": "图片",
+    "alt_text": "Alt文本",
+    "insert_image": "插入图像",
+    "open_sandbox": "开放式沙箱"
+  },
   "slack_notification": {
     "popover_title": "Slack Notification",
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
@@ -511,7 +522,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",
@@ -519,41 +530,42 @@
     "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": "链接共享已被禁用"
   },
-	"search_result": {
+  "search_result": {
     "title": "搜索",
-		"result_meta": "搜索结果:",
-		"deletion_mode_btn_lavel": "选择并删除页面",
-		"cancel": "取消",
-		"delete": "删除",
-		"check_all": "全部检查",
-		"deletion_modal_header": "删除页",
-		"delete_completely": "完全删除",
+    "result_meta": "搜索结果:",
+    "deletion_mode_btn_lavel": "选择并删除页面",
+    "cancel": "取消",
+    "delete": "删除",
+    "check_all": "全部检查",
+    "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" : "例",
+    "search_again": "再次搜索",
+    "number_of_list_to_display": "显示器的数量",
+    "page_number_unit": "例",
+    "hit_number_unit": "例",
     "sort_axis": {
       "relationScore": "按相关性排序",
       "createdAt": "按创建日期排序",
       "updatedAt": "按更新日期排序"
     }
-	},
+  },
   "private_legacy_pages": {
     "title": "私人遗留页面",
     "bulk_operation": "批量操作",
     "convert_all_selected_pages": "全部转换为新的v5兼容格式",
-		"input_path_to_convert": "输入一个转换页面的路径",
+    "input_path_to_convert": "输入一个转换页面的路径",
     "alert_title": "存在旧的v4兼容格式的私人网页。",
     "alert_desc1": "在这一页,你可以用复选框选择页面,并通过屏幕上方的批量操作按钮批量转换为新的v5兼容格式。",
     "nopages_title": "恭喜你。准备使用GROWI v5!",
     "nopages_desc1": "现在你能管理的所有页面似乎都是v5兼容的格式。",
-    "detail_info": "请参见 <a href='https://docs.growi.org/en/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/en/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": "转换页面",
@@ -579,14 +591,14 @@
       "error_duplicate_pages_found": "发现多个具有相同路径名称的页面。请重新命名或删除并重试。"
     }
   },
-	"login": {
+  "login": {
     "title": "登录",
-		"sign_in_error": "登录错误",
-		"registration_successful": "注册成功。请等待管理员批准",
-		"Setup": "安装程序",
-    "enabled_ldap_has_configuration_problem":"启用了LDAP,但配置有问题。",
+    "sign_in_error": "登录错误",
+    "registration_successful": "注册成功。请等待管理员批准",
+    "Setup": "安装程序",
+    "enabled_ldap_has_configuration_problem": "启用了LDAP,但配置有问题。",
     "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"
-	},
+  },
   "invited": {
     "invited": "邀请函",
     "discription_heading": "创建账户",
@@ -598,35 +610,35 @@
     "export_page_markdown": "以Markdown格式导出页面",
     "export_page_pdf": "以PDF格式导出页面"
   },
-	"message": {
-		"successfully_connected": "连接成功!",
-		"fail_to_save_access_token": "无法保存访问令牌。请再试一次。",
-		"fail_to_fetch_access_token": "无法获取访问令牌。请重新连接。",
-		"successfully_disconnected": "成功断开连接!",
+  "message": {
+    "successfully_connected": "连接成功!",
+    "fail_to_save_access_token": "无法保存访问令牌。请再试一次。",
+    "fail_to_fetch_access_token": "无法获取访问令牌。请重新连接。",
+    "successfully_disconnected": "成功断开连接!",
     "strategy_has_not_been_set_up": "{{strategy}} 尚未设置",
     "ldap_user_not_valid": "Ldap user is no valid",
     "external_account_not_exist": "查找或创建外部账户失败",
-		"maximum_number_of_users": "注册的用户数不能超过最大值。",
-		"sign_in_failure": "登录失败。",
-		"aws_sttings_required": "使用此功能所需的AWS设置。请询问管理员。",
-		"application_already_installed": "应用程序已安装。",
-		"email_address_could_not_be_used": "无法使用此电子邮件地址。(确保允许的电子邮件地址)",
+    "maximum_number_of_users": "注册的用户数不能超过最大值。",
+    "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":"用户名不应为空。请检查管理页面上的身份验证机制设置",
-		"email_address_is_already_registered": "此电子邮件地址已注册。",
-		"can_not_register_maximum_number_of_users": "注册的用户数不能超过最大值。",
-    "email_settings_is_not_setup":"邮箱设置未设置,请询问管理员。",
+    "username_should_not_be_null": "用户名不应为空。请检查管理页面上的身份验证机制设置",
+    "email_address_is_already_registered": "此电子邮件地址已注册。",
+    "can_not_register_maximum_number_of_users": "注册的用户数不能超过最大值。",
+    "email_settings_is_not_setup": "邮箱设置未设置,请询问管理员。",
     "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": "无法使用此用户。",
-		"complete_to_install1": "完成安装GROWI!请以管理员帐户登录。",
-		"complete_to_install2": "完成安装GROWI!请先检查此页上的每个设置。",
-		"failed_to_create_admin_user": "无法创建管理用户。{{errMessage}",
-    "successfully_send_email_auth":"我们向 {{email}} 发送了一封电子邮件。 请点击邮件中的网址并完成注册。",
-    "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": "无法使用此用户。",
+    "complete_to_install1": "完成安装GROWI!请以管理员帐户登录。",
+    "complete_to_install2": "完成安装GROWI!请先检查此页上的每个设置。",
+    "failed_to_create_admin_user": "无法创建管理用户。{{errMessage}",
+    "successfully_send_email_auth": "我们向 {{email}} 发送了一封电子邮件。 请点击邮件中的网址并完成注册。",
+    "incorrect_token_or_expired_url": "令牌不正确或 URL 已过期。",
     "user_already_logged_in": "当你登录的时候,你不能创建一个新的账户。",
     "registration_closed": "你无权创建一个新的账户。",
     "Username has invalid characters": "用户名有无效字符",
@@ -640,22 +652,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>发生了重复用户名异常</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网格",
+    "provider_duplicated_username_exception": "<p><strong><span class='material-symbols-outlined me-1'>cancel</span>发生了重复用户名异常</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_settings": "网格设置",
     "grid_pattern": "网格样式",
-    "division":"分割",
-    "smart_no":"手机/不分割",
-    "break_point":"按画面大小分割"
+    "division": "分割",
+    "smart_no": "手机/不分割",
+    "break_point": "按画面大小分割"
   },
-  "validation":{
+  "validation": {
     "aws_region": "关于地区,请输入AWS地区名,例如:ap-east-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": "返回登录",
@@ -668,11 +680,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": "重置",
@@ -702,7 +714,7 @@
       "6": "深色肤色"
     }
   },
-  "maintenance_mode":{
+  "maintenance_mode": {
     "maintenance_mode": "维护模式",
     "growi_is_under_maintenance": "GROWI正在进行维护。请等待,直到它结束。",
     "admin_page": "管理员页",
@@ -714,10 +726,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": "选择组别",
@@ -764,9 +776,9 @@
       }
     }
   },
-  "page_operation":{
+  "page_operation": {
     "paths_recovered": "成功恢复了页面路径",
-    "path_recovery_failed":"路径恢复失败"
+    "path_recovery_failed": "路径恢复失败"
   },
   "footer": {
     "bookmarks": "书签",
@@ -825,5 +837,19 @@
   },
   "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"
   }
 }

+ 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: '',
-      },
-    },
-  ],
-};

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

Some files were not shown because too many files changed in this diff