2
0
Эх сурвалжийг харах

Merge branch 'master' into support/gw7884-dependabot-alert-passport

Yohei-Shiina 3 жил өмнө
parent
commit
8bd9a776e9
100 өөрчлөгдсөн 1923 нэмэгдсэн , 2185 устгасан
  1. 1 0
      .devcontainer/Dockerfile
  2. 19 19
      .devcontainer/devcontainer.json
  3. 2 0
      .devcontainer/docker-compose.yml
  4. 22 0
      .eslintrc.js
  5. 5 1
      .github/workflows/ci-app-prod.yml
  6. 17 8
      .github/workflows/ci-app.yml
  7. 12 8
      .github/workflows/reusable-app-prod.yml
  8. 3 0
      .gitignore
  9. 45 25
      .vscode/launch.json
  10. 16 1
      CHANGELOG.md
  11. 1 1
      lerna.json
  12. 17 12
      package.json
  13. 0 1
      packages/app/.env.development
  14. 5 0
      packages/app/.eslintignore
  15. 12 3
      packages/app/.eslintrc.js
  16. 5 2
      packages/app/.gitignore
  17. 1 0
      packages/app/_obsolete/config/webpack.common.js
  18. 1 0
      packages/app/_obsolete/config/webpack.dev.dll.js
  19. 1 0
      packages/app/_obsolete/config/webpack.dev.js
  20. 1 0
      packages/app/_obsolete/config/webpack.prod.js
  21. 9 9
      packages/app/_obsolete/src/client/admin.jsx
  22. 4 16
      packages/app/_obsolete/src/client/app.jsx
  23. 2 5
      packages/app/_obsolete/src/client/base.jsx
  24. 5 0
      packages/app/_obsolete/src/client/boot.js
  25. 0 0
      packages/app/_obsolete/src/client/installer.jsx
  26. 0 0
      packages/app/_obsolete/src/client/nologin.jsx
  27. 0 0
      packages/app/_obsolete/src/client/plugin.js
  28. 37 54
      packages/app/_obsolete/src/client/services/ContextExtractor.tsx
  29. 5 0
      packages/app/_obsolete/src/util/i18n.js
  30. 0 0
      packages/app/_obsolete/src/util/old-ios.js
  31. 0 56
      packages/app/bin/generate-plugin-definitions-source.ts
  32. 1 2
      packages/app/bin/github-actions/update-readme.sh
  33. 0 1
      packages/app/config/ci/.env.local.for-ci
  34. 6 1
      packages/app/config/migrate-mongo-config.js
  35. 27 0
      packages/app/config/next-i18next.config.ts
  36. 1 1
      packages/app/config/rate-limiter.ts
  37. 10 4
      packages/app/docker/Dockerfile
  38. 3 4
      packages/app/docker/README.md
  39. 8 1
      packages/app/jest.config.js
  40. 5 0
      packages/app/next-env.d.ts
  41. 113 0
      packages/app/next.config.js
  42. 80 88
      packages/app/package.json
  43. 316 3
      packages/app/public/static/locales/en_US/admin.json
  44. 44 312
      packages/app/public/static/locales/en_US/translation.json
  45. 0 2
      packages/app/public/static/locales/index.js
  46. 329 3
      packages/app/public/static/locales/ja_JP/admin.json
  47. 43 313
      packages/app/public/static/locales/ja_JP/translation.json
  48. 284 3
      packages/app/public/static/locales/zh_CN/admin.json
  49. 45 269
      packages/app/public/static/locales/zh_CN/translation.json
  50. 0 7
      packages/app/resource/cdn-manifests.js
  51. 8 14
      packages/app/resource/locales/en_US/sandbox-diagrams.md
  52. 1 1
      packages/app/resource/locales/en_US/sandbox-math.md
  53. 34 37
      packages/app/resource/locales/en_US/sandbox.md
  54. 1 1
      packages/app/resource/locales/en_US/welcome.md
  55. 8 15
      packages/app/resource/locales/ja_JP/sandbox-diagrams.md
  56. 1 1
      packages/app/resource/locales/ja_JP/sandbox-math.md
  57. 34 37
      packages/app/resource/locales/ja_JP/sandbox.md
  58. 1 1
      packages/app/resource/locales/ja_JP/welcome.md
  59. 8 14
      packages/app/resource/locales/zh_CN/sandbox-diagrams.md
  60. 1 1
      packages/app/resource/locales/zh_CN/sandbox-math.md
  61. 34 37
      packages/app/resource/locales/zh_CN/sandbox.md
  62. 1 1
      packages/app/resource/locales/zh_CN/welcome.md
  63. 0 9
      packages/app/src/client/boot.js
  64. 0 2
      packages/app/src/client/legacy/crowi.js
  65. 0 0
      packages/app/src/client/legacy/thirdparty-js/waves.js
  66. 1 1
      packages/app/src/client/models/BootstrapGrid.js
  67. 2 2
      packages/app/src/client/models/Linker.js
  68. 4 4
      packages/app/src/client/models/MarkdownTable.js
  69. 5 4
      packages/app/src/client/services/AdminAppContainer.js
  70. 5 6
      packages/app/src/client/services/AdminBasicSecurityContainer.js
  71. 4 53
      packages/app/src/client/services/AdminCustomizeContainer.js
  72. 5 0
      packages/app/src/client/services/AdminExternalAccountsContainer.js
  73. 5 5
      packages/app/src/client/services/AdminGeneralSecurityContainer.js
  74. 5 2
      packages/app/src/client/services/AdminGitHubSecurityContainer.js
  75. 5 2
      packages/app/src/client/services/AdminGoogleSecurityContainer.js
  76. 5 0
      packages/app/src/client/services/AdminHomeContainer.js
  77. 6 4
      packages/app/src/client/services/AdminImportContainer.js
  78. 6 4
      packages/app/src/client/services/AdminLdapSecurityContainer.js
  79. 5 0
      packages/app/src/client/services/AdminLocalSecurityContainer.js
  80. 6 4
      packages/app/src/client/services/AdminMarkDownContainer.js
  81. 5 0
      packages/app/src/client/services/AdminNotificationContainer.js
  82. 6 6
      packages/app/src/client/services/AdminOidcSecurityContainer.js
  83. 6 6
      packages/app/src/client/services/AdminSamlSecurityContainer.js
  84. 6 3
      packages/app/src/client/services/AdminSlackIntegrationLegacyContainer.js
  85. 1 24
      packages/app/src/client/services/AdminSocketIoContainer.js
  86. 6 6
      packages/app/src/client/services/AdminTwitterSecurityContainer.js
  87. 0 197
      packages/app/src/client/services/AdminUserGroupDetailContainer.js
  88. 5 4
      packages/app/src/client/services/AdminUsersContainer.js
  89. 26 22
      packages/app/src/client/services/AppContainer.js
  90. 0 106
      packages/app/src/client/services/EditorContainer.js
  91. 3 81
      packages/app/src/client/services/PageContainer.js
  92. 0 50
      packages/app/src/client/services/SocketIoContainer.js
  93. 81 15
      packages/app/src/client/services/page-operation.ts
  94. 2 22
      packages/app/src/client/util/apiv1-client.ts
  95. 4 29
      packages/app/src/client/util/apiv3-client.ts
  96. 0 28
      packages/app/src/client/util/blink-section-header.ts
  97. 0 73
      packages/app/src/client/util/color-scheme.js
  98. 3 8
      packages/app/src/client/util/editor.ts
  99. 1 5
      packages/app/src/client/util/locale-utils.ts
  100. 4 3
      packages/app/src/client/util/smooth-scroll.ts

+ 1 - 0
.devcontainer/Dockerfile

@@ -16,6 +16,7 @@ ARG USER_GID=$USER_UID
 RUN mkdir -p /workspace/growi/node_modules
 RUN mkdir -p /workspace/growi/packages/app/node_modules
 RUN mkdir -p /workspace/growi/packages/slackbot-proxy/node_modules
+RUN mkdir -p /workspace/growi/packages/app/.next
 
 # [Optional] Update UID/GID if needed
 RUN if [ "$USER_GID" != "1000" ] || [ "$USER_UID" != "1000" ]; then \

+ 19 - 19
.devcontainer/devcontainer.json

@@ -2,18 +2,18 @@
 // https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/javascript-node-12-mongo
 // If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml.
 {
-	"name": "GROWI-Dev",
-	"dockerComposeFile": "docker-compose.yml",
-	"service": "node",
-	"workspaceFolder": "/workspace/growi",
+  "name": "GROWI-Dev",
+  "dockerComposeFile": "docker-compose.yml",
+  "service": "node",
+  "workspaceFolder": "/workspace/growi",
 
-	// Set *default* container specific settings.json values on container create.
-	"settings": {
-		"terminal.integrated.defaultProfile.linux": "bash"
-	},
+  // Set *default* container specific settings.json values on container create.
+  "settings": {
+    "terminal.integrated.defaultProfile.linux": "bash"
+  },
 
-	// Add the IDs of extensions you want installed when the container is created.
-	"extensions": [
+  // Add the IDs of extensions you want installed when the container is created.
+  "extensions": [
     "dbaeumer.vscode-eslint",
     "mhutchie.git-graph",
     "eamodio.gitlens",
@@ -26,17 +26,17 @@
     "esbenp.prettier-vscode",
     "shinnn.stylelint",
     "stylelint.vscode-stylelint"
-	],
+  ],
 
-	// Uncomment the next line if you want start specific services in your Docker Compose config.
-	// "runServices": [],
+  // Uncomment the next line if you want start specific services in your Docker Compose config.
+  // "runServices": [],
 
-	// Uncomment the line below if you want to keep your containers running after VS Code shuts down.
-	// "shutdownAction": "none",
+  // Uncomment the line below if you want to keep your containers running after VS Code shuts down.
+  // "shutdownAction": "none",
 
-	// Use 'postCreateCommand' to run commands after the container is created.
-	// "postCreateCommand": "yarn install",
+  // Use 'postCreateCommand' to run commands after the container is created.
+  // "postCreateCommand": "yarn install",
 
-	// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
-	"remoteUser": "node"
+  // Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
+  "remoteUser": "node"
 }

+ 2 - 0
.devcontainer/docker-compose.yml

@@ -22,6 +22,7 @@ services:
       - node_modules:/workspace/growi/node_modules
       - node_modules_app:/workspace/growi/packages/app/node_modules
       - node_modules_slackbot-proxy:/workspace/growi/packages/slackbot-proxy/node_modules
+      - buildcache_app:/workspace/growi/packages/app/.next
       - ../../growi-docker-compose:/workspace/growi-docker-compose:delegated
 
     tty: true
@@ -96,3 +97,4 @@ volumes:
   node_modules:
   node_modules_app:
   node_modules_slackbot-proxy:
+  buildcache_app:

+ 22 - 0
.eslintrc.js

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

+ 5 - 1
.github/workflows/ci-app-prod.yml

@@ -12,13 +12,15 @@ on:
       - yarn.lock
       - packages/app/**
       - '!packages/app/docker/**'
+      - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/remark-growi-plugin/**
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**
   pull_request:
     branches:
-        - master
+      - master
     types: [opened, reopened, synchronize]
     paths:
       - .github/workflows/ci-app-prod.yml
@@ -28,7 +30,9 @@ on:
       - yarn.lock
       - packages/app/**
       - '!packages/app/docker/**'
+      - packages/codemirror-textlint/**
       - packages/core/**
+      - packages/remark-growi-plugin/**
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**

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

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

+ 12 - 8
.github/workflows/reusable-app-prod.yml

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

+ 3 - 0
.gitignore

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

+ 45 - 25
.vscode/launch.json

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

+ 16 - 1
CHANGELOG.md

@@ -1,9 +1,24 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.1.4...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v5.1.5...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v5.1.5](https://github.com/weseek/growi/compare/v5.1.4...v5.1.5) - 2022-10-04
+
+### 💎 Features
+
+- feat: Add option to not use crop modal on brand logo upload (#6677) @Yohei-Shiina
+
+### 🚀 Improvement
+
+- imprv: Emoji picker performance for v5.x (#6689) @hakumizuki
+
+### 🐛 Bug Fixes
+
+- fix(auditlog): Attachment download is displayed even if the filter is unchecked (#6688) @miya
+- fix: firstName and lastName japanese translations in SAML  (#6631) @kaoritokashiki
+
 ## [v5.1.4](https://github.com/weseek/growi/compare/v5.1.3...v5.1.4) - 2022-09-12
 
 ### 💎 Features

+ 1 - 1
lerna.json

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

+ 17 - 12
package.json

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

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

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

+ 5 - 0
packages/app/.eslintignore

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

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

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

+ 5 - 2
packages/app/.gitignore

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

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

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

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

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

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

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

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

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

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

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

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

@@ -10,7 +10,7 @@ import { Provider } from 'unstated';
 import ContextExtractor from '~/client/services/ContextExtractor';
 import EditorContainer from '~/client/services/EditorContainer';
 import PageContainer from '~/client/services/PageContainer';
-import IdenticalPathPage from '~/components/IdenticalPathPage';
+import { IdenticalPathPage } from '~/components/IdenticalPathPage';
 import PrivateLegacyPages from '~/components/PrivateLegacyPages';
 import loggerFactory from '~/utils/logger';
 import { swrGlobalConfiguration } from '~/utils/swr-utils';
@@ -20,28 +20,24 @@ import Fab from '../components/Fab';
 import ForbiddenPage from '../components/ForbiddenPage';
 import RecentlyCreatedIcon from '../components/Icons/RecentlyCreatedIcon';
 import InAppNotificationPage from '../components/InAppNotification/InAppNotificationPage';
-import MaintenanceModeContent from '../components/MaintenanceModeContent';
 import PersonalSettings from '../components/Me/PersonalSettings';
 import MyDraftList from '../components/MyDraftList/MyDraftList';
 import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSubNavigation';
 import GrowiSubNavigationSwitcher from '../components/Navbar/GrowiSubNavigationSwitcher';
 import NotFoundPage from '../components/NotFoundPage';
-import Page from '../components/Page';
+import { Page } from '../components/Page';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
-import FixPageGrantAlert from '../components/Page/FixPageGrantAlert';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
-import TrashPageAlert from '../components/Page/TrashPageAlert';
-import PageComment from '../components/PageComment';
+import { PageComment } from '../components/PageComment';
 import CommentEditorLazyRenderer from '../components/PageComment/CommentEditorLazyRenderer';
 import PageContentFooter from '../components/PageContentFooter';
 import BookmarkList from '../components/PageList/BookmarkList';
 import PageStatusAlert from '../components/PageStatusAlert';
-import PageTimeline from '../components/PageTimeline';
+import { PageTimeline } from '../components/PageTimeline';
 import RecentCreated from '../components/RecentCreated/RecentCreated';
 import { SearchPage } from '../components/SearchPage';
 import Sidebar from '../components/Sidebar';
-import TagPage from '../components/TagPage';
 import TrashPageList from '../components/TrashPageList';
 
 import { appContainer, componentMappings } from './base';
@@ -77,14 +73,11 @@ Object.assign(componentMappings, {
   'identical-path-page': <IdenticalPathPage />,
 
   // 'revision-history': <PageHistory pageId={pageId} />,
-  'tags-page': <TagPage />,
 
   'grw-page-status-alert-container': <PageStatusAlert />,
 
   'maintenance-mode-content': <MaintenanceModeContent />,
 
-  'trash-page-alert': <TrashPageAlert />,
-
   'trash-page-list-container': <TrashPageList />,
 
   'not-found-page': <NotFoundPage />,
@@ -116,11 +109,6 @@ if (pageContainer.state.pageId != null) {
 
     'recent-created-icon': <RecentlyCreatedIcon />,
   });
-  if (!pageContainer.state.isEmpty) {
-    Object.assign(componentMappings, {
-      'fix-page-grant-alert': <FixPageGrantAlert />,
-    });
-  }
 }
 if (pageContainer.state.creator != null) {
   Object.assign(componentMappings, {

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

@@ -3,7 +3,6 @@ import React from 'react';
 import EventEmitter from 'events';
 
 import AppContainer from '~/client/services/AppContainer';
-import SocketIoContainer from '~/client/services/SocketIoContainer';
 import { DescendantsPageListModal } from '~/components/DescendantsPageListModal';
 import PutbackPageModal from '~/components/PutbackPageModal';
 import ShortcutsModal from '~/components/ShortcutsModal';
@@ -13,8 +12,8 @@ import loggerFactory from '~/utils/logger';
 
 import EmptyTrashModal from '../components/EmptyTrashModal';
 import HotkeysManager from '../components/Hotkeys/HotkeysManager';
-import GrowiNavbar from '../components/Navbar/GrowiNavbar';
-import GrowiNavbarBottom from '../components/Navbar/GrowiNavbarBottom';
+import { GrowiNavbar } from '../components/Navbar/GrowiNavbar';
+import { GrowiNavbarBottom } from '../components/Navbar/GrowiNavbarBottom';
 import PageAccessoriesModal from '../components/PageAccessoriesModal';
 import PageCreateModal from '../components/PageCreateModal';
 import PageDeleteModal from '../components/PageDeleteModal';
@@ -35,8 +34,6 @@ window.interceptorManager = new InterceptorManager();
 
 // create unstated container instance
 const appContainer = new AppContainer();
-// eslint-disable-next-line no-unused-vars
-const socketIoContainer = new SocketIoContainer(appContainer);
 
 appContainer.initApp();
 

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

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

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


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


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


+ 37 - 54
packages/app/src/client/services/ContextExtractor.tsx → packages/app/_obsolete/src/client/services/ContextExtractor.tsx

@@ -1,28 +1,28 @@
+/* eslint-disable */
 import React, { FC, useEffect, useState } from 'react';
 
 import { pagePathUtils } from '@growi/core';
 
 import { CustomWindow } from '~/interfaces/global';
 import { IUserUISettings } from '~/interfaces/user-ui-settings';
-import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-import { useRendererSettings } from '~/stores/renderer';
+// import { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
 import {
   useIsDeviceSmallerThanMd, useIsDeviceSmallerThanLg,
   usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
-  useSelectedGrant, useSelectedGrantGroupId, useSelectedGrantGroupName,
+  useSelectedGrant,
 } from '~/stores/ui';
 import { useSetupGlobalSocket, useSetupGlobalAdminSocket } from '~/stores/websocket';
 
 import {
   useSiteUrl,
-  useCurrentCreatedAt, useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
-  useIsNotCreatable, useIsTrashPage, useIsUserPage, useLastUpdateUsername,
+  useDeleteUsername, useDeletedAt, useHasChildren, useHasDraftOnHackmd,
+  useIsTrashPage, useIsUserPage, useLastUpdateUsername,
   useCurrentPageId, usePageIdOnHackmd, usePageUser, useCurrentPagePath, useRevisionCreatedAt, useRevisionId, useRevisionIdHackmdSynced,
-  useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUpdatedAt, useCreator, useRevisionAuthor, useCurrentUser, useTargetAndAncestors,
-  useNotFoundTargetPathOrId, useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
+  useShareLinkId, useShareLinksNumber, useTemplateTagData, useCurrentUser, useTargetAndAncestors,
+  useIsSearchPage, useIsForbidden, useIsIdenticalPath, useHasParent,
   useIsAclEnabled, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsEnabledAttachTitleHeader,
-  useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useIsEmptyPage, useEmptyPageId, useGrowiVersion, useAuditLogEnabled,
-  useActivityExpirationSeconds, useAuditLogAvailableActions, useGrowiRendererConfig,
+  useDefaultIndentSize, useIsIndentSizeForced, useCsrfToken, useGrowiVersion, useAuditLogEnabled,
+  useActivityExpirationSeconds, useAuditLogAvailableActions, useRendererConfig,
 } from '../../stores/context';
 
 const { isTrashPage: _isTrashPage } = pagePathUtils;
@@ -63,17 +63,9 @@ const ContextExtractorOnce: FC = () => {
   const path = decodeURI(mainContent?.getAttribute('data-path') || '');
   // assign `null` to avoid returning empty string
   const pageId = mainContent?.getAttribute('data-page-id') || null;
-  const emptyPageId = notFoundContext?.getAttribute('data-page-id') || null;
 
   const revisionCreatedAt = +(mainContent?.getAttribute('data-page-revision-created') || '');
 
-  // createdAt
-  const createdAtAttribute = mainContent?.getAttribute('data-page-created-at');
-  const createdAt: Date | null = (createdAtAttribute != null) ? new Date(createdAtAttribute) : null;
-  // updatedAt
-  const updatedAtAttribute = mainContent?.getAttribute('data-page-updated-at');
-  const updatedAt: Date | null = (updatedAtAttribute != null) ? new Date(updatedAtAttribute) : null;
-
   const deletedAt = mainContent?.getAttribute('data-page-deleted-at') || null;
   const isIdenticalPath = JSON.parse(mainContent?.getAttribute('data-identical-path') || jsonNull) ?? false;
   const isUserPage = JSON.parse(mainContent?.getAttribute('data-page-user') || jsonNull) != null;
@@ -91,8 +83,6 @@ const ContextExtractorOnce: FC = () => {
   const deleteUsername = mainContent?.getAttribute('data-page-delete-username') || null;
   const pageIdOnHackmd = mainContent?.getAttribute('data-page-id-on-hackmd') || null;
   const hasDraftOnHackmd = !!mainContent?.getAttribute('data-page-has-draft-on-hackmd');
-  const creator = JSON.parse(mainContent?.getAttribute('data-page-creator') || jsonNull);
-  const revisionAuthor = JSON.parse(mainContent?.getAttribute('data-page-revision-author') || jsonNull);
   const targetAndAncestors = JSON.parse(document.getElementById('growi-pagetree-target-and-ancestors')?.textContent || jsonNull);
   const notFoundTargetPathOrId = JSON.parse(notFoundContentForPt?.getAttribute('data-not-found-target-path-or-id') || jsonNull);
   const isSearchPage = document.getElementById('search-page') != null;
@@ -129,38 +119,36 @@ const ContextExtractorOnce: FC = () => {
   useActivityExpirationSeconds(configByContextHydrate.activityExpirationSeconds);
   useAuditLogAvailableActions(configByContextHydrate.auditLogAvailableActions);
   useGrowiVersion(configByContextHydrate.crowi.version);
-  useRendererSettings({
+  useRendererConfig({
     isEnabledLinebreaks: configByContextHydrate.isEnabledLinebreaks,
     isEnabledLinebreaksInComments: configByContextHydrate.isEnabledLinebreaksInComments,
     adminPreferredIndentSize: configByContextHydrate.adminPreferredIndentSize,
     isIndentSizeForced: configByContextHydrate.isIndentSizeForced,
-  });
-  useGrowiRendererConfig({
+
     isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
     attrWhiteList: configByContextHydrate.attrWhiteList,
     tagWhiteList: configByContextHydrate.tagWhiteList,
     highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
-    env: {
-      MATHJAX: configByContextHydrate.env.MATHJAX,
-      PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
-      BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
-    },
+
+    plantumlUri: configByContextHydrate.env.PLANTUML_URI,
+    blockdiagUri: configByContextHydrate.env.BLOCKDIAG_URI,
   });
+  // useNoCdn(configByContextHydrate.env.NO_CDN);
+  // useUploadableImage(configByContextHydrate.upload.image);
+  // useUploadableFile(configByContextHydrate.upload.file);
 
   // Page
-  useCurrentCreatedAt(createdAt);
   useDeleteUsername(deleteUsername);
   useDeletedAt(deletedAt);
   useHasChildren(hasChildren);
   useHasDraftOnHackmd(hasDraftOnHackmd);
   useIsIdenticalPath(isIdenticalPath);
-  useIsNotCreatable(isNotCreatable);
+  // useIsNotCreatable(isNotCreatable);
   useIsForbidden(isForbidden);
-  useIsTrashPage(isTrashPage);
+  // useIsTrashPage(isTrashPage);
   useIsUserPage(isUserPage);
   useLastUpdateUsername(lastUpdateUsername);
   useCurrentPageId(pageId);
-  useEmptyPageId(emptyPageId);
   usePageIdOnHackmd(pageIdOnHackmd);
   usePageUser(pageUser);
   useCurrentPagePath(path);
@@ -170,13 +158,8 @@ const ContextExtractorOnce: FC = () => {
   useShareLinkId(shareLinkId);
   useShareLinksNumber(shareLinksNumber);
   useTemplateTagData(templateTagData);
-  useCurrentUpdatedAt(updatedAt);
-  useCreator(creator);
-  useRevisionAuthor(revisionAuthor);
   useTargetAndAncestors(targetAndAncestors);
-  useNotFoundTargetPathOrId(notFoundTargetPathOrId);
   useIsSearchPage(isSearchPage);
-  useIsEmptyPage(isEmptyPage);
   useHasParent(hasParent);
 
   // Navigation
@@ -190,9 +173,9 @@ const ContextExtractorOnce: FC = () => {
   useIsDeviceSmallerThanMd();
 
   // Editor
-  useSelectedGrant(grant);
-  useSelectedGrantGroupId(grantGroupId);
-  useSelectedGrantGroupName(grantGroupName);
+  // useSelectedGrant(grant);
+  // useSelectedGrantGroupId(grantGroupId);
+  // useSelectedGrantGroupName(grantGroupName);
 
   // SearchResult
   useIsDeviceSmallerThanLg();
@@ -204,21 +187,21 @@ const ContextExtractorOnce: FC = () => {
 
   // TODO: Remove this code when reveal.js is omitted. see: https://github.com/weseek/growi/pull/6223
   // Do not access this property from other than reveal.js plugins.
-  (window as CustomWindow).previewRenderer = generatePreviewRenderer(
-    {
-      isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
-      attrWhiteList: configByContextHydrate.attrWhiteList,
-      tagWhiteList: configByContextHydrate.tagWhiteList,
-      highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
-      env: {
-        MATHJAX: configByContextHydrate.env.MATHJAX,
-        PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
-        BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
-      },
-    },
-    null,
-    path,
-  );
+  // (window as CustomWindow).previewRenderer = generatePreviewRenderer(
+  //   {
+  //     isEnabledXssPrevention: configByContextHydrate.isEnabledXssPrevention,
+  //     attrWhiteList: configByContextHydrate.attrWhiteList,
+  //     tagWhiteList: configByContextHydrate.tagWhiteList,
+  //     highlightJsStyleBorder: configByContextHydrate.highlightJsStyleBorder,
+  //     env: {
+  //       MATHJAX: configByContextHydrate.env.MATHJAX,
+  //       PLANTUML_URI: configByContextHydrate.env.PLANTUML_URI,
+  //       BLOCKDIAG_URI: configByContextHydrate.env.BLOCKDIAG_URI,
+  //     },
+  //   },
+  //   null,
+  //   path,
+  // );
 
   return null;
 };

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

@@ -1,3 +1,5 @@
+
+/* eslint-disable */
 import i18n from 'i18next';
 import LanguageDetector from 'i18next-browser-languagedetector';
 import { initReactI18next } from 'react-i18next';
@@ -14,6 +16,9 @@ Object.values(locales).forEach((locale) => {
   });
 });
 
+/*
+* Note: This file will be deleted. use "^/config/next-i18next.config" instead
+*/
 // extract metadata list from 'public/static/locales/${locale}/meta.json'
 export const localeMetadatas = Object.values(locales).map(locale => locale.meta);
 

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


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

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

+ 1 - 2
packages/app/bin/github-actions/update-readme.sh

@@ -2,5 +2,4 @@
 
 cd docker
 
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`5\.1\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md
-sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`5\.1-nocdn\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}-nocdn\2\3${RELEASED_VERSION}\4/" README.md
+sed -i -e "s/^\([*] \[\`\)[^\`]\+\(\`, \`6\.0\`, .\+\]\)\(.\+\/blob\/v\).\+\(\/packages\/app\/docker\/Dockerfile.\+\)$/\1${RELEASED_VERSION}\2\3${RELEASED_VERSION}\4/" README.md

+ 0 - 1
packages/app/config/ci/.env.local.for-ci

@@ -1,2 +1 @@
 FORMAT_NODE_LOG=true
-MATHJAX=1

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

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

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

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

+ 1 - 1
packages/app/config/rate-limiter.ts

@@ -33,7 +33,7 @@ export const defaultConfig: IApiRateLimitEndpointMap = {
     maxRequests: MAX_REQUESTS_TIER_1,
     usersPerIpProspection: 100,
   },
-  '/login/activateInvited': {
+  '/invited': {
     method: 'POST',
     maxRequests: MAX_REQUESTS_TIER_2,
   },

+ 10 - 4
packages/app/docker/Dockerfile

@@ -26,6 +26,10 @@ ENV optDir /opt
 
 WORKDIR ${optDir}
 
+ENV nodeModulesGrowiPackagesDir ${optDir}/node_modules/@growi
+# expect a string seperated by commas (e.g. "A,B")
+ENV removeNodeModulesSymlinkPaths ${nodeModulesGrowiPackagesDir}/slackbot-proxy
+
 # copy files
 COPY --from=packages-json-picker ${optDir} .
 
@@ -33,6 +37,9 @@ COPY --from=packages-json-picker ${optDir} .
 RUN yarn config set network-timeout 300000
 RUN npx -y lerna bootstrap -- --frozen-lockfile
 
+# remove unnecessary symlinks
+RUN rm -f $(echo ${removeNodeModulesSymlinkPaths} | sed -e "s/,/ /g")
+
 # make artifacts
 RUN tar -cf node_modules.tar \
   node_modules \
@@ -55,7 +62,6 @@ RUN tar -cf node_modules.tar \
   packages/*/node_modules
 
 
-
 ##
 ## prebuilder-default
 ##
@@ -74,7 +80,6 @@ RUN tar -xf node_modules.tar
 RUN rm node_modules.tar
 
 
-
 ##
 ## prebuilder-nocdn
 ##
@@ -94,6 +99,7 @@ ENV optDir /opt
 
 WORKDIR ${optDir}
 
+# ignore eslint and stylelint
 COPY ["package.json", "lerna.json", "tsconfig.base.json", "./"]
 # copy all related packages
 COPY packages/app packages/app
@@ -101,9 +107,9 @@ COPY packages/core packages/core
 COPY packages/codemirror-textlint packages/codemirror-textlint
 COPY packages/plugin-attachment-refs packages/plugin-attachment-refs
 COPY packages/plugin-lsx packages/plugin-lsx
-COPY packages/plugin-pukiwiki-like-linker packages/plugin-pukiwiki-like-linker
 COPY packages/slack packages/slack
 COPY packages/ui packages/ui
+COPY packages/remark-growi-plugin packages/remark-growi-plugin
 
 # build
 RUN yarn lerna run build
@@ -111,11 +117,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

+ 3 - 4
packages/app/docker/README.md

@@ -10,10 +10,9 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`5.1.4`, `5.1`, `5`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.4/packages/app/docker/Dockerfile)
-* [`5.1.4-nocdn`, `5.1-nocdn`, `5-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.1.4/packages/app/docker/Dockerfile)
-* [`5.0.11`, `5.0` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.11/packages/app/docker/Dockerfile)
-* [`5.0.11-nocdn`, `5.0-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v5.0.11/packages/app/docker/Dockerfile)
+* [`6.0.0`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.0/packages/app/docker/Dockerfile)
+* [`5.1.5`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.5/packages/app/docker/Dockerfile)
+* [`5.1.5-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.5/packages/app/docker/Dockerfile)
 * [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 * [`4.5.23-nocdn`, `4.5-nocdn`, `4-nocdn`, `latest-nocdn` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 

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

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

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

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

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

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

+ 80 - 88
packages/app/package.json

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

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

@@ -1,4 +1,276 @@
 {
+  "meta": {
+    "display_name": "English"
+  },
+  "wiki_management_home_page": "Wiki Management Home Page",
+  "app_settings": "App Settings",
+  "security_settings": {
+    "security_settings": "Security Settings",
+    "Guest Users Access": "Guest users access",
+    "always_hidden": "Always hidden",
+    "always_displayed": "Always displayed",
+    "displayed_or_hidden": "Displayed / Hidden",
+    "Fixed by env var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",
+    "Register limitation": "Register limitation",
+    "Register limitation desc": "Restriction of new users' registration",
+    "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
+    "users_without_account": "Users without account is not accessible",
+    "example": "Example",
+    "restrict_emails": "You can restrict email registration to your wiki by writing an email domain (beginning with @). ",
+    "for_example": " For example, if you would like to restrict registration to users within the growi.org domain, you can write ",
+    "in_this_case": "; in this case, only users within the growi.org domain would be able to register, and all other users would be rejected.",
+    "insert_single": "Please insert single e-mail address per line.",
+    "page_list_and_search_results": "Page list / Search results",
+    "page_listing_1": "Page listing/searching<br>restricted by 'Only me'",
+    "page_listing_1_desc": "Show pages that are restricted by 'Only me' option when listing/searching",
+    "page_listing_2": "Page listing/searching<br>restricted by User group",
+    "page_listing_2_desc": "Show pages that are restricted by User group when listing/searching",
+    "page_access_rights": "Page access",
+    "page_delete_rights": "Delete rights",
+    "page_delete": "Page Delete",
+    "page_delete_completely": "Page Delete Completely",
+    "other_options": "Other options",
+    "deletion_explain": "Restricts users who can trash the selected single page.",
+    "complete_deletion_explain": "Restricts users who can completely delete  selected single page.",
+    "recursive_deletion_explain": "Restricts users who can trash pages including descendants.",
+    "recursive_complete_deletion_explain": "Restricts users who can completely delete pages including descendants.",
+    "inherit": "Inherit(Use the same setting as for a single page)",
+    "admin_only": "Admin only",
+    "admin_and_author": "Admin and author",
+    "anyone": "Anyone",
+    "session": "Session",
+    "max_age": "Max age (msec)",
+    "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
+    "max_age_caution": "Restarting the server is required after you modify this value.",
+    "forced_update_desc": "Settings have been forcibly changed. Previous setting: ",
+    "page_delete_rights_caution": "The \"Delete / Delete All\" permission (including descendant pages) is forced to be stronger than the \"Delete / Completely Delete\" permission. <br> <br> Admin only > Admin and autor > Anyone",
+    "Authentication mechanism settings": "Authentication Mechanism Settings",
+    "setup_is_not_yet_complete": "Setup is not yet complete",
+    "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
+    "xss_prevent_setting": "Prevent XSS(Cross Site Scripting)",
+    "xss_prevent_setting_link": "Go to Markdown Settings",
+    "callback_URL": "Callback URL",
+    "providerName": "Provider Name",
+    "issuerHost": "Issuer Host",
+    "scope": "Scope",
+    "desc_of_callback_URL": "Use it in the setting of the {{AuthName}} Identity provider",
+    "authorization_endpoint": "Authorization Endpoint",
+    "token_endpoint": "Token Endpoint",
+    "revocation_endpoint": "Revocation Endpoint",
+    "introspection_endpoint": "Introspection Endpoint",
+    "userinfo_endpoint": "UserInfo Endpoint",
+    "end_session_endpoint": "EndSessioin Endpoint",
+    "registration_endpoint": "Registration Endpoint",
+    "jwks_uri": "JSON Web Key Set URL",
+    "clientID": "Client ID",
+    "client_secret": "Client Secret",
+    "updated_general_security_setting": "Succeeded to update security setting",
+    "setup_not_completed_yet": "Setup not completed yet",
+    "guest_mode": {
+      "deny": "Deny (Registered users only)",
+      "readonly": "Accept (Guests can read only)"
+    },
+    "registration_mode": {
+      "open": "Open (Anyone can register)",
+      "restricted": "Restricted (Requires approval by administrators)",
+      "closed": "Closed (Invitation Only)"
+    },
+    "share_link_rights": "Share link rights",
+    "enable_link_sharing": "Enable link sharing",
+    "all_share_links": "All share links",
+    "configuration": " Configuration",
+    "optional": "Optional",
+    "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
+    "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
+    "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>email</code> match",
+    "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>email</code>.",
+    "Use env var if empty": "Use env var <code>{{env}}</code> if empty",
+    "Use default if both are empty": "If both ​​are empty, the default value <code>{{target}}</code> is used.",
+    "missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
+    "Local": {
+      "name": "ID/Password",
+      "note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+      "enable_local": "Enable ID/Password",
+      "password_reset_by_users": "Password reset by users",
+      "enable_password_reset_by_users": "Enable password reset by users",
+      "password_reset_desc": "when forgot password, users are able to reset it by themselves.",
+      "email_authentication": "Email authentication on user registration",
+      "enable_email_authentication": "Enable email authentication",
+      "enable_email_authentication_desc": "Email authentication is going to be performed for user registration.",
+      "please_enable_mailer": "Please setup mailer first.",
+      "need_complete_mail_setting_warning": "To use the following functions, please complete the mail settings."
+    },
+    "ldap": {
+      "enable_ldap": "Enable LDAP",
+      "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
+      "bind_mode": "Binding Mode",
+      "bind_manager": "Manager Bind",
+      "bind_user": "User Bind",
+      "bind_DN_manager_detail": "The DN of the account that authenticates and queries the directory service",
+      "bind_DN_user_detail1": "The query used to bind with the directory service.",
+      "bind_DN_user_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+      "bind_DN_password": "Bind DN Password",
+      "bind_DN_password_manager_detail": "The password for the Bind DN account.",
+      "bind_DN_password_user_detail": "The password that is entered in the login page will be used to bind.",
+      "search_filter": "Search Filter",
+      "search_filter_detail1": "The query used to locate the authenticated user.",
+      "search_filter_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+      "search_filter_detail3": "If empty, the filter <code>(uid=&#123;&#123;username&#125;&#125;)</code> is used.",
+      "search_filter_example1": "Match with 'uid' or 'mail'",
+      "search_filter_example2": "Match with 'sAMAccountName' for Active Directory",
+      "username_detail": "Specification of mappings for <code>username</code> when creating new users",
+      "name_detail": "Specification of mappings for full name when creating new users",
+      "mail_detail": "Specification of mappings for mail address when creating new users",
+      "group_search_base_DN": "Group Search Base DN",
+      "group_search_base_DN_detail": "The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.",
+      "group_search_filter": "Group Search Filter",
+      "group_search_filter_detail1": "The query used to filter for groups.",
+      "group_search_filter_detail2": "Login via LDAP is accepted only when this query hits one or more groups.",
+      "group_search_filter_detail3": "Use <code>&#123;&#123;dn&#125;&#125;</code> to have it replaced of the found user object.",
+      "group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
+      "group_search_user_DN_property": "User DN Property",
+      "group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
+      "test_config": "Test Saved Configuration",
+      "updated_ldap": "Succeeded to update LDAP setting"
+    },
+    "SAML": {
+      "name": "SAML",
+      "enable_saml": "Enable SAML",
+      "id_detail": "Specification of the name of attribute which can identify the user in SAML Identity Provider",
+      "username_detail": "Specification of mappings for <code>username</code> when creating new users",
+      "mapping_detail": "Specification of mappings for {{target}} when creating new users",
+      "cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
+      "Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
+      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+      "attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
+      "attr_based_login_control_rule_help": "<h5>Supported Queries:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>Unsupported Queries:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>Escaping special characters</h5>It is needed to escape following special characters:<br><code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
+      "attr_based_login_control_rule_example1": "<h5>Example for conditions</h5>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",
+      "attr_based_login_control_rule_example2": "<h5>Example for escaping</h5>If you would like to use URL as a query value, escape the following:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
+      "updated_saml": "Succeeded to update SAML setting"
+    },
+    "Basic": {
+      "enable_basic": "Enable Basic",
+      "name": "Basic Authentication",
+      "desc_1": "Login with <code>username</code> in Authorization header.",
+      "desc_2": "User will be automatically generated if not exist.",
+      "updated_basic": "Succeeded to update Basic setting"
+    },
+    "OAuth": {
+      "enable_oidc": "Enable OIDC",
+      "register": "Register for %s",
+      "change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
+      "Google": {
+        "enable_google": "Enable Google OAuth",
+        "name": "Google OAuth",
+        "register_1": "Access {{link}}",
+        "register_2": "Create Project if no projects exist",
+        "register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+        "register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+        "register_5": "Copy and paste your ClientID and Client Secret above",
+        "updated_google": "Succeeded to update Google OAuth setting"
+      },
+      "Facebook": {
+        "name": "Facebook OAuth"
+      },
+      "Twitter": {
+        "enable_twitter": "Enable Twitter OAuth",
+        "name": "Twitter OAuth",
+        "register_1": "Access {{link}}",
+        "register_2": "Sign in Twitter",
+        "register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+        "register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+        "register_5": "Copy and paste your ClientID and Client Secret above",
+        "updated_twitter": "Succeeded to update Twitter OAuth setting"
+      },
+      "GitHub": {
+        "enable_github": "Enable GitHub OAuth",
+        "name": "GitHub OAuth",
+        "register_1": "Access {{link}}",
+        "register_2": "Register your OAuth App with \"Authorization callback URL\" as <code>{{url}}</code>",
+        "register_3": "Copy and paste your ClientID and Client Secret above",
+        "updated_github": "Succeeded to update GitHub OAuth setting"
+      },
+      "OIDC": {
+        "name": "OpenID Connect",
+        "id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
+        "username_detail": "Specification of mappings for <code>username</code> when creating new users",
+        "name_detail": "Specification of mappings for <code>name</code> when creating new users",
+        "mapping_detail": "Specification of mappings for %s when creating new users",
+        "register_1": "Contant to OIDC IdP Administrator",
+        "register_2": "Register your OIDC App with \"Authorization callback URL\" as <code>%s</code>",
+        "register_3": "Copy and paste your ClientID and Client Secret above",
+        "updated_oidc": "Succeeded to update OpenID Connect",
+        "Use discovered URL if empty": "Use discovered URL from \"Issuer Host\" if empty"
+      },
+      "how_to": {
+        "google": "How to configure Google OAuth?",
+        "github": "How to configure GitHub OAuth?",
+        "twitter": "How to configure Twitter OAuth?",
+        "oidc": "How to configure OIDC?"
+      }
+    },
+    "form_item_name": {
+      "entryPoint": "Entry point",
+      "issuer": "Issuer",
+      "cert": "Certificate",
+      "attrMapId": "ID",
+      "attrMapUsername": "Username",
+      "attrMapMail": "Mail Address",
+      "attrMapFirstName": "First Name",
+      "attrMapLastName": "Last Name",
+      "ABLCRule": "Rule"
+    }
+  },
+  "notification_settings": {
+    "notification_settings": "Notification Settings",
+    "slack_incoming_configuration": "Slack Incoming Webhooks configuration",
+    "prioritize_webhook": "Prioritize incoming webhook than Slack App",
+    "prioritize_webhook_desc": "Check this option and GROWI use Incoming Webhooks even if Slack App settings are enabled.",
+    "slack_app_configuration": "Slack app configuration",
+    "slack_app_configuration_desc": "This is the way that compatible with Crowi,<br /> but not recommended in GROWI because it is <strong>too complex</strong>.",
+    "use_instead":"Please use Slack Incoming Webhooks Configuration instead.",
+    "how_to": {
+      "header": "How to configure Incoming Webhooks?",
+      "workspace": "(At Workspace) Add a hook",
+      "workspace_desc1": "Go to <a href='https://slack.com/services/new/incoming-webhook'>Incoming Webhooks configuration page</a>.",
+      "workspace_desc2": "Choose the default channel to post.",
+      "workspace_desc3": "Add.",
+      "at_growi": "(At GROWI admin page) Set Webhook URL",
+      "at_growi_desc": "Input &rdquo;Webhook URL&rdquo; and submit on this page."
+    },
+    "user_trigger_notification_header": "Default notification settings for patterns",
+    "pattern": "Pattern",
+    "channel": "Channel",
+    "pattern_desc": "Path name of wiki. Pattern expression with <code>*</code> can be used.",
+    "channel_desc": "Slack channel name. Without <code>#</code>.",
+    "valid_page": "Enable/disable Notification",
+    "link_notification_help": "<strong>The page that is able to be viewed only by those who know the link 'Anyone with the link'</strong> is not notified always.",
+    "just_me_notification_help": "<strong>The page that is restricted by 'Only Me'</strong> is notify when the page edited.",
+    "group_notification_help": "<strong>The page that is restricted by 'User Group'</strong> is notify when the page edited.",
+    "notification_list": "List of notification settings",
+    "add_notification": "Add new",
+    "trigger_path": "Trigger path",
+    "trigger_path_help": "(expression with <code>*</code> is supported)",
+    "trigger_events": "Trigger events",
+    "notify_to": "Notify to",
+    "back_to_list": "Go back to list",
+    "notification_detail": "Notification Setting Details",
+    "event_pageCreate": "When new page is \"CREATED\"",
+    "event_pageEdit": "When page is \"EDITED\"",
+    "event_pageDelete": "When page is \"DELETED\"",
+    "event_pageMove": "When page is \"MOVED\" (renamed)",
+    "event_pageLike": "When someone \"LIKES\" page",
+    "event_comment": "When someone \"COMMENTS\" on page",
+    "email": {
+      "ifttt_link": "Create a new IFTTT applet with Email trigger"
+    },
+    "updated_slackApp": "Succeeded to update Slack App Configuration setting",
+    "add_notification_pattern": "Add user trigger notification patterns",
+    "delete_notification_pattern": "Delete notification pattern",
+    "delete_notification_pattern_desc1": "Delete Path: {{path}}",
+    "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
+    "toggle_notification": "Updated setting of {{path}}"
+  },
   "mailer_setup_required":"<a href='/admin/app'>Email settings</a> are required to send.",
   "admin_top": {
     "management_wiki": "Management Wiki",
@@ -105,7 +377,8 @@
     "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
     "note_for_the_only_env_option": "The GCS Settings is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> ."
   },
-  "markdown_setting": {
+  "markdown_settings": {
+    "markdown_settings": "Markdown Settings",
     "lineBreak_header": "Line break setting",
     "lineBreak_desc": "You can change line break settings.",
     "lineBreak_options": {
@@ -148,7 +421,8 @@
       "import_recommended": "Import recommended {{target}}"
     }
   },
-  "customize_setting": {
+  "customize_settings": {
+    "customize_settings": "Customize",
     "default_sidebar_mode": {
       "title": "Default sidebar mode",
       "desc": "You can set the sidebar mode for new users and guests visiting the page.",
@@ -216,6 +490,7 @@
     "delete_logo": "Delete Logo"
   },
   "importer_management": {
+    "import_data": "Import Data",
     "beta_warning": "This function is Beta.",
     "import_from": "Import from {{from}}",
     "import_growi_archive": "Import GROWI archive",
@@ -287,6 +562,7 @@
     "Directory_hierarchy_tag": "Directory hierarchy tag"
   },
   "export_management": {
+    "export_archive_data": "Export Archive Data",
     "exporting_collection_list": "Exporting Collection List",
     "exported_data_list": "Exported Archive Data List",
     "export_collections": "Export Collections",
@@ -305,12 +581,14 @@
     "delete": "Delete"
   },
   "external_notification": {
+    "external_notification": "External Notification",
     "enabled": "Enabled",
     "disabled": "Disabled",
     "header_status": "Slack Integration Status",
     "caution_enabled": "CAUTION: Currently, notifications that are configured in this page will send only to the Slack Workspace set as primary."
   },
   "slack_integration": {
+    "slack_integration": "Slack Integration",
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "Detailed explanation",
@@ -422,12 +700,16 @@
     }
   },
   "slack_integration_legacy": {
+    "slack_integration_legacy": "Legacy Slack Integration",
+    "alert_disabled": "This 'Slack Legacy Intenfation' has been currently disabled since <a href='/admin/slack-integration'>New settings</a> are enabled",
     "alert_Pd": "This 'Legacy Slack Integration' is currently disabled because <a href='/admin/slack-integration'>the new settings</a> is enabled.",
     "alert_deplicated": "This 'Legacy Slack Integration' is outdated and will be discontinued in the future. Use <a href='/admin/slack-integration'>the new settings</a> instead. "
   },
   "user_management": {
+    "user_management": "User Management",
     "invite_users": "Temporarily issue a new user",
     "click_twice_same_checkbox": "You should check at least one checkbox.",
+    "status": "Status",
     "invite_modal": {
       "emails": "Emails (Possible to issue multiple people with new lines)",
       "description1":"Temporarily issue new users by email addresses.",
@@ -483,12 +765,14 @@
     "current_users": "Current users:"
   },
   "user_group_management": {
+    "user_group_management": "User Group Management",
     "create_group": "Create new group",
     "add_child_group": "Add child group",
     "remove_child_group": "Remove",
     "deny_create_group": "You can't create a new group with the current settings.",
     "group_name": "Group name",
     "group_example": "e.g. : Group1",
+    "child_user_group": "Child User Group",
     "parent_group": "Parent Group",
     "select_parent_group": "Select Parent Group",
     "release_parent_group": "Release parent group",
@@ -528,7 +812,32 @@
       "force_update_parents_description": "Enable this option to force the addition of missing users to the ancestor groups if they exist after changing a parent group."
     }
   },
+  "full_text_search_management": {
+    "full_text_search_management": "Full Text Search Management",
+    "elasticsearch_management": "Elasticsearch management",
+    "connection_status": "Connection status",
+    "connection_status_label_unconfigured": "UNCONFIGURED",
+    "connection_status_label_connected": "CONNECTED",
+    "connection_status_label_disconnected": "DISCONNECTED",
+    "connection_status_label_erroroccured": "ERROR OCCURED ON SEARCH SERVICE",
+    "indices_status": "Indices Status",
+    "indices_status_label_normalized": "NORMALIZED",
+    "indices_status_label_unnormalized": "REBUILDING or BROKEN",
+    "indices_summary": "Indices summary",
+    "reconnect": "Reconnect",
+    "reconnect_button": "Try to reconnect",
+    "reconnect_description": "Click the button to try to reconnect to Elasticsearch.",
+    "normalize": "Normalize",
+    "normalize_button": "Normalize indices",
+    "normalize_description": "Click the button to repair broken indices.",
+    "rebuild": "Rebuild",
+    "rebuild_button": "Rebuild index",
+    "rebuild_description_1": "Click the button to rebuild index and add all page datas.",
+    "rebuild_description_2": "This may take a while."
+  },
   "audit_log_management": {
+    "audit_log": "Audit Log",
+    "audit_log_settings": "Audit Log Settings",
     "user": "User",
     "username": "Username",
     "date": "Date",
@@ -598,6 +907,10 @@
     "PAGE_DELETE_COMPLETELY": "Delete completely page",
     "PAGE_REVERT": "Revert page",
     "PAGE_EMPTY_TRASH": "Empty trash",
+    "PAGE_RECURSIVELY_RENAME": "Recursive page rename",
+    "PAGE_RECURSIVELY_DELETE": "Recursive page delete",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "Recursive page delete completely",
+    "PAGE_RECURSIVELY_REVERT": "Recursive page revert",
     "PAGE_SUBSCRIBE": "Subscribe page",
     "PAGE_UNSUBSCRIBE": "Unsubscribe page",
     "PAGE_EXPORT": "Export page",
@@ -615,7 +928,7 @@
     "SHARE_LINK_NOT_FOUND": "Page view (Not found share link)",
     "ATTACHMENT_ADD": "Add Attachment",
     "ATTACHMENT_REMOVE": "Delete Attachment",
-    "ACTION_ATTACHMENT_DOWNLOAD": "Download Attachment",
+    "ATTACHMENT_DOWNLOAD": "Download Attachment",
     "SEARCH_PAGE": "Page Search",
     "SEARCH_PAGE_VIEW": "Page view(Search results page)",
     "ADMIN_APP_SETTING_UPDATE": "Update App Settings",

+ 44 - 312
packages/app/public/static/locales/en_US/translation.json

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "English"
+  },
   "Help": "Help",
   "view": "View",
   "Edit": "Edit",
@@ -16,6 +19,7 @@
   "Move/Rename": "Move/Rename",
   "Redirected": "Redirected",
   "Unlinked": "Unlinked",
+  "unlink_redirection": "Unlink redirection",
   "Done": "Done",
   "Cancel": "Cancel",
   "Create": "Create",
@@ -27,6 +31,7 @@
   "New": "New",
   "Close": "Close",
   "Shortcuts": "Shortcuts",
+  "CustomSidebar": "Custom Sidebar",
   "eg": "e.g.",
   "add": "Add",
   "Undo": "Undo",
@@ -35,7 +40,6 @@
   "Page Path": "Page path",
   "Category": "Category",
   "User": "User",
-  "status": "Status",
   "account_id": "Account Id",
   "Update": "Update",
   "Update Page": "Update Page",
@@ -74,7 +78,7 @@
   "username": "Username",
   "Created": "Created",
   "Last updated": "Updated",
-  "Last_Login": "Last login",
+  "last_login": "Last login",
   "Share": "Share",
   "Markdown Link": "Markdown Link",
   "Create/Edit Template": "Create/Edit template page",
@@ -109,28 +113,12 @@
   "Input page name (optional)": "Input page name (optional)",
   "New Page": "New page",
   "Create under": "Create page under below:",
-  "Wiki Management Home Page": "Wiki Management Home Page",
-  "App Settings": "App Settings",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "See_more_detail_on_new_schema": "See more detail on <a href='#'>{{url}}</a> <i class='icon-share-alt'></i> ",
   "Site URL settings": "Site URL settings",
-  "Markdown Settings": "Markdown Settings",
-  "Customize": "Customize",
-  "Notification Settings": "Notification Settings",
-  "slack_integration": "Slack Integration",
-  "External_Notification": "External Notification",
-  "Legacy_Slack_Integration": "Legacy Slack Integration",
-  "User_Management": "User Management",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
-  "ChildUserGroup": "ChildUserGroup",
-  "UserGroup Management": "UserGroup Management",
-  "AuditLog": "Audit Log",
-  "AuditLog Settings": "Audit Log Settings",
-  "Full Text Search Management": "Full Text Search Management",
-  "Import Data": "Import Data",
-  "Export Archive Data": "Export Archive Data",
   "Basic Settings": "Basic Settings",
   "Basic authentication": "Basic authentication",
   "Register limitation": "Register limitation",
@@ -143,9 +131,6 @@
   "page_list": "Page List",
   "scope_of_page_disclosure": "Scope of page disclosure",
   "set_point": "Set point",
-  "always_displayed": "Always displayed",
-  "always_hidden": "Always hidden",
-  "displayed_or_hidden": "Displayed / Hidden",
   "Reselect the group": "Reselect the group",
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
@@ -165,7 +150,6 @@
   "add_bookmark": "Add to Bookmarks",
   "remove_bookmark": "Remove from Bookmarks",
   "wide_view": "Wide View",
-  "Recent Created": "Recent Created",
   "Recent Changes": "Recent Changes",
   "Page Tree": "Page Tree",
   "original_path":"Original path",
@@ -196,6 +180,9 @@
     "page_not_exist": "This page does not exist.",
     "page_not_exist_alert": "This page does not exist. Please create a new page."
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "Couldn't create path."
+  },
   "custom_navigation": {
     "no_page_list": "There are no pages under this page.",
     "link_sharing_is_disabled": "Link sharing is disabled."
@@ -204,7 +191,9 @@
     "setup": "Setup",
     "create_initial_account": "Create an initial account",
     "initial_account_will_be_administrator_automatically": "The initial account will be administrator automatically.",
-    "unavaliable_user_id": "This 'User ID' is unavailable."
+    "unavaliable_user_id": "This 'User ID' is unavailable.",
+    "failed_to_install": "Failed to install GROWI. Please try again.",
+    "failed_to_login_after_install": "Failed to login after installation. Redirecting to the login form ..."
   },
   "breaking_changes": {
     "v346_using_basic_auth": "Basic Authentication currently in use will <strong>no longer be available</strong> in the near future. Remove settings from %s"
@@ -248,7 +237,6 @@
     "new_password_confirm": "Re-enter new password",
     "password_is_not_set": "Password is not set"
   },
-  "security_settings": "Security settings",
   "share_links": {
     "Shere this page link to public": "Shere this page link to public",
     "share_link_list": "Share link list",
@@ -390,7 +378,8 @@
     "overwrite_scopes": "{{operation}} and Overwrite scopes of all descendants",
     "notice": {
       "conflict": "Couldn't save the changes you made because someone else was editing this page. Please re-edit the affected section after reloading the page."
-    }
+    },
+    "changes_not_saved": "Changes you made may not be saved."
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",
@@ -681,295 +670,17 @@
       "error_duplicate_pages_found": "Multiple pages with the same path name were found. Please rename or delete and try again."
     }
   },
-  "security_setting": {
-    "Guest Users Access": "Guest users access",
-    "Fixed by env var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",
-    "Register limitation": "Register limitation",
-    "Register limitation desc": "Restriction of new users' registration",
-    "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
-    "users_without_account": "Users without account is not accessible",
-    "example": "Example",
-    "restrict_emails": "You can restrict email registration to your wiki by writing an email domain (beginning with @). ",
-    "for_example": " For example, if you would like to restrict registration to users within the growi.org domain, you can write ",
-    "in_this_case": "; in this case, only users within the growi.org domain would be able to register, and all other users would be rejected.",
-    "insert_single": "Please insert single e-mail address per line.",
-    "page_list_and_search_results": "Page list / Search results",
-    "page_listing_1": "Page listing/searching<br>restricted by 'Only me'",
-    "page_listing_1_desc": "Show pages that are restricted by 'Only me' option when listing/searching",
-    "page_listing_2": "Page listing/searching<br>restricted by User group",
-    "page_listing_2_desc": "Show pages that are restricted by User group when listing/searching",
-    "page_access_rights": "Page access",
-    "page_delete_rights": "Delete rights",
-    "page_delete": "Page Delete",
-    "page_delete_completely": "Page Delete Completely",
-    "other_options": "Other options",
-    "deletion_explain": "Restricts users who can trash the selected single page.",
-    "complete_deletion_explain": "Restricts users who can completely delete  selected single page.",
-    "recursive_deletion_explain": "Restricts users who can trash pages including descendants.",
-    "recursive_complete_deletion_explain": "Restricts users who can completely delete pages including descendants.",
-    "inherit": "Inherit(Use the same setting as for a single page)",
-    "admin_only": "Admin only",
-    "admin_and_author": "Admin and author",
-    "anyone": "Anyone",
-    "session": "Session",
-    "max_age": "Max age (msec)",
-    "max_age_desc": "Specifies the number (in milliseconds) to expire users session.<br>Default: 2592000000 (30days)",
-    "max_age_caution": "Restarting the server is required after you modify this value.",
-    "forced_update_desc": "Settings have been forcibly changed. Previous setting: ",
-    "page_delete_rights_caution": "The \"Delete / Delete All\" permission (including descendant pages) is forced to be stronger than the \"Delete / Completely Delete\" permission. <br> <br> Admin only > Admin and autor > Anyone",
-    "Authentication mechanism settings": "Authentication Mechanism Settings",
-    "setup_is_not_yet_complete": "Setup is not yet complete",
-    "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
-    "xss_prevent_setting": "Prevent XSS(Cross Site Scripting)",
-    "xss_prevent_setting_link": "Go to Markdown Settings",
-    "callback_URL": "Callback URL",
-    "providerName": "Provider Name",
-    "issuerHost": "Issuer Host",
-    "scope": "Scope",
-    "desc_of_callback_URL": "Use it in the setting of the {{AuthName}} Identity provider",
-    "authorization_endpoint": "Authorization Endpoint",
-    "token_endpoint": "Token Endpoint",
-    "revocation_endpoint": "Revocation Endpoint",
-    "introspection_endpoint": "Introspection Endpoint",
-    "userinfo_endpoint": "UserInfo Endpoint",
-    "end_session_endpoint": "EndSessioin Endpoint",
-    "registration_endpoint": "Registration Endpoint",
-    "jwks_uri": "JSON Web Key Set URL",
-    "clientID": "Client ID",
-    "client_secret": "Client Secret",
-    "updated_general_security_setting": "Succeeded to update security setting",
-    "setup_not_completed_yet": "Setup not completed yet",
-    "guest_mode": {
-      "deny": "Deny (Registered users only)",
-      "readonly": "Accept (Guests can read only)"
-    },
-    "registration_mode": {
-      "open": "Open (Anyone can register)",
-      "restricted": "Restricted (Requires approval by administrators)",
-      "closed": "Closed (Invitation Only)"
-    },
-    "share_link_rights": "Share link rights",
-    "enable_link_sharing": "Enable link sharing",
-    "all_share_links": "All share links",
-    "configuration": " Configuration",
-    "optional": "Optional",
-    "Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
-    "Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
-    "Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>email</code> match",
-    "Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>email</code>.",
-    "Use env var if empty": "Use env var <code>{{env}}</code> if empty",
-    "Use default if both are empty": "If both ​​are empty, the default value <code>{{target}}</code> is used.",
-    "missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
-    "Local": {
-      "name": "ID/Password",
-      "note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
-      "enable_local": "Enable ID/Password",
-      "password_reset_by_users": "Password reset by users",
-      "enable_password_reset_by_users": "Enable password reset by users",
-      "password_reset_desc": "when forgot password, users are able to reset it by themselves.",
-      "email_authentication": "Email authentication on user registration",
-      "enable_email_authentication": "Enable email authentication",
-      "enable_email_authentication_desc": "Email authentication is going to be performed for user registration.",
-      "please_enable_mailer": "Please setup mailer first.",
-      "need_complete_mail_setting_warning": "To use the following functions, please complete the mail settings."
-    },
-    "ldap": {
-      "enable_ldap": "Enable LDAP",
-      "server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
-      "bind_mode": "Binding Mode",
-      "bind_manager": "Manager Bind",
-      "bind_user": "User Bind",
-      "bind_DN_manager_detail": "The DN of the account that authenticates and queries the directory service",
-      "bind_DN_user_detail1": "The query used to bind with the directory service.",
-      "bind_DN_user_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
-      "bind_DN_password": "Bind DN Password",
-      "bind_DN_password_manager_detail": "The password for the Bind DN account.",
-      "bind_DN_password_user_detail": "The password that is entered in the login page will be used to bind.",
-      "search_filter": "Search Filter",
-      "search_filter_detail1": "The query used to locate the authenticated user.",
-      "search_filter_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
-      "search_filter_detail3": "If empty, the filter <code>(uid=&#123;&#123;username&#125;&#125;)</code> is used.",
-      "search_filter_example1": "Match with 'uid' or 'mail'",
-      "search_filter_example2": "Match with 'sAMAccountName' for Active Directory",
-      "username_detail": "Specification of mappings for <code>username</code> when creating new users",
-      "name_detail": "Specification of mappings for full name when creating new users",
-      "mail_detail": "Specification of mappings for mail address when creating new users",
-      "group_search_base_DN": "Group Search Base DN",
-      "group_search_base_DN_detail": "The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.",
-      "group_search_filter": "Group Search Filter",
-      "group_search_filter_detail1": "The query used to filter for groups.",
-      "group_search_filter_detail2": "Login via LDAP is accepted only when this query hits one or more groups.",
-      "group_search_filter_detail3": "Use <code>&#123;&#123;dn&#125;&#125;</code> to have it replaced of the found user object.",
-      "group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
-      "group_search_user_DN_property": "User DN Property",
-      "group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
-      "test_config": "Test Saved Configuration",
-      "updated_ldap": "Succeeded to update LDAP setting"
-    },
-    "SAML": {
-      "name": "SAML",
-      "enable_saml": "Enable SAML",
-      "id_detail": "Specification of the name of attribute which can identify the user in SAML Identity Provider",
-      "username_detail": "Specification of mappings for <code>username</code> when creating new users",
-      "mapping_detail": "Specification of mappings for {{target}} when creating new users",
-      "cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
-      "Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
-      "note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
-      "attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
-      "attr_based_login_control_rule_help": "<h5>Supported Queries:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>Unsupported Queries:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>Escaping special characters</h5>It is needed to escape following special characters:<br><code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
-      "attr_based_login_control_rule_example1": "<h5>Example for conditions</h5>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",
-      "attr_based_login_control_rule_example2": "<h5>Example for escaping</h5>If you would like to use URL as a query value, escape the following:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
-      "updated_saml": "Succeeded to update SAML setting"
-    },
-    "Basic": {
-      "enable_basic": "Enable Basic",
-      "name": "Basic Authentication",
-      "desc_1": "Login with <code>username</code> in Authorization header.",
-      "desc_2": "User will be automatically generated if not exist.",
-      "updated_basic": "Succeeded to update Basic setting"
-    },
-    "OAuth": {
-      "enable_oidc": "Enable OIDC",
-      "register": "Register for %s",
-      "change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
-      "Google": {
-        "enable_google": "Enable Google OAuth",
-        "name": "Google OAuth",
-        "register_1": "Access {{link}}",
-        "register_2": "Create Project if no projects exist",
-        "register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
-        "register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
-        "register_5": "Copy and paste your ClientID and Client Secret above",
-        "updated_google": "Succeeded to update Google OAuth setting"
-      },
-      "Facebook": {
-        "name": "Facebook OAuth"
-      },
-      "Twitter": {
-        "enable_twitter": "Enable Twitter OAuth",
-        "name": "Twitter OAuth",
-        "register_1": "Access {{link}}",
-        "register_2": "Sign in Twitter",
-        "register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
-        "register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
-        "register_5": "Copy and paste your ClientID and Client Secret above",
-        "updated_twitter": "Succeeded to update Twitter OAuth setting"
-      },
-      "GitHub": {
-        "enable_github": "Enable GitHub OAuth",
-        "name": "GitHub OAuth",
-        "register_1": "Access {{link}}",
-        "register_2": "Register your OAuth App with \"Authorization callback URL\" as <code>{{url}}</code>",
-        "register_3": "Copy and paste your ClientID and Client Secret above",
-        "updated_github": "Succeeded to update GitHub OAuth setting"
-      },
-      "OIDC": {
-        "name": "OpenID Connect",
-        "id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
-        "username_detail": "Specification of mappings for <code>username</code> when creating new users",
-        "name_detail": "Specification of mappings for <code>name</code> when creating new users",
-        "mapping_detail": "Specification of mappings for %s when creating new users",
-        "register_1": "Contant to OIDC IdP Administrator",
-        "register_2": "Register your OIDC App with \"Authorization callback URL\" as <code>%s</code>",
-        "register_3": "Copy and paste your ClientID and Client Secret above",
-        "updated_oidc": "Succeeded to update OpenID Connect",
-        "Use discovered URL if empty": "Use discovered URL from \"Issuer Host\" if empty"
-      },
-      "how_to": {
-        "google": "How to configure Google OAuth?",
-        "github": "How to configure GitHub OAuth?",
-        "twitter": "How to configure Twitter OAuth?",
-        "oidc": "How to configure OIDC?"
-      }
-    },
-    "form_item_name": {
-      "entryPoint": "Entry point",
-      "issuer": "Issuer",
-      "cert": "Certificate",
-      "attrMapId": "ID",
-      "attrMapUsername": "Username",
-      "attrMapMail": "Mail Address",
-      "attrMapFirstName": "First Name",
-      "attrMapLastName": "Last Name",
-      "ABLCRule": "Rule"
-    }
-  },
-  "notification_setting": {
-    "slack_incoming_configuration": "Slack Incoming Webhooks configuration",
-    "prioritize_webhook": "Prioritize incoming webhook than Slack App",
-    "prioritize_webhook_desc": "Check this option and GROWI use Incoming Webhooks even if Slack App settings are enabled.",
-    "slack_app_configuration": "Slack app configuration",
-    "slack_app_configuration_desc": "This is the way that compatible with Crowi,<br /> but not recommended in GROWI because it is <strong>too complex</strong>.",
-    "use_instead":"Please use Slack Incoming Webhooks Configuration instead.",
-    "how_to": {
-      "header": "How to configure Incoming Webhooks?",
-      "workspace": "(At Workspace) Add a hook",
-      "workspace_desc1": "Go to <a href='https://slack.com/services/new/incoming-webhook'>Incoming Webhooks configuration page</a>.",
-      "workspace_desc2": "Choose the default channel to post.",
-      "workspace_desc3": "Add.",
-      "at_growi": "(At GROWI admin page) Set Webhook URL",
-      "at_growi_desc": "Input &rdquo;Webhook URL&rdquo; and submit on this page."
-    },
-    "user_trigger_notification_header": "Default notification settings for patterns",
-    "pattern": "Pattern",
-    "channel": "Channel",
-    "pattern_desc": "Path name of wiki. Pattern expression with <code>*</code> can be used.",
-    "channel_desc": "Slack channel name. Without <code>#</code>.",
-    "valid_page": "Enable/disable Notification",
-    "link_notification_help": "<strong>The page that is able to be viewed only by those who know the link 'Anyone with the link'</strong> is not notified always.",
-    "just_me_notification_help": "<strong>The page that is restricted by 'Only Me'</strong> is notify when the page edited.",
-    "group_notification_help": "<strong>The page that is restricted by 'User Group'</strong> is notify when the page edited.",
-    "notification_list": "List of notification settings",
-    "add_notification": "Add new",
-    "trigger_path": "Trigger path",
-    "trigger_path_help": "(expression with <code>*</code> is supported)",
-    "trigger_events": "Trigger events",
-    "notify_to": "Notify to",
-    "back_to_list": "Go back to list",
-    "notification_detail": "Notification Setting Details",
-    "event_pageCreate": "When new page is \"CREATED\"",
-    "event_pageEdit": "When page is \"EDITED\"",
-    "event_pageDelete": "When page is \"DELETED\"",
-    "event_pageMove": "When page is \"MOVED\" (renamed)",
-    "event_pageLike": "When someone \"LIKES\" page",
-    "event_comment": "When someone \"COMMENTS\" on page",
-    "email": {
-      "ifttt_link": "Create a new IFTTT applet with Email trigger"
-    },
-    "updated_slackApp": "Succeeded to update Slack App Configuration setting",
-    "add_notification_pattern": "Add user trigger notification patterns",
-    "delete_notification_pattern": "Delete notification pattern",
-    "delete_notification_pattern_desc1": "Delete Path: {{path}}",
-    "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-    "toggle_notification": "Updated setting of {{path}}"
-  },
-  "full_text_search_management": {
-    "elasticsearch_management": "Elasticsearch management",
-    "connection_status": "Connection status",
-    "connection_status_label_unconfigured": "UNCONFIGURED",
-    "connection_status_label_connected": "CONNECTED",
-    "connection_status_label_disconnected": "DISCONNECTED",
-    "connection_status_label_erroroccured": "ERROR OCCURED ON SEARCH SERVICE",
-    "indices_status": "Indices Status",
-    "indices_status_label_normalized": "NORMALIZED",
-    "indices_status_label_unnormalized": "REBUILDING or BROKEN",
-    "indices_summary": "Indices summary",
-    "reconnect": "Reconnect",
-    "reconnect_button": "Try to reconnect",
-    "reconnect_description": "Click the button to try to reconnect to Elasticsearch.",
-    "normalize": "Normalize",
-    "normalize_button": "Normalize indices",
-    "normalize_description": "Click the button to repair broken indices.",
-    "rebuild": "Rebuild",
-    "rebuild_button": "Rebuild index",
-    "rebuild_description_1": "Click the button to rebuild index and add all page datas.",
-    "rebuild_description_2": "This may take a while."
-  },
   "to_cloud_settings": "Open GROWI.cloud Settings",
   "login": {
     "Sign in error": "Login error",
     "Registration successful": "Registration successful",
-    "Setup": "Setup"
+    "Setup": "Setup",
+    "enabled_ldap_has_configuration_problem":"LDAP is enabled but the configuration has something wrong.",
+    "set_env_var_for_logs": "(Please set the environment variables <code>DEBUG=crowi:service:PassportService</code> to get the logs)"
+  },
+  "invited": {
+    "discription_heading": "Create Account",
+    "discription": "Create an your account with the invited email address"
   },
   "export_bulk": {
     "failed_to_export": "Failed to export",
@@ -983,8 +694,9 @@
     "fail_to_fetch_access_token": "Failed to fetch access_token. Please do connect again.",
     "successfully_disconnected": "Successfully Disconnected!",
     "strategy_has_not_been_set_up": "{{strategy}} has not been set up",
+    "ldap_user_not_valid": "Ldap user is no valid",
+    "external_account_not_exist": "Failed to find or create External account",
     "maximum_number_of_users": "Can not register more than the maximum number of users.",
-    "database_error": "Database Server Error occured",
     "sign_in_failure": "Sign in failure.",
     "aws_sttings_required": "AWS settings required to use this function. Please ask the administrator.",
     "application_already_installed": "Application already installed.",
@@ -1002,7 +714,21 @@
     "complete_to_install2":"Complete to Install GROWI ! Please check each settings on this page first.",
     "failed_to_create_admin_user":"Failed to create admin user. {{errMessage}}",
     "successfully_send_email_auth":"We sent an email to {{email}}. Please click the URL in the email and complete the registration.",
-    "incorrect_token_or_expired_url": "The token is incorrect or the URL has expired."
+    "incorrect_token_or_expired_url": "The token is incorrect or the URL has expired.",
+    "user_already_loggedin": "You cannot create a new account when you are logged in.",
+    "registration_closed": "You are not authorized to create a new account.",
+    "Username has invalid characters": "Username has invalid characters.",
+    "Username field is required": "User ID field is required.",
+    "Name field is required": "Name field is required.",
+    "Email format is invalid": "Email format is invalid.",
+    "Email field is required": "Email field is required.",
+    "Password has invalid character": "Password has invalid character.",
+    "Password minimum character should be more than 8 characters": "Password minimum character should be more than 8 characters.",
+    "Password field is required": "Password field is required.",
+    "Username or E-mail has invalid characters": "Username or E-mail has invalid characters.",
+    "Password minimum character should be more than 6 characters": "Password minimum character should be more than 6 characters.",
+    "user_not_found": "User not found.",
+    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>DuplicatedUsernameException occured</strong></p><p class='mb-0'> Your {{ failedProviderForDuplicatedUsernameException }} authentication was succeeded, but a new user could not be created. See the issue <a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
   },
   "grid_edit":{
     "create_bootstrap_4_grid":"Create Bootstrap 4 Grid",
@@ -1029,6 +755,7 @@
     "confirm_new_password": "Confirm the new password",
     "email_is_required": "Email is required",
     "success_to_send_email": "Success to send email",
+    "feature_is_unavailable": "This feature is unavailable.",
     "incorrect_token_or_expired_url": "The token is incorrect or the URL has expired. Please resend a password reset request via the link below.",
     "password_and_confirm_password_does_not_match": "Password and confirm password does not match"
   },
@@ -1088,6 +815,7 @@
   "crop_image_modal": {
     "image_crop": "Image Crop",
     "crop": "Crop",
+    "save": "Save",
     "reset": "Reset",
     "cancel": "Cancel"
   },
@@ -1134,5 +862,9 @@
   "page_operation":{
     "paths_recovered": "Paths recovered successfully",
     "path_recovery_failed":"Path recovery failed"
+  },
+  "footer": {
+    "bookmarks": "Bookmarks",
+    "recently_created": "Recently Created"
   }
 }

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

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

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

@@ -1,4 +1,308 @@
 {
+  "meta": {
+    "display_name": "日本語"
+  },
+  "Update": "更新",
+  "Delete": "削除",
+  "User": "ユーザー",
+  "Name": "名前",
+  "Page": "ページ",
+  "Created": "作成日",
+  "Edit": "編集",
+  "Description": "説明",
+  "last_login": "最終ログイン",
+  "wiki_management_home_page": "Wiki管理トップ",
+  "app_settings": "アプリ設定",
+  "public": "公開",
+  "anyone_with_the_link": "リンクを知っている人のみ",
+  "specified_users": "特定ユーザーのみ",
+  "only_me": "自分のみ",
+  "only_inside_the_group": "特定グループのみ",
+  "security_settings": {
+    "security_settings": "セキュリティ設定",
+    "scope_of_page_disclosure": "ページの公開範囲",
+    "set_point": "設定値",
+    "Guest Users Access":"ゲストユーザーのアクセス",
+    "always_hidden": "非表示 (固定)",
+    "always_displayed": "表示 (固定)",
+    "displayed_or_hidden": "表示 / 非表示",
+    "Fixed by env var": "環境変数 <code>{{forcewikimode}}={{wikimode}}</code> により固定されています。",
+    "Register limitation": "登録の制限",
+    "Register limitation desc": "新しいユーザーを登録する方法を制限します.",
+    "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
+    "users_without_account": "アカウントを持たないユーザーはアクセス不可",
+    "example": "例",
+    "restrict_emails": "登録可能なメールアドレスを制限することができます。",
+    "for_example": "例えば、",
+    "in_this_case": "と記載することで、そのドメインのメールアドレスを持っている人のみ登録可能になります。",
+    "insert_single": "1行に1メールアドレス入力してください。",
+    "page_list_and_search_results": "ページリスト・検索結果",
+    "page_listing_1": "ページのリスト表示と検索<br>'自分のみ'に閲覧制限しているページ",
+    "page_listing_1_desc": "ページのリスト表示や検索結果において、'自分のみ'に閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
+    "page_listing_2": "ページのリスト表示と検索<br>特定グループに閲覧制限しているページ",
+    "page_listing_2_desc": "ページのリスト表示や検索結果において、特定グループにのみ閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
+    "page_access_rights": "ページの閲覧権限",
+    "page_delete_rights": "ページの削除権限",
+    "page_delete": "ゴミ箱に入れる",
+    "page_delete_completely": "完全に削除する",
+    "other_options": "その他のオプション",
+    "deletion_explain": "ページをゴミ箱に入れることができるユーザーを制限します。",
+    "complete_deletion_explain": "ページを完全削除することができるユーザーを制限します。",
+    "recursive_deletion_explain": "子孫を含めたページをゴミ箱に入れることができるユーザーを制限します。",
+    "recursive_complete_deletion_explain": "子孫を含めたページを完全削除することができるユーザーを制限します。",
+    "inherit": "単体のみと同じ",
+    "admin_only": "管理者のみ可能",
+    "admin_and_author": "管理者とページ作者が可能",
+    "anyone": "誰でも可能",
+    "session": "セッション",
+    "max_age": "有効期間 (ミリ秒)",
+    "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
+    "max_age_caution": "この値を変更した後は、サーバーを再起動する必要があります。",
+    "forced_update_desc": "設定が強制変更されました。前回の設定: ",
+    "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 管理者のみ可能 > 管理者とページ作者が可能 > 誰でも可能",
+    "Authentication mechanism settings": "認証機構設定",
+    "setup_is_not_yet_complete":"セットアップはまだ完了してません",
+    "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
+    "xss_prevent_setting": "XSS(Cross Site Scripting)対策設定",
+    "xss_prevent_setting_link": "マークダウン設定ページに移動",
+    "callback_URL": "コールバックURL",
+    "desc_of_callback_URL": "{{AuthName}} プロバイダ側の設定で利用してください。",
+    "authorization_endpoint": "認可エンドポイント",
+    "token_endpoint": "トークンエンドポイント",
+    "revocation_endpoint": "失効エンドポイント",
+    "introspection_endpoint": "検証エンドポイント",
+    "userinfo_endpoint": "ユーザ情報エンドポイント",
+    "end_session_endpoint": "セッション終了エンドポイント",
+    "registration_endpoint": "登録エンドポイント",
+    "jwks_uri": "JSON Web Key Set URL",
+    "clientID": "クライアントID",
+    "client_secret": "クライアントシークレット",
+    "updated_general_security_setting": "セキュリティ設定を更新しました。",
+    "setup_not_completed_yet": "まだセットアップは完了していません。",
+    "guest_mode": {
+      "deny": "拒否 (アカウントを持つユーザーのみ利用可能)",
+      "readonly": "許可 (ゲストユーザーも閲覧のみ可能)"
+    },
+    "registration_mode": {
+      "open": "公開 (だれでも登録可能)",
+      "restricted": "制限 (登録完了には管理者の承認が必要)",
+      "closed": "非公開 (登録には管理者による招待が必要)"
+    },
+    "share_link_rights": "シェアリンクの権限",
+    "enable_link_sharing": "リンクのシェアを許可",
+    "all_share_links": "全てのシェアリンク",
+    "configuration": "設定",
+    "optional": "オプション",
+    "Treat username matching as identical": "新規ログイン時、<code>username</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
+    "Treat username matching as identical_warn": "警告: <code>username</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
+    "Treat email matching as identical": "新規ログイン時、<code>email</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
+    "Treat email matching as identical_warn": "警告: <code>email</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
+    "Use env var if empty": "空の場合、環境変数 <code>{{env}}</code> を利用します",
+    "Use default if both are empty": "どちらの値も空の場合、デフォルト値 <code>{{target}}</code> を利用します",
+    "missing mandatory configs": "以下の必須項目の値がデータベースと環境変数のどちらにも設定されていません",
+    "Local": {
+      "name": "ID/Password",
+      "note for the only env option": "現在LOCAL認証のON/OFFは環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
+      "enable_local": "ID/Password を有効にする",
+      "password_reset_by_users": "ユーザーによるパスワード再設定",
+      "enable_password_reset_by_users": "ユーザーによるパスワード再設定を有効にする",
+      "password_reset_desc": "ログイン時のパスワードを忘れた際に、ユーザー自身がパスワードを再設定できます。",
+      "email_authentication": "ユーザー登録時のメール認証",
+      "enable_email_authentication": "メール認証を有効にする",
+      "enable_email_authentication_desc": "ユーザー登録時にメール認証を行います。",
+      "please_enable_mailer": "メール認証を有効にするには、メール設定を完了させてください。",
+      "need_complete_mail_setting_warning": "以下の機能を使えるようにするには、メール設定を完了させてください。"
+    },
+    "ldap": {
+      "enable_ldap": "LDAP を有効にする",
+      "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",
+      "bind_mode": "Bind モード",
+      "bind_manager": "管理者 Bind",
+      "bind_user": "ユーザー Bind",
+      "bind_DN_manager_detail": "ディレクトリーサービスに認証する際のアカウント DN",
+      "bind_DN_user_detail1": "ディレクトリーサービスに Bind するアカウント DN を決定するためのクエリ",
+      "bind_DN_user_detail2": "ログイン時に入力されるユーザー名を使用するには <code>&#123;&#123;username&#125;&#125;</code> の形式を使用してください。",
+      "bind_DN_password": "Bind DN パスワード",
+      "bind_DN_password_manager_detail": "Bind DN アカウントのパスワード",
+      "bind_DN_password_user_detail": "ログイン時のパスワードが使用されます。",
+      "search_filter": "検索フィルター",
+      "search_filter_detail1": "認証されるユーザーを一意に決定するための LDAP フィルタ",
+      "search_filter_detail2": "ログイン時のユーザー名を使用するには <code>&#123;&#123;username&#125;&#125;</code> の形式を使用してください。",
+      "search_filter_detail3": "空欄の場合 <code>(uid=&#123;&#123;username&#125;&#125;)</code> が使用されます。",
+      "search_filter_example1": "'uid' または 'mail' に一致",
+      "search_filter_example2": "'sAMAccountName' に一致 (Active Directory)",
+      "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
+      "name_detail": "新規ユーザーの表示名に関連付ける属性",
+      "mail_detail": "新規ユーザーのメールアドレスに関連付ける属性",
+      "group_search_base_DN": "グループ検索ベース DN",
+      "group_search_base_DN_detail": "グループ検索を実行するベース DN。利用する場合は <code>グループ検索フィルター</code> も入力する必要があります。",
+      "group_search_filter": "グループ検索フィルター",
+      "group_search_filter_detail1": "グループフィルターに用いるクエリ",
+      "group_search_filter_detail2": "このクエリにヒットするグループがあったときのみ、LDAPでのログインが成功します。",
+      "group_search_filter_detail3": "ログイン対象ユーザーオブジェクトのプロパティーで置換する場合は <code>&#123;&#123;dn&#125;&#125;</code> を用いてください。",
+      "group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> は <code>cn=group1</code> と、ユーザーの <code>uid</code> を含む <code>memberUid</code> を持つグループにヒットします(<code>ユーザーの DN プロパティー</code> がデフォルトから変更されていない場合)",
+      "group_search_user_DN_property": "ユーザーの DN プロパティー",
+      "group_search_user_DN_property_detail": "<code>グループ検索フィルター</code> 内の <code>&#123;&#123;dn&#125;&#125;</code> で置換される、ユーザーオブジェクトのプロパティー",
+      "test_config": "ログインテスト",
+      "updated_ldap": "LDAP設定 を更新しました"
+    },
+    "SAML": {
+      "name": "SAML",
+      "enable_saml": "SAML を有効にする",
+      "id_detail": "SAML Identity プロバイダ内で一意に識別可能な値を格納している属性",
+      "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
+      "mapping_detail": "新規ユーザーの{{target}}に関連付ける属性",
+      "cert_detail": "IdP からのレスポンスの validation を行うためのPEMエンコードされた X.509 証明書",
+      "Use env var if empty": "データベース側の値が空の場合、環境変数 <code>{{env}}</code> の値を利用します",
+      "note for the only env option": "現在SAML認証のON/OFFの設定値及びハイライトされている設定値は環境変数の値のみを使用するようになっています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
+      "attr_based_login_control_detail": "SAMLの <code>&lt;saml:AttributeStatement&gt;</code> 要素に含まれる <code>&lt;saml:Attribute&gt;</code> 要素と、その子要素 <code>&lt;saml:AttributeValue&gt;</code> を利用してログインの可否を制御します。",
+      "attr_based_login_control_rule_help": "<h5>利用可能なクエリ:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>利用不可なクエリ:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>特殊文字のエスケープ</h5>次の特殊文字はエスケープする必要があります。<code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
+      "attr_based_login_control_rule_example1": "<h5>条件式の例</h5>ルールに <code>(Department: A || Department: B) && Position: Leader</code> を指定した場合, <code>Department: A</code> または <code>Department: B</code> のどちらかに該当し、かつ <code>Position: Leader</code> を持つユーザーにログインを<strong>許可</strong>します。",
+      "attr_based_login_control_rule_exampl2": "<h5>エスケープの例</h5>ルールに URL を利用したい場合は、次のようにエスケープしてください:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
+      "updated_saml": "Succeeded to update SAML setting"
+    },
+    "Basic": {
+      "enable_basic": "Basic を有効にする",
+      "name": "Basic 認証",
+      "desc_1": "Authorization ヘッダに格納されている <code>username</code> でログインします。",
+      "desc_2": "ユーザーが存在しなかった場合は自動生成します。",
+      "updated_basic": "Basic認証 を更新しました"
+    },
+    "OAuth": {
+      "enable_oidc": "OIDC を有効にする",
+      "register": "%sに登録",
+      "change_redirect_url": "承認済みのリダイレクトURLに、 <code>%s</code> を入力",
+      "Google": {
+        "enable_google": "Google OAuth を有効にする",
+        "name": "Google OAuth",
+        "register_1": "{{link}}へアクセス",
+        "register_2": "プロジェクトがない場合はプロジェクトを作成",
+        "register_3": "認証情報を作成 &rightarrow; OAuthクライアントID &rightarrow; ウェブアプリケーションを選択",
+        "register_4": "承認済みのリダイレクトURIを<code>{{url}}</code>としてGrowiを登録",
+        "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
+        "updated_google": "Google OAuth を更新しました"
+      },
+      "Facebook": {
+        "name": "Facebook OAuth"
+      },
+      "Twitter": {
+        "enable_twitter": "Twitter OAuth を有効にする",
+        "name": "Twitter OAuth",
+        "register_1": "{{link}} へアクセス",
+        "register_2": "Twitterにサインイン",
+        "register_3": "Create New Appをクリック &rightarrow; Application Detailsの各項目を入力",
+        "register_4": "Create your Twitter Applicationで作成",
+        "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
+        "updated_twitter": "Twitter OAuth を更新しました"
+      },
+      "GitHub": {
+        "enable_github": "GitHub OAuth を有効にする",
+        "name": "GitHub OAuth",
+        "register_1": "{{link}} へアクセス",
+        "register_2": "\"Authorization callback URL\"を<code>{{url}}</code>としてGrowiを登録",
+        "register_3": "上記フォームにクライアントIDとクライアントシークレットを入力",
+        "updated_github": "GitHub OAuth を更新しました"
+      },
+      "OIDC": {
+        "name": "OpenID Connect",
+        "id_detail": "OIDC claims で一意に識別可能な値を格納している属性",
+        "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
+        "name_detail": "新規ユーザー名(<code>name</code>)に関連付ける属性",
+        "mapping_detail": "新規ユーザーの{{target}}に関連付ける属性",
+        "updated_oidc": "OpenID Connect を更新しました",
+        "Use discovered URL if empty": "データベース側の値が空の場合、\"Issuer Host\"から検出した値を利用します。"
+      },
+      "how_to": {
+        "google": "Google OAuth の設定方法",
+        "github": "GitHub OAuth の設定方法",
+        "twitter": "Twitter OAuth の設定方法"
+      }
+    },
+    "form_item_name": {
+      "entryPoint": "エントリーポイント",
+      "issuer": "発行者",
+      "cert": "証明書",
+      "attrMapId": "ID",
+      "attrMapUsername": "ユーザー名",
+      "attrMapMail": "メールアドレス",
+      "attrMapFirstName": "名",
+      "attrMapLastName": "姓",
+      "ABLCRule": "ルール"
+    }
+  },
+  "notification_settings": {
+    "notification_settings": "通知設定",
+    "slack_incoming_configuration": "Slack Incoming Webhooks 設定",
+    "prioritize_webhook": "Slack アプリより Incoming Webhook を優先する",
+    "prioritize_webhook_desc": "このオプションをオンにすると、 Slack App が有効になっていても GROWI は Incoming Webhook を使用します。",
+    "slack_app_configuration": "Slack App 設定",
+    "slack_app_configuration_desc": "Crowi 互換の機能です。<br /> <strong>設定が複雑すぎる</strong>のでオススメしません。",
+    "use_instead": "代わりに Slack Incoming Webhooks 設定を使用してください。",
+    "how_to": {
+      "header": "Incoming Webhooks の設定方法",
+      "workspace": "ワークスペースで Webhook を追加します。",
+      "workspace_desc1": "<a href='https://slack.com/services/new/incoming-webhook'>Incoming Webhooks Configuration page</a> にアクセスします。",
+      "workspace_desc2": "投稿するチャンネルを選びます。",
+      "workspace_desc3": "追加します。",
+      "at_growi": "GROWI 管理画面で Webhook URL を設定します。",
+      "at_growi_desc": "このページで &rdquo;Webhook URL&rdquo; を入力して送信します。"
+    },
+    "user_trigger_notification_header": "デフォルトパターンの通知設定",
+    "pattern": "パターン",
+    "channel": "チャンネル名",
+    "pattern_desc": "Wiki のパス名。 パスには <code>*</code> を使用できます。",
+    "channel_desc": "<code>#</code> を除いた Slack チャンネル名",
+    "valid_page": "通知の有効 / 無効",
+    "link_notification_help": "<strong>linkを知っている人のみ閲覧できるページ</strong>は常に通知されません。",
+    "just_me_notification_help": "<strong>'自分のみ'に閲覧制限をしているページ</strong>に変更を加えた際に通知する",
+    "group_notification_help": "<strong>'特定グループにのみ'に閲覧制限をしているページ</strong>に変更を加えた際に通知する",
+    "notification_list": "通知設定の一覧",
+    "add_notification": "通知設定の追加",
+    "trigger_path": "トリガーパス",
+    "trigger_path_help": "(<code>*</code>が使用できます)",
+    "trigger_events": "トリガーイベント",
+    "notify_to": "通知先",
+    "back_to_list": "通知設定一覧に戻る",
+    "notification_detail": "通知詳細設定",
+    "event_pageCreate": "ページが新規作成されたとき",
+    "event_pageEdit": "ページが編集されたとき",
+    "event_pageDelete": "ページが削除されたとき",
+    "event_pageMove": "ページが移動(名前が変更)されたとき",
+    "event_pageLike": "ページに「いいね」がついたとき",
+    "event_comment": "コメントが投稿されたとき",
+    "email": {
+      "ifttt_link": "IFTTT でメールトリガの新しいアプレットを作る"
+    },
+    "updated_slackApp": "SlackApp設定を更新しました",
+    "add_notification_pattern": "通知パターンを追加しました。",
+    "delete_notification_pattern": "通知パターンを削除しました。",
+    "delete_notification_pattern_desc1": "Path: {{path}} を削除します。",
+    "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
+    "toggle_notification": "{{path}}の通知設定を変更しました"
+  },
+  "full_text_search_management": {
+    "full_text_search_management": "全文検索管理",
+    "elasticsearch_management": "Elasticsearch 管理",
+    "connection_status": "接続の状態",
+    "connection_status_label_unconfigured": "設定されていません",
+    "connection_status_label_connected": "接続されています",
+    "connection_status_label_disconnected": "切断されています",
+    "connection_status_label_erroroccured": "SearchService でエラーが発生しています",
+    "indices_status": "インデックスの状態",
+    "indices_status_label_normalized": "正規化されています",
+    "indices_status_label_unnormalized": "リビルド中 または 破損しています",
+    "indices_summary": "インデックスのサマリ",
+    "reconnect": "再接続",
+    "reconnect_button": "再接続の試行",
+    "reconnect_description": "Elasticsearch への再接続を試みます。",
+    "normalize": "正規化",
+    "normalize_button": "インデックスの正規化",
+    "normalize_description": "破損したインデックスを修復します。",
+    "rebuild": "リビルド",
+    "rebuild_button": "インデックスのリビルド",
+    "rebuild_description_1": "全てのページのインデックスを削除し、作り直します。",
+    "rebuild_description_2": "この作業には数秒かかります。"
+  },
   "mailer_setup_required": "送信するには <a href='/admin/app'>メールの設定</a> が必要です。",
   "admin_top": {
     "management_wiki": "Wiki管理",
@@ -105,7 +409,8 @@
     "use_env_var_if_empty": "データベース側の値が空の場合、環境変数 <code>{{variable}}</code> の値を利用します",
     "note_for_the_only_env_option": "現在GCS設定は環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください"
   },
-  "markdown_setting": {
+  "markdown_settings": {
+    "markdown_settings": "マークダウン設定",
     "lineBreak_header": "Line Break設定",
     "lineBreak_desc": "Line Breakの設定を変更できます。",
     "lineBreak_options": {
@@ -148,7 +453,8 @@
       "import_recommended": "{{target}} のおすすめをインポート"
     }
   },
-  "customize_setting": {
+  "customize_settings": {
+    "customize_settings": "カスタマイズ",
     "default_sidebar_mode": {
       "title": "デフォルトのサイドバーモード",
       "desc": "新規ユーザー、ページを訪れたゲストのサイドバーモードを設定できます。",
@@ -216,6 +522,7 @@
     "delete_logo": "ロゴを削除"
   },
   "export_management": {
+    "export_archive_data": "データアーカイブ",
     "exporting_collection_list": "エクスポート中のコレクション",
     "exported_data_list": "エクスポートされたアーカイブリスト",
     "export_collections": "コレクションのエクスポート",
@@ -234,6 +541,12 @@
     "delete": "削除"
   },
   "importer_management": {
+    "import_data": "データインポート",
+    "article": "記事",
+    "category": "カテゴリー",
+    "tag": "タグ",
+    "page": "ページ",
+    "page_path": "ページパス",
     "beta_warning": "この機能はベータ版です",
     "import_from": "{{from}} からインポート",
     "import_growi_archive": "GROWI アーカイブをインポート",
@@ -305,12 +618,14 @@
     "Directory_hierarchy_tag": "ディレクトリ階層タグ"
   },
   "external_notification": {
+    "external_notification": "外部ツールへの通知",
     "enabled": "有効",
     "disabled": "無効",
     "header_status": "Slack 連携の状態",
     "caution_enabled": "CAUTION: このページで設定される通知は、Primary として設定された Slack ワークスペースにのみ送信されます。 "
   },
   "slack_integration": {
+    "slack_integration": "Slack連携",
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "詳しい説明はこちら",
@@ -421,12 +736,15 @@
     }
   },
   "slack_integration_legacy": {
+    "slack_integration_legacy":  "Slack連携 (レガシー)",
     "alert_disabled": "<a href='/admin/slack-integration'>新しい設定</a>が有効になっているため、この 'Slack連携 (レガシー)' は現在無効になっています。",
     "alert_deplicated": "この 'Slack連携 (レガシー)' は将来廃止されます。代わりに<a href='/admin/slack-integration'>新しいSlack連携機能</a>を利用してください。"
   },
   "user_management": {
+    "user_management": "ユーザー管理",
     "invite_users": "新規ユーザーの仮発行",
     "click_twice_same_checkbox": "少なくとも一つはチェックしてください。",
+    "status": "ステータス",
     "invite_modal": {
       "emails": "メールアドレス (複数行入力で複数人発行可能)",
       "description1": "メールアドレスを使用して新規ユーザーを仮発行します。",
@@ -482,12 +800,14 @@
     "current_users": "現在のユーザー数:"
   },
   "user_group_management": {
+    "user_group_management": "グループ管理",
     "create_group": "新規グループの作成",
     "add_child_group": "子グループの追加",
     "remove_child_group": "解除",
     "deny_create_group": "新規グループの作成はできません。",
     "group_name": "グループ名",
     "group_example": "例: Group1",
+    "child_user_group": "子グループ",
     "parent_group": "親グループ",
     "select_parent_group": "親グループを選択",
     "release_parent_group": "親グループの解除",
@@ -528,6 +848,8 @@
     }
   },
   "audit_log_management": {
+    "audit_log": "監査ログ",
+    "audit_log_settings": "監査ログ設定",
     "user": "ユーザー",
     "username": "ユーザー名",
     "date": "日付",
@@ -597,6 +919,10 @@
     "PAGE_DELETE_COMPLETELY": "ページの完全削除",
     "PAGE_REVERT": "ページを元に戻す",
     "PAGE_EMPTY_TRASH": "ゴミ箱を空にする",
+    "PAGE_RECURSIVELY_RENAME": "再帰的なページのリネーム",
+    "PAGE_RECURSIVELY_DELETE": "再帰的なページの削除",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "再起的なページの完全削除",
+    "PAGE_RECURSIVELY_REVERT": "再起的なページの復元",
     "PAGE_SUBSCRIBE": "ページをサブスクライブ",
     "PAGE_UNSUBSCRIBE": "ページをアンサブスクライブ",
     "PAGE_EXPORT": "マークダウン形式でページをエクスポート",
@@ -614,7 +940,7 @@
     "SHARE_LINK_NOT_FOUND": "ページ閲覧(存在しない共有リンク)",
     "ATTACHMENT_ADD": "添付データの追加",
     "ATTACHMENT_REMOVE": "添付データの削除",
-    "ACTION_ATTACHMENT_DOWNLOAD": "添付データのダウンロード",
+    "ATTACHMENT_DOWNLOAD": "添付データのダウンロード",
     "SEARCH_PAGE": "ページの検索",
     "SEARCH_PAGE_VIEW": "ページ閲覧(検索結果ページ)",
     "ADMIN_APP_SETTING_UPDATE": "アプリ設定の更新",

+ 43 - 313
packages/app/public/static/locales/ja_JP/translation.json

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "日本語"
+  },
   "Help": "ヘルプ",
   "view": "View",
   "Edit": "編集",
@@ -16,6 +19,7 @@
   "Move/Rename": "移動/名前変更",
   "Redirected": "リダイレクトされました",
   "Unlinked": "リダイレクト削除",
+  "unlink_redirection": "リダイレクト削除",
   "Done": "完了",
   "Cancel": "キャンセル",
   "Create": "作成",
@@ -27,15 +31,10 @@
   "New": "作成",
   "Close": "閉じる",
   "Shortcuts": "ショートカット",
+  "CustomSidebar": "カスタムサイドバー",
   "eg": "例:",
   "add": "追加",
   "Undo": "元に戻す",
-  "Article": "記事",
-  "Page": "ページ",
-  "Page Path": "ページパス",
-  "Category": "カテゴリー",
-  "User": "ユーザー",
-  "status": "ステータス",
   "account_id": "アカウントID",
   "Initialize": "初期化",
   "Update": "更新",
@@ -74,7 +73,6 @@
   "username": "ユーザー名",
   "Created": "作成日",
   "Last updated": "最終更新",
-  "Last_Login": "最終ログイン",
   "Share": "共有",
   "Markdown Link": "Markdown形式のリンク",
   "Create/Edit Template": "テンプレートページの作成/編集",
@@ -109,30 +107,13 @@
   "Input page name (optional)": "ページ名を入力(空欄OK)",
   "New Page": "新規ページ",
   "Create under": "ページを以下に作成",
-  "Wiki Management Home Page": "Wiki管理トップ",
-  "App Settings": "アプリ設定",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
   "See_more_detail_on_new_schema": "詳しくは<a href='#'>{{url}}</a><i class='icon-share-alt'></i>を参照ください。",
   "Site URL settings": "サイトURL設定",
-  "Markdown Settings": "マークダウン設定",
-  "Customize": "カスタマイズ",
-  "Notification Settings": "通知設定",
-  "slack_integration": "Slack連携",
-  "External_Notification": "外部ツールへの通知",
-  "Legacy_Slack_Integration": "Slack連携 (レガシー)",
-  "User_Management": "ユーザー管理",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
-  "ChildUserGroup": "子グループ",
-  "UserGroup Management": "グループ管理",
-  "AuditLog": "監査ログ",
-  "AuditLog Settings": "監査ログ設定",
-  "Full Text Search Management": "全文検索管理",
-  "Import Data": "データインポート",
-  "Export Archive Data": "データアーカイブ",
   "Basic Settings": "基本設定",
-  "Register limitation": "登録の制限",
   "The contents entered here will be shown in the header etc": "ここに入力した内容は、ヘッダー等に表示されます。",
   "Public": "公開",
   "Anyone with the link": "リンクを知っている人のみ",
@@ -140,11 +121,6 @@
   "Only me": "自分のみ",
   "Only inside the group": "特定グループのみ",
   "page_list": "ページリスト",
-  "scope_of_page_disclosure": "ページの公開範囲",
-  "set_point": "設定値",
-  "always_displayed": "表示 (固定)",
-  "always_hidden": "非表示 (固定)",
-  "displayed_or_hidden": "表示 / 非表示",
   "Reselect the group": "グループの再選択",
   "Shareable link": "このページの共有用URL",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
@@ -167,7 +143,6 @@
   "add_bookmark": "ブックマークに追加",
   "remove_bookmark": "ブックマークから削除",
   "wide_view": "ワイドビュー",
-  "Recent Created": "最新の作成",
   "Recent Changes": "最新の変更",
   "Page Tree": "ページツリー",
   "original_path":"元のパス",
@@ -198,6 +173,9 @@
     "page_not_exist": "このページは存在しません。",
     "page_not_exist_alert": "このページは存在しません。新たに作成する必要があります。"
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "パスを作成できませんでした。"
+  },
   "custom_navigation": {
     "no_page_list": "このページの配下にはページが存在しません。",
     "link_sharing_is_disabled": "リンクのシェアは無効化されています"
@@ -206,7 +184,9 @@
     "setup": "セットアップ",
     "create_initial_account": "最初のアカウントの作成",
     "initial_account_will_be_administrator_automatically": "初めに作成するアカウントは、自動的に管理者権限が付与されます",
-    "unavaliable_user_id": "このユーザーIDは利用できません。"
+    "unavaliable_user_id": "このユーザーIDは利用できません。",
+    "failed_to_install": "GROWI のインストールに失敗しました。再度お試しください。",
+    "failed_to_login_after_install": "インストール後、ログインに失敗しました。ログインフォームに遷移しています ..."
   },
   "breaking_changes": {
     "v346_using_basic_auth": "現在利用中の Basic 認証機能は、近い将来<strong>廃止されます</strong>。%s から設定を削除してください。"
@@ -250,7 +230,6 @@
     "new_password_confirm": "(確認用)",
     "password_is_not_set": "パスワードが設定されていません"
   },
-  "security_settings": "セキュリティ設定",
   "share_links": {
     "Shere this page link to public": "外部に共有するリンクを発行する",
     "share_link_list": "共有リンクリスト",
@@ -390,7 +369,8 @@
     "overwrite_scopes": "{{operation}}と同時に全ての配下ページのスコープを上書き",
     "notice": {
       "conflict": "すでに他の人がこのページを編集していたため保存できませんでした。ページを再読み込み後、自分の編集箇所のみ再度編集してください。"
-    }
+    },
+    "changes_not_saved": "変更が保存されていない可能性があります。"
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "投稿時のページを表示する",
@@ -681,288 +661,17 @@
       "error_duplicate_pages_found": "同名のパスを持つページが複数見つかりました。リネームまたは削除してから再度実行してください"
     }
   },
-  "security_setting": {
-    "Guest Users Access": "ゲストユーザーのアクセス",
-    "Fixed by env var": "環境変数 <code>{{forcewikimode}}={{wikimode}}</code> により固定されています。",
-    "Register limitation": "登録の制限",
-    "Register limitation desc": "新しいユーザーを登録する方法を制限します.",
-    "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
-    "users_without_account": "アカウントを持たないユーザーはアクセス不可",
-    "example": "例",
-    "restrict_emails": "登録可能なメールアドレスを制限することができます。",
-    "for_example": "例えば、",
-    "in_this_case": "と記載することで、そのドメインのメールアドレスを持っている人のみ登録可能になります。",
-    "insert_single": "1行に1メールアドレス入力してください。",
-    "page_list_and_search_results": "ページリスト・検索結果",
-    "page_listing_1": "ページのリスト表示と検索<br>'自分のみ'に閲覧制限しているページ",
-    "page_listing_1_desc": "ページのリスト表示や検索結果において、'自分のみ'に閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
-    "page_listing_2": "ページのリスト表示と検索<br>特定グループに閲覧制限しているページ",
-    "page_listing_2_desc": "ページのリスト表示や検索結果において、特定グループにのみ閲覧制限をしているページをアクセス権のないユーザーにも表示します。",
-    "page_access_rights": "ページの閲覧権限",
-    "page_delete_rights": "ページの削除権限",
-    "page_delete": "ゴミ箱に入れる",
-    "page_delete_completely": "完全に削除する",
-    "other_options": "その他のオプション",
-    "deletion_explain": "ページをゴミ箱に入れることができるユーザーを制限します。",
-    "complete_deletion_explain": "ページを完全削除することができるユーザーを制限します。",
-    "recursive_deletion_explain": "子孫を含めたページをゴミ箱に入れることができるユーザーを制限します。",
-    "recursive_complete_deletion_explain": "子孫を含めたページを完全削除することができるユーザーを制限します。",
-    "inherit": "単体のみと同じ",
-    "admin_only": "管理者のみ可能",
-    "admin_and_author": "管理者とページ作者が可能",
-    "anyone": "誰でも可能",
-    "session": "セッション",
-    "max_age": "有効期間 (ミリ秒)",
-    "max_age_desc": "ユーザーのセッション情報の有効期間をミリ秒で指定できます。<br>デフォルト値: 2592000000 (30日間)",
-    "max_age_caution": "この値を変更した後は、サーバーを再起動する必要があります。",
-    "forced_update_desc": "設定が強制変更されました。前回の設定: ",
-    "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 管理者のみ可能 > 管理者とページ作者が可能 > 誰でも可能",
-    "Authentication mechanism settings": "認証機構設定",
-    "setup_is_not_yet_complete":"セットアップはまだ完了してません",
-    "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
-    "xss_prevent_setting": "XSS(Cross Site Scripting)対策設定",
-    "xss_prevent_setting_link": "マークダウン設定ページに移動",
-    "callback_URL": "コールバックURL",
-    "desc_of_callback_URL": "{{AuthName}} プロバイダ側の設定で利用してください。",
-    "authorization_endpoint": "認可エンドポイント",
-    "token_endpoint": "トークンエンドポイント",
-    "revocation_endpoint": "失効エンドポイント",
-    "introspection_endpoint": "検証エンドポイント",
-    "userinfo_endpoint": "ユーザ情報エンドポイント",
-    "end_session_endpoint": "セッション終了エンドポイント",
-    "registration_endpoint": "登録エンドポイント",
-    "jwks_uri": "JSON Web Key Set URL",
-    "clientID": "クライアントID",
-    "client_secret": "クライアントシークレット",
-    "updated_general_security_setting": "セキュリティ設定を更新しました。",
-    "setup_not_completed_yet": "まだセットアップは完了していません。",
-    "guest_mode": {
-      "deny": "拒否 (アカウントを持つユーザーのみ利用可能)",
-      "readonly": "許可 (ゲストユーザーも閲覧のみ可能)"
-    },
-    "registration_mode": {
-      "open": "公開 (だれでも登録可能)",
-      "restricted": "制限 (登録完了には管理者の承認が必要)",
-      "closed": "非公開 (登録には管理者による招待が必要)"
-    },
-    "share_link_rights": "シェアリンクの権限",
-    "enable_link_sharing": "リンクのシェアを許可",
-    "all_share_links": "全てのシェアリンク",
-    "configuration": "設定",
-    "optional": "オプション",
-    "Treat username matching as identical": "新規ログイン時、<code>username</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
-    "Treat username matching as identical_warn": "警告: <code>username</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
-    "Treat email matching as identical": "新規ログイン時、<code>email</code> が一致したローカルアカウントが存在した場合は自動的に紐付ける",
-    "Treat email matching as identical_warn": "警告: <code>email</code> の一致を以て同一ユーザーであるとみなすので、セキュリティに注意してください",
-    "Use env var if empty": "空の場合、環境変数 <code>{{env}}</code> を利用します",
-    "Use default if both are empty": "どちらの値も空の場合、デフォルト値 <code>{{target}}</code> を利用します",
-    "missing mandatory configs": "以下の必須項目の値がデータベースと環境変数のどちらにも設定されていません",
-    "Local": {
-      "name": "ID/Password",
-      "note for the only env option": "現在LOCAL認証のON/OFFは環境変数の値によって制限されています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
-      "enable_local": "ID/Password を有効にする",
-      "password_reset_by_users": "ユーザーによるパスワード再設定",
-      "enable_password_reset_by_users": "ユーザーによるパスワード再設定を有効にする",
-      "password_reset_desc": "ログイン時のパスワードを忘れた際に、ユーザー自身がパスワードを再設定できます。",
-      "email_authentication": "ユーザー登録時のメール認証",
-      "enable_email_authentication": "メール認証を有効にする",
-      "enable_email_authentication_desc": "ユーザー登録時にメール認証を行います。",
-      "please_enable_mailer": "メール認証を有効にするには、メール設定を完了させてください。",
-      "need_complete_mail_setting_warning": "以下の機能を使えるようにするには、メール設定を完了させてください。"
-    },
-    "ldap": {
-      "enable_ldap": "LDAP を有効にする",
-      "server_url_detail": "LDAP URLを <code>ldap://host:port/DN</code> または <code>ldaps://host:port/DN</code> の形式で入力してください。",
-      "bind_mode": "Bind モード",
-      "bind_manager": "管理者 Bind",
-      "bind_user": "ユーザー Bind",
-      "bind_DN_manager_detail": "ディレクトリーサービスに認証する際のアカウント DN",
-      "bind_DN_user_detail1": "ディレクトリーサービスに Bind するアカウント DN を決定するためのクエリ",
-      "bind_DN_user_detail2": "ログイン時に入力されるユーザー名を使用するには <code>&#123;&#123;username&#125;&#125;</code> の形式を使用してください。",
-      "bind_DN_password": "Bind DN パスワード",
-      "bind_DN_password_manager_detail": "Bind DN アカウントのパスワード",
-      "bind_DN_password_user_detail": "ログイン時のパスワードが使用されます。",
-      "search_filter": "検索フィルター",
-      "search_filter_detail1": "認証されるユーザーを一意に決定するための LDAP フィルタ",
-      "search_filter_detail2": "ログイン時のユーザー名を使用するには <code>&#123;&#123;username&#125;&#125;</code> の形式を使用してください。",
-      "search_filter_detail3": "空欄の場合 <code>(uid=&#123;&#123;username&#125;&#125;)</code> が使用されます。",
-      "search_filter_example1": "'uid' または 'mail' に一致",
-      "search_filter_example2": "'sAMAccountName' に一致 (Active Directory)",
-      "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
-      "name_detail": "新規ユーザーの表示名に関連付ける属性",
-      "mail_detail": "新規ユーザーのメールアドレスに関連付ける属性",
-      "group_search_base_DN": "グループ検索ベース DN",
-      "group_search_base_DN_detail": "グループ検索を実行するベース DN。利用する場合は <code>グループ検索フィルター</code> も入力する必要があります。",
-      "group_search_filter": "グループ検索フィルター",
-      "group_search_filter_detail1": "グループフィルターに用いるクエリ",
-      "group_search_filter_detail2": "このクエリにヒットするグループがあったときのみ、LDAPでのログインが成功します。",
-      "group_search_filter_detail3": "ログイン対象ユーザーオブジェクトのプロパティーで置換する場合は <code>&#123;&#123;dn&#125;&#125;</code> を用いてください。",
-      "group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> は <code>cn=group1</code> と、ユーザーの <code>uid</code> を含む <code>memberUid</code> を持つグループにヒットします(<code>ユーザーの DN プロパティー</code> がデフォルトから変更されていない場合)",
-      "group_search_user_DN_property": "ユーザーの DN プロパティー",
-      "group_search_user_DN_property_detail": "<code>グループ検索フィルター</code> 内の <code>&#123;&#123;dn&#125;&#125;</code> で置換される、ユーザーオブジェクトのプロパティー",
-      "test_config": "ログインテスト",
-      "updated_ldap": "LDAP設定 を更新しました"
-    },
-    "SAML": {
-      "name": "SAML",
-      "enable_saml": "SAML を有効にする",
-      "id_detail": "SAML Identity プロバイダ内で一意に識別可能な値を格納している属性",
-      "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
-      "mapping_detail": "新規ユーザーの{{target}}に関連付ける属性",
-      "cert_detail": "IdP からのレスポンスの validation を行うためのPEMエンコードされた X.509 証明書",
-      "Use env var if empty": "データベース側の値が空の場合、環境変数 <code>{{env}}</code> の値を利用します",
-      "note for the only env option": "現在SAML認証のON/OFFの設定値及びハイライトされている設定値は環境変数の値のみを使用するようになっています<br>この設定を変更する場合は環境変数 <code>{{env}}</code> の値をfalseに変更もしくは削除してください",
-      "attr_based_login_control_detail": "SAMLの <code>&lt;saml:AttributeStatement&gt;</code> 要素に含まれる <code>&lt;saml:Attribute&gt;</code> 要素と、その子要素 <code>&lt;saml:AttributeValue&gt;</code> を利用してログインの可否を制御します。",
-      "attr_based_login_control_rule_help": "<h5>利用可能なクエリ:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>利用不可なクエリ:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>特殊文字のエスケープ</h5>次の特殊文字はエスケープする必要があります。<code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
-      "attr_based_login_control_rule_example1": "<h5>条件式の例</h5>ルールに <code>(Department: A || Department: B) && Position: Leader</code> を指定した場合, <code>Department: A</code> または <code>Department: B</code> のどちらかに該当し、かつ <code>Position: Leader</code> を持つユーザーにログインを<strong>許可</strong>します。",
-      "attr_based_login_control_rule_exampl2": "<h5>エスケープの例</h5>ルールに URL を利用したい場合は、次のようにエスケープしてください:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
-      "updated_saml": "Succeeded to update SAML setting"
-    },
-    "Basic": {
-      "enable_basic": "Basic を有効にする",
-      "name": "Basic 認証",
-      "desc_1": "Authorization ヘッダに格納されている <code>username</code> でログインします。",
-      "desc_2": "ユーザーが存在しなかった場合は自動生成します。",
-      "updated_basic": "Basic認証 を更新しました"
-    },
-    "OAuth": {
-      "enable_oidc": "OIDC を有効にする",
-      "register": "%sに登録",
-      "change_redirect_url": "承認済みのリダイレクトURLに、 <code>%s</code> を入力",
-      "Google": {
-        "enable_google": "Google OAuth を有効にする",
-        "name": "Google OAuth",
-        "register_1": "{{link}}へアクセス",
-        "register_2": "プロジェクトがない場合はプロジェクトを作成",
-        "register_3": "認証情報を作成 &rightarrow; OAuthクライアントID &rightarrow; ウェブアプリケーションを選択",
-        "register_4": "承認済みのリダイレクトURIを<code>{{url}}</code>としてGrowiを登録",
-        "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
-        "updated_google": "Google OAuth を更新しました"
-      },
-      "Facebook": {
-        "name": "Facebook OAuth"
-      },
-      "Twitter": {
-        "enable_twitter": "Twitter OAuth を有効にする",
-        "name": "Twitter OAuth",
-        "register_1": "{{link}} へアクセス",
-        "register_2": "Twitterにサインイン",
-        "register_3": "Create New Appをクリック &rightarrow; Application Detailsの各項目を入力",
-        "register_4": "Create your Twitter Applicationで作成",
-        "register_5": "上記フォームにクライアントIDとクライアントシークレットを入力",
-        "updated_twitter": "Twitter OAuth を更新しました"
-      },
-      "GitHub": {
-        "enable_github": "GitHub OAuth を有効にする",
-        "name": "GitHub OAuth",
-        "register_1": "{{link}} へアクセス",
-        "register_2": "\"Authorization callback URL\"を<code>{{url}}</code>としてGrowiを登録",
-        "register_3": "上記フォームにクライアントIDとクライアントシークレットを入力",
-        "updated_github": "GitHub OAuth を更新しました"
-      },
-      "OIDC": {
-        "name": "OpenID Connect",
-        "id_detail": "OIDC claims で一意に識別可能な値を格納している属性",
-        "username_detail": "新規ユーザーのアカウント名(<code>username</code>)に関連付ける属性",
-        "name_detail": "新規ユーザー名(<code>name</code>)に関連付ける属性",
-        "mapping_detail": "新規ユーザーの{{target}}に関連付ける属性",
-        "updated_oidc": "OpenID Connect を更新しました",
-        "Use discovered URL if empty": "データベース側の値が空の場合、\"Issuer Host\"から検出した値を利用します。"
-      },
-      "how_to": {
-        "google": "Google OAuth の設定方法",
-        "github": "GitHub OAuth の設定方法",
-        "twitter": "Twitter OAuth の設定方法"
-      }
-    },
-    "form_item_name": {
-      "entryPoint": "エントリーポイント",
-      "issuer": "発行者",
-      "cert": "証明書",
-      "attrMapId": "ID",
-      "attrMapUsername": "ユーザー名",
-      "attrMapMail": "メールアドレス",
-      "attrMapFirstName": "姓",
-      "attrMapLastName": "名",
-      "ABLCRule": "ルール"
-    }
-  },
-  "notification_setting": {
-    "slack_incoming_configuration": "Slack Incoming Webhooks 設定",
-    "prioritize_webhook": "Slack アプリより Incoming Webhook を優先する",
-    "prioritize_webhook_desc": "このオプションをオンにすると、 Slack App が有効になっていても GROWI は Incoming Webhook を使用します。",
-    "slack_app_configuration": "Slack App 設定",
-    "slack_app_configuration_desc": "Crowi 互換の機能です。<br /> <strong>設定が複雑すぎる</strong>のでオススメしません。",
-    "use_instead": "代わりに Slack Incoming Webhooks 設定を使用してください。",
-    "how_to": {
-      "header": "Incoming Webhooks の設定方法",
-      "workspace": "ワークスペースで Webhook を追加します。",
-      "workspace_desc1": "<a href='https://slack.com/services/new/incoming-webhook'>Incoming Webhooks Configuration page</a> にアクセスします。",
-      "workspace_desc2": "投稿するチャンネルを選びます。",
-      "workspace_desc3": "追加します。",
-      "at_growi": "GROWI 管理画面で Webhook URL を設定します。",
-      "at_growi_desc": "このページで &rdquo;Webhook URL&rdquo; を入力して送信します。"
-    },
-    "user_trigger_notification_header": "デフォルトパターンの通知設定",
-    "pattern": "パターン",
-    "channel": "チャンネル名",
-    "pattern_desc": "Wiki のパス名。 パスには <code>*</code> を使用できます。",
-    "channel_desc": "<code>#</code> を除いた Slack チャンネル名",
-    "valid_page": "通知の有効 / 無効",
-    "link_notification_help": "<strong>linkを知っている人のみ閲覧できるページ</strong>は常に通知されません。",
-    "just_me_notification_help": "<strong>'自分のみ'に閲覧制限をしているページ</strong>に変更を加えた際に通知する",
-    "group_notification_help": "<strong>'特定グループにのみ'に閲覧制限をしているページ</strong>に変更を加えた際に通知する",
-    "notification_list": "通知設定の一覧",
-    "add_notification": "通知設定の追加",
-    "trigger_path": "トリガーパス",
-    "trigger_path_help": "(<code>*</code>が使用できます)",
-    "trigger_events": "トリガーイベント",
-    "notify_to": "通知先",
-    "back_to_list": "通知設定一覧に戻る",
-    "notification_detail": "通知詳細設定",
-    "event_pageCreate": "ページが新規作成されたとき",
-    "event_pageEdit": "ページが編集されたとき",
-    "event_pageDelete": "ページが削除されたとき",
-    "event_pageMove": "ページが移動(名前が変更)されたとき",
-    "event_pageLike": "ページに「いいね」がついたとき",
-    "event_comment": "コメントが投稿されたとき",
-    "email": {
-      "ifttt_link": "IFTTT でメールトリガの新しいアプレットを作る"
-    },
-    "updated_slackApp": "SlackApp設定を更新しました",
-    "add_notification_pattern": "通知パターンを追加しました。",
-    "delete_notification_pattern": "通知パターンを削除しました。",
-    "delete_notification_pattern_desc1": "Path: {{path}} を削除します。",
-    "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-    "toggle_notification": "{{path}}の通知設定を変更しました"
-  },
-  "full_text_search_management": {
-    "elasticsearch_management": "Elasticsearch 管理",
-    "connection_status": "接続の状態",
-    "connection_status_label_unconfigured": "設定されていません",
-    "connection_status_label_connected": "接続されています",
-    "connection_status_label_disconnected": "切断されています",
-    "connection_status_label_erroroccured": "SearchService でエラーが発生しています",
-    "indices_status": "インデックスの状態",
-    "indices_status_label_normalized": "正規化されています",
-    "indices_status_label_unnormalized": "リビルド中 または 破損しています",
-    "indices_summary": "インデックスのサマリ",
-    "reconnect": "再接続",
-    "reconnect_button": "再接続の試行",
-    "reconnect_description": "Elasticsearch への再接続を試みます。",
-    "normalize": "正規化",
-    "normalize_button": "インデックスの正規化",
-    "normalize_description": "破損したインデックスを修復します。",
-    "rebuild": "リビルド",
-    "rebuild_button": "インデックスのリビルド",
-    "rebuild_description_1": "全てのページのインデックスを削除し、作り直します。",
-    "rebuild_description_2": "この作業には数秒かかります。"
-  },
   "to_cloud_settings": "GROWI.cloud の管理画面へ",
   "login": {
     "Sign in error": "ログインエラー",
     "Registration successful": "登録完了",
-    "Setup": "セットアップ"
+    "Setup": "セットアップ",
+    "enabled_ldap_has_configuration_problem":"LDAPは有効ですが、設定に問題があります。",
+    "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"
+  },
+  "invited": {
+    "discription_heading": "アカウント作成",
+    "discription": "招待を受け取ったメールアドレスでアカウントを作成します"
   },
   "export_bulk": {
     "failed_to_export": "ページのエクスポートに失敗しました",
@@ -976,8 +685,9 @@
     "fail_to_fetch_access_token": "アクセストークンの取得に失敗しました、再度お試しください。",
     "successfully_disconnected": "切断に成功しました!",
     "strategy_has_not_been_set_up": "{{strategy}} はセットアップされていません。",
+    "ldap_user_not_valid": "Ldap user is no valid",
+    "external_account_not_exist": "外部アカウントが見つからない、または作成に失敗しました",
     "maximum_number_of_users": "ユーザー数が上限を超えたためアクティベートできません。",
-    "database_error":"データベースサーバーに問題があります。",
     "sign_in_failure": "ログインに失敗しました。",
     "aws_sttings_required": "この機能にはAWS設定が必要です。管理者に訪ねて下さい。",
     "application_already_installed": "アプリケーションのインストールが完了しました。",
@@ -995,7 +705,21 @@
     "complete_to_install2":"GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。",
     "failed_to_create_admin_user":"管理ユーザーの作成に失敗しました。{{errMessage}}",
     "successfully_send_email_auth":"{{email}} にメールを送信しました。添付されたURLをクリックし、本登録を完了させてください",
-    "incorrect_token_or_expired_url":"トークンが正しくないか、URLの有効期限が切れています。"
+    "incorrect_token_or_expired_url":"トークンが正しくないか、URLの有効期限が切れています。",
+    "user_already_loggedin": "ログイン中のため、新規アカウントを作成できませんでした。",
+    "registration_closed": "新しいアカウントを作成する権限がありません。",
+    "Username has invalid characters": "ユーザー名に不正な文字が含まれています.",
+    "Username field is required": "User ID は必須項目です",
+    "Name field is required": "ユーザーID は必須項目です",
+    "Email format is invalid": "メールアドレスのフォーマットが無効です",
+    "Email field is required": "メールアドレスは必須項目です",
+    "Password has invalid character": "パスワードに無効な文字があります",
+    "Password minimum character should be more than 8 characters": "パスワードの最小文字数は8文字以上です",
+    "Password field is required": "パスワードの欄は必ず入力してください",
+    "Username or E-mail has invalid characters": "ユーザー名または、メールアドレスに無効な文字があります",
+    "Password minimum character should be more than 6 characters": "パスワードの最小文字数は6文字以上です",
+    "user_not_found": "ユーザーが見つかりません",
+    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>エラー: DuplicatedUsernameException</strong></p><p class='mb-0'> {{ failedProviderForDuplicatedUsernameException }} 認証は成功しましたが、新しいユーザーを作成できませんでした。詳しくは<a href='https://github.com/weseek/growi/issues/193'>こちら: #193</a>.</p>"
   },
   "grid_edit":{
     "create_bootstrap_4_grid":"Bootstrap 4 グリッドを作成",
@@ -1022,6 +746,7 @@
     "confirm_new_password": "新しいパスワードの確認",
     "email_is_required": "メールを入力してください",
     "success_to_send_email": "メールを送信しました",
+    "feature_is_unavailable": "この機能を利用することはできません。",
     "incorrect_token_or_expired_url":"トークンが正しくないか、URLの有効期限が切れています。 以下のリンクからパスワードリセットリクエストを再送信してください。",
     "password_and_confirm_password_does_not_match": "パスワードと確認パスワードが一致しません"
   },
@@ -1081,6 +806,7 @@
   "crop_image_modal": {
     "image_crop": "画像の切り抜き",
     "crop": "トリミング",
+    "save": "保存",
     "reset": "リセット",
     "cancel": "キャンセル"
   },
@@ -1127,5 +853,9 @@
   "page_operation":{
     "paths_recovered": "パスを修復しました",
     "path_recovery_failed":"パスを修復できませんでした"
+  },
+  "footer": {
+    "bookmarks": "ブックマーク",
+    "recently_created": "最近作成したページ"
   }
 }

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

@@ -1,4 +1,263 @@
 {
+  "meta": {
+    "display_name": "简体中文"
+  },
+  "Update": "更新",
+  "Delete": "删除",
+  "User": "用户",
+  "Name": "姓名",
+  "Created": "创建",
+  "Edit": "编辑",
+  "Description": "描述",
+  "wiki_management_home_page": "Wiki管理首页",
+  "app_settings": "系统设置",
+  "public": "公共",
+  "anyone_with_the_link": "任何人",
+  "specified_users": "仅指定用户",
+  "only_me": "只有我",
+  "only_inside_the_group": "仅组内",
+  "security_settings": {
+    "security_settings": "安全设置",
+    "scope_of_page_disclosure": "页面公开范围",
+    "set_point": "设定值",
+    "always_displayed": "始终显示",
+    "always_hidden": "总是隐藏",
+    "displayed_or_hidden": "显示/隐藏",
+    "Guest Users Access": "来宾用户访问",
+		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
+		"Register limitation": "注册限制",
+		"Register limitation desc": "限制新用户注册",
+		"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
+		"users_without_account": "无法访问没有帐户的用户",
+		"example": "例子",
+		"restrict_emails": "您可以通过编写电子邮件域(以@开头)将电子邮件注册限制为wiki。",
+		"for_example": " 例如,如果要将注册限制为growi.org网站域,你可以写",
+		"in_this_case": ";在这种情况下,只有growi.org网站域将能够注册,所有其他用户将被拒绝。",
+		"insert_single": "请每行插入一个电子邮件地址。",
+    "page_list_and_search_results": "页面列表/搜索结果",
+		"page_listing_1": "页面列表/搜索<br>受“仅限我”限制",
+		"page_listing_1_desc": "列出/搜索时显示受“仅限我”选项限制的页面",
+		"page_listing_2": "页面列表/搜索<br>受用户组限制",
+		"page_listing_2_desc": "显示列出/搜索时受用户组限制的页面",
+    "page_access_rights": "页面访问",
+    "page_delete_rights": "删除权限",
+    "page_delete": "删除",
+    "page_delete_completely": "彻底删除",
+    "other_options": "其他选项",
+    "deletion_explain": "限制用户对选定的单一页面进行垃圾处理。",
+    "complete_deletion_explain": "限制可以完全删除所选单页的用户。",
+    "recursive_deletion_explain": "限制用户可以捣毁包括子孙在内的页面。",
+    "recursive_complete_deletion_explain": "限制可以完全删除页面的用户,包括子孙。",
+    "inherit": "继承(使用与单页相同的设置)。",
+		"admin_only": "仅管理员",
+		"admin_and_author": "管理员|作者",
+		"anyone": "任何人",
+    "session": "会议",
+    "max_age": "有效期间  (msec)",
+    "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
+    "max_age_caution": "修改该值后需要重启服务器。",
+    "forced_update_desc": "设置已被强行更改。以前的设置: ",
+    "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 仅管理员 > 管理员|作者 > 何人",
+		"Authentication mechanism settings": "身份验证机制设置",
+		"setup_is_not_yet_complete": "安装尚未完成",
+		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
+		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
+		"xss_prevent_setting_link": "转到Markdown设置",
+		"callback_URL": "回调URL",
+		"providerName": "提供程序名称",
+		"issuerHost": "发行者主机",
+		"scope": "Scope",
+		"desc_of_callback_URL": "在{{AuthName}}身份提供程序的设置中使用它",
+    "authorization_endpoint": "Authorization Endpoint",
+    "token_endpoint": "Token Endpoint",
+    "revocation_endpoint": "Revocation Endpoint",
+    "introspection_endpoint": "Introspection Endpoint",
+    "userinfo_endpoint": "UserInfo Endpoint",
+    "end_session_endpoint": "EndSessioin Endpoint",
+    "registration_endpoint": "Registration Endpoint",
+    "jwks_uri": "JSON Web Key Set URL",
+		"clientID": "Client ID",
+		"client_secret": "客户机密",
+		"updated_general_security_setting": "更新安全设置成功",
+		"setup_not_completed_yet": "安装尚未完成",
+		"guest_mode": {
+			"deny": "拒绝(仅限注册用户)",
+			"readonly": "接受(来宾可以只读)"
+		},
+		"registration_mode": {
+			"open": "打开(任何人都可以注册)",
+			"restricted": "受限(需要管理员批准)",
+			"closed": "已关闭(仅限邀请)"
+		},
+    "share_link_rights": "分享链接权",
+    "enable_link_sharing": "启用链接共享",
+    "all_share_links": "所有共享链接",
+		"configuration": " 配置",
+		"optional": "可选的",
+		"Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
+		"Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
+		"Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>email</code> match",
+		"Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>email</code>.",
+		"Use env var if empty": "Use env var <code>{{env}}</code> if empty",
+		"Use default if both are empty": "If both ​​are empty, the default value <code>{{target}}</code> is used.",
+		"missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
+		"Local": {
+			"name": "ID/Password",
+			"note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+      "enable_local": "Enable ID/Password",
+      "password_reset_by_users": "用户重置密码",
+      "enable_password_reset_by_users": "启用用户重置密码",
+      "password_reset_desc": "忘记密码时,用户可以自行重置",
+      "email_authentication": "用户注册时的电子邮件身份验证",
+      "enable_email_authentication": "启用电子邮件身份验证",
+      "enable_email_authentication_desc": "用户注册将执行电子邮件身份验证。",
+      "please_enable_mailer": "请先设置邮件程序。",
+      "need_complete_mail_setting_warning": "要使用以下功能,请完成邮件设置。"
+		},
+		"ldap": {
+			"enable_ldap": "Enable LDAP",
+			"server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
+			"bind_mode": "Binding Mode",
+			"bind_manager": "Manager Bind",
+			"bind_user": "User Bind",
+			"bind_DN_manager_detail": "The DN of the account that authenticates and queries the directory service",
+			"bind_DN_user_detail1": "The query used to bind with the directory service.",
+			"bind_DN_user_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+			"bind_DN_password": "Bind DN Password",
+			"bind_DN_password_manager_detail": "The password for the Bind DN account.",
+			"bind_DN_password_user_detail": "The password that is entered in the login page will be used to bind.",
+			"search_filter": "Search Filter",
+			"search_filter_detail1": "The query used to locate the authenticated user.",
+			"search_filter_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
+			"search_filter_detail3": "If empty, the filter <code>(uid=&#123;&#123;username&#125;&#125;)</code> is used.",
+			"search_filter_example1": "Match with 'uid' or 'mail'",
+			"search_filter_example2": "Match with 'sAMAccountName' for Active Directory",
+			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+			"name_detail": "Specification of mappings for full name when creating new users",
+			"mail_detail": "Specification of mappings for mail address when creating new users",
+			"group_search_base_DN": "Group Search Base DN",
+			"group_search_base_DN_detail": "The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.",
+			"group_search_filter": "Group Search Filter",
+			"group_search_filter_detail1": "The query used to filter for groups.",
+			"group_search_filter_detail2": "Login via LDAP is accepted only when this query hits one or more groups.",
+			"group_search_filter_detail3": "Use <code>&#123;&#123;dn&#125;&#125;</code> to have it replaced of the found user object.",
+			"group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
+			"group_search_user_DN_property": "User DN Property",
+			"group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
+			"test_config": "Test Saved Configuration",
+			"updated_ldap": "Succeeded to update LDAP setting"
+		},
+		"SAML": {
+			"name": "SAML",
+			"enable_saml": "Enable SAML",
+			"id_detail": "Specification of the name of attribute which can identify the user in SAML Identity Provider",
+			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+			"mapping_detail": "Specification of mappings for {{target}} when creating new users",
+			"cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
+			"Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
+			"note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
+			"attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
+			"attr_based_login_control_rule_help": "<h5>Supported Queries:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>Unsupported Queries:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>Escaping special characters</h5>It is needed to escape following special characters:<br><code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
+			"attr_based_login_control_rule_example1": "<h5>Example for conditions</h5>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",
+      "attr_based_login_control_rule_example2": "<h5>Example for escaping</h5>If you would like to use URL as a query value, escape the following:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
+      "updated_saml": "Succeeded to update SAML setting"
+		},
+		"Basic": {
+			"enable_basic": "Enable Basic",
+			"name": "Basic Authentication",
+			"desc_1": "Login with <code>username</code> in Authorization header.",
+			"desc_2": "User will be automatically generated if not exist.",
+			"updated_basic": "Succeeded to update Basic setting"
+		},
+		"OAuth": {
+			"enable_oidc": "Enable OIDC",
+			"register": "Register for %s",
+			"change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
+			"Google": {
+				"enable_google": "Enable Google OAuth",
+				"name": "Google OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Create Project if no projects exist",
+				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+				"register_5": "Copy and paste your ClientID and Client Secret above",
+				"updated_google": "Succeeded to update Google OAuth setting"
+			},
+			"Facebook": {
+				"name": "Facebook OAuth"
+			},
+			"Twitter": {
+				"enable_twitter": "Enable Twitter OAuth",
+				"name": "Twitter OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Sign in Twitter",
+				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
+				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
+				"register_5": "Copy and paste your ClientID and Client Secret above",
+				"updated_twitter": "Succeeded to update Twitter OAuth setting"
+			},
+			"GitHub": {
+				"enable_github": "Enable GitHub OAuth",
+				"name": "GitHub OAuth",
+				"register_1": "Access {{link}}",
+				"register_2": "Register your OAuth App with \"Authorization callback URL\" as <code>{{url}}</code>",
+				"register_3": "Copy and paste your ClientID and Client Secret above",
+				"updated_github": "Succeeded to update GitHub OAuth setting"
+			},
+			"OIDC": {
+				"name": "OpenID Connect",
+				"id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
+				"username_detail": "Specification of mappings for <code>username</code> when creating new users",
+				"name_detail": "Specification of mappings for <code>name</code> when creating new users",
+				"mapping_detail": "Specification of mappings for %s when creating new users",
+				"register_1": "Contant to OIDC IdP Administrator",
+				"register_2": "Register your OIDC App with \"Authorization callback URL\" as <code>%s</code>",
+				"register_3": "Copy and paste your ClientID and Client Secret above",
+				"updated_oidc": "Succeeded to update OpenID Connect",
+        "Use discovered URL if empty": "Use discovered URL from \"Issuer Host\" if empty"
+			},
+			"how_to": {
+				"google": "How to configure Google OAuth?",
+				"github": "How to configure GitHub OAuth?",
+				"twitter": "How to configure Twitter OAuth?",
+				"oidc": "How to configure OIDC?"
+			}
+		},
+		"form_item_name": {
+			"entryPoint": "Entry point",
+			"issuer": "Issuer",
+			"cert": "Certificate",
+			"attrMapId": "ID",
+			"attrMapUsername": "Username",
+			"attrMapMail": "Mail Address",
+			"attrMapFirstName": "First Name",
+			"attrMapLastName": "Last Name",
+			"ABLCRule": "Rule"
+		}
+  },
+  "full_text_search_management": {
+    "full_text_search_management": "全文搜索管理",
+		"elasticsearch_management": "Elasticsearch管理",
+		"connection_status": "连接状态",
+		"connection_status_label_unconfigured": "未配置",
+		"connection_status_label_connected": "已连接",
+		"connection_status_label_disconnected": "断开的",
+		"connection_status_label_erroroccured": "搜索服务出错",
+		"indices_status": "索引状态",
+		"indices_status_label_normalized": "标准化",
+		"indices_status_label_unnormalized": "重建或损坏",
+		"indices_summary": "索引摘要",
+		"reconnect": "重新连接",
+		"reconnect_button": "尝试重新连接",
+		"reconnect_description": "单击按钮尝试重新连接到Elasticsearch。",
+		"normalize": "规范化",
+		"normalize_button": "规范化索引",
+		"normalize_description": "单击按钮修复损坏的索引。",
+		"rebuild": "重建",
+		"rebuild_button": "重建索引",
+		"rebuild_description_1": "单击按钮以重新生成索引并添加所有页面数据。",
+		"rebuild_description_2": "这可能需要一段时间。"
+	},
   "mailer_setup_required": "<a href='/admin/app'>Email settings</a> are required to send.",
   "admin_top": {
     "management_wiki": "管理Wiki",
@@ -104,7 +363,8 @@
     "use_env_var_if_empty": "如果数据库中的值为空,则环境变量的值 <cod>{{variable}}</code> 启用。",
     "note_for_the_only_env_option": "The GCS settings is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> ."
   },
-  "markdown_setting": {
+  "markdown_settings": {
+    "markdown_settings": "Markdown设置",
     "lineBreak_header": "换行设置",
     "lineBreak_desc": "您可以更改换行设置。",
     "lineBreak_options": {
@@ -147,7 +407,8 @@
       "import_recommended": "导入建议 {{target}}"
     }
   },
-  "customize_setting": {
+  "customize_settings": {
+    "customize_settings": "页面定制",
     "default_sidebar_mode": {
       "title": "默认的侧边栏模式",
       "desc": "你可以为新用户和访问该网页的客人设置侧边栏模式。",
@@ -226,6 +487,12 @@
     "delete_logo": "删除徽标"
   },
   "importer_management": {
+    "import_data": "导入数据",
+    "article": "主题",
+    "category": "分类",
+    "tag": "标签",
+    "page": "页面",
+    "page_path": "相对路径",
     "beta_warning": "这个函数是Beta。",
     "import_from": "Import from {{from}}",
     "import_growi_archive": "Import GROWI archive",
@@ -297,6 +564,7 @@
     "Directory_hierarchy_tag": "Directory hierarchy tag"
   },
   "export_management": {
+    "export_archive_data": "导出主题数据",
     "exporting_collection_list": "正在导出集合列表",
     "exported_data_list": "导出的存档数据列表",
     "export_collections": "导出集合",
@@ -315,12 +583,14 @@
     "delete": "删除"
   },
   "external_notification": {
+    "external_notification": "外部通知",
     "enabled": "Enabled",
     "disabled": "Disabled",
     "header_status": "Slack整合状态",
     "caution_enabled": "CAUTION: 目前,在此页面中配置的通知只会通知设置为主要的 Slack 工作区。 "
   },
   "slack_integration": {
+    "slack_integration": "Slack一体化",
     "selecting_bot_types": {
       "slack_bot": "Slack bot",
       "detailed_explanation": "详细说明",
@@ -431,12 +701,15 @@
     }
   },
   "slack_integration_legacy": {
+    "slack_integration_legacy": "旧版Slack一体化",
     "alert_disabled": "由于<a href='/admin/slack-integration'>新设置</a>已启用,因此该'旧版Slack一体化'目前已被禁用。",
     "alert_deplicated": "这个 '旧版Slack一体化' 已经过时了,将来会停止使用。使用<a href='/admin/slack-integration'>新的设置</a>来代替。"
   },
   "user_management": {
+    "user_management": "用户管理",
     "invite_users": "临时发布新用户",
     "click_twice_same_checkbox": "您应该至少选中一个复选框。",
+    "status": "状态",
     "invite_modal": {
       "emails": "电子邮件",
       "description1": "通过电子邮件地址临时发布新用户。",
@@ -492,12 +765,14 @@
     "current_users": "当前用户:"
   },
   "user_group_management": {
+    "user_group_management": "用户组管理",
     "create_group": "创建新组",
     "add_child_group": "添加一个子组",
     "remove_child_group": "移除",
     "deny_create_group": "不能用当前设置创建新组。",
     "group_name": "组名",
     "group_example": "e.g.:第1组",
+    "child_user_group": "儿童用户组",
     "parent_group": "父母组",
     "select_parent_group": "选择父组",
     "release_parent_group": "Release parent group",
@@ -538,6 +813,8 @@
     }
   },
   "audit_log_management": {
+    "audit_log": "审计日志",
+    "audit_log_settings": "审计日志设置",
     "user": "用户",
     "username": "帐号",
     "date": "日期",
@@ -608,6 +885,10 @@
     "PAGE_DELETE_COMPLETELY": "彻底删除页面",
     "PAGE_REVERT": "还原页面",
     "PAGE_EMPTY_TRASH": "清空垃圾箱",
+    "PAGE_RECURSIVELY_RENAME": "递归页面重命名",
+    "PAGE_RECURSIVELY_DELETE": "递归页面删除",
+    "PAGE_RECURSIVELY_DELETE_COMPLETELY": "递归页面完全删除",
+    "PAGE_RECURSIVELY_REVERT": "递归页面还原",
     "PAGE_SUBSCRIBE": "订阅页面",
     "PAGE_UNSUBSCRIBE": "退订页面",
     "PAGE_EXPORT": "导出页面",
@@ -625,7 +906,7 @@
     "SHARE_LINK_NOT_FOUND": "页面浏览量(未找到分享链接)",
     "ATTACHMENT_ADD": "添加附件",
     "ATTACHMENT_REMOVE": "删除附件",
-    "ACTION_ATTACHMENT_DOWNLOAD": "下载附件",
+    "ATTACHMENT_DOWNLOAD": "下载附件",
     "SEARCH_PAGE": "页面搜索",
     "SEARCH_PAGE_VIEW": "页面浏览量(搜索结果页面)",
     "ADMIN_APP_SETTING_UPDATE": "更新应用设置",

+ 45 - 269
packages/app/public/static/locales/zh_CN/translation.json

@@ -1,4 +1,7 @@
 {
+  "meta": {
+    "display_name": "简体中文"
+  },
   "Help": "帮助",
   "view": "View",
 	"Edit": "编辑",
@@ -17,26 +20,21 @@
 	"Move/Rename": "移动/重命名",
 	"Redirected": "重定向",
 	"Unlinked": "Unlinked",
+  "unlink_redirection": "取消链接重定向",
   "Done": "Done",
   "Cancel": "取消",
 	"Create": "创建",
   "Description": "描述",
 	"Admin": "管理",
 	"administrator": "管理员",
-	"Tag": "标签",
 	"Tags": "Tags",
   "New": "新建",
   "Close": "Close",
 	"Shortcuts": "快捷方式",
+  "CustomSidebar": "Custom Sidebar",
 	"eg": "e.g.",
 	"add": "添加",
 	"Undo": "撤销",
-	"Article": "主题",
-	"Page": "页面",
-	"Page Path": "相对路径",
-	"Category": "分类",
-	"User": "用户",
-	"status": "状态",
 	"account_id": "用户Id",
 	"Initialize": "初始化",
   "Update": "更新",
@@ -75,7 +73,7 @@
   "username": "用户名",
 	"Created": "创建",
 	"Last updated": "上次更新",
-  "Last_Login": "上次登录",
+  "last_login": "上次登录",
 	"Share": "分享",
   "Share Link": "分享链接",
 	"Markdown Link": "Markdown链接",
@@ -117,28 +115,15 @@
 	"Input page name (optional)": "Input page name (optional)",
 	"New Page": "新页面",
 	"Create under": "Create page under below:",
-	"Wiki Management Home Page": "Wiki管理首页",
-	"App Settings": "系统设置",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "See_more_detail_on_new_schema": "更多详情请见<a href='#'>{{url}}</a> <i class='icon-share-alt'></i> ",
 	"Site URL settings": "主页URL设置",
 	"Markdown Settings": "Markdown设置",
-	"Customize": "页面定制",
 	"Notification Settings": "通知设置",
-  "slack_integration": "Slack一体化",
-  "External_Notification": "外部通知",
-  "Legacy_Slack_Integration": "旧版Slack一体化",
-	"User_Management": "用户管理",
 	"external_account_management": "外部账户管理",
   "UserGroup": "用户组",
   "ChildUserGroup": "儿童用户组",
-	"UserGroup Management": "用户组管理",
-  "AuditLog": "审计日志",
-  "AuditLog Settings": "审计日志设置",
-	"Full Text Search Management": "全文搜索管理",
-	"Import Data": "导入数据",
-	"Export Archive Data": "导出主题数据",
 	"Basic Settings": "基础设置",
 	"Basic authentication": "基本身份验证",
 	"Register limitation": "注册限制",
@@ -149,11 +134,6 @@
 	"Only me": "只有我",
   "Only inside the group": "仅组内",
   "page_list": "Page List",
-	"scope_of_page_disclosure": "页面公开范围",
-	"set_point": "设定值",
-	"always_displayed": "始终显示",
-	"always_hidden": "总是隐藏",
-	"displayed_or_hidden": "显示/隐藏",
 	"Reselect the group": "重新选择组",
 	"Shareable link": "可分享链接",
 	"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
@@ -173,7 +153,6 @@
   "add_bookmark": "添加到书签",
   "remove_bookmark": "从书签中删除",
   "wide_view": "视野开阔",
-	"Recent Created": "最新创建",
   "Recent Changes": "最新修改",
   "Page Tree": "页面树",
   "original_path":"Original path",
@@ -196,6 +175,9 @@
     "page_not_exist": "该页面不存在",
     "page_not_exist_alert": "该页面不存在,请创建一个新页面"
   },
+  "not_creatable_page": {
+    "could_not_creata_path": "无法创建路径"
+  },
   "custom_navigation": {
     "no_page_list": "There are no pages under this page.",
     "link_sharing_is_disabled": "链接共享已被禁用"
@@ -204,7 +186,9 @@
 		"setup": "安装",
 		"create_initial_account": "创建初始用户",
 		"initial_account_will_be_administrator_automatically": "初始帐户将自动成为管理员。",
-		"unavaliable_user_id": "用户ID不可用"
+		"unavaliable_user_id": "用户ID不可用",
+    "failed_to_install": "GROWI安装失败。请再试一次。",
+    "failed_to_login_after_install": "安装后登录失败。重定向到登录表格..."
 	},
 	"breaking_changes": {
 		"v346_using_basic_auth": "当前使用的基本身份验证在不久的将来将不再可用。从%s中删除设置"
@@ -248,7 +232,6 @@
 		"new_password_confirm": "重复新密码",
 		"password_is_not_set": "密码未设置"
 	},
-	"Security Settings": "安全设置",
 	"API Settings": "API设置",
 	"API Token Settings": "API token 设置",
 	"Current API Token": "当前 API token",
@@ -369,7 +352,8 @@
 		"overwrite_scopes": "{{operation}和覆盖所有子体的作用域",
 		"notice": {
 			"conflict": "无法保存您所做的更改,因为其他人正在编辑此页。请在重新加载页面后重新编辑受影响的部分。"
-		}
+		},
+    "changes_not_saved": "您所做的更改可能不会保存。"
   },
   "page_comment": {
     "display_the_page_when_posting_this_comment": "Display the page when posting this comment",
@@ -622,219 +606,6 @@
     "share_settings" :"Share settings",
     "Invalid_Number_of_Date" : "You entered invalid value"
   },
-	"security_setting": {
-		"Guest Users Access": "来宾用户访问",
-		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
-		"Register limitation": "注册限制",
-		"Register limitation desc": "限制新用户注册",
-		"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
-		"users_without_account": "无法访问没有帐户的用户",
-		"example": "例子",
-		"restrict_emails": "您可以通过编写电子邮件域(以@开头)将电子邮件注册限制为wiki。",
-		"for_example": " 例如,如果要将注册限制为growi.org网站域,你可以写",
-		"in_this_case": ";在这种情况下,只有growi.org网站域将能够注册,所有其他用户将被拒绝。",
-		"insert_single": "请每行插入一个电子邮件地址。",
-    "page_list_and_search_results": "页面列表/搜索结果",
-		"page_listing_1": "页面列表/搜索<br>受“仅限我”限制",
-		"page_listing_1_desc": "列出/搜索时显示受“仅限我”选项限制的页面",
-		"page_listing_2": "页面列表/搜索<br>受用户组限制",
-		"page_listing_2_desc": "显示列出/搜索时受用户组限制的页面",
-    "page_access_rights": "页面访问",
-    "page_delete_rights": "删除权限",
-    "page_delete": "删除",
-    "page_delete_completely": "彻底删除",
-    "other_options": "其他选项",
-    "deletion_explain": "限制用户对选定的单一页面进行垃圾处理。",
-    "complete_deletion_explain": "限制可以完全删除所选单页的用户。",
-    "recursive_deletion_explain": "限制用户可以捣毁包括子孙在内的页面。",
-    "recursive_complete_deletion_explain": "限制可以完全删除页面的用户,包括子孙。",
-    "inherit": "继承(使用与单页相同的设置)。",
-		"admin_only": "仅管理员",
-		"admin_and_author": "管理员|作者",
-		"anyone": "任何人",
-    "session": "会议",
-    "max_age": "有效期间  (msec)",
-    "max_age_desc": "指定使用户会话过期的数量(以毫秒为单位)。<br>默认值: 2592000000 (30天)",
-    "max_age_caution": "修改该值后需要重启服务器。",
-    "forced_update_desc": "设置已被强行更改。以前的设置: ",
-    "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 仅管理员 > 管理员|作者 > 何人",
-		"Authentication mechanism settings": "身份验证机制设置",
-		"setup_is_not_yet_complete": "安装尚未完成",
-		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
-		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
-		"xss_prevent_setting_link": "转到Markdown设置",
-		"callback_URL": "回调URL",
-		"providerName": "提供程序名称",
-		"issuerHost": "发行者主机",
-		"scope": "Scope",
-		"desc_of_callback_URL": "在{{AuthName}}身份提供程序的设置中使用它",
-    "authorization_endpoint": "Authorization Endpoint",
-    "token_endpoint": "Token Endpoint",
-    "revocation_endpoint": "Revocation Endpoint",
-    "introspection_endpoint": "Introspection Endpoint",
-    "userinfo_endpoint": "UserInfo Endpoint",
-    "end_session_endpoint": "EndSessioin Endpoint",
-    "registration_endpoint": "Registration Endpoint",
-    "jwks_uri": "JSON Web Key Set URL",
-		"clientID": "Client ID",
-		"client_secret": "客户机密",
-		"updated_general_security_setting": "更新安全设置成功",
-		"setup_not_completed_yet": "安装尚未完成",
-		"guest_mode": {
-			"deny": "拒绝(仅限注册用户)",
-			"readonly": "接受(来宾可以只读)"
-		},
-		"registration_mode": {
-			"open": "打开(任何人都可以注册)",
-			"restricted": "受限(需要管理员批准)",
-			"closed": "已关闭(仅限邀请)"
-		},
-    "share_link_rights": "分享链接权",
-    "enable_link_sharing": "启用链接共享",
-    "all_share_links": "所有共享链接",
-		"configuration": " 配置",
-		"optional": "可选的",
-		"Treat username matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>username</code> match",
-		"Treat username matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>username</code>.",
-		"Treat email matching as identical": "Automatically bind external accounts newly logged in to local accounts when <code>email</code> match",
-		"Treat email matching as identical_warn": "WARNING: Be aware of security because the system treats the same user as a match of <code>email</code>.",
-		"Use env var if empty": "Use env var <code>{{env}}</code> if empty",
-		"Use default if both are empty": "If both ​​are empty, the default value <code>{{target}}</code> is used.",
-		"missing mandatory configs": "The following mandatory items are not set in either database nor environment variables.",
-		"Local": {
-			"name": "ID/Password",
-			"note for the only env option": "The LOCAL authentication is limited by the value of environment variable.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
-      "enable_local": "Enable ID/Password",
-      "password_reset_by_users": "用户重置密码",
-      "enable_password_reset_by_users": "启用用户重置密码",
-      "password_reset_desc": "忘记密码时,用户可以自行重置",
-      "email_authentication": "用户注册时的电子邮件身份验证",
-      "enable_email_authentication": "启用电子邮件身份验证",
-      "enable_email_authentication_desc": "用户注册将执行电子邮件身份验证。",
-      "please_enable_mailer": "请先设置邮件程序。",
-      "need_complete_mail_setting_warning": "要使用以下功能,请完成邮件设置。"
-		},
-		"ldap": {
-			"enable_ldap": "Enable LDAP",
-			"server_url_detail": "The LDAP URL of the directory service in the format <code>ldap://host:port/DN</code> or <code>ldaps://host:port/DN</code>.",
-			"bind_mode": "Binding Mode",
-			"bind_manager": "Manager Bind",
-			"bind_user": "User Bind",
-			"bind_DN_manager_detail": "The DN of the account that authenticates and queries the directory service",
-			"bind_DN_user_detail1": "The query used to bind with the directory service.",
-			"bind_DN_user_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
-			"bind_DN_password": "Bind DN Password",
-			"bind_DN_password_manager_detail": "The password for the Bind DN account.",
-			"bind_DN_password_user_detail": "The password that is entered in the login page will be used to bind.",
-			"search_filter": "Search Filter",
-			"search_filter_detail1": "The query used to locate the authenticated user.",
-			"search_filter_detail2": "Use <code>&#123;&#123;username&#125;&#125;</code> to reference the username entered in the login page.",
-			"search_filter_detail3": "If empty, the filter <code>(uid=&#123;&#123;username&#125;&#125;)</code> is used.",
-			"search_filter_example1": "Match with 'uid' or 'mail'",
-			"search_filter_example2": "Match with 'sAMAccountName' for Active Directory",
-			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
-			"name_detail": "Specification of mappings for full name when creating new users",
-			"mail_detail": "Specification of mappings for mail address when creating new users",
-			"group_search_base_DN": "Group Search Base DN",
-			"group_search_base_DN_detail": "The base DN from which to search for groups. If defined, also <code>Group Search Filter</code> must be defined for the search to work.",
-			"group_search_filter": "Group Search Filter",
-			"group_search_filter_detail1": "The query used to filter for groups.",
-			"group_search_filter_detail2": "Login via LDAP is accepted only when this query hits one or more groups.",
-			"group_search_filter_detail3": "Use <code>&#123;&#123;dn&#125;&#125;</code> to have it replaced of the found user object.",
-			"group_search_filter_detail4": "<code>(&(cn=group1)(memberUid=&#123;&#123;dn&#125;&#125;))</code> hits the groups which has <code>cn=group1</code> and <code>memberUid</code> includes the user's <code>uid</code>(when <code>Group DN Property</code> is not changed from the default value.)",
-			"group_search_user_DN_property": "User DN Property",
-			"group_search_user_DN_property_detail": "The property of user object to use in <code>&#123;&#123;dn&#125;&#125;</code> interpolation of <code>Group Search Filter</code>.",
-			"test_config": "Test Saved Configuration",
-			"updated_ldap": "Succeeded to update LDAP setting"
-		},
-		"SAML": {
-			"name": "SAML",
-			"enable_saml": "Enable SAML",
-			"id_detail": "Specification of the name of attribute which can identify the user in SAML Identity Provider",
-			"username_detail": "Specification of mappings for <code>username</code> when creating new users",
-			"mapping_detail": "Specification of mappings for {{target}} when creating new users",
-			"cert_detail": "PEM-encoded X.509 signing certificate to validate the response from IdP",
-			"Use env var if empty": "If the value in the database is empty, the value of the environment variable <code>{{env}}</code> is used.",
-			"note for the only env option": "The setting item that enables or disables the SAML authentication and the highlighted setting items use only the value of environment variables.<br>To change this setting, please change to false or delete the value of the environment variable <code>{{env}}</code> .",
-			"attr_based_login_control_detail": "Limit who can sign up by using <code>&lt;saml: Attribute&gt;</code> element included in <code>&lt;saml: AttributeStatement&gt;</code> element and its child element <code>&lt;saml: AttributeValue&gt;</code>.",
-			"attr_based_login_control_rule_help": "<h5>Supported Queries:</h5><ul><li>Terms</li><li>Fields</li><li>AND/NOT/OR Operator</li><li>Grouping</li></ul><h5>Unsupported Queries:</h5><ul><li>Wildcard, Fuzzy, Proximity, Range and Boosting</li><li>+/- Operator</li><li>Field Grouping</li></ul><h5>Escaping special characters</h5>It is needed to escape following special characters:<br><code>+ - && || ! ( ) { } [ ] ^ &quot; &tilde; * ? : &#92;</code> and <code>/</code>",
-			"attr_based_login_control_rule_example1": "<h5>Example for conditions</h5>If a rule is <code>(Department: A || Department: B) && Position: Leader</code>, users who have either <code>Department: A</code> or <code>Department: B</code> and have <code>Position: Leader</code> <strong>can</strong> sign in.",
-      "attr_based_login_control_rule_example2": "<h5>Example for escaping</h5>If you would like to use URL as a query value, escape the following:<br><code>http&#92;:&#92;/&#92;/schemas.example.com&#92;/ws&#92;/2005&#92;/05&#92;/identity&#92;/claims&#92;/emailaddress: &quot;myname@example.com&quot;</code>",
-      "updated_saml": "Succeeded to update SAML setting"
-		},
-		"Basic": {
-			"enable_basic": "Enable Basic",
-			"name": "Basic Authentication",
-			"desc_1": "Login with <code>username</code> in Authorization header.",
-			"desc_2": "User will be automatically generated if not exist.",
-			"updated_basic": "Succeeded to update Basic setting"
-		},
-		"OAuth": {
-			"enable_oidc": "Enable OIDC",
-			"register": "Register for %s",
-			"change_redirect_url": "Enter <code>%s</code> <br>(where <code>%s</code> is your host name) for \"Authorized redirect URIs\".",
-			"Google": {
-				"enable_google": "Enable Google OAuth",
-				"name": "Google OAuth",
-				"register_1": "Access {{link}}",
-				"register_2": "Create Project if no projects exist",
-				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
-				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
-				"register_5": "Copy and paste your ClientID and Client Secret above",
-				"updated_google": "Succeeded to update Google OAuth setting"
-			},
-			"Facebook": {
-				"name": "Facebook OAuth"
-			},
-			"Twitter": {
-				"enable_twitter": "Enable Twitter OAuth",
-				"name": "Twitter OAuth",
-				"register_1": "Access {{link}}",
-				"register_2": "Sign in Twitter",
-				"register_3": "Create Credentials &rightarrow; OAuth client ID &rightarrow; Select \"Web application\"",
-				"register_4": "Register your OAuth App with one of Authorized redirect URIs as <code>{{url}}</code>",
-				"register_5": "Copy and paste your ClientID and Client Secret above",
-				"updated_twitter": "Succeeded to update Twitter OAuth setting"
-			},
-			"GitHub": {
-				"enable_github": "Enable GitHub OAuth",
-				"name": "GitHub OAuth",
-				"register_1": "Access {{link}}",
-				"register_2": "Register your OAuth App with \"Authorization callback URL\" as <code>{{url}}</code>",
-				"register_3": "Copy and paste your ClientID and Client Secret above",
-				"updated_github": "Succeeded to update GitHub OAuth setting"
-			},
-			"OIDC": {
-				"name": "OpenID Connect",
-				"id_detail": "Specification of the name of attribute which can identify the user in OIDC claims",
-				"username_detail": "Specification of mappings for <code>username</code> when creating new users",
-				"name_detail": "Specification of mappings for <code>name</code> when creating new users",
-				"mapping_detail": "Specification of mappings for %s when creating new users",
-				"register_1": "Contant to OIDC IdP Administrator",
-				"register_2": "Register your OIDC App with \"Authorization callback URL\" as <code>%s</code>",
-				"register_3": "Copy and paste your ClientID and Client Secret above",
-				"updated_oidc": "Succeeded to update OpenID Connect",
-        "Use discovered URL if empty": "Use discovered URL from \"Issuer Host\" if empty"
-			},
-			"how_to": {
-				"google": "How to configure Google OAuth?",
-				"github": "How to configure GitHub OAuth?",
-				"twitter": "How to configure Twitter OAuth?",
-				"oidc": "How to configure OIDC?"
-			}
-		},
-		"form_item_name": {
-			"entryPoint": "Entry point",
-			"issuer": "Issuer",
-			"cert": "Certificate",
-			"attrMapId": "ID",
-			"attrMapUsername": "Username",
-			"attrMapMail": "Mail Address",
-			"attrMapFirstName": "First Name",
-			"attrMapLastName": "Last Name",
-			"ABLCRule": "Rule"
-		}
-	},
 	"notification_setting": {
 		"slack_incoming_configuration": "Slack Incoming Webhooks configuration",
 		"prioritize_webhook": "Prioritize incoming webhook than Slack App",
@@ -884,28 +655,6 @@
 		"delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
 		"toggle_notification": "Updated setting of {{path}}"
 	},
-	"full_text_search_management": {
-		"elasticsearch_management": "Elasticsearch管理",
-		"connection_status": "连接状态",
-		"connection_status_label_unconfigured": "未配置",
-		"connection_status_label_connected": "已连接",
-		"connection_status_label_disconnected": "断开的",
-		"connection_status_label_erroroccured": "搜索服务出错",
-		"indices_status": "索引状态",
-		"indices_status_label_normalized": "标准化",
-		"indices_status_label_unnormalized": "重建或损坏",
-		"indices_summary": "索引摘要",
-		"reconnect": "重新连接",
-		"reconnect_button": "尝试重新连接",
-		"reconnect_description": "单击按钮尝试重新连接到Elasticsearch。",
-		"normalize": "规范化",
-		"normalize_button": "规范化索引",
-		"normalize_description": "单击按钮修复损坏的索引。",
-		"rebuild": "重建",
-		"rebuild_button": "重建索引",
-		"rebuild_description_1": "单击按钮以重新生成索引并添加所有页面数据。",
-		"rebuild_description_2": "这可能需要一段时间。"
-	},
 	"personal_dropdown": {
 		"home": "家",
 		"settings": "设置",
@@ -972,8 +721,14 @@
 	"login": {
 		"Sign in error": "登录错误",
 		"Registration successful": "注册成功",
-		"Setup": "安装程序"
+		"Setup": "安装程序",
+    "enabled_ldap_has_configuration_problem":"启用了LDAP,但配置有问题。",
+    "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"
 	},
+  "invited": {
+    "discription_heading": "创建账户",
+    "discription": "用被邀请的电子邮件地址创建一个你的账户"
+  },
   "export_bulk": {
     "failed_to_export": "导出失败",
     "failed_to_count_pages": "页面计数失败",
@@ -985,9 +740,10 @@
 		"fail_to_save_access_token": "无法保存访问令牌。请再试一次。",
 		"fail_to_fetch_access_token": "无法获取访问令牌。请重新连接。",
 		"successfully_disconnected": "成功断开连接!",
-		"strategy_has_not_been_set_up": "{{strategy}尚未设置",
+    "strategy_has_not_been_set_up": "{{strategy}} 尚未设置",
+    "ldap_user_not_valid": "Ldap user is no valid",
+    "external_account_not_exist": "查找或创建外部账户失败",
 		"maximum_number_of_users": "注册的用户数不能超过最大值。",
-		"database_error": "发生数据库服务器错误",
 		"sign_in_failure": "登录失败。",
 		"aws_sttings_required": "使用此功能所需的AWS设置。请询问管理员。",
 		"application_already_installed": "应用程序已安装。",
@@ -1005,7 +761,21 @@
 		"complete_to_install2": "完成安装GROWI!请先检查此页上的每个设置。",
 		"failed_to_create_admin_user": "无法创建管理用户。{{errMessage}",
     "successfully_send_email_auth":"我们向 {{email}} 发送了一封电子邮件。 请点击邮件中的网址并完成注册。",
-    "incorrect_token_or_expired_url":"令牌不正确或 URL 已过期。"
+    "incorrect_token_or_expired_url":"令牌不正确或 URL 已过期。",
+    "user_already_loggedin": "当你登录的时候,你不能创建一个新的账户。",
+    "registration_closed": "你无权创建一个新的账户。",
+    "Username has invalid characters": "用户名有无效字符",
+    "Username field is required": "用户ID字段是必需的",
+    "Name field is required": "姓名字段为必填项",
+    "Email format is invalid": "电子邮件的格式是无效的",
+    "Email field is required": "电子邮件字段是必需的",
+    "Password has invalid character": "密码有无效字符",
+    "Password minimum character should be more than 8 characters": "密码最小字符应超过8个字符",
+    "Password field is required": "密码字段是必需的",
+    "Username or E-mail has invalid characters": "用户名或电子邮件有无效的字符",
+    "Password minimum character should be more than 6 characters": "密码最小字符应超过6个字符",
+    "user_not_found": "未找到用户",
+    "provider_duplicated_username_exception": "<p><strong><i class='icon-fw icon-ban'></i>发生了重复用户名异常</strong></p><p class='mb-0'> 你的 {{ failedProviderForDuplicatedUsernameException }} 认证成功了,但不能创建新的用户。参见问题<a href='https://github.com/weseek/growi/issues/193'>#193</a>.</p>"
 	},
   "grid_edit":{
     "create_bootstrap_4_grid":"创建Bootstrap 4网格",
@@ -1032,6 +802,7 @@
     "confirm_new_password": "确认新密码",
     "email_is_required": "电子邮件是必需的",
     "success_to_send_email": "我发了一封电子邮件",
+    "feature_is_unavailable": "此功能不可用",
     "incorrect_token_or_expired_url":"令牌不正确或 URL 已过期。 请通过以下链接重新发送密码重置请求",
     "password_and_confirm_password_does_not_match": "密码和确认密码不匹配"
   },
@@ -1091,6 +862,7 @@
   "crop_image_modal": {
     "image_crop": "图像裁剪",
     "crop": "修剪",
+    "save": "节省",
     "reset": "重启",
     "cancel": "取消"
   },
@@ -1137,5 +909,9 @@
   "page_operation":{
     "paths_recovered": "成功恢复了页面路径",
     "path_recovery_failed":"路径恢复失败"
+  },
+  "footer": {
+    "bookmarks": "书签",
+    "recently_created": "最近创建页面"
   }
 }

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

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

+ 8 - 14
packages/app/resource/locales/en_US/sandbox-diagrams.md

@@ -29,6 +29,7 @@ See [PlantUML](http://plantuml.com/).
 
 ## Sequence diagram
 
+``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
 skinparam roundcorner 20
@@ -58,13 +59,12 @@ A --> User: Done
 deactivate A
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## Class diagram
 
+``` plantuml
 @startuml
 
 class BaseClass
@@ -86,13 +86,11 @@ namespace net.foo {
 BaseClass <|-- net.unused.Person
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## Component diagram
-
+``` plantuml
 @startuml
 
 package "Some Group" {
@@ -125,14 +123,12 @@ database "MySql" {
 [Folder 3] --> [Frame 4]
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## State diagram
 
-
+``` plantuml
 @startuml
 scale 600 width
 
@@ -153,9 +149,7 @@ State3 --> [*] : Succeeded / Save Result
 State3 --> [*] : Aborted
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 # :pencil: blockdiag

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

@@ -1,6 +1,6 @@
 # :pencil: Math
 
-See [MathJax](https://www.mathjax.org/).
+See [KaTeX](https://katex.org/).
 
 ## Inline Formula
 

+ 34 - 37
packages/app/resource/locales/en_US/sandbox.md

@@ -245,9 +245,6 @@ You can create links using `[Display text](URL)`.
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 This is the most flexible linker.
 Both the page description and link address can be displayed on the page.
 
@@ -354,65 +351,65 @@ aligned    | aligned     | aligned
 | left       | right       | center       |
 | aligned    | aligned     | aligned      |
 
-## TSV (crowi-plus notation)
+## TSV
 
+~~~
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv
-Content Cell  Content Cell
-Content Cell  Content Cell
-:::
-```
+~~~
 
-::: tsv
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## TSV with header (crowi-plus notation)
+## TSV with header
 
+~~~
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
-```
+~~~
 
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## CSV (crowi-plus original notation)
+## CSV
 
-```
-::: csv
+~~~
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
-## CSV with header (crowi-plus original notation)
+## CSV with header
 
-```
-::: csv-h
+~~~
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv-h
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
 
 # :memo: Footnote

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

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

+ 8 - 15
packages/app/resource/locales/ja_JP/sandbox-diagrams.md

@@ -28,7 +28,7 @@ See [diagrams.net](https://diagrams.net)
 See [PlantUML](http://plantuml.com/).
 
 ## シーケンス図
-
+``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
 skinparam roundcorner 20
@@ -58,13 +58,11 @@ A --> User: Done
 deactivate A
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## クラス図
-
+``` plantuml
 @startuml
 
 class BaseClass
@@ -86,13 +84,11 @@ namespace net.foo {
 BaseClass <|-- net.unused.Person
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## コンポーネント図
-
+``` plantuml
 @startuml
 
 package "Some Group" {
@@ -125,14 +121,12 @@ database "MySql" {
 [Folder 3] --> [Frame 4]
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## ステート図
 
-
+``` plantuml
 @startuml
 scale 600 width
 
@@ -153,9 +147,8 @@ State3 --> [*] : Succeeded / Save Result
 State3 --> [*] : Aborted
 
 @enduml
+```
 
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
 
 # :pencil: blockdiag
 

+ 1 - 1
packages/app/resource/locales/ja_JP/sandbox-math.md

@@ -1,6 +1,6 @@
 # :pencil: Math
 
-See [MathJax](https://www.mathjax.org/).
+See [KaTeX](https://katex.org/).
 
 ## Inline Formula
 

+ 34 - 37
packages/app/resource/locales/ja_JP/sandbox.md

@@ -244,9 +244,6 @@ ___
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 最も柔軟な Linker です。
 記述中のページを基点とした相対リンクと、表示テキストに対するリンクを同時に実現できます。
 
@@ -353,65 +350,65 @@ aligned    | aligned     | aligned
 | left       | right       | center       |
 | aligned    | aligned     | aligned      |
 
-## TSV (crowi-plus 独自記法)
+## TSV
 
+~~~
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv
-Content Cell  Content Cell
-Content Cell  Content Cell
-:::
-```
+~~~
 
-::: tsv
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## TSV ヘッダ付き (crowi-plus 独自記法)
+## TSV (ヘッダ付き)
 
+~~~
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
-```
+~~~
 
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## CSV (crowi-plus 独自記法)
+## CSV
 
-```
-::: csv
+~~~
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
-## CSV ヘッダ付き (crowi-plus 独自記法)
+## CSV (ヘッダ付き)
 
-```
-::: csv-h
+~~~
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv-h
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
 
 # :memo: Footnote

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

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

+ 8 - 14
packages/app/resource/locales/zh_CN/sandbox-diagrams.md

@@ -29,6 +29,7 @@ See [PlantUML](http://plantuml.com/).
 
 ## Sequence diagram
 
+``` plantuml
 @startuml
 skinparam sequenceArrowThickness 2
 skinparam roundcorner 20
@@ -58,13 +59,12 @@ A --> User: Done
 deactivate A
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## Class diagram
 
+``` plantuml
 @startuml
 
 class BaseClass
@@ -86,13 +86,11 @@ namespace net.foo {
 BaseClass <|-- net.unused.Person
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## Component diagram
-
+``` plantuml
 @startuml
 
 package "Some Group" {
@@ -125,14 +123,12 @@ database "MySql" {
 [Folder 3] --> [Frame 4]
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 ## State diagram
 
-
+``` plantuml
 @startuml
 scale 600 width
 
@@ -153,9 +149,7 @@ State3 --> [*] : Succeeded / Save Result
 State3 --> [*] : Aborted
 
 @enduml
-
-<!-- Reset PlantUML -->
-<div class="clearfix"></div>
+```
 
 
 # :pencil: blockdiag

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

@@ -1,6 +1,6 @@
 # :pencil: Math
 
-See [MathJax](https://www.mathjax.org/).
+See [KaTeX](https://katex.org/).
 
 ## Inline Formula
 

+ 34 - 37
packages/app/resource/locales/zh_CN/sandbox.md

@@ -245,9 +245,6 @@ You can create links using `[Display text](URL)`.
 
 ## Pukiwiki like linker
 
-(available by [weseek/growi-plugin-pukiwiki-like-linker
-](https://github.com/weseek/growi-plugin-pukiwiki-like-linker) )
-
 This is the most flexible linker.
 Both the page description and link address can be displayed on the page.
 
@@ -354,65 +351,65 @@ aligned    | aligned     | aligned
 | left       | right       | center       |
 | aligned    | aligned     | aligned      |
 
-## TSV (crowi-plus notation)
+## TSV
 
+~~~
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv
-Content Cell  Content Cell
-Content Cell  Content Cell
-:::
-```
+~~~
 
-::: tsv
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## TSV with header (crowi-plus notation)
+## TSV with header
 
+~~~
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
 ```
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
-```
+~~~
 
-::: tsv-h
-First Header Second Header
-Content Cell Content Cell
-Content Cell Content Cell
-:::
+``` tsv-h
+First Header	Second Header
+Content Cell	Content Cell
+Content Cell	Content Cell
+```
 
-## CSV (crowi-plus original notation)
+## CSV
 
-```
-::: csv
+~~~
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv
+``` csv
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
-## CSV with header (crowi-plus original notation)
+## CSV with header
 
-```
-::: csv-h
+~~~
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
 ```
+~~~
 
-::: csv-h
+``` csv-h
 First Header,Second Header
 Content Cell,Content Cell
 Content Cell,Content Cell
-:::
+```
 
 
 # :memo: Footnote

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

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

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

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

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

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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
packages/app/src/client/legacy/thirdparty-js/waves.js


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 26 - 22
packages/app/src/client/services/AppContainer.js

@@ -1,9 +1,6 @@
 import { Container } from 'unstated';
 
-
-import GrowiRenderer, { generatePreviewRenderer } from '~/services/renderer/growi-renderer';
-
-import { i18nFactory } from '../util/i18n';
+// import { i18nFactory } from '../util/i18n';
 
 /**
  * Service container related to options for Application
@@ -23,7 +20,7 @@ export default class AppContainer extends Container {
       const currentUser = JSON.parse(currentUserElem.textContent);
       userLocaleId = currentUser?.lang;
     }
-    this.i18n = i18nFactory(userLocaleId);
+    // this.i18n = i18nFactory(userLocaleId);
 
     this.containerInstances = {};
     this.componentInstances = {};
@@ -59,17 +56,19 @@ export default class AppContainer extends Container {
   }
 
   injectToWindow() {
-    window.appContainer = this;
+    // for fix lint error
+
+    // window.appContainer = this;
 
-    const growiRenderer = new GrowiRenderer(this.getConfig());
-    growiRenderer.init();
+    // const growiRenderer = new GrowiRenderer(this.getConfig());
+    // growiRenderer.init();
 
-    window.growiRenderer = growiRenderer;
+    // window.growiRenderer = growiRenderer;
 
-    // backward compatibility
-    window.crowi = this;
-    window.crowiRenderer = window.growiRenderer;
-    window.crowiPlugin = window.growiPlugin;
+    // // backward compatibility
+    // window.crowi = this;
+    // window.crowiRenderer = window.growiRenderer;
+    // window.crowiPlugin = window.growiPlugin;
   }
 
   getConfig() {
@@ -105,25 +104,30 @@ export default class AppContainer extends Container {
     return this.containerInstances[className];
   }
 
+
+  /*
+  * Note: Use globalEmitter instaead of registerComponentInstance and getComponentInstance
+  */
+
   /**
    * Register React component instance
    * @param {string} id
    * @param {object} instance React component instance
    */
-  registerComponentInstance(id, instance) {
-    if (instance == null) {
-      throw new Error('The specified instance must not be null');
-    }
+  // registerComponentInstance(id, instance) {
+  //   if (instance == null) {
+  //     throw new Error('The specified instance must not be null');
+  //   }
 
-    this.componentInstances[id] = instance;
-  }
+  //   this.componentInstances[id] = instance;
+  // }
 
   /**
    * Get registered React component instance
    * @param {string} id
    */
-  getComponentInstance(id) {
-    return this.componentInstances[id];
-  }
+  // getComponentInstance(id) {
+  //   return this.componentInstances[id];
+  // }
 
 }

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

@@ -1,106 +0,0 @@
-import { Container } from 'unstated';
-
-import loggerFactory from '~/utils/logger';
-
-const logger = loggerFactory('growi:services:EditorContainer');
-
-
-/**
- * Service container related to options for Editor/Preview
- * @extends {Container} unstated Container
- */
-export default class EditorContainer extends Container {
-
-  constructor(appContainer) {
-    super();
-
-    this.appContainer = appContainer;
-    this.appContainer.registerContainer(this);
-
-    this.state = {
-      tags: null,
-    };
-
-    this.isSetBeforeunloadEventHandler = false;
-
-    this.initDrafts();
-
-  }
-
-  /**
-   * Workaround for the mangling in production build to break constructor.name
-   */
-  static getClassName() {
-    return 'EditorContainer';
-  }
-
-  /**
-   * initialize state for drafts
-   */
-  initDrafts() {
-    this.drafts = {};
-
-    // restore data from localStorage
-    const contents = window.localStorage.drafts;
-    if (contents != null) {
-      try {
-        this.drafts = JSON.parse(contents);
-      }
-      catch (e) {
-        window.localStorage.removeItem('drafts');
-      }
-    }
-
-    if (this.state.pageId == null) {
-      const draft = this.findDraft(this.state.path);
-      if (draft != null) {
-        this.state.markdown = draft;
-      }
-    }
-  }
-
-
-  // See https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#example
-  showUnsavedWarning(e) {
-    // Cancel the event
-    e.preventDefault();
-    // display browser default message
-    e.returnValue = '';
-    return '';
-  }
-
-  disableUnsavedWarning() {
-    window.removeEventListener('beforeunload', this.showUnsavedWarning);
-    this.isSetBeforeunloadEventHandler = false;
-  }
-
-  enableUnsavedWarning() {
-    if (!this.isSetBeforeunloadEventHandler) {
-      window.addEventListener('beforeunload', this.showUnsavedWarning);
-      this.isSetBeforeunloadEventHandler = true;
-    }
-  }
-
-  clearDraft(path) {
-    delete this.drafts[path];
-    window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
-  }
-
-  clearAllDrafts() {
-    window.localStorage.removeItem('drafts');
-  }
-
-  saveDraft(path, body) {
-    this.drafts[path] = body;
-    window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
-  }
-
-  findDraft(path) {
-    if (this.drafts != null && this.drafts[path]) {
-      return this.drafts[path];
-    }
-
-    return null;
-  }
-
-}

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

@@ -7,9 +7,6 @@ import { Container } from 'unstated';
 import { EditorMode } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
-import { toastError } from '../util/apiNotification';
-import { apiPost } from '../util/apiv1-client';
-import { apiv3Post } from '../util/apiv3-client';
 import {
   DetachCodeBlockInterceptor,
   RestoreCodeBlockInterceptor,
@@ -17,6 +14,8 @@ import {
 import {
   DrawioInterceptor,
 } from '../../services/renderer/interceptor/drawio-interceptor';
+import { toastError } from '../util/apiNotification';
+import { apiPost } from '../util/apiv1-client';
 
 const { isTrashPage } = pagePathUtils;
 
@@ -54,9 +53,6 @@ export default class PageContainer extends Container {
       path,
       isEmpty: mainContent.getAttribute('data-page-is-empty'),
 
-      createdAt: mainContent.getAttribute('data-page-created-at'),
-      // please use useCurrentUpdatedAt instead
-      updatedAt: mainContent.getAttribute('data-page-updated-at'),
       deletedAt: mainContent.getAttribute('data-page-deleted-at') || null,
 
       isUserPage: JSON.parse(mainContent.getAttribute('data-page-user')) != null,
@@ -138,6 +134,7 @@ export default class PageContainer extends Container {
 
   /**
    * initialize state for markdown data
+   * [Already SWRized]
    */
   initStateMarkdown() {
     let pageContent = '';
@@ -265,81 +262,6 @@ export default class PageContainer extends Container {
     return res;
   }
 
-  async saveAndReload(optionsToSave, editorMode) {
-    if (optionsToSave == null) {
-      const msg = '\'saveAndReload\' requires the \'optionsToSave\' param';
-      throw new Error(msg);
-    }
-
-    if (editorMode == null) {
-      logger.warn('\'saveAndReload\' requires the \'editorMode\' param');
-      return;
-    }
-
-    const { pageId, path } = this.state;
-    let { revisionId } = this.state;
-
-    const options = Object.assign({}, optionsToSave);
-
-    let markdown;
-    if (editorMode === EditorMode.HackMD) {
-      const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-      markdown = await pageEditorByHackmd.getMarkdown();
-      // set option to sync
-      options.isSyncRevisionToHackmd = true;
-      revisionId = this.state.revisionIdHackmdSynced;
-    }
-    else {
-      const pageEditor = this.appContainer.getComponentInstance('PageEditor');
-      markdown = pageEditor.getMarkdown();
-    }
-
-    let res;
-    if (pageId == null) {
-      res = await this.createPage(path, markdown, options);
-    }
-    else {
-      res = await this.updatePage(pageId, revisionId, markdown, options);
-    }
-
-    const editorContainer = this.appContainer.getContainer('EditorContainer');
-    editorContainer.clearDraft(path);
-    window.location.href = path;
-
-    return res;
-  }
-
-  async createPage(pagePath, markdown, tmpParams) {
-    const socketIoContainer = this.appContainer.getContainer('SocketIoContainer');
-
-    // clone
-    const params = Object.assign(tmpParams, {
-      path: pagePath,
-      body: markdown,
-    });
-
-    const res = await apiv3Post('/pages/', params);
-    const { page, tags, revision } = res.data;
-
-    return { page, tags, revision };
-  }
-
-  async updatePage(pageId, revisionId, markdown, tmpParams) {
-    const socketIoContainer = this.appContainer.getContainer('SocketIoContainer');
-
-    // clone
-    const params = Object.assign(tmpParams, {
-      page_id: pageId,
-      revision_id: revisionId,
-      body: markdown,
-    });
-
-    const res = await apiPost('/pages.update', params);
-    if (!res.ok) {
-      throw new Error(res.error);
-    }
-    return res;
-  }
 
   showSuccessToastr() {
     toastr.success(undefined, 'Saved successfully', {

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

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

+ 81 - 15
packages/app/src/client/services/page-operation.ts

@@ -1,10 +1,15 @@
+import { SubscriptionStatusType, Nullable } from '@growi/core';
 import urljoin from 'url-join';
 
-import { SubscriptionStatusType } from '~/interfaces/subscription';
+import { OptionsToSave } from '~/interfaces/editor-settings';
+import loggerFactory from '~/utils/logger';
+
 
 import { toastError } from '../util/apiNotification';
+import { apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 
+const logger = loggerFactory('growi:services:page-operation');
 
 export const toggleSubscribe = async(pageId: string, currentStatus: SubscriptionStatusType | undefined): Promise<void> => {
   try {
@@ -37,23 +42,9 @@ export const toggleBookmark = async(pageId: string, currentValue?: boolean): Pro
   }
 };
 
-// Utility to update body class
-const updateBodyClassByView = (expandContentWidth: boolean): void => {
-  const bodyClasses = document.body.classList;
-  const isLayoutFluid = bodyClasses.contains('growi-layout-fluid');
-
-  if (expandContentWidth && !isLayoutFluid) {
-    bodyClasses.add('growi-layout-fluid');
-  }
-  else if (isLayoutFluid) {
-    bodyClasses.remove('growi-layout-fluid');
-  }
-};
-
 export const updateContentWidth = async(pageId: string, newValue: boolean): Promise<void> => {
   try {
     await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
-    updateBodyClassByView(newValue);
   }
   catch (err) {
     toastError(err);
@@ -91,3 +82,78 @@ export const exportAsMarkdown = (pageId: string, revisionId: string, format: str
 export const resumeRenameOperation = async(pageId: string): Promise<void> => {
   await apiv3Post('/pages/resume-rename', { pageId });
 };
+
+// TODO: define return type
+const createPage = async(pagePath: string, markdown: string, tmpParams: OptionsToSave) => {
+  // clone
+  const params = Object.assign(tmpParams, {
+    path: pagePath,
+    body: markdown,
+  });
+
+  const res = await apiv3Post('/pages/', params);
+  const { page, tags, revision } = res.data;
+
+  return { page, tags, revision };
+};
+
+// TODO: define return type
+const updatePage = async(pageId: string, revisionId: string, markdown: string, tmpParams: OptionsToSave) => {
+  // clone
+  const params = Object.assign(tmpParams, {
+    page_id: pageId,
+    revision_id: revisionId,
+    body: markdown,
+  });
+
+  const res: any = await apiPost('/pages.update', params);
+  if (!res.ok) {
+    throw new Error(res.error);
+  }
+  return res;
+};
+
+type PageInfo= {
+  path: string,
+  pageId: Nullable<string>,
+  revisionId: Nullable<string>,
+}
+
+// TODO: define return type
+export const saveOrUpdate = async(optionsToSave: OptionsToSave, pageInfo: PageInfo, markdown: string) => {
+  const { path, pageId, revisionId } = pageInfo;
+
+  const options = Object.assign({}, optionsToSave);
+
+  /*
+  * Note: variable "markdown" will be received from params
+  * please delete the following code after implemating HackMD editor function
+  */
+  // let markdown;
+  // if (editorMode === EditorMode.HackMD) {
+  // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
+  // markdown = await pageEditorByHackmd.getMarkdown();
+  // // set option to sync
+  // options.isSyncRevisionToHackmd = true;
+  // revisionId = this.state.revisionIdHackmdSynced;
+  // }
+  // else {
+  // const pageEditor = this.appContainer.getComponentInstance('PageEditor');
+  // const pageEditor = getComponentInstance('PageEditor');
+  // markdown = pageEditor.getMarkdown();
+  // }
+
+  let res;
+  if (pageId == null) {
+    res = await createPage(path, markdown, options);
+  }
+  else {
+    if (revisionId == null) {
+      const msg = '\'revisionId\' is required to update page';
+      throw new Error(msg);
+    }
+    res = await updatePage(pageId, revisionId, markdown, options);
+  }
+
+  return res;
+};

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

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

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

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

+ 0 - 28
packages/app/src/client/util/blink-section-header.ts

@@ -1,28 +0,0 @@
-let lastBlinkedElem;
-
-export const blinkElem = (elem: HTMLElement): void => {
-  if (lastBlinkedElem != null) {
-    lastBlinkedElem.classList.remove('blink');
-  }
-
-  elem.classList.add('blink');
-  lastBlinkedElem = elem;
-};
-
-export const blinkSectionHeaderAtBoot = (): HTMLElement | undefined => {
-  const { hash } = window.location;
-
-  if (hash.length === 0) {
-    return;
-  }
-
-  // omit '#'
-  const id = hash.replace('#', '');
-  // don't use jQuery and document.querySelector
-  //  because hash may containe Base64 encoded strings
-  const elem = document.getElementById(id);
-  if (elem != null && elem.tagName.match(/h\d+/i)) { // match h1, h2, h3...
-    blinkElem(elem);
-    return elem;
-  }
-};

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

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

+ 3 - 8
packages/app/src/client/util/editor.ts

@@ -1,11 +1,4 @@
-type OptionsToSave = {
-  isSlackEnabled: boolean;
-  slackChannels: string;
-  grant: number;
-  pageTags: string[] | null;
-  grantUserGroupId?: string | null;
-  grantUserGroupName?: string | null;
-};
+import { OptionsToSave } from '~/interfaces/editor-settings';
 
 export const getOptionsToSave = (
     isSlackEnabled: boolean,
@@ -14,6 +7,7 @@ export const getOptionsToSave = (
     grantUserGroupId: string | null | undefined,
     grantUserGroupName: string | null | undefined,
     pageTags: string[],
+    isSyncRevisionToHackmd?: boolean,
 ): OptionsToSave => {
   return {
     pageTags,
@@ -22,5 +16,6 @@ export const getOptionsToSave = (
     grant,
     grantUserGroupId,
     grantUserGroupName,
+    isSyncRevisionToHackmd,
   };
 };

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

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

+ 4 - 3
packages/app/src/client/util/smooth-scroll.ts

@@ -1,10 +1,11 @@
 const WIKI_HEADER_LINK = 120;
 
-export const smoothScrollIntoView = (element: HTMLElement, offsetTop = 0, scrollElement: HTMLElement | Window = window): void => {
-  const targetElement = element || window.document.body;
+export const smoothScrollIntoView = (
+    element: HTMLElement = window.document.body, offsetTop = 0, scrollElement: HTMLElement | Window = window,
+): void => {
 
   // get the distance to the target element top
-  const rectTop = targetElement.getBoundingClientRect().top;
+  const rectTop = element.getBoundingClientRect().top;
 
   const top = window.pageYOffset + rectTop - offsetTop;
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно