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

Merge branch 'feat/gw7907-compare-page-versions-on-history-tab' into feat/gw7908-compare-all-page-versions

Mudana-Grune 3 лет назад
Родитель
Сommit
165706cc8f
100 измененных файлов с 1174 добавлено и 2411 удалено
  1. 5 2
      .github/ISSUE_TEMPLATE/config.yml
  2. 0 25
      .github/ISSUE_TEMPLATE/user-request.md
  3. 3 2
      .github/dependabot.yml
  4. 8 2
      .github/workflows/ci-app-prod.yml
  5. 6 2
      .github/workflows/ci-app.yml
  6. 5 1
      .github/workflows/ci-slackbot-proxy.yml
  7. 3 3
      .github/workflows/codeql-analysis.yml
  8. 26 0
      .github/workflows/dependabot-auto-approve.yml
  9. 6 2
      .github/workflows/draft-release.yml
  10. 6 1
      .github/workflows/pr-to-master.yml
  11. 56 29
      .github/workflows/release-rc.yml
  12. 5 5
      .github/workflows/release-slackbot-proxy.yml
  13. 67 45
      .github/workflows/release.yml
  14. 56 0
      .github/workflows/reusable-app-build-image.yml
  15. 52 0
      .github/workflows/reusable-app-create-manifests.yml
  16. 1 1
      .github/workflows/reusable-app-prod.yml
  17. 5 0
      .gitignore
  18. 22 0
      .mergify.yml
  19. 125 1
      CHANGELOG.md
  20. 85 0
      bin/data-migrations/v6/README.md
  21. 57 0
      bin/data-migrations/v6/src/migration.js
  22. 75 0
      bin/data-migrations/v6/src/processor.js
  23. 1 1
      lerna.json
  24. 1 1
      package.json
  25. 0 1
      packages/app/.env.development
  26. 0 191
      packages/app/_obsolete/config/webpack.common.js
  27. 0 49
      packages/app/_obsolete/config/webpack.dev.dll.js
  28. 0 81
      packages/app/_obsolete/config/webpack.dev.js
  29. 0 77
      packages/app/_obsolete/config/webpack.prod.js
  30. 0 167
      packages/app/_obsolete/src/client/app.jsx
  31. 0 69
      packages/app/_obsolete/src/client/base.jsx
  32. 0 5
      packages/app/_obsolete/src/client/boot.js
  33. 0 60
      packages/app/_obsolete/src/client/installer.jsx
  34. 0 52
      packages/app/_obsolete/src/client/legacy/crowi.js
  35. 0 142
      packages/app/_obsolete/src/client/nologin.jsx
  36. 0 55
      packages/app/_obsolete/src/client/plugin.js
  37. 0 133
      packages/app/_obsolete/src/client/services/AppContainer.js
  38. 0 214
      packages/app/_obsolete/src/client/services/ContextExtractor.tsx
  39. 0 358
      packages/app/_obsolete/src/client/services/PageContainer.js
  40. 0 144
      packages/app/_obsolete/src/components/MyDraftList/Draft.tsx
  41. 0 192
      packages/app/_obsolete/src/components/MyDraftList/MyDraftList.jsx
  42. 0 76
      packages/app/_obsolete/src/util/i18n.js
  43. 0 17
      packages/app/_obsolete/src/util/old-ios.js
  44. 1 0
      packages/app/config/logger/config.dev.js
  45. 2 1
      packages/app/config/next-i18next.config.ts
  46. 14 21
      packages/app/docker/Dockerfile
  47. 1 1
      packages/app/docker/README.md
  48. 65 0
      packages/app/docker/codebuild/.terraform.lock.hcl
  49. 32 0
      packages/app/docker/codebuild/buildspec.yml
  50. 25 0
      packages/app/docker/codebuild/codebuild.tf
  51. 23 0
      packages/app/docker/codebuild/main.tf
  52. 26 0
      packages/app/docker/codebuild/oidc.tf
  53. 15 0
      packages/app/docker/codebuild/secretsmanager.tf
  54. 0 6
      packages/app/docker/nocdn/.env.production.local
  55. 21 20
      packages/app/package.json
  56. 23 1
      packages/app/public/static/locales/en_US/admin.json
  57. 10 0
      packages/app/public/static/locales/en_US/commons.json
  58. 14 0
      packages/app/public/static/locales/en_US/translation.json
  59. 22 0
      packages/app/public/static/locales/ja_JP/admin.json
  60. 10 0
      packages/app/public/static/locales/ja_JP/commons.json
  61. 15 0
      packages/app/public/static/locales/ja_JP/translation.json
  62. 23 1
      packages/app/public/static/locales/zh_CN/admin.json
  63. 10 0
      packages/app/public/static/locales/zh_CN/commons.json
  64. 16 1
      packages/app/public/static/locales/zh_CN/translation.json
  65. 6 6
      packages/app/resource/locales/en_US/admin/userInvitation.txt
  66. 8 8
      packages/app/resource/locales/en_US/admin/userWaitingActivation.txt
  67. 3 3
      packages/app/resource/locales/en_US/notifications/comment.txt
  68. 4 4
      packages/app/resource/locales/en_US/notifications/notActiveUser.txt
  69. 2 2
      packages/app/resource/locales/en_US/notifications/pageCreate.txt
  70. 2 2
      packages/app/resource/locales/en_US/notifications/pageDelete.txt
  71. 2 2
      packages/app/resource/locales/en_US/notifications/pageEdit.txt
  72. 2 2
      packages/app/resource/locales/en_US/notifications/pageLike.txt
  73. 2 2
      packages/app/resource/locales/en_US/notifications/pageMove.txt
  74. 4 4
      packages/app/resource/locales/en_US/notifications/passwordReset.txt
  75. 1 1
      packages/app/resource/locales/en_US/notifications/passwordResetSuccessful.txt
  76. 4 4
      packages/app/resource/locales/en_US/notifications/userActivation.txt
  77. 1 11
      packages/app/resource/locales/en_US/sandbox.md
  78. 6 6
      packages/app/resource/locales/ja_JP/admin/userInvitation.txt
  79. 8 8
      packages/app/resource/locales/ja_JP/admin/userWaitingActivation.txt
  80. 4 4
      packages/app/resource/locales/ja_JP/notifications/notActiveUser.txt
  81. 4 4
      packages/app/resource/locales/ja_JP/notifications/passwordReset.txt
  82. 1 1
      packages/app/resource/locales/ja_JP/notifications/passwordResetSuccessful.txt
  83. 4 4
      packages/app/resource/locales/ja_JP/notifications/userActivation.txt
  84. 0 10
      packages/app/resource/locales/ja_JP/sandbox.md
  85. 6 6
      packages/app/resource/locales/zh_CN/admin/userInvitation.txt
  86. 8 8
      packages/app/resource/locales/zh_CN/admin/userWaitingActivation.txt
  87. 3 3
      packages/app/resource/locales/zh_CN/notifications/comment.txt
  88. 4 4
      packages/app/resource/locales/zh_CN/notifications/notActiveUser.txt
  89. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageCreate.txt
  90. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageDelete.txt
  91. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageEdit.txt
  92. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageLike.txt
  93. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageMove.txt
  94. 3 3
      packages/app/resource/locales/zh_CN/notifications/passwordReset.txt
  95. 1 1
      packages/app/resource/locales/zh_CN/notifications/passwordResetSuccessful.txt
  96. 4 4
      packages/app/resource/locales/zh_CN/notifications/userActivation.txt
  97. 1 11
      packages/app/resource/locales/zh_CN/sandbox.md
  98. 0 1
      packages/app/src/client/services/AdminAppContainer.js
  99. 15 0
      packages/app/src/client/services/g2g-transfer.ts
  100. 7 14
      packages/app/src/client/services/layout.ts

+ 5 - 2
.github/ISSUE_TEMPLATE/config.yml

@@ -1,5 +1,8 @@
 blank_issues_enabled: false
 contact_links:
-  - name: Question or Suggestions
+  - name: User request or Suggestions
+    url: https://github.com/weseek/growi/discussions
+    about: If you have feature requests or suggestions, you can create a new discussion and consider it with the community.
+  - name: Questions
     url: https://growi-slackin.weseek.co.jp/
-    about: If you have questions or suggestions, you can join our Slack team and talk about anything, anytime.
+    about: If you have questions, you can join our Slack team and talk about anything, anytime.

+ 0 - 25
.github/ISSUE_TEMPLATE/user-request.md

@@ -1,25 +0,0 @@
----
-name: User request
-about: Suggest an idea for this project
-title: 'Request:'
-labels: user requests
----
-
-
-## Informations
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. 
-
-e.g. I'm having trouble getting immediate access to information
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to realization.
-
-e.g. It's good if there is a space where everyone can access information in common.
-
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
-
-- [ ] Custom Page In Sidebar
-  - [ ] Can be edited in `/Sidebar` path
-  - [ ] Can be described with md like a page

+ 3 - 2
.github/dependabot.yml.org → .github/dependabot.yml

@@ -2,7 +2,7 @@ version: 2
 updates:
   - package-ecosystem: github-actions
     directory: '/'
-    open-pull-requests-limit: 0
+    open-pull-requests-limit: 3
     schedule:
       interval: monthly
     commit-message:
@@ -11,7 +11,7 @@ updates:
 
   - package-ecosystem: npm
     directory: '/'
-    open-pull-requests-limit: 0
+    open-pull-requests-limit: 3
     schedule:
       interval: weekly
     commit-message:
@@ -22,4 +22,5 @@ updates:
       - dependency-name: string-width
       - dependency-name: "@handsontable/react"
       - dependency-name: handsontable
+      - dependency-name: reveal.js
 

+ 8 - 2
.github/workflows/ci-app-prod.yml

@@ -14,6 +14,7 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**
@@ -31,6 +32,7 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**
@@ -41,6 +43,10 @@ on:
         type: boolean
         default: false
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 
 jobs:
 
@@ -57,7 +63,7 @@ jobs:
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     with:
       node-version: 16.x
-      skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
@@ -73,7 +79,7 @@ jobs:
 
     with:
       node-version: 16.x
-      skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) }}
       cypress-report-artifact-name: Cypress report
     secrets:
       REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}

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

@@ -5,8 +5,6 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - chore/**
-      - support/prepare-v**
     paths:
       - .github/workflows/ci-app.yml
       - .eslint*
@@ -16,10 +14,16 @@ on:
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/preset-themes/**
       - packages/remark-*/**
       - packages/slack/**
       - packages/ui/**
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
   lint:
     runs-on: ubuntu-latest

+ 5 - 1
.github/workflows/ci-slackbot-proxy.yml

@@ -5,7 +5,6 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - chore/**
       - support/prepare-v**
     paths:
       - .github/workflows/ci-slackbot-proxy.yml
@@ -16,6 +15,11 @@ on:
       - '!packages/slackbot-proxy/docker/**'
       - packages/slack/**
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 
   test:

+ 3 - 3
.github/workflows/codeql-analysis.yml

@@ -45,7 +45,7 @@ jobs:
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@v1
+      uses: github/codeql-action/init@v2
       with:
         languages: ${{ matrix.language }}
         # If you wish to specify custom queries, you can do so here or in a config file.
@@ -56,7 +56,7 @@ jobs:
     # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
     # If this step fails, then you should remove it and run the build manually (see below)
     - name: Autobuild
-      uses: github/codeql-action/autobuild@v1
+      uses: github/codeql-action/autobuild@v2
 
     # ℹ️ Command-line programs to run using the OS shell.
     # 📚 https://git.io/JvXDl
@@ -70,4 +70,4 @@ jobs:
     #   make release
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@v1
+      uses: github/codeql-action/analyze@v2

+ 26 - 0
.github/workflows/dependabot-auto-approve.yml

@@ -0,0 +1,26 @@
+# by https://zenn.dev/nemuki/articles/dependabot-auto-merge
+name: Auto approve on dependabot PR at patch update
+
+on:
+  pull_request_target:
+    types: [opened, reopened, synchronize]
+
+permissions:
+  pull-requests: write
+
+jobs:
+  dependabot-auto-approve:
+    runs-on: ubuntu-latest
+    if: ${{ github.actor == 'dependabot[bot]' }}
+    steps:
+      - name: Dependabot metadata
+        id: dependabot-metadata
+        uses: dependabot/fetch-metadata@v1
+        with:
+          github-token: '${{ secrets.GITHUB_TOKEN }}'
+      - name: Approve a PR
+        if: ${{ 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 }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

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

@@ -5,6 +5,11 @@ on:
     branches:
       - master
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 
   # Refs: https://github.com/release-drafter/release-drafter
@@ -50,9 +55,8 @@ jobs:
           RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
           echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
 
-      # See: https://github.com/bakunyo/git-pr-release-action/issues/15, https://github.com/samunohito/SimpleVolumeMixer/commit/2059044c71236509466cf9b1bb2d56d515274938
       - name: Create/Update Pull Request
-        uses: bakunyo/git-pr-release-action@281e1fe424fac01f3992542266805e4202a22fe0
+        uses: bakunyo/git-pr-release-action@master
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
           GIT_PR_RELEASE_BRANCH_PRODUCTION: release/current

+ 6 - 1
.github/workflows/pr-to-master.yml

@@ -7,6 +7,11 @@ on:
     # Only following types are handled by the action, but one can default to all as well
     types: [opened, reopened, edited, synchronize]
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 
   # Refs: https://github.com/release-drafter/release-drafter
@@ -31,7 +36,7 @@ jobs:
         !startsWith( github.head_ref, 'dependabot/' ))
 
     steps:
-      - uses: amannn/action-semantic-pull-request@v4.2.0
+      - uses: amannn/action-semantic-pull-request@v5.0.2
         with:
           types: |
             feat

+ 56 - 29
.github/workflows/release-rc.yml

@@ -5,52 +5,79 @@ on:
     branches:
       - rc/**
 
-jobs:
 
-  build-rc:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 
+jobs:
+
+  determine-tags:
     runs-on: ubuntu-latest
 
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
+
     steps:
     - uses: actions/checkout@v3
-      with:
-        lfs: true
 
     - name: Retrieve information from package.json
       uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
 
-    - name: Docker meta
+    - name: Docker meta for docker.io
+      uses: docker/metadata-action@v4
       id: meta
-      uses: docker/metadata-action@v3
       with:
-        images: weseek/growi,ghcr.io/weseek/growi
+        images: docker.io/weseek/growi
+        sep-tags: ','
         tags: |
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
 
-    - name: Login to docker.io registry
-      run: |
-        echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
-
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v2
+    - name: Docker meta for ghcr.io
+      uses: docker/metadata-action@v4
+      id: meta-ghcr
       with:
-        registry: ghcr.io
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+        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}}
 
-    - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v2
 
-    - name: Build and push
-      uses: docker/build-push-action@v2
-      with:
-        context: .
-        file: ./packages/app/docker/Dockerfile
-        platforms: linux/amd64
-        push: true
-        builder: ${{ steps.buildx.outputs.name }}
-        cache-from: type=gha
-        cache-to: type=gha,mode=max
-        tags: ${{ steps.meta.outputs.tags }}
+  build-image-rc:
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image-rc:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+  publish-image-rc-ghcr:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
+      registry: ghcr.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+

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

@@ -24,7 +24,7 @@ jobs:
 
     - name: Docker meta
       id: meta
-      uses: docker/metadata-action@v3
+      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
         tags: |
@@ -43,12 +43,12 @@ jobs:
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
     - name: Authenticate to Google Cloud for GROWI.cloud
-      uses: google-github-actions/auth@v0
+      uses: google-github-actions/auth@v1
       with:
         credentials_json: '${{ secrets.GCP_SA_KEY_SLACKBOT_PROXY }}'
 
     - name: Setup gcloud
-      uses: google-github-actions/setup-gcloud@v0
+      uses: google-github-actions/setup-gcloud@v1
 
     - name: Configure docker for gcloud
       run: |
@@ -58,7 +58,7 @@ jobs:
       uses: docker/setup-buildx-action@v2
 
     - name: Build and push
-      uses: docker/build-push-action@v2
+      uses: docker/build-push-action@v4
       with:
         context: .
         file: ./packages/slackbot-proxy/docker/Dockerfile
@@ -75,7 +75,7 @@ jobs:
         mv /tmp/.buildx-cache-new /tmp/.buildx-cache
 
     - name: Add tag
-      uses: anothrNick/github-tag-action@1.38.0
+      uses: anothrNick/github-tag-action@v1
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         CUSTOM_TAG: v${{ steps.package-json.outputs.packageVersion }}

+ 67 - 45
.github/workflows/release.yml

@@ -116,74 +116,96 @@ jobs:
         source_branch: support/prepare-v${{ steps.package-json.outputs.packageVersion }}
         destination_branch: master
         pr_title: Prepare v${{ steps.package-json.outputs.packageVersion }}
-        pr_label: exclude from changelog
-        pr_body: "An automated PR generated by ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
+        pr_label: exclude from changelog,prepare next version
+        pr_body: "An automated PR generated by create-pr-for-next-rc"
         github_token: ${{ secrets.GITHUB_TOKEN }}
 
 
-  build-image:
+  determine-tags:
     needs: create-github-release
-
     runs-on: ubuntu-latest
 
-    strategy:
-      matrix:
-        flavor: [default, nocdn]
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
-      with:
-        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
-        lfs: true
 
-    - name: Setup suffix
-      id: suffix
-      run: |
-        [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo "SUFFIX=$suffix" >> $GITHUB_OUTPUT
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
 
-    - name: Docker meta
-      id: meta
+    - name: Docker meta for docker.io
       uses: docker/metadata-action@v4
+      id: meta
       with:
-        images: weseek/growi,ghcr.io/weseek/growi
-        flavor: |
-          suffix=${{ steps.suffix.outputs.SUFFIX }}
+        images: docker.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}}
 
-    - name: Login to Docker Hub
-      uses: docker/login-action@v2
+    - name: Docker meta for ghcr.io
+      uses: docker/metadata-action@v4
+      id: meta-ghcr
       with:
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+        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}}
 
-    - 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: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v2
+  build-image:
+    needs: create-github-release
+
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      source-version: refs/tags/v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
+      image-name: weseek/growi
+      tag-temporary: latest
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image:
+    needs: [determine-tags, build-image]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest
+    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]
+    runs-on: ubuntu-latest
 
-    - name: Build and push
-      uses: docker/build-push-action@v3
+    steps:
+    - uses: actions/checkout@v3
       with:
-        context: .
-        file: ./packages/app/docker/Dockerfile
-        platforms: linux/amd64
-        push: true
-        build-args: |
-          flavor=${{ matrix.flavor }}
-        builder: ${{ steps.buildx.outputs.name }}
-        cache-from: type=gha
-        cache-to: type=gha,mode=max
-        tags: ${{ steps.meta.outputs.tags }}
+        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
 
     - name: Update Docker Hub Description
       uses: peter-evans/dockerhub-description@v3
@@ -198,7 +220,7 @@ jobs:
       with:
         channel: '#release'
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
-        created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}${{ steps.suffix.outputs.SUFFIX }}'
+        created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}'
 
     - name: Check whether workspace is clean
       run: |

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

@@ -0,0 +1,56 @@
+name: Reusable build app container image workflow
+
+on:
+  workflow_call:
+    inputs:
+      source-version:
+        type: string
+        default: ${{ github.sha }}
+      image-name:
+        type: string
+        default: weseek/growi
+      tag-temporary:
+        type: string
+        default: latest
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC:
+        required: true
+
+
+
+jobs:
+
+  build-image:
+    runs-on: ubuntu-latest
+
+    # These permissions are needed to interact with GitHub's OIDC Token endpoint.
+    permissions:
+      id-token: write
+      contents: write
+
+    strategy:
+      matrix:
+        platform: [amd64, arm64]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Configure AWS Credentials
+      uses: aws-actions/configure-aws-credentials@v1
+      with:
+        aws-region: ap-northeast-1
+        role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+        role-session-name: SessionForReleaseGROWI-RC
+
+    - name: Run CodeBuild
+      uses: dark-mechanicum/aws-codebuild@v1
+      with:
+        projectName: growi-official-image-builder
+      env:
+        CODEBUILD__sourceVersion: ${{ inputs.source-version }}
+        CODEBUILD__imageOverride: ${{ (matrix.platform == 'amd64' && 'aws/codebuild/standard:6.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 }}" }
+        ]'

+ 52 - 0
.github/workflows/reusable-app-create-manifests.yml

@@ -0,0 +1,52 @@
+name: Reusable create app container image manifests workflow
+
+on:
+  workflow_call:
+    inputs:
+      tags:
+        type: string
+        required: true
+      registry:
+        type: string
+        default: 'docker.io'
+      image-name:
+        type: string
+        default: weseek/growi
+      tag-temporary:
+        type: string
+        default: latest
+    secrets:
+      DOCKER_REGISTRY_PASSWORD:
+        required: true
+
+
+
+jobs:
+
+  create-manifest:
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Docker meta for extra-images
+      id: meta-extra-images
+      uses: docker/metadata-action@v4
+      with:
+        images: ${{ inputs.registry }}/${{ inputs.image-name }}
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ inputs.tag-temporary }}-amd64
+          type=raw,value=${{ inputs.tag-temporary }}-arm64
+
+    - name: Login to Container Registry
+      uses: docker/login-action@v2
+      with:
+        registry: ${{ inputs.registry }}
+        username: wsmoogle
+        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+    - name: Create and push manifest images
+      uses: Noelware/docker-manifest-action@master
+      with:
+        base-image: ${{ inputs.tags }}
+        extra-images: ${{ steps.meta-extra-images.outputs.tags }}
+        push: true

+ 1 - 1
.github/workflows/reusable-app-prod.yml

@@ -196,7 +196,7 @@ jobs:
       fail-fast: false
       matrix:
         # List string expressions that is comma separated ids of tests in "test/cypress/integration"
-        spec-group: ['10', '20', '21', '30', '40', '50', '60']
+        spec-group: ['10', '20', '21', '22', '30', '40', '50', '60']
 
     services:
       mongodb:

+ 5 - 0
.gitignore

@@ -29,6 +29,11 @@ yarn-error.log*
 # typescript
 *.tsbuildinfo
 
+# Terraform
+**/.terraform/*
+# *.tfstate
+*.tfstate.*
+
 # IDE, dev #
 .idea
 *.orig

+ 22 - 0
.mergify.yml

@@ -0,0 +1,22 @@
+pull_request_rules:
+  - name: Automatic merge for Dependabot pull requests
+    conditions:
+      - author = dependabot[bot]
+      - '#approved-reviews-by >= 1'
+      - check-success = "lint (16.x)"
+      - check-success = "test (16.x)"
+      - check-success = "launch-dev (16.x)"
+      - check-success = "test-prod-node14 / launch-prod"
+      - check-success = "test-prod-node16 / launch-prod"
+    actions:
+      merge:
+        method: merge
+
+  - name: Automatic merge for Preparing next version
+    conditions:
+      - author = github-actions[bot]
+      - '#approved-reviews-by >= 1'
+      - label = "prepare next version"
+    actions:
+      merge:
+        method: merge

+ 125 - 1
CHANGELOG.md

@@ -1,9 +1,133 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.1.7...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.0.5...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.0.5](https://github.com/weseek/growi/compare/v6.0.4...v6.0.5) - 2023-01-30
+
+### 🚀 Improvement
+
+- imprv: Override process for CommonSanitizeOptions (#7305) @miya
+
+### 🐛 Bug Fixes
+
+- fix:  Request to "/_api/v3/personal-settings"  occurs when in guest mode (#7307) @miya
+- fix: Undeleteable trash pages when clicked empty trash button bug (#7250) @jam411
+- fix: Guest users are able to move to pages that require authentication (#7300) @miya
+- fix: Modal does not close after clicking on path in DescendantsPageListModal (#7291) @miya
+- fix: GrowiContextualSubNavigation style is broken (#7304) @jam411
+- fix: Markdown in the editor reverted when save with shortcut (#7301) @yukendev
+
+## [v6.0.4](https://github.com/weseek/growi/compare/v6.0.3...v6.0.4) - 2023-01-25
+
+### 🐛 Bug Fixes
+
+- fix: Invalid URL in markdown breaks browser (#7292) @yuki-takei
+- fix: Previous editing markdown remains after changing page (#7285) @yukendev
+
+### 🧰 Maintenance
+
+- ci(deps): bump ua-parser-js from 0.7.31 to 0.7.33 (#7293) @dependabot
+
+## [v6.0.3](https://github.com/weseek/growi/compare/v6.0.2...v6.0.3) - 2023-01-24
+
+### 💎 Features
+
+- feat: GROWI to GROWI transfer (#6727) @hakumizuki
+- feat: Use configured xss custom whitelist (#7252) @miya
+- imprv: UI admin g2g transfer advanced options (#7261) @hakumizuki
+
+### 🚀 Improvement
+
+- imprv: Do not retrieve page data using API in shared page (#7240) @miya
+- imprv: Use CSS variables (#7093) @yuki-takei
+- imprv: Do not request /pages.getPageTag when on a shared page (#7214) @miya
+
+### 🐛 Bug Fixes
+
+- fix: Share link management button is not available (#7286) @yuki-takei
+- fix: Color for blinked section (#7287) @ayaka0417
+- fix: Error toaster appears after renaming (#7276) @miya
+- fix: Search Tag From Tag Sidebar Correctly (#7282) @yukendev
+- fix: Ignore backslash in page path (#7284) @yuki-takei
+- fix: Bug in Page Tree Selected Item Background Color (#7272) @yukendev
+- fix: Type guard comment.createdAt (#7281) @hakumizuki
+- fix: Color of login form (#7275) @ayaka0417
+- fix: Body of shared page is not displayed (#7270) @miya
+- fix: Refactor uri decoding in getServerSideProps (#7268) @yukendev
+- fix: Cannot login with LDAP unless local strategy is enabled (#7259) @miya
+- fix: Skeleton color (#7264) @ayaka0417
+- fix: Refactor axios date serializer config (#7249) @yukendev
+- fix: DeletePageModal shows an incorrect label when open (#7224) @yukendev
+- fix: Page path is not displayed in browser tab on shared page (#7243) @miya
+- fix: Lsx encode prefix twice (#7239) @yuki-takei
+- fix: Initial value of the page grant respects the parent page's one (#7232) @yukendev
+
+### 🧰 Maintenance
+
+- support: Build container images with AWS CodeBuild (#7258) @yuki-takei
+
+## [v6.0.2](https://github.com/weseek/growi/compare/v6.0.1...v6.0.2) - 2023-01-10
+
+### 🐛 Bug Fixes
+
+- fix: Attaching page title as initial header section (#7228) @yukendev
+- fix: Update PageTree data after saving page (#7227) @yukendev
+- fix: Lsx "filter" and "except" options does not work (#7226) @yuki-takei
+- fix: Omit remark-growi-directive shortcuts (#7225) @yuki-takei
+
+### 🧰 Maintenance
+
+- ci(deps-dev): bump textlint-rule-no-doubled-joshi from 4.0.0 to 4.0.1 (#7222) @dependabot
+
+## [v6.0.1](https://github.com/weseek/growi/compare/v6.0.0...v6.0.1) - 2023-01-07
+
+### 🚀 Improvement
+
+- imprv: Reduce frequent API calling by SWR (#7218) @yuki-takei
+- imprv: Do not use api for fetching pages when using shared pages (#7213) @miya
+
+### 🐛 Bug Fixes
+
+- fix: Custom logo not displayed on shared page (#7205) @miya
+- fix: Attach i18n User Setting and TagEditModal (#7216) @jam411
+- fix: Make PLANTUML_URI v5.x compatible (#7215) @yuki-takei
+- fix: Launch with PROMSTER_ENABLED=true failed (#7210) @yuki-takei
+- fix: Lsx performs with strange behavior (#7209) @yuki-takei
+
+### 🧰 Maintenance
+
+- support: Arm architecture (#7212) @yuki-takei
+- ci(deps): bump anothrNick/github-tag-action from 1.38.0 to 1.56.0 (#7195) @dependabot
+- ci(deps): bump google-github-actions/setup-gcloud from 0 to 1 (#7193) @dependabot
+- ci(deps): bump github/codeql-action from 1 to 2 (#7194) @dependabot
+- ci(deps): bump flat from 5.0.0 to 5.0.2 (#7200) @dependabot
+- ci(deps): bump json5 from 1.0.1 to 1.0.2 (#7201) @dependabot
+- ci(Mergify): configuration update (#7202) @yuki-takei
+- support: Uninstall swig-template (#7192) @yuki-takei
+
+## [v6.0.0](https://github.com/weseek/growi/compare/v5.1.8...v6.0.0) - 2022-12-27
+
+### 💎 Features
+
+- feat: Apply Next.js
+- feat: New plugin (#7034) @yuki-takei
+- feat: Save prevent xss custom settings in new format (#7124) @miya
+
+### 🧰 Maintenance
+
+- support: Request scoped SWR (#6742) @yuki-takei
+- support: Build preset themes within external package (#7057) @yuki-takei
+
+## [v5.1.8](https://github.com/weseek/growi/compare/v5.1.7...v5.1.8) - 2022-11-17
+
+### 🐛 Bug Fixes
+
+- fix: Put back page from trash (#6835) @yukendev
+- fix: Updating page content width is not working (#6914) @yukendev
+- fix: Create page at installer (#6930) @hakumizuki @yuki-takei
+
 ## [v5.1.7](https://github.com/weseek/growi/compare/v5.1.6...v5.1.7) - 2022-10-26
 
 ### 🐛 Bug Fixes

+ 85 - 0
bin/data-migrations/v6/README.md

@@ -0,0 +1,85 @@
+# Migration to v6 from v5
+
+> **Warning**
+> **Migration in this way is applied only to the latest revision. Past revisions are not applied.**
+
+## Usage
+```
+git clone https://github.com/weseek/growi
+cd growi/bin/data-migrations/v6
+
+NETWORK=growi_devcontainer_default \
+MONGO_URI=mongodb://growi_devcontainer_mongo_1/growi \
+docker run --rm \
+  --network $NETWORK \
+  -v "$(pwd)"/src:/opt \
+  -w /opt \
+  -e MIGRATION_TYPE=v6 \
+  mongo:6.0 \
+  /bin/mongosh $MONGO_URI migration.js
+```
+
+## Variables
+| Variable              | Description                                                                    | Default |
+| --------------------- | ------------------------------------------------------------------------------ | ------- |
+| NETWORK     | Network in docker compose of MongoDB server                                                         | -       |
+| MONGO_URI| URI that can connect to MongoDB                                                     | -       |
+
+## Environment variables
+### Required
+
+| Variable              | Description                                                                    | Default |
+| --------------------- | ------------------------------------------------------------------------------ | ------- |
+| MIGRATION_TYPE     | Migrated notation                                                        | -       |
+
+The value of `MIGRATION_TYPE` is one of the following.
+- `v6-drawio`: Migration for Draw.io notation only([
+reference](https://docs.growi.org/ja/admin-guide/upgrading/60x.html#%E4%BB%95%E6%A7%98%E5%A4%89%E6%9B%B4-draw-io-diagrams-net-%E8%A8%98%E6%B3%95))
+- `v6-plantuml`: Migration for PlantUML notation only([
+reference](https://docs.growi.org/ja/admin-guide/upgrading/60x.html#%E4%BB%95%E6%A7%98%E5%A4%89%E6%9B%B4-plantuml-%E8%A8%98%E6%B3%95))
+- `v6-tsv`: Migration for table notation by TSV only([
+reference](https://docs.growi.org/ja/admin-guide/upgrading/60x.html#%E4%BB%95%E6%A7%98%E5%A4%89%E6%9B%B4-csv-tsv-%E3%81%AB%E3%82%88%E3%82%8B%E3%83%86%E3%83%BC%E3%83%95%E3%82%99%E3%83%AB%E6%8F%8F%E7%94%BB%E8%A8%98%E6%B3%95))
+- `v6-csv`: Migration for table notation by CSV only([
+reference](https://docs.growi.org/ja/admin-guide/upgrading/60x.html#%E4%BB%95%E6%A7%98%E5%A4%89%E6%9B%B4-csv-tsv-%E3%81%AB%E3%82%88%E3%82%8B%E3%83%86%E3%83%BC%E3%83%95%E3%82%99%E3%83%AB%E6%8F%8F%E7%94%BB%E8%A8%98%E6%B3%95))
+- `v6-bracketlink`: Migration for only page links within GROWI([
+reference](https://docs.growi.org/ja/admin-guide/upgrading/60x.html#%E6%9C%AA%E5%AE%9F%E8%A3%85-%E5%BB%83%E6%AD%A2%E6%A4%9C%E8%A8%8E%E4%B8%AD-growi-%E7%8B%AC%E8%87%AA%E8%A8%98%E6%B3%95%E3%81%AE%E3%83%98%E3%82%9A%E3%83%BC%E3%82%B7%E3%82%99%E3%83%AA%E3%83%B3%E3%82%AF))
+- `v6`: Migration for all the above notations
+- `custom`: You can define your own processors and apply them to `revision` (see "Advanced" below for details)
+
+### Optional
+
+| Variable              | Description                                                                    | Default |
+| --------------------- | ------------------------------------------------------------------------------ | ------- |
+| BATCH_SIZE     | Number of revisions to be processed at one time(revision)                                                         | 100       |
+| BATCH_INTERVAL| Interval after batch processing(ms)                                                     | 3000       |
+
+※The `BATCH_INTERVAL` is for server load control. If you don't mind the load of the MongoDB, there is no problem to reduce it.
+
+## Advanced
+
+By creating a function in `growi/bin/data-migrations/v6/src/processor.js` that replaces a specific regular expression, you can replace all specific strings in the latest revisions for all pages.
+
+The following function replaces the string `foo` with the string `bar`.
+
+``` javascript
+function customProcessor(body) {
+  var fooRegExp = /foo/g; // foo regex
+  return body.replace(fooRegExp, 'bar'); // replace to bar
+}
+```
+
+By passing `custom` in the environment variable `MIGRATION_TYPE` and executing it, you can apply the `customProcessor` to all the latest `revisions`.
+```
+git clone https://github.com/weseek/growi
+cd growi/bin/data-migrations/v6
+
+NETWORK=growi_devcontainer_default \
+MONGO_URI=mongodb://growi_devcontainer_mongo_1/growi \
+docker run --rm \
+  --network $NETWORK \
+  -v "$(pwd)"/src:/opt \
+  -w /opt \
+  -e MIGRATION_TYPE=custom \
+  mongo:6.0 \
+  /bin/mongosh $MONGO_URI migration.js
+```

+ 57 - 0
bin/data-migrations/v6/src/migration.js

@@ -0,0 +1,57 @@
+
+/* eslint-disable no-undef, no-var, vars-on-top, no-restricted-globals, regex/invalid, import/extensions */
+// ignore lint error because this file is js as mongoshell
+
+var pagesCollection = db.getCollection('pages');
+var revisionsCollection = db.getCollection('revisions');
+
+var getProcessorArray = require('./processor.js');
+
+var migrationType = process.env.MIGRATION_TYPE;
+var processors = getProcessorArray(migrationType);
+
+var operations = [];
+
+var batchSize = process.env.BATCH_SIZE ?? 100; // default 100 revisions in 1 bulkwrite
+var batchSizeInterval = process.env.BATCH_INTERVAL ?? 3000; // default 3 sec
+
+// ===========================================
+// replace method with processors
+// ===========================================
+function replaceLatestRevisions(body, processors) {
+  var replacedBody = body;
+  processors.forEach((processor) => {
+    replacedBody = processor(replacedBody);
+  });
+  return replacedBody;
+}
+
+if (processors.length === 0) {
+  throw Error('No valid processors found. Please enter a valid environment variable');
+}
+
+pagesCollection.find({}).forEach((doc) => {
+  if (doc.revision) {
+    var revision = revisionsCollection.findOne({ _id: doc.revision });
+    var replacedBody = replaceLatestRevisions(revision.body, [...processors]);
+    var operation = {
+      updateOne: {
+        filter: { _id: revision._id },
+        update: {
+          $set: { body: replacedBody },
+        },
+      },
+    };
+    operations.push(operation);
+
+    // bulkWrite per 100 revisions
+    if (operations.length > (batchSize - 1)) {
+      revisionsCollection.bulkWrite(operations);
+      // sleep time can be set from env var
+      sleep(batchSizeInterval);
+      operations = [];
+    }
+  }
+});
+revisionsCollection.bulkWrite(operations);
+print('migration complete!');

+ 75 - 0
bin/data-migrations/v6/src/processor.js

@@ -0,0 +1,75 @@
+
+/* eslint-disable no-undef, no-var, vars-on-top, no-restricted-globals, regex/invalid */
+// ignore lint error because this file is js as mongoshell
+
+// ===========================================
+// processors for old format
+// ===========================================
+function drawioProcessor(body) {
+  var oldDrawioRegExp = /:::\s?drawio\n(.+?)\n:::/g; // drawio old format
+  return body.replace(oldDrawioRegExp, '``` drawio\n$1\n```');
+}
+
+function plantumlProcessor(body) {
+  var oldPlantUmlRegExp = /@startuml\n([\s\S]*?)\n@enduml/g; // plantUML old format
+  return body.replace(oldPlantUmlRegExp, '``` plantuml\n$1\n```');
+}
+
+function tsvProcessor(body) {
+  var oldTsvTableRegExp = /::: tsv(-h)?\n([\s\S]*?)\n:::/g; // TSV old format
+  return body.replace(oldTsvTableRegExp, '``` tsv$1\n$2\n```');
+}
+
+function csvProcessor(body) {
+  var oldCsvTableRegExp = /::: csv(-h)?\n([\s\S]*?)\n:::/g; // CSV old format
+  return body.replace(oldCsvTableRegExp, '``` csv$1\n$2\n```');
+}
+
+function bracketlinkProcessor(body) {
+  // https://regex101.com/r/btZ4hc/1
+  var oldBracketLinkRegExp = /(?<!\[)\[{1}(\/.*?)\]{1}(?!\])/g; // Page Link old format
+  return body.replace(oldBracketLinkRegExp, '[[$1]]');
+}
+
+// processor for MIGRATION_TYPE=custom
+function customProcessor(body) {
+  // ADD YOUR PROCESS HERE!
+  // https://github.com/weseek/growi/discussions/7180
+  return body;
+}
+
+// ===========================================
+// define processors
+// ===========================================
+
+function getProcessorArray(migrationType) {
+  var oldFormatProcessors;
+  switch (migrationType) {
+    case 'v6-drawio':
+      oldFormatProcessors = [drawioProcessor];
+      break;
+    case 'v6-plantuml':
+      oldFormatProcessors = [plantumlProcessor];
+      break;
+    case 'v6-tsv':
+      oldFormatProcessors = [tsvProcessor];
+      break;
+    case 'v6-csv':
+      oldFormatProcessors = [csvProcessor];
+      break;
+    case 'v6-bracketlink':
+      oldFormatProcessors = [bracketlinkProcessor];
+      break;
+    case 'v6':
+      oldFormatProcessors = [drawioProcessor, plantumlProcessor, tsvProcessor, csvProcessor, bracketlinkProcessor];
+      break;
+    case 'custom':
+      oldFormatProcessors = [customProcessor];
+      break;
+    default:
+      oldFormatProcessors = [];
+  }
+  return oldFormatProcessors;
+}
+
+module.exports = getProcessorArray;

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "6.0.0-RC.14",
+  "version": "6.0.6-RC.0",
   "packages": [
     "packages/*"
   ]

+ 1 - 1
package.json

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

+ 0 - 1
packages/app/.env.development

@@ -7,7 +7,6 @@ MIGRATIONS_DIR=src/migrations/
 APP_SITE_URL=http://localhost:3000
 FILE_UPLOAD=mongodb
 # MONGO_GRIDFS_TOTAL_LIMIT=10485760
-# NO_CDN=true
 MONGO_URI="mongodb://mongo:27017/growi"
 # REDIS_URI="http://redis:6379"
 # NCHAN_URI="http://nchan"

+ 0 - 191
packages/app/_obsolete/config/webpack.common.js

@@ -1,191 +0,0 @@
-/* eslint-disable */
-/**
- * @author: Yuki Takei <yuki@weseek.co.jp>
- */
-const path = require('path');
-
-const LodashModuleReplacementPlugin = require('lodash-webpack-plugin');
-const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
-const webpack = require('webpack');
-
-/*
-  * Webpack Plugins
-  */
-const WebpackAssetsManifest = require('webpack-assets-manifest');
-
-/*
-  * Webpack configuration
-  *
-  * See: http://webpack.github.io/docs/configuration.html#cli
-  */
-module.exports = (options) => {
-  return {
-    mode: options.mode,
-    entry: Object.assign({
-      'js/boot':                      './src/client/boot',
-      'js/app':                       './src/client/app',
-      'js/admin':                     './src/client/admin',
-      'js/nologin':                   './src/client/nologin',
-      'js/installer':                   './src/client/installer',
-      'js/legacy':                    './src/client/legacy/crowi',
-      'js/legacy-presentation':       './src/client/legacy/crowi-presentation',
-      'js/plugin':                    './src/client/plugin',
-      'js/hackmd-agent':              './src/client/hackmd-agent',
-      'js/hackmd-styles':             './src/client/hackmd-styles',
-      // styles
-      'styles/style-app':             './src/styles/style-app.scss',
-      'styles/style-presentation':    './src/styles/style-presentation.scss',
-      // themes
-      'styles/theme-default':         './src/styles/theme/default.scss',
-      'styles/theme-nature':          './src/styles/theme/nature.scss',
-      'styles/theme-mono-blue':       './src/styles/theme/mono-blue.scss',
-      'styles/theme-future':          './src/styles/theme/future.scss',
-      'styles/theme-kibela':          './src/styles/theme/kibela.scss',
-      'styles/theme-halloween':       './src/styles/theme/halloween.scss',
-      'styles/theme-christmas':       './src/styles/theme/christmas.scss',
-      'styles/theme-wood':            './src/styles/theme/wood.scss',
-      'styles/theme-island':          './src/styles/theme/island.scss',
-      'styles/theme-antarctic':       './src/styles/theme/antarctic.scss',
-      'styles/theme-spring':          './src/styles/theme/spring.scss',
-      'styles/theme-hufflepuff':      './src/styles/theme/hufflepuff.scss',
-      'styles/theme-fire-red':      './src/styles/theme/fire-red.scss',
-      'styles/theme-jade-green':      './src/styles/theme/jade-green.scss',
-      'styles/theme-blackboard':      './src/styles/theme/blackboard.scss',
-      // styles for external services
-      'styles/style-hackmd':          './src/styles-hackmd/style.scss',
-    }, options.entry || {}), // Merge with env dependent settings
-    output: Object.assign({
-      path: path.resolve(__dirname, '../public'),
-      publicPath: '/',
-      filename: '[name].bundle.js',
-    }, options.output || {}), // Merge with env dependent settings
-    externals: {
-      // require("jquery") is external and available
-      //  on the global var jQuery
-      jquery: 'jQuery',
-      hljs: 'hljs',
-      'dtrace-provider': 'dtrace-provider',
-    },
-    resolve: {
-      extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
-      plugins: [
-        new TsconfigPathsPlugin({
-          configFile: path.resolve(__dirname, '../tsconfig.build.client.json'),
-          extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
-        }),
-      ],
-    },
-    node: {
-      fs: 'empty',
-      module: 'empty',
-    },
-    module: {
-      rules: options.module.rules.concat([
-        {
-          test: /.(jsx?|tsx?)$/,
-          exclude: {
-            test: /node_modules/,
-            exclude: [ // include as a result
-              /node_modules\/codemirror/,
-            ],
-          },
-          use: [{
-            loader: 'ts-loader',
-            options: {
-              transpileOnly: true,
-              configFile: path.resolve(__dirname, '../tsconfig.build.client.json'),
-            },
-          }],
-        },
-        {
-          test: /locales/,
-          loader: '@alienfast/i18next-loader',
-          options: {
-            basenameAsNamespace: true,
-          },
-        },
-        /*
-          * File loader for supporting images, for example, in CSS files.
-          */
-        {
-          test: /\.(jpg|png|gif)$/,
-          use: 'file-loader',
-        },
-        /* File loader for supporting fonts, for example, in CSS files.
-         */
-        {
-          test: /\.(eot|woff2?|svg|ttf)([?]?.*)$/,
-          use: 'null-loader',
-        },
-      ]),
-    },
-    plugins: options.plugins.concat([
-
-      new WebpackAssetsManifest({ publicPath: true }),
-
-      new webpack.DefinePlugin({
-        'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
-      }),
-
-      // ignore
-      new webpack.IgnorePlugin(/^\.\/lib\/deflate\.js/, /markdown-it-plantuml/),
-      new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
-
-      new LodashModuleReplacementPlugin({
-        flattening: true,
-      }),
-
-      new webpack.ProvidePlugin({ // refs externals
-        jQuery: 'jquery',
-        $: 'jquery',
-      }),
-
-    ]),
-
-    devtool: options.devtool,
-    target: 'web', // Make web variables accessible to webpack, e.g. window
-    optimization: {
-      namedModules: true,
-      splitChunks: {
-        cacheGroups: {
-          style_commons: {
-            test: /\.(sc|sa|c)ss$/,
-            chunks: (chunk) => {
-              // ignore patterns
-              return chunk.name != null && !chunk.name.match(/style-|theme-|legacy-presentation/);
-            },
-            name: 'styles/style-commons',
-            minSize: 1,
-            priority: 30,
-            enforce: true,
-          },
-          commons: {
-            test: /(src|resource)[\\/].*\.(js|jsx|json)$/,
-            chunks: (chunk) => {
-              // ignore patterns
-              return chunk.name != null && !chunk.name.match(/boot/);
-            },
-            name: 'js/commons',
-            minChunks: 2,
-            minSize: 1,
-            priority: 20,
-          },
-          vendors: {
-            test: /node_modules[\\/].*\.(js|jsx|json)$/,
-            chunks: (chunk) => {
-              // ignore patterns
-              return chunk.name != null && !chunk.name.match(/boot|legacy-presentation|hackmd-/);
-            },
-            name: 'js/vendors',
-            minSize: 1,
-            priority: 10,
-            enforce: true,
-          },
-        },
-      },
-      minimizer: options.optimization.minimizer || [],
-    },
-    performance: options.performance || {},
-    stats: options.stats || {},
-  };
-};

+ 0 - 49
packages/app/_obsolete/config/webpack.dev.dll.js

@@ -1,49 +0,0 @@
-/* eslint-disable */
-/**
- * @author: Yuki Takei <yuki@weseek.co.jp>
- */
-const path = require('path');
-const webpack = require('webpack');
-
-
-module.exports = {
-  mode: 'development',
-  entry: {
-    dlls: [
-      // Libraries
-      'axios',
-      'browser-bunyan', 'bunyan-format',
-      'codemirror', 'react-codemirror2',
-      'date-fns',
-      'diff2html',
-      'debug',
-      'entities',
-      'i18next', 'i18next-browser-languagedetector',
-      'jquery-slimscroll',
-      'lodash', 'pako',
-      'markdown-it', 'csv-to-markdown-table',
-      'react', 'react-dom',
-      'reactstrap', 'react-bootstrap-typeahead',
-      'react-i18next', 'react-dropzone', 'react-hotkeys', 'react-copy-to-clipboard', 'react-waypoint',
-      'socket.io-client',
-      'toastr',
-      'unstated',
-      'xss',
-    ],
-  },
-  output: {
-    path: path.resolve(__dirname, '../public/dll'),
-    filename: 'dll.js',
-    library: 'growi_dlls',
-  },
-  resolve: {
-    extensions: ['.js', '.json'],
-    modules: [path.resolve(__dirname, '../src'), path.resolve(__dirname, '../node_modules')],
-  },
-  plugins: [
-    new webpack.DllPlugin({
-      path: path.resolve(__dirname, '../public/dll/manifest.json'),
-      name: 'growi_dlls',
-    }),
-  ],
-};

+ 0 - 81
packages/app/_obsolete/config/webpack.dev.js

@@ -1,81 +0,0 @@
-/* eslint-disable */
-/**
- * @author: Yuki Takei <yuki@weseek.co.jp>
- */
-
-const path = require('path');
-
-/*
- * Webpack Plugins
- */
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
-const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
-
-/**
- * Webpack Constants
- */
-const { ANALYZE } = process.env;
-
-module.exports = require('./webpack.common')({
-  mode: 'development',
-  devtool: 'cheap-module-eval-source-map',
-  entry: {
-    'js/dev': './src/client/dev',
-  },
-  resolve: {
-    modules: ['../node_modules'],
-  },
-  module: {
-    rules: [
-      {
-        test: /\.(css|scss)$/,
-        use: [
-          'style-loader',
-          { loader: 'css-loader', options: { sourceMap: true } },
-          { loader: 'sass-loader', options: { sourceMap: true } },
-        ],
-        exclude: [
-          path.resolve(__dirname, '../src/styles-hackmd'),
-          path.resolve(__dirname, '../src/styles/style-presentation.scss'),
-        ],
-      },
-      { // Dump CSS for HackMD
-        test: /\.(css|scss)$/,
-        use: [
-          MiniCssExtractPlugin.loader,
-          'css-loader',
-          'sass-loader',
-        ],
-        include: [
-          path.resolve(__dirname, '../src/styles-hackmd'),
-          path.resolve(__dirname, '../src/styles/style-presentation.scss'),
-        ],
-      },
-    ],
-  },
-  plugins: [
-
-    new MiniCssExtractPlugin({
-      filename: '[name].bundle.css',
-    }),
-
-    new BundleAnalyzerPlugin({
-      analyzerMode: ANALYZE ? 'server' : 'disabled',
-    }),
-
-    new HardSourceWebpackPlugin(),
-    new HardSourceWebpackPlugin.ExcludeModulePlugin([
-      {
-        // see https://github.com/mzgoddard/hard-source-webpack-plugin/blob/master/README.md#excludemoduleplugin
-        test: /mini-css-extract-plugin[\\/]dist[\\/]loader/,
-      },
-    ]),
-
-  ],
-  optimization: {},
-  performance: {
-    hints: false,
-  },
-
-});

+ 0 - 77
packages/app/_obsolete/config/webpack.prod.js

@@ -1,77 +0,0 @@
-/* eslint-disable */
-/**
- * @author: Yuki Takei <yuki@weseek.co.jp>
- */
-
-const path = require('path');
-
-/**
-  * Webpack Plugins
-  */
-const TerserPlugin = require('terser-webpack-plugin');
-const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
-const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
-
-/**
-  * Webpack Constants
-  */
-const { ANALYZE_BUNDLE_SIZE } = process.env;
-
-module.exports = require('./webpack.common')({
-  mode: 'production',
-  devtool: undefined,
-  output: {
-    filename: '[name].[chunkhash].bundle.js',
-    chunkFilename: '[name].[chunkhash].chunk.js',
-  },
-  module: {
-    rules: [
-      {
-        test: /\.(css|scss)$/,
-        use: [
-          MiniCssExtractPlugin.loader,
-          'css-loader',
-          {
-            loader: 'postcss-loader',
-            options: {
-              sourceMap: false,
-              postcssOptions: {
-                plugins: [
-                  require('autoprefixer')(),
-                ],
-              },
-            },
-          },
-          'sass-loader',
-        ],
-        exclude: [path.resolve(__dirname, '../src/client/legacy')],
-      },
-      {
-        test: /\.(css|scss)$/,
-        use: ['style-loader', 'css-loader', 'sass-loader'],
-        include: [path.resolve(__dirname, '../src/client/legacy')],
-      },
-    ],
-  },
-  plugins: [
-
-    new MiniCssExtractPlugin({
-      filename: '[name].[hash].css',
-    }),
-
-    new BundleAnalyzerPlugin({
-      analyzerMode: ANALYZE_BUNDLE_SIZE ? 'static' : 'disabled',
-      reportFilename: path.resolve(__dirname, '../report/bundle-analyzer.html'),
-      openAnalyzer: false,
-    }),
-
-  ],
-  optimization: {
-    minimize: true,
-    minimizer: [
-      new TerserPlugin({}),
-      new OptimizeCSSAssetsPlugin({}),
-    ],
-  },
-});

+ 0 - 167
packages/app/_obsolete/src/client/app.jsx

@@ -1,167 +0,0 @@
-import React from 'react';
-
-import { DndProvider } from 'react-dnd';
-import { HTML5Backend } from 'react-dnd-html5-backend';
-import ReactDOM from 'react-dom';
-import { I18nextProvider } from 'react-i18next';
-import { SWRConfig } from 'swr';
-import { Provider } from 'unstated';
-
-import ContextExtractor from '~/client/services/ContextExtractor';
-import EditorContainer from '~/client/services/EditorContainer';
-import PageContainer from '~/client/services/PageContainer';
-import { IdenticalPathPage } from '~/components/IdenticalPathPage';
-import PrivateLegacyPages from '~/components/PrivateLegacyPages';
-import loggerFactory from '~/utils/logger';
-import { swrGlobalConfiguration } from '~/utils/swr-utils';
-
-import ErrorBoundary from '../components/ErrorBoudary';
-import Fab from '../components/Fab';
-import ForbiddenPage from '../components/ForbiddenPage';
-import RecentlyCreatedIcon from '../components/Icons/RecentlyCreatedIcon';
-import InAppNotificationPage from '../components/InAppNotification/InAppNotificationPage';
-import PersonalSettings from '../components/Me/PersonalSettings';
-import MyDraftList from '../components/MyDraftList/MyDraftList';
-import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSubNavigation';
-import GrowiSubNavigationSwitcher from '../components/Navbar/GrowiSubNavigationSwitcher';
-import NotFoundPage from '../components/NotFoundPage';
-import { Page } from '../components/Page';
-import DisplaySwitcher from '../components/Page/DisplaySwitcher';
-import RedirectedAlert from '../components/Page/RedirectedAlert';
-import ShareLinkAlert from '../components/Page/ShareLinkAlert';
-import { PageComment } from '../components/PageComment';
-import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLazyRenderer';
-import PageContentFooter from '../components/PageContentFooter';
-import BookmarkList from '../components/PageList/BookmarkList';
-import PageStatusAlert from '../components/PageStatusAlert';
-import { PageTimeline } from '../components/PageTimeline';
-import RecentCreated from '../components/RecentCreated/RecentCreated';
-import { SearchPage } from '../components/SearchPage';
-import Sidebar from '../components/Sidebar';
-import TrashPageList from '../components/TrashPageList';
-
-import { appContainer, componentMappings } from './base';
-
-const logger = loggerFactory('growi:cli:app');
-
-appContainer.initContents();
-
-const { i18n } = appContainer;
-const socketIoContainer = appContainer.getContainer('SocketIoContainer');
-
-// create unstated container instance
-const pageContainer = new PageContainer(appContainer);
-const editorContainer = new EditorContainer(appContainer);
-const injectableContainers = [
-  appContainer, socketIoContainer, pageContainer, editorContainer,
-];
-
-logger.info('unstated containers have been initialized');
-
-/**
- * define components
- *  key: id of element
- *  value: React Element
- */
-Object.assign(componentMappings, {
-  'grw-sidebar-wrapper': <Sidebar />,
-
-  'search-page': <SearchPage appContainer={appContainer} />,
-  'private-regacy-pages': <PrivateLegacyPages appContainer={appContainer} />,
-
-  'all-in-app-notifications': <InAppNotificationPage />,
-  'identical-path-page': <IdenticalPathPage />,
-
-  // 'revision-history': <PageHistory pageId={pageId} />,
-
-  'grw-page-status-alert-container': <PageStatusAlert />,
-
-  'maintenance-mode-content': <MaintenanceModeContent />,
-
-  'trash-page-list-container': <TrashPageList />,
-
-  'not-found-page': <NotFoundPage />,
-
-  'forbidden-page': <ForbiddenPage isLinkSharingDisabled={appContainer.config.disableLinkSharing} />,
-
-  'page-timeline': <PageTimeline />,
-
-  'personal-setting': <PersonalSettings />,
-  'my-drafts': <MyDraftList />,
-
-  'grw-fab-container': <Fab />,
-
-  'share-link-alert': <ShareLinkAlert />,
-  'redirected-alert': <RedirectedAlert />,
-});
-
-// additional definitions if data exists
-if (pageContainer.state.pageId != null) {
-  Object.assign(componentMappings, {
-    'page-comments-list': <PageComment appContainer={appContainer} pageId={pageContainer.state.pageId} isReadOnly={false} titleAlign="left" />,
-    'page-comment-write': <CommentEditorLazyRenderer appContainer={appContainer} pageId={pageContainer.state.pageId} />,
-    'page-content-footer': <PageContentFooter
-      createdAt={new Date(pageContainer.state.createdAt)}
-      updatedAt={new Date(pageContainer.state.updatedAt)}
-      creator={pageContainer.state.creator}
-      revisionAuthor={pageContainer.state.revisionAuthor}
-    />,
-
-    'recent-created-icon': <RecentlyCreatedIcon />,
-  });
-}
-if (pageContainer.state.creator != null) {
-  Object.assign(componentMappings, {
-    'user-created-list': <RecentCreated userId={pageContainer.state.creator._id} />,
-    'user-bookmark-list': <BookmarkList userId={pageContainer.state.creator._id} />,
-  });
-}
-if (pageContainer.state.path != null) {
-  Object.assign(componentMappings, {
-    // eslint-disable-next-line quote-props
-    'page': <Page />,
-    'grw-subnav-container': <GrowiContextualSubNavigation isLinkSharingDisabled={appContainer.config.disableLinkSharing} />,
-    'grw-subnav-switcher-container': <GrowiSubNavigationSwitcher isLinkSharingDisabled={appContainer.config.disableLinkSharing} />,
-    'display-switcher': <DisplaySwitcher />,
-  });
-}
-
-const renderMainComponents = () => {
-  Object.keys(componentMappings).forEach((key) => {
-    const elem = document.getElementById(key);
-    if (elem) {
-      ReactDOM.render(
-        <I18nextProvider i18n={i18n}>
-          <ErrorBoundary>
-            <SWRConfig value={swrGlobalConfiguration}>
-              <Provider inject={injectableContainers}>
-                <DndProvider backend={HTML5Backend}>
-                  {componentMappings[key]}
-                </DndProvider>
-              </Provider>
-            </SWRConfig>
-          </ErrorBoundary>
-        </I18nextProvider>,
-        elem,
-      );
-    }
-  });
-};
-
-// extract context before rendering main components
-const elem = document.getElementById('growi-context-extractor');
-if (elem != null) {
-  ReactDOM.render(
-    <SWRConfig value={swrGlobalConfiguration}>
-      <ContextExtractor></ContextExtractor>
-    </SWRConfig>,
-    elem,
-    renderMainComponents,
-  );
-}
-else {
-  renderMainComponents();
-}
-
-// initialize scrollpos-styler
-ScrollPosStyler.init();

+ 0 - 69
packages/app/_obsolete/src/client/base.jsx

@@ -1,69 +0,0 @@
-import React from 'react';
-
-import EventEmitter from 'events';
-
-import AppContainer from '~/client/services/AppContainer';
-import { DescendantsPageListModal } from '~/components/DescendantsPageListModal';
-import PutbackPageModal from '~/components/PutbackPageModal';
-import ShortcutsModal from '~/components/ShortcutsModal';
-import SystemVersion from '~/components/SystemVersion';
-import InterceptorManager from '~/services/interceptor-manager';
-import loggerFactory from '~/utils/logger';
-
-import EmptyTrashModal from '../components/EmptyTrashModal';
-import HotkeysManager from '../components/Hotkeys/HotkeysManager';
-import { GrowiNavbar } from '../components/Navbar/GrowiNavbar';
-import { GrowiNavbarBottom } from '../components/Navbar/GrowiNavbarBottom';
-import PageAccessoriesModal from '../components/PageAccessoriesModal';
-import PageCreateModal from '../components/PageCreateModal';
-import PageDeleteModal from '../components/PageDeleteModal';
-import PageDuplicateModal from '../components/PageDuplicateModal';
-import PagePresentationModal from '../components/PagePresentationModal';
-import PageRenameModal from '../components/PageRenameModal';
-
-import ShowPageAccessoriesModal from './services/ShowPageAccessoriesModal';
-
-const logger = loggerFactory('growi:cli:app');
-
-if (!window) {
-  window = {};
-}
-
-window.globalEmitter = new EventEmitter();
-window.interceptorManager = new InterceptorManager();
-
-// create unstated container instance
-const appContainer = new AppContainer();
-
-appContainer.initApp();
-
-logger.info('AppContainer has been initialized');
-
-/**
- * define components
- *  key: id of element
- *  value: React Element
- */
-const componentMappings = {
-  'grw-navbar': <GrowiNavbar />,
-  'grw-navbar-bottom-container': <GrowiNavbarBottom />,
-
-  'page-create-modal': <PageCreateModal />,
-  'page-delete-modal': <PageDeleteModal />,
-  'empty-trash-modal': <EmptyTrashModal />,
-  'page-duplicate-modal': <PageDuplicateModal />,
-  'page-rename-modal': <PageRenameModal />,
-  'page-presentation-modal': <PagePresentationModal />,
-  'page-accessories-modal': <PageAccessoriesModal />,
-  'descendants-page-list-modal': <DescendantsPageListModal />,
-  'page-put-back-modal': <PutbackPageModal />,
-  'shortcuts-modal': <ShortcutsModal />,
-
-  'grw-hotkeys-manager': <HotkeysManager />,
-  'system-version': <SystemVersion />,
-
-
-  'show-page-accessories-modal': <ShowPageAccessoriesModal />,
-};
-
-export { appContainer, componentMappings };

+ 0 - 5
packages/app/_obsolete/src/client/boot.js

@@ -1,5 +0,0 @@
-import {
-  applyOldIos,
-} from './util/old-ios';
-
-applyOldIos();

+ 0 - 60
packages/app/_obsolete/src/client/installer.jsx

@@ -1,60 +0,0 @@
-import React from 'react';
-
-import ReactDOM from 'react-dom';
-import { I18nextProvider } from 'react-i18next';
-import { SWRConfig } from 'swr';
-
-
-import { swrGlobalConfiguration } from '~/utils/swr-utils';
-
-import InstallerForm from '../components/InstallerForm';
-
-import ContextExtractor from './services/ContextExtractor';
-import { i18nFactory } from './util/i18n';
-
-const i18n = i18nFactory();
-
-const componentMappings = {};
-
-// render InstallerForm
-const installerFormContainerElem = document.getElementById('installer-form-container');
-if (installerFormContainerElem) {
-  const userName = installerFormContainerElem.dataset.userName;
-  const name = installerFormContainerElem.dataset.name;
-  const email = installerFormContainerElem.dataset.email;
-
-  Object.assign(componentMappings, {
-    'installer-form-container': <InstallerForm userName={userName} name={name} email={email} />,
-  });
-}
-
-const renderMainComponents = () => {
-  Object.keys(componentMappings).forEach((key) => {
-    const elem = document.getElementById(key);
-    if (elem) {
-      ReactDOM.render(
-        <I18nextProvider i18n={i18n}>
-          <SWRConfig value={swrGlobalConfiguration}>
-            {componentMappings[key]}
-          </SWRConfig>
-        </I18nextProvider>,
-        elem,
-      );
-    }
-  });
-};
-
-// extract context before rendering main components
-const elem = document.getElementById('growi-context-extractor');
-if (elem != null) {
-  ReactDOM.render(
-    <SWRConfig value={swrGlobalConfiguration}>
-      <ContextExtractor></ContextExtractor>
-    </SWRConfig>,
-    elem,
-    renderMainComponents,
-  );
-}
-else {
-  renderMainComponents();
-}

+ 0 - 52
packages/app/_obsolete/src/client/legacy/crowi.js

@@ -1,52 +0,0 @@
-/* eslint-disable react/jsx-filename-extension */
-require('jquery.cookie');
-
-const Crowi = {};
-
-if (!window) {
-  window = {};
-}
-window.Crowi = Crowi;
-
-Crowi.setCaretLine = function(line) {
-  // eslint-disable-next-line no-undef
-  globalEmitter.emit('setCaretLine', line);
-};
-
-// original: middleware.swigFilter
-Crowi.userPicture = function(user) {
-  if (!user) {
-    return '/images/icons/user.svg';
-  }
-
-  return user.image || '/images/icons/user.svg';
-};
-
-Crowi.initClassesByOS = function() {
-  // add classes to cmd-key by OS
-  const platform = navigator.platform.toLowerCase();
-  const isMac = (platform.indexOf('mac') > -1);
-
-  document.querySelectorAll('.system-version .cmd-key').forEach((element) => {
-    if (isMac) {
-      element.classList.add('mac');
-    }
-    else {
-      element.classList.add('win');
-    }
-  });
-
-  document.querySelectorAll('#shortcuts-modal .cmd-key').forEach((element) => {
-    if (isMac) {
-      element.classList.add('mac');
-    }
-    else {
-      element.classList.add('win', 'key-longer');
-    }
-  });
-};
-
-// adjust min-height of page for print temporarily
-window.onbeforeprint = function() {
-  $('#page-wrapper').css('min-height', '0px');
-};

+ 0 - 142
packages/app/_obsolete/src/client/nologin.jsx

@@ -1,142 +0,0 @@
-import React from 'react';
-
-import ReactDOM from 'react-dom';
-import { I18nextProvider } from 'react-i18next';
-import { SWRConfig } from 'swr';
-import { Provider } from 'unstated';
-
-
-import AppContainer from '~/client/services/AppContainer';
-import CompleteUserRegistrationForm from '~/components/CompleteUserRegistrationForm';
-import { swrGlobalConfiguration } from '~/utils/swr-utils';
-
-import LoginForm from '../components/LoginForm';
-import PasswordResetExecutionForm from '../components/PasswordResetExecutionForm';
-import PasswordResetRequestForm from '../components/PasswordResetRequestForm';
-
-import ContextExtractor from './services/ContextExtractor';
-import { i18nFactory } from './util/i18n';
-
-const i18n = i18nFactory();
-
-
-const componentMappings = {};
-
-const appContainer = new AppContainer();
-appContainer.initApp();
-
-// render loginForm
-const loginFormElem = document.getElementById('login-form');
-if (loginFormElem) {
-  const username = loginFormElem.dataset.username;
-  const name = loginFormElem.dataset.name;
-  const email = loginFormElem.dataset.email;
-  const isRegistrationEnabled = loginFormElem.dataset.isRegistrationEnabled === 'true';
-  const isEmailAuthenticationEnabled = loginFormElem.dataset.isEmailAuthenticationEnabled === 'true';
-  const registrationMode = loginFormElem.dataset.registrationMode;
-  const isPasswordResetEnabled = loginFormElem.dataset.isPasswordResetEnabled === 'true';
-
-
-  let registrationWhiteList = loginFormElem.dataset.registrationWhiteList;
-  registrationWhiteList = registrationWhiteList.length > 0
-    ? registrationWhiteList = loginFormElem.dataset.registrationWhiteList.split(',')
-    : registrationWhiteList = [];
-
-
-  const isLocalStrategySetup = loginFormElem.dataset.isLocalStrategySetup === 'true';
-  const isLdapStrategySetup = loginFormElem.dataset.isLdapStrategySetup === 'true';
-  const objOfIsExternalAuthEnableds = {
-    google: loginFormElem.dataset.isGoogleAuthEnabled === 'true',
-    github: loginFormElem.dataset.isGithubAuthEnabled === 'true',
-    facebook: loginFormElem.dataset.isFacebookAuthEnabled === 'true',
-    saml: loginFormElem.dataset.isSamlAuthEnabled === 'true',
-    oidc: loginFormElem.dataset.isOidcAuthEnabled === 'true',
-  };
-
-  Object.assign(componentMappings, {
-    [loginFormElem.id]: (
-      <LoginForm
-        username={username}
-        name={name}
-        email={email}
-        isRegistrationEnabled={isRegistrationEnabled}
-        isEmailAuthenticationEnabled={isEmailAuthenticationEnabled}
-        registrationMode={registrationMode}
-        registrationWhiteList={registrationWhiteList}
-        isPasswordResetEnabled={isPasswordResetEnabled}
-        isLocalStrategySetup={isLocalStrategySetup}
-        isLdapStrategySetup={isLdapStrategySetup}
-        objOfIsExternalAuthEnableds={objOfIsExternalAuthEnableds}
-      />
-    ),
-  });
-}
-
-// render PasswordResetRequestForm
-const passwordResetRequestFormElem = document.getElementById('password-reset-request-form');
-if (passwordResetRequestFormElem) {
-  Object.assign(componentMappings, {
-    [passwordResetRequestFormElem.id]: <PasswordResetRequestForm />,
-  });
-}
-
-// render PasswordResetExecutionForm
-const passwordResetExecutionFormElem = document.getElementById('password-reset-execution-form');
-if (passwordResetExecutionFormElem) {
-  Object.assign(componentMappings, {
-    [passwordResetExecutionFormElem.id]: <PasswordResetExecutionForm />,
-  });
-}
-
-// render UserActivationForm
-const UserActivationForm = document.getElementById('user-activation-form');
-if (UserActivationForm) {
-  const messageErrors = UserActivationForm.dataset.messageErrors;
-  const inputs = UserActivationForm.dataset.inputs;
-  const email = UserActivationForm.dataset.email;
-  const token = UserActivationForm.dataset.token;
-
-  Object.assign(componentMappings, {
-    [UserActivationForm.id]: (
-      <CompleteUserRegistrationForm
-        messageErrors={messageErrors}
-        inputs={inputs}
-        email={email}
-        token={token}
-      />
-    ),
-  });
-}
-
-const renderMainComponents = () => {
-  Object.keys(componentMappings).forEach((key) => {
-    const elem = document.getElementById(key);
-    if (elem) {
-      ReactDOM.render(
-        <I18nextProvider i18n={i18n}>
-          <SWRConfig value={swrGlobalConfiguration}>
-            <Provider inject={[appContainer]}>
-              {componentMappings[key]}
-            </Provider>
-          </SWRConfig>
-        </I18nextProvider>,
-        elem,
-      );
-    }
-  });
-};
-
-// extract context before rendering main components
-const elem = document.getElementById('growi-context-extractor');
-if (elem != null) {
-  ReactDOM.render(
-    <SWRConfig value={swrGlobalConfiguration}>
-      <ContextExtractor></ContextExtractor>
-    </SWRConfig>,
-    elem,
-    renderMainComponents,
-  );
-}
-else {
-  renderMainComponents();
-}

+ 0 - 55
packages/app/_obsolete/src/client/plugin.js

@@ -1,55 +0,0 @@
-import loggerFactory from '~/utils/logger';
-
-const logger = loggerFactory('growi:plugin');
-
-export default class GrowiPlugin {
-
-  /**
-   * process plugin entry
-   *
-   * @param {AppContainer} appContainer
-   *
-   * @memberof CrowiPlugin
-   */
-  // eslint-disable-next-line @typescript-eslint/no-unused-vars
-  installAll(appContainer) {
-    // import plugin definitions
-    let definitions = [];
-    try {
-      definitions = require('^/tmp/plugins/plugin-definitions');
-    }
-    catch (e) {
-      logger.error('failed to load definitions');
-      logger.error(e);
-      return;
-    }
-
-    definitions.forEach((definition) => {
-      const meta = definition.meta;
-
-      switch (meta.pluginSchemaVersion) {
-        // v1, v2 and v3 is deprecated
-        case 1:
-          logger.warn('pluginSchemaVersion 1 is deprecated', definition);
-          break;
-        case 2:
-          logger.warn('pluginSchemaVersion 2 is deprecated', definition);
-          break;
-        case 3:
-          logger.warn('pluginSchemaVersion 2 is deprecated', definition);
-          break;
-        case 4:
-          definition.entries.forEach((entry) => {
-            entry(appContainer);
-          });
-          break;
-        default:
-          logger.warn('Unsupported schema version', meta.pluginSchemaVersion);
-      }
-    });
-
-  }
-
-}
-
-window.growiPlugin = new GrowiPlugin();

+ 0 - 133
packages/app/_obsolete/src/client/services/AppContainer.js

@@ -1,133 +0,0 @@
-import { Container } from 'unstated';
-
-// import { i18nFactory } from '../util/i18n';
-
-/**
- * Service container related to options for Application
- * @extends {Container} unstated Container
- */
-export default class AppContainer extends Container {
-
-  constructor() {
-    super();
-
-    this.config = JSON.parse(document.getElementById('growi-context-hydrate').textContent || '{}');
-
-    // init i18n
-    const currentUserElem = document.getElementById('growi-current-user');
-    let userLocaleId;
-    if (currentUserElem != null) {
-      const currentUser = JSON.parse(currentUserElem.textContent);
-      userLocaleId = currentUser?.lang;
-    }
-    // this.i18n = i18nFactory(userLocaleId);
-
-    this.containerInstances = {};
-    this.componentInstances = {};
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AppContainer';
-  }
-
-  initApp() {
-    this.injectToWindow();
-  }
-
-  initContents() {
-    const body = document.querySelector('body');
-
-    this.isDocSaved = true;
-
-    const isPluginEnabled = body.dataset.pluginEnabled === 'true';
-    if (isPluginEnabled) {
-      this.initPlugins();
-    }
-
-    this.injectToWindow();
-  }
-
-  initPlugins() {
-    const growiPlugin = window.growiPlugin;
-    growiPlugin.installAll(this);
-  }
-
-  injectToWindow() {
-    // for fix lint error
-
-    // window.appContainer = this;
-
-    // const growiRenderer = new GrowiRenderer(this.getConfig());
-    // growiRenderer.init();
-
-    // window.growiRenderer = growiRenderer;
-
-    // // backward compatibility
-    // window.crowi = this;
-    // window.crowiRenderer = window.growiRenderer;
-    // window.crowiPlugin = window.growiPlugin;
-  }
-
-  getConfig() {
-    return this.config;
-  }
-
-  /**
-   * Register unstated container instance
-   * @param {object} instance unstated container instance
-   */
-  registerContainer(instance) {
-    if (instance == null) {
-      throw new Error('The specified instance must not be null');
-    }
-
-    const className = instance.constructor.getClassName();
-
-    if (this.containerInstances[className] != null) {
-      throw new Error('The specified instance couldn\'t register because the same type object has already been registered');
-    }
-
-    this.containerInstances[className] = instance;
-  }
-
-  /**
-   * Get registered unstated container instance
-   * !! THIS METHOD SHOULD ONLY BE USED FROM unstated CONTAINERS !!
-   * !! From component instances, inject containers with `import { Subscribe } from 'unstated'` !!
-   *
-   * @param {string} className
-   */
-  getContainer(className) {
-    return this.containerInstances[className];
-  }
-
-
-  /*
-  * Note: Use globalEmitter instaead of registerComponentInstance and getComponentInstance
-  */
-
-  /**
-   * Register React component instance
-   * @param {string} id
-   * @param {object} instance React component instance
-   */
-  // registerComponentInstance(id, instance) {
-  //   if (instance == null) {
-  //     throw new Error('The specified instance must not be null');
-  //   }
-
-  //   this.componentInstances[id] = instance;
-  // }
-
-  /**
-   * Get registered React component instance
-   * @param {string} id
-   */
-  // getComponentInstance(id) {
-  //   return this.componentInstances[id];
-  // }
-
-}

+ 0 - 214
packages/app/_obsolete/src/client/services/ContextExtractor.tsx

@@ -1,214 +0,0 @@
-/* eslint-disable */
-import React, { FC, useEffect, useState } from 'react';
-
-import { pagePathUtils } from '@growi/core';
-
-import { CustomWindow } from '~/interfaces/global';
-import { IUserUISettings } from '~/interfaces/user-ui-settings';
-// import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-import {
-  useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
-  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
-  useSelectedGrant,
-} from '~/stores/ui';
-import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
-
-import {
-  useSiteUrl,
-  useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
-  useIsTrashPage, useIsUserPage, useLastUpdateUsername,
-  useCurrentPageId, usePageIdOnHackmd, usePageUser, useCurrentPagePath, useRevisionCreatedAt, useRevisionId, useRevisionIdHackmdSynced,
-  useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUser, useTargetAndAncestors,
-  useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
-  useIsAclEnabled, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsEnabledAttachTitleHeader,
-  useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useGrowiVersion, useAuditLogEnabled,
-  useActivityExpirationSeconds, useAuditLogAvailableActions, useRendererConfig,
-} from '../../stores/context';
-
-const { isTrashPage: _isTrashPage } = pagePathUtils;
-
-const jsonNull = 'null';
-
-const ContextExtractorOnce: FC = () => {
-
-  const mainContent = document.querySelector('#content-main');
-  const notFoundContentForPt = document.getElementById('growi-pagetree-not-found-context');
-  const notFoundContext = document.getElementById('growi-not-found-context');
-  const forbiddenContent = document.getElementById('forbidden-page');
-
-  // get csrf token from body element
-  // DO NOT REMOVE: uploading attachment data requires appContainer.csrfToken
-  const body = document.querySelector('body');
-  const csrfToken = body?.dataset.csrftoken;
-
-  /*
-   * App Context from DOM
-   */
-  const currentUser = JSON.parse(document.getElementById('growi-current-user')?.textContent || jsonNull);
-
-  /*
-   * Settings from context-hydrate DOM
-   */
-  const configByContextHydrate = JSON.parse(document.getElementById('growi-context-hydrate')?.textContent || jsonNull);
-
-  /*
-   * UserUISettings from DOM
-   */
-  const userUISettings: Partial<IUserUISettings> = JSON.parse(document.getElementById('growi-user-ui-settings')?.textContent || jsonNull);
-
-  /*
-   * Page Context from DOM
-   */
-  const revisionId = mainContent?.getAttribute('data-page-revision-id');
-  const path = decodeURI(mainContent?.getAttribute('data-path') || '');
-  // assign `null` to avoid returning empty string
-  const pageId = mainContent?.getAttribute('data-page-id') || null;
-
-  const revisionCreatedAt = +(mainContent?.getAttribute('data-page-revision-created') || '');
-
-  const deletedAt = mainContent?.getAttribute('data-page-deleted-at') || null;
-  const isIdenticalPath = JSON.parse(mainContent?.getAttribute('data-identical-path') || jsonNull) ?? false;
-  const isUserPage = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull) != null;
-  const isTrashPage = _isTrashPage(path);
-  const isNotCreatable = JSON.parse(mainContent?.getAttribute('data-page-is-not-creatable') || jsonNull) ?? false;
-  const isForbidden = forbiddenContent != null;
-  const pageUser = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull);
-  const hasChildren = JSON.parse(mainContent?.getAttribute('data-page-has-children') || jsonNull);
-  const hasParent = JSON.parse(mainContent?.getAttribute('data-has-parent') || jsonNull);
-  const templateTagData = mainContent?.getAttribute('data-template-tags') || null;
-  const shareLinksNumber = mainContent?.getAttribute('data-share-links-number');
-  const shareLinkId = JSON.parse(mainContent?.getAttribute('data-share-link-id') || jsonNull);
-  const revisionIdHackmdSynced = mainContent?.getAttribute('data-page-revision-id-hackmd-synced') || null;
-  const lastUpdateUsername = mainContent?.getAttribute('data-page-last-update-username') || null;
-  const deleteUsername = mainContent?.getAttribute('data-page-delete-username') || null;
-  const pageIdOnHackmd = mainContent?.getAttribute('data-page-id-on-hackmd') || null;
-  const hasDraftOnHackmd = !!mainContent?.getAttribute('data-page-has-draft-on-hackmd');
-  const targetAndAncestors = JSON.parse(document.getElementById('growi-pagetree-target-and-ancestors')?.textContent || jsonNull);
-  const notFoundTargetPathOrId = JSON.parse(notFoundContentForPt?.getAttribute('data-not-found-target-path-or-id') || jsonNull);
-  const isSearchPage = document.getElementById('search-page') != null;
-  const isEmptyPage = JSON.parse(mainContent?.getAttribute('data-page-is-empty') || jsonNull) ?? false;
-
-  const grant = +(mainContent?.getAttribute('data-page-grant') || 1);
-  const grantGroupId = mainContent?.getAttribute('data-page-grant-group') || null;
-  const grantGroupName = mainContent?.getAttribute('data-page-grant-group-name') || null;
-
-  /*
-   * use static swr
-   */
-  useCsrfToken(csrfToken);
-
-  // App
-  useCurrentUser(currentUser);
-
-  // UserUISettings
-  usePreferDrawerModeByUser(userUISettings?.preferDrawerModeByUser ?? configByContextHydrate.isSidebarDrawerMode);
-  usePreferDrawerModeOnEditByUser(userUISettings?.preferDrawerModeOnEditByUser);
-  useSidebarCollapsed(userUISettings?.isSidebarCollapsed ?? configByContextHydrate.isSidebarClosedAtDockMode);
-  useCurrentSidebarContents(userUISettings?.currentSidebarContents);
-  useCurrentProductNavWidth(userUISettings?.currentProductNavWidth);
-
-  // hydrated config
-  useSiteUrl(configByContextHydrate.crowi.url);
-  useIsAclEnabled(configByContextHydrate.isAclEnabled);
-  useIsSearchServiceConfigured(configByContextHydrate.isSearchServiceConfigured);
-  useIsSearchServiceReachable(configByContextHydrate.isSearchServiceReachable);
-  useIsEnabledAttachTitleHeader(configByContextHydrate.isEnabledAttachTitleHeader);
-  useIsIndentSizeForced(configByContextHydrate.isIndentSizeForced);
-  useDefaultIndentSize(configByContextHydrate.adminPreferredIndentSize);
-  useAuditLogEnabled(configByContextHydrate.auditLogEnabled);
-  useActivityExpirationSeconds(configByContextHydrate.activityExpirationSeconds);
-  useAuditLogAvailableActions(configByContextHydrate.auditLogAvailableActions);
-  useGrowiVersion(configByContextHydrate.crowi.version);
-  useRendererConfig({
-    isEnabledLinebreaks: configByContextHydrate.isEnabledLinebreaks,
-    isEnabledLinebreaksInComments: configByContextHydrate.isEnabledLinebreaksInComments,
-    adminPreferredIndentSize: configByContextHydrate.adminPreferredIndentSize,
-    isIndentSizeForced: configByContextHydrate.isIndentSizeForced,
-
-    isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
-    attrWhiteList: configByContextHydrate.attrWhiteList,
-    tagWhiteList: configByContextHydrate.tagWhiteList,
-    highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
-
-    plantumlUri: configByContextHydrate.env.PLANTUML_URI,
-    blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
-  });
-  // useNoCdn(configByContextHydrate.env.NO_CDN);
-  // useUploadableImage(configByContextHydrate.upload.image);
-  // useUploadableFile(configByContextHydrate.upload.file);
-
-  // Page
-  useDeleteUsername(deleteUsername);
-  useDeletedAt(deletedAt);
-  useHasChildren(hasChildren);
-  useHasDraftOnHackmd(hasDraftOnHackmd);
-  useIsIdenticalPath(isIdenticalPath);
-  // useIsNotCreatable(isNotCreatable);
-  useIsForbidden(isForbidden);
-  // useIsTrashPage(isTrashPage);
-  useIsUserPage(isUserPage);
-  useLastUpdateUsername(lastUpdateUsername);
-  useCurrentPageId(pageId);
-  usePageIdOnHackmd(pageIdOnHackmd);
-  usePageUser(pageUser);
-  useCurrentPagePath(path);
-  useRevisionCreatedAt(revisionCreatedAt);
-  useRevisionId(revisionId);
-  useRevisionIdHackmdSynced(revisionIdHackmdSynced);
-  useShareLinkId(shareLinkId);
-  useShareLinksNumber(shareLinksNumber);
-  useTemplateTagData(templateTagData);
-  useTargetAndAncestors(targetAndAncestors);
-  useIsSearchPage(isSearchPage);
-  useHasParent(hasParent);
-
-  // Navigation
-  usePreferDrawerModeByUser();
-  usePreferDrawerModeOnEditByUser();
-  useIsDeviceSmallerThanMd();
-
-  // Editor
-  // useSelectedGrant(grant);
-  // useSelectedGrantGroupId(grantGroupId);
-  // useSelectedGrantGroupName(grantGroupName);
-
-  // SearchResult
-  useIsDeviceSmallerThanLg();
-
-  // Global Socket
-  useSetupGlobalSocket();
-  const shouldInitAdminSock = !!currentUser?.isAdmin;
-  useSetupGlobalAdminSocket(shouldInitAdminSock);
-
-  // TODO: Remove this code when reveal.js is omitted. see: https://github.com/weseek/growi/pull/6223
-  // Do not access this property from other than reveal.js plugins.
-  // (window as CustomWindow).previewRenderer = generatePreviewRenderer(
-  //   {
-  //     isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
-  //     attrWhiteList: configByContextHydrate.attrWhiteList,
-  //     tagWhiteList: configByContextHydrate.tagWhiteList,
-  //     highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
-  //     env: {
-  //       MATHJAX: configByContextHydrate.env.MATHJAX,
-  //       PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
-  //       BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
-  //     },
-  //   },
-  //   null,
-  //   path,
-  // );
-
-  return null;
-};
-
-const ContextExtractor: FC = React.memo(() => {
-  const [isRunOnce, setRunOnce] = useState(false);
-
-  useEffect(() => {
-    setRunOnce(true);
-  }, []);
-
-  return isRunOnce ? null : <ContextExtractorOnce></ContextExtractorOnce>;
-});
-
-export default ContextExtractor;

+ 0 - 358
packages/app/_obsolete/src/client/services/PageContainer.js

@@ -1,358 +0,0 @@
-import { pagePathUtils } from '@growi/core';
-import * as entities from 'entities';
-import * as toastr from 'toastr';
-import { Container } from 'unstated';
-
-
-import { EditorMode } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import {
-  DetachCodeBlockInterceptor,
-  RestoreCodeBlockInterceptor,
-} from '../../services/renderer/interceptor/detach-code-blocks';
-import {
-  DrawioInterceptor,
-} from '../../services/renderer/interceptor/drawio-interceptor';
-
-const { isTrashPage } = pagePathUtils;
-
-const logger = loggerFactory('growi:services:PageContainer');
-
-/**
- * Service container related to Page
- * @extends {Container} unstated Container
- */
-export default class PageContainer extends Container {
-
-  constructor(appContainer) {
-    super();
-
-    this.appContainer = appContainer;
-    this.appContainer.registerContainer(this);
-
-    this.state = {};
-
-    const mainContent = document.querySelector('#content-main');
-    if (mainContent == null) {
-      logger.debug('#content-main element is not exists');
-      return;
-    }
-
-    const revisionId = mainContent.getAttribute('data-page-revision-id');
-    const path = decodeURI(mainContent.getAttribute('data-path'));
-
-    this.state = {
-      // local page data
-      markdown: null, // will be initialized after initStateMarkdown()
-      pageId: mainContent.getAttribute('data-page-id'),
-      revisionId,
-      revisionCreatedAt: +mainContent.getAttribute('data-page-revision-created'),
-      path,
-      isEmpty: mainContent.getAttribute('data-page-is-empty'),
-
-      deletedAt: mainContent.getAttribute('data-page-deleted-at') || null,
-
-      isUserPage: JSON.parse(mainContent.getAttribute('data-page-user')) != null,
-      isTrashPage: isTrashPage(path),
-      isNotCreatable: JSON.parse(mainContent.getAttribute('data-page-is-not-creatable')),
-      isPageExist: mainContent.getAttribute('data-page-id') != null,
-
-      pageUser: JSON.parse(mainContent.getAttribute('data-page-user')),
-      tags: null,
-      hasChildren: JSON.parse(mainContent.getAttribute('data-page-has-children')),
-      templateTagData: mainContent.getAttribute('data-template-tags') || null,
-      shareLinksNumber: mainContent.getAttribute('data-share-links-number'),
-      shareLinkId: JSON.parse(mainContent.getAttribute('data-share-link-id') || null),
-
-      // latest(on remote) information
-      remoteRevisionId: revisionId,
-      remoteRevisionBody: null,
-      remoteRevisionUpdateAt: null,
-      revisionIdHackmdSynced: mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null,
-      lastUpdateUsername: mainContent.getAttribute('data-page-last-update-username') || null,
-      deleteUsername: mainContent.getAttribute('data-page-delete-username') || null,
-      pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd') || null,
-      hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
-      isHackmdDraftUpdatingInRealtime: false,
-      isConflictDiffModalOpen: false,
-    };
-
-    // parse creator, lastUpdateUser and revisionAuthor
-    try {
-      this.state.creator = JSON.parse(mainContent.getAttribute('data-page-creator'));
-    }
-    catch (e) {
-      logger.warn('The data of \'data-page-creator\' is invalid', e);
-    }
-    try {
-      this.state.revisionAuthor = JSON.parse(mainContent.getAttribute('data-page-revision-author'));
-      this.state.lastUpdateUser = JSON.parse(mainContent.getAttribute('data-page-revision-author'));
-    }
-    catch (e) {
-      logger.warn('The data of \'data-page-revision-author\' is invalid', e);
-    }
-
-    const { interceptorManager } = window;
-    interceptorManager.addInterceptor(new DetachCodeBlockInterceptor(), 10); // process as soon as possible
-    interceptorManager.addInterceptor(new DrawioInterceptor(), 20);
-    interceptorManager.addInterceptor(new RestoreCodeBlockInterceptor(), 900); // process as late as possible
-
-    this.initStateMarkdown();
-
-    this.save = this.save.bind(this);
-
-    this.emitJoinPageRoomRequest = this.emitJoinPageRoomRequest.bind(this);
-    this.emitJoinPageRoomRequest();
-
-    this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
-    this.addWebSocketEventHandlers();
-
-    const unlinkPageButton = document.getElementById('unlink-page-button');
-    if (unlinkPageButton != null) {
-      unlinkPageButton.addEventListener('click', async() => {
-        try {
-          const res = await apiPost('/pages.unlink', { path });
-          window.location.href = encodeURI(`${res.path}?unlinked=true`);
-        }
-        catch (err) {
-          toastError(err);
-        }
-      });
-    }
-
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'PageContainer';
-  }
-
-  /**
-   * initialize state for markdown data
-   * [Already SWRized]
-   */
-  initStateMarkdown() {
-    let pageContent = '';
-
-    const rawText = document.getElementById('raw-text-original');
-    if (rawText) {
-      pageContent = rawText.innerHTML;
-    }
-    const markdown = entities.decodeHTML(pageContent);
-
-    this.state.markdown = markdown;
-  }
-
-  setLatestRemotePageData(s2cMessagePageUpdated) {
-    const newState = {
-      remoteRevisionId: s2cMessagePageUpdated.revisionId,
-      remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
-      remoteRevisionUpdateAt: s2cMessagePageUpdated.revisionUpdateAt,
-      revisionIdHackmdSynced: s2cMessagePageUpdated.revisionIdHackmdSynced,
-      // TODO // TODO remove lastUpdateUsername and refactor parts that lastUpdateUsername is used
-      lastUpdateUsername: s2cMessagePageUpdated.lastUpdateUsername,
-      lastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
-    };
-
-    if (s2cMessagePageUpdated.hasDraftOnHackmd != null) {
-      newState.hasDraftOnHackmd = s2cMessagePageUpdated.hasDraftOnHackmd;
-    }
-
-    this.setState(newState);
-  }
-
-  /**
-   * save success handler
-   * @param {object} page Page instance
-   * @param {Array[Tag]} tags Array of Tag
-   * @param {object} revision Revision instance
-   */
-  updateStateAfterSave(page, tags, revision, editorMode) {
-    // update state of PageContainer
-    // const newState = {
-    //   pageId: page._id,
-    //   revisionId: revision._id,
-    //   revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
-    //   remoteRevisionId: revision._id,
-    //   revisionAuthor: revision.author,
-    //   revisionIdHackmdSynced: page.revisionHackmdSynced,
-    //   hasDraftOnHackmd: page.hasDraftOnHackmd,
-    //   markdown: revision.body,
-    //   createdAt: page.createdAt,
-    //   updatedAt: page.updatedAt,
-    // };
-    // if (tags != null) {
-    //   newState.tags = tags;
-    // }
-    // this.setState(newState);
-
-    // PageEditorByHackmd component
-    // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-    // if (pageEditorByHackmd != null) {
-    //   // reset
-    //   if (editorMode !== EditorMode.HackMD) {
-    //     pageEditorByHackmd.reset();
-    //   }
-    // }
-  }
-
-  /**
-   * update page meta data
-   * @param {object} page Page instance
-   * @param {object} revision Revision instance
-   * @param {String[]} tags Array of Tag
-   */
-  updatePageMetaData(page, revision, tags) {
-
-    const newState = {
-      revisionId: revision._id,
-      revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
-      remoteRevisionId: revision._id,
-      revisionAuthor: revision.author,
-      revisionIdHackmdSynced: page.revisionHackmdSynced,
-      hasDraftOnHackmd: page.hasDraftOnHackmd,
-      updatedAt: page.updatedAt,
-    };
-    if (tags != null) {
-      newState.tags = tags;
-    }
-
-    this.setState(newState);
-
-  }
-
-  /**
-   * Save page
-   * @param {string} markdown
-   * @param {object} optionsToSave
-   * @return {object} { page: Page, tags: Tag[] }
-   */
-  async save(markdown, editorMode, optionsToSave = {}) {
-    const { pageId, path } = this.state;
-    let { revisionId } = this.state;
-    const options = Object.assign({}, optionsToSave);
-
-    if (editorMode === EditorMode.HackMD) {
-      // set option to sync
-      options.isSyncRevisionToHackmd = true;
-      revisionId = this.state.revisionIdHackmdSynced;
-    }
-
-    let res;
-    if (pageId == null) {
-      res = await this.createPage(path, markdown, options);
-    }
-    else {
-      res = await this.updatePage(pageId, revisionId, markdown, options);
-    }
-
-    this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
-    return res;
-  }
-
-
-  showSuccessToastr() {
-    toastr.success(undefined, 'Saved successfully', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '1200',
-      extendedTimeOut: '150',
-    });
-  }
-
-  showErrorToastr(error) {
-    toastr.error(error.message, 'Error occured', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '3000',
-    });
-  }
-
-  // replaced accompanied by omiting container: https://github.com/weseek/growi/pull/6968
-  // // request to server so the client to join a room for each page
-  // emitJoinPageRoomRequest() {
-  //   const socketIoContainer = this.appContainer.getContainer('SocketIoContainer');
-  //   const socket = socketIoContainer.getSocket();
-  //   socket.emit('join:page', { socketId: socket.id, pageId: this.state.pageId });
-  // }
-
-  addWebSocketEventHandlers() {
-    // eslint-disable-next-line @typescript-eslint/no-this-alias
-    const pageContainer = this;
-    const socketIoContainer = this.appContainer.getContainer('SocketIoContainer');
-    const socket = socketIoContainer.getSocket();
-
-    socket.on('page:create', (data) => {
-      logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
-
-      // update remote page data
-      const { s2cMessagePageUpdated } = data;
-      if (s2cMessagePageUpdated.pageId === pageContainer.state.pageId) {
-        pageContainer.setLatestRemotePageData(s2cMessagePageUpdated);
-      }
-    });
-
-    // replaced accompanied by omiting container: https://github.com/weseek/growi/pull/6968
-    // socket.on('page:update', (data) => {
-    //   logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
-
-    //   // update remote page data
-    //   const { s2cMessagePageUpdated } = data;
-    //   if (s2cMessagePageUpdated.pageId === pageContainer.state.pageId) {
-    //     pageContainer.setLatestRemotePageData(s2cMessagePageUpdated);
-    //   }
-    // });
-
-    socket.on('page:delete', (data) => {
-      logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
-
-      // update remote page data
-      const { s2cMessagePageUpdated } = data;
-      if (s2cMessagePageUpdated.pageId === pageContainer.state.pageId) {
-        pageContainer.setLatestRemotePageData(s2cMessagePageUpdated);
-      }
-    });
-
-    socket.on('page:editingWithHackmd', (data) => {
-      logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
-
-      // update isHackmdDraftUpdatingInRealtime
-      const { s2cMessagePageUpdated } = data;
-      if (s2cMessagePageUpdated.pageId === pageContainer.state.pageId) {
-        pageContainer.setState({ isHackmdDraftUpdatingInRealtime: true });
-      }
-    });
-
-  }
-
-  /* TODO GW-325 */
-  retrieveMyBookmarkList() {
-  }
-
-  async resolveConflict(markdown, editorMode, optionsToSave) {
-
-    const { pageId, remoteRevisionId, path } = this.state;
-    const editorContainer = this.appContainer.getContainer('EditorContainer');
-
-    const res = await this.updatePage(pageId, remoteRevisionId, markdown, optionsToSave);
-
-    editorContainer.clearDraft(path);
-    this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
-
-    window.globalEmitter.emit('updateEditorValue', markdown);
-
-    editorContainer.setState({ tags: res.tags });
-
-    return res;
-  }
-
-}

+ 0 - 144
packages/app/_obsolete/src/components/MyDraftList/Draft.tsx

@@ -1,144 +0,0 @@
-import React, { useState } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import { CopyToClipboard } from 'react-copy-to-clipboard';
-import ReactMarkdown from 'react-markdown';
-import {
-  Collapse,
-  UncontrolledTooltip,
-} from 'reactstrap';
-
-import { useDraftOptions } from '~/stores/renderer';
-
-type DraftProps = {
-  path: string,
-  isExist: boolean,
-  index: number,
-  markdown: string,
-  clearDraft: (path: string) => void,
-}
-
-export const Draft = (props: DraftProps): JSX.Element => {
-
-  const {
-    path, isExist, index, markdown, clearDraft,
-  } = props;
-  const { t } = useTranslation();
-  const { data: rendererOptions } = useDraftOptions();
-  const [isPanelExpanded, setIsPanelExpanded] = useState(false);
-  const [showCopiedMessage, setShowCopiedMessage] = useState(false);
-
-  const changeToolTipLabel = () => {
-    setShowCopiedMessage(true);
-    setTimeout(() => {
-      setShowCopiedMessage(false);
-    }, 1000);
-  };
-
-  const collapsePanelHandler = () => {
-    setIsPanelExpanded(false);
-  };
-
-  const expandPanelHandler = () => {
-    setIsPanelExpanded(true);
-  };
-
-  const Controls = () => {
-
-    const tooltipTargetId = `draft-copied-tooltip_${index}`;
-
-    return (
-      <div className="icon-container">
-        {isExist
-          ? null
-          : (
-            <a
-              href={`${path}#edit`}
-              target="_blank"
-              rel="noopener noreferrer"
-              data-toggle="tooltip"
-              title={t('Edit')}
-            >
-              <i className="mx-2 icon-note" />
-            </a>
-          )
-        }
-        <span id={tooltipTargetId}>
-          <CopyToClipboard text={markdown} onCopy={changeToolTipLabel}>
-            <a
-              className="text-center draft-copy"
-            >
-              <i className="mx-2 ti-clipboard" />
-            </a>
-          </CopyToClipboard>
-        </span>
-        <UncontrolledTooltip placement="top" target={tooltipTargetId} fade={false} trigger="hover">
-          { showCopiedMessage && (
-            <strong>copied!</strong>
-          ) }
-          { !showCopiedMessage && (
-            <span>{t('Copy')}</span>
-          ) }
-        </UncontrolledTooltip>
-        <a
-          className="text-danger text-center"
-          data-toggle="tooltip"
-          data-placement="top"
-          title={t('Delete')}
-          onClick={() => { return clearDraft(path) }}
-        >
-          <i className="mx-2 icon-trash" />
-        </a>
-      </div>
-    );
-  };
-
-  const AccordionTitle = () => {
-    const iconClass = isPanelExpanded ? 'fa-rotate-90' : '';
-
-    return (
-      <span>
-
-        <span className="mr-2 draft-path" onClick={() => setIsPanelExpanded(!isPanelExpanded)}>
-          <i className={`fa fa-fw fa-angle-right mr-2 ${iconClass}`}></i>
-          {path}
-        </span>
-        { isExist && (
-          <span className="badge badge-warning">{t('page exists')}</span>
-        ) }
-        { !isExist && (
-          <span className="badge badge-info">draft</span>
-        ) }
-
-        <a className="ml-2" href={path}><i className="icon icon-login"></i></a>
-      </span>
-    );
-  };
-
-
-  return (
-    <div className="accordion draft-list-item" role="tablist">
-      <div className="card">
-
-        <div className="card-header d-flex" role="tab">
-          <AccordionTitle/>
-
-          <div className="flex-grow-1"></div>
-
-          <Controls/>
-        </div>
-
-        <Collapse isOpen={isPanelExpanded} onEntering={expandPanelHandler} onExiting={collapsePanelHandler}>
-          <div className="card-body">
-            { isPanelExpanded && (
-              <ReactMarkdown {...rendererOptions} className='wiki'>
-                {markdown}
-              </ReactMarkdown>
-            ) }
-          </div>
-        </Collapse>
-
-      </div>
-    </div>
-  );
-};

+ 0 - 192
packages/app/_obsolete/src/components/MyDraftList/MyDraftList.jsx

@@ -1,192 +0,0 @@
-import React from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-
-import PageContainer from '~/client/services/PageContainer';
-import { apiGet } from '~/client/util/apiv1-client';
-
-import PaginationWrapper from '../PaginationWrapper';
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-import { Draft } from './Draft';
-
-class MyDraftList extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      drafts: [],
-      currentDrafts: [],
-      activePage: 1,
-      totalDrafts: 0,
-      pagingLimit: Infinity,
-    };
-
-    this.handlePage = this.handlePage.bind(this);
-    this.getDraftsFromLocalStorage = this.getDraftsFromLocalStorage.bind(this);
-    this.getCurrentDrafts = this.getCurrentDrafts.bind(this);
-    this.clearDraft = this.clearDraft.bind(this);
-    this.clearAllDrafts = this.clearAllDrafts.bind(this);
-  }
-
-  async UNSAFE_componentWillMount() {
-    await this.getDraftsFromLocalStorage();
-    this.getCurrentDrafts(1);
-  }
-
-  async handlePage(selectedPage) {
-    await this.getDraftsFromLocalStorage();
-    await this.getCurrentDrafts(selectedPage);
-  }
-
-  async getDraftsFromLocalStorage() {
-    const draftsAsObj = this.props.editorContainer.drafts;
-
-    if (draftsAsObj == null) {
-      return;
-    }
-
-    const res = await apiGet('/pages.exist', {
-      pagePaths: JSON.stringify(Object.keys(draftsAsObj)),
-    });
-
-    // {'/a': '#a', '/b': '#b'} => [{path: '/a', markdown: '#a'}, {path: '/b', markdown: '#b'}]
-    const drafts = Object.entries(draftsAsObj).map((d) => {
-      const path = d[0];
-      return {
-        path,
-        markdown: d[1],
-        isExist: res.pages[path],
-      };
-    });
-
-    this.setState({ drafts, totalDrafts: drafts.length });
-  }
-
-  getCurrentDrafts(selectPageNumber) {
-
-    const limit = 50; // implement only this component.(this default value is 50 (pageLimitationL))
-
-    const totalDrafts = this.state.drafts.length;
-    const activePage = selectPageNumber;
-
-    const currentDrafts = this.state.drafts.slice((activePage - 1) * limit, activePage * limit);
-
-    this.setState({
-      currentDrafts,
-      activePage,
-      totalDrafts,
-      pagingLimit: limit,
-    });
-  }
-
-  /**
-   * generate Elements of Draft
-   *
-   * @param {any} drafts Array of pages Model Obj
-   *
-   */
-  generateDraftList(drafts) {
-    return drafts.map((draft, index) => {
-      return (
-        <Draft
-          index={index}
-          key={draft.path}
-          path={draft.path}
-          markdown={draft.markdown}
-          isExist={draft.isExist}
-          clearDraft={this.clearDraft}
-        />
-      );
-    });
-  }
-
-  clearDraft(path) {
-    // this.props.editorContainer.clearDraft(path);
-
-    this.setState((prevState) => {
-      return {
-        drafts: prevState.drafts.filter((draft) => { return draft.path !== path }),
-        currentDrafts: prevState.drafts.filter((draft) => { return draft.path !== path }),
-      };
-    });
-  }
-
-  clearAllDrafts() {
-    // this.props.editorContainer.clearAllDrafts();
-
-    this.setState({
-      drafts: [],
-      currentDrafts: [],
-      activePage: 1,
-      totalDrafts: 0,
-      pagingLimit: Infinity,
-    });
-  }
-
-  render() {
-    const { t } = this.props;
-
-    const draftList = this.generateDraftList(this.state.currentDrafts);
-    const totalCount = this.state.totalDrafts;
-
-    return (
-      <div className="page-list-container-create ">
-        { totalCount === 0
-          && <span className="mt-2">No drafts yet.</span>
-        }
-
-        { totalCount > 0 && (
-          <React.Fragment>
-            <div className="d-flex justify-content-between mt-2">
-              <h4>Total: {totalCount} drafts</h4>
-              <div className="align-self-center">
-                <button type="button" className="btn btn-sm btn-outline-danger" onClick={this.clearAllDrafts}>
-                  <i className="icon-fw icon-fire"></i>
-                  {t('delete_all')}
-                </button>
-              </div>
-            </div>
-
-            <div className="tab-pane mt-2 accordion" id="draft-list">
-              {draftList}
-            </div>
-            <PaginationWrapper
-              activePage={this.state.activePage}
-              changePage={this.handlePage}
-              totalItemsCount={this.state.totalDrafts}
-              pagingLimit={this.state.pagingLimit}
-              align="center"
-              size="sm"
-            />
-          </React.Fragment>
-        ) }
-
-      </div>
-    );
-  }
-
-}
-
-MyDraftList.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
-  // editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
-};
-
-const MyDraftListWrapperFC = (props) => {
-  const { t } = useTranslation();
-  return <MyDraftList t={t} {...props} />;
-};
-
-export default MyDraftListWrapperFC;
-
-/**
- * Wrapper component for using unstated
- */
-// const MyDraftListWrapper = withUnstatedContainers(MyDraftListWrapperFC, [PageContainer, EditorContainer]);
-
-// export default MyDraftListWrapper;

+ 0 - 76
packages/app/_obsolete/src/util/i18n.js

@@ -1,76 +0,0 @@
-
-/* eslint-disable */
-import i18n from 'i18next';
-import LanguageDetector from 'i18next-browser-languagedetector';
-import { initReactI18next } from 'react-i18next';
-
-import locales from '^/public/static/locales';
-
-const aliasesMapping = {};
-Object.values(locales).forEach((locale) => {
-  if (locale.meta.aliases == null) {
-    return;
-  }
-  locale.meta.aliases.forEach((alias) => {
-    aliasesMapping[alias] = locale.meta.id;
-  });
-});
-
-/*
-* Note: This file will be deleted. use "^/config/next-i18next.config" instead
-*/
-// extract metadata list from 'public/static/locales/${locale}/meta.json'
-export const localeMetadatas = Object.values(locales).map(locale => locale.meta);
-
-export const i18nFactory = (userLocaleId) => {
-  // setup LanguageDetector
-  const langDetector = new LanguageDetector();
-  langDetector.addDetector({
-    name: 'userSettingDetector',
-    lookup(options) {
-      return userLocaleId;
-    },
-  });
-  // Wrapper to convert lang after detected from browser
-  langDetector.addDetector({
-    name: 'navigatorWrapperToConvertByAlias',
-    lookup(options) {
-      const results = langDetector.detectors.navigator.lookup(options);
-      const lang = results[0];
-      if (lang == null) {
-        return;
-      }
-
-      return aliasesMapping[lang] || lang;
-    },
-  });
-
-  i18n
-    .use(langDetector)
-    .use(initReactI18next) // if not using I18nextProvider
-    .init({
-      debug: (process.env.NODE_ENV !== 'production'),
-      resources: locales,
-      load: 'currentOnly',
-
-      fallbackLng: 'en_US',
-      detection: {
-        order: ['userSettingDetector', 'navigatorWrapperToConvertByAlias', 'querystring'],
-      },
-
-      interpolation: {
-        escapeValue: false, // not needed for react!!
-      },
-
-      // react i18next special options (optional)
-      react: {
-        wait: false,
-        withRef: true,
-        bindI18n: 'languageChanged loaded',
-        bindStore: 'added removed',
-        nsMode: 'default',
-      },
-    });
-
-  return i18n;
-};

+ 0 - 17
packages/app/_obsolete/src/util/old-ios.js

@@ -1,17 +0,0 @@
-const userAgent = window.navigator.userAgent.toLowerCase();
-// https://youtrack.weseek.co.jp/issue/GW-4826
-const isOldIos = /(iphone|ipad|ipod) os (9|10|11|12)/.test(userAgent);
-
-/**
- * Apply 'oldIos' attribute to <html></html>
- */
-function applyOldIos() {
-  if (isOldIos) {
-    document.documentElement.setAttribute('old-ios', 'true');
-  }
-}
-
-export {
-  // eslint-disable-next-line import/prefer-default-export
-  applyOldIos,
-};

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

@@ -27,6 +27,7 @@ module.exports = {
   'growi-plugin:*': 'debug',
   // 'growi:InterceptorManager': 'debug',
   'growi:service:search-delegator:elasticsearch': 'debug',
+  'growi:service:g2g-transfer': 'debug',
 
   /*
    * configure level for client

+ 2 - 1
packages/app/config/next-i18next.config.ts

@@ -7,8 +7,9 @@ import I18NextLocalStorageBackend from 'i18next-localstorage-backend';
 
 const isDev = process.env.NODE_ENV === 'development';
 
+export const defaultLang = Lang.en_US;
 export const i18n = {
-  defaultLocale: Lang.en_US,
+  defaultLocale: defaultLang,
   locales: AllLang,
 };
 export const defaultNS = 'translation';

+ 14 - 21
packages/app/docker/Dockerfile

@@ -1,7 +1,5 @@
 # syntax = docker/dockerfile:1.4
 
-ARG flavor=default
-
 
 ##
 ## packages-json-picker
@@ -30,11 +28,15 @@ ENV nodeModulesGrowiPackagesDir ${optDir}/node_modules/@growi
 # expect a string seperated by commas (e.g. "A,B")
 ENV removeNodeModulesSymlinkPaths ${nodeModulesGrowiPackagesDir}/slackbot-proxy
 
+RUN set -eux; \
+	apt-get update; \
+	apt-get install -y python3 build-essential;
+
 # copy files
 COPY --from=packages-json-picker ${optDir} .
 
-# setup
-RUN yarn config set network-timeout 300000
+# setup (with network-timeout = 1 hour)
+RUN yarn config set network-timeout 3600000
 RUN npx -y lerna bootstrap -- --frozen-lockfile
 
 # remove unnecessary symlinks
@@ -63,9 +65,9 @@ RUN tar -cf node_modules.tar \
 
 
 ##
-## prebuilder-default
+## prebuilder
 ##
-FROM node:16-slim AS prebuilder-default
+FROM node:16-slim AS prebuilder
 
 ENV optDir /opt
 
@@ -80,20 +82,11 @@ RUN tar -xf node_modules.tar
 RUN rm node_modules.tar
 
 
-##
-## prebuilder-nocdn
-##
-FROM prebuilder-default AS prebuilder-nocdn
-
-# add dotenv file for NO_CDN
-COPY packages/app/docker/nocdn/.env.production.local ${optDir}/packages/app/
-
-
 
 ##
 ## builder
 ##
-FROM prebuilder-${flavor} AS builder
+FROM prebuilder AS builder
 
 ENV optDir /opt
 
@@ -102,16 +95,16 @@ WORKDIR ${optDir}
 # ignore eslint and stylelint
 COPY ["package.json", "lerna.json", "tsconfig.base.json", "./"]
 # copy all related packages
-COPY packages/app packages/app
-COPY packages/core packages/core
 COPY packages/codemirror-textlint packages/codemirror-textlint
+COPY packages/preset-themes packages/preset-themes
 COPY packages/slack packages/slack
-COPY packages/ui packages/ui
+COPY packages/hackmd packages/hackmd
 COPY packages/remark-drawio packages/remark-drawio
 COPY packages/remark-growi-directive packages/remark-growi-directive
 COPY packages/remark-lsx packages/remark-lsx
-COPY packages/hackmd packages/hackmd
-COPY packages/preset-themes packages/preset-themes
+COPY packages/ui packages/ui
+COPY packages/core packages/core
+COPY packages/app packages/app
 
 # build
 RUN yarn lerna run build

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

@@ -10,7 +10,7 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`6.0.0`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.0/packages/app/docker/Dockerfile)
+* [`6.0.5`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.5/packages/app/docker/Dockerfile)
 * [`5.1.7`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)

+ 65 - 0
packages/app/docker/codebuild/.terraform.lock.hcl

@@ -0,0 +1,65 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version     = "4.49.0"
+  constraints = "~> 4.16"
+  hashes = [
+    "h1:oOwWQpvQWd1uVP1axBz/TO6xzzLWoL982AY/MQfeF7I=",
+    "zh:09803937f00fdf2873eccf685eec7854408925cbf183c9b683df7c5825be463f",
+    "zh:2af1575e538fb0b669266f8d1385b17bfdaf17c521b6b6329baa1f2971fc4a4d",
+    "zh:3f71882b438cde3108fe68cfe2637839d3eed08157a9721bd97babf3912247a8",
+    "zh:577af1b38f5da8a9f29eebe5eebec9279d26e757cd03b0c8c59311f9ce8a859b",
+    "zh:60160d39094973beefb9b10cfd6aaa5b63a2e68c32445ecffcd1b101356e6f9b",
+    "zh:762656454722548baeccf35cbaa23b887976337e1ed321682df7390419fdf22d",
+    "zh:7f6d7887821659bf3bef815949077dc91ffcdb0d911644a887b6683b264a5ca6",
+    "zh:8f16a352cc903f8951fa4619c36233b3e66e27d724817b131f2035dd8896f524",
+    "zh:8f768f65e370366c8b91c00d01c9a6264fe26ea9ae1819f14bdcd12c066272bc",
+    "zh:95ad78c689a83c08ef7c3e544c3c9aca93ed528054aa77cc968ddd9efa3a1023",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:a47097ab6a4ca8302da82964303ffdd2310ed65e8f8524bfe4058816cf1addb7",
+    "zh:b66d820c70cd5fd628ffe882d2b97e76b969dca4e6827ac2ba0f8d3bc5d6e9c6",
+    "zh:b80f713a4f3e1355c3dd1600e9d08b9f15ed2370054ec792ad2c01f2541abe02",
+    "zh:ce065bc3962cb71fa7652562226b9d486f3d7fcb88285c1020ebe2f550e28641",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/random" {
+  version     = "3.4.3"
+  constraints = ">= 2.1.0"
+  hashes = [
+    "h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=",
+    "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
+    "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
+    "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
+    "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
+    "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
+    "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
+    "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
+    "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
+    "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
+    "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
+    "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
+    "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/tls" {
+  version     = "4.0.4"
+  constraints = ">= 3.0.0"
+  hashes = [
+    "h1:pe9vq86dZZKCm+8k1RhzARwENslF3SXb9ErHbQfgjXU=",
+    "zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55",
+    "zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848",
+    "zh:59fedb519f4433c0fdb1d58b27c210b27415fddd0cd73c5312530b4309c088be",
+    "zh:5a8eec2409a9ff7cd0758a9d818c74bcba92a240e6c5e54b99df68fff312bbd5",
+    "zh:5e6a4b39f3171f53292ab88058a59e64825f2b842760a4869e64dc1dc093d1fe",
+    "zh:810547d0bf9311d21c81cc306126d3547e7bd3f194fc295836acf164b9f8424e",
+    "zh:824a5f3617624243bed0259d7dd37d76017097dc3193dac669be342b90b2ab48",
+    "zh:9361ccc7048be5dcbc2fafe2d8216939765b3160bd52734f7a9fd917a39ecbd8",
+    "zh:aa02ea625aaf672e649296bce7580f62d724268189fe9ad7c1b36bb0fa12fa60",
+    "zh:c71b4cd40d6ec7815dfeefd57d88bc592c0c42f5e5858dcc88245d371b4b8b1e",
+    "zh:dabcd52f36b43d250a3d71ad7abfa07b5622c69068d989e60b79b2bb4f220316",
+    "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
+  ]
+}

+ 32 - 0
packages/app/docker/codebuild/buildspec.yml

@@ -0,0 +1,32 @@
+version: 0.2
+
+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:
+    commands:
+      # 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 ./packages/app/docker/Dockerfile .
+      - docker tag ${IMAGE_TAG} ${IMAGE_TAG_GHCR}
+
+  post_build:
+    commands:
+      - docker push $IMAGE_TAG
+      - docker push $IMAGE_TAG_GHCR
+
+cache:
+  paths:
+    - node_modules/**/*
+    - packages/*/node_modules/**/*

+ 25 - 0
packages/app/docker/codebuild/codebuild.tf

@@ -0,0 +1,25 @@
+module "codebuild" {
+  source = "cloudposse/codebuild/aws"
+
+  name                = "growi-official-image-builder"
+  description         = "The CodeBuild Project for GROWI official docker image"
+
+  artifact_type       = "NO_ARTIFACTS"
+
+  source_type         = "GITHUB"
+  source_location     = "https://github.com/weseek/growi.git"
+  source_version      = "refs/heads/master"
+  git_clone_depth     = 1
+
+  buildspec           = "packages/app/docker/codebuild/buildspec.yml"
+
+  # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
+  build_image         = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
+  build_compute_type  = "BUILD_GENERAL1_LARGE"
+
+  privileged_mode     = true
+
+  cache_type          = "LOCAL"
+  local_cache_modes   = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_CUSTOM_CACHE"]
+
+}

+ 23 - 0
packages/app/docker/codebuild/main.tf

@@ -0,0 +1,23 @@
+terraform {
+  backend "remote" {
+    organization = "weseek"
+
+    workspaces {
+      name = "growi-official-image-builder"
+    }
+  }
+
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "~> 4.16"
+    }
+  }
+
+  required_version = ">= 1.2.0"
+}
+
+provider "aws" {
+  profile = "weseek"
+  region  = "ap-northeast-1"
+}

+ 26 - 0
packages/app/docker/codebuild/oidc.tf

@@ -0,0 +1,26 @@
+module "oidc_github" {
+  source  = "unfunco/oidc-github/aws"
+
+  iam_role_name = "GitHubOIDC-for-growi"
+  iam_role_inline_policies = {
+    "inline_policy" : data.aws_iam_policy_document.policy_document.json
+  }
+
+  github_repositories = [
+    "weseek/growi",
+  ]
+}
+
+data "aws_iam_policy_document" "policy_document" {
+  statement {
+    actions   = [
+      "codebuild:StartBuild",
+      "codebuild:StopBuild",
+      "codebuild:RetryBuild",
+      "codebuild:BatchGetBuilds"
+    ]
+    resources = [
+      module.codebuild.project_arn
+    ]
+  }
+}

+ 15 - 0
packages/app/docker/codebuild/secretsmanager.tf

@@ -0,0 +1,15 @@
+resource "aws_secretsmanager_secret" "secret" {
+  name = "growi/official-image-builder"
+}
+
+resource "aws_secretsmanager_secret_version" "main" {
+  secret_id     = aws_secretsmanager_secret.secret.id
+  secret_string = jsonencode({
+    KEY1 = "CHANGE THIS"
+    KEY2 = "CHANGE THIS"
+  })
+
+  lifecycle {
+    ignore_changes = [secret_string, version_stages]
+  }
+}

+ 0 - 6
packages/app/docker/nocdn/.env.production.local

@@ -1,6 +0,0 @@
-
-##
-## Handled by Next.js with dotenv or dotenv-flow
-## https://nextjs.org/docs/basic-features/environment-variables
-##
-NO_CDN=true

+ 21 - 20
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.0.0-RC.14",
+  "version": "6.0.6-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -9,7 +9,7 @@
     "build:client": "yarn next build",
     "prebuild:client": "tsc -p tsconfig.build.next.config.json",
     "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server-tsc-alias.json",
-    "postbuild:server": "npx -y shx echo \"Listing files under transpiled\" && npx -y shx ls transpiled && npx -y shx mv transpiled/src dist && npx -y shx cp -r transpiled/config/* config && npx -y shx cp -r src/server/views dist/server/ && npx -y shx rm -rf transpiled",
+    "postbuild:server": "npx -y shx echo \"Listing files under transpiled\" && npx -y shx ls transpiled && npx -y shx mv transpiled/src dist && npx -y shx cp -r transpiled/config/* config && npx -y shx rm -rf transpiled",
     "clean": "npx -y shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
@@ -46,7 +46,7 @@
     "openapi:v3": "yarn cross-env API_VERSION=3 yarn swagger-jsdoc -- \"src/server/routes/apiv3/**/*.js\" \"src/server/models/**/*.js\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
     "resources:hackmd": "yarn lerna run build --scope=@growi/hackmd",
-    "resources:preset-themes": "yarn lerna run build --scope=@growi/preset-themes",
+    "resources:preset-themes": "yarn lerna run dev:nowatch --scope=@growi/preset-themes",
     "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
     "ts-node": "node -r ts-node/register -r tsconfig-paths/register -r dotenv-flow/config"
   },
@@ -54,7 +54,9 @@
     "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
     "escape-string-regexp": "5.0.0 or above exports only ESM",
     "next": "/Sandbox rendering is crashed with v12.3 or above ",
-    "string-width": "5.0.0 or above exports only ESM."
+    "string-width": "5.0.0 or above exports only ESM.",
+    "prom-client": "!!DO NOT REMOVE!! A peer dependency of @promster.",
+    "remark-wiki-link": "!!DO NOT REMOVE!! including 'mdast-util-wiki-link' and 'micromark-extension-wiki-link' required by pukiwiki-like-linker"
   },
   "dependencies": {
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
@@ -65,16 +67,16 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.0-RC.14",
-    "@growi/core": "^6.0.0-RC.14",
-    "@growi/hackmd": "^6.0.0-RC.14",
-    "@growi/preset-themes": "^6.0.0-RC.14",
-    "@growi/remark-drawio": "^6.0.0-RC.14",
-    "@growi/remark-growi-directive": "^6.0.0-RC.14",
-    "@growi/remark-lsx": "^6.0.0-RC.14",
-    "@growi/slack": "^6.0.0-RC.14",
-    "@promster/express": "^7.0.2",
-    "@promster/server": "^7.0.4",
+    "@growi/codemirror-textlint": "^6.0.6-RC.0",
+    "@growi/core": "^6.0.6-RC.0",
+    "@growi/hackmd": "^6.0.6-RC.0",
+    "@growi/preset-themes": "^6.0.6-RC.0",
+    "@growi/remark-drawio": "^6.0.6-RC.0",
+    "@growi/remark-growi-directive": "^6.0.6-RC.0",
+    "@growi/remark-lsx": "^6.0.6-RC.0",
+    "@growi/slack": "^6.0.6-RC.0",
+    "@promster/express": "^7.0.6",
+    "@promster/server": "^7.0.8",
     "@slack/web-api": "^6.2.4",
     "@slack/webhook": "^6.0.0",
     "JSONStream": "^1.3.5",
@@ -95,12 +97,12 @@
     "cookie-parser": "^1.4.5",
     "csurf": "^1.11.0",
     "csv-to-markdown-table": "^1.1.0",
+    "dayjs": "^1.11.7",
     "date-fns": "^2.23.0",
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
     "ejs": "^3.1.8",
-    "entities": "^2.0.0",
     "esa-node": "^0.2.2",
     "escape-string-regexp": "=4.0.0",
     "eslint-plugin-regex": "^1.8.0",
@@ -110,8 +112,8 @@
     "express-mongo-sanitize": "^2.1.0",
     "express-session": "^1.16.1",
     "express-validator": "^6.14.0",
-    "express-webpack-assets": "^0.1.0",
     "extensible-custom-error": "^0.0.7",
+    "form-data": "^4.0.0",
     "graceful-fs": "^4.1.11",
     "hast-util-select": "^5.0.2",
     "helmet": "^4.6.0",
@@ -148,6 +150,7 @@
     "passport-ldapauth": "^3.0.1",
     "passport-local": "^1.0.0",
     "passport-saml": "^3.2.0",
+    "prom-client": "^14.1.1",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
     "react-bootstrap-typeahead": "^5.2.2",
@@ -182,8 +185,7 @@
     "string-width": "=4.2.2",
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
-    "swig-templates": "^2.0.2",
-    "swr": "^1.3.0",
+    "swr": "^2.0.3",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
     "uglifycss": "^0.0.29",
@@ -201,7 +203,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
-    "@growi/ui": "^6.0.0-RC.14",
+    "@growi/ui": "^6.0.6-RC.0",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",
@@ -235,7 +237,6 @@
     "react-copy-to-clipboard": "^5.0.1",
     "react-dropzone": "^11.2.4",
     "react-hotkeys": "^2.0.0",
-    "react-waypoint": "^10.1.0",
     "rehype-rewrite": "^3.0.6",
     "replacestream": "^4.0.3",
     "reveal.js": "^4.3.1",

+ 23 - 1
packages/app/public/static/locales/en_US/admin.json

@@ -383,6 +383,7 @@
     "bucket_name": "Bucket name",
     "custom_endpoint": "Custom endpoint",
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
+    "s3_secret_access_key_input_description": "Setting value is hidden",
     "load_plugins": "Load plugins",
     "enable": "Enable",
     "disable": "Disable",
@@ -526,7 +527,7 @@
       "upload": "Upload",
       "discard": "Discard uploaded data",
       "errors": {
-        "different_versions": "This growi and the uploaded data versions are not met",
+        "different_versions": "The version of this GROWI and the uploaded data are not the same",
         "at_least_one": "Select one or more collections.",
         "page_and_revision": "'Pages' and 'Revisions' must be imported both.",
         "depends": "'{{target}}' must be selected when '{{condition}}' is selected."
@@ -848,6 +849,22 @@
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
   },
+  "g2g_data_transfer": {
+    "transfer_data_to_another_growi": "Transfer data from this GROWI to another GROWI",
+    "advanced_options": "Advanced options",
+    "start_transfer": "Start transfer",
+    "paste_transfer_key": "Paste transter key here"
+  },
+  "plugins": {
+    "plugins": "Plugins",
+    "plugin_installer": "Plugin Installer",
+    "repository_url": "Repository URL",
+    "description": "You can install plugins by inputting the URL",
+    "plugin_card": "Plugin Card",
+    "plugin_is_not_installed": "Plugin is not installed",
+    "install": "Install",
+    "delete": "Delete"
+  },
   "cloud_setting_management": {
     "to_cloud_settings": "Open GROWI.cloud Settings"
   },
@@ -1014,6 +1031,11 @@
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
     "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
   },
+  "g2g": {
+    "transfer_success": "Completed GROWI to GROWI transfer successfully",
+    "error_generate_growi_archive": "Failed to generate GROWI archive file",
+    "error_send_growi_archive": "Failed to send GROWI archive file to the destination GROWI"
+  },
   "toaster": {
     "give_user_admin": "Succeeded to give {{username}} admin",
     "remove_user_admin": "Succeeded to remove {{username}} admin",

+ 10 - 0
packages/app/public/static/locales/en_US/commons.json

@@ -96,5 +96,15 @@
 
   "not_found_page": {
     "page_not_exist": "This page does not exist."
+  },
+
+  "g2g_data_transfer": {
+    "tab": "Data transfer",
+    "data_transfer": "Data Transfer",
+    "transfer_data_to_this_growi": "Transfer data from another GROWI to this GROWI",
+    "publish_transfer_key": "Publish transfer key",
+    "transfer_key_limit": "Transfer keys are valid for 1 hour after issuance.",
+    "once_transfer_key_used": "Once the transfer key is used for transfer, it cannot be used for any other transfer.",
+    "transfer_to_growi_cloud": "If you wish to transfer to GROWI.cloud, please click here."
   }
 }

+ 14 - 0
packages/app/public/static/locales/en_US/translation.json

@@ -60,6 +60,7 @@
   "Presentation Mode": "Presentation",
   "The end": "The end",
   "Not available for guest": "Not available for guest",
+  "Not available in this version": "Not available in this version",
   "No users have liked this yet": "No users have liked this yet",
   "No users have liked this yet.": "No users have liked this yet.",
   "No users have bookmarked yet": "No users have bookmarked yet",
@@ -84,6 +85,7 @@
   "No diff": "No diff",
   "User ID": "User ID",
   "User Information": "User information",
+  "User Activation": "User Activation",
   "Basic Info": "Basic info",
   "Name": "Name",
   "Email": "Email",
@@ -163,6 +165,8 @@
     "no_page_list": "There are no pages under this page."
   },
   "installer": {
+    "tab": "Create account",
+    "title": "Installer",
     "setup": "Setup",
     "create_initial_account": "Create an initial account",
     "initial_account_will_be_administrator_automatically": "The initial account will be administrator automatically.",
@@ -550,6 +554,7 @@
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
   },
   "search_result": {
+    "title": "Search",
     "result_meta": "Search results for:",
     "deletion_mode_btn_lavel": "Select and delete page",
     "cancel": "Cancel",
@@ -605,6 +610,7 @@
     }
   },
   "login": {
+    "title": "Login",
     "sign_in_error": "Login error",
     "registration_successful": "registration_successful. Please wait for administrator approval.",
     "Setup": "Setup",
@@ -612,6 +618,7 @@
     "set_env_var_for_logs": "(Please set the environment variables <code>DEBUG=crowi:service:PassportService</code> to get the logs)"
   },
   "invited": {
+    "title": "Invited",
     "discription_heading": "Create Account",
     "discription": "Create an your account with the invited email address"
   },
@@ -797,5 +804,12 @@
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page tree feature is not available yet.",
     "go_to_settings": "Go to settings to enable the feature"
+  },
+  "tag_edit_modal": {
+    "edit_tags": "Edit Tags",
+    "done": "Done",
+    "tags_input": {
+      "tag_name": "tag name"
+    }
   }
 }

+ 22 - 0
packages/app/public/static/locales/ja_JP/admin.json

@@ -391,6 +391,7 @@
     "bucket_name": "バケット名",
     "custom_endpoint": "カスタムエンドポイント",
     "custom_endpoint_change": "MinIOなど、S3互換APIを持つ他のオブジェクトストレージサービスを使用する場合のみ、そのエンドポイントのURLを入力してください。空欄の場合は、Amazon S3を使用します。",
+    "s3_secret_access_key_input_description": "設定値は非表示です",
     "load_plugins": "プラグインを読み込む",
     "enable": "有効",
     "disable": "無効",
@@ -856,6 +857,22 @@
       "log_type": "https://docs.growi.org/ja/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
   },
+  "g2g_data_transfer": {
+    "transfer_data_to_another_growi": "このGROWIのデータを別GROWIへ移行する",
+    "advanced_options": "詳細オプション",
+    "start_transfer": "移行を開始する",
+    "paste_transfer_key": "移行キーをここにペースト"
+  },
+  "plugins": {
+    "plugins": "プラグイン",
+    "plugin_installer": "プラグインインストーラー",
+    "repository_url": "URL",
+    "description": "リポジトリのURLの入力してください。",
+    "plugin_card": "プラグインカード",
+    "plugin_is_not_installed": "プラグインがインストールされていません",
+    "install": "インストール",
+    "delete": "削除"
+  },
   "cloud_setting_management": {
     "to_cloud_settings": "GROWI.cloud の管理画面へ"
   },
@@ -1022,6 +1039,11 @@
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
     "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
   },
+  "g2g": {
+    "transfer_success": "G2G移行が完了しました",
+    "error_generate_growi_archive": "GROWI アーカイブファイルの作成に失敗しました",
+    "error_send_growi_archive": "GROWI アーカイブファイルの送信に失敗しました"
+  },
   "toaster": {
     "give_user_admin": "{{username}}を管理者に設定しました",
     "remove_user_admin": "{{username}}を管理者から外しました",

+ 10 - 0
packages/app/public/static/locales/ja_JP/commons.json

@@ -96,5 +96,15 @@
 
   "not_found_page": {
     "page_not_exist": "このページは存在しません。"
+  },
+
+  "g2g_data_transfer": {
+    "tab": "データ移行",
+    "data_transfer": "データ移行",
+    "transfer_data_to_this_growi": "別GROWIのデータをこのGROWIへ移行する",
+    "publish_transfer_key": "移行キーを発行する",
+    "transfer_key_limit": "※ 移行キーの有効期限は発行から1時間となります。",
+    "once_transfer_key_used": "※ 移行キーは一度移行に利用するとそれ以降はご利用いただけなくなります。",
+    "transfer_to_growi_cloud": "※ GROWI.cloud への移行を実施する場合はこちらをご確認ください。"
   }
 }

+ 15 - 0
packages/app/public/static/locales/ja_JP/translation.json

@@ -57,6 +57,7 @@
   "Presentation Mode": "プレゼンテーション",
   "The end": "おしまい",
   "Not available for guest": "ゲストユーザーは利用できません",
+  "Not available in this version": "このバージョンでは利用できません",
   "No users have liked this yet": "いいねをしているユーザーはいません",
   "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "Create Archive Page": "アーカイブページの作成",
@@ -79,7 +80,9 @@
   "View diff": "差分を表示",
   "No diff": "差分なし",
   "User ID": "ユーザーID",
+  "User Settings": "ユーザー設定",
   "User Information": "ユーザー情報",
+  "User Activation": "ユーザーアクティベーション",
   "Basic Info": "ユーザーの基本情報",
   "Name": "名前",
   "Email": "メールアドレス",
@@ -164,6 +167,8 @@
     "no_page_list": "このページの配下にはページが存在しません。"
   },
   "installer": {
+    "tab": "アカウント作成",
+    "title": "インストーラー",
     "setup": "セットアップ",
     "create_initial_account": "最初のアカウントの作成",
     "initial_account_will_be_administrator_automatically": "初めに作成するアカウントは、自動的に管理者権限が付与されます",
@@ -549,6 +554,7 @@
     "popover_desc": "チャンネル名を入れてください。カンマ区切りのリストを入力することで複数のチャンネルに通知することができます。"
   },
   "search_result": {
+    "title": "検索",
     "result_meta": "検索結果:",
     "deletion_mode_btn_lavel": "ページを指定して削除",
     "cancel": "キャンセル",
@@ -604,6 +610,7 @@
     }
   },
   "login": {
+    "title": "ログイン",
     "sign_in_error": "ログインエラー",
     "registration_successful": "登録が完了しました。管理者の承認をお待ちください。",
     "Setup": "セットアップ",
@@ -611,6 +618,7 @@
     "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"
   },
   "invited": {
+    "title": "招待",
     "discription_heading": "アカウント作成",
     "discription": "招待を受け取ったメールアドレスでアカウントを作成します"
   },
@@ -796,5 +804,12 @@
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
     "go_to_settings": "設定する"
+  },
+  "tag_edit_modal": {
+    "edit_tags": "タグの編集",
+    "done": "完了",
+    "tags_input": {
+      "tag_name": "タグ名"
+    }
   }
 }

+ 23 - 1
packages/app/public/static/locales/zh_CN/admin.json

@@ -391,6 +391,7 @@
     "bucket_name": "Bucket name",
     "custom_endpoint": "Custom endpoint",
     "custom_endpoint_change": "输入对象存储服务(如MinIO)端点的URL,MinIO具有与S3兼容的API。如果为空,则使用Amazon S3。",
+    "s3_secret_access_key_input_description": "设定的值被隐藏。",
     "load_plugins": "加载插件",
     "enable": "启用",
     "disable": "停用",
@@ -534,7 +535,7 @@
       "upload": "Upload",
       "discard": "Discard uploaded data",
       "errors": {
-        "versions_not_met": "this growi and the uploaded data versions are not met",
+        "different_versions": "The version of this GROWI and the uploaded data are not the same",
         "at_least_one": "Select one or more collections.",
         "page_and_revision": "'Pages' and 'Revisions' must be imported both.",
         "depends": "'{{target}}' must be selected when '{{condition}}' is selected."
@@ -856,6 +857,22 @@
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
   },
+  "g2g_data_transfer": {
+    "transfer_data_to_another_growi": "将数据从这个GROWI迁移到另一个GROWI上",
+    "advanced_options": "高级选项",
+    "start_transfer": "开始迁移",
+    "paste_transfer_key": "在这里粘贴过渡键"
+  },
+  "plugins": {
+    "plugins": "Plugins",
+    "plugin_installer": "Plugin Installer",
+    "repository_url": "Repository URL",
+    "description": "You can install plugins by inputting the URL",
+    "plugin_card": "Plugin Card",
+    "plugin_is_not_installed": "Plugin is not installed",
+    "install": "Install",
+    "delete": "Delete"
+  },
   "cloud_setting_management": {
     "to_cloud_settings": "進入 GROWI.cloud 的管理界面"
   },
@@ -1022,6 +1039,11 @@
     "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
     "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
   },
+  "g2g": {
+    "transfer_success": "Completed GROWI to GROWI transfer successfully",
+    "error_generate_growi_archive": "Failed to generate GROWI archive file",
+    "error_send_growi_archive": "Failed to send GROWI archive file to the destination GROWI"
+  },
   "toaster": {
     "give_user_admin": "Succeeded to give {{username}} admin",
     "remove_user_admin": "Succeeded to remove {{username}} admin ",

+ 10 - 0
packages/app/public/static/locales/zh_CN/commons.json

@@ -96,5 +96,15 @@
 
   "not_found_page": {
     "page_not_exist": "该页面不存在"
+  },
+
+  "g2g_data_transfer": {
+    "tab": "数据迁移",
+    "data_transfer": "数据迁移",
+    "transfer_data_to_this_growi": "将数据从另一个GROWI迁移到这个GROWI上",
+    "publish_transfer_key": "发布迁移密钥",
+    "transfer_key_limit": "迁移密钥在签发后一小时内有效。",
+    "once_transfer_key_used": "一旦迁移密钥被用于迁移,它将不再可用于进一步的迁移。",
+    "transfer_to_growi_cloud": "如果您希望迁移到GROWI.cloud,请点击这里。"
   }
 }

+ 16 - 1
packages/app/public/static/locales/zh_CN/translation.json

@@ -56,7 +56,8 @@
   "No_attachments_yet": "暂无附件",
 	"Presentation Mode": "演示文稿",
   "The end": "结束",
-  "Not available for guest": "Not available for guest",
+  "Not available for guest": "不提供给客人",
+  "Not available in this version": "此版本中不提供",
   "No users have liked this yet": "还没有用户喜欢这个",
   "No users have bookmarked yet": "还没有用户加入书签",
   "Create Archive Page": "创建归档页",
@@ -85,6 +86,7 @@
 	"My Drafts": "My Drafts",
 	"User Settings": "用户设置",
 	"User Information": "用户信息",
+  "User Activation": "用户激活",
 	"Basic Info": "基础信息",
 	"Name": "姓名",
 	"Email": "邮箱",
@@ -157,6 +159,7 @@
   "not_allowed_to_see_this_page": "你不能看到这个页面",
   "Confirm": "确定",
   "Successfully requested": "进程成功接受",
+  "copied_to_clipboard": "它已复制到剪贴板。",
 	"form_validation": {
 		"error_message": "有些值不正确",
 		"required": "%s 是必需的",
@@ -170,6 +173,8 @@
     "no_page_list": "There are no pages under this page."
   },
 	"installer": {
+    "tab": "创建账户",
+    "title": "安装",
 		"setup": "安装",
 		"create_initial_account": "创建初始用户",
 		"initial_account_will_be_administrator_automatically": "初始帐户将自动成为管理员。",
@@ -554,6 +559,7 @@
     "link_sharing_is_disabled": "链接共享已被禁用"
   },
 	"search_result": {
+    "title": "搜索",
 		"result_meta": "搜索结果:",
 		"deletion_mode_btn_lavel": "选择并删除页面",
 		"cancel": "取消",
@@ -609,6 +615,7 @@
     }
   },
 	"login": {
+    "title": "登录",
 		"sign_in_error": "登录错误",
 		"registration_successful": "注册成功。请等待管理员批准",
 		"Setup": "安装程序",
@@ -616,6 +623,7 @@
     "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"
 	},
   "invited": {
+    "invited": "邀请函",
     "discription_heading": "创建账户",
     "discription": "用被邀请的电子邮件地址创建一个你的账户"
   },
@@ -801,5 +809,12 @@
   "v5_page_migration": {
     "page_tree_not_avaliable": "Page Tree 功能不可用",
     "go_to_settings": "进入设置,启用该功能"
+  },
+  "tag_edit_modal": {
+    "edit_tags": "编辑标签",
+    "done": "完毕",
+    "tags_input": {
+      "tag_name": "标签名称"
+    }
   }
 }

+ 6 - 6
packages/app/resource/locales/en_US/admin/userInvitation.txt

@@ -1,14 +1,14 @@
-Hi, {{ email }}
+Hi, <%- email -%>
 
 You are invited to our Wiki, you can log in with following account:
 
-Email: {{ email }}
-Password: {{ password }}
+Email: <%- email -%>
+Password: <%- password -%>
 (This password was auto generated. Update required at the first time you logging in)
 
 We are waiting for you!
-{{ url }}
+<%- url -%>
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>

+ 8 - 8
packages/app/resource/locales/en_US/admin/userWaitingActivation.txt

@@ -1,21 +1,21 @@
-Hi, {{ adminUser.name }}
+Hi, <%- adminUser.name -%>
 
-A user registered to {{ appTitle }}.
+A user registered to <%- appTitle -%>.
 
 
 ====
 Created user:
 
-Name: {{ createdUser.name }}
-User Name: {{ createdUser.username }}
-Email: {{ createdUser.email }}
+Name: <%- createdUser.name -%>
+User Name: <%- createdUser.username -%>
+Email: <%- createdUser.email -%>
 ====
 
 Please do some action with following URL:
-{{ url }}/admin/users
+<%- url -%>/admin/users
 
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>
 

+ 3 - 3
packages/app/resource/locales/en_US/notifications/comment.txt

@@ -1,9 +1,9 @@
-{{ username }} commented on {{ path }}.
+<%- username }} commented on {{ path -%>.
 
 ----------------------
 
-{{ comment }}
+<%- comment -%>
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 4 - 4
packages/app/resource/locales/en_US/notifications/notActiveUser.txt

@@ -1,13 +1,13 @@
 Password Reset
 
-Hi, {{ email }}
+Hi, <%- email -%>
 
-A request has been received to change the password from {{ appTitle }}.
+A request has been received to change the password from <%- appTitle -%>.
 However, this email is not registerd. Please try again with different email.
 
 If you did not request a password reset, you can safely ignore this email.
 
 -------------------------------------------------------------------------
 
-GROWI: {{ appTitle }}
-URL: {{ url }}
+GROWI: <%- appTitle -%>
+URL: <%- url -%>

+ 2 - 2
packages/app/resource/locales/en_US/notifications/pageCreate.txt

@@ -1,5 +1,5 @@
-{{ username }} created a new page under {{ path }}.
+<%- username -%> created a new page under <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/en_US/notifications/pageDelete.txt

@@ -1,5 +1,5 @@
-{{ username }} deleted the page  {{ path }}.
+<%- username -%> deleted the page  <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/en_US/notifications/pageEdit.txt

@@ -1,5 +1,5 @@
-{{ username }} edited the page {{ path }}.
+<%- username -%> edited the page <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/en_US/notifications/pageLike.txt

@@ -1,5 +1,5 @@
-{{ username }} liked the page {{ path }}.
+<%- username -%> liked the page <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/en_US/notifications/pageMove.txt

@@ -1,5 +1,5 @@
-{{ username }} renamed the page {{ oldPath }} to {{ newPath }}.
+<%- username -%> renamed the page <%- oldPath -%> to <%- newPath -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 4 - 4
packages/app/resource/locales/en_US/notifications/passwordReset.txt

@@ -1,12 +1,12 @@
 Password Reset
 
-Hi, {{ email }}
+Hi, <%- email -%>
 
-A request has been received to change the password your GROWI ({{ appTitle }}) account.
+A request has been received to change the password your GROWI (<%- appTitle -%>) account.
 To reset your password, click on the link below.
 
-{{ url }}
+<%- url -%>
 
-This link will expire in 10 minutes at  {{ expiredAt }}.
+This link will expire in 10 minutes at  <%- expiredAt -%>.
 
 If you did not request a password reset, you can safely ignore this email.

+ 1 - 1
packages/app/resource/locales/en_US/notifications/passwordResetSuccessful.txt

@@ -1,6 +1,6 @@
 Password Reset Successful
 
-Hi {{ email }}
+Hi <%- email -%>
 
 Your password has been successfully reset.
 Please log in with your new password.

+ 4 - 4
packages/app/resource/locales/en_US/notifications/userActivation.txt

@@ -1,12 +1,12 @@
 Account confirmation
 
-Hi, {{ email }}
+Hi, <%- email -%>
 
-An acount has been created in GROWI ({{ appTitle }}).
+An acount has been created in GROWI (<%- appTitle -%>).
 To activate your account, click on the link below.
 
-{{ url }}
+<%- url -%>
 
-This link will expire in 1 hour at  {{ expiredAt }}.
+This link will expire in 1 hour at  <%- expiredAt -%>.
 
 If you did not created the account, you can safely ignore this email.

+ 1 - 11
packages/app/resource/locales/en_US/sandbox.md

@@ -233,16 +233,6 @@ You can create links using `[Display text](URL)`.
 
 [Google](https://www.google.co.jp/)
 
-## Crowi compatibility
-
-```
-[/Sandbox]
-</user/admin1>
-```
-
-[/Sandbox]  
-</user/admin1>
-
 ## Pukiwiki like linker
 
 This is the most flexible linker.
@@ -254,7 +244,7 @@ Example of Bootstrap4 is [[here>./Bootstrap4]]
 ```
 
 [[./Bootstrap4]]  
-Example of Bootstrap4 is[[here>./Bootstrap4]]
+Example of Bootstrap4 is [[here>./Bootstrap4]]
 
 # :memo: Lists
 

+ 6 - 6
packages/app/resource/locales/ja_JP/admin/userInvitation.txt

@@ -1,14 +1,14 @@
-Hi, {{ email }}
+Hi, <%- email -%>
 
 You are invited to our Wiki, you can log in with following account:
 
-Email: {{ email }}
-Password: {{ password }}
+Email: <%- email -%>
+Password: <%- password -%>
 (This password was auto generated. Update required at the first time you logging in)
 
 We are waiting for you!
-{{ url }}
+<%- url -%>
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>

+ 8 - 8
packages/app/resource/locales/ja_JP/admin/userWaitingActivation.txt

@@ -1,21 +1,21 @@
-Hi, {{ adminUser.name }}
+Hi, <%- adminUser.name -%>
 
-A user registered to {{ appTitle }}.
+A user registered to <%- appTitle -%>.
 
 
 ====
 Created user:
 
-Name: {{ createdUser.name }}
-User Name: {{ createdUser.username }}
-Email: {{ createdUser.email }}
+Name: <%- createdUser.name -%>
+User Name: <%- createdUser.username -%>
+Email: <%- createdUser.email -%>
 ====
 
 Please do some action with following URL:
-{{ url }}/admin/users
+<%- url -%>/admin/users
 
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>
 

+ 4 - 4
packages/app/resource/locales/ja_JP/notifications/notActiveUser.txt

@@ -1,13 +1,13 @@
 パスワードリセット
 
-こんにちは、 {{ email }}
+こんにちは、 <%- email -%>
 
-{{ appTitle }} からパスワード再設定のリクエストがありましたが、このemailは登録されておりません。
+<%- appTitle -%> からパスワード再設定のリクエストがありましたが、このemailは登録されておりません。
 他のemailアドレスで再度お試しください。
 
 もしこのリクエストに心当たりがない場合は、このメールを無視してください。
 
 -------------------------------------------------------------------------
 
-GROWI: {{ appTitle }}
-URL: {{ url }}
+GROWI: <%- appTitle -%>
+URL: <%- url -%>

+ 4 - 4
packages/app/resource/locales/ja_JP/notifications/passwordReset.txt

@@ -1,12 +1,12 @@
 パスワード リセット
 
-こんにちは, {{ email }}
+こんにちは, <%- email -%>
 
-あなたのGROWI ({{ appTitle }}) アカウントから、パスワード再設定のリクエストがありました。
+あなたのGROWI (<%- appTitle -%>) アカウントから、パスワード再設定のリクエストがありました。
 パスワードをリセットするには、以下のリンクをクリックしてください。
 
-{{ url }}
+<%- url -%>
 
-このリンクは10分後の {{ expiredAt }} に失効します。
+このリンクは10分後の <%- expiredAt -%> に失効します。
 
 もしこのリクエストに心当たりがない場合は、このメールを無視してください。

+ 1 - 1
packages/app/resource/locales/ja_JP/notifications/passwordResetSuccessful.txt

@@ -1,6 +1,6 @@
 パスワードリセットに成功
 
-こんにちは、 {{ email }}
+こんにちは、 <%- email -%>
 
 あなたのパスワードは正常にリセットされました。
 新しいパスワードでログインしてください。

+ 4 - 4
packages/app/resource/locales/ja_JP/notifications/userActivation.txt

@@ -1,13 +1,13 @@
 仮登録完了のお知らせ
 
-{{ email }} さん
+<%- email -%> さん
 
-GROWI ({{ appTitle }}) で仮登録が完了いたしました。
+GROWI (<%- appTitle -%>) で仮登録が完了いたしました。
 
 ご本人様確認のため、下記リンクをクリックし、アカウントの本登録を完了させて下さい。
 
-{{ url }}
+<%- url -%>
 
-このリンクは1時間後の {{ expiredAt }} に失効します。
+このリンクは1時間後の <%- expiredAt -%> に失効します。
 
 ※当メールの内容に心当たりがない場合は、このメールを無視してください。

+ 0 - 10
packages/app/resource/locales/ja_JP/sandbox.md

@@ -232,16 +232,6 @@ ___
 
 [Google](https://www.google.co.jp/)
 
-## Crowi 互換
-
-```
-[/Sandbox]
-</user/admin1>
-```
-
-[/Sandbox]  
-</user/admin1>
-
 ## Pukiwiki like linker
 
 最も柔軟な Linker です。

+ 6 - 6
packages/app/resource/locales/zh_CN/admin/userInvitation.txt

@@ -1,14 +1,14 @@
-Hi, {{ email }}
+Hi, <%- email -%>
 
 You are invited to our Wiki, you can log in with following account:
 
-Email: {{ email }}
-Password: {{ password }}
+Email: <%- email -%>
+Password: <%- password -%>
 (This password was auto generated. Update required at the first time you logging in)
 
 We are waiting for you!
-{{ url }}
+<%- url -%>
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>

+ 8 - 8
packages/app/resource/locales/zh_CN/admin/userWaitingActivation.txt

@@ -1,21 +1,21 @@
-Hi, {{ adminUser.name }}
+Hi, <%- adminUser.name -%>
 
-A user registered to {{ appTitle }}.
+A user registered to <%- appTitle -%>.
 
 
 ====
 Created user:
 
-Name: {{ createdUser.name }}
-User Name: {{ createdUser.username }}
-Email: {{ createdUser.email }}
+Name: <%- createdUser.name -%>
+User Name: <%- createdUser.username -%>
+Email: <%- createdUser.email -%>
 ====
 
 Please do some action with following URL:
-{{ url }}/admin/users
+<%- url -%>/admin/users
 
 
 --
-{{ appTitle }}
-{{ url }}
+<%- appTitle -%>
+<%- url -%>
 

+ 3 - 3
packages/app/resource/locales/zh_CN/notifications/comment.txt

@@ -1,9 +1,9 @@
-{{ username }} commented on {{ path }}.
+<%- username -%> commented on <%- path -%>.
 
 ----------------------
 
-{{ comment }}
+<%- comment -%>
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 4 - 4
packages/app/resource/locales/zh_CN/notifications/notActiveUser.txt

@@ -1,13 +1,13 @@
 重设密码
 
-嗨,{{电子邮件}}
+嗨,<%-电子邮件-%>
 
-已收到来自 {{appTitle}} 的更改密码请求。
+已收到来自 <%-appTitle-%> 的更改密码请求。
 但是,此电子邮件未注册。请使用其他电子邮件重试。
 
 如果您没有要求重置密码,则可以放心地忽略此电子邮件。
 
 -------------------------------------------------------------------------
 
-GROWI: {{ appTitle }}
-URL: {{ url }}
+GROWI: <%- appTitle -%>
+URL: <%- url -%>

+ 2 - 2
packages/app/resource/locales/zh_CN/notifications/pageCreate.txt

@@ -1,5 +1,5 @@
-{{ username }} created a new page under {{ path }}.
+<%- username -%> created a new page under <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/zh_CN/notifications/pageDelete.txt

@@ -1,5 +1,5 @@
-{{ username }} deleted the page  {{ path }}.
+<%- username -%> deleted the page  <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/zh_CN/notifications/pageEdit.txt

@@ -1,5 +1,5 @@
-{{ username }} edited the page {{ path }}.
+<%- username -%> edited the page <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/zh_CN/notifications/pageLike.txt

@@ -1,5 +1,5 @@
-{{ username }} liked the page {{ path }}.
+<%- username -%> liked the page <%- path -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 2 - 2
packages/app/resource/locales/zh_CN/notifications/pageMove.txt

@@ -1,5 +1,5 @@
-{{ username }} renamed the page {{ oldPath }} to {{ newPath }}.
+<%- username -%> renamed the page <%- oldPath -%> to <%- newPath -%>.
 
 ----------------------
 
-Growi: {{ appTitle }}
+Growi: <%- appTitle -%>

+ 3 - 3
packages/app/resource/locales/zh_CN/notifications/passwordReset.txt

@@ -1,11 +1,11 @@
 重设密码
 
-嗨,{{ email }}
+嗨,<%- email -%>
 
-已收到更改您 GROWI ({{appTitle}}) 帐户 密码的请求。
+已收到更改您 GROWI (<%-appTitle-%>) 帐户 密码的请求。
 要重置密码,请单击下面的链接。
 
-{{ url }}
+<%- url -%>
 
 这个链接在10分钟后的{ expiredAt }}失效。
 

+ 1 - 1
packages/app/resource/locales/zh_CN/notifications/passwordResetSuccessful.txt

@@ -1,6 +1,6 @@
 密码重置成功
 
-嗨, {{email}}
+嗨, <%-email-%>
 
 您的密码已成功重置。
 请使用您的新密码登录。

+ 4 - 4
packages/app/resource/locales/zh_CN/notifications/userActivation.txt

@@ -1,12 +1,12 @@
 确认账户创建
 
-致{{ email }}
+致<%- email -%>
 
-已使用 GROWI ({{ appTitle }}) 创建帐户。
+已使用 GROWI (<%- appTitle -%>) 创建帐户。
 单击下面的链接以激活您的帐户。
 
-{{ url }}
+<%- url -%>
 
-这个链接将在1小时后即{{ expiredAt }}失效。
+这个链接将在1小时后即<%- expiredAt -%>失效。
 
 如果您尚未创建,请忽略此电子邮件。

+ 1 - 11
packages/app/resource/locales/zh_CN/sandbox.md

@@ -233,16 +233,6 @@ You can create links using `[Display text](URL)`.
 
 [Google](https://www.google.co.jp/)
 
-## Crowi compatibility
-
-```
-[/Sandbox]
-</user/admin1>
-```
-
-[/Sandbox]  
-</user/admin1>
-
 ## Pukiwiki like linker
 
 This is the most flexible linker.
@@ -250,7 +240,7 @@ Both the page description and link address can be displayed on the page.
 
 ```
 [[./Bootstrap4]]
-Example of Bootstrap4 is[[here>./Bootstrap4]]
+Example of Bootstrap4 is [[here>./Bootstrap4]]
 ```
 
 [[./Bootstrap4]]  

+ 0 - 1
packages/app/src/client/services/AdminAppContainer.js

@@ -108,7 +108,6 @@ export default class AdminAppContainer extends Container {
       s3CustomEndpoint: appSettingsParams.s3CustomEndpoint,
       s3Bucket: appSettingsParams.s3Bucket,
       s3AccessKeyId: appSettingsParams.s3AccessKeyId,
-      s3SecretAccessKey: appSettingsParams.s3SecretAccessKey,
       s3ReferenceFileWithRelayMode: appSettingsParams.s3ReferenceFileWithRelayMode,
 
       gcsUseOnlyEnvVars: appSettingsParams.gcsUseOnlyEnvVars,

+ 15 - 0
packages/app/src/client/services/g2g-transfer.ts

@@ -0,0 +1,15 @@
+import { useCallback, useState } from 'react';
+
+import { apiv3Post } from '~/client/util/apiv3-client';
+
+export const useGenerateTransferKey = (): {transferKey: string, generateTransferKey: () => Promise<void>} => {
+  const [transferKey, setTransferKey] = useState('');
+
+  const generateTransferKey = useCallback(async() => {
+    const response = await apiv3Post('/g2g-transfer/generate-key', { appSiteUrl: window.location.origin });
+    const { transferKey } = response.data;
+    setTransferKey(transferKey);
+  }, []);
+
+  return { transferKey, generateTransferKey };
+};

+ 7 - 14
packages/app/src/client/services/layout.ts

@@ -1,30 +1,23 @@
-import { useIsContainerFluid, useShareLinkId } from '~/stores/context';
+import type { IPage } from '~/interfaces/page';
+import { useIsContainerFluid } from '~/stores/context';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useEditorMode } from '~/stores/ui';
 
 export const useEditorModeClassName = (): string => {
   const { getClassNamesByEditorMode } = useEditorMode();
 
-  // TODO: Enable `editing-sidebar` class somehow
-  // https://redmine.weseek.co.jp/issues/111527
-  // const classNames: string[] = [];
-  // if (currentPage != null) {
-  //   const isSidebar = currentPage.path === '/Sidebar';
-  //   classNames.push(...getClassNamesByEditorMode(/* isSidebar */));
-  // }
-
   return `${getClassNamesByEditorMode().join(' ') ?? ''}`;
 };
 
-export const useCurrentGrowiLayoutFluidClassName = (): string => {
-  const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+export const useCurrentGrowiLayoutFluidClassName = (initialPage?: IPage): string => {
+  const { data: currentPage } = useSWRxCurrentPage();
 
   const { data: dataIsContainerFluid } = useIsContainerFluid();
 
-  const isContainerFluidEachPage = currentPage == null || !('expandContentWidth' in currentPage)
+  const page = currentPage ?? initialPage;
+  const isContainerFluidEachPage = page == null || !('expandContentWidth' in page)
     ? null
-    : currentPage.expandContentWidth;
+    : page.expandContentWidth;
   const isContainerFluidDefault = dataIsContainerFluid;
   const isContainerFluid = isContainerFluidEachPage ?? isContainerFluidDefault;
 

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