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

Merge branch 'support/apply-nextjs-2' into imprv/101293-empty-NotFoundPage

yuken 3 лет назад
Родитель
Сommit
3887378e24
100 измененных файлов с 603 добавлено и 726 удалено
  1. 10 0
      .eslintrc.js
  2. 38 33
      .github/workflows/ci-app-prod.yml
  3. 5 4
      .github/workflows/ci-app.yml
  4. 6 2
      .github/workflows/reusable-app-prod.yml
  5. 2 2
      package.json
  6. 0 3
      packages/app-next/.eslintrc.json
  7. 0 35
      packages/app-next/.gitignore
  8. 0 20
      packages/app-next/tsconfig.json
  9. 4 0
      packages/app/.eslintignore
  10. 11 1
      packages/app/.eslintrc.js
  11. 4 1
      packages/app/.gitignore
  12. 0 0
      packages/app/_obsolete/config/webpack.common.js
  13. 0 0
      packages/app/_obsolete/config/webpack.dev.dll.js
  14. 0 0
      packages/app/_obsolete/config/webpack.dev.js
  15. 0 0
      packages/app/_obsolete/config/webpack.prod.js
  16. 6 6
      packages/app/_obsolete/src/client/admin.jsx
  17. 0 0
      packages/app/_obsolete/src/client/app.jsx
  18. 0 0
      packages/app/_obsolete/src/client/base.jsx
  19. 0 0
      packages/app/_obsolete/src/client/installer.jsx
  20. 0 0
      packages/app/_obsolete/src/client/nologin.jsx
  21. 6 1
      packages/app/config/migrate-mongo-config.js
  22. 0 0
      packages/app/config/next-i18next.config.ts
  23. 1 1
      packages/app/docker/Dockerfile
  24. 92 70
      packages/app/next.config.js
  25. 15 13
      packages/app/package.json
  26. 3 0
      packages/app/public/static/locales/en_US/admin.json
  27. 1 0
      packages/app/public/static/locales/en_US/translation.json
  28. 3 0
      packages/app/public/static/locales/ja_JP/admin.json
  29. 1 0
      packages/app/public/static/locales/ja_JP/translation.json
  30. 3 0
      packages/app/public/static/locales/zh_CN/admin.json
  31. 1 0
      packages/app/public/static/locales/zh_CN/translation.json
  32. 3 0
      packages/app/src/client/interfaces/clearable.ts
  33. 1 5
      packages/app/src/client/services/AdminAppContainer.js
  34. 1 4
      packages/app/src/client/services/AdminCustomizeContainer.js
  35. 1 4
      packages/app/src/client/services/AdminImportContainer.js
  36. 1 4
      packages/app/src/client/services/AdminMarkDownContainer.js
  37. 1 3
      packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  38. 1 1
      packages/app/src/client/util/apiv3-client.ts
  39. 1 1
      packages/app/src/client/util/i18n.js
  40. 2 1
      packages/app/src/components/Admin/App/AppSetting.jsx
  41. 0 51
      packages/app/src/components/Admin/App/AppSettingsPage.jsx
  42. 22 1
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  43. 11 2
      packages/app/src/components/Admin/AuditLog/ActivityTable.tsx
  44. 18 3
      packages/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx
  45. 32 8
      packages/app/src/components/Admin/AuditLogManagement.tsx
  46. 0 56
      packages/app/src/components/Admin/CustomCssEditor.jsx
  47. 0 57
      packages/app/src/components/Admin/CustomHeaderEditor.jsx
  48. 0 56
      packages/app/src/components/Admin/CustomScriptEditor.jsx
  49. 18 22
      packages/app/src/components/Admin/Customize/Customize.jsx
  50. 4 3
      packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  51. 4 3
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  52. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  53. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  54. 4 3
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  55. 4 2
      packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx
  56. 2 13
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  57. 1 0
      packages/app/src/components/Admin/Customize/ThemeColorBox.jsx
  58. 6 4
      packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx
  59. 23 1
      packages/app/src/components/Admin/ImportData/ImportDataPageContents.jsx
  60. 0 52
      packages/app/src/components/Admin/ImportDataPage.jsx
  61. 18 23
      packages/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx
  62. 0 49
      packages/app/src/components/Admin/MarkdownSetting/MarkDownSetting.jsx
  63. 36 3
      packages/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx
  64. 2 1
      packages/app/src/components/Admin/SlackIntegration/Bridge.jsx
  65. 14 18
      packages/app/src/components/Layout/Admin.module.scss
  66. 4 1
      packages/app/src/components/Layout/AdminLayout.tsx
  67. 1 0
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  68. 5 1
      packages/app/src/components/PageAlert/FixPageGrantAlert.tsx
  69. 1 1
      packages/app/src/components/ShareLink/ShareLink.jsx
  70. 11 10
      packages/app/src/components/Theme/ThemeFireRed.module.scss
  71. 8 0
      packages/app/src/components/Theme/ThemeFireRed.tsx
  72. 9 8
      packages/app/src/components/Theme/ThemeIsland.module.scss
  73. 8 0
      packages/app/src/components/Theme/ThemeIsland.tsx
  74. 11 10
      packages/app/src/components/Theme/ThemeMonoBlue.module.scss
  75. 8 0
      packages/app/src/components/Theme/ThemeMonoBlue.tsx
  76. 7 7
      packages/app/src/components/Theme/ThemeNature.module.scss
  77. 8 0
      packages/app/src/components/Theme/ThemeNature.tsx
  78. 9 8
      packages/app/src/components/Theme/ThemeSpring.module.scss
  79. 8 0
      packages/app/src/components/Theme/ThemeSpring.tsx
  80. 7 7
      packages/app/src/components/Theme/ThemeWood.module.scss
  81. 8 0
      packages/app/src/components/Theme/ThemeWood.tsx
  82. 18 0
      packages/app/src/components/Theme/utils/ThemeProvider.tsx
  83. 2 0
      packages/app/src/interfaces/activity.ts
  84. 3 4
      packages/app/src/interfaces/in-app-notification.ts
  85. 1 1
      packages/app/src/migrations/20180926134048-make-email-unique.js
  86. 1 1
      packages/app/src/migrations/20180927102719-init-serverurl.js
  87. 2 2
      packages/app/src/migrations/20181019114028-abolish-page-group-relation.js
  88. 1 1
      packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js
  89. 1 1
      packages/app/src/migrations/20190618104011-add-config-app-installed.js
  90. 2 2
      packages/app/src/migrations/20190619055421-adjust-page-grant.js
  91. 2 2
      packages/app/src/migrations/20190624110950-fill-last-update-user.js
  92. 2 2
      packages/app/src/migrations/20190629193445-make-root-page-public.js
  93. 1 1
      packages/app/src/migrations/20191102223900-drop-configs-indices.js
  94. 1 1
      packages/app/src/migrations/20191102223901-drop-pages-indices.js
  95. 3 2
      packages/app/src/migrations/20191126173016-adjust-pages-path.js
  96. 1 1
      packages/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js
  97. 1 1
      packages/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js
  98. 1 1
      packages/app/src/migrations/20200420160390-remove-crowi-layout.js
  99. 1 1
      packages/app/src/migrations/20200512005851-remove-behavior-type.js
  100. 1 1
      packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js

+ 10 - 0
.eslintrc.js

@@ -56,6 +56,7 @@ module.exports = {
       },
     ],
     '@typescript-eslint/no-explicit-any': 'off',
+    '@typescript-eslint/explicit-module-boundary-types': 'off',
     indent: [
       'error',
       2,
@@ -80,4 +81,13 @@ module.exports = {
       },
     ]],
   },
+  overrides: [
+    {
+      // enable the rule specifically for TypeScript files
+      files: ['*.ts', '*.tsx'],
+      rules: {
+        '@typescript-eslint/explicit-module-boundary-types': ['error'],
+      },
+    },
+  ],
 };

+ 38 - 33
.github/workflows/ci-app-prod.yml

@@ -3,7 +3,25 @@ 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
+      - .github/workflows/reusable-app-reg-suit.yml
+      - tsconfig.base.json
+      - yarn.lock
+      - packages/app/**
+      - '!packages/app/docker/**'
+      - packages/core/**
+      - packages/slack/**
+      - packages/ui/**
+      - packages/plugin-**
+  pull_request:
+    branches:
+      # - master
+      - support/apply-nextjs-2
+    types: [opened, reopened, synchronize]
     paths:
       - .github/workflows/ci-app-prod.yml
       - .github/workflows/reusable-app-prod.yml
@@ -16,27 +34,12 @@ on:
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**
-  # pull_request:
-  #   branches:
-  #       - master
-  #   types: [opened, reopened, synchronize]
-  #   paths:
-  #     - .github/workflows/ci-app-prod.yml
-  #     - .github/workflows/reusable-app-prod.yml
-  #     - .github/workflows/reusable-app-reg-suit.yml
-  #     - tsconfig.base.json
-  #     - yarn.lock
-  #     - packages/app/**
-  #     - '!packages/app/docker/**'
-  #     - packages/core/**
-  #     - packages/slack/**
-  #     - packages/ui/**
-  #     - packages/plugin-**
 
 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 +48,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 }}

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

@@ -150,16 +150,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: |

+ 6 - 2
.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: |
@@ -61,11 +62,11 @@ jobs:
       run: |
         tar -cf production.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
@@ -81,7 +82,9 @@ jobs:
       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: |

+ 2 - 2
package.json

@@ -82,9 +82,9 @@
     "stylelint": "^14.2.0",
     "stylelint-config-recess-order": "^3.0.0",
     "ts-jest": "^27.0.4",
-    "ts-node": "^9.1.1",
+    "ts-node": "^10.9.1",
     "tsconfig-paths": "^3.9.0",
-    "typescript": "^4.7.3",
+    "typescript": "~4.7",
     "yargs": "^17.3.1"
   },
   "engines": {

+ 0 - 3
packages/app-next/.eslintrc.json

@@ -1,3 +0,0 @@
-{
-  "extends": "next/core-web-vitals"
-}

+ 0 - 35
packages/app-next/.gitignore

@@ -1,35 +0,0 @@
-# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
-
-# dependencies
-/node_modules
-/.pnp
-.pnp.js
-
-# testing
-/coverage
-
-# next.js
-/.next/
-/out/
-
-# production
-/build
-
-# misc
-.DS_Store
-*.pem
-
-# debug
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-.pnpm-debug.log*
-
-# local env files
-.env*.local
-
-# vercel
-.vercel
-
-# typescript
-*.tsbuildinfo

+ 0 - 20
packages/app-next/tsconfig.json

@@ -1,20 +0,0 @@
-{
-  "compilerOptions": {
-    "target": "es5",
-    "lib": ["dom", "dom.iterable", "esnext"],
-    "allowJs": true,
-    "skipLibCheck": true,
-    "strict": true,
-    "forceConsistentCasingInFileNames": true,
-    "noEmit": true,
-    "esModuleInterop": true,
-    "module": "esnext",
-    "moduleResolution": "node",
-    "resolveJsonModule": true,
-    "isolatedModules": true,
-    "jsx": "preserve",
-    "incremental": true
-  },
-  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
-  "exclude": ["node_modules"]
-}

+ 4 - 0
packages/app/.eslintignore

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

+ 11 - 1
packages/app/.eslintrc.js

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

+ 4 - 1
packages/app/.gitignore

@@ -11,12 +11,15 @@ test/cypress/videos
 /build/
 /dist/
 /transpiled/
-/report/
 /public/static/js
 /public/static/styles
 /public/uploads
 /tmp/
 
+# 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
 /public/*.chunk.js.LICENSE.txt

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


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


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


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


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

@@ -30,16 +30,16 @@ 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';
@@ -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 />,

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


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


+ 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


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

+ 0 - 0
packages/app/src/next-i18next.config.ts → packages/app/config/next-i18next.config.ts


+ 1 - 1
packages/app/docker/Dockerfile

@@ -111,11 +111,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

+ 92 - 70
packages/app/next.config.js

@@ -1,23 +1,33 @@
-import eazyLogger from 'eazy-logger';
-import { I18NextHMRPlugin } from 'i18next-hmr/plugin';
-import { withSuperjson } from 'next-superjson';
-import { WebpackManifestPlugin } from 'webpack-manifest-plugin';
+/**
+ * == 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
+ */
 
-import { i18n, localePath } from './src/next-i18next.config';
-import { listScopedPackages, listPrefixedPackages } from './src/utils/next.config.utils';
+const { withSuperjson } = require('next-superjson');
+const { PHASE_PRODUCTION_BUILD, PHASE_PRODUCTION_SERVER } = require('next/constants');
 
 
-// setup logger
-const logger = eazyLogger.Logger({
-  prefix: '[{green:next.config.js}] ',
-  useLevelPrefixes: false,
-});
+// define additional entries
+const additionalWebpackEntries = {
+  boot: './src/client/boot',
+};
 
 
-const setupWithTM = () => {
+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' }),
+    ...listScopedPackages(['@growi'], { ignorePackageNames: ['@growi/app'] }),
     // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
     'react-markdown',
     'unified',
@@ -39,63 +49,75 @@ const setupWithTM = () => {
 
   return require('next-transpile-modules')(packages);
 };
-const withTM = setupWithTM();
-
-
-// define additional entries
-const additionalWebpackEntries = {
-  boot: './src/client/boot',
-};
-
 
-/** @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,
-  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');
-
-    // configure additional entries
-    const orgEntry = config.entry;
-    config.entry = () => {
-      return orgEntry().then((entry) => {
-        return { ...entry, ...additionalWebpackEntries };
-      });
-    };
-
-    config.plugins.push(
-      new WebpackManifestPlugin({
-        fileName: 'custom-manifest.json',
-      }),
-    );
-
-    // setup i18next-hmr
-    if (!options.isServer && options.dev) {
-      config.plugins.push(new I18NextHMRPlugin({ localesDir: localePath }));
-    }
-
-    return config;
-  },
 
+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');
+
+      // configure additional entries
+      const orgEntry = config.entry;
+      config.entry = () => {
+        return orgEntry().then((entry) => {
+          return { ...entry, ...additionalWebpackEntries };
+        });
+      };
+
+      const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
+      config.plugins.push(
+        new WebpackManifestPlugin({
+          fileName: 'custom-manifest.json',
+        }),
+      );
+
+      // 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)));
 };
-
-module.exports = withSuperjson()(withTM(nextConfig));

+ 15 - 13
packages/app/package.json

@@ -4,29 +4,28 @@
   "license": "MIT",
   "scripts": {
     "//// for production": "",
-    "build": "yarn next build",
+    "build": "run-p build:*",
     "start": "yarn next start",
-    "//// for production (obsolete)": "",
-    "start:obsolete": "yarn build && yarn server",
-    "build:obsolete": "run-p build:*",
-    "build:client": "yarn cross-env NODE_ENV=production webpack --config config/webpack.prod.js",
+    "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.json",
+    "postbuild:server": "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": "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 --ci",
@@ -64,6 +63,7 @@
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@growi/codemirror-textlint": "^5.1.1-RC.0",
+    "@growi/core": "^5.1.1-RC.0",
     "@growi/plugin-attachment-refs": "^5.1.1-RC.0",
     "@growi/plugin-lsx": "^5.1.1-RC.0",
     "@growi/plugin-pukiwiki-like-linker": "^5.1.1-RC.0",
@@ -82,6 +82,7 @@
     "axios-retry": "^3.2.4",
     "body-parser": "^1.18.2",
     "browser-bunyan": "^1.6.3",
+    "bson-objectid": "^2.0.3",
     "bunyan": "^1.8.15",
     "check-node-version": "^4.1.0",
     "compression": "^1.7.4",
@@ -126,6 +127,7 @@
     "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",
@@ -183,6 +185,8 @@
     "@alienfast/i18next-loader": "^1.1.4",
     "@growi/ui": "^5.1.1-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",
@@ -214,7 +218,6 @@
     "markdown-table": "^1.1.1",
     "material-icons": "^1.11.3",
     "morgan": "^1.10.0",
-    "next-superjson": "^0.0.4",
     "next-transpile-modules": "^9.0.0",
     "normalize-path": "^3.0.0",
     "penpal": "^4.0.0",
@@ -239,7 +242,6 @@
     "sticky-events": "^3.4.11",
     "swagger2openapi": "^5.3.1",
     "swr": "^1.3.0",
-    "@icon/themify-icons": "1.0.1-alpha.3",
     "throttle-debounce": "^3.0.1",
     "toastr": "^2.1.2",
     "ts-node-dev": "^2.0.0",

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

@@ -529,6 +529,7 @@
     }
   },
   "audit_log_management": {
+    "user": "User",
     "username": "Username",
     "date": "Date",
     "action": "Action",
@@ -536,6 +537,8 @@
     "url": "URL",
     "settings": "Settings",
     "return": "Return",
+    "clear": "Clear search criteria",
+    "reload": "Reload",
     "activity_expiration_date": "Audit Log expiration date",
     "activity_expiration_date_explain": "Created Audit Log are automatically deleted after the number of seconds set in the environment variable from the creation time",
     "fixed_by_env_var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",

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

@@ -1101,6 +1101,7 @@
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
       "need_to_fix_grant": "The permissions associated with this page must be modified in order to use the functionality correctly. <br> Please select from the options below to make the change.",
       "grant_label": {
+        "public": "Public",
         "isForbidden": "Authority not allowed to view",
         "currentPageGrantLabel": "Authorization for this page: ",
         "parentPageGrantLabel": "Authority of parent page: ",

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

@@ -528,6 +528,7 @@
     }
   },
   "audit_log_management": {
+    "user": "ユーザー",
     "username": "ユーザー名",
     "date": "日付",
     "action": "アクション",
@@ -535,6 +536,8 @@
     "url": "URL",
     "settings": "設定",
     "return": "戻る",
+    "clear": "検索条件のクリア",
+    "reload": "再読み込み",
     "activity_expiration_date": "監査ログの有効期限",
     "activity_expiration_date_explain": "作成された監査ログは、作成時間から環境変数に設定した秒数後に自動的に削除されます",
     "fixed_by_env_var": "環境変数により固定されています <code>{{key}}={{value}}</code>.",

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

@@ -1094,6 +1094,7 @@
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
       "need_to_fix_grant": "正しく機能を使用するためにはこのページに紐づく権限を修正する必要があります。 <br> 下記の選択肢から選んで変更してください。",
       "grant_label": {
+        "public": "公開",
         "isForbidden": "権限の閲覧が許可されていません",
         "currentPageGrantLabel": "このページの権限: ",
         "parentPageGrantLabel": "親のページの権限: ",

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

@@ -538,6 +538,7 @@
     }
   },
   "audit_log_management": {
+    "user": "用户",
     "username": "帐号",
     "date": "日期",
     "action": "行动",
@@ -545,6 +546,8 @@
     "url": "URL",
     "settings": "设置",
     "return": "返回",
+    "clear": "清除搜索标准",
+    "reload": "重新加载",
     "activity_expiration_date": "审计日志的到期日",
     "activity_expiration_date_explain": "创建的审计日志会在环境变量中设置的从创建时间算起的秒数后自动删除",
     "fixed_by_env_var": "这是由env var 修复的 <code>{{key}}={{value}}</code>.",

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

@@ -1104,6 +1104,7 @@
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
       "need_to_fix_grant": "为了正确使用该功能,需要修改与该页面相关的权限。 <br> 请从以下选项中选择进行更改。",
       "grant_label": {
+        "public": "向公众提供",
         "isForbidden": "无权查看的机构",
         "currentPageGrantLabel": "本页的权限: ",
         "parentPageGrantLabel": "父页的权限: ",

+ 3 - 0
packages/app/src/client/interfaces/clearable.ts

@@ -0,0 +1,3 @@
+export interface IClearable {
+  clear: () => void,
+}

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

@@ -11,13 +11,9 @@ export default class AdminAppContainer extends Container {
   constructor() {
     super();
 
-    this.dummyTitle = 0;
-    this.dummyTitleForError = 1;
-
     this.state = {
       retrieveError: null,
-      // set dummy value tile for using suspense
-      title: this.dummyTitle,
+      title: '',
       confidential: '',
       globalLang: '',
       isEmailPublishedForNewUser: true,

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

@@ -17,13 +17,10 @@ export default class AdminCustomizeContainer extends Container {
   constructor() {
     super();
 
-    this.dummyCurrentTheme = 0;
-    this.dummyCurrentThemeForError = 1;
-
     this.state = {
       retrieveError: null,
       // set dummy value tile for using suspense
-      currentTheme: this.dummyCurrentTheme,
+      currentTheme: 'default',
       isEnabledTimeline: false,
       isSavedStatesOfTabChanges: false,
       isEnabledAttachTitleHeader: false,

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

@@ -18,13 +18,10 @@ export default class AdminImportContainer extends Container {
     super();
 
     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: '',

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

@@ -12,13 +12,10 @@ export default class AdminMarkDownContainer extends Container {
     super();
 
     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,

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

@@ -12,14 +12,12 @@ export default class AdminSlackIntegrationLegacyContainer extends Container {
     super();
 
     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 - 1
packages/app/src/client/util/apiv3-client.ts

@@ -13,7 +13,7 @@ const apiv3Root = '/_api/v3';
 const logger = loggerFactory('growi:apiv3');
 
 
-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);

+ 1 - 1
packages/app/src/client/util/i18n.js

@@ -17,7 +17,7 @@ Object.values(locales).forEach((locale) => {
 });
 
 /*
-* Note: This file will be deleted. use "~/next-i18next.config" instead
+* 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);

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

@@ -3,9 +3,10 @@ import React, { useCallback } from 'react';
 import { useTranslation, i18n } from 'next-i18next';
 import PropTypes from 'prop-types';
 
+import { i18n as i18nConfig } from '^/config/next-i18next.config';
+
 import AdminAppContainer from '~/client/services/AdminAppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { i18n as i18nConfig } from '~/next-i18next.config';
 import loggerFactory from '~/utils/logger';
 
 

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

@@ -1,51 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-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';
-
-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(AppSettingsPage, [AdminAppContainer]);
-
-export default AppSettingsPageWithUnstatedContainer;

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

@@ -1,8 +1,12 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 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">
       {

+ 11 - 2
packages/app/src/components/Admin/AuditLog/ActivityTable.tsx

@@ -1,5 +1,7 @@
 import React, { FC } from 'react';
 
+import { pagePathUtils } from '@growi/core';
+import { UserPicture } from '@growi/ui';
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 
@@ -21,7 +23,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
       <table className="table table-default table-bordered table-user-list">
         <thead>
           <tr>
-            <th scope="col">{t('admin:audit_log_management.username')}</th>
+            <th scope="col">{t('admin:audit_log_management.user')}</th>
             <th scope="col">{t('admin:audit_log_management.date')}</th>
             <th scope="col">{t('admin:audit_log_management.action')}</th>
             <th scope="col">{t('admin:audit_log_management.ip')}</th>
@@ -32,7 +34,14 @@ export const ActivityTable : FC<Props> = (props: Props) => {
           {props.activityList.map((activity) => {
             return (
               <tr data-testid="activity-table" key={activity._id}>
-                <td>{activity.snapshot?.username}</td>
+                <td>
+                  { activity.user != null && (
+                    <>
+                      <UserPicture user={activity.user} className="picture rounded-circle" />
+                      <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
+                    </>
+                  )}
+                </td>
                 <td>{formatDate(activity.createdAt)}</td>
                 <td>{t(`admin:audit_log_action.${activity.action}`)}</td>
                 <td>{activity.ip}</td>

+ 18 - 3
packages/app/src/components/Admin/AuditLog/SearchUsernameTypeahead.tsx

@@ -1,10 +1,11 @@
 import React, {
-  FC, Fragment, useState, useCallback,
+  Fragment, useState, useCallback, useRef, ForwardRefRenderFunction, forwardRef, useImperativeHandle,
 } from 'react';
 
 import { AsyncTypeahead, Menu, MenuItem } from 'react-bootstrap-typeahead';
 import { useTranslation } from 'react-i18next';
 
+import { IClearable } from '~/client/interfaces/clearable';
 import { useSWRxUsernames } from '~/stores/user';
 
 
@@ -25,10 +26,12 @@ type Props = {
   onChange: (text: string[]) => void
 }
 
-export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
+const SearchUsernameTypeaheadSubstance: ForwardRefRenderFunction<IClearable, Props> = ((props: Props, ref) => {
   const { onChange } = props;
   const { t } = useTranslation();
 
+  const typeaheadRef = useRef<IClearable>(null);
+
   /*
    * State
    */
@@ -96,6 +99,15 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
     );
   }, []);
 
+  useImperativeHandle(ref, () => ({
+    clear() {
+      const instance = typeaheadRef?.current;
+      if (instance != null) {
+        instance.clear();
+      }
+    },
+  }));
+
   return (
     <div className="input-group mr-2">
       <div className="input-group-prepend">
@@ -104,6 +116,7 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
         </span>
       </div>
       <AsyncTypeahead
+        ref={typeaheadRef}
         id="search-username-typeahead-asynctypeahead"
         multiple
         delay={400}
@@ -119,4 +132,6 @@ export const SearchUsernameTypeahead: FC<Props> = (props: Props) => {
       />
     </div>
   );
-};
+});
+
+export const SearchUsernameTypeahead = forwardRef(SearchUsernameTypeaheadSubstance);

+ 32 - 8
packages/app/src/components/Admin/AuditLogManagement.tsx

@@ -1,8 +1,11 @@
-import React, { FC, useState, useCallback } from 'react';
+import React, {
+  FC, useState, useCallback, useRef,
+} from 'react';
 
 import { format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 
+import { IClearable } from '~/client/interfaces/clearable';
 import { toastError } from '~/client/util/apiNotification';
 import { SupportedActionType } from '~/interfaces/activity';
 import { useSWRxActivity } from '~/stores/activity';
@@ -17,7 +20,6 @@ import { DateRangePicker } from './AuditLog/DateRangePicker';
 import { SearchUsernameTypeahead } from './AuditLog/SearchUsernameTypeahead';
 import { SelectActionDropdown } from './AuditLog/SelectActionDropdown';
 
-
 const formatDate = (date: Date | null) => {
   if (date == null) {
     return '';
@@ -30,8 +32,9 @@ const PAGING_LIMIT = 10;
 export const AuditLogManagement: FC = () => {
   const { t } = useTranslation();
 
+  const typeaheadRef = useRef<IClearable>(null);
+
   const { data: auditLogAvailableActionsData } = useAuditLogAvailableActions();
-  const auditLogAvailableActions = auditLogAvailableActionsData != null ? auditLogAvailableActionsData : [];
 
   /*
    * State
@@ -43,7 +46,7 @@ export const AuditLogManagement: FC = () => {
   const [endDate, setEndDate] = useState<Date | null>(null);
   const [selectedUsernames, setSelectedUsernames] = useState<string[]>([]);
   const [actionMap, setActionMap] = useState(
-    new Map<SupportedActionType, boolean>(auditLogAvailableActions.map(action => [action, true])),
+    new Map<SupportedActionType, boolean>(auditLogAvailableActionsData != null ? auditLogAvailableActionsData.map(action => [action, true]) : []),
   );
 
   /*
@@ -94,6 +97,18 @@ export const AuditLogManagement: FC = () => {
     setSelectedUsernames(usernames);
   }, []);
 
+  const clearButtonPushedHandler = useCallback(() => {
+    setActivePage(1);
+    setStartDate(null);
+    setEndDate(null);
+    setSelectedUsernames([]);
+    typeaheadRef.current?.clear();
+
+    if (auditLogAvailableActionsData != null) {
+      setActionMap(new Map<SupportedActionType, boolean>(auditLogAvailableActionsData.map(action => [action, true])));
+    }
+  }, [setActivePage, setStartDate, setEndDate, setSelectedUsernames, setActionMap, auditLogAvailableActionsData]);
+
   const reloadButtonPushedHandler = useCallback(() => {
     setActivePage(1);
     mutateActivity();
@@ -128,6 +143,7 @@ export const AuditLogManagement: FC = () => {
         <>
           <div className="form-inline mb-3">
             <SearchUsernameTypeahead
+              ref={typeaheadRef}
               onChange={setUsernamesHandler}
             />
 
@@ -139,14 +155,22 @@ export const AuditLogManagement: FC = () => {
 
             <SelectActionDropdown
               actionMap={actionMap}
-              availableActions={auditLogAvailableActions}
+              availableActions={auditLogAvailableActionsData || []}
               onChangeAction={actionCheckboxChangedHandler}
               onChangeMultipleAction={multipleActionCheckboxChangedHandler}
             />
 
-            <button type="button" className="btn ml-auto grw-btn-reload" onClick={reloadButtonPushedHandler}>
-              <i className="icon icon-reload" />
-            </button>
+            <div className="ml-auto">
+              <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={clearButtonPushedHandler}>
+                <span className="icon-refresh mr-1" />
+                {t('admin:audit_log_management.clear')}
+              </button>
+
+              <button type="button" className="btn btn-outline-secondary btn-sm" onClick={reloadButtonPushedHandler}>
+                <i className="icon icon-reload mr-1" />
+                {t('admin:audit_log_management.reload')}
+              </button>
+            </div>
           </div>
 
           <p

+ 0 - 56
packages/app/src/components/Admin/CustomCssEditor.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/lint/css-lint');
-require('codemirror/addon/hint/css-hint');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/css/css');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomCssEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'css',
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomCssEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 0 - 57
packages/app/src/components/Admin/CustomHeaderEditor.jsx

@@ -1,57 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/htmlmixed/htmlmixed');
-require('codemirror/addon/hint/html-hint');
-require('codemirror/addon/edit/closetag');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomHeaderEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'htmlmixed',
-          autoCloseTags: true,
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomHeaderEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 0 - 56
packages/app/src/components/Admin/CustomScriptEditor.jsx

@@ -1,56 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { UnControlled as CodeMirror } from 'react-codemirror2';
-
-require('codemirror/addon/lint/javascript-lint');
-require('codemirror/addon/hint/javascript-hint');
-require('codemirror/addon/hint/show-hint');
-require('codemirror/addon/edit/matchbrackets');
-require('codemirror/addon/edit/closebrackets');
-require('codemirror/mode/javascript/javascript');
-require('~/client/util/codemirror/autorefresh.ext');
-
-require('jquery-ui/ui/widgets/resizable');
-
-export default class CustomScriptEditor extends React.Component {
-
-  render() {
-
-    return (
-      <CodeMirror
-        value={this.props.value}
-        autoFocus
-        detach
-        options={{
-          mode: 'javascript',
-          lineNumbers: true,
-          tabSize: 2,
-          indentUnit: 2,
-          theme: 'eclipse',
-          autoRefresh: { force: true }, // force option is enabled by autorefresh.ext.js -- Yuki Takei
-          matchBrackets: true,
-          autoCloseBrackets: true,
-          extraKeys: { 'Ctrl-Space': 'autocomplete' },
-        }}
-        editorDidMount={(editor, next) => {
-          // resizable with jquery.ui
-          $(editor.getWrapperElement()).resizable({
-            resize() {
-              editor.setSize($(this).width(), $(this).height());
-            },
-          });
-        }}
-        onChange={(editor, data, value) => {
-          this.props.onChange(value);
-        }}
-      />
-    );
-  }
-
-}
-
-CustomScriptEditor.propTypes = {
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};

+ 18 - 22
packages/app/src/components/Admin/Customize/Customize.jsx

@@ -1,5 +1,5 @@
 
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import PropTypes from 'prop-types';
 
@@ -23,28 +23,24 @@ import CustomizeTitle from './CustomizeTitle';
 
 const logger = loggerFactory('growi:services:AdminCustomizePage');
 
-const retrieveErrors = null;
 function Customize(props) {
   const { adminCustomizeContainer } = props;
 
-  // if (adminCustomizeContainer.state.currentTheme === adminCustomizeContainer.dummyCurrentTheme) {
-  //   throw (async() => {
-  //     try {
-  //       await adminCustomizeContainer.retrieveCustomizeData();
-  //     }
-  //     catch (err) {
-  //       const errs = toArrayIfNot(err);
-  //       toastError(errs);
-  //       logger.error(errs);
-  //       retrieveErrors = errs;
-  //       adminCustomizeContainer.setState({ currentTheme: adminCustomizeContainer.dummyCurrentThemeForError });
-  //     }
-  //   })();
-  // }
+  useEffect(() => {
+    async function fetchCustomizeSettingsData() {
+      await adminCustomizeContainer.retrieveCustomizeData();
+    }
+
+    try {
+      fetchCustomizeSettingsData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminCustomizeContainer]);
 
-  // if (adminCustomizeContainer.state.currentTheme === adminCustomizeContainer.dummyCurrentThemeForError) {
-  //   throw new Error(`${retrieveErrors.length} errors occured`);
-  // }
 
   return (
     <div data-testid="admin-customize">
@@ -55,7 +51,9 @@ function Customize(props) {
         <CustomizeThemeSetting />
       </div>
       <div className="mb-5">
-        <CustomizeSidebarSetting />
+        {/* TODO: [resolve browser err] A component is changing an uncontrolled input to be controlled. by https://redmine.weseek.co.jp/issues/101155
+          <CustomizeSidebarSetting />
+        */}
       </div>
       <div className="mb-5">
         <CustomizeFunctionSetting />
@@ -66,7 +64,6 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeTitle />
       </div>
-      {/* TODO: show CustomizeHeaderSetting, CustomizeCssSetting and CustomizeScriptSetting by https://redmine.weseek.co.jp/issues/100534
       <div className="mb-5">
         <CustomizeHeaderSetting />
       </div>
@@ -76,7 +73,6 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeScriptSetting />
       </div>
-    */}
       <div className="mb-5">
         <CustomizeLogoSetting />
       </div>

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomCssEditor from '../CustomCssEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -43,9 +42,11 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
           </Card>
 
           <div className="form-group">
-            <CustomCssEditor
+            <textarea
+              className="form-control"
+              name="customizeCss"
               value={adminCustomizeContainer.state.currentCustomizeCss || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeCss(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomHeaderEditor from '../CustomHeaderEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -54,9 +53,11 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="form-group">
-            <CustomHeaderEditor
+            <textarea
+              className="form-control"
+              name="customizeHeader"
               value={adminCustomizeContainer.state.currentCustomizeHeader || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeHeader(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>

+ 1 - 1
packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx

@@ -12,7 +12,7 @@ const CustomizeLayoutSetting = (): JSX.Element => {
   const { resolvedTheme } = useNextThemes();
 
   const [isContainerFluid, setIsContainerFluid] = useState(false);
-  const [retrieveError, setRetrieveError] = useState();
+  const [retrieveError, setRetrieveError] = useState<any>();
 
   const retrieveData = useCallback(async() => {
     try {

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

@@ -19,7 +19,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
   const [uploadLogoSrc, setUploadLogoSrc] = useState<ArrayBuffer | string | null>(null);
   const [isImageCropModalShow, setIsImageCropModalShow] = useState<boolean>(false);
   const [isDefaultLogo, setIsDefaultLogo] = useState<boolean>(true);
-  const [retrieveError, setRetrieveError] = useState<string | null>(null);
+  const [retrieveError, setRetrieveError] = useState<any>();
   const [customizedLogoSrc, setCustomizedLogoSrc] = useState< string | null >(null);
 
   const retrieveData = useCallback(async() => {

+ 4 - 3
packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx

@@ -8,7 +8,6 @@ import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-import CustomScriptEditor from '../CustomScriptEditor';
 
 type Props = {
   adminCustomizeContainer: AdminCustomizeContainer
@@ -84,9 +83,11 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
           </div>
 
           <div className="form-group">
-            <CustomScriptEditor
+            <textarea
+              className="form-control"
+              name="customizeScript"
               value={adminCustomizeContainer.state.currentCustomizeScript || ''}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeScript(inputValue) }}
+              onChange={(e) => { adminCustomizeContainer.changeCustomizeScript(e.target.value) }}
             />
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />

+ 4 - 2
packages/app/src/components/Admin/Customize/CustomizeThemeOptions.jsx

@@ -6,6 +6,7 @@ import PropTypes from 'prop-types';
 
 import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
 import { GrowiThemes } from '~/interfaces/theme';
+import { useGrowiTheme } from '~/stores/context';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
@@ -51,6 +52,7 @@ const CustomizeThemeOptions = (props) => {
 
   const { adminCustomizeContainer } = props;
   const { t } = useTranslation();
+  const { mutate: mutateGrowiTheme } = useGrowiTheme();
   const { currentLayout, currentTheme } = adminCustomizeContainer.state;
 
   return (
@@ -64,7 +66,7 @@ const CustomizeThemeOptions = (props) => {
               <ThemeColorBox
                 key={theme.name}
                 isSelected={currentTheme === theme.name}
-                onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
+                onSelected={() => mutateGrowiTheme(theme.name)}
                 {...theme}
               />
             );
@@ -80,7 +82,7 @@ const CustomizeThemeOptions = (props) => {
               <ThemeColorBox
                 key={theme.name}
                 isSelected={currentTheme === theme.name}
-                onSelected={() => adminCustomizeContainer.switchThemeType(theme.name)}
+                onSelected={() => mutateGrowiTheme(theme.name)}
                 {...theme}
               />
             );

+ 2 - 13
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -19,7 +19,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
   const { adminCustomizeContainer } = props;
   const { t } = useTranslation();
 
-  const onClickSubmit = useCallback(async() => {
+  const submitHandler = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizeTheme();
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.theme') }));
@@ -29,24 +29,13 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     }
   }, [t, adminCustomizeContainer]);
 
-  const renderDevAlert = useCallback(() => {
-    if (process.env.NODE_ENV === 'development') {
-      return (
-        <div className="alert alert-warning">
-          <strong>DEBUG MESSAGE:</strong> Live preview for theme is disabled in development mode.
-        </div>
-      );
-    }
-  }, []);
-
   return (
     <React.Fragment>
       <div className="row">
         <div className="col-12">
           <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
-          {renderDevAlert()}
           <CustomizeThemeOptions />
-          <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
+          <AdminUpdateButtonRow onClick={submitHandler} disabled={adminCustomizeContainer.state.retrieveError != null} />
         </div>
       </div>
     </React.Fragment>

+ 1 - 0
packages/app/src/components/Admin/Customize/ThemeColorBox.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
+
 import PropTypes from 'prop-types';
 
 

+ 6 - 4
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx

@@ -44,13 +44,15 @@ const ElasticsearchManagement = () => {
       setAliasesData(info.aliases);
       setIsNormalized(info.isNormalized);
     }
-    catch (errors) {
+    catch (errors: unknown) {
       setIsConnected(false);
 
       // evaluate whether configured or not
-      for (const error of errors) {
-        if (error.code === 'search-service-unconfigured') {
-          setIsConfigured(false);
+      if (Array.isArray(errors)) {
+        for (const error of errors) {
+          if (error.code === 'search-service-unconfigured') {
+            setIsConfigured(false);
+          }
         }
       }
 

+ 23 - 1
packages/app/src/components/Admin/ImportData/ImportDataPageContents.jsx

@@ -1,14 +1,19 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 
 import AdminImportContainer from '~/client/services/AdminImportContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
 
 import { withUnstatedContainers } from '../../UnstatedUtils';
 
 import GrowiArchiveSection from './GrowiArchiveSection';
 
+const logger = loggerFactory('growi:importer');
+
 class ImportDataPageContents extends React.Component {
 
   render() {
@@ -242,6 +247,23 @@ ImportDataPageContents.propTypes = {
 const ImportDataPageContentsWrapperFc = (props) => {
   const { t } = useTranslation();
 
+  const { adminImportContainer } = props;
+
+  useEffect(() => {
+    const fetchImportSettingsData = async() => {
+      await adminImportContainer.retrieveImportSettingsData();
+    };
+
+    try {
+      fetchImportSettingsData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminImportContainer]);
+
   return <ImportDataPageContents t={t} {...props} />;
 };
 

+ 0 - 52
packages/app/src/components/Admin/ImportDataPage.jsx

@@ -1,52 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import AdminImportContainer from '~/client/services/AdminImportContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-import ImportDataPageContents from './ImportData/ImportDataPageContents';
-
-const logger = loggerFactory('growi:importer');
-
-let retrieveErrors = null;
-function ImportDataPage(props) {
-  const { adminImportContainer } = props;
-
-  if (adminImportContainer.state.esaTeamName === adminImportContainer.dummyEsaTeamName) {
-    throw (async() => {
-      try {
-        await adminImportContainer.retrieveImportSettingsData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        retrieveErrors = errs;
-        adminImportContainer.setState({ esaTeamName: adminImportContainer.dummyEsaTeamNameForError });
-      }
-    })();
-  }
-
-  if (adminImportContainer.state.esaTeamName === adminImportContainer.dummyEsaTeamNameForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <ImportDataPageContents />;
-}
-
-ImportDataPage.propTypes = {
-  adminImportContainer: PropTypes.instanceOf(AdminImportContainer).isRequired,
-};
-
-
-/**
- * Wrapper component for using unstated
- */
-const ImportDataPageWithUnstatedContainer = withUnstatedContainers(ImportDataPage, [AdminImportContainer]);
-
-export default ImportDataPageWithUnstatedContainer;

+ 18 - 23
packages/app/src/components/Admin/LegacySlackIntegration/LegacySlackIntegration.jsx

@@ -15,31 +15,26 @@ import SlackConfiguration from './SlackConfiguration';
 
 const logger = loggerFactory('growi:NotificationSetting');
 
-const retrieveErrors = null;
-function LegacySlackIntegration(props) {
+const LegacySlackIntegration = (props) => {
   const { t } = useTranslation();
   const { adminSlackIntegrationLegacyContainer } = props;
 
-  if (adminSlackIntegrationLegacyContainer.state.webhookUrl === adminSlackIntegrationLegacyContainer.dummyWebhookUrl) {
-    // TODO: Omit AdminSlackIntegrationLegacyContainer by https://redmine.weseek.co.jp/issues/100947
-
-    // throw (async() => {
-    //   try {
-    //     await adminSlackIntegrationLegacyContainer.retrieveData();
-    //   }
-    //   catch (err) {
-    //     const errs = toArrayIfNot(err);
-    //     toastError(errs);
-    //     logger.error(errs);
-    //     retrieveErrors = errs;
-    //     adminSlackIntegrationLegacyContainer.setState({ webhookUrl: adminSlackIntegrationLegacyContainer.dummyWebhookUrlForError });
-    //   }
-    // })();
-  }
-
-  if (adminSlackIntegrationLegacyContainer.state.webhookUrl === adminSlackIntegrationLegacyContainer.dummyWebhookUrlForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
+
+  useEffect(() => {
+    const fetchLegacySlackIntegrationData = async() => {
+      await adminSlackIntegrationLegacyContainer.retrieveData();
+    };
+
+    try {
+      fetchLegacySlackIntegrationData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminSlackIntegrationLegacyContainer]);
+
 
   const isDisabled = adminSlackIntegrationLegacyContainer.state.isSlackbotConfigured;
 
@@ -62,7 +57,7 @@ function LegacySlackIntegration(props) {
       <SlackConfiguration />
     </div>
   );
-}
+};
 
 const LegacySlackIntegrationWithUnstatedContainer = withUnstatedContainers(LegacySlackIntegration, [AdminSlackIntegrationLegacyContainer]);
 

+ 0 - 49
packages/app/src/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -1,49 +0,0 @@
-import React from 'react';
-
-import PropTypes from 'prop-types';
-
-import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
-import { toastError } from '~/client/util/apiNotification';
-import { toArrayIfNot } from '~/utils/array-utils';
-import loggerFactory from '~/utils/logger';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-import MarkDownSettingContents from './MarkDownSettingContents';
-
-
-const logger = loggerFactory('growi:MarkDown');
-
-let retrieveErrors = null;
-function MarkdownSetting(props) {
-  const { adminMarkDownContainer } = props;
-
-  if (adminMarkDownContainer.state.isEnabledLinebreaks === adminMarkDownContainer.dummyIsEnabledLinebreaks) {
-    throw (async() => {
-      try {
-        await adminMarkDownContainer.retrieveMarkdownData();
-      }
-      catch (err) {
-        const errs = toArrayIfNot(err);
-        toastError(errs);
-        logger.error(errs);
-        retrieveErrors = errs;
-        adminMarkDownContainer.setState({ isEnabledLinebreaks: adminMarkDownContainer.dummyIsEnabledLinebreaksForError });
-      }
-    })();
-  }
-
-  if (adminMarkDownContainer.state.isEnabledLinebreaks === adminMarkDownContainer.dummyIsEnabledLinebreaksForError) {
-    throw new Error(`${retrieveErrors.length} errors occured`);
-  }
-
-  return <MarkDownSettingContents />;
-}
-
-const MarkdownSettingWithUnstatedContainer = withUnstatedContainers(MarkdownSetting, [AdminMarkDownContainer]);
-
-MarkdownSetting.propTypes = {
-  adminMarkDownContainer: PropTypes.instanceOf(AdminMarkDownContainer).isRequired,
-};
-
-export default MarkdownSettingWithUnstatedContainer;

+ 36 - 3
packages/app/src/components/Admin/MarkdownSetting/MarkDownSettingContents.tsx

@@ -1,15 +1,44 @@
-import React from 'react';
+import React, { useEffect } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { Card, CardBody } from 'reactstrap';
 
+import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
+import { toastError } from '~/client/util/apiNotification';
+import { toArrayIfNot } from '~/utils/array-utils';
+import loggerFactory from '~/utils/logger';
+
+import { withUnstatedContainers } from '../../UnstatedUtils';
+
 import IndentForm from './IndentForm';
 import LineBreakForm from './LineBreakForm';
 import PresentationForm from './PresentationForm';
 import XssForm from './XssForm';
 
-const MarkDownSettingContents = React.memo((): JSX.Element => {
+const logger = loggerFactory('growi:MarkDown');
+
+type Props ={
+  adminMarkDownContainer: AdminMarkDownContainer
+}
+
+const MarkDownSettingContents = React.memo((props: Props): JSX.Element => {
   const { t } = useTranslation();
+  const { adminMarkDownContainer } = props;
+
+  useEffect(() => {
+    const fetchMarkdownData = async() => {
+      await adminMarkDownContainer.retrieveMarkdownData();
+    };
+
+    try {
+      fetchMarkdownData();
+    }
+    catch (err) {
+      const errs = toArrayIfNot(err);
+      toastError(errs);
+      logger.error(errs);
+    }
+  }, [adminMarkDownContainer]);
 
   return (
     <div data-testid="admin-markdown">
@@ -45,4 +74,8 @@ const MarkDownSettingContents = React.memo((): JSX.Element => {
 });
 MarkDownSettingContents.displayName = 'MarkDownSettingContents';
 
-export default MarkDownSettingContents;
+
+const MarkdownSettingWithUnstatedContainer = withUnstatedContainers(MarkDownSettingContents, [AdminMarkDownContainer]);
+
+
+export default MarkdownSettingWithUnstatedContainer;

+ 2 - 1
packages/app/src/components/Admin/SlackIntegration/Bridge.jsx

@@ -1,4 +1,5 @@
 import React from 'react';
+
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import { UncontrolledTooltip } from 'reactstrap';
@@ -20,7 +21,7 @@ const BridgeCore = (props) => {
   return (
     <>
       <div id="grw-bridge-container" className={`grw-bridge-container ${withProxy ? 'with-proxy' : ''}`}>
-        <p className="label">
+        <p className={`${withProxy ? 'mt-0' : 'mt-2'}`}>
           <i className={iconClass} />
           <small
             className="ml-2 d-none d-lg-inline"

+ 14 - 18
packages/app/src/styles/_admin.scss → packages/app/src/components/Layout/Admin.module.scss

@@ -1,14 +1,17 @@
+@use '~/styles/bootstrap/init' as *;
+@use '~/styles/mixins';
+
 $slack-work-space-name-card-background: #fff5ff;
 $slack-work-space-name-card-border: #efc1f6;
 
-.admin-page {
+.admin-page :global {
   .title {
     padding-top: 1rem;
     padding-bottom: 1rem;
 
     line-height: 1em;
 
-    @include variable-font-size(28px);
+    @include mixins.variable-font-size(28px);
     line-height: 1.1em;
   }
 
@@ -28,8 +31,6 @@ $slack-work-space-name-card-border: #efc1f6;
   }
 
   .admin-customize {
-    @import 'hljs';
-
     .ss-container img {
       padding: 0.5em;
       background-color: $gray-300;
@@ -169,15 +170,8 @@ $slack-work-space-name-card-border: #efc1f6;
 
     // switch layout for Bridge component
     .grw-bridge-container {
-      .label {
-        @extend .mt-5;
-      }
-
       // with ProxyCircle
       &.with-proxy {
-        .label {
-          @extend .mt-0;
-        }
         .hr-container {
           margin-top: 40px;
           @include media-breakpoint-up(lg) {
@@ -293,13 +287,15 @@ $slack-work-space-name-card-border: #efc1f6;
       background-color: rgba($info, 0.1);
     }
   }
-}
 
-.admin-navigation {
-  & > a + a {
-    margin-top: 2px;
-  }
-  &.sticky-top {
-    top: 30px;
+  .admin-navigation {
+    & > a + a {
+      margin-top: 2px;
+    }
+    &.sticky-top {
+      top: 30px;
+    }
   }
 }
+
+

+ 4 - 1
packages/app/src/components/Layout/AdminLayout.tsx

@@ -7,6 +7,9 @@ import { GrowiNavbar } from '../Navbar/GrowiNavbar';
 
 import { RawLayout } from './RawLayout';
 
+import styles from './Admin.module.scss';
+
+
 // import { injectableContainers } from '~/client/admin';
 
 type Props = {
@@ -29,7 +32,7 @@ const AdminLayout = ({
   const SystemVersion = dynamic(() => import('../SystemVersion'), { ssr: false });
 
   return (
-    <RawLayout title={title}>
+    <RawLayout title={title} className={`admin-page ${styles['admin-page']}`}>
       <GrowiNavbar />
 
       <header className="py-0">

+ 1 - 0
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -122,6 +122,7 @@ const AdditionalMenuItems = (props: AdditionalMenuItemsProps): JSX.Element => {
       <DropdownItem
         onClick={() => openAccessoriesModal(PageAccessoriesModalContents.ShareLink)}
         disabled={isGuestUser || isSharedUser || isLinkSharingDisabled}
+        data-testid="open-page-accessories-modal-btn-with-share-link-management-data-tab"
         className="grw-page-control-dropdown-item"
       >
         <span className="grw-page-control-dropdown-icon">

+ 5 - 1
packages/app/src/components/PageAlert/FixPageGrantAlert.tsx

@@ -76,6 +76,10 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
       return t('fix_page_grant.modal.grant_label.isForbidden');
     }
 
+    if (grantData.grant === 1) {
+      return t('fix_page_grant.modal.grant_label.public');
+    }
+
     if (grantData.grant === 4) {
       return t('fix_page_grant.modal.radio_btn.only_me');
     }
@@ -87,7 +91,7 @@ const FixPageGrantModal = (props: ModalProps): JSX.Element => {
       return `${t('fix_page_grant.modal.radio_btn.grant_group')}: (${grantData.grantedGroup.name})`;
     }
 
-    throw Error('cannnot get grant label'); // this error can't be throwed
+    throw Error('cannot get grant label'); // this error can't be throwed
   }, [t]);
 
   const renderGrantDataLabel = useCallback(() => {

+ 1 - 1
packages/app/src/components/ShareLink/ShareLink.jsx

@@ -86,7 +86,7 @@ class ShareLink extends React.Component {
     const { t } = this.props;
 
     return (
-      <div className="container p-0">
+      <div className="container p-0" data-testid="share-link-management">
         <h3 className="grw-modal-head d-flex pb-2">
           { t('share_links.share_link_list') }
           <button className="btn btn-danger ml-auto " type="button" onClick={this.deleteAllLinksButtonHandler}>{t('delete_all')}</button>

+ 11 - 10
packages/app/src/components/Theme/ThemeFireRed.module.scss

@@ -1,7 +1,8 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
-html[light] {
+.theme[data-color-scheme='light'] :global {
   // Theme colors
   $themecolor: #ea5532;
   $themelight: #ffffff;
@@ -70,8 +71,8 @@ html[light] {
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -89,12 +90,12 @@ html[light] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
     }
   }
 }
 
-html[dark] {
+.theme[data-color-scheme='dark'] :global {
   // Theme colors
   $themecolor: #ea5532;
   $themedark: #333333;
@@ -171,8 +172,8 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import 'apply-colors';
-  @import 'apply-colors-dark';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {
@@ -198,7 +199,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
     }
   }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeFireRed.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeFireRed.module.scss';
+
+const ThemeFireRed = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeFireRed;

+ 9 - 8
packages/app/src/components/Theme/ThemeIsland.module.scss

@@ -1,11 +1,12 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use '../../styles/mixins';
 
 $color-primary: #97cbc3;
 $color-themelight: rgba(183, 226, 219, 1);
 
-html[light],
-html[dark] {
+.theme :global {
   $primary: $color-primary;
   // Background colors
   $bgcolor-card: $gray-50;
@@ -81,8 +82,8 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: darken($primary, 15%);
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   .rbt-menu {
     background: lighten($color-themelight, 5%);
@@ -106,7 +107,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(darken($primary, 50%), lighten($primary, 5%), darken($primary, 5%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 50%), lighten($primary, 5%), darken($primary, 5%));
     }
   }
 
@@ -124,7 +125,7 @@ html[dark] {
     // Pagetree
     .grw-pagetree {
       .grw-pagetree-triangle-btn {
-        @include button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar);
+        @include mixins.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar);
       }
     }
   }

+ 8 - 0
packages/app/src/components/Theme/ThemeIsland.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeIsland.module.scss';
+
+const ThemeIsland = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeIsland;

+ 11 - 10
packages/app/src/components/Theme/ThemeMonoBlue.module.scss

@@ -1,7 +1,8 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
-html[light] {
+.theme[data-color-scheme='light'] :global {
   // Theme colors
   $themecolor: #00587a;
   $themelight: #f7fbfd;
@@ -70,8 +71,8 @@ html[light] {
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   // Navs {
   .nav-tabs {
@@ -89,12 +90,12 @@ html[light] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager($primary, lighten($primary, 65%), lighten($primary, 70%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager($primary, lighten($primary, 65%), lighten($primary, 70%));
     }
   }
 }
 
-html[dark] {
+.theme[data-color-scheme='dark'] :global {
   // Theme colors
   $themecolor: #00587a;
   $themedark: #061f2f;
@@ -167,8 +168,8 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: $primary;
 
-  @import 'apply-colors';
-  @import 'apply-colors-dark';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-dark';
 
   // Navs
   .nav-tabs {
@@ -194,7 +195,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(lighten($primary, 30%), $primary, darken($primary, 10%), darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 30%), $primary, darken($primary, 10%), darken($primary, 20%));
     }
   }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeMonoBlue.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeMonoBlue.module.scss';
+
+const ThemeMonoBlue = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeMonoBlue;

+ 7 - 7
packages/app/src/components/Theme/ThemeNature.module.scss

@@ -1,5 +1,6 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -35,8 +36,7 @@ $themecolor: #12b105;
 
 //== Light Mode
 //
-html[light],
-html[dark] {
+.theme :global {
   $primary: #460039;
   $light: $gray-100;
 
@@ -90,8 +90,8 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: lighten($primary, 20%);
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   // Search Top
   .grw-global-search {
@@ -111,7 +111,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager($bgcolor-navbar, lighten($bgcolor-navbar, 65%), lighten($bgcolor-navbar, 70%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager($bgcolor-navbar, lighten($bgcolor-navbar, 65%), lighten($bgcolor-navbar, 70%));
     }
   }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeNature.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeNature.module.scss';
+
+const ThemeNature = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeNature;

+ 9 - 8
packages/app/src/components/Theme/ThemeSpring.module.scss

@@ -1,5 +1,7 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
+@use '../../styles/bootstrap/init' as bs;
 
 // == Define Bootstrap theme colors
 //
@@ -25,8 +27,7 @@ $accentcolor: #e08dbc;
 
 //== Light Mode
 //
-html[light],
-html[dark] {
+.theme :global {
   $primary: $themecolor;
   $secondary: $accentcolor;
 
@@ -90,17 +91,17 @@ html[dark] {
   // admin theme box
   $color-theme-color-box: darken($primary, 20%);
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   //Button
   // Outline buttons are applyed the accent color to this spring theme cuz the primary is too light and it looks like unable to click them.
   .btn.btn-outline-primary {
-    @include button-outline-variant($accentcolor, $accentcolor, lighten($accentcolor, 20%), $accentcolor);
+    @include bs.button-outline-variant($accentcolor, $accentcolor, lighten($accentcolor, 20%), $accentcolor);
   }
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(darken($primary, 50%), lighten($primary, 5%), lighten($primary, 10%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 50%), lighten($primary, 5%), lighten($primary, 10%));
     }
   }
 

+ 8 - 0
packages/app/src/components/Theme/ThemeSpring.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeSpring.module.scss';
+
+const ThemeSpring = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeSpring;

+ 7 - 7
packages/app/src/components/Theme/ThemeWood.module.scss

@@ -1,5 +1,6 @@
-@import '../variables';
-@import '../override-bootstrap-variables';
+@use '../../styles/variables' as *;
+@use '../../styles/bootstrap/variables' as *;
+@use '../../styles/theme/mixins/page-editor-mode-manager';
 
 // == Define Bootstrap theme colors
 //
@@ -36,8 +37,7 @@ $themelight: #f5f3ee;
 
 //== Light Mode
 //
-html[light],
-html[dark] {
+.theme :global {
   $primary: #aaa45f;
 
   // Background colors
@@ -114,8 +114,8 @@ html[dark] {
   // portal
   $info: lighten($themecolor, 10%);
 
-  @import 'apply-colors';
-  @import 'apply-colors-light';
+  @import '../../styles/theme/apply-colors';
+  @import '../../styles/theme/apply-colors-light';
 
   /*
    * Modal
@@ -164,7 +164,7 @@ html[dark] {
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include btn-page-editor-mode-manager(darken($primary, 30%), lighten($primary, 15%), lighten($primary, 25%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 30%), lighten($primary, 15%), lighten($primary, 25%));
     }
   }
 }

+ 8 - 0
packages/app/src/components/Theme/ThemeWood.tsx

@@ -0,0 +1,8 @@
+import { ThemeInjector } from './utils/ThemeInjector';
+
+import styles from './ThemeWood.module.scss';
+
+const ThemeWood = ({ children }: { children: JSX.Element }): JSX.Element => {
+  return <ThemeInjector className={styles.theme}>{children}</ThemeInjector>;
+};
+export default ThemeWood;

+ 18 - 0
packages/app/src/components/Theme/utils/ThemeProvider.tsx

@@ -10,7 +10,13 @@ const ThemeAntarctic = dynamic(() => import('../ThemeAntarctic'));
 const ThemeBlackboard = dynamic(() => import('../ThemeBlackboard'));
 const ThemeChristmas = dynamic(() => import('../ThemeChristmas'));
 const ThemeDefault = dynamic(() => import('../ThemeDefault'));
+const ThemeFireRed = dynamic(() => import('../ThemeFireRed'));
 const ThemeJadeGreen = dynamic(() => import('../ThemeJadeGreen'));
+const ThemeIsland = dynamic(() => import('../ThemeIsland'));
+const ThemeSpring = dynamic(() => import('../ThemeSpring'));
+const ThemeNature = dynamic(() => import('../ThemeNature'));
+const ThemeWood = dynamic(() => import('../ThemeWood'));
+const ThemeMonoBlue = dynamic(() => import('../ThemeMonoBlue'));
 
 
 type Props = {
@@ -26,8 +32,20 @@ export const ThemeProvider = ({ theme, children }: Props): JSX.Element => {
       return <ThemeBlackboard>{children}</ThemeBlackboard>;
     case GrowiThemes.CHRISTMAS:
       return <ThemeChristmas>{children}</ThemeChristmas>;
+    case GrowiThemes.FIRE_RED:
+      return <ThemeFireRed>{children}</ThemeFireRed>;
     case GrowiThemes.JADE_GREEN:
       return <ThemeJadeGreen>{children}</ThemeJadeGreen>;
+    case GrowiThemes.ISLAND:
+      return <ThemeIsland>{children}</ThemeIsland>;
+    case GrowiThemes.SPRING:
+      return <ThemeSpring>{children}</ThemeSpring>;
+    case GrowiThemes.NATURE:
+      return <ThemeNature>{children}</ThemeNature>;
+    case GrowiThemes.WOOD:
+      return <ThemeWood>{children}</ThemeWood>;
+    case GrowiThemes.MONO_BLUE:
+      return <ThemeMonoBlue>{children}</ThemeMonoBlue>;
     default:
       return <ThemeDefault>{children}</ThemeDefault>;
   }

+ 2 - 0
packages/app/src/interfaces/activity.ts

@@ -376,6 +376,8 @@ export const SmallActionGroup = {
   ACTION_USER_LOGOUT,
   ACTION_PAGE_CREATE,
   ACTION_PAGE_DELETE,
+  ACTION_PAGE_DELETE_COMPLETELY,
+  ACTION_PAGE_EMPTY_TRASH,
 } as const;
 
 // SmallActionGroup + Action by all General Users - PAGE_VIEW

+ 3 - 4
packages/app/src/interfaces/in-app-notification.ts

@@ -1,6 +1,5 @@
-import { Types } from 'mongoose';
-import { IUser } from './user';
 import { IPage } from './page';
+import { IUser } from './user';
 
 export enum InAppNotificationStatuses {
   STATUS_UNREAD = 'UNREAD',
@@ -54,7 +53,7 @@ export interface ISubscribeRule {
   name: subscribeRuleNames;
   isEnabled: boolean;
 }
-export interface IInAppNotificationSettings {
-  userId: Types.ObjectId;
+export interface IInAppNotificationSettings<UserID> {
+  userId: UserID | string;
   subscribeRules: ISubscribeRule[];
 }

+ 1 - 1
packages/app/src/migrations/20180926134048-make-email-unique.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:make-email-unique');

+ 1 - 1
packages/app/src/migrations/20180927102719-init-serverurl.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:init-serverurl');

+ 2 - 2
packages/app/src/migrations/20181019114028-abolish-page-group-relation.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 

+ 1 - 1
packages/app/src/migrations/20190618055300-abolish-crowi-classic-auth.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:abolish-crowi-classic-auth');

+ 1 - 1
packages/app/src/migrations/20190618104011-add-config-app-installed.js

@@ -1,8 +1,8 @@
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:add-config-app-installed');

+ 2 - 2
packages/app/src/migrations/20190619055421-adjust-page-grant.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:adjust-page-grant');
 

+ 2 - 2
packages/app/src/migrations/20190624110950-fill-last-update-user.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:abolish-page-group-relation');
 

+ 2 - 2
packages/app/src/migrations/20190629193445-make-root-page-public.js

@@ -1,8 +1,8 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
-import loggerFactory from '~/utils/logger';
 import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
+import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:make-root-page-public');
 

+ 1 - 1
packages/app/src/migrations/20191102223900-drop-configs-indices.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-configs-indices');

+ 1 - 1
packages/app/src/migrations/20191102223901-drop-pages-indices.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-pages-indices');

+ 3 - 2
packages/app/src/migrations/20191126173016-adjust-pages-path.js

@@ -1,7 +1,8 @@
+import { pathUtils } from '@growi/core';
 import mongoose from 'mongoose';
-import { pathUtils, getMongoUri, mongoOptions } from '@growi/core';
-import getPageModel from '~/server/models/page';
 
+import getPageModel from '~/server/models/page';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:adjust-pages-path');

+ 1 - 1
packages/app/src/migrations/20191127023815-drop-wrong-index-of-page-tag-relation.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { getMongoUri, mongoOptions } from '@growi/core';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:drop-wrong-index-of-page-tag-relation');

+ 1 - 1
packages/app/src/migrations/20200402160380-remove-deleteduser-from-relationgroup.js

@@ -1,6 +1,6 @@
 import mongoose from 'mongoose';
 
-import { getModelSafely, getMongoUri, mongoOptions } from '@growi/core';
+import { getModelSafely, getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-deleteduser-from-relationgroup');

+ 1 - 1
packages/app/src/migrations/20200420160390-remove-crowi-layout.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-crowi-lauout');

+ 1 - 1
packages/app/src/migrations/20200512005851-remove-behavior-type.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:remove-behavior-type');

+ 1 - 1
packages/app/src/migrations/20200514001356-update-theme-color-for-dark.js

@@ -1,8 +1,8 @@
-import { getMongoUri, mongoOptions } from '@growi/core';
 import mongoose from 'mongoose';
 
 // eslint-disable-next-line import/no-named-as-default
 import Config from '~/server/models/config';
+import { getMongoUri, mongoOptions } from '~/server/util/mongoose-utils';
 import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:migrate:update-theme-color-for-dark');

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