Преглед изворни кода

Merge branch 'support/apply-nextjs-2' into imprv/98451-PageEditorByHackmd.jsx-FC-TS

yuken пре 3 година
родитељ
комит
6c6ff40762
100 измењених фајлова са 686 додато и 986 уклоњено
  1. 22 0
      .eslintrc.js
  2. 27 18
      .github/workflows/ci-app-prod.yml
  3. 17 8
      .github/workflows/ci-app.yml
  4. 10 6
      .github/workflows/reusable-app-prod.yml
  5. 3 0
      .gitignore
  6. 45 25
      .vscode/launch.json
  7. 16 11
      package.json
  8. 0 1
      packages/app/.env.development
  9. 5 0
      packages/app/.eslintignore
  10. 12 3
      packages/app/.eslintrc.js
  11. 5 2
      packages/app/.gitignore
  12. 1 0
      packages/app/_obsolete/config/webpack.common.js
  13. 1 0
      packages/app/_obsolete/config/webpack.dev.dll.js
  14. 1 0
      packages/app/_obsolete/config/webpack.dev.js
  15. 1 0
      packages/app/_obsolete/config/webpack.prod.js
  16. 9 9
      packages/app/_obsolete/src/client/admin.jsx
  17. 4 15
      packages/app/_obsolete/src/client/app.jsx
  18. 2 5
      packages/app/_obsolete/src/client/base.jsx
  19. 5 0
      packages/app/_obsolete/src/client/boot.js
  20. 0 0
      packages/app/_obsolete/src/client/installer.jsx
  21. 0 0
      packages/app/_obsolete/src/client/nologin.jsx
  22. 0 0
      packages/app/_obsolete/src/client/plugin.js
  23. 5 0
      packages/app/_obsolete/src/util/i18n.js
  24. 0 0
      packages/app/_obsolete/src/util/old-ios.js
  25. 0 56
      packages/app/bin/generate-plugin-definitions-source.ts
  26. 6 1
      packages/app/config/migrate-mongo-config.js
  27. 27 0
      packages/app/config/next-i18next.config.ts
  28. 1 2
      packages/app/docker/Dockerfile
  29. 8 1
      packages/app/jest.config.js
  30. 5 0
      packages/app/next-env.d.ts
  31. 110 0
      packages/app/next.config.js
  32. 60 72
      packages/app/package.json
  33. 0 0
      packages/app/public/static/locales/en_US/admin.json
  34. 10 1
      packages/app/public/static/locales/en_US/translation.json
  35. 0 2
      packages/app/public/static/locales/index.js
  36. 0 0
      packages/app/public/static/locales/ja_JP/admin.json
  37. 10 1
      packages/app/public/static/locales/ja_JP/translation.json
  38. 0 0
      packages/app/public/static/locales/zh_CN/admin.json
  39. 10 1
      packages/app/public/static/locales/zh_CN/translation.json
  40. 0 7
      packages/app/resource/cdn-manifests.js
  41. 0 3
      packages/app/resource/locales/en_US/sandbox.md
  42. 1 1
      packages/app/resource/locales/en_US/welcome.md
  43. 0 3
      packages/app/resource/locales/ja_JP/sandbox.md
  44. 1 1
      packages/app/resource/locales/ja_JP/welcome.md
  45. 0 3
      packages/app/resource/locales/zh_CN/sandbox.md
  46. 1 1
      packages/app/resource/locales/zh_CN/welcome.md
  47. 0 9
      packages/app/src/client/boot.js
  48. 0 2
      packages/app/src/client/legacy/crowi.js
  49. 0 0
      packages/app/src/client/legacy/thirdparty-js/waves.js
  50. 1 1
      packages/app/src/client/models/BootstrapGrid.js
  51. 2 2
      packages/app/src/client/models/Linker.js
  52. 4 4
      packages/app/src/client/models/MarkdownTable.js
  53. 5 4
      packages/app/src/client/services/AdminAppContainer.js
  54. 5 6
      packages/app/src/client/services/AdminBasicSecurityContainer.js
  55. 4 53
      packages/app/src/client/services/AdminCustomizeContainer.js
  56. 5 0
      packages/app/src/client/services/AdminExternalAccountsContainer.js
  57. 5 5
      packages/app/src/client/services/AdminGeneralSecurityContainer.js
  58. 5 2
      packages/app/src/client/services/AdminGitHubSecurityContainer.js
  59. 5 2
      packages/app/src/client/services/AdminGoogleSecurityContainer.js
  60. 5 0
      packages/app/src/client/services/AdminHomeContainer.js
  61. 6 4
      packages/app/src/client/services/AdminImportContainer.js
  62. 6 4
      packages/app/src/client/services/AdminLdapSecurityContainer.js
  63. 5 0
      packages/app/src/client/services/AdminLocalSecurityContainer.js
  64. 6 4
      packages/app/src/client/services/AdminMarkDownContainer.js
  65. 5 0
      packages/app/src/client/services/AdminNotificationContainer.js
  66. 6 6
      packages/app/src/client/services/AdminOidcSecurityContainer.js
  67. 6 6
      packages/app/src/client/services/AdminSamlSecurityContainer.js
  68. 6 3
      packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  69. 1 24
      packages/app/src/client/services/AdminSocketIoContainer.js
  70. 6 6
      packages/app/src/client/services/AdminTwitterSecurityContainer.js
  71. 0 197
      packages/app/src/client/services/AdminUserGroupDetailContainer.js
  72. 5 4
      packages/app/src/client/services/AdminUsersContainer.js
  73. 12 13
      packages/app/src/client/services/AppContainer.js
  74. 34 51
      packages/app/src/client/services/ContextExtractor.tsx
  75. 0 24
      packages/app/src/client/services/EditorContainer.js
  76. 1 3
      packages/app/src/client/services/PageContainer.js
  77. 0 50
      packages/app/src/client/services/SocketIoContainer.js
  78. 1 2
      packages/app/src/client/services/page-operation.ts
  79. 2 22
      packages/app/src/client/util/apiv1-client.ts
  80. 4 29
      packages/app/src/client/util/apiv3-client.ts
  81. 0 73
      packages/app/src/client/util/color-scheme.js
  82. 1 5
      packages/app/src/client/util/locale-utils.ts
  83. 11 9
      packages/app/src/components/Admin/AdminHome/AdminHome.jsx
  84. 1 1
      packages/app/src/components/Admin/AdminHome/InstalledPluginTable.jsx
  85. 24 18
      packages/app/src/components/Admin/App/AppSetting.jsx
  86. 0 51
      packages/app/src/components/Admin/App/AppSettingsPage.jsx
  87. 23 2
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  88. 1 1
      packages/app/src/components/Admin/App/AwsSetting.jsx
  89. 1 1
      packages/app/src/components/Admin/App/ConfirmModal.tsx
  90. 2 2
      packages/app/src/components/Admin/App/FileUploadSetting.tsx
  91. 1 1
      packages/app/src/components/Admin/App/GcsSettings.jsx
  92. 1 1
      packages/app/src/components/Admin/App/MailSetting.tsx
  93. 1 1
      packages/app/src/components/Admin/App/MaintenanceMode.tsx
  94. 1 1
      packages/app/src/components/Admin/App/PluginSetting.tsx
  95. 1 2
      packages/app/src/components/Admin/App/SesSetting.tsx
  96. 1 1
      packages/app/src/components/Admin/App/SiteUrlSetting.tsx
  97. 2 3
      packages/app/src/components/Admin/App/SmtpSetting.tsx
  98. 1 1
      packages/app/src/components/Admin/App/V5PageMigration.tsx
  99. 2 0
      packages/app/src/components/Admin/AuditLog/DateRangePicker.tsx
  100. 12 11
      packages/app/src/components/Admin/Common/AdminNavigation.jsx

+ 22 - 0
.eslintrc.js

@@ -35,6 +35,18 @@ module.exports = {
             group: 'parent',
             position: 'before',
           },
+          {
+            pattern: '*.css',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
+          {
+            pattern: '*.scss',
+            group: 'type',
+            patternOptions: { matchBase: true },
+            position: 'after',
+          },
         ],
         alphabetize: {
           order: 'asc',
@@ -44,6 +56,7 @@ module.exports = {
       },
     ],
     '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/explicit-module-boundary-types': 'off',
     indent: [
       'error',
       2,
@@ -68,4 +81,13 @@ module.exports = {
       },
     ]],
   },
+  overrides: [
+    {
+      // enable the rule specifically for TypeScript files
+      files: ['*.ts', '*.tsx'],
+      rules: {
+        '@typescript-eslint/explicit-module-boundary-types': ['error'],
+      },
+    },
+  ],
 };

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

@@ -3,7 +3,8 @@ name: Node CI for app production
 on:
   push:
     branches:
-      - master
+      # - master
+      - support/apply-nextjs-2
     paths:
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
@@ -12,13 +13,16 @@ on:
       - yarn.lock
       - packages/app/**
       - '!packages/app/docker/**'
+      - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/remark-growi-plugin/**
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**
   pull_request:
     branches:
-        - master
+      # - master
+      - support/apply-nextjs-2
     types: [opened, reopened, synchronize]
     paths:
       - .github/workflows/ci-app-prod.yml
@@ -28,7 +32,9 @@ on:
       - yarn.lock
       - packages/app/**
       - '!packages/app/docker/**'
+      - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/remark-growi-plugin/**
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**
@@ -36,7 +42,8 @@ on:
 jobs:
 
   test-prod-node14:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    # uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/master
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/apply-nextjs-2
     with:
       node-version: 14.x
       skip-cypress: true
@@ -45,28 +52,30 @@ jobs:
 
 
   test-prod-node16:
-    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    # uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
+    uses: weseek/growi/.github/workflows/reusable-app-prod.yml@support/apply-nextjs-2
     with:
       node-version: 16.x
-      skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      # skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+      skip-cypress: true
       cypress-report-artifact-name: Cypress report
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 
-  run-reg-suit-node16:
-    needs: [test-prod-node16]
+  # run-reg-suit-node16:
+  #   needs: [test-prod-node16]
 
-    uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
+  #   uses: weseek/growi/.github/workflows/reusable-app-reg-suit.yml@master
 
-    if: always()
+  #   if: always()
 
-    with:
-      node-version: 16.x
-      skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
-      cypress-report-artifact-name: Cypress report
-    secrets:
-      REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
-      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
-      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
-      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
+  #   with:
+  #     node-version: 16.x
+  #     skip-reg-suit: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
+  #     cypress-report-artifact-name: Cypress report
+  #   secrets:
+  #     REG_NOTIFY_GITHUB_PLUGIN_CLIENTID: ${{ secrets.REG_NOTIFY_GITHUB_PLUGIN_CLIENTID }}
+  #     AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+  #     AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+  #     SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

+ 17 - 8
.github/workflows/ci-app.yml

@@ -14,7 +14,9 @@ on:
       - yarn.lock
       - packages/app/**
       - '!packages/app/docker/**'
+      - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/remark-growi-plugin/**
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-*/**
@@ -53,10 +55,10 @@ jobs:
 
       - name: lerna run lint for plugins
         run: |
-          yarn lerna run lint --scope @growi/plugin-*
+          yarn lerna run lint --scope @growi/remark-growi-plugin --scope @growi/plugin-*
       - name: lerna run lint for app
         run: |
-          yarn lerna run lint --scope @growi/app --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/ui
+          yarn lerna run lint --scope @growi/app --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/slack --scope @growi/ui
 
       - name: Slack Notification
         uses: weseek/ghaction-slack-notification@master
@@ -105,7 +107,11 @@ jobs:
         run: |
           npx lerna bootstrap -- --frozen-lockfile
 
-      - name: yarn test
+      - name: lerna run test for plugins
+        run: |
+          yarn lerna run test --scope @growi/remark-growi-plugin --scope @growi/plugin-*
+
+      - name: Test app
         working-directory: ./packages/app
         run: |
           yarn test:ci --selectProjects unit server ; yarn test:ci --selectProjects server-v5
@@ -116,7 +122,9 @@ jobs:
         uses: actions/upload-artifact@v3
         with:
           name: Coverage Report
-          path: packages/app/coverage
+          path: |
+            packages/app/coverage
+            packages/remark-growi-plugin/coverage
 
       - name: Slack Notification
         uses: weseek/ghaction-slack-notification@master
@@ -150,16 +158,17 @@ jobs:
           cache: 'yarn'
           cache-dependency-path: '**/yarn.lock'
 
-      - name: Cache/Restore node_modules
+      - name: Cache/Restore node_modules and next cache files
         id: cache-dependencies
         uses: actions/cache@v3
         with:
           path: |
             **/node_modules
-          key: node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/app/package.json') }}
+            ${{ github.workspace }}/packages/app/.next/cache
+          key: dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('packages/app/package.json') }}
           restore-keys: |
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
-            node_modules-${{ runner.OS }}-node${{ matrix.node-version }}-
+            dev-${{ runner.OS }}-node${{ matrix.node-version }}-${{ hashFiles('**/yarn.lock') }}-
+            dev-${{ runner.OS }}-node${{ matrix.node-version }}-
 
       - name: lerna bootstrap
         run: |

+ 10 - 6
.github/workflows/reusable-app-prod.yml

@@ -49,6 +49,7 @@ jobs:
     - name: Remove unnecessary packages
       run: |
         rm -rf packages/slackbot-proxy
+        rm -f "node_modules/@growi/slackbot-proxy"
 
     - name: Build
       run: |
@@ -59,29 +60,31 @@ jobs:
     - name: Archive production files
       id: archive-prod-files
       run: |
-        tar -cf production.tar \
+        tar -zcf production.tar.gz \
           package.json \
+          packages/app/.next \
           packages/app/config \
           packages/app/public \
           packages/app/resource \
           packages/app/tmp \
-          packages/app/migrate-mongo-config.js \
           packages/app/.env.production* \
           packages/*/package.json \
           packages/*/dist
-        echo ::set-output name=file::production.tar
+        echo ::set-output name=file::production.tar.gz
 
     - name: Upload production files as artifact
       uses: actions/upload-artifact@v3
       with:
-        name: Production Files
+        name: Production Files (node${{ inputs.node-version }})
         path: ${{ steps.archive-prod-files.outputs.file }}
 
     - name: Upload report as artifact
       uses: actions/upload-artifact@v3
       with:
         name: Bundle Analyzing Report
-        path: packages/app/report/bundle-analyzer.html
+        path: |
+          packages/app/.next/analyze/client.html
+          packages/app/.next/analyze/server.html
 
     - name: Slack Notification
       uses: weseek/ghaction-slack-notification@master
@@ -143,6 +146,7 @@ jobs:
     - name: Remove unnecessary packages
       run: |
         rm -rf packages/slackbot-proxy
+        rm -f "node_modules/@growi/slackbot-proxy"
 
     - name: lerna bootstrap --production
       run: |
@@ -151,7 +155,7 @@ jobs:
     - name: Download production files artifact
       uses: actions/download-artifact@v3
       with:
-        name: Production Files
+        name: Production Files (node${{ inputs.node-version }})
 
     - name: Extract procution files artifact
       run: |

+ 3 - 0
.gitignore

@@ -26,6 +26,9 @@ yarn-error.log*
 .env.test.local
 .env.production.local
 
+# typescript
+*.tsbuildinfo
+
 # IDE, dev #
 .idea
 *.orig

+ 45 - 25
.vscode/launch.json

@@ -1,37 +1,57 @@
 {
-    // IntelliSense を使用して利用可能な属性を学べます。
-    // 既存の属性の説明をホバーして表示します。
-    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
     "version": "0.2.0",
     "configurations": [
       {
-        "type": "node",
+        "type": "pwa-node",
         "request": "attach",
         "name": "Debug: Attach Debugger to Server",
-        "port": 9229
+        "port": 9229,
+        "cwd": "${workspaceFolder}/packages/app",
+        "sourceMapPathOverrides": {
+          "webpack://@growi/app/*": "${workspaceFolder}/packages/app/*"
+        }
+      },
+      {
+        "type": "pwa-node",
+        "request": "launch",
+        "name": "Debug: Current File",
+        "skipFiles": [
+          "<node_internals>/**"
+        ],
+        "console": "integratedTerminal",
+        "cwd": "${fileDirname}",
+        "runtimeExecutable": "yarn",
+        "runtimeArgs": [
+          "ts-node",
+          "${file}"
+        ]
       },
       {
-        "type": "node",
+        "type": "pwa-node",
         "request": "launch",
         "name": "Debug: Server",
         "cwd": "${workspaceFolder}/packages/app",
-        "runtimeExecutable": "npm",
+        "runtimeExecutable": "yarn",
         "runtimeArgs": [
-          "run",
-          "dev:server"
+          "dev"
+        ],
+        "skipFiles": [
+          "<node_internals>/**"
         ],
-        "port": 9229,
         "restart": true,
         "console": "integratedTerminal",
-        "internalConsoleOptions": "neverOpen"
+        "internalConsoleOptions": "neverOpen",
+        "sourceMapPathOverrides": {
+          "webpack://@growi/app/*": "${workspaceFolder}/packages/app/*"
+        }
       },
       {
-        "type": "chrome",
+        "type": "pwa-chrome",
         "request": "launch",
         "name": "Debug: Chrome",
         "sourceMaps": true,
         "sourceMapPathOverrides": {
-          "webpack:///*": "${workspaceFolder}/packages/app/*"
+          "webpack://_N_E/*": "${workspaceFolder}/packages/app/*"
         },
         "webRoot": "${workspaceFolder}/packages/app/public",
         "url": "http://localhost:3000"
@@ -41,32 +61,32 @@
         "request": "launch",
         "name": "Debug: Firefox",
         "reAttach": true,
-        "url": "http://localhost:3000",
         "webRoot": "${workspaceFolder}/packages/app/public",
+        "url": "http://localhost:3000",
         "pathMappings": [
           {
-            "url": "webpack:///core",
-            "path": "${workspaceFolder}/packages/core"
+            "url": "webpack://_n_e/src",
+            "path": "${workspaceFolder}/packages/app/src"
           },
           {
-            "url": "webpack:///plugin-attachment-refs",
-            "path": "${workspaceFolder}/packages/plugin-attachment-refs"
+            "url": "webpack://_n_e/core",
+            "path": "${workspaceFolder}/packages/core"
           },
           {
-            "url": "webpack:///plugin-pukiwiki-like-linker",
-            "path": "${workspaceFolder}/packages/plugin-pukiwiki-like-linker"
+            "url": "webpack://_n_e/plugin-attachment-refs",
+            "path": "${workspaceFolder}/packages/plugin-attachment-refs"
           },
           {
-            "url": "webpack:///plugin-lsx",
+            "url": "webpack://_n_e/plugin-lsx",
             "path": "${workspaceFolder}/packages/plugin-lsx"
           },
           {
-            "url": "webpack:///ui",
-            "path": "${workspaceFolder}/packages/ui"
+            "url": "webpack://_n_e/slack",
+            "path": "${workspaceFolder}/packages/app/slack"
           },
           {
-            "url": "webpack:///src",
-            "path": "${workspaceFolder}/packages/app/src"
+            "url": "webpack://_n_e/ui",
+            "path": "${workspaceFolder}/packages/ui"
           },
           {
             "url": "http://localhost:3000",

+ 16 - 11
package.json

@@ -48,24 +48,29 @@
     "cross-env": "^7.0.0",
     "dotenv-flow": "^3.2.0",
     "npm-run-all": "^4.1.5",
+    "ts-deepmerge": "^3.0.0",
     "tslib": "^2.3.1"
   },
   "devDependencies": {
+    "@swc/core": "^1.2.239",
+    "@swc/helpers": "^0.4.7",
     "@testing-library/cypress": "^8.0.2",
+    "@types/css-modules": "^1.0.2",
     "@types/jest": "^26.0.22",
-    "@types/node": "^14.14.35",
+    "@types/node": "^17.0.43",
     "@types/rewire": "^2.5.28",
-    "@typescript-eslint/eslint-plugin": "^4.28.5",
-    "@typescript-eslint/parser": "^4.28.5",
+    "@typescript-eslint/eslint-plugin": "^5.0.0",
+    "@typescript-eslint/parser": "^5.0.0",
     "cypress": "^9.2.0",
-    "eslint": "^7.31.0",
+    "eslint": "^8.18.0",
+    "eslint-config-next": "^12.1.6",
     "eslint-config-weseek": "^2.1.0",
-    "eslint-import-resolver-typescript": "^2.4.0",
-    "eslint-plugin-import": "^2.23.4",
-    "eslint-plugin-jest": "^24.3.2",
-    "eslint-plugin-react": "^7.24.0",
-    "eslint-plugin-react-hooks": "^4.2.0",
-    "jest": "^27.0.6",
+    "eslint-import-resolver-typescript": "^3.2.5",
+    "eslint-plugin-import": "^2.26.0",
+    "eslint-plugin-jest": "^26.5.3",
+    "eslint-plugin-react": "^7.30.1",
+    "eslint-plugin-react-hooks": "^4.6.0",
+    "jest": "^28.1.3",
     "jest-date-mock": "^1.0.8",
     "jest-localstorage-mock": "^2.4.14",
     "lerna": "^4.0.0",
@@ -80,7 +85,7 @@
     "shipjs": "^0.24.1",
     "stylelint": "^14.2.0",
     "stylelint-config-recess-order": "^3.0.0",
-    "ts-jest": "^27.0.4",
+    "ts-jest": "^28.0.7",
     "ts-node": "^10.9.1",
     "tsconfig-paths": "^3.9.0",
     "typescript": "~4.7",

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

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

+ 5 - 0
packages/app/.eslintignore

@@ -1,6 +1,11 @@
+/_obsolete/**
 /dist/**
+/transpiled/**
 /public/**
+/config/next-i18next.config.js
 /src/client/legacy/thirdparty-js/**
 /src/client/util/reveal/plugins/markdown.js
 /src/linter-checker/**
+/src/utils/next.config.utils.js
 /tmp/**
+/next-env.d.ts

+ 12 - 3
packages/app/.eslintrc.js

@@ -1,7 +1,6 @@
 module.exports = {
   extends: [
-    'weseek/react',
-    'weseek/typescript',
+    'next/core-web-vitals',
   ],
   plugins: [
     'regex',
@@ -39,9 +38,19 @@ module.exports = {
     '@typescript-eslint/no-var-requires': 'off',
 
     // set 'warn' temporarily -- 2021.08.02 Yuki Takei
-    '@typescript-eslint/explicit-module-boundary-types': ['warn'],
     '@typescript-eslint/no-use-before-define': ['warn'],
     '@typescript-eslint/no-this-alias': ['warn'],
     'jest/no-done-callback': ['warn'],
   },
+  overrides: [
+    {
+      // enable the rule specifically for TypeScript files
+      files: ['*.ts', '*.tsx'],
+      rules: {
+        // '@typescript-eslint/explicit-module-boundary-types': ['error'],
+        // set 'warn' temporarily -- 2022.07.25 Yuki Takei
+        '@typescript-eslint/explicit-module-boundary-types': ['warn'],
+      },
+    },
+  ],
 };

+ 5 - 2
packages/app/.gitignore

@@ -8,14 +8,17 @@ test/cypress/videos
 .reg
 
 # dist
+/build/
 /dist/
 /transpiled/
-/report/
 /public/static/js
 /public/static/styles
 /public/uploads
 /tmp/
-*.d.ts
+
+# transpiled configuration files for production build
+/config/next-i18next.config.js
+/src/utils/next.config.utils.js
 
 # dist (for GROWI v4.x and below)
 /public/*.chunk.js

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

@@ -1,3 +1,4 @@
+/* eslint-disable */
 /**
  * @author: Yuki Takei <yuki@weseek.co.jp>
  */

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

@@ -1,3 +1,4 @@
+/* eslint-disable */
 /**
  * @author: Yuki Takei <yuki@weseek.co.jp>
  */

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

@@ -1,3 +1,4 @@
+/* eslint-disable */
 /**
  * @author: Yuki Takei <yuki@weseek.co.jp>
  */

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

@@ -1,3 +1,4 @@
+/* eslint-disable */
 /**
  * @author: Yuki Takei <yuki@weseek.co.jp>
  */

+ 9 - 9
packages/app/src/client/admin.jsx → packages/app/_obsolete/src/client/admin.jsx

@@ -23,23 +23,23 @@ import AdminSamlSecurityContainer from '~/client/services/AdminSamlSecurityConta
 import AdminSlackIntegrationLegacyContainer from '~/client/services/AdminSlackIntegrationLegacyContainer';
 import AdminSocketIoContainer from '~/client/services/AdminSocketIoContainer';
 import AdminTwitterSecurityContainer from '~/client/services/AdminTwitterSecurityContainer';
-import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
+// import AdminUserGroupDetailContainer from '~/client/services/AdminUserGroupDetailContainer';
 import AdminUsersContainer from '~/client/services/AdminUsersContainer';
 import ContextExtractor from '~/client/services/ContextExtractor';
 import loggerFactory from '~/utils/logger';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
 
 import AdminHome from '../components/Admin/AdminHome/AdminHome';
-import AppSettingsPage from '../components/Admin/App/AppSettingsPage';
+// import AppSettingsPage from '../components/Admin/App/AppSettingsPage';
 import { AuditLogManagement } from '../components/Admin/AuditLogManagement';
 import AdminNavigation from '../components/Admin/Common/AdminNavigation';
 import Customize from '../components/Admin/Customize/Customize';
 import ExportArchiveDataPage from '../components/Admin/ExportArchiveDataPage';
 import FullTextSearchManagement from '../components/Admin/FullTextSearchManagement';
-import ImportDataPage from '../components/Admin/ImportDataPage';
+// import ImportDataPage from '../components/Admin/ImportDataPage';
 import LegacySlackIntegration from '../components/Admin/LegacySlackIntegration/LegacySlackIntegration';
 import ManageExternalAccount from '../components/Admin/ManageExternalAccount';
-import MarkdownSetting from '../components/Admin/MarkdownSetting/MarkDownSetting';
+// import MarkdownSetting from '../components/Admin/MarkdownSetting/MarkDownSetting';
 import ManageGlobalNotification from '../components/Admin/Notification/ManageGlobalNotification';
 import NotificationSetting from '../components/Admin/Notification/NotificationSetting';
 import SecurityManagement from '../components/Admin/Security/SecurityManagement';
@@ -67,7 +67,7 @@ const adminExternalAccountsContainer = new AdminExternalAccountsContainer(appCon
 const adminNotificationContainer = new AdminNotificationContainer(appContainer);
 const adminSlackIntegrationLegacyContainer = new AdminSlackIntegrationLegacyContainer(appContainer);
 const adminMarkDownContainer = new AdminMarkDownContainer(appContainer);
-const adminUserGroupDetailContainer = new AdminUserGroupDetailContainer(appContainer);
+// const adminUserGroupDetailContainer = new AdminUserGroupDetailContainer(appContainer);
 const socketIoContainer = appContainer.getContainer('SocketIoContainer');
 const injectableContainers = [
   appContainer,
@@ -81,7 +81,7 @@ const injectableContainers = [
   adminNotificationContainer,
   adminSlackIntegrationLegacyContainer,
   adminMarkDownContainer,
-  adminUserGroupDetailContainer,
+  // adminUserGroupDetailContainer,
   socketIoContainer,
 ];
 
@@ -94,10 +94,10 @@ logger.info('unstated containers have been initialized');
  */
 Object.assign(componentMappings, {
   'admin-home': <AdminHome />,
-  'admin-app': <AppSettingsPage />,
-  'admin-markdown-setting': <MarkdownSetting />,
+  // 'admin-app': <AppSettingsPage />,
+  // 'admin-markdown-setting': <MarkdownSetting />,
   'admin-customize': <Customize />,
-  'admin-importer': <ImportDataPage />,
+  // 'admin-importer': <ImportDataPage />,
   'admin-export-page': <ExportArchiveDataPage />,
   'admin-notification-setting': <NotificationSetting />,
   'admin-slack-integration': <SlackIntegration />,

+ 4 - 15
packages/app/src/client/app.jsx → packages/app/_obsolete/src/client/app.jsx

@@ -10,7 +10,7 @@ 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 { IdenticalPathPage } from '~/components/IdenticalPathPage';
 import PrivateLegacyPages from '~/components/PrivateLegacyPages';
 import loggerFactory from '~/utils/logger';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
@@ -26,22 +26,19 @@ 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 { Page } from '../components/Page';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
-import FixPageGrantAlert from '../components/Page/FixPageGrantAlert';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
-import TrashPageAlert from '../components/Page/TrashPageAlert';
-import PageComment from '../components/PageComment';
+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 { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
-import TagPage from '../components/TagPage';
 import TrashPageList from '../components/TrashPageList';
 
 import { appContainer, componentMappings } from './base';
@@ -77,14 +74,11 @@ Object.assign(componentMappings, {
   'identical-path-page': <IdenticalPathPage />,
 
   // 'revision-history': <PageHistory pageId={pageId} />,
-  'tags-page': <TagPage />,
 
   'grw-page-status-alert-container': <PageStatusAlert />,
 
   'maintenance-mode-content': <MaintenanceModeContent />,
 
-  'trash-page-alert': <TrashPageAlert />,
-
   'trash-page-list-container': <TrashPageList />,
 
   'not-found-page': <NotFoundPage />,
@@ -116,11 +110,6 @@ if (pageContainer.state.pageId != null) {
 
     'recent-created-icon': <RecentlyCreatedIcon />,
   });
-  if (!pageContainer.state.isEmpty) {
-    Object.assign(componentMappings, {
-      'fix-page-grant-alert': <FixPageGrantAlert />,
-    });
-  }
 }
 if (pageContainer.state.creator != null) {
   Object.assign(componentMappings, {

+ 2 - 5
packages/app/src/client/base.jsx → packages/app/_obsolete/src/client/base.jsx

@@ -3,7 +3,6 @@ import React from 'react';
 import EventEmitter from 'events';
 
 import AppContainer from '~/client/services/AppContainer';
-import SocketIoContainer from '~/client/services/SocketIoContainer';
 import { DescendantsPageListModal } from '~/components/DescendantsPageListModal';
 import PutbackPageModal from '~/components/PutbackPageModal';
 import ShortcutsModal from '~/components/ShortcutsModal';
@@ -13,8 +12,8 @@ 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 { 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';
@@ -35,8 +34,6 @@ window.interceptorManager = new InterceptorManager();
 
 // create unstated container instance
 const appContainer = new AppContainer();
-// eslint-disable-next-line no-unused-vars
-const socketIoContainer = new SocketIoContainer(appContainer);
 
 appContainer.initApp();
 

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

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

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


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


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


+ 5 - 0
packages/app/src/client/util/i18n.js → packages/app/_obsolete/src/util/i18n.js

@@ -1,3 +1,5 @@
+
+/* eslint-disable */
 import i18n from 'i18next';
 import LanguageDetector from 'i18next-browser-languagedetector';
 import { initReactI18next } from 'react-i18next';
@@ -14,6 +16,9 @@ Object.values(locales).forEach((locale) => {
   });
 });
 
+/*
+* 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);
 

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


+ 0 - 56
packages/app/bin/generate-plugin-definitions-source.ts

@@ -1,56 +0,0 @@
-/**
- * the tool for genetion of plugin definitions source code
- *
- * @author Yuki Takei <yuki@weseek.co.jp>
- */
-import fs from 'graceful-fs';
-import normalize from 'normalize-path';
-import swig from 'swig-templates';
-
-import { PluginDefinitionV4 } from '@growi/core';
-
-import PluginUtils from '../src/server/plugins/plugin-utils';
-import loggerFactory from '../src/utils/logger';
-import { resolveFromRoot } from '../src/utils/project-dir-utils';
-
-const logger = loggerFactory('growi:bin:generate-plugin-definitions-source');
-
-
-const pluginUtils = new PluginUtils();
-
-const TEMPLATE = resolveFromRoot('bin/templates/plugin-definitions.js.swig');
-const OUT = resolveFromRoot('tmp/plugins/plugin-definitions.js');
-
-// list plugin names
-const pluginNames: string[] = pluginUtils.listPluginNames();
-logger.info('Detected plugins: ', pluginNames);
-
-async function main(): Promise<void> {
-
-  // get definitions
-  const definitions: PluginDefinitionV4[] = [];
-  for (const pluginName of pluginNames) {
-    // eslint-disable-next-line no-await-in-loop
-    const definition = await pluginUtils.generatePluginDefinition(pluginName, true);
-    if (definition != null) {
-      definitions.push(definition);
-    }
-  }
-
-  definitions.map((definition) => {
-    // convert backslash to slash
-    definition.entries = definition.entries.map((entryPath) => {
-      return normalize(entryPath);
-    });
-    return definition;
-  });
-
-  const compiledTemplate = swig.compileFile(TEMPLATE);
-  const code = compiledTemplate({ definitions });
-
-  // write
-  fs.writeFileSync(OUT, code);
-
-}
-
-main();

+ 6 - 1
packages/app/migrate-mongo-config.js → packages/app/config/migrate-mongo-config.js

@@ -4,16 +4,21 @@
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
+const isProduction = process.env.NODE_ENV === 'production';
 
 const { URL } = require('url');
 
+const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = isProduction
+  // eslint-disable-next-line import/extensions, import/no-unresolved
+  ? require('../dist/server/util/mongoose-utils')
+  : require('../src/server/util/mongoose-utils');
+
 // get migrationsDir from env var
 const migrationsDir = process.env.MIGRATIONS_DIR;
 if (migrationsDir == null) {
   throw new Error('An env var MIGRATIONS_DIR must be set.');
 }
 
-const { initMongooseGlobalSettings, getMongoUri, mongoOptions } = require('@growi/core');
 
 initMongooseGlobalSettings();
 

+ 27 - 0
packages/app/config/next-i18next.config.ts

@@ -0,0 +1,27 @@
+import path from 'path';
+
+import { isServer, AllLang, Lang } from '@growi/core';
+import I18nextChainedBackend from 'i18next-chained-backend';
+import I18NextHttpBackend from 'i18next-http-backend';
+import I18NextLocalStorageBackend from 'i18next-localstorage-backend';
+
+const isDev = process.env.NODE_ENV === 'development';
+
+export const i18n = {
+  defaultLocale: Lang.en_US,
+  locales: AllLang,
+};
+export const defaultNS = 'translation';
+export const localePath = path.resolve('./public/static/locales');
+
+export const serializeConfig = false;
+export const use = isServer() ? [] : [I18nextChainedBackend];
+export const backend = {
+  backends: isServer() ? [] : [I18NextLocalStorageBackend, I18NextHttpBackend],
+  backendOptions: [
+    // options for i18next-localstorage-backend
+    { expirationTime: isDev ? 0 : 24 * 60 * 60 * 1000 }, // 1 day in production
+    // options for i18next-http-backend
+    { loadPath: '/static/locales/{{lng}}/{{ns}}.json' },
+  ],
+};

+ 1 - 2
packages/app/docker/Dockerfile

@@ -101,7 +101,6 @@ COPY packages/core packages/core
 COPY packages/codemirror-textlint packages/codemirror-textlint
 COPY packages/plugin-attachment-refs packages/plugin-attachment-refs
 COPY packages/plugin-lsx packages/plugin-lsx
-COPY packages/plugin-pukiwiki-like-linker packages/plugin-pukiwiki-like-linker
 COPY packages/slack packages/slack
 COPY packages/ui packages/ui
 
@@ -111,11 +110,11 @@ RUN yarn lerna run build
 # make artifacts
 RUN tar -cf packages.tar \
   package.json \
+  packages/app/.next \
   packages/app/config \
   packages/app/public \
   packages/app/resource \
   packages/app/tmp \
-  packages/app/migrate-mongo-config.js \
   packages/app/.env.production* \
   packages/*/package.json \
   packages/*/dist

+ 8 - 1
packages/app/jest.config.js

@@ -5,7 +5,8 @@
 const MODULE_NAME_MAPPING = {
   '^\\^/(.+)$': '<rootDir>/$1',
   '^~/(.+)$': '<rootDir>/src/$1',
-  '^@growi/(.+)$': '<rootDir>/../$1/src',
+  '^@growi/([^/]+)$': '<rootDir>/../$1/src',
+  '^@growi/([^/]+)/(.+)$': '<rootDir>/../$1/src/$2',
 };
 
 module.exports = {
@@ -20,6 +21,11 @@ module.exports = {
 
       preset: 'ts-jest/presets/js-with-ts',
 
+      // transform ESM to CJS
+      transformIgnorePatterns: [
+        '/node_modules/(?!remark-gfm)/',
+      ],
+
       rootDir: '.',
       roots: ['<rootDir>'],
       testMatch: ['<rootDir>/test/unit/**/*.test.ts', '<rootDir>/test/unit/**/*.test.js'],
@@ -29,6 +35,7 @@ module.exports = {
       // Automatically clear mock calls and instances between every test
       clearMocks: true,
       moduleNameMapper: MODULE_NAME_MAPPING,
+
     },
     {
       displayName: 'server',

+ 5 - 0
packages/app/next-env.d.ts

@@ -0,0 +1,5 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.

+ 110 - 0
packages/app/next.config.js

@@ -0,0 +1,110 @@
+/**
+ * == Notes for production build==
+ * The modules required from this file must be transpiled before running `next build`.
+ *
+ * See: https://github.com/vercel/next.js/discussions/35969#discussioncomment-2522954
+ */
+
+const { withSuperjson } = require('next-superjson');
+const { PHASE_PRODUCTION_BUILD, PHASE_PRODUCTION_SERVER } = require('next/constants');
+
+
+const setupTranspileModules = () => {
+  const eazyLogger = require('eazy-logger');
+  const { listScopedPackages, listPrefixedPackages } = require('./src/utils/next.config.utils');
+
+  // setup logger
+  const logger = eazyLogger.Logger({
+    prefix: '[{green:next.config.js}] ',
+    useLevelPrefixes: false,
+  });
+
+  // define transpiled packages for '@growi/*'
+  const packages = [
+    ...listScopedPackages(['@growi'], { ignorePackageNames: ['@growi/app'] }),
+    // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
+    'react-markdown',
+    'unified',
+    'character-entities-html4',
+    'comma-separated-tokens',
+    'decode-named-character-reference',
+    'hastscript',
+    'html-void-elements',
+    'is-absolute-url',
+    'longest-streak',
+    'property-information',
+    'space-separated-tokens',
+    'stringify-entities',
+    'trim-lines',
+    'trough',
+    'web-namespaces',
+    'vfile',
+    'zwitch',
+    'emoticon',
+    'direction', // for hast-util-select
+    'bcp-47-match', // for hast-util-select
+    ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'unist-']),
+  ];
+
+  logger.info('{bold:Listing scoped packages for transpiling:}');
+  logger.unprefixed('info', `{grey:${JSON.stringify(packages, null, 2)}}`);
+
+  return require('next-transpile-modules')(packages);
+};
+
+
+module.exports = async(phase, { defaultConfig }) => {
+
+  const { i18n, localePath } = require('./config/next-i18next.config');
+
+  /** @type {import('next').NextConfig} */
+  const nextConfig = {
+    // == DOES NOT WORK
+    // see: https://github.com/vercel/next.js/discussions/27876
+    // experimental: { esmExternals: true }, // Prefer loading of ES Modules over CommonJS
+
+    reactStrictMode: true,
+    swcMinify: true,
+    typescript: {
+      tsconfigPath: 'tsconfig.build.client.json',
+    },
+    pageExtensions: ['page.tsx', 'page.ts', 'page.jsx', 'page.js'],
+
+    i18n,
+
+    /** @param config {import('next').NextConfig} */
+    webpack(config, options) {
+      // Avoid "Module not found: Can't resolve 'fs'"
+      // See: https://stackoverflow.com/a/68511591
+      if (!options.isServer) {
+        config.resolve.fallback.fs = false;
+      }
+
+      // See: https://webpack.js.org/configuration/externals/
+      // This provides a way of excluding dependencies from the output bundles
+      config.externals.push('dtrace-provider');
+      config.externals.push('mongoose');
+
+      // setup i18next-hmr
+      if (!options.isServer && options.dev) {
+        const { I18NextHMRPlugin } = require('i18next-hmr/plugin');
+        config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
+      }
+
+      return config;
+    },
+
+  };
+
+  // production server
+  if (phase === PHASE_PRODUCTION_SERVER) {
+    return withSuperjson()(nextConfig);
+  }
+
+  const withTM = setupTranspileModules();
+  const withBundleAnalyzer = require('@next/bundle-analyzer')({
+    enabled: phase === PHASE_PRODUCTION_BUILD || process.env.ANALYZE === 'true',
+  });
+
+  return withBundleAnalyzer(withTM(withSuperjson()(nextConfig)));
+};

+ 60 - 72
packages/app/package.json

@@ -4,33 +4,31 @@
   "license": "MIT",
   "scripts": {
     "//// for production": "",
-    "start": "yarn build && yarn server",
     "build": "run-p build:*",
-    "build:client": "yarn cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
-    "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server.json",
+    "start": "yarn next start",
+    "build:client": "yarn next build",
+    "prebuild:client": "tsc -p tsconfig.build.next.config.json",
+    "build:server": "yarn cross-env NODE_ENV=production tsc -p tsconfig.build.server.json && tsc-alias -p tsconfig.build.server-tsc-alias.json",
+    "postbuild:server": "npx -y shx echo \"Listing files under transpiled\" && npx -y shx ls transpiled && npx -y shx mv transpiled/src dist && npx -y shx cp -r transpiled/config/* config && npx -y shx cp -r src/server/views dist/server/ && npx -y shx rm -rf transpiled",
     "clean": "npx -y shx rm -rf dist transpiled",
     "prebuild": "yarn cross-env NODE_ENV=production run-p clean resources:*",
-    "postbuild": "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",
     "server": "yarn cross-env NODE_ENV=production node -r dotenv-flow/config dist/server/app.js",
     "server:ci": "yarn server --ci",
     "preserver": "yarn cross-env NODE_ENV=production yarn migrate",
-    "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up",
+    "migrate": "node -r dotenv-flow/config node_modules/.bin/migrate-mongo up -f config/migrate-mongo-config.js",
     "//// for development": "",
-    "dev": "run-p dev:client dev:server",
-    "dev:client": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js --progress --watch",
-    "dev:client:nowatch": "yarn cross-env NODE_ENV=development webpack --config config/webpack.dev.js",
-    "dev:server": "yarn cross-env NODE_ENV=development ts-node-dev --inspect -r tsconfig-paths/register -r dotenv-flow/config --transpile-only src/server/app.ts",
-    "predev:client": "yarn cross-env NODE_ENV=development run-p resources:*",
-    "predev:server": "yarn cross-env NODE_ENV=development yarn dev:migrate:up",
+    "dev": "yarn cross-env NODE_ENV=development ts-node-dev -r tsconfig-paths/register -r dotenv-flow/config --inspect --transpile-only src/server/app.ts",
+    "predev": "yarn cross-env NODE_ENV=development run-p resources:* dev:migrate:up",
+    "dev:analyze": "yarn cross-env ANALYZE=true yarn dev",
     "dev:migrate-mongo": "yarn cross-env NODE_ENV=development yarn ts-node node_modules/.bin/migrate-mongo",
     "dev:migrate": "yarn dev:migrate:up",
-    "dev:migrate:create": "yarn dev:migrate-mongo create",
-    "dev:migrate:status": "yarn dev:migrate-mongo status",
-    "dev:migrate:up": "yarn dev:migrate-mongo up",
-    "dev:migrate:down": "yarn dev:migrate-mongo down",
+    "dev:migrate:create": "yarn dev:migrate-mongo create -f config/migrate-mongo-config.js",
+    "dev:migrate:status": "yarn dev:migrate-mongo status -f config/migrate-mongo-config.js",
+    "dev:migrate:up": "yarn dev:migrate-mongo up -f config/migrate-mongo-config.js",
+    "dev:migrate:down": "yarn dev:migrate-mongo down -f config/migrate-mongo-config.js",
     "cy:run": "cypress run --browser chrome",
     "//// for CI": "",
-    "dev:ci": "yarn dev:client:nowatch && yarn dev:server --ci",
+    "dev:ci": "yarn dev --ci",
     "predev:ci": "run-p resources:*",
     "lint:typecheck": "npx -y tsc",
     "lint:eslint": "eslint --quiet \"**/*.{js,jsx,ts,tsx}\"",
@@ -39,7 +37,7 @@
     "lint": "run-p lint:*",
     "test": "cross-env NODE_ENV=test jest --passWithNoTests -- ",
     "test:ci": "cross-env NODE_ENV=test jest",
-    "prelint:eslint": "yarn resources:plugin",
+    "// prelint:eslint": "yarn resources:plugin",
     "prelint:swagger2openapi": "yarn openapi:v3",
     "reg:run": "reg-suit run",
     "//// misc": "",
@@ -47,9 +45,10 @@
     "swagger-jsdoc": "swagger-jsdoc -o tmp/swagger.json -d config/swagger-definition.js",
     "openapi:v3": "yarn cross-env API_VERSION=3 yarn swagger-jsdoc -- \"src/server/routes/apiv3/**/*.js\" \"src/server/models/**/*.js\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
-    "resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
-    "resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
-    "ts-node": "ts-node -r tsconfig-paths/register -r dotenv-flow/config --transpile-only"
+    "resources:dummy": "true",
+    "// resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
+    "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
+    "ts-node": "node -r ts-node/register -r tsconfig-paths/register -r dotenv-flow/config"
   },
   "// comments for dependencies": {
     "openid-client": "Node.js 12 or higher is required for openid-client@3 and above.",
@@ -68,7 +67,6 @@
     "@growi/core": "^5.1.3-RC.0",
     "@growi/plugin-attachment-refs": "^5.1.3-RC.0",
     "@growi/plugin-lsx": "^5.1.3-RC.0",
-    "@growi/plugin-pukiwiki-like-linker": "^5.1.3-RC.0",
     "@growi/slack": "^5.1.3-RC.0",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
@@ -84,6 +82,7 @@
     "axios-retry": "^3.2.4",
     "body-parser": "^1.18.2",
     "browser-bunyan": "^1.8.0",
+    "bson-objectid": "^2.0.3",
     "bunyan": "^1.8.15",
     "check-node-version": "^4.1.0",
     "compression": "^1.7.4",
@@ -91,7 +90,7 @@
     "connect-mongo": "^4.6.0",
     "connect-redis": "^4.0.4",
     "cookie-parser": "^1.4.5",
-    "csrf": "^3.1.0",
+    "csurf": "^1.11.0",
     "date-fns": "^2.23.0",
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
@@ -109,12 +108,13 @@
     "express-webpack-assets": "^0.1.0",
     "extensible-custom-error": "^0.0.7",
     "graceful-fs": "^4.1.11",
+    "hast-util-select": "^5.0.2",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
-    "i18next": "^20.3.2",
-    "i18next-express-middleware": "^2.0.0",
-    "i18next-node-fs-backend": "^2.1.3",
-    "i18next-sprintf-postprocessor": "^0.2.2",
+    "i18next-chained-backend": "^3.0.2",
+    "i18next-http-backend": "^1.4.1",
+    "i18next-localstorage-backend": "^3.1.3",
+    "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "lucene-query-parser": "^1.2.0",
     "md5": "^2.2.1",
@@ -127,6 +127,10 @@
     "mongoose-unique-validator": "^2.0.3",
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
+    "next": "^12.1.6",
+    "next-i18next": "^11.0.0",
+    "next-superjson": "^0.0.4",
+    "next-themes": "^0.2.0",
     "nocache": "^3.0.1",
     "nodemailer": "^6.6.2",
     "nodemailer-ses-transport": "~1.5.0",
@@ -140,20 +144,36 @@
     "passport-local": "^1.0.0",
     "passport-saml": "^3.2.0",
     "passport-twitter": "^1.0.4",
+    "prism-themes": "^1.9.0",
     "prom-client": "^13.0.0",
     "rate-limiter-flexible": "^2.3.7",
+    "react": "^18.2.0",
     "react-card-flip": "^1.0.10",
     "react-datepicker": "^4.7.0",
     "react-dnd": "^14.0.5",
     "react-dnd-html5-backend": "^14.1.0",
+    "react-dom": "^18.2.0",
     "react-image-crop": "^8.3.0",
+    "react-markdown": "^8.0.3",
     "react-multiline-clamp": "^2.0.0",
+    "react-syntax-highlighter": "^15.5.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
+    "rehype-katex": "^6.0.2",
+    "rehype-raw": "^6.1.1",
+    "rehype-sanitize": "^5.0.1",
+    "rehype-slug": "^5.0.1",
+    "rehype-toc": "^3.0.2",
+    "remark-breaks": "^3.0.2",
+    "remark-emoji": "^3.0.2",
+    "remark-gfm": "^3.0.1",
+    "remark-math": "^5.1.1",
+    "remark-wiki-link": "^1.0.4",
     "rimraf": "^3.0.0",
     "socket.io": "^4.2.0",
     "stream-to-promise": "^3.0.0",
     "string-width": "=4.2.2",
+    "superjson": "^1.9.1",
     "swagger-jsdoc": "^6.1.0",
     "swig-templates": "^2.0.2",
     "uglifycss": "^0.0.29",
@@ -166,21 +186,21 @@
   },
   "// comments for defDependencies": {
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
-    "handsontable": "v7.0.0 or above is no loger MIT lisence.",
-    "ts-loader": "v9 is not compatible with webpack@5",
-    "ts-node": "v10 occurs 'SyntaxError: Cannot use import statement outside a module' when using migrate-mongo"
+    "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
     "@alienfast/i18next-loader": "^1.1.4",
     "@growi/ui": "^5.1.3-RC.0",
     "@handsontable/react": "=2.1.0",
+    "@icon/themify-icons": "1.0.1-alpha.3",
+    "@next/bundle-analyzer": "^12.2.3",
     "@types/compression": "^1.7.0",
     "@types/express": "^4.17.11",
     "@types/jquery": "^3.5.8",
     "@types/multer": "^1.4.5",
-    "@types/react-dom": "^17.0.9",
     "autoprefixer": "^9.0.0",
-    "bootstrap": "^4.5.0",
+    "babel-loader": "^8.2.5",
+    "bootstrap": "^4.6.1",
     "browser-sync": "^2.27.7",
     "bunyan-debug": "^2.0.0",
     "cli": "~1.0.1",
@@ -188,82 +208,50 @@
     "colors": "=1.4.0",
     "connect-browser-sync": "^2.1.0",
     "core-js": "=2.6.9",
-    "css-loader": "^3.0.0",
     "csv-to-markdown-table": "^1.0.1",
     "diff2html": "^3.1.2",
     "eazy-logger": "^3.1.0",
     "emoji-mart": "npm:panta82-emoji-mart@^3.0.1",
     "eslint-plugin-cypress": "^2.12.1",
     "eslint-plugin-regex": "^1.8.0",
-    "file-loader": "^5.0.2",
+    "font-awesome": "^4.7.0",
     "handsontable": "=6.2.2",
-    "hard-source-webpack-plugin": "^0.13.1",
-    "i18next-browser-languagedetector": "^4.0.1",
-    "imports-loader": "^0.8.0",
+    "i18next-hmr": "^1.7.7",
     "jquery-slimscroll": "^1.3.8",
-    "jquery-ui": "^1.12.1",
     "jquery.cookie": "~1.4.1",
     "jshint": "^2.13.0",
     "load-css-file": "^1.0.0",
-    "lodash-webpack-plugin": "^0.11.5",
-    "markdown-it": "^10.0.0",
-    "markdown-it-blockdiag": "^1.1.1",
-    "markdown-it-drawio-viewer": "^1.3.1",
-    "markdown-it-emoji": "^1.4.0",
-    "markdown-it-emoji-mart": "^0.1.1",
-    "markdown-it-footnote": "^3.0.1",
-    "markdown-it-mathjax": "^2.0.0",
-    "markdown-it-named-headers": "^0.0.4",
-    "markdown-it-plantuml": "^1.3.0",
-    "markdown-it-task-checkbox": "^1.0.6",
-    "markdown-it-toc-and-anchor-with-slugid": "^1.1.4",
     "markdown-table": "^1.1.1",
-    "mini-css-extract-plugin": "^0.9.0",
+    "material-icons": "^1.11.3",
     "morgan": "^1.10.0",
-    "node-dev": "^4.0.0",
+    "next-transpile-modules": "^9.0.0",
     "normalize-path": "^3.0.0",
-    "null-loader": "^3.0.0",
-    "on-headers": "^1.0.1",
-    "optimize-css-assets-webpack-plugin": "^5.0.3",
     "penpal": "^4.0.0",
     "plantuml-encoder": "^1.2.5",
-    "postcss-loader": "^3.0.0",
     "prettier": "^1.19.1",
-    "react": "^16.8.3",
     "react-bootstrap-typeahead": "^5.2.2",
     "react-codemirror2": "^6.0.0",
     "react-copy-to-clipboard": "^5.0.1",
-    "react-dom": "^16.8.3",
     "react-dropzone": "^11.2.4",
     "react-frame-component": "^4.0.0",
     "react-hotkeys": "^2.0.0",
-    "react-i18next": "^11.1.0",
+    "react-use-ripple": "^1.5.2",
     "react-waypoint": "^10.1.0",
     "reactstrap": "^8.9.0",
     "replacestream": "^4.0.3",
     "reveal.js": "^4.3.1",
-    "sass": "^1.43.4",
-    "sass-loader": "^10.1.1",
+    "sass": "^1.53.0",
+    "simple-line-icons": "^2.5.5",
     "simple-load-script": "^1.0.2",
     "simplebar-react": "^2.3.6",
     "socket.io-client": "^4.2.0",
     "sticky-events": "^3.4.11",
-    "style-loader": "^1.0.0",
-    "styled-components": "^5.0.1",
     "swagger2openapi": "^5.3.1",
-    "swr": "^1.1.2",
-    "terser-webpack-plugin": "^4.1.0",
+    "swr": "^1.3.0",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
-    "ts-loader": "^8.3.0",
-    "ts-node": "^9.1.1",
     "ts-node-dev": "^2.0.0",
     "tsc-alias": "^1.2.9",
-    "tsconfig-paths-webpack-plugin": "^3.5.1",
-    "unstated": "^2.1.1",
-    "webpack": "^4.46.0",
-    "webpack-assets-manifest": "^3.1.1",
-    "webpack-bundle-analyzer": "^3.9.0",
-    "webpack-cli": "^4.9.1"
+    "unstated": "^2.1.1"
   }
 }

+ 0 - 0
packages/app/public/static/locales/en_US/admin/admin.json → packages/app/public/static/locales/en_US/admin.json


+ 10 - 1
packages/app/public/static/locales/en_US/translation.json

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "English"
+  },
   "Help": "Help",
   "view": "View",
   "Edit": "Edit",
@@ -16,6 +19,7 @@
   "Move/Rename": "Move/Rename",
   "Redirected": "Redirected",
   "Unlinked": "Unlinked",
+  "unlink_redirection": "Unlink redirection",
   "Done": "Done",
   "Cancel": "Cancel",
   "Create": "Create",
@@ -27,6 +31,7 @@
   "New": "New",
   "Close": "Close",
   "Shortcuts": "Shortcuts",
+  "CustomSidebar": "Custom Sidebar",
   "eg": "e.g.",
   "add": "Add",
   "Undo": "Undo",
@@ -196,6 +201,9 @@
     "page_not_exist": "This page does not exist.",
     "page_not_exist_alert": "This page does not exist. Please create a new page."
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "Couldn't create path."
+  },
   "custom_navigation": {
     "no_page_list": "There are no pages under this page.",
     "link_sharing_is_disabled": "Link sharing is disabled."
@@ -390,7 +398,8 @@
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
     "notice": {
       "conflict": "Couldn't save the changes you made because someone else was editing this page. Please re-edit the affected section after reloading the page."
-    }
+    },
+    "changes_not_saved": "Changes you made may not be saved."
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",

+ 0 - 2
packages/app/public/static/locales/index.js

@@ -1,2 +0,0 @@
-// !!DO NOT EDIT/REMOVE THIS FILE!!
-// entry point for @alienfast/i18next-loader

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


+ 10 - 1
packages/app/public/static/locales/ja_JP/translation.json

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "日本語"
+  },
   "Help": "ヘルプ",
   "view": "View",
   "Edit": "編集",
@@ -16,6 +19,7 @@
   "Move/Rename": "移動/名前変更",
   "Redirected": "リダイレクトされました",
   "Unlinked": "リダイレクト削除",
+  "unlink_redirection": "リダイレクト削除",
   "Done": "完了",
   "Cancel": "キャンセル",
   "Create": "作成",
@@ -27,6 +31,7 @@
   "New": "作成",
   "Close": "閉じる",
   "Shortcuts": "ショートカット",
+  "CustomSidebar": "カスタムサイドバー",
   "eg": "例:",
   "add": "追加",
   "Undo": "元に戻す",
@@ -198,6 +203,9 @@
     "page_not_exist": "このページは存在しません。",
     "page_not_exist_alert": "このページは存在しません。新たに作成する必要があります。"
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "パスを作成できませんでした。"
+  },
   "custom_navigation": {
     "no_page_list": "このページの配下にはページが存在しません。",
     "link_sharing_is_disabled": "リンクのシェアは無効化されています"
@@ -390,7 +398,8 @@
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
     "notice": {
       "conflict": "すでに他の人がこのページを編集していたため保存できませんでした。ページを再読み込み後、自分の編集箇所のみ再度編集してください。"
-    }
+    },
+    "changes_not_saved": "変更が保存されていない可能性があります。"
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "投稿時のページを表示する",

+ 0 - 0
packages/app/public/static/locales/zh_CN/admin/admin.json → packages/app/public/static/locales/zh_CN/admin.json


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

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "简体中文"
+  },
   "Help": "帮助",
   "view": "View",
 	"Edit": "编辑",
@@ -17,6 +20,7 @@
 	"Move/Rename": "移动/重命名",
 	"Redirected": "重定向",
 	"Unlinked": "Unlinked",
+  "unlink_redirection": "取消链接重定向",
   "Done": "Done",
   "Cancel": "取消",
 	"Create": "创建",
@@ -28,6 +32,7 @@
   "New": "新建",
   "Close": "Close",
 	"Shortcuts": "快捷方式",
+  "CustomSidebar": "Custom Sidebar",
 	"eg": "e.g.",
 	"add": "添加",
 	"Undo": "撤销",
@@ -196,6 +201,9 @@
     "page_not_exist": "该页面不存在",
     "page_not_exist_alert": "该页面不存在,请创建一个新页面"
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "无法创建路径"
+  },
   "custom_navigation": {
     "no_page_list": "There are no pages under this page.",
     "link_sharing_is_disabled": "链接共享已被禁用"
@@ -369,7 +377,8 @@
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
 		"notice": {
 			"conflict": "无法保存您所做的更改,因为其他人正在编辑此页。请在重新加载页面后重新编辑受影响的部分。"
-		}
+		},
+    "changes_not_saved": "您所做的更改可能不会保存。"
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",

+ 0 - 7
packages/app/resource/cdn-manifests.js

@@ -147,13 +147,6 @@ module.exports = {
         integrity: '',
       },
     },
-    {
-      name: 'jquery-ui',
-      url: 'https://cdn.jsdelivr.net/jquery.ui/1.11.4/jquery-ui.min.css',
-      args: {
-        integrity: '',
-      },
-    },
     {
       name: 'highlight-theme-github',
       url: 'https://cdn.jsdelivr.net/npm/highlight.js@9.13.0/styles/github.css',

+ 0 - 3
packages/app/resource/locales/en_US/sandbox.md

@@ -245,9 +245,6 @@ You can create links using `[Display text](URL)`.
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 This is the most flexible linker.
 Both the page description and link address can be displayed on the page.
 

+ 1 - 1
packages/app/resource/locales/en_US/welcome.md

@@ -1,7 +1,7 @@
 # :tada: Welcome to GROWI
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI is a Wiki for Individuals and Corporations | A knowledge base tool.
 Knowledge in companies, university laboratories, and clubs can be easily shared and anyone can edit the page.

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

@@ -244,9 +244,6 @@ ___
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 最も柔軟な Linker です。
 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
 

+ 1 - 1
packages/app/resource/locales/ja_JP/welcome.md

@@ -1,6 +1,6 @@
 # :tada: GROWI へようこそ
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI は個人・法人向けの Wiki | ナレッジベースツールです。  
 会社や大学の研究室、サークルでのナレッジ情報を簡単に共有でき、作られたページは誰でも編集が可能です。

+ 0 - 3
packages/app/resource/locales/zh_CN/sandbox.md

@@ -245,9 +245,6 @@ You can create links using `[Display text](URL)`.
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 This is the most flexible linker.
 Both the page description and link address can be displayed on the page.
 

+ 1 - 1
packages/app/resource/locales/zh_CN/welcome.md

@@ -1,7 +1,7 @@
 # :tada: 欢迎来到GROWI
 
 [![GitHub Releases](https://img.shields.io/github/release/weseek/growi.svg)](https://github.com/weseek/growi/releases/latest)
-[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://github.com/weseek/growi/blob/master/LICENSE)
 
 GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 公司、大学实验室和俱乐部的知识可以轻松共享,任何人都可以编辑页面。

+ 0 - 9
packages/app/src/client/boot.js

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

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

@@ -1,8 +1,6 @@
 /* eslint-disable react/jsx-filename-extension */
 require('jquery.cookie');
 
-require('./thirdparty-js/waves');
-
 const Crowi = {};
 
 if (!window) {

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
packages/app/src/client/legacy/thirdparty-js/waves.js


+ 1 - 1
packages/app/src/client/models/BootstrapGrid.js

@@ -7,7 +7,7 @@ export default class BootstrapGrid {
 
   static ResponsiveSize = {
     XS_SIZE: 'xs', SM_SIZE: 'sm', MD_SIZE: 'md',
-  }
+  };
 
   static validateColsRatios(colsRatios) {
 

+ 2 - 2
packages/app/src/client/models/Linker.js

@@ -26,14 +26,14 @@ export default class Linker {
     markdownLink: 'mdLink',
     growiLink: 'growiLink',
     pukiwikiLink: 'pukiwikiLink',
-  }
+  };
 
   static patterns = {
     pukiwikiLinkWithLabel: /^\[\[(?<label>.+)>(?<link>.+)\]\]$/, // https://regex101.com/r/2fNmUN/2
     pukiwikiLinkWithoutLabel: /^\[\[(?<label>.+)\]\]$/, // https://regex101.com/r/S7w5Xu/1
     growiLink: /^\[(?<label>\/.+)\]$/, // https://regex101.com/r/DJfkYf/3
     markdownLink: /^\[(?<label>.*)\]\((?<link>.*)\)$/, // https://regex101.com/r/DZCKP3/2
-  }
+  };
 
   initWhenMarkdownLink() {
     // fill label with link if empty

+ 4 - 4
packages/app/src/client/models/MarkdownTable.js

@@ -1,6 +1,6 @@
+import csvToMarkdown from 'csv-to-markdown-table';
 import markdownTable from 'markdown-table';
 import stringWidth from 'string-width';
-import csvToMarkdown from 'csv-to-markdown-table';
 
 // https://github.com/markdown-it/markdown-it/blob/d29f421927e93e88daf75f22089a3e732e195bd2/lib/rules_block/table.js#L83
 // https://regex101.com/r/7BN2fR/7
@@ -8,9 +8,6 @@ const tableAlignmentLineRE = /^[-:|][-:|\s]*$/;
 const tableAlignmentLineNegRE = /^[^-:]*$/; // it is need to check to ignore empty row which is matched above RE
 const linePartOfTableRE = /^\|[^\r\n]*|[^\r\n]*\|$|([^|\r\n]+\|[^|\r\n]*)+/; // own idea
 
-// set up DOMParser
-const domParser = new (window.DOMParser)();
-
 const defaultOptions = { stringLength: stringWidth };
 
 /**
@@ -67,6 +64,9 @@ export default class MarkdownTable {
    * The error message is a innerHTML, so must not assign it into element.innerHTML because it can lead to Mutation-based XSS
    */
   static fromHTMLTableTag(str) {
+    // set up DOMParser
+    const domParser = new (window.DOMParser)();
+
     // use DOMParser to prevent DOM based XSS (https://developer.mozilla.org/en-US/docs/Web/API/DOMParser)
     const dom = domParser.parseFromString(str, 'application/xml');
 

+ 5 - 4
packages/app/src/client/services/AdminAppContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Post, apiv3Put } from '../util/apiv3-client';
@@ -11,13 +12,13 @@ export default class AdminAppContainer extends Container {
   constructor() {
     super();
 
-    this.dummyTitle = 0;
-    this.dummyTitleForError = 1;
+    if (isServer()) {
+      return;
+    }
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      title: this.dummyTitle,
+      title: '',
       confidential: '',
       globalLang: '',
       isEmailPublishedForNewUser: true,

+ 5 - 6
packages/app/src/client/services/AdminBasicSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -16,15 +17,13 @@ export default class AdminBasicSecurityContainer extends Container {
   constructor() {
     super();
 
-    this.dummyIsSameUsernameTreatedAsIdenticalUser = 0;
-    this.dummyIsSameUsernameTreatedAsIdenticalUserForError = 1;
+    if (isServer()) {
+      return;
+    }
 
     this.state = {
-      retrieveError: null,
-      // set dummy value tile for using suspense
-      isSameUsernameTreatedAsIdenticalUser: this.dummyIsSameUsernameTreatedAsIdenticalUser,
+      isSameUsernameTreatedAsIdenticalUser: false,
     };
-
   }
 
   /**

+ 4 - 53
packages/app/src/client/services/AdminCustomizeContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -17,13 +18,12 @@ export default class AdminCustomizeContainer extends Container {
   constructor() {
     super();
 
-    this.dummyCurrentTheme = 0;
-    this.dummyCurrentThemeForError = 1;
+    if (isServer()) {
+      return;
+    }
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      currentTheme: this.dummyCurrentTheme,
       isEnabledTimeline: false,
       isSavedStatesOfTabChanges: false,
       isEnabledAttachTitleHeader: false,
@@ -80,7 +80,6 @@ export default class AdminCustomizeContainer extends Container {
       const { customizeParams } = response.data;
 
       this.setState({
-        currentTheme: customizeParams.themeType,
         isEnabledTimeline: customizeParams.isEnabledTimeline,
         isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
@@ -109,17 +108,6 @@ export default class AdminCustomizeContainer extends Container {
     }
   }
 
-  /**
-   * Switch themeType
-   */
-  switchThemeType(themeName) {
-    this.setState({ currentTheme: themeName });
-
-    // preview if production
-    if (process.env.NODE_ENV !== 'development') {
-      this.previewTheme(themeName);
-    }
-  }
 
   /**
    * Switch enabledTimeLine
@@ -239,24 +227,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentCustomizeScript: inpuValue });
   }
 
-  /**
-   * Preview theme
-   * @param {string} themeName
-   */
-  async previewTheme(themeName) {
-    try {
-      // get theme asset path
-      const response = await apiv3Get('/customize-setting/theme/asset-path', { themeName });
-      const { assetPath } = response.data;
-
-      const themeLink = document.getElementById('grw-theme-link');
-      themeLink.setAttribute('href', assetPath);
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
   /**
    * Preview hljs style
    * @param {string} styleId
@@ -268,25 +238,6 @@ export default class AdminCustomizeContainer extends Container {
     styleLInk.href = styleLInk.href.replace(/[^/]+\.css$/, `${styleId}.css`);
   }
 
-  /**
-   * Update theme
-   * @memberOf AdminCustomizeContainer
-   */
-  async updateCustomizeTheme() {
-    try {
-      const response = await apiv3Put('/customize-setting/theme', {
-        themeType: this.state.currentTheme,
-      });
-      const { customizedParams } = response.data;
-      this.setState({
-        themeType: customizedParams.themeType,
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      throw new Error('Failed to update data');
-    }
-  }
 
   /**
    * Update function

+ 5 - 0
packages/app/src/client/services/AdminExternalAccountsContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminExternalAccountsContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.state = {
       externalAccounts: [],
       totalAccounts: 0,

+ 5 - 5
packages/app/src/client/services/AdminGeneralSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import {
@@ -18,15 +19,15 @@ export default class AdminGeneralSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
-    this.dummyCurrentRestrictGuestMode = 0;
-    this.dummyCurrentRestrictGuestModeForError = 1;
+    if (isServer()) {
+      return;
+    }
 
     this.state = {
       retrieveError: null,
       sessionMaxAge: null,
       wikiMode: '',
-      // set dummy value tile for using suspense
-      currentRestrictGuestMode: this.dummyCurrentRestrictGuestMode,
+      currentRestrictGuestMode: '',
       currentPageDeletionAuthority: PageSingleDeleteConfigValue.AdminOnly,
       currentPageRecursiveDeletionAuthority: PageRecursiveDeleteConfigValue.Inherit,
       currentPageCompleteDeletionAuthority: PageSingleDeleteCompConfigValue.AdminOnly,
@@ -37,7 +38,6 @@ export default class AdminGeneralSecurityContainer extends Container {
       expandOtherOptionsForCompleteDeletion: false,
       isShowRestrictedByOwner: false,
       isShowRestrictedByGroup: false,
-      appSiteUrl: appContainer.config.crowi.url || '',
       isLocalEnabled: false,
       isLdapEnabled: false,
       isSamlEnabled: false,

+ 5 - 2
packages/app/src/client/services/AdminGitHubSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 
@@ -18,12 +18,15 @@ export default class AdminGitHubSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGithubClientId = 0;
     this.dummyGithubClientIdForError = 1;
 
     this.state = {
       retrieveError: null,
-      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/github/callback'),
       // set dummy value tile for using suspense
       githubClientId: this.dummyGithubClientId,
       githubClientSecret: '',

+ 5 - 2
packages/app/src/client/services/AdminGoogleSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 
@@ -18,12 +18,15 @@ export default class AdminGoogleSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.dummyGoogleClientId = 0;
     this.dummyGoogleClientIdForError = 1;
 
     this.state = {
       retrieveError: null,
-      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/google/callback'),
       // set dummy value tile for using suspense
       googleClientId: this.dummyGoogleClientId,
       googleClientSecret: '',

+ 5 - 0
packages/app/src/client/services/AdminHomeContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -17,6 +18,10 @@ export default class AdminHomeContainer extends Container {
   constructor() {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.copyStateValues = {
       DEFAULT: 'default',
       DONE: 'done',

+ 6 - 4
packages/app/src/client/services/AdminImportContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -17,14 +18,15 @@ export default class AdminImportContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyEsaTeamName = 0;
-    this.dummyEsaTeamNameForError = 1;
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      esaTeamName: this.dummyEsaTeamName,
+      esaTeamName: '',
       esaAccessToken: '',
       qiitaTeamName: '',
       qiitaAccessToken: '',

+ 6 - 4
packages/app/src/client/services/AdminLdapSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -16,14 +17,15 @@ export default class AdminLdapSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyServerUrl = 0;
-    this.dummyServerUrlForError = 1;
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      serverUrl: this.dummyServerUrl,
+      serverUrl: '',
       isUserBind: false,
       ldapBindDN: '',
       ldapBindDNPassword: '',

+ 5 - 0
packages/app/src/client/services/AdminLocalSecurityContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import loggerFactory from '~/utils/logger';
@@ -15,6 +16,10 @@ export default class AdminLocalSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
     this.dummyRegistrationMode = 0;
     this.dummyRegistrationModeForError = 1;

+ 6 - 4
packages/app/src/client/services/AdminMarkDownContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,14 +12,15 @@ export default class AdminMarkDownContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyIsEnabledLinebreaks = 0;
-    this.dummyIsEnabledLinebreaksForError = 1;
 
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      isEnabledLinebreaks: this.dummyIsEnabledLinebreaks,
+      isEnabledLinebreaks: false,
       isEnabledLinebreaksInComments: false,
       adminPreferredIndentSize: 4,
       isIndentSizeForced: false,

+ 5 - 0
packages/app/src/client/services/AdminNotificationContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import {
@@ -13,6 +14,10 @@ export default class AdminNotificationContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     this.state = {

+ 6 - 6
packages/app/src/client/services/AdminOidcSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 
@@ -18,15 +18,15 @@ export default class AdminOidcSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyOidcProviderName = 0;
-    this.dummyOidcProviderNameForError = 1;
 
     this.state = {
       retrieveError: null,
-      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/oidc/callback'),
-      // set dummy value tile for using suspense
-      oidcProviderName: this.dummyOidcProviderName,
+      oidcProviderName: '',
       oidcIssuerHost: '',
       oidcAuthorizationEndpoint: '',
       oidcTokenEndpoint: '',

+ 6 - 6
packages/app/src/client/services/AdminSamlSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 
@@ -18,18 +18,18 @@ export default class AdminSamlSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummySamlEntryPoint = 0;
-    this.dummySamlEntryPointForError = 1;
 
     this.state = {
       retrieveError: null,
       // TODO GW-1324 ABLCRure DB value takes precedence
       useOnlyEnvVars: false,
-      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/saml/callback'),
       missingMandatoryConfigKeys: [],
-      // set dummy value tile for using suspense
-      samlEntryPoint: this.dummySamlEntryPoint,
+      samlEntryPoint: '',
       samlIssuer: '',
       samlCert: '',
       samlAttrMapId: '',

+ 6 - 3
packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js

@@ -1,3 +1,4 @@
+import { isServer } from '@growi/core';
 import { Container } from 'unstated';
 
 import { apiv3Get, apiv3Put } from '../util/apiv3-client';
@@ -11,15 +12,17 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyWebhookUrl = 0;
-    this.dummyWebhookUrlForError = 1;
 
     this.state = {
       isSlackbotConfigured: false,
       retrieveError: null,
       selectSlackOption: 'Incoming Webhooks',
-      webhookUrl: this.dummyWebhookUrl,
+      webhookUrl: '',
       isIncomingWebhookPrioritized: false,
       slackToken: '',
     };

+ 1 - 24
packages/app/src/client/services/AdminSocketIoContainer.js

@@ -1,25 +1,2 @@
-import SocketIoContainer from './SocketIoContainer';
-import { toastError } from '../util/apiNotification';
 
-/**
- * A subclass of SocketIoContainer for /admin namespace
- */
-export default class AdminSocketIoContainer extends SocketIoContainer {
-
-  constructor(appContainer) {
-    super(appContainer, '/admin');
-
-    // show toastr
-    this.socket.on('error', (error) => {
-      toastError(new Error(error));
-    });
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminSocketIoContainer';
-  }
-
-}
+export default class AdminSocketIoContainer {}

+ 6 - 6
packages/app/src/client/services/AdminTwitterSecurityContainer.js

@@ -1,4 +1,4 @@
-import { pathUtils } from '@growi/core';
+import { isServer, pathUtils } from '@growi/core';
 import { Container } from 'unstated';
 import urljoin from 'url-join';
 
@@ -18,14 +18,14 @@ export default class AdminTwitterSecurityContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
-    this.dummyTwitterConsumerKey = 0;
-    this.dummyTwitterConsumerKeyForError = 1;
 
     this.state = {
-      callbackUrl: urljoin(pathUtils.removeTrailingSlash(appContainer.config.crowi.url), '/passport/twitter/callback'),
-      // set dummy value tile for using suspense
-      twitterConsumerKey: this.dummyTwitterConsumerKey,
+      twitterConsumerKey: '',
       twitterConsumerSecret: '',
       isSameUsernameTreatedAsIdenticalUser: false,
     };

+ 0 - 197
packages/app/src/client/services/AdminUserGroupDetailContainer.js

@@ -1,197 +0,0 @@
-/*
- * TODO 85062: AdminUserGroupDetailContainer is under transplantation to UserGroupDetailPage.tsx
- */
-
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-import { toastError } from '../util/apiNotification';
-
-import {
-  apiv3Get, apiv3Delete, apiv3Put, apiv3Post,
-} from '~/client/util/apiv3-client';
-
-// eslint-disable-next-line no-unused-vars
-const logger = loggerFactory('growi:services:AdminUserGroupDetailContainer');
-
-/**
- * Service container for admin user group detail page (UserGroupDetailPage.jsx)
- * @extends {Container} unstated Container
- */
-export default class AdminUserGroupDetailContainer extends Container {
-
-  constructor(appContainer) {
-    super();
-
-    this.appContainer = appContainer;
-
-    const rootElem = document.getElementById('admin-user-group-detail');
-
-    if (rootElem == null) {
-      return;
-    }
-
-    this.state = {
-      // TODO: [SPA] get userGroup from props
-      userGroup: JSON.parse(rootElem.getAttribute('data-user-group')),
-      userGroupRelations: [], // For user list
-
-      // TODO 85062: /_api/v3/user-groups/children?include_grand_child=boolean
-      childUserGroups: [], // TODO 85062: fetch data on init (findChildGroupsByParentIds) For child group list
-      grandChildUserGroups: [], // TODO 85062: fetch data on init (findChildGroupsByParentIds) For child group list
-
-      childUserGroupRelations: [], // TODO 85062: fetch data on init (findRelationsByGroupIds) For child group list users
-      relatedPages: [], // For page list
-      isUserGroupUserModalOpen: false,
-      searchType: 'partial',
-      isAlsoMailSearched: false,
-      isAlsoNameSearched: false,
-    };
-
-    this.init();
-
-    this.switchIsAlsoMailSearched = this.switchIsAlsoMailSearched.bind(this);
-    this.switchIsAlsoNameSearched = this.switchIsAlsoNameSearched.bind(this);
-    this.openUserGroupUserModal = this.openUserGroupUserModal.bind(this);
-    this.closeUserGroupUserModal = this.closeUserGroupUserModal.bind(this);
-    this.addUserByUsername = this.addUserByUsername.bind(this);
-    this.removeUserByUsername = this.removeUserByUsername.bind(this);
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'AdminUserGroupDetailContainer';
-  }
-
-  /**
-   * retrieve user group data
-   */
-  async init() {
-    try {
-      const [
-        userGroupRelations,
-        relatedPages,
-      ] = await Promise.all([
-        apiv3Get(`/user-groups/${this.state.userGroup._id}/user-group-relations`).then((res) => { return res.data.userGroupRelations }),
-        apiv3Get(`/user-groups/${this.state.userGroup._id}/pages`).then((res) => { return res.data.pages }),
-      ]);
-
-      await this.setState({
-        userGroupRelations,
-        relatedPages,
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      toastError(new Error('Failed to fetch data'));
-    }
-  }
-
-  /**
-   * switch isAlsoMailSearched
-   */
-  switchIsAlsoMailSearched() {
-    this.setState({ isAlsoMailSearched: !this.state.isAlsoMailSearched });
-  }
-
-  /**
-   * switch isAlsoNameSearched
-   */
-  switchIsAlsoNameSearched() {
-    this.setState({ isAlsoNameSearched: !this.state.isAlsoNameSearched });
-  }
-
-  /**
-   * switch searchType
-   */
-  switchSearchType(searchType) {
-    this.setState({ searchType });
-  }
-
-  /**
-   * update user group
-   *
-   * @memberOf AdminUserGroupDetailContainer
-   * @param {object} param update param for user group
-   * @return {object} response object
-   */
-  async updateUserGroup(param) {
-    const res = await apiv3Put(`/user-groups/${this.state.userGroup._id}`, param);
-    const { userGroup } = res.data;
-
-    await this.setState({ userGroup });
-
-    return res;
-  }
-
-  /**
-   * open a modal
-   *
-   * @memberOf AdminUserGroupDetailContainer
-   */
-  async openUserGroupUserModal() {
-    await this.setState({ isUserGroupUserModalOpen: true });
-  }
-
-  /**
-   * close a modal
-   *
-   * @memberOf AdminUserGroupDetailContainer
-   */
-  async closeUserGroupUserModal() {
-    await this.setState({ isUserGroupUserModalOpen: false });
-  }
-
-  /**
-   * search user for invitation
-   * @param {string} username username of the user to be searched
-   */
-  async fetchApplicableUsers(searchWord) {
-    const res = await apiv3Get(`/user-groups/${this.state.userGroup._id}/unrelated-users`, {
-      searchWord,
-      searchType: this.state.searchType,
-      isAlsoMailSearched: this.state.isAlsoMailSearched,
-      isAlsoNameSearched: this.state.isAlsoNameSearched,
-    });
-
-    const { users } = res.data;
-
-    return users;
-  }
-
-
-  /**
-   * update user group
-   *
-   * @memberOf AdminUserGroupDetailContainer
-   * @param {string} username username of the user to be added to the group
-   */
-  async addUserByUsername(username) {
-    const res = await apiv3Post(`/user-groups/${this.state.userGroup._id}/users/${username}`);
-
-    // do not add users for ducaplicate
-    if (res.data.userGroupRelation == null) { return }
-
-    this.init();
-  }
-
-  /**
-   * update user group
-   *
-   * @memberOf AdminUserGroupDetailContainer
-   * @param {string} username username of the user to be removed from the group
-   */
-  async removeUserByUsername(username) {
-    const res = await apiv3Delete(`/user-groups/${this.state.userGroup._id}/users/${username}`);
-
-    this.setState((prevState) => {
-      return {
-        userGroupRelations: prevState.userGroupRelations.filter((u) => { return u._id !== res.data.userGroupRelation._id }),
-      };
-    });
-  }
-
-}

+ 5 - 4
packages/app/src/client/services/AdminUsersContainer.js

@@ -1,14 +1,11 @@
+import { isServer } from '@growi/core';
 import { debounce } from 'throttle-debounce';
 import { Container } from 'unstated';
 
-import loggerFactory from '~/utils/logger';
-
 import {
   apiv3Delete, apiv3Get, apiv3Post, apiv3Put,
 } from '../util/apiv3-client';
 
-// eslint-disable-next-line no-unused-vars
-const logger = loggerFactory('growi:services:AdminUserGroupDetailContainer');
 
 /**
  * Service container for admin users page (Users.jsx)
@@ -19,6 +16,10 @@ export default class AdminUsersContainer extends Container {
   constructor(appContainer) {
     super();
 
+    if (isServer()) {
+      return;
+    }
+
     this.appContainer = appContainer;
 
     this.state = {

+ 12 - 13
packages/app/src/client/services/AppContainer.js

@@ -1,9 +1,6 @@
 import { Container } from 'unstated';
 
-
-import GrowiRenderer, { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-
-import { i18nFactory } from '../util/i18n';
+// import { i18nFactory } from '../util/i18n';
 
 /**
  * Service container related to options for Application
@@ -23,7 +20,7 @@ export default class AppContainer extends Container {
       const currentUser = JSON.parse(currentUserElem.textContent);
       userLocaleId = currentUser?.lang;
     }
-    this.i18n = i18nFactory(userLocaleId);
+    // this.i18n = i18nFactory(userLocaleId);
 
     this.containerInstances = {};
     this.componentInstances = {};
@@ -59,17 +56,19 @@ export default class AppContainer extends Container {
   }
 
   injectToWindow() {
-    window.appContainer = this;
+    // for fix lint error
+
+    // window.appContainer = this;
 
-    const growiRenderer = new GrowiRenderer(this.getConfig());
-    growiRenderer.init();
+    // const growiRenderer = new GrowiRenderer(this.getConfig());
+    // growiRenderer.init();
 
-    window.growiRenderer = growiRenderer;
+    // window.growiRenderer = growiRenderer;
 
-    // backward compatibility
-    window.crowi = this;
-    window.crowiRenderer = window.growiRenderer;
-    window.crowiPlugin = window.growiPlugin;
+    // // backward compatibility
+    // window.crowi = this;
+    // window.crowiRenderer = window.growiRenderer;
+    // window.crowiPlugin = window.growiPlugin;
   }
 
   getConfig() {

+ 34 - 51
packages/app/src/client/services/ContextExtractor.tsx

@@ -1,28 +1,28 @@
+/* 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 { useRendererSettings } from '~/stores/renderer';
+// import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
 import {
   useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
-  useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useSelectedGrant,
 } from '~/stores/ui';
 import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
 
 import {
   useSiteUrl,
-  useCurrentCreatedAt, useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
+  useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
   useIsNotCreatable, useIsTrashPage, useIsUserPage, useLastUpdateUsername,
   useCurrentPageId, usePageIdOnHackmd, usePageUser, useCurrentPagePath, useRevisionCreatedAt, useRevisionId, useRevisionIdHackmdSynced,
-  useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser, useTargetAndAncestors,
-  useNotFoundTargetPathOrId, useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
+  useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUser, useTargetAndAncestors,
+  useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
   useIsAclEnabled, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsEnabledAttachTitleHeader,
-  useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useIsEmptyPage, useEmptyPageId, useGrowiVersion, useAuditLogEnabled,
-  useActivityExpirationSeconds, useAuditLogAvailableActions, useGrowiRendererConfig,
+  useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useGrowiVersion, useAuditLogEnabled,
+  useActivityExpirationSeconds, useAuditLogAvailableActions, useRendererConfig,
 } from '../../stores/context';
 
 const { isTrashPage: _isTrashPage } = pagePathUtils;
@@ -63,17 +63,9 @@ const ContextExtractorOnce: FC = () => {
   const path = decodeURI(mainContent?.getAttribute('data-path') || '');
   // assign `null` to avoid returning empty string
   const pageId = mainContent?.getAttribute('data-page-id') || null;
-  const emptyPageId = notFoundContext?.getAttribute('data-page-id') || null;
 
   const revisionCreatedAt = +(mainContent?.getAttribute('data-page-revision-created') || '');
 
-  // createdAt
-  const createdAtAttribute = mainContent?.getAttribute('data-page-created-at');
-  const createdAt: Date | null = (createdAtAttribute != null) ? new Date(createdAtAttribute) : null;
-  // updatedAt
-  const updatedAtAttribute = mainContent?.getAttribute('data-page-updated-at');
-  const updatedAt: Date | null = (updatedAtAttribute != null) ? new Date(updatedAtAttribute) : null;
-
   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;
@@ -91,8 +83,6 @@ const ContextExtractorOnce: FC = () => {
   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 creator = JSON.parse(mainContent?.getAttribute('data-page-creator') || jsonNull);
-  const revisionAuthor = JSON.parse(mainContent?.getAttribute('data-page-revision-author') || jsonNull);
   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;
@@ -129,26 +119,25 @@ const ContextExtractorOnce: FC = () => {
   useActivityExpirationSeconds(configByContextHydrate.activityExpirationSeconds);
   useAuditLogAvailableActions(configByContextHydrate.auditLogAvailableActions);
   useGrowiVersion(configByContextHydrate.crowi.version);
-  useRendererSettings({
+  useRendererConfig({
     isEnabledLinebreaks: configByContextHydrate.isEnabledLinebreaks,
     isEnabledLinebreaksInComments: configByContextHydrate.isEnabledLinebreaksInComments,
     adminPreferredIndentSize: configByContextHydrate.adminPreferredIndentSize,
     isIndentSizeForced: configByContextHydrate.isIndentSizeForced,
-  });
-  useGrowiRendererConfig({
+
     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,
-    },
+
+    plantumlUri: configByContextHydrate.env.PLANTUML_URI,
+    blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
   });
+  // useNoCdn(configByContextHydrate.env.NO_CDN);
+  // useUploadableImage(configByContextHydrate.upload.image);
+  // useUploadableFile(configByContextHydrate.upload.file);
 
   // Page
-  useCurrentCreatedAt(createdAt);
   useDeleteUsername(deleteUsername);
   useDeletedAt(deletedAt);
   useHasChildren(hasChildren);
@@ -160,7 +149,6 @@ const ContextExtractorOnce: FC = () => {
   useIsUserPage(isUserPage);
   useLastUpdateUsername(lastUpdateUsername);
   useCurrentPageId(pageId);
-  useEmptyPageId(emptyPageId);
   usePageIdOnHackmd(pageIdOnHackmd);
   usePageUser(pageUser);
   useCurrentPagePath(path);
@@ -170,13 +158,8 @@ const ContextExtractorOnce: FC = () => {
   useShareLinkId(shareLinkId);
   useShareLinksNumber(shareLinksNumber);
   useTemplateTagData(templateTagData);
-  useCurrentUpdatedAt(updatedAt);
-  useCreator(creator);
-  useRevisionAuthor(revisionAuthor);
   useTargetAndAncestors(targetAndAncestors);
-  useNotFoundTargetPathOrId(notFoundTargetPathOrId);
   useIsSearchPage(isSearchPage);
-  useIsEmptyPage(isEmptyPage);
   useHasParent(hasParent);
 
   // Navigation
@@ -190,9 +173,9 @@ const ContextExtractorOnce: FC = () => {
   useIsDeviceSmallerThanMd();
 
   // Editor
-  useSelectedGrant(grant);
-  useSelectedGrantGroupId(grantGroupId);
-  useSelectedGrantGroupName(grantGroupName);
+  // useSelectedGrant(grant);
+  // useSelectedGrantGroupId(grantGroupId);
+  // useSelectedGrantGroupName(grantGroupName);
 
   // SearchResult
   useIsDeviceSmallerThanLg();
@@ -204,21 +187,21 @@ const ContextExtractorOnce: FC = () => {
 
   // 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,
-  );
+  // (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;
 };

+ 0 - 24
packages/app/src/client/services/EditorContainer.js

@@ -21,8 +21,6 @@ export default class EditorContainer extends Container {
       tags: null,
     };
 
-    this.isSetBeforeunloadEventHandler = false;
-
     this.initDrafts();
 
   }
@@ -59,28 +57,6 @@ export default class EditorContainer extends Container {
     }
   }
 
-
-  // See https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#example
-  showUnsavedWarning(e) {
-    // Cancel the event
-    e.preventDefault();
-    // display browser default message
-    e.returnValue = '';
-    return '';
-  }
-
-  disableUnsavedWarning() {
-    window.removeEventListener('beforeunload', this.showUnsavedWarning);
-    this.isSetBeforeunloadEventHandler = false;
-  }
-
-  enableUnsavedWarning() {
-    if (!this.isSetBeforeunloadEventHandler) {
-      window.addEventListener('beforeunload', this.showUnsavedWarning);
-      this.isSetBeforeunloadEventHandler = true;
-    }
-  }
-
   clearDraft(path) {
     delete this.drafts[path];
     window.localStorage.setItem('drafts', JSON.stringify(this.drafts));

+ 1 - 3
packages/app/src/client/services/PageContainer.js

@@ -54,9 +54,6 @@ export default class PageContainer extends Container {
       path,
       isEmpty: mainContent.getAttribute('data-page-is-empty'),
 
-      createdAt: mainContent.getAttribute('data-page-created-at'),
-      // please use useCurrentUpdatedAt instead
-      updatedAt: mainContent.getAttribute('data-page-updated-at'),
       deletedAt: mainContent.getAttribute('data-page-deleted-at') || null,
 
       isUserPage: JSON.parse(mainContent.getAttribute('data-page-user')) != null,
@@ -138,6 +135,7 @@ export default class PageContainer extends Container {
 
   /**
    * initialize state for markdown data
+   * [Already SWRized]
    */
   initStateMarkdown() {
     let pageContent = '';

+ 0 - 50
packages/app/src/client/services/SocketIoContainer.js

@@ -1,50 +0,0 @@
-import { Container } from 'unstated';
-
-import io from 'socket.io-client';
-
-import loggerFactory from '~/utils/logger';
-
-const logger = loggerFactory('growi:cli:SocketIoContainer');
-
-/**
- * Service container related to options for WebSocket
- * @extends {Container} unstated Container
- */
-export default class SocketIoContainer extends Container {
-
-  constructor(appContainer, namespace) {
-    super();
-
-    this.appContainer = appContainer;
-    this.appContainer.registerContainer(this);
-
-    const ns = namespace || '/';
-
-    this.socket = io(ns, {
-      transports: ['websocket'],
-    });
-
-    this.socket.on('connect_error', (error) => {
-      logger.error(error);
-    });
-    this.socket.on('error', (error) => {
-      logger.error(error);
-    });
-
-    this.state = {
-    };
-
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'SocketIoContainer';
-  }
-
-  getSocket() {
-    return this.socket;
-  }
-
-}

+ 1 - 2
packages/app/src/client/services/page-operation.ts

@@ -1,7 +1,6 @@
+import { SubscriptionStatusType } from '@growi/core';
 import urljoin from 'url-join';
 
-import { SubscriptionStatusType } from '~/interfaces/subscription';
-
 import { toastError } from '../util/apiNotification';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 

+ 2 - 22
packages/app/src/client/util/apiv1-client.ts

@@ -4,15 +4,6 @@ import axios from '~/utils/axios';
 
 const apiv1Root = '/_api';
 
-// get csrf token from body element
-const body = document.querySelector('body');
-const csrfToken = body?.dataset.csrftoken;
-
-
-type ParamWithCsrfKey = {
-  _csrf: string,
-}
-
 class Apiv1ErrorHandler extends Error {
 
   code;
@@ -50,25 +41,14 @@ export async function apiGet<T>(path: string, params: unknown = {}): Promise<T>
   return apiRequest<T>('get', path, { params });
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function apiPost<T>(path: string, params: any & ParamWithCsrfKey = {}): Promise<T> {
-  if (params._csrf == null) {
-    params._csrf = csrfToken;
-  }
+export async function apiPost<T>(path: string, params: unknown = {}): Promise<T> {
   return apiRequest<T>('post', path, params);
 }
 
 export async function apiPostForm<T>(path: string, formData: FormData): Promise<T> {
-  if (formData.get('_csrf') == null && csrfToken != null) {
-    formData.append('_csrf', csrfToken);
-  }
   return apiPost<T>(path, formData);
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function apiDelete<T>(path: string, params: any & ParamWithCsrfKey = {}): Promise<T> {
-  if (params._csrf == null) {
-    params._csrf = csrfToken;
-  }
+export async function apiDelete<T>(path: string, params: unknown = {}): Promise<T> {
   return apiRequest<T>('delete', path, { data: params });
 }

+ 4 - 29
packages/app/src/client/util/apiv3-client.ts

@@ -12,16 +12,8 @@ const apiv3Root = '/_api/v3';
 
 const logger = loggerFactory('growi:apiv3');
 
-// get csrf token from body element
-const body = document.querySelector('body');
-const csrfToken = body?.dataset.csrftoken;
 
-
-type ParamWithCsrfKey = {
-  _csrf: string,
-}
-
-const apiv3ErrorHandler = (_err) => {
+const apiv3ErrorHandler = (_err: any): any[] => {
   // extract api errors from general 400 err
   const err = _err.response ? _err.response.data.errors : _err;
   const errs = toArrayIfNot(err);
@@ -45,39 +37,22 @@ export async function apiv3Request<T = any>(method: string, path: string, params
   }
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
 export async function apiv3Get<T = any>(path: string, params: unknown = {}): Promise<AxiosResponse<T>> {
   return apiv3Request('get', path, { params });
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function apiv3Post<T = any>(path: string, params: any & ParamWithCsrfKey = {}): Promise<AxiosResponse<T>> {
-  if (params._csrf == null) {
-    params._csrf = csrfToken;
-  }
+export async function apiv3Post<T = any>(path: string, params: unknown = {}): Promise<AxiosResponse<T>> {
   return apiv3Request('post', path, params);
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
 export async function apiv3PostForm<T = any>(path: string, formData: FormData): Promise<AxiosResponse<T>> {
-  if (formData.get('_csrf') == null && csrfToken != null) {
-    formData.append('_csrf', csrfToken);
-  }
   return apiv3Post<T>(path, formData);
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function apiv3Put<T = any>(path: string, params: any & ParamWithCsrfKey = {}): Promise<AxiosResponse<T>> {
-  if (params._csrf == null) {
-    params._csrf = csrfToken;
-  }
+export async function apiv3Put<T = any>(path: string, params: unknown = {}): Promise<AxiosResponse<T>> {
   return apiv3Request('put', path, params);
 }
 
-// eslint-disable-next-line @typescript-eslint/no-explicit-any
-export async function apiv3Delete<T = any>(path: string, params: any & ParamWithCsrfKey = {}): Promise<AxiosResponse<T>> {
-  if (params._csrf == null) {
-    params._csrf = csrfToken;
-  }
+export async function apiv3Delete<T = any>(path: string, params: unknown = {}): Promise<AxiosResponse<T>> {
   return apiv3Request('delete', path, { params });
 }

+ 0 - 73
packages/app/src/client/util/color-scheme.js

@@ -1,73 +0,0 @@
-const mediaQueryListForDarkMode = window.matchMedia('(prefers-color-scheme: dark)');
-
-function isUserPreferenceExists() {
-  return localStorage.preferDarkModeByUser != null;
-}
-
-function isPreferedDarkModeByUser() {
-  return localStorage.preferDarkModeByUser === 'true';
-}
-
-function isDarkMode() {
-  if (isUserPreferenceExists()) {
-    return isPreferedDarkModeByUser();
-  }
-  return mediaQueryListForDarkMode.matches;
-}
-
-/**
- * Apply color scheme as 'dark' attribute of <html></html>
- */
-function applyColorScheme() {
-  let isDarkMode = mediaQueryListForDarkMode.matches;
-  if (isUserPreferenceExists()) {
-    isDarkMode = isPreferedDarkModeByUser();
-  }
-
-  // switch to dark mode
-  if (isDarkMode) {
-    document.documentElement.removeAttribute('light');
-    document.documentElement.setAttribute('dark', 'true');
-  }
-  // switch to light mode
-  else {
-    document.documentElement.setAttribute('light', 'true');
-    document.documentElement.removeAttribute('dark');
-  }
-}
-
-/**
- * Remove color scheme preference
- */
-function removeUserPreference() {
-  if (isUserPreferenceExists()) {
-    delete localStorage.removeItem('preferDarkModeByUser');
-  }
-}
-
-/**
- * Set color scheme preference
- * @param {boolean} isDarkMode
- */
-function updateUserPreference(isDarkMode) {
-  // store settings to localStorage
-  localStorage.preferDarkModeByUser = isDarkMode;
-}
-
-/**
- * Set color scheme preference with OS settings
- */
-function updateUserPreferenceWithOsSettings() {
-  localStorage.preferDarkModeByUser = mediaQueryListForDarkMode.matches;
-}
-
-export {
-  mediaQueryListForDarkMode,
-  isUserPreferenceExists,
-  isPreferedDarkModeByUser,
-  isDarkMode,
-  applyColorScheme,
-  removeUserPreference,
-  updateUserPreference,
-  updateUserPreferenceWithOsSettings,
-};

+ 1 - 5
packages/app/src/client/util/locale-utils.js → packages/app/src/client/util/locale-utils.ts

@@ -4,10 +4,6 @@ const DIAGRAMS_NET_LANG_MAP = {
   zh_CN: 'zh',
 };
 
-const getDiagramsNetLangCode = (lang) => {
+export const getDiagramsNetLangCode = (lang) => {
   return DIAGRAMS_NET_LANG_MAP[lang];
 };
-
-module.exports = {
-  getDiagramsNetLangCode,
-};

+ 11 - 9
packages/app/src/components/Admin/AdminHome/AdminHome.jsx

@@ -1,19 +1,22 @@
 import React, { useEffect, useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import { Tooltip } from 'reactstrap';
-import loggerFactory from '~/utils/logger';
 
+import AdminHomeContainer from '~/client/services/AdminHomeContainer';
 import { toastError } from '~/client/util/apiNotification';
+import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
+import loggerFactory from '~/utils/logger';
+
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
-import AppContainer from '~/client/services/AppContainer';
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
-import { useSWRxV5MigrationStatus } from '~/stores/page-listing';
-import SystemInfomationTable from './SystemInfomationTable';
-import InstalledPluginTable from './InstalledPluginTable';
+
+
 import EnvVarsTable from './EnvVarsTable';
+import InstalledPluginTable from './InstalledPluginTable';
+import SystemInfomationTable from './SystemInfomationTable';
 
 const logger = loggerFactory('growi:admin');
 
@@ -129,10 +132,9 @@ const AdminHome = (props) => {
 };
 
 
-const AdminHomeWrapper = withUnstatedContainers(AdminHome, [AppContainer, AdminHomeContainer]);
+const AdminHomeWrapper = withUnstatedContainers(AdminHome, [AdminHomeContainer]);
 
 AdminHome.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   adminHomeContainer: PropTypes.instanceOf(AdminHomeContainer).isRequired,
 };
 

+ 1 - 1
packages/app/src/components/Admin/AdminHome/InstalledPluginTable.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminHomeContainer from '~/client/services/AdminHomeContainer';
 

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

@@ -1,11 +1,12 @@
 import React, { useCallback } from 'react';
 
+import { useTranslation, i18n } from 'next-i18next';
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
+
+import { i18n as i18nConfig } from '^/config/next-i18next.config';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { localeMetadatas } from '~/client/util/i18n';
 import loggerFactory from '~/utils/logger';
 
 
@@ -77,22 +78,27 @@ const AppSetting = (props) => {
         </label>
         <div className="col-md-6 py-2">
           {
-            localeMetadatas.map(meta => (
-              <div key={meta.id} className="custom-control custom-radio custom-control-inline">
-                <input
-                  type="radio"
-                  id={`radioLang${meta.id}`}
-                  className="custom-control-input"
-                  name="globalLang"
-                  value={meta.id}
-                  checked={adminAppContainer.state.globalLang === meta.id}
-                  onChange={(e) => {
-                    adminAppContainer.changeGlobalLang(e.target.value);
-                  }}
-                />
-                <label className="custom-control-label" htmlFor={`radioLang${meta.id}`}>{meta.displayName}</label>
-              </div>
-            ))
+            i18nConfig.locales.map((locale) => {
+              const fixedT = i18n.getFixedT(locale);
+              i18n.loadLanguages(i18nConfig.locales);
+
+              return (
+                <div key={locale} className="custom-control custom-radio custom-control-inline">
+                  <input
+                    type="radio"
+                    id={`radioLang${locale}`}
+                    className="custom-control-input"
+                    name="globalLang"
+                    value={locale}
+                    checked={adminAppContainer.state.globalLang === locale}
+                    onChange={(e) => {
+                      adminAppContainer.changeGlobalLang(e.target.value);
+                    }}
+                  />
+                  <label className="custom-control-label" htmlFor={`radioLang${locale}`}>{fixedT('meta.display_name')}</label>
+                </div>
+              );
+            })
           }
         </div>
       </div>

+ 0 - 51
packages/app/src/components/Admin/App/AppSettingsPage.jsx

@@ -1,51 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import { withLoadingSppiner } from '../../SuspenseUtils';
-
-import AdminAppContainer from '~/client/services/AdminAppContainer';
-
-import AppSettingsPageContents from './AppSettingsPageContents';
-
-const logger = loggerFactory('growi:appSettings');
-
-let retrieveErrors = null;
-function AppSettingsPage(props) {
-  if (props.adminAppContainer.state.title === props.adminAppContainer.dummyTitle) {
-    throw (async() => {
-      try {
-        await props.adminAppContainer.retrieveAppSettingsData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        props.adminAppContainer.setState({
-          title: props.adminAppContainer.dummyTitleForError,
-        });
-        retrieveErrors = errs;
-      }
-    })();
-  }
-
-  if (props.adminAppContainer.state.title === props.adminAppContainer.dummyTitleForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <AppSettingsPageContents />;
-}
-
-AppSettingsPage.propTypes = {
-  adminAppContainer: PropTypes.instanceOf(AdminAppContainer).isRequired,
-};
-
-/**
- * Wrapper component for using unstated
- */
-const AppSettingsPageWithUnstatedContainer = withUnstatedContainers(withLoadingSppiner(AppSettingsPage), [AdminAppContainer]);
-
-export default AppSettingsPageWithUnstatedContainer;

+ 23 - 2
packages/app/src/components/Admin/App/AppSettingsPageContents.tsx

@@ -1,8 +1,12 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
+
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -14,6 +18,8 @@ import PluginSetting from './PluginSetting';
 import SiteUrlSetting from './SiteUrlSetting';
 import V5PageMigration from './V5PageMigration';
 
+const logger = loggerFactory('growi:appSettings');
+
 type Props = {
   adminAppContainer: AdminAppContainer,
 }
@@ -23,6 +29,21 @@ const AppSettingsPageContents = (props: Props) => {
   const { adminAppContainer } = props;
   const { isV5Compatible } = adminAppContainer.state;
 
+  useEffect(() => {
+    const fetchAppSettingsData = async() => {
+      await adminAppContainer.retrieveAppSettingsData();
+    };
+
+    try {
+      fetchAppSettingsData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminAppContainer]);
+
   return (
     <div data-testid="admin-app-settings">
       {

+ 1 - 1
packages/app/src/components/Admin/App/AwsSetting.jsx

@@ -1,7 +1,7 @@
 import React from 'react';
 
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 

+ 1 - 1
packages/app/src/components/Admin/App/ConfirmModal.tsx

@@ -2,7 +2,7 @@ import React, { FC } from 'react';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 import { TFunctionResult } from 'i18next';
 
 type ConfirmModalProps = {

+ 2 - 2
packages/app/src/components/Admin/App/FileUploadSetting.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
@@ -40,7 +40,7 @@ const FileUploadSetting = (props: Props) => {
         <br />
         <br />
         <span className="text-danger">
-          <i className="ti-unlink"></i>
+          <i className="ti ti-unlink"></i>
           {t('admin:app_setting.change_setting')}
         </span>
       </p>

+ 1 - 1
packages/app/src/components/Admin/App/GcsSettings.jsx

@@ -2,7 +2,7 @@
 import React from 'react';
 
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 

+ 1 - 1
packages/app/src/components/Admin/App/MailSetting.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';

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

@@ -1,5 +1,5 @@
 import React, { FC, useState, useCallback } from 'react';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import loggerFactory from '~/utils/logger';
 

+ 1 - 1
packages/app/src/components/Admin/App/PluginSetting.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';

+ 1 - 2
packages/app/src/components/Admin/App/SesSetting.tsx

@@ -3,7 +3,6 @@ import React from 'react';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 
-import { withLoadingSppiner } from '../../SuspenseUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 type Props = {
@@ -57,6 +56,6 @@ const SmtpSetting = (props: Props) => {
 /**
  * Wrapper component for using unstated
  */
-const SmtpSettingWrapper = withUnstatedContainers(withLoadingSppiner(SmtpSetting), [AdminAppContainer]);
+const SmtpSettingWrapper = withUnstatedContainers(SmtpSetting, [AdminAppContainer]);
 
 export default SmtpSettingWrapper;

+ 1 - 1
packages/app/src/components/Admin/App/SiteUrlSetting.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback } from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';

+ 2 - 3
packages/app/src/components/Admin/App/SmtpSetting.tsx

@@ -1,11 +1,10 @@
 
 import React from 'react';
 
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 
-import { withLoadingSppiner } from '../../SuspenseUtils';
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 
@@ -82,5 +81,5 @@ const SmtpSetting = (props: Props) => {
 /**
  * Wrapper component for using unstated
  */
-const SmtpSettingWrapper = withUnstatedContainers(withLoadingSppiner(SmtpSetting), [AdminAppContainer]);
+const SmtpSettingWrapper = withUnstatedContainers(SmtpSetting, [AdminAppContainer]);
 export default SmtpSettingWrapper;

+ 1 - 1
packages/app/src/components/Admin/App/V5PageMigration.tsx

@@ -1,7 +1,7 @@
 import React, {
   FC, useCallback, useEffect, useState,
 } from 'react';
-import { useTranslation } from 'react-i18next';
+import { useTranslation } from 'next-i18next';
 import { ConfirmModal } from './ConfirmModal';
 import AdminAppContainer from '../../../client/services/AdminAppContainer';
 import { withUnstatedContainers } from '../../UnstatedUtils';

+ 2 - 0
packages/app/src/components/Admin/AuditLog/DateRangePicker.tsx

@@ -37,6 +37,8 @@ const CustomInput = forwardRef<HTMLInputElement, CustomInputProps>((props: Custo
   );
 });
 
+CustomInput.displayName = 'CustomInput';
+
 type DateRangePickerProps = {
   startDate: Date | null
   endDate: Date | null

+ 12 - 11
packages/app/src/components/Admin/Common/AdminNavigation.jsx

@@ -5,22 +5,22 @@
 import React from 'react';
 
 import { pathUtils } from '@growi/core';
+import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import { useTranslation } from 'react-i18next';
 import urljoin from 'url-join';
 
 
-import AppContainer from '~/client/services/AppContainer';
+// import AppContainer from '~/client/services/AppContainer';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
+// import { withUnstatedContainers } from '../../UnstatedUtils';
 
 const AdminNavigation = (props) => {
   const { t } = useTranslation();
-  const { appContainer } = props;
+  // const { appContainer } = props;
   const pathname = window.location.pathname;
 
-  const growiCloudUri = appContainer.config.env.GROWI_CLOUD_URI;
-  const growiAppIdForGrowiCloud = appContainer.config.env.GROWI_APP_ID_FOR_GROWI_CLOUD;
+  // const growiCloudUri = appContainer.config.env.GROWI_CLOUD_URI;
+  // const growiAppIdForGrowiCloud = appContainer.config.env.GROWI_APP_ID_FOR_GROWI_CLOUD;
 
   // eslint-disable-next-line react/prop-types
   const MenuLabel = ({ menu }) => {
@@ -89,7 +89,7 @@ const AdminNavigation = (props) => {
         <MenuLink menu="user-groups"  isListGroupItems isActive={isActiveMenu('/user-groups')} />
         <MenuLink menu="search"       isListGroupItems isActive={isActiveMenu('/search')} />
         <MenuLink menu="audit-log"    isListGroupItems isActive={isActiveMenu('/audit-log')} />
-        {growiCloudUri != null && growiAppIdForGrowiCloud != null
+        {/* {growiCloudUri != null && growiAppIdForGrowiCloud != null
           && (
             <a
               href={`${growiCloudUri}/my/apps/${growiAppIdForGrowiCloud}`}
@@ -98,7 +98,7 @@ const AdminNavigation = (props) => {
               <MenuLabel menu="cloud" />
             </a>
           )
-        }
+        } */}
       </>
     );
   };
@@ -146,10 +146,11 @@ const AdminNavigation = (props) => {
   );
 };
 
-const AdminNavigationWrapper = withUnstatedContainers(AdminNavigation, [AppContainer]);
+// const AdminNavigationWrapper = withUnstatedContainers(AdminNavigation, [AppContainer]);
 
 AdminNavigation.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  // appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 };
 
-export default AdminNavigationWrapper;
+// export default AdminNavigationWrapper;
+export default AdminNavigation;

Неке датотеке нису приказане због велике количине промена