فهرست منبع

Merge pull request #7186 from weseek/master

Release v6.0.1
Yuki Takei 3 سال پیش
والد
کامیت
1c60973cfd
100فایلهای تغییر یافته به همراه490 افزوده شده و 2462 حذف شده
  1. 3 2
      .github/dependabot.yml
  2. 6 2
      .github/workflows/ci-app-prod.yml
  3. 5 0
      .github/workflows/ci-app.yml
  4. 5 0
      .github/workflows/ci-slackbot-proxy.yml
  5. 3 3
      .github/workflows/codeql-analysis.yml
  6. 26 0
      .github/workflows/dependabot-auto-approve.yml
  7. 5 0
      .github/workflows/draft-release.yml
  8. 5 0
      .github/workflows/pr-to-master.yml
  9. 10 2
      .github/workflows/release-rc.yml
  10. 4 4
      .github/workflows/release-slackbot-proxy.yml
  11. 8 13
      .github/workflows/release.yml
  12. 13 0
      .mergify.yml
  13. 1 1
      lerna.json
  14. 1 1
      package.json
  15. 0 191
      packages/app/_obsolete/config/webpack.common.js
  16. 0 49
      packages/app/_obsolete/config/webpack.dev.dll.js
  17. 0 81
      packages/app/_obsolete/config/webpack.dev.js
  18. 0 77
      packages/app/_obsolete/config/webpack.prod.js
  19. 0 167
      packages/app/_obsolete/src/client/app.jsx
  20. 0 69
      packages/app/_obsolete/src/client/base.jsx
  21. 0 5
      packages/app/_obsolete/src/client/boot.js
  22. 0 60
      packages/app/_obsolete/src/client/installer.jsx
  23. 0 52
      packages/app/_obsolete/src/client/legacy/crowi.js
  24. 0 142
      packages/app/_obsolete/src/client/nologin.jsx
  25. 0 55
      packages/app/_obsolete/src/client/plugin.js
  26. 0 133
      packages/app/_obsolete/src/client/services/AppContainer.js
  27. 0 214
      packages/app/_obsolete/src/client/services/ContextExtractor.tsx
  28. 0 358
      packages/app/_obsolete/src/client/services/PageContainer.js
  29. 0 144
      packages/app/_obsolete/src/components/MyDraftList/Draft.tsx
  30. 0 192
      packages/app/_obsolete/src/components/MyDraftList/MyDraftList.jsx
  31. 0 76
      packages/app/_obsolete/src/util/i18n.js
  32. 0 17
      packages/app/_obsolete/src/util/old-ios.js
  33. 9 16
      packages/app/docker/Dockerfile
  34. 16 17
      packages/app/package.json
  35. 8 0
      packages/app/public/static/locales/en_US/translation.json
  36. 9 0
      packages/app/public/static/locales/ja_JP/translation.json
  37. 9 1
      packages/app/public/static/locales/zh_CN/translation.json
  38. 6 6
      packages/app/resource/locales/en_US/admin/userInvitation.txt
  39. 8 8
      packages/app/resource/locales/en_US/admin/userWaitingActivation.txt
  40. 3 3
      packages/app/resource/locales/en_US/notifications/comment.txt
  41. 4 4
      packages/app/resource/locales/en_US/notifications/notActiveUser.txt
  42. 2 2
      packages/app/resource/locales/en_US/notifications/pageCreate.txt
  43. 2 2
      packages/app/resource/locales/en_US/notifications/pageDelete.txt
  44. 2 2
      packages/app/resource/locales/en_US/notifications/pageEdit.txt
  45. 2 2
      packages/app/resource/locales/en_US/notifications/pageLike.txt
  46. 2 2
      packages/app/resource/locales/en_US/notifications/pageMove.txt
  47. 4 4
      packages/app/resource/locales/en_US/notifications/passwordReset.txt
  48. 1 1
      packages/app/resource/locales/en_US/notifications/passwordResetSuccessful.txt
  49. 4 4
      packages/app/resource/locales/en_US/notifications/userActivation.txt
  50. 6 6
      packages/app/resource/locales/ja_JP/admin/userInvitation.txt
  51. 8 8
      packages/app/resource/locales/ja_JP/admin/userWaitingActivation.txt
  52. 4 4
      packages/app/resource/locales/ja_JP/notifications/notActiveUser.txt
  53. 4 4
      packages/app/resource/locales/ja_JP/notifications/passwordReset.txt
  54. 1 1
      packages/app/resource/locales/ja_JP/notifications/passwordResetSuccessful.txt
  55. 4 4
      packages/app/resource/locales/ja_JP/notifications/userActivation.txt
  56. 6 6
      packages/app/resource/locales/zh_CN/admin/userInvitation.txt
  57. 8 8
      packages/app/resource/locales/zh_CN/admin/userWaitingActivation.txt
  58. 3 3
      packages/app/resource/locales/zh_CN/notifications/comment.txt
  59. 4 4
      packages/app/resource/locales/zh_CN/notifications/notActiveUser.txt
  60. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageCreate.txt
  61. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageDelete.txt
  62. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageEdit.txt
  63. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageLike.txt
  64. 2 2
      packages/app/resource/locales/zh_CN/notifications/pageMove.txt
  65. 3 3
      packages/app/resource/locales/zh_CN/notifications/passwordReset.txt
  66. 1 1
      packages/app/resource/locales/zh_CN/notifications/passwordResetSuccessful.txt
  67. 4 4
      packages/app/resource/locales/zh_CN/notifications/userActivation.txt
  68. 2 3
      packages/app/src/client/services/layout.ts
  69. 1 1
      packages/app/src/components/Admin/AdminHome/SystemInfomationTable.tsx
  70. 31 42
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  71. 4 1
      packages/app/src/components/InAppNotification/InAppNotificationDropdown.tsx
  72. 1 1
      packages/app/src/components/Layout/MainPane.tsx
  73. 1 1
      packages/app/src/components/Me/ApiSettings.tsx
  74. 2 2
      packages/app/src/components/Me/ProfileImageSettings.tsx
  75. 1 1
      packages/app/src/components/Navbar/AuthorInfo.tsx
  76. 25 15
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  77. 8 8
      packages/app/src/components/Navbar/GrowiNavbar.tsx
  78. 4 1
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  79. 35 0
      packages/app/src/components/NotAvailable.tsx
  80. 15 19
      packages/app/src/components/NotAvailableForGuest.tsx
  81. 27 0
      packages/app/src/components/NotAvailableForNow.tsx
  82. 16 8
      packages/app/src/components/Page.tsx
  83. 2 2
      packages/app/src/components/Page/DisplaySwitcher.tsx
  84. 5 3
      packages/app/src/components/Page/TagEditModal.jsx
  85. 3 1
      packages/app/src/components/Page/TagsInput.tsx
  86. 1 1
      packages/app/src/components/PageAlert/TrashPageAlert.tsx
  87. 6 1
      packages/app/src/components/PageEditor/DrawioModal.tsx
  88. 6 1
      packages/app/src/components/PageEditor/ScrollSyncHelper.js
  89. 1 1
      packages/app/src/components/SearchPage.tsx
  90. 1 1
      packages/app/src/components/Sidebar/RecentChanges.tsx
  91. 1 1
      packages/app/src/components/Sidebar/SidebarNav.tsx
  92. 1 1
      packages/app/src/components/User/UserDate.jsx
  93. 0 12
      packages/app/src/lib/util/isSecurityEnv.js
  94. 3 3
      packages/app/src/pages/[[...path]].page.tsx
  95. 2 2
      packages/app/src/pages/_app.page.tsx
  96. 4 1
      packages/app/src/pages/admin/customize.page.tsx
  97. 24 4
      packages/app/src/pages/share/[[...path]].page.tsx
  98. 5 4
      packages/app/src/pages/utils/commons.ts
  99. 0 29
      packages/app/src/server/crowi/dev.js
  100. 0 41
      packages/app/src/server/crowi/express-init.js

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

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

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

@@ -41,6 +41,10 @@ on:
         type: boolean
         type: boolean
         default: false
         default: false
 
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 
 
 jobs:
 jobs:
 
 
@@ -57,7 +61,7 @@ jobs:
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     with:
     with:
       node-version: 16.x
       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-report-artifact-name: Cypress report
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
       cypress-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
     secrets:
@@ -73,7 +77,7 @@ jobs:
 
 
     with:
     with:
       node-version: 16.x
       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
       cypress-report-artifact-name: Cypress report
     secrets:
     secrets:
       REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
       REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}

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

@@ -20,6 +20,11 @@ on:
       - packages/slack/**
       - packages/slack/**
       - packages/ui/**
       - packages/ui/**
 
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 jobs:
   lint:
   lint:
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest

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

@@ -16,6 +16,11 @@ on:
       - '!packages/slackbot-proxy/docker/**'
       - '!packages/slackbot-proxy/docker/**'
       - packages/slack/**
       - packages/slack/**
 
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 jobs:
 
 
   test:
   test:

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

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

+ 5 - 0
.github/workflows/draft-release.yml

@@ -5,6 +5,11 @@ on:
     branches:
     branches:
       - master
       - master
 
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 jobs:
 
 
   # Refs: https://github.com/release-drafter/release-drafter
   # Refs: https://github.com/release-drafter/release-drafter

+ 5 - 0
.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
     # Only following types are handled by the action, but one can default to all as well
     types: [opened, reopened, edited, synchronize]
     types: [opened, reopened, edited, synchronize]
 
 
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
 jobs:
 
 
   # Refs: https://github.com/release-drafter/release-drafter
   # Refs: https://github.com/release-drafter/release-drafter

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

@@ -11,6 +11,10 @@ jobs:
 
 
     runs-on: ubuntu-latest
     runs-on: ubuntu-latest
 
 
+    strategy:
+      matrix:
+        platform: [linux/amd64, linux/arm64]
+
     steps:
     steps:
     - uses: actions/checkout@v3
     - uses: actions/checkout@v3
       with:
       with:
@@ -22,7 +26,7 @@ jobs:
 
 
     - name: Docker meta
     - name: Docker meta
       id: meta
       id: meta
-      uses: docker/metadata-action@v3
+      uses: docker/metadata-action@v4
       with:
       with:
         images: weseek/growi,ghcr.io/weseek/growi
         images: weseek/growi,ghcr.io/weseek/growi
         tags: |
         tags: |
@@ -40,6 +44,10 @@ jobs:
         username: wsmoogle
         username: wsmoogle
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
 
+    - name: Set up QEMU
+      if: ${{ matrix.platform == 'linux/arm64' }}
+      uses: docker/setup-qemu-action@v1
+
     - name: Set up Docker Buildx
     - name: Set up Docker Buildx
       uses: docker/setup-buildx-action@v2
       uses: docker/setup-buildx-action@v2
 
 
@@ -48,7 +56,7 @@ jobs:
       with:
       with:
         context: .
         context: .
         file: ./packages/app/docker/Dockerfile
         file: ./packages/app/docker/Dockerfile
-        platforms: linux/amd64
+        platforms: ${{ matrix.platform }}
         push: true
         push: true
         builder: ${{ steps.buildx.outputs.name }}
         builder: ${{ steps.buildx.outputs.name }}
         cache-from: type=gha
         cache-from: type=gha

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

@@ -24,7 +24,7 @@ jobs:
 
 
     - name: Docker meta
     - name: Docker meta
       id: meta
       id: meta
-      uses: docker/metadata-action@v3
+      uses: docker/metadata-action@v4
       with:
       with:
         images: weseek/growi-slackbot-proxy,ghcr.io/weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
         images: weseek/growi-slackbot-proxy,ghcr.io/weseek/growi-slackbot-proxy,asia.gcr.io/${{ secrets.GCP_PRJ_ID_SLACKBOT_PROXY }}/growi-slackbot-proxy
         tags: |
         tags: |
@@ -43,12 +43,12 @@ jobs:
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
 
     - name: Authenticate to Google Cloud for GROWI.cloud
     - name: Authenticate to Google Cloud for GROWI.cloud
-      uses: google-github-actions/auth@v0
+      uses: google-github-actions/auth@v1
       with:
       with:
         credentials_json: '${{ secrets.GCP_SA_KEY_SLACKBOT_PROXY }}'
         credentials_json: '${{ secrets.GCP_SA_KEY_SLACKBOT_PROXY }}'
 
 
     - name: Setup gcloud
     - name: Setup gcloud
-      uses: google-github-actions/setup-gcloud@v0
+      uses: google-github-actions/setup-gcloud@v1
 
 
     - name: Configure docker for gcloud
     - name: Configure docker for gcloud
       run: |
       run: |
@@ -75,7 +75,7 @@ jobs:
         mv /tmp/.buildx-cache-new /tmp/.buildx-cache
         mv /tmp/.buildx-cache-new /tmp/.buildx-cache
 
 
     - name: Add tag
     - name: Add tag
-      uses: anothrNick/github-tag-action@1.38.0
+      uses: anothrNick/github-tag-action@v1
       env:
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         CUSTOM_TAG: v${{ steps.package-json.outputs.packageVersion }}
         CUSTOM_TAG: v${{ steps.package-json.outputs.packageVersion }}

+ 8 - 13
.github/workflows/release.yml

@@ -128,7 +128,7 @@ jobs:
 
 
     strategy:
     strategy:
       matrix:
       matrix:
-        flavor: [default, nocdn]
+        platform: [linux/amd64, linux/arm64]
 
 
     steps:
     steps:
     - uses: actions/checkout@v3
     - uses: actions/checkout@v3
@@ -136,19 +136,11 @@ jobs:
         ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
         ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
         lfs: true
         lfs: true
 
 
-    - name: Setup suffix
-      id: suffix
-      run: |
-        [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo "SUFFIX=$suffix" >> $GITHUB_OUTPUT
-
     - name: Docker meta
     - name: Docker meta
       id: meta
       id: meta
       uses: docker/metadata-action@v4
       uses: docker/metadata-action@v4
       with:
       with:
         images: weseek/growi,ghcr.io/weseek/growi
         images: weseek/growi,ghcr.io/weseek/growi
-        flavor: |
-          suffix=${{ steps.suffix.outputs.SUFFIX }}
         tags: |
         tags: |
           type=raw,value=latest
           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}}
@@ -168,6 +160,10 @@ jobs:
         username: wsmoogle
         username: wsmoogle
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
         password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
 
+    - name: Set up QEMU
+      if: ${{ matrix.platform == 'linux/arm64' }}
+      uses: docker/setup-qemu-action@v1
+
     - name: Set up Docker Buildx
     - name: Set up Docker Buildx
       uses: docker/setup-buildx-action@v2
       uses: docker/setup-buildx-action@v2
 
 
@@ -176,10 +172,8 @@ jobs:
       with:
       with:
         context: .
         context: .
         file: ./packages/app/docker/Dockerfile
         file: ./packages/app/docker/Dockerfile
-        platforms: linux/amd64
+        platforms: ${{ matrix.platform }}
         push: true
         push: true
-        build-args: |
-          flavor=${{ matrix.flavor }}
         builder: ${{ steps.buildx.outputs.name }}
         builder: ${{ steps.buildx.outputs.name }}
         cache-from: type=gha
         cache-from: type=gha
         cache-to: type=gha,mode=max
         cache-to: type=gha,mode=max
@@ -198,7 +192,8 @@ jobs:
       with:
       with:
         channel: '#release'
         channel: '#release'
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
         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 }}'
+        message: '*Release v${{ needs.create-github-release.outputs.RELEASED_VERSION }} (${{ matrix.platform }})* Succeeded'
 
 
     - name: Check whether workspace is clean
     - name: Check whether workspace is clean
       run: |
       run: |

+ 13 - 0
.mergify.yml

@@ -0,0 +1,13 @@
+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

+ 1 - 1
lerna.json

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

+ 1 - 1
package.json

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

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

+ 9 - 16
packages/app/docker/Dockerfile

@@ -1,7 +1,5 @@
 # syntax = docker/dockerfile:1.4
 # syntax = docker/dockerfile:1.4
 
 
-ARG flavor=default
-
 
 
 ##
 ##
 ## packages-json-picker
 ## packages-json-picker
@@ -30,11 +28,15 @@ ENV nodeModulesGrowiPackagesDir ${optDir}/node_modules/@growi
 # expect a string seperated by commas (e.g. "A,B")
 # expect a string seperated by commas (e.g. "A,B")
 ENV removeNodeModulesSymlinkPaths ${nodeModulesGrowiPackagesDir}/slackbot-proxy
 ENV removeNodeModulesSymlinkPaths ${nodeModulesGrowiPackagesDir}/slackbot-proxy
 
 
+RUN set -eux; \
+	apt-get update; \
+	apt-get install -y python3 build-essential;
+
 # copy files
 # copy files
 COPY --from=packages-json-picker ${optDir} .
 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
 RUN npx -y lerna bootstrap -- --frozen-lockfile
 
 
 # remove unnecessary symlinks
 # 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
 ENV optDir /opt
 
 
@@ -80,20 +82,11 @@ RUN tar -xf node_modules.tar
 RUN rm 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
 ## builder
 ##
 ##
-FROM prebuilder-${flavor} AS builder
+FROM prebuilder AS builder
 
 
 ENV optDir /opt
 ENV optDir /opt
 
 

+ 16 - 17
packages/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "6.0.0",
+  "version": "6.0.1-RC.0",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",
@@ -9,7 +9,7 @@
     "build:client": "yarn next build",
     "build:client": "yarn next build",
     "prebuild:client": "tsc -p tsconfig.build.next.config.json",
     "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",
     "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",
     "clean": "npx -y shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
     "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",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
@@ -54,7 +54,8 @@
     "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
     "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",
     "escape-string-regexp": "5.0.0 or above exports only ESM",
     "next": "/Sandbox rendering is crashed with v12.3 or above ",
     "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."
   },
   },
   "dependencies": {
   "dependencies": {
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
     "@akebifiky/remark-simple-plantuml": "^1.0.2",
@@ -65,16 +66,16 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.0",
-    "@growi/core": "^6.0.0",
-    "@growi/hackmd": "^6.0.0",
-    "@growi/preset-themes": "^6.0.0",
-    "@growi/remark-drawio": "^6.0.0",
-    "@growi/remark-growi-directive": "^6.0.0",
-    "@growi/remark-lsx": "^6.0.0",
-    "@growi/slack": "^6.0.0",
-    "@promster/express": "^7.0.2",
-    "@promster/server": "^7.0.4",
+    "@growi/codemirror-textlint": "^6.0.1-RC.0",
+    "@growi/core": "^6.0.1-RC.0",
+    "@growi/hackmd": "^6.0.1-RC.0",
+    "@growi/preset-themes": "^6.0.1-RC.0",
+    "@growi/remark-drawio": "^6.0.1-RC.0",
+    "@growi/remark-growi-directive": "^6.0.1-RC.0",
+    "@growi/remark-lsx": "^6.0.1-RC.0",
+    "@growi/slack": "^6.0.1-RC.0",
+    "@promster/express": "^7.0.6",
+    "@promster/server": "^7.0.8",
     "@slack/web-api": "^6.2.4",
     "@slack/web-api": "^6.2.4",
     "@slack/webhook": "^6.0.0",
     "@slack/webhook": "^6.0.0",
     "JSONStream": "^1.3.5",
     "JSONStream": "^1.3.5",
@@ -100,7 +101,6 @@
     "diff": "^5.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
     "diff_match_patch": "^0.1.1",
     "ejs": "^3.1.8",
     "ejs": "^3.1.8",
-    "entities": "^2.0.0",
     "esa-node": "^0.2.2",
     "esa-node": "^0.2.2",
     "escape-string-regexp": "=4.0.0",
     "escape-string-regexp": "=4.0.0",
     "eslint-plugin-regex": "^1.8.0",
     "eslint-plugin-regex": "^1.8.0",
@@ -110,7 +110,6 @@
     "express-mongo-sanitize": "^2.1.0",
     "express-mongo-sanitize": "^2.1.0",
     "express-session": "^1.16.1",
     "express-session": "^1.16.1",
     "express-validator": "^6.14.0",
     "express-validator": "^6.14.0",
-    "express-webpack-assets": "^0.1.0",
     "extensible-custom-error": "^0.0.7",
     "extensible-custom-error": "^0.0.7",
     "graceful-fs": "^4.1.11",
     "graceful-fs": "^4.1.11",
     "hast-util-select": "^5.0.2",
     "hast-util-select": "^5.0.2",
@@ -148,6 +147,7 @@
     "passport-ldapauth": "^3.0.1",
     "passport-ldapauth": "^3.0.1",
     "passport-local": "^1.0.0",
     "passport-local": "^1.0.0",
     "passport-saml": "^3.2.0",
     "passport-saml": "^3.2.0",
+    "prom-client": "^14.1.1",
     "rate-limiter-flexible": "^2.3.7",
     "rate-limiter-flexible": "^2.3.7",
     "react": "^18.2.0",
     "react": "^18.2.0",
     "react-bootstrap-typeahead": "^5.2.2",
     "react-bootstrap-typeahead": "^5.2.2",
@@ -182,7 +182,6 @@
     "string-width": "=4.2.2",
     "string-width": "=4.2.2",
     "superjson": "^1.9.1",
     "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
     "swagger-jsdoc": "^6.1.0",
-    "swig-templates": "^2.0.2",
     "swr": "^1.3.0",
     "swr": "^1.3.0",
     "throttle-debounce": "^3.0.1",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
     "toastr": "^2.1.2",
@@ -201,7 +200,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@growi/ui": "^6.0.0",
+    "@growi/ui": "^6.0.1-RC.0",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",
     "@next/bundle-analyzer": "^12.2.3",

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

@@ -60,6 +60,7 @@
   "Presentation Mode": "Presentation",
   "Presentation Mode": "Presentation",
   "The end": "The end",
   "The end": "The end",
   "Not available for guest": "Not available for guest",
   "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 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",
   "No users have bookmarked yet": "No users have bookmarked yet",
@@ -797,5 +798,12 @@
   "v5_page_migration": {
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page tree feature is not available yet.",
     "page_tree_not_avaliable" : "Page tree feature is not available yet.",
     "go_to_settings": "Go to settings to enable the feature"
     "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"
+    }
   }
   }
 }
 }

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

@@ -57,6 +57,7 @@
   "Presentation Mode": "プレゼンテーション",
   "Presentation Mode": "プレゼンテーション",
   "The end": "おしまい",
   "The end": "おしまい",
   "Not available for guest": "ゲストユーザーは利用できません",
   "Not available for guest": "ゲストユーザーは利用できません",
+  "Not available in this version": "このバージョンでは利用できません",
   "No users have liked this yet": "いいねをしているユーザーはいません",
   "No users have liked this yet": "いいねをしているユーザーはいません",
   "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "No users have bookmarked yet": "ブックマークしているユーザーはいません",
   "Create Archive Page": "アーカイブページの作成",
   "Create Archive Page": "アーカイブページの作成",
@@ -79,6 +80,7 @@
   "View diff": "差分を表示",
   "View diff": "差分を表示",
   "No diff": "差分なし",
   "No diff": "差分なし",
   "User ID": "ユーザーID",
   "User ID": "ユーザーID",
+  "User Settings": "ユーザー設定",
   "User Information": "ユーザー情報",
   "User Information": "ユーザー情報",
   "Basic Info": "ユーザーの基本情報",
   "Basic Info": "ユーザーの基本情報",
   "Name": "名前",
   "Name": "名前",
@@ -796,5 +798,12 @@
   "v5_page_migration": {
   "v5_page_migration": {
     "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
     "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
     "go_to_settings": "設定する"
     "go_to_settings": "設定する"
+  },
+  "tag_edit_modal": {
+    "edit_tags": "タグの編集",
+    "done": "完了",
+    "tags_input": {
+      "tag_name": "タグ名"
+    }
   }
   }
 }
 }

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

@@ -56,7 +56,8 @@
   "No_attachments_yet": "暂无附件",
   "No_attachments_yet": "暂无附件",
 	"Presentation Mode": "演示文稿",
 	"Presentation Mode": "演示文稿",
   "The end": "结束",
   "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 liked this yet": "还没有用户喜欢这个",
   "No users have bookmarked yet": "还没有用户加入书签",
   "No users have bookmarked yet": "还没有用户加入书签",
   "Create Archive Page": "创建归档页",
   "Create Archive Page": "创建归档页",
@@ -801,5 +802,12 @@
   "v5_page_migration": {
   "v5_page_migration": {
     "page_tree_not_avaliable": "Page Tree 功能不可用",
     "page_tree_not_avaliable": "Page Tree 功能不可用",
     "go_to_settings": "进入设置,启用该功能"
     "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:
 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)
 (This password was auto generated. Update required at the first time you logging in)
 
 
 We are waiting for you!
 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:
 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:
 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
 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.
 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.
 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
 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.
 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.
 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
 Password Reset Successful
 
 
-Hi {{ email }}
+Hi <%- email -%>
 
 
 Your password has been successfully reset.
 Your password has been successfully reset.
 Please log in with your new password.
 Please log in with your new password.

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

@@ -1,12 +1,12 @@
 Account confirmation
 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.
 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.
 If you did not created the account, you can safely ignore this email.

+ 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:
 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)
 (This password was auto generated. Update required at the first time you logging in)
 
 
 We are waiting for you!
 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:
 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:
 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アドレスで再度お試しください。
 他の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 -%> に失効します。
 
 
 ※当メールの内容に心当たりがない場合は、このメールを無視してください。
 ※当メールの内容に心当たりがない場合は、このメールを無視してください。

+ 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:
 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)
 (This password was auto generated. Update required at the first time you logging in)
 
 
 We are waiting for you!
 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:
 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:
 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 }}失效。
 这个链接在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 -%>失效。
 
 
 如果您尚未创建,请忽略此电子邮件。
 如果您尚未创建,请忽略此电子邮件。

+ 2 - 3
packages/app/src/client/services/layout.ts

@@ -1,4 +1,4 @@
-import { useIsContainerFluid, useShareLinkId } from '~/stores/context';
+import { useIsContainerFluid } from '~/stores/context';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useEditorMode } from '~/stores/ui';
 import { useEditorMode } from '~/stores/ui';
 
 
@@ -17,8 +17,7 @@ export const useEditorModeClassName = (): string => {
 };
 };
 
 
 export const useCurrentGrowiLayoutFluidClassName = (): string => {
 export const useCurrentGrowiLayoutFluidClassName = (): string => {
-  const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { data: currentPage } = useSWRxCurrentPage();
 
 
   const { data: dataIsContainerFluid } = useIsContainerFluid();
   const { data: dataIsContainerFluid } = useIsContainerFluid();
 
 

+ 1 - 1
packages/app/src/components/Admin/AdminHome/SystemInfomationTable.tsx

@@ -25,7 +25,7 @@ const SystemInformationTable = (props: Props) => {
       <tbody>
       <tbody>
         <tr>
         <tr>
           <th>GROWI</th>
           <th>GROWI</th>
-          <td data-hide-in-vrt>{ growiVersion }</td>
+          <td data-vrt-blackout>{ growiVersion }</td>
         </tr>
         </tr>
         <tr>
         <tr>
           <th>node.js</th>
           <th>node.js</th>

+ 31 - 42
packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx

@@ -1,45 +1,34 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, { useCallback, useMemo, useState } from 'react';
 
 
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
 
 
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import {
 import {
-  apiv3Delete, apiv3Get, apiv3PostForm, apiv3Put,
+  apiv3Delete, apiv3PostForm, apiv3Put,
 } from '~/client/util/apiv3-client';
 } from '~/client/util/apiv3-client';
 import ImageCropModal from '~/components/Common/ImageCropModal';
 import ImageCropModal from '~/components/Common/ImageCropModal';
+import { useIsDefaultLogo, useIsCustomizedLogoUploaded } from '~/stores/context';
 
 
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
 
 
+
 const DEFAULT_LOGO = '/images/logo.svg';
 const DEFAULT_LOGO = '/images/logo.svg';
+const CUSTOMIZED_LOGO = '/attachment/brand-logo';
 
 
 const CustomizeLogoSetting = (): JSX.Element => {
 const CustomizeLogoSetting = (): JSX.Element => {
 
 
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { data: isDefaultLogo } = useIsDefaultLogo();
+  const { data: isCustomizedLogoUploaded, mutate: mutateIsCustomizedLogoUploaded } = useIsCustomizedLogoUploaded();
 
 
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
-  const [isDefaultLogo, setIsDefaultLogo] = useState<boolean>(true);
+  const [isDefaultLogoSelected, setIsDefaultLogoSelected] = useState<boolean>(isDefaultLogo ?? true);
   const [retrieveError, setRetrieveError] = useState<any>();
   const [retrieveError, setRetrieveError] = useState<any>();
-  const [customizedLogoSrc, setCustomizedLogoSrc] = useState< string | null >(null);
-
-  const retrieveData = useCallback(async() => {
-    try {
-      const response = await apiv3Get('/customize-setting/customize-logo');
-      const { isDefaultLogo: _isDefaultLogo, customizedLogoSrc } = response.data;
-      const isDefaultLogo = _isDefaultLogo ?? true;
-
-      setIsDefaultLogo(isDefaultLogo);
-      setCustomizedLogoSrc(customizedLogoSrc);
-    }
-    catch (err) {
-      setRetrieveError(err);
-      throw new Error('Failed to fetch data');
-    }
-  }, []);
 
 
-  useEffect(() => {
-    retrieveData();
-  }, [retrieveData]);
+  const currentLogo = useMemo(() => {
+    return isDefaultLogo ? DEFAULT_LOGO : CUSTOMIZED_LOGO;
+  }, [isDefaultLogo]);
 
 
   const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
   const onSelectFile = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
     if (e.target.files != null && e.target.files.length > 0) {
     if (e.target.files != null && e.target.files.length > 0) {
@@ -52,22 +41,18 @@ const CustomizeLogoSetting = (): JSX.Element => {
 
 
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {
-      const response = await apiv3Put('/customize-setting/customize-logo', {
-        isDefaultLogo,
-      });
-      const { customizedParams } = response.data;
-      setIsDefaultLogo(customizedParams.isDefaultLogo);
+      await apiv3Put('/customize-setting/customize-logo', { isDefaultLogo: isDefaultLogoSelected });
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [t, isDefaultLogo]);
+  }, [t, isDefaultLogoSelected]);
 
 
   const onClickDeleteBtn = useCallback(async() => {
   const onClickDeleteBtn = useCallback(async() => {
     try {
     try {
       await apiv3Delete('/customize-setting/delete-brand-logo');
       await apiv3Delete('/customize-setting/delete-brand-logo');
-      setCustomizedLogoSrc(null);
+      mutateIsCustomizedLogoUploaded(false);
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
@@ -75,15 +60,15 @@ const CustomizeLogoSetting = (): JSX.Element => {
       setRetrieveError(err);
       setRetrieveError(err);
       throw new Error('Failed to delete logo');
       throw new Error('Failed to delete logo');
     }
     }
-  }, [t]);
+  }, [mutateIsCustomizedLogoUploaded, t]);
 
 
 
 
   const processImageCompletedHandler = useCallback(async(croppedImage) => {
   const processImageCompletedHandler = useCallback(async(croppedImage) => {
     try {
     try {
       const formData = new FormData();
       const formData = new FormData();
       formData.append('file', croppedImage);
       formData.append('file', croppedImage);
-      const { data } = await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
-      setCustomizedLogoSrc(data.attachment.filePathProxied);
+      await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
+      mutateIsCustomizedLogoUploaded(true);
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
@@ -91,7 +76,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       setRetrieveError(err);
       setRetrieveError(err);
       throw new Error('Failed to upload brand logo');
       throw new Error('Failed to upload brand logo');
     }
     }
-  }, [t]);
+  }, [mutateIsCustomizedLogoUploaded, t]);
 
 
   return (
   return (
     <React.Fragment>
     <React.Fragment>
@@ -109,8 +94,8 @@ const CustomizeLogoSetting = (): JSX.Element => {
                       className="custom-control-input"
                       className="custom-control-input"
                       form="formImageType"
                       form="formImageType"
                       name="imagetypeForm[isDefaultLogo]"
                       name="imagetypeForm[isDefaultLogo]"
-                      checked={isDefaultLogo}
-                      onChange={() => { setIsDefaultLogo(true) }}
+                      checked={isDefaultLogoSelected}
+                      onChange={() => { setIsDefaultLogoSelected(true) }}
                     />
                     />
                     <label className="custom-control-label" htmlFor="radioDefaultLogo">
                     <label className="custom-control-label" htmlFor="radioDefaultLogo">
                       {t('admin:customize_settings.default_logo')}
                       {t('admin:customize_settings.default_logo')}
@@ -128,8 +113,8 @@ const CustomizeLogoSetting = (): JSX.Element => {
                       className="custom-control-input"
                       className="custom-control-input"
                       form="formImageType"
                       form="formImageType"
                       name="imagetypeForm[isDefaultLogo]"
                       name="imagetypeForm[isDefaultLogo]"
-                      checked={!isDefaultLogo}
-                      onChange={() => { setIsDefaultLogo(false) }}
+                      checked={!isDefaultLogoSelected}
+                      onChange={() => { setIsDefaultLogoSelected(false) }}
                     />
                     />
                     <label className="custom-control-label" htmlFor="radioUploadLogo">
                     <label className="custom-control-label" htmlFor="radioUploadLogo">
                       { t('admin:customize_settings.upload_logo') }
                       { t('admin:customize_settings.upload_logo') }
@@ -141,11 +126,15 @@ const CustomizeLogoSetting = (): JSX.Element => {
                     { t('admin:customize_settings.current_logo') }
                     { t('admin:customize_settings.current_logo') }
                   </label>
                   </label>
                   <div className="col-sm-8 col-12">
                   <div className="col-sm-8 col-12">
-                    <p><img src={customizedLogoSrc || DEFAULT_LOGO} className="picture picture-lg " id="settingBrandLogo" width="64" /></p>
-                    {(customizedLogoSrc != null) && (
-                      <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
-                        { t('admin:customize_settings.delete_logo') }
-                      </button>
+                    {isCustomizedLogoUploaded && (
+                      <>
+                        <p>
+                          <img src='/attachment/brand-logo' className="picture picture-lg " id="settingBrandLogo" width="64" />
+                        </p>
+                        <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
+                          { t('admin:customize_settings.delete_logo') }
+                        </button>
+                      </>
                     )}
                     )}
                   </div>
                   </div>
                 </div>
                 </div>

+ 4 - 1
packages/app/src/components/InAppNotification/InAppNotificationDropdown.tsx

@@ -26,7 +26,10 @@ export const InAppNotificationDropdown = (): JSX.Element => {
   const limit = 6;
   const limit = 6;
 
 
   const { data: socket } = useDefaultSocket();
   const { data: socket } = useDefaultSocket();
-  const { data: inAppNotificationData, mutate: mutateInAppNotificationData } = useSWRxInAppNotifications(limit);
+  const { data: inAppNotificationData, mutate: mutateInAppNotificationData } = useSWRxInAppNotifications(
+    limit, undefined, undefined,
+    { revalidateOnFocus: isOpen },
+  );
   const { data: inAppNotificationUnreadStatusCount, mutate: mutateInAppNotificationUnreadStatusCount } = useSWRxInAppNotificationStatus();
   const { data: inAppNotificationUnreadStatusCount, mutate: mutateInAppNotificationUnreadStatusCount } = useSWRxInAppNotificationStatus();
 
 
   // ripple
   // ripple

+ 1 - 1
packages/app/src/components/Layout/MainPane.tsx

@@ -24,7 +24,7 @@ export const MainPane = (props: Props): JSX.Element => {
                   <div className="flex-grow-1 flex-basis-0 mw-0">
                   <div className="flex-grow-1 flex-basis-0 mw-0">
                     {children}
                     {children}
                   </div>
                   </div>
-                  <div className="grw-side-contents-container d-edit-none">
+                  <div className="grw-side-contents-container d-edit-none" data-vrt-blackout-side-contents>
                     <div className="grw-side-contents-sticky-container">
                     <div className="grw-side-contents-sticky-container">
                       {sideContents}
                       {sideContents}
                     </div>
                     </div>

+ 1 - 1
packages/app/src/components/Me/ApiSettings.tsx

@@ -39,7 +39,7 @@ const ApiSettings = React.memo((): JSX.Element => {
             ? (
             ? (
               <input
               <input
                 data-testid="grw-api-settings-input"
                 data-testid="grw-api-settings-input"
-                data-hide-in-vrt
+                data-vrt-blackout
                 className="form-control"
                 className="form-control"
                 type="text"
                 type="text"
                 name="apiToken"
                 name="apiToken"

+ 2 - 2
packages/app/src/components/Me/ProfileImageSettings.tsx

@@ -105,14 +105,14 @@ const ProfileImageSettings = (): JSX.Element => {
                 onChange={() => setGravatarEnabled(true)}
                 onChange={() => setGravatarEnabled(true)}
               />
               />
               <label className="custom-control-label" htmlFor="radioGravatar">
               <label className="custom-control-label" htmlFor="radioGravatar">
-                <img src={GRAVATAR_DEFAULT} data-hide-in-vrt /> Gravatar
+                <img src={GRAVATAR_DEFAULT} data-vrt-blackout-profile /> Gravatar
               </label>
               </label>
               <a href="https://gravatar.com/">
               <a href="https://gravatar.com/">
                 <small><i className="icon-arrow-right-circle" aria-hidden="true"></i></small>
                 <small><i className="icon-arrow-right-circle" aria-hidden="true"></i></small>
               </a>
               </a>
             </div>
             </div>
           </h4>
           </h4>
-          <img src={generateGravatarSrc(currentUser.email)} width="64" data-hide-in-vrt />
+          <img src={generateGravatarSrc(currentUser.email)} width="64" data-vrt-blackout-profile />
         </div>
         </div>
 
 
         <div className="col-md-6 col-12">
         <div className="col-md-6 col-12">

+ 1 - 1
packages/app/src/components/Navbar/AuthorInfo.tsx

@@ -66,7 +66,7 @@ export const AuthorInfo = (props: AuthorInfoProps): JSX.Element => {
       </div>
       </div>
       <div>
       <div>
         <div>{infoLabelForSubNav} {userLabel}</div>
         <div>{infoLabelForSubNav} {userLabel}</div>
-        <div className="text-muted text-date" data-hide-in-vrt>
+        <div className="text-muted text-date" data-vrt-blackout-datetime>
           {renderParsedDate()}
           {renderParsedDate()}
         </div>
         </div>
       </div>
       </div>

+ 25 - 15
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -1,6 +1,8 @@
 import React, { useState, useEffect, useCallback } from 'react';
 import React, { useState, useEffect, useCallback } from 'react';
 
 
-import { isPopulated, IUser, pagePathUtils } from '@growi/core';
+import {
+  isPopulated, IUser, pagePathUtils, IPagePopulatedToShowRevision,
+} from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import { useRouter } from 'next/router';
 import { useRouter } from 'next/router';
@@ -33,6 +35,7 @@ import AttachmentIcon from '../Icons/AttachmentIcon';
 import HistoryIcon from '../Icons/HistoryIcon';
 import HistoryIcon from '../Icons/HistoryIcon';
 import PresentationIcon from '../Icons/PresentationIcon';
 import PresentationIcon from '../Icons/PresentationIcon';
 import ShareLinkIcon from '../Icons/ShareLinkIcon';
 import ShareLinkIcon from '../Icons/ShareLinkIcon';
+import { NotAvailableForNow } from '../NotAvailableForNow';
 import { Skeleton } from '../Skeleton';
 import { Skeleton } from '../Skeleton';
 
 
 import type { AuthorInfoProps } from './AuthorInfo';
 import type { AuthorInfoProps } from './AuthorInfo';
@@ -86,16 +89,18 @@ const PageOperationMenuItems = (props: PageOperationMenuItemsProps): JSX.Element
   return (
   return (
     <>
     <>
       {/* Presentation */}
       {/* Presentation */}
-      <DropdownItem
-        onClick={() => openPresentationModal(hrefForPresentationModal)}
-        data-testid="open-presentation-modal-btn"
-        className="grw-page-control-dropdown-item"
-      >
-        <i className="icon-fw grw-page-control-dropdown-icon">
-          <PresentationIcon />
-        </i>
-        { t('Presentation Mode') }
-      </DropdownItem>
+      <NotAvailableForNow>
+        <DropdownItem
+          onClick={() => openPresentationModal(hrefForPresentationModal)}
+          data-testid="open-presentation-modal-btn"
+          className="grw-page-control-dropdown-item"
+        >
+          <i className="icon-fw grw-page-control-dropdown-icon">
+            <PresentationIcon />
+          </i>
+          { t('Presentation Mode') }
+        </DropdownItem>
+      </NotAvailableForNow>
 
 
       {/* Export markdown */}
       {/* Export markdown */}
       <DropdownItem
       <DropdownItem
@@ -179,16 +184,19 @@ const CreateTemplateMenuItems = (props: CreateTemplateMenuItemsProps): JSX.Eleme
 };
 };
 
 
 type GrowiContextualSubNavigationProps = {
 type GrowiContextualSubNavigationProps = {
+  currentPage?: IPagePopulatedToShowRevision,
   isCompactMode?: boolean,
   isCompactMode?: boolean,
   isLinkSharingDisabled: boolean,
   isLinkSharingDisabled: boolean,
 };
 };
 
 
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
 const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
 
 
+  const { currentPage } = props;
+
   const router = useRouter();
   const router = useRouter();
 
 
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
 
 
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPathname } = useCurrentPathname();
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
@@ -311,9 +319,11 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
   }, [currentPathname, openDeleteModal, router]);
   }, [currentPathname, openDeleteModal, router]);
 
 
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
   const switchContentWidthHandler = useCallback(async(pageId: string, value: boolean) => {
-    await updateContentWidth(pageId, value);
-    mutateCurrentPage();
-  }, [mutateCurrentPage]);
+    if (!isSharedPage) {
+      await updateContentWidth(pageId, value);
+      mutateCurrentPage();
+    }
+  }, [isSharedPage, mutateCurrentPage]);
 
 
   const templateMenuItemClickHandler = useCallback(() => {
   const templateMenuItemClickHandler = useCallback(() => {
     setIsPageTempleteModalShown(true);
     setIsPageTempleteModalShown(true);

+ 8 - 8
packages/app/src/components/Navbar/GrowiNavbar.tsx

@@ -11,7 +11,7 @@ import { useRipple } from 'react-use-ripple';
 import { UncontrolledTooltip } from 'reactstrap';
 import { UncontrolledTooltip } from 'reactstrap';
 
 
 import {
 import {
-  useIsSearchPage, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useCustomizedLogoSrc,
+  useIsSearchPage, useIsGuestUser, useIsSearchServiceConfigured, useAppTitle, useConfidential, useIsDefaultLogo,
 } from '~/stores/context';
 } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
 import { useCurrentPagePath } from '~/stores/page';
@@ -122,16 +122,16 @@ const Confidential: FC<ConfidentialProps> = memo((props: ConfidentialProps): JSX
 Confidential.displayName = 'Confidential';
 Confidential.displayName = 'Confidential';
 
 
 interface NavbarLogoProps {
 interface NavbarLogoProps {
-  logoSrc?: string,
+  isDefaultLogo?: boolean
 }
 }
 
 
 const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
 const GrowiNavbarLogo: FC<NavbarLogoProps> = memo((props: NavbarLogoProps) => {
-  const { logoSrc } = props;
+  const { isDefaultLogo } = props;
 
 
-  return logoSrc != null
+  return isDefaultLogo
+    ? <GrowiLogo />
     // eslint-disable-next-line @next/next/no-img-element
     // eslint-disable-next-line @next/next/no-img-element
-    ? (<img src={logoSrc} alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />)
-    : <GrowiLogo />;
+    : (<img src='/attachment/brand-logo' alt="custom logo" className="picture picture-lg p-2 mx-2" id="settingBrandLogo" width="32" />);
 });
 });
 
 
 GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
 GrowiNavbarLogo.displayName = 'GrowiNavbarLogo';
@@ -151,7 +151,7 @@ export const GrowiNavbar = (props: Props): JSX.Element => {
   const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
   const { data: isSearchServiceConfigured } = useIsSearchServiceConfigured();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isDeviceSmallerThanMd } = useIsDeviceSmallerThanMd();
   const { data: isSearchPage } = useIsSearchPage();
   const { data: isSearchPage } = useIsSearchPage();
-  const { data: customizedLogoSrc } = useCustomizedLogoSrc();
+  const { data: isDefaultLogo } = useIsDefaultLogo();
 
 
   return (
   return (
     <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
     <nav id="grw-navbar" className={`navbar grw-navbar ${styles['grw-navbar']} navbar-expand navbar-dark sticky-top mb-0 px-0`}>
@@ -159,7 +159,7 @@ export const GrowiNavbar = (props: Props): JSX.Element => {
       <div className="navbar-brand mr-0">
       <div className="navbar-brand mr-0">
         <Link href="/" prefetch={false}>
         <Link href="/" prefetch={false}>
           <a className="grw-logo d-block">
           <a className="grw-logo d-block">
-            <GrowiNavbarLogo logoSrc={customizedLogoSrc}/>
+            <GrowiNavbarLogo isDefaultLogo={isDefaultLogo} />
           </a>
           </a>
         </Link>
         </Link>
       </div>
       </div>

+ 4 - 1
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -9,6 +9,8 @@ import { debounce } from 'throttle-debounce';
 import { useSidebarCollapsed } from '~/stores/ui';
 import { useSidebarCollapsed } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
+import { useSWRxCurrentPage } from '~/stores/page';
+
 import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
 import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
 
 
 import styles from './GrowiSubNavigationSwitcher.module.scss';
 import styles from './GrowiSubNavigationSwitcher.module.scss';
@@ -27,6 +29,7 @@ const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
  */
  */
 const GrowiSubNavigationSwitcher = (props) => {
 const GrowiSubNavigationSwitcher = (props) => {
 
 
+  const { data: currentPage } = useSWRxCurrentPage();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
   const { data: isSidebarCollapsed } = useSidebarCollapsed();
 
 
   const [isVisible, setVisible] = useState(false);
   const [isVisible, setVisible] = useState(false);
@@ -125,7 +128,7 @@ const GrowiSubNavigationSwitcher = (props) => {
         ref={fixedContainerRef}
         ref={fixedContainerRef}
         style={{ width }}
         style={{ width }}
       >
       >
-        <GrowiContextualSubNavigation isCompactMode isLinkSharingDisabled />
+        <GrowiContextualSubNavigation currentPage isCompactMode isLinkSharingDisabled />
       </div>
       </div>
     </div>
     </div>
   );
   );

+ 35 - 0
packages/app/src/components/NotAvailable.tsx

@@ -0,0 +1,35 @@
+import React from 'react';
+
+import { TFunction } from 'next-i18next';
+import { Disable } from 'react-disable';
+import { UncontrolledTooltip, UncontrolledTooltipProps } from 'reactstrap';
+
+type NotAvailableProps = {
+  children: JSX.Element
+  isDisabled: boolean
+  title: ReturnType<TFunction>
+  classNamePrefix?: string
+  placement?: UncontrolledTooltipProps['placement']
+}
+
+export const NotAvailable = ({
+  children, isDisabled, title, classNamePrefix = 'grw-not-available', placement = 'top',
+}: NotAvailableProps): JSX.Element => {
+
+  if (!isDisabled) {
+    return children;
+  }
+
+  const id = `${classNamePrefix}-${Math.random().toString(32).substring(2)}`;
+
+  return (
+    <>
+      <div id={id}>
+        <Disable disabled={isDisabled}>
+          {children}
+        </Disable>
+      </div>
+      <UncontrolledTooltip placement={placement} target={id}>{title}</UncontrolledTooltip>
+    </>
+  );
+};

+ 15 - 19
packages/app/src/components/NotAvailableForGuest.tsx

@@ -1,35 +1,31 @@
 import React from 'react';
 import React from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
-import { Disable } from 'react-disable';
-import { UncontrolledTooltip } from 'reactstrap';
 
 
 import { useIsGuestUser } from '~/stores/context';
 import { useIsGuestUser } from '~/stores/context';
 
 
+import { NotAvailable } from './NotAvailable';
+
+
 type NotAvailableForGuestProps = {
 type NotAvailableForGuestProps = {
   children: JSX.Element
   children: JSX.Element
 }
 }
 
 
-export const NotAvailableForGuest = ({ children }: NotAvailableForGuestProps): JSX.Element => {
+export const NotAvailableForGuest = React.memo(({ children }: NotAvailableForGuestProps): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
-
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
-  const isDisabled = !!isGuestUser;
-
-  if (!isGuestUser) {
-    return children;
-  }
 
 
-  const id = `grw-not-available-for-guest-${Math.random().toString(32).substring(2)}`;
+  const isDisabled = !!isGuestUser;
+  const title = t('Not available for guest');
 
 
   return (
   return (
-    <>
-      <div id={id}>
-        <Disable disabled={isDisabled}>
-          { children }
-        </Disable>
-      </div>
-      <UncontrolledTooltip placement="top" target={id}>{t('Not available for guest')}</UncontrolledTooltip>
-    </>
+    <NotAvailable
+      isDisabled={isDisabled}
+      title={title}
+      classNamePrefix="grw-not-available-for-guest"
+    >
+      {children}
+    </NotAvailable>
   );
   );
-};
+});
+NotAvailableForGuest.displayName = 'NotAvailableForGuest';

+ 27 - 0
packages/app/src/components/NotAvailableForNow.tsx

@@ -0,0 +1,27 @@
+import React from 'react';
+
+import { useTranslation } from 'next-i18next';
+
+import { NotAvailable } from './NotAvailable';
+
+
+type NotAvailableForNowProps = {
+  children: JSX.Element
+}
+
+export const NotAvailableForNow = React.memo(({ children }: NotAvailableForNowProps): JSX.Element => {
+  const { t } = useTranslation();
+
+  const title = t('Not available in this version');
+
+  return (
+    <NotAvailable
+      isDisabled
+      title={title}
+      classNamePrefix="grw-not-available-for-now"
+    >
+      {children}
+    </NotAvailable>
+  );
+});
+NotAvailableForNow.displayName = 'NotAvailableForNow';

+ 16 - 8
packages/app/src/components/Page.tsx

@@ -1,11 +1,11 @@
 import React, {
 import React, {
-  useCallback,
+  FC, useCallback,
   useEffect, useRef,
   useEffect, useRef,
 } from 'react';
 } from 'react';
 
 
 import EventEmitter from 'events';
 import EventEmitter from 'events';
 
 
-import { pagePathUtils } from '@growi/core';
+import { pagePathUtils, IPagePopulatedToShowRevision } from '@growi/core';
 import { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import { DrawioEditByViewerProps } from '@growi/remark-drawio';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
@@ -48,9 +48,13 @@ const LinkEditModal = dynamic(() => import('./PageEditor/LinkEditModal'), { ssr:
 
 
 const logger = loggerFactory('growi:Page');
 const logger = loggerFactory('growi:Page');
 
 
+type Props = {
+  currentPage?: IPagePopulatedToShowRevision,
+}
 
 
-export const Page = (props) => {
+export const Page: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const { t } = useTranslation();
+  const { currentPage } = props;
 
 
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // Pass tocRef to generateViewOptions (=> rehypePlugin => customizeTOC) to call mutateCurrentPageTocNode when tocRef.current changes.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
   // The toc node passed by customizeTOC is assigned to tocRef.current.
@@ -64,7 +68,7 @@ export const Page = (props) => {
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
   const isSharedPage = pagePathUtils.isSharedPage(currentPathname ?? '');
 
 
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
-  const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { data: tagsInfo } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
   const { data: tagsInfo } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
@@ -128,14 +132,16 @@ export const Page = (props) => {
       toastSuccess(t('toaster.save_succeeded'));
       toastSuccess(t('toaster.save_succeeded'));
 
 
       // rerender
       // rerender
-      mutateCurrentPage();
+      if (!isSharedPage) {
+        mutateCurrentPage();
+      }
       mutateEditingMarkdown(newMarkdown);
       mutateEditingMarkdown(newMarkdown);
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error);
       toastError(error);
     }
     }
-  }, [currentPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
+  }, [currentPage, isSharedPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
 
 
   // set handler to open DrawioModal
   // set handler to open DrawioModal
   useEffect(() => {
   useEffect(() => {
@@ -182,14 +188,16 @@ export const Page = (props) => {
       toastSuccess(t('toaster.save_succeeded'));
       toastSuccess(t('toaster.save_succeeded'));
 
 
       // rerender
       // rerender
-      mutateCurrentPage();
+      if (!isSharedPage) {
+        mutateCurrentPage();
+      }
       mutateEditingMarkdown(newMarkdown);
       mutateEditingMarkdown(newMarkdown);
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       toastError(error);
       toastError(error);
     }
     }
-  }, [currentPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
+  }, [currentPage, isSharedPage, mutateCurrentPage, mutateEditingMarkdown, saveOrUpdate, shareLinkId, t, tagsInfo]);
 
 
   // set handler to open HandsonTableModal
   // set handler to open HandsonTableModal
   useEffect(() => {
   useEffect(() => {

+ 2 - 2
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -34,7 +34,7 @@ const PageView = React.memo((): JSX.Element => {
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: shareLinkId } = useShareLinkId();
   const { data: shareLinkId } = useShareLinkId();
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
-  const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
+  const { data: currentPage } = useSWRxCurrentPage();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
   const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
 
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
   const { mutate: mutateIsHackmdDraftUpdatingInRealtime } = useIsHackmdDraftUpdatingInRealtime();
@@ -92,7 +92,7 @@ const PageView = React.memo((): JSX.Element => {
   return (
   return (
     <>
     <>
       { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
       { isUsersHomePagePath && <UserInfo author={currentPage?.creator} /> }
-      { !isNotFound && <Page /> }
+      { !isNotFound && <Page currentPage={currentPage ?? undefined} /> }
       { isNotFound && <NotFoundPage /> }
       { isNotFound && <NotFoundPage /> }
     </>
     </>
   );
   );

+ 5 - 3
packages/app/src/components/Page/TagEditModal.jsx

@@ -1,6 +1,7 @@
 import React, { useState, useEffect } from 'react';
 import React, { useState, useEffect } from 'react';
-import PropTypes from 'prop-types';
 
 
+import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
@@ -9,6 +10,7 @@ import TagsInput from './TagsInput';
 
 
 function TagEditModal(props) {
 function TagEditModal(props) {
   const [tags, setTags] = useState([]);
   const [tags, setTags] = useState([]);
+  const { t } = useTranslation();
 
 
   function onTagsUpdatedByTagsInput(tags) {
   function onTagsUpdatedByTagsInput(tags) {
     setTags(tags);
     setTags(tags);
@@ -37,14 +39,14 @@ function TagEditModal(props) {
   return (
   return (
     <Modal isOpen={props.isOpen} toggle={closeModalHandler} id="edit-tag-modal" autoFocus={false}>
     <Modal isOpen={props.isOpen} toggle={closeModalHandler} id="edit-tag-modal" autoFocus={false}>
       <ModalHeader tag="h4" toggle={closeModalHandler} className="bg-primary text-light">
       <ModalHeader tag="h4" toggle={closeModalHandler} className="bg-primary text-light">
-        Edit Tags
+        {t('tag_edit_modal.edit_tags')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
         <TagsInput tags={tags} onTagsUpdated={onTagsUpdatedByTagsInput} autoFocus />
         <TagsInput tags={tags} onTagsUpdated={onTagsUpdatedByTagsInput} autoFocus />
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         <button type="button" className="btn btn-primary" onClick={handleSubmit}>
         <button type="button" className="btn btn-primary" onClick={handleSubmit}>
-          Done
+          {t('tag_edit_modal.done')}
         </button>
         </button>
       </ModalFooter>
       </ModalFooter>
     </Modal>
     </Modal>

+ 3 - 1
packages/app/src/components/Page/TagsInput.tsx

@@ -2,6 +2,7 @@ import React, {
   FC, useRef, useState, useCallback,
   FC, useRef, useState, useCallback,
 } from 'react';
 } from 'react';
 
 
+import { useTranslation } from 'next-i18next';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 
 
 import { useSWRxTagsSearch } from '~/stores/tag';
 import { useSWRxTagsSearch } from '~/stores/tag';
@@ -20,6 +21,7 @@ type Props = {
 }
 }
 
 
 const TagsInput: FC<Props> = (props: Props) => {
 const TagsInput: FC<Props> = (props: Props) => {
+  const { t } = useTranslation();
   const tagsInputRef = useRef<TypeaheadInstance>(null);
   const tagsInputRef = useRef<TypeaheadInstance>(null);
 
 
   const [resultTags, setResultTags] = useState<string[]>([]);
   const [resultTags, setResultTags] = useState<string[]>([]);
@@ -71,7 +73,7 @@ const TagsInput: FC<Props> = (props: Props) => {
         onSearch={searchHandler}
         onSearch={searchHandler}
         onKeyDown={keyDownHandler}
         onKeyDown={keyDownHandler}
         options={resultTags} // Search result (Some tag names)
         options={resultTags} // Search result (Some tag names)
-        placeholder="tag name"
+        placeholder={t('tag_edit_modal.tags_input.tag_name')}
         autoFocus={props.autoFocus}
         autoFocus={props.autoFocus}
       />
       />
     </div>
     </div>

+ 1 - 1
packages/app/src/components/PageAlert/TrashPageAlert.tsx

@@ -114,7 +114,7 @@ export const TrashPageAlert = (): JSX.Element => {
           <br />
           <br />
           <UserPicture user={deleteUser} />
           <UserPicture user={deleteUser} />
           <span className="ml-2">
           <span className="ml-2">
-            Deleted by { deleteUser?.name } at {deletedAt || pageData?.updatedAt}
+            Deleted by { deleteUser?.name } at <span data-vrt-blackout-datetime>{deletedAt || pageData?.updatedAt}</span>
           </span>
           </span>
         </div>
         </div>
         <div className="pt-1 d-flex align-items-end align-items-lg-center">
         <div className="pt-1 d-flex align-items-end align-items-lg-center">

+ 6 - 1
packages/app/src/components/PageEditor/DrawioModal.tsx

@@ -37,7 +37,12 @@ const drawioConfig = {
 
 
 export const DrawioModal = (): JSX.Element => {
 export const DrawioModal = (): JSX.Element => {
   const { data: drawioUri } = useDrawioUri();
   const { data: drawioUri } = useDrawioUri();
-  const { data: personalSettingsInfo } = usePersonalSettings();
+  const { data: personalSettingsInfo } = usePersonalSettings({
+    // make immutable
+    revalidateIfStale: false,
+    revalidateOnFocus: false,
+    revalidateOnReconnect: false,
+  });
 
 
   const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
   const { data: drawioModalData, close: closeDrawioModal } = useDrawioModal();
   const isOpened = drawioModalData?.isOpened ?? false;
   const isOpened = drawioModalData?.isOpened ?? false;

+ 6 - 1
packages/app/src/components/PageEditor/ScrollSyncHelper.js

@@ -77,6 +77,11 @@ class ScrollSyncHelper {
     }
     }
 
 
     const hiElement = lines[hi];
     const hiElement = lines[hi];
+
+    if (hiElement == null) {
+      return {};
+    }
+
     if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) {
     if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) {
       const loElement = lines[lo];
       const loElement = lines[lo];
       const bounds = loElement.element.getBoundingClientRect();
       const bounds = loElement.element.getBoundingClientRect();
@@ -95,7 +100,7 @@ class ScrollSyncHelper {
 
 
   getEditorLineNumberForPageOffset(parentElement, offset) {
   getEditorLineNumberForPageOffset(parentElement, offset) {
     const { previous, next } = this.getLineElementsAtPageOffset(parentElement, offset);
     const { previous, next } = this.getLineElementsAtPageOffset(parentElement, offset);
-    if (previous) {
+    if (previous != null) {
       if (next) {
       if (next) {
         const betweenProgress = (
         const betweenProgress = (
           offset - parentElement.scrollTop - previous.element.getBoundingClientRect().top)
           offset - parentElement.scrollTop - previous.element.getBoundingClientRect().top)

+ 1 - 1
packages/app/src/components/SearchPage.tsx

@@ -63,7 +63,7 @@ const SearchResultListHead = React.memo((props: SearchResultListHeadProps): JSX.
         <span className="ml-3">{`${leftNum}-${rightNum}`} / {total}</span>
         <span className="ml-3">{`${leftNum}-${rightNum}`} / {total}</span>
         { took != null && (
         { took != null && (
           // blackout 70px rectangle in VRT
           // blackout 70px rectangle in VRT
-          <span data-hide-in-vrt className="ml-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
+          <span data-vrt-blackout className="ml-3 text-muted d-inline-block" style={{ minWidth: '70px' }}>({took}ms)</span>
         ) }
         ) }
       </div>
       </div>
       <div className="input-group flex-nowrap search-result-select-group ml-auto d-md-flex d-none">
       <div className="input-group flex-nowrap search-result-select-group ml-auto d-md-flex d-none">

+ 1 - 1
packages/app/src/components/Sidebar/RecentChanges.tsx

@@ -42,7 +42,7 @@ const PageItemLower = memo(({ page }: PageItemLowerProps): JSX.Element => {
         <div className="icon-bubble mr-1 d-inline-block"></div>
         <div className="icon-bubble mr-1 d-inline-block"></div>
         <div className="mr-2 grw-list-counts d-inline-block">{page.commentCount}</div>
         <div className="mr-2 grw-list-counts d-inline-block">{page.commentCount}</div>
       </div>
       </div>
-      <div className="grw-formatted-distance-date small mt-auto" data-hide-in-vrt>
+      <div className="grw-formatted-distance-date small mt-auto" data-vrt-blackout-datetime>
         <FormattedDistanceDate id={page._id} date={page.updatedAt} />
         <FormattedDistanceDate id={page._id} date={page.updatedAt} />
       </div>
       </div>
     </div>
     </div>

+ 1 - 1
packages/app/src/components/Sidebar/SidebarNav.tsx

@@ -85,7 +85,7 @@ export const SidebarNav: FC<Props> = (props: Props) => {
   const { onItemSelected } = props;
   const { onItemSelected } = props;
 
 
   return (
   return (
-    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`}>
+    <div className={`grw-sidebar-nav ${styles['grw-sidebar-nav']}`} data-vrt-blackout-sidebar-nav>
       <div className="grw-sidebar-nav-primary-container">
       <div className="grw-sidebar-nav-primary-container">
         {/* eslint-disable max-len */}
         {/* eslint-disable max-len */}
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />
         <PrimaryItem contents={SidebarContentsType.TREE} label="Page Tree" iconName="format_list_bulleted" onItemSelected={onItemSelected} />

+ 1 - 1
packages/app/src/components/User/UserDate.jsx

@@ -16,7 +16,7 @@ export default class UserDate extends React.Component {
     const dt = format(date, this.props.format);
     const dt = format(date, this.props.format);
 
 
     return (
     return (
-      <span className={this.props.className} data-hide-in-vrt>
+      <span className={this.props.className} data-vrt-blackout-datetime>
         {dt}
         {dt}
       </span>
       </span>
     );
     );

+ 0 - 12
packages/app/src/lib/util/isSecurityEnv.js

@@ -1,12 +0,0 @@
-/**
- * return whether env belongs to Security settings
- * @param {string} key ex. 'security:passport-saml:isEnabled' is true
- * @returns {boolean}
- * @memberof envUtils
- */
-const isSecurityEnv = (key) => {
-  const array = key.split(':');
-  return (array[0] === 'security');
-};
-
-module.exports = isSecurityEnv;

+ 3 - 3
packages/app/src/pages/[[...path]].page.tsx

@@ -71,7 +71,7 @@ import {
   useIsAclEnabled, useIsSearchPage, useTemplateTagData, useTemplateBodyData, useIsEnabledAttachTitleHeader,
   useIsAclEnabled, useIsSearchPage, useTemplateTagData, useTemplateBodyData, useIsEnabledAttachTitleHeader,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useCsrfToken, useIsSearchScopeChildrenAsDefault, useCurrentPageId, useCurrentPathname,
   useIsSlackConfigured, useRendererConfig,
   useIsSlackConfigured, useRendererConfig,
-  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useCustomizedLogoSrc, useIsContainerFluid, useIsNotCreatable,
+  useEditorConfig, useIsAllReplyShown, useIsUploadableFile, useIsUploadableImage, useIsContainerFluid, useIsNotCreatable,
 } from '../stores/context';
 } from '../stores/context';
 
 
 import { NextPageWithLayout } from './_app.page';
 import { NextPageWithLayout } from './_app.page';
@@ -265,7 +265,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
   useHasDraftOnHackmd(pageWithMeta?.data.hasDraftOnHackmd ?? false);
   useCurrentPathname(props.currentPathname);
   useCurrentPathname(props.currentPathname);
 
 
-  useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
+  useSWRxCurrentPage(pageWithMeta?.data ?? null); // store initial data
 
 
   useEditingMarkdown(pageWithMeta?.data.revision?.body);
   useEditingMarkdown(pageWithMeta?.data.revision?.body);
 
 
@@ -328,7 +328,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
         <header className="py-0 position-relative">
           <div id="grw-subnav-container">
           <div id="grw-subnav-container">
-            <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
+            <GrowiContextualSubNavigation currentPage={pageWithMeta?.data} isLinkSharingDisabled={props.disableLinkSharing} />
           </div>
           </div>
         </header>
         </header>
         <div className="d-edit-none">
         <div className="d-edit-none">

+ 2 - 2
packages/app/src/pages/_app.page.tsx

@@ -11,7 +11,7 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import {
 import {
-  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useCustomizedLogoSrc,
+  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo,
 } from '~/stores/context';
 } from '~/stores/context';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 
 
@@ -63,7 +63,7 @@ function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
   useSiteUrl(commonPageProps.siteUrl);
   useSiteUrl(commonPageProps.siteUrl);
   useConfidential(commonPageProps.confidential);
   useConfidential(commonPageProps.confidential);
   useGrowiVersion(commonPageProps.growiVersion);
   useGrowiVersion(commonPageProps.growiVersion);
-  useCustomizedLogoSrc(commonPageProps.customizedLogoSrc);
+  useIsDefaultLogo(commonPageProps.isDefaultLogo);
 
 
   // Use the layout defined at the page level, if available
   // Use the layout defined at the page level, if available
   const getLayout = Component.getLayout ?? (page => page);
   const getLayout = Component.getLayout ?? (page => page);

+ 4 - 1
packages/app/src/pages/admin/customize.page.tsx

@@ -10,7 +10,7 @@ import { Container, Provider } from 'unstated';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CommonProps, generateCustomTitle } from '~/pages/utils/commons';
 import { CommonProps, generateCustomTitle } from '~/pages/utils/commons';
-import { useCustomizeTitle, useCurrentUser } from '~/stores/context';
+import { useCustomizeTitle, useCurrentUser, useIsCustomizedLogoUploaded } from '~/stores/context';
 
 
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 import { retrieveServerSideProps } from '../../utils/admin-page-util';
 
 
@@ -20,6 +20,7 @@ const CustomizeSettingContents = dynamic(() => import('~/components/Admin/Custom
 
 
 type Props = CommonProps & {
 type Props = CommonProps & {
   customizeTitle: string,
   customizeTitle: string,
+  isCustomizedLogoUploaded: boolean,
 };
 };
 
 
 
 
@@ -27,6 +28,7 @@ const AdminCustomizeSettingsPage: NextPage<Props> = (props) => {
   const { t } = useTranslation('admin');
   const { t } = useTranslation('admin');
   useCustomizeTitle(props.customizeTitle);
   useCustomizeTitle(props.customizeTitle);
   useCurrentUser(props.currentUser ?? null);
   useCurrentUser(props.currentUser ?? null);
+  useIsCustomizedLogoUploaded(props.isCustomizedLogoUploaded);
 
 
   const componentTitle = t('customize_settings.customize_settings');
   const componentTitle = t('customize_settings.customize_settings');
   const pageTitle = generateCustomTitle(props, componentTitle);
   const pageTitle = generateCustomTitle(props, componentTitle);
@@ -57,6 +59,7 @@ const injectServerConfigurations = async(context: GetServerSidePropsContext, pro
   const { crowi } = req;
   const { crowi } = req;
 
 
   props.customizeTitle = crowi.configManager.getConfig('crowi', 'customize:title');
   props.customizeTitle = crowi.configManager.getConfig('crowi', 'customize:title');
+  props.isCustomizedLogoUploaded = await crowi.attachmentService.isBrandLogoExist();
 };
 };
 
 
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {
 export const getServerSideProps: GetServerSideProps = async(context: GetServerSidePropsContext) => {

+ 24 - 4
packages/app/src/pages/share/[[...path]].page.tsx

@@ -1,12 +1,13 @@
 import React from 'react';
 import React from 'react';
 
 
-import { IUserHasId } from '@growi/core';
+import { IUserHasId, IPagePopulatedToShowRevision } from '@growi/core';
 import {
 import {
   GetServerSideProps, GetServerSidePropsContext,
   GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
 } from 'next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 import Head from 'next/head';
+import superjson from 'superjson';
 
 
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { MainPane } from '~/components/Layout/MainPane';
 import { MainPane } from '~/components/Layout/MainPane';
@@ -19,6 +20,7 @@ import { SupportedAction, SupportedActionType } from '~/interfaces/activity';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { CrowiRequest } from '~/interfaces/crowi-request';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { RendererConfig } from '~/interfaces/services/renderer';
 import { IShareLinkHasId } from '~/interfaces/share-link';
 import { IShareLinkHasId } from '~/interfaces/share-link';
+import type { PageDocument } from '~/server/models/page';
 import {
 import {
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
@@ -38,6 +40,7 @@ const ShareLinkAlert = dynamic(() => import('~/components/Page/ShareLinkAlert'),
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 const ForbiddenPage = dynamic(() => import('~/components/ForbiddenPage'), { ssr: false });
 
 
 type Props = CommonProps & {
 type Props = CommonProps & {
+  shareLinkRelatedPage?: IShareLinkRelatedPage,
   shareLink?: IShareLinkHasId,
   shareLink?: IShareLinkHasId,
   isExpired: boolean,
   isExpired: boolean,
   disableLinkSharing: boolean,
   disableLinkSharing: boolean,
@@ -48,6 +51,23 @@ type Props = CommonProps & {
   rendererConfig: RendererConfig,
   rendererConfig: RendererConfig,
 };
 };
 
 
+type IShareLinkRelatedPage = IPagePopulatedToShowRevision & PageDocument;
+
+superjson.registerCustom<IShareLinkRelatedPage, string>(
+  {
+    isApplicable: (v): v is IShareLinkRelatedPage => {
+      return v != null
+        && v.toObject != null
+        && v.lastUpdateUser != null
+        && v.creator != null
+        && v.revision != null;
+    },
+    serialize: (v) => { return superjson.stringify(v.toObject()) },
+    deserialize: (v) => { return superjson.parse(v) },
+  },
+  'IShareLinkRelatedPageTransformer',
+);
+
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useIsSearchPage(false);
   useIsSearchPage(false);
   useShareLinkId(props.shareLink?._id);
   useShareLinkId(props.shareLink?._id);
@@ -91,7 +111,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
 
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
         <header className="py-0 position-relative">
-          {isShowSharedPage && <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />}
+          {isShowSharedPage && <GrowiContextualSubNavigation currentPage={props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />}
         </header>
         </header>
 
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>
@@ -128,7 +148,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
           {(isShowSharedPage && shareLink != null) && (
           {(isShowSharedPage && shareLink != null) && (
             <>
             <>
               <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
               <ShareLinkAlert expiredAt={shareLink.expiredAt} createdAt={shareLink.createdAt} />
-              <Page />
+              <Page currentPage={props.shareLinkRelatedPage} />
             </>
             </>
           )}
           )}
         </MainPane>
         </MainPane>
@@ -227,7 +247,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
     const ShareLinkModel = crowi.model('ShareLink');
     const ShareLinkModel = crowi.model('ShareLink');
     const shareLink = await ShareLinkModel.findOne({ _id: params.linkId }).populate('relatedPage');
     const shareLink = await ShareLinkModel.findOne({ _id: params.linkId }).populate('relatedPage');
     if (shareLink != null) {
     if (shareLink != null) {
-      await shareLink.relatedPage.populateDataToShowRevision();
+      props.shareLinkRelatedPage = await shareLink.relatedPage.populateDataToShowRevision();
       props.isExpired = shareLink.isExpired();
       props.isExpired = shareLink.isExpired();
       props.shareLink = shareLink.toObject();
       props.shareLink = shareLink.toObject();
     }
     }

+ 5 - 4
packages/app/src/pages/utils/commons.ts

@@ -20,7 +20,7 @@ export type CommonProps = {
   growiVersion: string,
   growiVersion: string,
   isMaintenanceMode: boolean,
   isMaintenanceMode: boolean,
   redirectDestination: string | null,
   redirectDestination: string | null,
-  customizedLogoSrc?: string,
+  isDefaultLogo: boolean,
   currentUser?: IUser,
   currentUser?: IUser,
 } & Partial<SSRConfig>;
 } & Partial<SSRConfig>;
 
 
@@ -30,7 +30,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   const req = context.req as CrowiRequest<IUserHasId & any>;
   const req = context.req as CrowiRequest<IUserHasId & any>;
   const { crowi, user } = req;
   const { crowi, user } = req;
   const {
   const {
-    appService, configManager, customizeService,
+    appService, configManager, customizeService, attachmentService,
   } = crowi;
   } = crowi;
 
 
   const url = new URL(context.resolvedUrl, 'http://example.com');
   const url = new URL(context.resolvedUrl, 'http://example.com');
@@ -45,7 +45,8 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
 
 
   // eslint-disable-next-line max-len, no-nested-ternary
   // eslint-disable-next-line max-len, no-nested-ternary
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
-  const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo');
+  const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
+  const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
 
 
   const props: CommonProps = {
   const props: CommonProps = {
     namespacesRequired: ['translation'],
     namespacesRequired: ['translation'],
@@ -59,8 +60,8 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     growiVersion: crowi.version,
     growiVersion: crowi.version,
     isMaintenanceMode,
     isMaintenanceMode,
     redirectDestination,
     redirectDestination,
-    customizedLogoSrc: isDefaultLogo ? null : configManager.getConfig('crowi', 'customize:customizedLogoSrc'),
     currentUser,
     currentUser,
+    isDefaultLogo,
   };
   };
 
 
   return { props };
   return { props };

+ 0 - 29
packages/app/src/server/crowi/dev.js

@@ -8,8 +8,6 @@ import loggerFactory from '~/utils/logger';
 
 
 import nextFactory from '../routes/next';
 import nextFactory from '../routes/next';
 
 
-const swig = require('swig-templates');
-
 const logger = loggerFactory('growi:crowi:dev');
 const logger = loggerFactory('growi:crowi:dev');
 
 
 
 
@@ -99,33 +97,6 @@ class CrowiDev {
     this.setupNextjsStackFrame(app);
     this.setupNextjsStackFrame(app);
   }
   }
 
 
-  // setupHeaderDebugger(app) {
-  //   logger.debug('setupHeaderDebugger');
-
-  //   app.use((req, res, next) => {
-  //     onHeaders(res, () => {
-  //       logger.debug('HEADERS GOING TO BE WRITTEN');
-  //     });
-  //     next();
-  //   });
-  // }
-
-  // setupBrowserSync(app) {
-  //   logger.debug('setupBrowserSync');
-
-  //   const browserSync = require('browser-sync');
-  //   const bs = browserSync.create().init({
-  //     logSnippet: false,
-  //     notify: false,
-  //     files: [
-  //       `${this.crowi.viewsDir}/**/*.html`,
-  //       `${this.crowi.publicDir}/**/*.js`,
-  //       `${this.crowi.publicDir}/**/*.css`,
-  //     ],
-  //   });
-  //   app.use(require('connect-browser-sync')(bs));
-  // }
-
   setupNextBundleAnalyzer(app) {
   setupNextBundleAnalyzer(app) {
     const next = nextFactory(this.crowi);
     const next = nextFactory(this.crowi);
     app.use('/analyze', express.static(path.resolve(__dirname, '../../../.next/analyze')));
     app.use('/analyze', express.static(path.resolve(__dirname, '../../../.next/analyze')));

+ 0 - 41
packages/app/src/server/crowi/express-init.js

@@ -2,8 +2,6 @@ import { manifestPath as presetThemesManifestPath } from '@growi/preset-themes';
 import csrf from 'csurf';
 import csrf from 'csurf';
 import mongoose from 'mongoose';
 import mongoose from 'mongoose';
 
 
-import { i18n, localePath } from '^/config/next-i18next.config';
-
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 
 
@@ -22,12 +20,6 @@ module.exports = function(crowi, app) {
   const expressSession = require('express-session');
   const expressSession = require('express-session');
   const flash = require('connect-flash');
   const flash = require('connect-flash');
   const mongoSanitize = require('express-mongo-sanitize');
   const mongoSanitize = require('express-mongo-sanitize');
-  const swig = require('swig-templates');
-  const webpackAssets = require('express-webpack-assets');
-  // const i18next = require('i18next');
-  // const i18nFsBackend = require('i18next-node-fs-backend');
-  // const i18nSprintf = require('i18next-sprintf-postprocessor');
-  // const i18nMiddleware = require('i18next-express-middleware');
 
 
   const promster = require('../middlewares/promster')(crowi, app);
   const promster = require('../middlewares/promster')(crowi, app);
   const registerSafeRedirect = require('../middlewares/safe-redirect')();
   const registerSafeRedirect = require('../middlewares/safe-redirect')();
@@ -35,33 +27,9 @@ module.exports = function(crowi, app) {
   const autoReconnectToS2sMsgServer = require('../middlewares/auto-reconnect-to-s2s-msg-server')(crowi);
   const autoReconnectToS2sMsgServer = require('../middlewares/auto-reconnect-to-s2s-msg-server')(crowi);
 
 
   const avoidSessionRoutes = require('../routes/avoid-session-routes');
   const avoidSessionRoutes = require('../routes/avoid-session-routes');
-  // const i18nUserSettingDetector = require('../util/i18nUserSettingDetector');
 
 
   const env = crowi.node_env;
   const env = crowi.node_env;
 
 
-  // const lngDetector = new i18nMiddleware.LanguageDetector();
-  // lngDetector.addDetector(i18nUserSettingDetector);
-
-  // i18next
-  //   .use(lngDetector)
-  //   .use(i18nFsBackend)
-  //   .use(i18nSprintf)
-  //   .init({
-  //     // debug: true,
-  //     fallbackLng: ['en_US'],
-  //     whitelist: i18n.locales,
-  //     backend: {
-  //       loadPath: `${localePath}/{{lng}}/translation.json`,
-  //     },
-  //     detection: {
-  //       order: ['userSettingDetector', 'header', 'navigator'],
-  //     },
-  //     overloadTranslationOptionHandler: i18nSprintf.overloadTranslationOptionHandler,
-
-  //     // change nsSeparator from ':' to '::' because ':' is used in config keys and these are used in i18n keys
-  //     nsSeparator: '::',
-  //   });
-
   app.use(compression());
   app.use(compression());
 
 
 
 
@@ -122,10 +90,6 @@ module.exports = function(crowi, app) {
   ));
   ));
   app.use('/static/plugins', express.static(path.resolve(__dirname, '../../../tmp/plugins')));
   app.use('/static/plugins', express.static(path.resolve(__dirname, '../../../tmp/plugins')));
 
 
-  app.engine('html', swig.renderFile);
-  // app.set('view cache', false);  // Default: true in production, otherwise undefined. -- 2017.07.04 Yuki Takei
-  app.set('view engine', 'html');
-  app.set('views', crowi.viewsDir);
   app.use(methodOverride());
   app.use(methodOverride());
 
 
   // inject rawBody to req
   // inject rawBody to req
@@ -173,11 +137,6 @@ module.exports = function(crowi, app) {
   app.use(injectCurrentuserToLocalvars);
   app.use(injectCurrentuserToLocalvars);
   app.use(autoReconnectToS2sMsgServer);
   app.use(autoReconnectToS2sMsgServer);
 
 
-  const middlewares = require('../util/middlewares')(crowi, app);
-  app.use(middlewares.swigFilters(swig));
-  app.use(middlewares.swigFunctions());
-
-  // app.use(i18nMiddleware.handle(i18next));
   // TODO: Remove this workaround implementation when i18n works correctly.
   // TODO: Remove this workaround implementation when i18n works correctly.
   //       For now, req.t returns string given to req.t(string)
   //       For now, req.t returns string given to req.t(string)
   app.use((req, res, next) => {
   app.use((req, res, next) => {

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است