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

Merge branch 'master' into feat/new-plugin

Yuki Takei 3 лет назад
Родитель
Сommit
3d1c472a85
100 измененных файлов с 979 добавлено и 1332 удалено
  1. 4 2
      .github/dependabot.yml
  2. 11 6
      .github/workflows/ci-app-prod.yml
  3. 3 4
      .github/workflows/ci-app.yml
  4. 2 2
      .github/workflows/draft-release.yml
  5. 4 4
      .github/workflows/list-unhealthy-branches.yml
  6. 1 1
      .github/workflows/release-rc.yml
  7. 2 2
      .github/workflows/release-slackbot-proxy.yml
  8. 3 3
      .github/workflows/release.yml
  9. 15 7
      .github/workflows/reusable-app-prod.yml
  10. 2 6
      .vscode/launch.json
  11. 14 1
      CHANGELOG.md
  12. 33 11
      THIRD-PARTY-NOTICES.md
  13. 1 1
      lerna.json
  14. 1 1
      package.json
  15. 0 0
      packages-obsolete/plugin-attachment-refs/.eslintignore
  16. 0 0
      packages-obsolete/plugin-attachment-refs/.gitignore
  17. 0 0
      packages-obsolete/plugin-attachment-refs/README.md
  18. 1 1
      packages-obsolete/plugin-attachment-refs/package.json
  19. 0 0
      packages-obsolete/plugin-attachment-refs/src/client-entry.js
  20. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/css/index.css
  21. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/components/AttachmentList.jsx
  22. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/components/ExtractedAttachments.jsx
  23. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/util/GalleryContext.js
  24. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPostRenderInterceptor.js
  25. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPreRenderInterceptor.js
  26. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/util/RefsContext.js
  27. 0 0
      packages-obsolete/plugin-attachment-refs/src/client/js/util/TagCacheManagerFactory.js
  28. 0 0
      packages-obsolete/plugin-attachment-refs/src/index.js
  29. 0 0
      packages-obsolete/plugin-attachment-refs/src/server-entry.js
  30. 0 0
      packages-obsolete/plugin-attachment-refs/src/server/routes/index.js
  31. 0 0
      packages-obsolete/plugin-attachment-refs/src/server/routes/refs.js
  32. 0 0
      packages-obsolete/plugin-attachment-refs/src/utils/logger/index.ts
  33. 0 0
      packages-obsolete/plugin-attachment-refs/tsconfig.base.json
  34. 0 0
      packages-obsolete/plugin-attachment-refs/tsconfig.build.cjs.json
  35. 0 0
      packages-obsolete/plugin-attachment-refs/tsconfig.build.esm.json
  36. 0 0
      packages-obsolete/plugin-attachment-refs/tsconfig.json
  37. 0 173
      packages/app/_obsolete/src/client/admin.jsx
  38. 0 0
      packages/app/_obsolete/src/client/services/AppContainer.js
  39. 0 5
      packages/app/_obsolete/src/client/services/ContextExtractor.tsx
  40. 25 40
      packages/app/_obsolete/src/client/services/PageContainer.js
  41. 0 0
      packages/app/_obsolete/src/components/MyDraftList/Draft.tsx
  42. 0 0
      packages/app/_obsolete/src/components/MyDraftList/MyDraftList.jsx
  43. 2 1
      packages/app/cypress.json
  44. 3 2
      packages/app/docker/Dockerfile
  45. 2 2
      packages/app/docker/README.md
  46. 3 2
      packages/app/next.config.js
  47. 22 18
      packages/app/package.json
  48. BIN
      packages/app/public/static/fonts/Lato-Bold-latin-ext.woff2
  49. BIN
      packages/app/public/static/fonts/Lato-Bold-latin.woff2
  50. BIN
      packages/app/public/static/fonts/Lato-Regular-latin-ext.woff2
  51. BIN
      packages/app/public/static/fonts/Lato-Regular-latin.woff2
  52. BIN
      packages/app/public/static/fonts/PressStart2P-latin-ext.woff2
  53. BIN
      packages/app/public/static/fonts/PressStart2P-latin.woff2
  54. 35 15
      packages/app/public/static/locales/en_US/admin.json
  55. 100 0
      packages/app/public/static/locales/en_US/commons.json
  56. 15 82
      packages/app/public/static/locales/en_US/translation.json
  57. 28 14
      packages/app/public/static/locales/ja_JP/admin.json
  58. 100 0
      packages/app/public/static/locales/ja_JP/commons.json
  59. 16 77
      packages/app/public/static/locales/ja_JP/translation.json
  60. 77 14
      packages/app/public/static/locales/zh_CN/admin.json
  61. 100 0
      packages/app/public/static/locales/zh_CN/commons.json
  62. 16 126
      packages/app/public/static/locales/zh_CN/translation.json
  63. 1 1
      packages/app/resource/locales/en_US/sandbox-diagrams.md
  64. 2 2
      packages/app/resource/locales/en_US/welcome.md
  65. 1 1
      packages/app/resource/locales/ja_JP/sandbox-diagrams.md
  66. 2 2
      packages/app/resource/locales/ja_JP/welcome.md
  67. 1 1
      packages/app/resource/locales/zh_CN/sandbox-diagrams.md
  68. 2 2
      packages/app/resource/locales/zh_CN/welcome.md
  69. 27 0
      packages/app/src/client/interfaces/global-notification.ts
  70. 8 0
      packages/app/src/client/interfaces/notification.ts
  71. 0 84
      packages/app/src/client/services/AdminCustomizeContainer.js
  72. 12 2
      packages/app/src/client/services/activate-plugin.ts
  73. 50 50
      packages/app/src/client/services/page-operation.ts
  74. 0 21
      packages/app/src/client/util/editor.ts
  75. 4 45
      packages/app/src/client/util/smooth-scroll.ts
  76. 4 4
      packages/app/src/components/Admin/App/AppSetting.jsx
  77. 2 2
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  78. 2 2
      packages/app/src/components/Admin/App/FileUploadSetting.tsx
  79. 2 2
      packages/app/src/components/Admin/App/MailSetting.tsx
  80. 1 1
      packages/app/src/components/Admin/App/PluginSetting.tsx
  81. 6 6
      packages/app/src/components/Admin/App/SiteUrlSetting.tsx
  82. 1 1
      packages/app/src/components/Admin/AuditLog/ActivityTable.tsx
  83. 26 28
      packages/app/src/components/Admin/Common/AdminNavigation.jsx
  84. 1 5
      packages/app/src/components/Admin/Customize/Customize.jsx
  85. 5 5
      packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  86. 19 34
      packages/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  87. 5 5
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  88. 0 148
      packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx
  89. 32 22
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  90. 10 12
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  91. 6 6
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  92. 3 2
      packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  93. 7 5
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  94. 0 96
      packages/app/src/components/Admin/Customize/CustomizeTitle.jsx
  95. 79 0
      packages/app/src/components/Admin/Customize/CustomizeTitle.tsx
  96. 1 1
      packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx
  97. 2 4
      packages/app/src/components/Admin/FullTextSearchManagement.tsx
  98. 11 12
      packages/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  99. 3 4
      packages/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx
  100. 0 91
      packages/app/src/components/Admin/ManageExternalAccount.jsx

+ 4 - 2
.github/dependabot.yml

@@ -2,16 +2,18 @@ version: 2
 updates:
 updates:
   - package-ecosystem: github-actions
   - package-ecosystem: github-actions
     directory: '/'
     directory: '/'
+    open-pull-requests-limit: 0
     schedule:
     schedule:
-      interval: daily
+      interval: monthly
     commit-message:
     commit-message:
       prefix: ci
       prefix: ci
       include: scope
       include: scope
 
 
   - package-ecosystem: npm
   - package-ecosystem: npm
     directory: '/'
     directory: '/'
+    open-pull-requests-limit: 0
     schedule:
     schedule:
-      interval: daily
+      interval: weekly
     commit-message:
     commit-message:
       prefix: ci
       prefix: ci
       include: scope
       include: scope

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

@@ -14,10 +14,9 @@ on:
       - '!packages/app/docker/**'
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/codemirror-textlint/**
       - packages/core/**
       - packages/core/**
-      - packages/remark-growi-plugin/**
+      - packages/remark-*/**
       - packages/slack/**
       - packages/slack/**
       - packages/ui/**
       - packages/ui/**
-      - packages/plugin-**
   pull_request:
   pull_request:
     branches:
     branches:
       - master
       - master
@@ -32,10 +31,16 @@ on:
       - '!packages/app/docker/**'
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/codemirror-textlint/**
       - packages/core/**
       - packages/core/**
-      - packages/remark-growi-plugin/**
+      - packages/remark-*/**
       - packages/slack/**
       - packages/slack/**
       - packages/ui/**
       - packages/ui/**
-      - packages/plugin-**
+  workflow_call:
+    inputs:
+      cypress-config-video:
+        description: 'Enable video when running Cypress test'
+        type: boolean
+        default: false
+
 
 
 jobs:
 jobs:
 
 
@@ -52,9 +57,9 @@ jobs:
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     uses: weseek/growi/.github/workflows/reusable-app-prod.yml@master
     with:
     with:
       node-version: 16.x
       node-version: 16.x
-      # skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
-      skip-cypress: true
+      skip-cypress: ${{ contains( github.event.pull_request.labels.*.name, 'dependencies' ) && contains( github.event.pull_request.labels.*.name, 'github_actions' ) }}
       cypress-report-artifact-name: Cypress report
       cypress-report-artifact-name: Cypress report
+      cypress-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 
 

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

@@ -16,10 +16,9 @@ on:
       - '!packages/app/docker/**'
       - '!packages/app/docker/**'
       - packages/codemirror-textlint/**
       - packages/codemirror-textlint/**
       - packages/core/**
       - packages/core/**
-      - packages/remark-growi-plugin/**
+      - packages/remark-*/**
       - packages/slack/**
       - packages/slack/**
       - packages/ui/**
       - packages/ui/**
-      - packages/plugin-*/**
 
 
 jobs:
 jobs:
   lint:
   lint:
@@ -55,7 +54,7 @@ jobs:
 
 
       - name: lerna run lint for plugins
       - name: lerna run lint for plugins
         run: |
         run: |
-          yarn lerna run lint --scope @growi/remark-growi-plugin --scope @growi/plugin-*
+          yarn lerna run lint --scope @growi/remark-*
       - name: lerna run lint for app
       - name: lerna run lint for app
         run: |
         run: |
           yarn lerna run lint --scope @growi/app --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/slack --scope @growi/ui
           yarn lerna run lint --scope @growi/app --scope @growi/codemirror-textlint --scope @growi/core --scope @growi/slack --scope @growi/ui
@@ -109,7 +108,7 @@ jobs:
 
 
       - name: lerna run test for plugins
       - name: lerna run test for plugins
         run: |
         run: |
-          yarn lerna run test --scope @growi/remark-growi-plugin --scope @growi/plugin-*
+          yarn lerna run test --scope @growi/remark-*
 
 
       - name: Test app
       - name: Test app
         working-directory: ./packages/app
         working-directory: ./packages/app

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

@@ -19,7 +19,7 @@ jobs:
       - uses: actions/checkout@v3
       - uses: actions/checkout@v3
 
 
       - name: Retrieve information from package.json
       - name: Retrieve information from package.json
-        uses: myrotvorets/info-from-package-json-action@1.1.0
+        uses: myrotvorets/info-from-package-json-action@1.2.0
         id: package-json
         id: package-json
 
 
       # Drafts your next Release notes as Pull Requests are merged into "master"
       # Drafts your next Release notes as Pull Requests are merged into "master"
@@ -48,7 +48,7 @@ jobs:
         id: release-version
         id: release-version
         run: |
         run: |
           RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
           RELEASE_VERSION=`npx semver -i patch ${{ needs.update-release-draft.outputs.CURRENT_VERSION }}`
-          echo ::set-output name=RELEASE_VERSION::$RELEASE_VERSION
+          echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_OUTPUT
 
 
       # See: https://github.com/bakunyo/git-pr-release-action/issues/15, https://github.com/samunohito/SimpleVolumeMixer/commit/2059044c71236509466cf9b1bb2d56d515274938
       # See: https://github.com/bakunyo/git-pr-release-action/issues/15, https://github.com/samunohito/SimpleVolumeMixer/commit/2059044c71236509466cf9b1bb2d56d515274938
       - name: Create/Update Pull Request
       - name: Create/Update Pull Request

+ 4 - 4
.github/workflows/list-unhealthy-branches.yml

@@ -23,10 +23,10 @@ jobs:
       run: |
       run: |
         export SLACK_ATTACHMENTS_ILLEGAL=`node bin/github-actions/list-branches --illegal`
         export SLACK_ATTACHMENTS_ILLEGAL=`node bin/github-actions/list-branches --illegal`
         export SLACK_ATTACHMENTS_INACTIVE=`node bin/github-actions/list-branches --inactive`
         export SLACK_ATTACHMENTS_INACTIVE=`node bin/github-actions/list-branches --inactive`
-        echo ::set-output name=SLACK_ATTACHMENTS_ILLEGAL::$SLACK_ATTACHMENTS_ILLEGAL
-        echo ::set-output name=SLACK_ATTACHMENTS_INACTIVE::$SLACK_ATTACHMENTS_INACTIVE
-        echo ::set-output name=SLACK_ATTACHMENTS_LENGTH_ILLEGAL::$(echo $SLACK_ATTACHMENTS_ILLEGAL | jq '. | length')
-        echo ::set-output name=SLACK_ATTACHMENTS_LENGTH_INACTIVE::$(echo $SLACK_ATTACHMENTS_INACTIVE | jq '. | length')
+        echo "SLACK_ATTACHMENTS_ILLEGAL=$SLACK_ATTACHMENTS_ILLEGAL" >> $GITHUB_OUTPUT
+        echo "SLACK_ATTACHMENTS_INACTIVE=$SLACK_ATTACHMENTS_INACTIVE" >> $GITHUB_OUTPUT
+        echo "SLACK_ATTACHMENTS_LENGTH_ILLEGAL=$(echo $SLACK_ATTACHMENTS_ILLEGAL | jq '. | length')" >> $GITHUB_OUTPUT
+        echo "SLACK_ATTACHMENTS_LENGTH_INACTIVE=$(echo $SLACK_ATTACHMENTS_INACTIVE | jq '. | length')" >> $GITHUB_OUTPUT
 
 
     - name: Slack Notification for illegal named branches
     - name: Slack Notification for illegal named branches
       if: steps.list-branches.outputs.SLACK_ATTACHMENTS_LENGTH_ILLEGAL > 0
       if: steps.list-branches.outputs.SLACK_ATTACHMENTS_LENGTH_ILLEGAL > 0

+ 1 - 1
.github/workflows/release-rc.yml

@@ -17,7 +17,7 @@ jobs:
         lfs: true
         lfs: true
 
 
     - name: Retrieve information from package.json
     - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.1.0
+      uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
       id: package-json
 
 
     - name: Docker meta
     - name: Docker meta

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

@@ -17,7 +17,7 @@ jobs:
         ref: ${{ github.event.pull_request.base.ref }}
         ref: ${{ github.event.pull_request.base.ref }}
 
 
     - name: Retrieve information from package.json
     - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.1.0
+      uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
       id: package-json
       with:
       with:
         workingDir: packages/slackbot-proxy
         workingDir: packages/slackbot-proxy
@@ -115,7 +115,7 @@ jobs:
         yarn bump-versions:slackbot-proxy
         yarn bump-versions:slackbot-proxy
 
 
     - name: Retrieve information from package.json
     - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.1.0
+      uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
       id: package-json
       with:
       with:
         workingDir: packages/slackbot-proxy
         workingDir: packages/slackbot-proxy

+ 3 - 3
.github/workflows/release.yml

@@ -38,7 +38,7 @@ jobs:
         sh ./packages/app/bin/github-actions/update-readme.sh
         sh ./packages/app/bin/github-actions/update-readme.sh
 
 
     - name: Retrieve information from package.json
     - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.1.0
+      uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
       id: package-json
 
 
     - name: Update Changelog
     - name: Update Changelog
@@ -99,7 +99,7 @@ jobs:
         yarn bump-versions:slackbot-proxy
         yarn bump-versions:slackbot-proxy
 
 
     - name: Retrieve information from package.json
     - name: Retrieve information from package.json
-      uses: myrotvorets/info-from-package-json-action@1.1.0
+      uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
       id: package-json
 
 
     - name: Commit
     - name: Commit
@@ -140,7 +140,7 @@ jobs:
       id: suffix
       id: suffix
       run: |
       run: |
         [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
         [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo "::set-output name=SUFFIX::$suffix"
+        echo "SUFFIX=$suffix" >> $GITHUB_OUTPUT
 
 
     - name: Docker meta
     - name: Docker meta
       id: meta
       id: meta

+ 15 - 7
.github/workflows/reusable-app-prod.yml

@@ -10,6 +10,9 @@ on:
         type: boolean
         type: boolean
       cypress-report-artifact-name:
       cypress-report-artifact-name:
         type: string
         type: string
+      cypress-config-video:
+        type: boolean
+        default: false
     secrets:
     secrets:
       SLACK_WEBHOOK_URL:
       SLACK_WEBHOOK_URL:
         required: true
         required: true
@@ -70,7 +73,7 @@ jobs:
           packages/app/.env.production* \
           packages/app/.env.production* \
           packages/*/package.json \
           packages/*/package.json \
           packages/*/dist
           packages/*/dist
-        echo ::set-output name=file::production.tar.gz
+        echo "file=production.tar.gz" >> $GITHUB_OUTPUT
 
 
     - name: Upload production files as artifact
     - name: Upload production files as artifact
       uses: actions/upload-artifact@v3
       uses: actions/upload-artifact@v3
@@ -126,8 +129,8 @@ jobs:
     - name: Get Date
     - name: Get Date
       id: get-date
       id: get-date
       run: |
       run: |
-        echo "::set-output name=dateYmdHM::$(/bin/date -u "+%Y%m%d%H%M")"
-        echo "::set-output name=dateYm::$(/bin/date -u "+%Y%m")"
+        echo "dateYmdHM=$(/bin/date -u "+%Y%m%d%H%M")" >> $GITHUB_OUTPUT
+        echo "dateYm=$(/bin/date -u "+%Y%m")" >> $GITHUB_OUTPUT
 
 
     - name: Cache/Restore node_modules (not reused)
     - name: Cache/Restore node_modules (not reused)
       id: cache-dependencies
       id: cache-dependencies
@@ -215,7 +218,7 @@ jobs:
 
 
     - uses: actions/setup-node@v3
     - uses: actions/setup-node@v3
       with:
       with:
-        node-version: ${{ matrix.node-version }}
+        node-version: ${{ inputs.node-version }}
         cache: 'yarn'
         cache: 'yarn'
         cache-dependency-path: '**/yarn.lock'
         cache-dependency-path: '**/yarn.lock'
 
 
@@ -232,12 +235,16 @@ jobs:
 
 
     - name: lerna bootstrap
     - name: lerna bootstrap
       run: |
       run: |
-        npx lerna bootstrap -- --frozen-lockfile
+        npx lerna bootstrap -- --production
+
+    - name: lerna add packages needed for CI
+      run: |
+        npx lerna add yargs
 
 
     - name: Download production files artifact
     - name: Download production files artifact
       uses: actions/download-artifact@v3
       uses: actions/download-artifact@v3
       with:
       with:
-        name: Production Files
+        name: Production Files (node${{ inputs.node-version }})
 
 
     - name: Extract procution files artifact
     - name: Extract procution files artifact
       run: |
       run: |
@@ -247,7 +254,7 @@ jobs:
       id: determine-spec-exp
       id: determine-spec-exp
       run: |
       run: |
         SPEC=`node bin/github-actions/generate-cypress-spec-arg.js --prefix="test/cypress/integration/" --suffix="-*/**" "${{ matrix.spec-group }}"`
         SPEC=`node bin/github-actions/generate-cypress-spec-arg.js --prefix="test/cypress/integration/" --suffix="-*/**" "${{ matrix.spec-group }}"`
-        echo "::set-output name=value::$SPEC"
+        echo "value=$SPEC" >> $GITHUB_OUTPUT
 
 
     - name: Copy dotenv file for ci
     - name: Copy dotenv file for ci
       working-directory: ./packages/app
       working-directory: ./packages/app
@@ -274,6 +281,7 @@ jobs:
         spec: '${{ steps.determine-spec-exp.outputs.value }}'
         spec: '${{ steps.determine-spec-exp.outputs.value }}'
         start: yarn server
         start: yarn server
         wait-on: 'http://localhost:3000'
         wait-on: 'http://localhost:3000'
+        config: video=${{ inputs.cypress-config-video }}
       env:
       env:
         MONGO_URI: mongodb://localhost:${{ job.services.mongodb.ports['27017'] }}/growi-vrt
         MONGO_URI: mongodb://localhost:${{ job.services.mongodb.ports['27017'] }}/growi-vrt
         ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi
         ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi

+ 2 - 6
.vscode/launch.json

@@ -73,12 +73,8 @@
             "path": "${workspaceFolder}/packages/core"
             "path": "${workspaceFolder}/packages/core"
           },
           },
           {
           {
-            "url": "webpack://_n_e/plugin-attachment-refs",
-            "path": "${workspaceFolder}/packages/plugin-attachment-refs"
-          },
-          {
-            "url": "webpack://_n_e/plugin-lsx",
-            "path": "${workspaceFolder}/packages/plugin-lsx"
+            "url": "webpack://_n_e/remark-lsx",
+            "path": "${workspaceFolder}/packages/remark-lsx"
           },
           },
           {
           {
             "url": "webpack://_n_e/slack",
             "url": "webpack://_n_e/slack",

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
 # Changelog
 # Changelog
 
 
-## [Unreleased](https://github.com/weseek/growi/compare/v5.1.5...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v5.1.7...HEAD)
 
 
 *Please do not manually update this file. We've automated the process.*
 *Please do not manually update this file. We've automated the process.*
 
 
+## [v5.1.7](https://github.com/weseek/growi/compare/v5.1.6...v5.1.7) - 2022-10-26
+
+### 🐛 Bug Fixes
+
+- fix: Page move event notification message (#6823) @hakumizuki
+
+## [v5.1.6](https://github.com/weseek/growi/compare/v5.1.5...v5.1.6) - 2022-10-19
+
+### 🐛 Bug Fixes
+
+- fix: image not showing and exceed crop modal area (#6712) @mudana-grune
+- fix: Conflict Diff Modal Error getCurrentOptionsToSave is not a function (#6745) @kaoritokashiki
+
 ## [v5.1.5](https://github.com/weseek/growi/compare/v5.1.4...v5.1.5) - 2022-10-04
 ## [v5.1.5](https://github.com/weseek/growi/compare/v5.1.4...v5.1.5) - 2022-10-04
 
 
 ### 💎 Features
 ### 💎 Features

+ 33 - 11
THIRD-PARTY-NOTICES.md

@@ -13,10 +13,12 @@ https://github.com/weseek/growi.
 
 
 
 
 1. Apache License, Version 2.0 Derivative Works
 1. Apache License, Version 2.0 Derivative Works
-2. crowi/crowi (https://github.com/crowi/crowi)
-3. Microsoft/vscode (https://github.com/Microsoft/vscode)
-4. stephenhutchings/typicons.font (https://github.com/stephenhutchings/typicons.font)
-5. Kuromoji.js (https://github.com/takuyaa/kuromoji.js)
+1. crowi/crowi (https://github.com/crowi/crowi)
+1. Microsoft/vscode (https://github.com/Microsoft/vscode)
+1. Kuromoji.js (https://github.com/takuyaa/kuromoji.js)
+1. Lato (https://fonts.google.com/specimen/Lato)
+1. Press Start 2P (https://fonts.google.com/specimen/Press+Start+2P)
+1. stephenhutchings/typicons.font (https://github.com/stephenhutchings/typicons.font)
 
 
 
 
 License Notice for Apache License, Version 2.0 Derivative Works
 License Notice for Apache License, Version 2.0 Derivative Works
@@ -90,21 +92,41 @@ SOFTWARE.
 ```
 ```
 
 
 
 
-License Notice for Typicons
+License Notice for Kuromoji.js
 ------------------------
 ------------------------
 
 
-https://creativecommons.org/licenses/by-sa/3.0/
+https://github.com/takuyaa/kuromoji.js/blob/master/LICENSE-2.0.txt
 
 
 ```
 ```
-Copyright (c) 2018 Stephen Hutchings
+author: "Takuya Asano <takuya.a@gmail.com>"
 ```
 ```
 
 
 
 
-License Notice for Kuromoji.js
-------------------------
+License Notice for Lato
+---------------------
 
 
-https://github.com/takuyaa/kuromoji.js/blob/master/LICENSE-2.0.txt
+https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
 
 
 ```
 ```
-author: "Takuya Asano <takuya.a@gmail.com>"
+Designed by Łukasz Dziedzic 
 ```
 ```
+
+
+License Notice for Press Start 2P
+------------------------------
+
+https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL
+
+```
+Designed by CodeMan38
+```
+
+
+License Notice for Typicons
+------------------------
+
+https://creativecommons.org/licenses/by-sa/3.0/
+
+```
+Copyright (c) 2018 Stephen Hutchings
+```

+ 1 - 1
lerna.json

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

+ 1 - 1
package.json

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

+ 0 - 0
packages/plugin-attachment-refs/.eslintignore → packages-obsolete/plugin-attachment-refs/.eslintignore


+ 0 - 0
packages/plugin-attachment-refs/.gitignore → packages-obsolete/plugin-attachment-refs/.gitignore


+ 0 - 0
packages/plugin-attachment-refs/README.md → packages-obsolete/plugin-attachment-refs/README.md


+ 1 - 1
packages/plugin-attachment-refs/package.json → packages-obsolete/plugin-attachment-refs/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/plugin-attachment-refs",
   "name": "@growi/plugin-attachment-refs",
-  "version": "6.0.0-RC.1",
+  "version": "6.0.0-RC.9",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "description": "GROWI Plugin to add ref/refimg/refs/refsimg tags",
   "license": "MIT",
   "license": "MIT",
   "keywords": [
   "keywords": [

+ 0 - 0
packages/plugin-attachment-refs/src/client-entry.js → packages-obsolete/plugin-attachment-refs/src/client-entry.js


+ 0 - 0
packages/plugin-attachment-refs/src/client/css/index.css → packages-obsolete/plugin-attachment-refs/src/client/css/index.css


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/components/AttachmentList.jsx → packages-obsolete/plugin-attachment-refs/src/client/js/components/AttachmentList.jsx


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/components/ExtractedAttachments.jsx → packages-obsolete/plugin-attachment-refs/src/client/js/components/ExtractedAttachments.jsx


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/util/GalleryContext.js → packages-obsolete/plugin-attachment-refs/src/client/js/util/GalleryContext.js


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPostRenderInterceptor.js → packages-obsolete/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPostRenderInterceptor.js


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPreRenderInterceptor.js → packages-obsolete/plugin-attachment-refs/src/client/js/util/Interceptor/RefsPreRenderInterceptor.js


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/util/RefsContext.js → packages-obsolete/plugin-attachment-refs/src/client/js/util/RefsContext.js


+ 0 - 0
packages/plugin-attachment-refs/src/client/js/util/TagCacheManagerFactory.js → packages-obsolete/plugin-attachment-refs/src/client/js/util/TagCacheManagerFactory.js


+ 0 - 0
packages/plugin-attachment-refs/src/index.js → packages-obsolete/plugin-attachment-refs/src/index.js


+ 0 - 0
packages/plugin-attachment-refs/src/server-entry.js → packages-obsolete/plugin-attachment-refs/src/server-entry.js


+ 0 - 0
packages/plugin-attachment-refs/src/server/routes/index.js → packages-obsolete/plugin-attachment-refs/src/server/routes/index.js


+ 0 - 0
packages/plugin-attachment-refs/src/server/routes/refs.js → packages-obsolete/plugin-attachment-refs/src/server/routes/refs.js


+ 0 - 0
packages/plugin-attachment-refs/src/utils/logger/index.ts → packages-obsolete/plugin-attachment-refs/src/utils/logger/index.ts


+ 0 - 0
packages/plugin-attachment-refs/tsconfig.base.json → packages-obsolete/plugin-attachment-refs/tsconfig.base.json


+ 0 - 0
packages/plugin-attachment-refs/tsconfig.build.cjs.json → packages-obsolete/plugin-attachment-refs/tsconfig.build.cjs.json


+ 0 - 0
packages/plugin-attachment-refs/tsconfig.build.esm.json → packages-obsolete/plugin-attachment-refs/tsconfig.build.esm.json


+ 0 - 0
packages/plugin-attachment-refs/tsconfig.json → packages-obsolete/plugin-attachment-refs/tsconfig.json


+ 0 - 173
packages/app/_obsolete/src/client/admin.jsx

@@ -1,173 +0,0 @@
-import React from 'react';
-
-import ReactDOM from 'react-dom';
-import { I18nextProvider } from 'react-i18next';
-import { SWRConfig } from 'swr';
-import { Provider } from 'unstated';
-
-import AdminAppContainer from '~/client/services/AdminAppContainer';
-import AdminBasicSecurityContainer from '~/client/services/AdminBasicSecurityContainer';
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer';
-import AdminGeneralSecurityContainer from '~/client/services/AdminGeneralSecurityContainer';
-import AdminGitHubSecurityContainer from '~/client/services/AdminGitHubSecurityContainer';
-import AdminGoogleSecurityContainer from '~/client/services/AdminGoogleSecurityContainer';
-import AdminHomeContainer from '~/client/services/AdminHomeContainer';
-import AdminImportContainer from '~/client/services/AdminImportContainer';
-import AdminLdapSecurityContainer from '~/client/services/AdminLdapSecurityContainer';
-import AdminLocalSecurityContainer from '~/client/services/AdminLocalSecurityContainer';
-import AdminMarkDownContainer from '~/client/services/AdminMarkDownContainer';
-import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
-import AdminOidcSecurityContainer from '~/client/services/AdminOidcSecurityContainer';
-import AdminSamlSecurityContainer from '~/client/services/AdminSamlSecurityContainer';
-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 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 { 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 LegacySlackIntegration from '../components/Admin/LegacySlackIntegration/LegacySlackIntegration';
-import ManageExternalAccount from '../components/Admin/ManageExternalAccount';
-// 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';
-import SlackIntegration from '../components/Admin/SlackIntegration/SlackIntegration';
-import UserGroupPage from '../components/Admin/UserGroup/UserGroupPage';
-import UserGroupDetailPage from '../components/Admin/UserGroupDetail/UserGroupDetailPage';
-import UserManagement from '../components/Admin/UserManagement';
-import ErrorBoundary from '../components/ErrorBoudary';
-
-import { appContainer, componentMappings } from './base';
-
-const logger = loggerFactory('growi:admin');
-
-appContainer.initContents();
-
-const { i18n } = appContainer;
-// create unstated container instance
-const adminAppContainer = new AdminAppContainer(appContainer);
-const adminImportContainer = new AdminImportContainer(appContainer);
-const adminSocketIoContainer = new AdminSocketIoContainer(appContainer);
-const adminHomeContainer = new AdminHomeContainer(appContainer);
-const adminCustomizeContainer = new AdminCustomizeContainer(appContainer);
-const adminUsersContainer = new AdminUsersContainer(appContainer);
-const adminExternalAccountsContainer = new AdminExternalAccountsContainer(appContainer);
-const adminNotificationContainer = new AdminNotificationContainer(appContainer);
-const adminSlackIntegrationLegacyContainer = new AdminSlackIntegrationLegacyContainer(appContainer);
-const adminMarkDownContainer = new AdminMarkDownContainer(appContainer);
-// const adminUserGroupDetailContainer = new AdminUserGroupDetailContainer(appContainer);
-const socketIoContainer = appContainer.getContainer('SocketIoContainer');
-const injectableContainers = [
-  appContainer,
-  adminAppContainer,
-  adminImportContainer,
-  adminSocketIoContainer,
-  adminHomeContainer,
-  adminCustomizeContainer,
-  adminUsersContainer,
-  adminExternalAccountsContainer,
-  adminNotificationContainer,
-  adminSlackIntegrationLegacyContainer,
-  adminMarkDownContainer,
-  // adminUserGroupDetailContainer,
-  socketIoContainer,
-];
-
-logger.info('unstated containers have been initialized');
-
-/**
- * define components
- *  key: id of element
- *  value: React Element
- */
-Object.assign(componentMappings, {
-  'admin-home': <AdminHome />,
-  // 'admin-app': <AppSettingsPage />,
-  // 'admin-markdown-setting': <MarkdownSetting />,
-  'admin-customize': <Customize />,
-  // 'admin-importer': <ImportDataPage />,
-  'admin-export-page': <ExportArchiveDataPage />,
-  'admin-notification-setting': <NotificationSetting />,
-  'admin-slack-integration': <SlackIntegration />,
-  'admin-slack-integration-legacy': <LegacySlackIntegration />,
-  'admin-global-notification-setting': <ManageGlobalNotification />,
-  'admin-user-page': <UserManagement />,
-  'admin-external-account-setting': <ManageExternalAccount />,
-  'admin-user-group-detail': <UserGroupDetailPage />,
-  'admin-full-text-search-management': <FullTextSearchManagement />,
-  'admin-user-group-page': <UserGroupPage />,
-  'admin-audit-log': <AuditLogManagement />,
-  'admin-navigation': <AdminNavigation />,
-});
-
-const renderMainComponents = () => {
-  Object.keys(componentMappings).forEach((key) => {
-    const elem = document.getElementById(key);
-    if (elem) {
-      ReactDOM.render(
-        <I18nextProvider i18n={i18n}>
-          <ErrorBoundary>
-            <Provider inject={injectableContainers}>
-              {componentMappings[key]}
-            </Provider>
-          </ErrorBoundary>
-        </I18nextProvider>,
-        elem,
-      );
-    }
-  });
-};
-
-// extract context before rendering main components
-const elem = document.getElementById('growi-context-extractor');
-if (elem != null) {
-  ReactDOM.render(
-    <SWRConfig value={swrGlobalConfiguration}>
-      <ContextExtractor></ContextExtractor>
-    </SWRConfig>,
-    elem,
-    renderMainComponents,
-  );
-}
-else {
-  renderMainComponents();
-}
-
-const adminSecuritySettingElem = document.getElementById('admin-security-setting');
-if (adminSecuritySettingElem != null) {
-  const adminGeneralSecurityContainer = new AdminGeneralSecurityContainer(appContainer);
-  const adminLocalSecurityContainer = new AdminLocalSecurityContainer(appContainer);
-  const adminLdapSecurityContainer = new AdminLdapSecurityContainer(appContainer);
-  const adminSamlSecurityContainer = new AdminSamlSecurityContainer(appContainer);
-  const adminOidcSecurityContainer = new AdminOidcSecurityContainer(appContainer);
-  const adminBasicSecurityContainer = new AdminBasicSecurityContainer(appContainer);
-  const adminGoogleSecurityContainer = new AdminGoogleSecurityContainer(appContainer);
-  const adminGitHubSecurityContainer = new AdminGitHubSecurityContainer(appContainer);
-  const adminTwitterSecurityContainer = new AdminTwitterSecurityContainer(appContainer);
-  const adminSecurityContainers = [
-    adminGeneralSecurityContainer, adminLocalSecurityContainer, adminLdapSecurityContainer, adminSamlSecurityContainer,
-    adminOidcSecurityContainer, adminBasicSecurityContainer, adminGoogleSecurityContainer, adminGitHubSecurityContainer, adminTwitterSecurityContainer,
-  ];
-  ReactDOM.render(
-    <I18nextProvider i18n={i18n}>
-      <ErrorBoundary>
-        <Provider inject={[...injectableContainers, ...adminSecurityContainers]}>
-          <SecurityManagement />
-        </Provider>
-      </ErrorBoundary>
-    </I18nextProvider>,
-    adminSecuritySettingElem,
-  );
-}

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


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

@@ -167,11 +167,6 @@ const ContextExtractorOnce: FC = () => {
   usePreferDrawerModeOnEditByUser();
   usePreferDrawerModeOnEditByUser();
   useIsDeviceSmallerThanMd();
   useIsDeviceSmallerThanMd();
 
 
-  // Navigation
-  usePreferDrawerModeByUser();
-  usePreferDrawerModeOnEditByUser();
-  useIsDeviceSmallerThanMd();
-
   // Editor
   // Editor
   // useSelectedGrant(grant);
   // useSelectedGrant(grant);
   // useSelectedGrantGroupId(grantGroupId);
   // useSelectedGrantGroupId(grantGroupId);

+ 25 - 40
packages/app/src/client/services/PageContainer.js → packages/app/_obsolete/src/client/services/PageContainer.js

@@ -14,8 +14,6 @@ import {
 import {
 import {
   DrawioInterceptor,
   DrawioInterceptor,
 } from '../../services/renderer/interceptor/drawio-interceptor';
 } from '../../services/renderer/interceptor/drawio-interceptor';
-import { toastError } from '../util/apiNotification';
-import { apiPost } from '../util/apiv1-client';
 
 
 const { isTrashPage } = pagePathUtils;
 const { isTrashPage } = pagePathUtils;
 
 
@@ -174,38 +172,31 @@ export default class PageContainer extends Container {
    */
    */
   updateStateAfterSave(page, tags, revision, editorMode) {
   updateStateAfterSave(page, tags, revision, editorMode) {
     // update state of PageContainer
     // update state of PageContainer
-    const newState = {
-      pageId: page._id,
-      revisionId: revision._id,
-      revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
-      remoteRevisionId: revision._id,
-      revisionAuthor: revision.author,
-      revisionIdHackmdSynced: page.revisionHackmdSynced,
-      hasDraftOnHackmd: page.hasDraftOnHackmd,
-      markdown: revision.body,
-      createdAt: page.createdAt,
-      updatedAt: page.updatedAt,
-    };
-    if (tags != null) {
-      newState.tags = tags;
-    }
-    this.setState(newState);
-
-    // Update PageEditor component
-    if (editorMode !== EditorMode.Editor) {
-      // eslint-disable-next-line no-undef
-      globalEmitter.emit('updateEditorValue', newState.markdown);
-    }
+    // const newState = {
+    //   pageId: page._id,
+    //   revisionId: revision._id,
+    //   revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
+    //   remoteRevisionId: revision._id,
+    //   revisionAuthor: revision.author,
+    //   revisionIdHackmdSynced: page.revisionHackmdSynced,
+    //   hasDraftOnHackmd: page.hasDraftOnHackmd,
+    //   markdown: revision.body,
+    //   createdAt: page.createdAt,
+    //   updatedAt: page.updatedAt,
+    // };
+    // if (tags != null) {
+    //   newState.tags = tags;
+    // }
+    // this.setState(newState);
 
 
     // PageEditorByHackmd component
     // PageEditorByHackmd component
-    const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-    if (pageEditorByHackmd != null) {
-      // reset
-      if (editorMode !== EditorMode.HackMD) {
-        pageEditorByHackmd.reset();
-      }
-    }
-
+    // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
+    // if (pageEditorByHackmd != null) {
+    //   // reset
+    //   if (editorMode !== EditorMode.HackMD) {
+    //     pageEditorByHackmd.reset();
+    //   }
+    // }
   }
   }
 
 
   /**
   /**
@@ -345,23 +336,17 @@ export default class PageContainer extends Container {
   retrieveMyBookmarkList() {
   retrieveMyBookmarkList() {
   }
   }
 
 
-  async resolveConflict(markdown, editorMode) {
+  async resolveConflict(markdown, editorMode, optionsToSave) {
 
 
     const { pageId, remoteRevisionId, path } = this.state;
     const { pageId, remoteRevisionId, path } = this.state;
     const editorContainer = this.appContainer.getContainer('EditorContainer');
     const editorContainer = this.appContainer.getContainer('EditorContainer');
-    const options = editorContainer.getCurrentOptionsToSave();
-    const optionsToSave = Object.assign({}, options);
 
 
     const res = await this.updatePage(pageId, remoteRevisionId, markdown, optionsToSave);
     const res = await this.updatePage(pageId, remoteRevisionId, markdown, optionsToSave);
 
 
     editorContainer.clearDraft(path);
     editorContainer.clearDraft(path);
     this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
     this.updateStateAfterSave(res.page, res.tags, res.revision, editorMode);
 
 
-    // Update PageEditor component
-    if (editorMode !== EditorMode.Editor) {
-      // eslint-disable-next-line no-undef
-      globalEmitter.emit('updateEditorValue', markdown);
-    }
+    window.globalEmitter.emit('updateEditorValue', markdown);
 
 
     editorContainer.setState({ tags: res.tags });
     editorContainer.setState({ tags: res.tags });
 
 

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


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


+ 2 - 1
packages/app/cypress.json

@@ -13,5 +13,6 @@
   "viewportWidth": 1400,
   "viewportWidth": 1400,
   "viewportHeight": 1024,
   "viewportHeight": 1024,
 
 
-  "experimentalSessionSupport": true
+  "experimentalSessionSupport": true,
+  "defaultCommandTimeout": 30000
 }
 }

+ 3 - 2
packages/app/docker/Dockerfile

@@ -105,11 +105,12 @@ COPY ["package.json", "lerna.json", "tsconfig.base.json", "./"]
 COPY packages/app packages/app
 COPY packages/app packages/app
 COPY packages/core packages/core
 COPY packages/core packages/core
 COPY packages/codemirror-textlint packages/codemirror-textlint
 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/slack packages/slack
 COPY packages/slack packages/slack
 COPY packages/ui packages/ui
 COPY packages/ui packages/ui
+COPY packages/remark-drawio-plugin packages/remark-drawio-plugin
 COPY packages/remark-growi-plugin packages/remark-growi-plugin
 COPY packages/remark-growi-plugin packages/remark-growi-plugin
+COPY packages/remark-lsx packages/remark-lsx
+COPY packages/hackmd packages/hackmd
 
 
 # build
 # build
 RUN yarn lerna run build
 RUN yarn lerna run build

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

@@ -11,8 +11,8 @@ Supported tags and respective Dockerfile links
 ------------------------------------------------
 ------------------------------------------------
 
 
 * [`6.0.0`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.0/packages/app/docker/Dockerfile)
 * [`6.0.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)
+* [`5.1.7`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
+* [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)
 * [`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)
 * [`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)
 
 

+ 3 - 2
packages/app/next.config.js

@@ -25,6 +25,7 @@ const setupTranspileModules = () => {
     // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
     // listing ESM packages until experimental.esmExternals works correctly to avoid ERR_REQUIRE_ESM
     'react-markdown',
     'react-markdown',
     'unified',
     'unified',
+    'markdown-table',
     'character-entities-html4',
     'character-entities-html4',
     'comma-separated-tokens',
     'comma-separated-tokens',
     'decode-named-character-reference',
     'decode-named-character-reference',
@@ -46,8 +47,8 @@ const setupTranspileModules = () => {
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'unist-']),
     ...listPrefixedPackages(['remark-', 'rehype-', 'hast-', 'mdast-', 'micromark-', 'unist-']),
   ];
   ];
 
 
-  logger.info('{bold:Listing scoped packages for transpiling:}');
-  logger.unprefixed('info', `{grey:${JSON.stringify(packages, null, 2)}}`);
+  // logger.info('{bold:Listing scoped packages for transpiling:}');
+  // logger.unprefixed('info', `{grey:${JSON.stringify(packages, null, 2)}}`);
 
 
   return require('next-transpile-modules')(packages);
   return require('next-transpile-modules')(packages);
 };
 };

+ 22 - 18
packages/app/package.json

@@ -1,6 +1,6 @@
 {
 {
   "name": "@growi/app",
   "name": "@growi/app",
-  "version": "6.0.0-RC.1",
+  "version": "6.0.0-RC.9",
   "license": "MIT",
   "license": "MIT",
   "scripts": {
   "scripts": {
     "//// for production": "",
     "//// for production": "",
@@ -45,6 +45,7 @@
     "swagger-jsdoc": "swagger-jsdoc -o tmp/swagger.json -d config/swagger-definition.js",
     "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: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\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
+    "resources:hackmd": "yarn lerna run build --scope=@growi/hackmd",
     "resources:dummy": "true",
     "resources:dummy": "true",
     "// resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
     "// resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
     "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
     "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
@@ -64,11 +65,13 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.0-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",
+    "@growi/codemirror-textlint": "^6.0.0-RC.9",
+    "@growi/core": "^6.0.0-RC.9",
+    "@growi/hackmd": "^6.0.0-RC.9",
+    "@growi/remark-drawio-plugin": "^6.0.0-RC.9",
+    "@growi/remark-growi-plugin": "^6.0.0-RC.9",
+    "@growi/remark-lsx": "^6.0.0-RC.9",
+    "@growi/slack": "^6.0.0-RC.9",
     "@promster/express": "^7.0.2",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
     "@promster/server": "^7.0.4",
     "@slack/events-api": "^3.0.0",
     "@slack/events-api": "^3.0.0",
@@ -97,6 +100,7 @@
     "detect-indent": "^7.0.0",
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
     "diff_match_patch": "^0.1.1",
+    "ejs": "^3.1.8",
     "entities": "^2.0.0",
     "entities": "^2.0.0",
     "esa-node": "^0.2.2",
     "esa-node": "^0.2.2",
     "escape-string-regexp": "=4.0.0",
     "escape-string-regexp": "=4.0.0",
@@ -113,12 +117,13 @@
     "hast-util-select": "^5.0.2",
     "hast-util-select": "^5.0.2",
     "helmet": "^4.6.0",
     "helmet": "^4.6.0",
     "http-errors": "^2.0.0",
     "http-errors": "^2.0.0",
-    "i18next-chained-backend": "^3.0.2",
-    "i18next-http-backend": "^1.4.1",
-    "i18next-localstorage-backend": "^3.1.3",
+    "i18next-chained-backend": "^4.0.0",
+    "i18next-http-backend": "^2.0.0",
+    "i18next-localstorage-backend": "^4.0.0",
     "is-absolute-url": "^4.0.1",
     "is-absolute-url": "^4.0.1",
     "is-iso-date": "^0.0.1",
     "is-iso-date": "^0.0.1",
     "lucene-query-parser": "^1.2.0",
     "lucene-query-parser": "^1.2.0",
+    "markdown-table": "^1.1.1",
     "md5": "^2.2.1",
     "md5": "^2.2.1",
     "method-override": "^3.0.0",
     "method-override": "^3.0.0",
     "migrate-mongo": "^8.2.3",
     "migrate-mongo": "^8.2.3",
@@ -130,7 +135,7 @@
     "multer": "~1.4.0",
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "multer-autoreap": "^1.0.3",
     "next": "^12.2.5",
     "next": "^12.2.5",
-    "next-i18next": "^11.3.0",
+    "next-i18next": "^12.1.0",
     "next-superjson": "^0.0.4",
     "next-superjson": "^0.0.4",
     "next-themes": "^0.2.0",
     "next-themes": "^0.2.0",
     "nocache": "^3.0.1",
     "nocache": "^3.0.1",
@@ -138,7 +143,7 @@
     "nodemailer-ses-transport": "~1.5.0",
     "nodemailer-ses-transport": "~1.5.0",
     "openid-client": "^5.1.2",
     "openid-client": "^5.1.2",
     "p-retry": "^4.0.0",
     "p-retry": "^4.0.0",
-    "passport": "^0.5.0",
+    "passport": "^0.6.0",
     "passport-github": "^1.1.0",
     "passport-github": "^1.1.0",
     "passport-google-oauth20": "^2.0.0",
     "passport-google-oauth20": "^2.0.0",
     "passport-http": "^0.3.0",
     "passport-http": "^0.3.0",
@@ -159,9 +164,9 @@
     "react-image-crop": "^8.3.0",
     "react-image-crop": "^8.3.0",
     "react-markdown": "^8.0.3",
     "react-markdown": "^8.0.3",
     "react-multiline-clamp": "^2.0.0",
     "react-multiline-clamp": "^2.0.0",
+    "react-scroll": "^1.8.7",
     "react-syntax-highlighter": "^15.5.0",
     "react-syntax-highlighter": "^15.5.0",
     "react-use-ripple": "^1.5.2",
     "react-use-ripple": "^1.5.2",
-    "react-scroll": "^1.8.7",
     "reactstrap": "^8.9.0",
     "reactstrap": "^8.9.0",
     "reconnecting-websocket": "^4.4.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
     "redis": "^3.0.2",
@@ -189,21 +194,20 @@
     "toastr": "^2.1.2",
     "toastr": "^2.1.2",
     "uglifycss": "^0.0.29",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
     "universal-bunyan": "^0.9.2",
+    "unstated": "^2.1.1",
     "unzipper": "^0.10.5",
     "unzipper": "^0.10.5",
     "url-join": "^4.0.0",
     "url-join": "^4.0.0",
     "usehooks-ts": "^2.6.0",
     "usehooks-ts": "^2.6.0",
     "validator": "^13.7.0",
     "validator": "^13.7.0",
     "ws": "^8.3.0",
     "ws": "^8.3.0",
-    "xss": "^1.0.6",
-    "unstated": "^2.1.1"
+    "xss": "^1.0.6"
   },
   },
   "// comments for defDependencies": {
   "// comments for defDependencies": {
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   },
   "devDependencies": {
   "devDependencies": {
-    "@alienfast/i18next-loader": "^1.1.4",
-    "@growi/ui": "^6.0.0-RC.1",
+    "@growi/ui": "^6.0.0-RC.9",
     "@handsontable/react": "=2.1.0",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",
     "@next/bundle-analyzer": "^12.2.3",
@@ -211,6 +215,7 @@
     "@types/express": "^4.17.11",
     "@types/express": "^4.17.11",
     "@types/jquery": "^3.5.8",
     "@types/jquery": "^3.5.8",
     "@types/multer": "^1.4.5",
     "@types/multer": "^1.4.5",
+    "@types/react-scroll": "^1.8.4",
     "autoprefixer": "^9.0.0",
     "autoprefixer": "^9.0.0",
     "babel-loader": "^8.2.5",
     "babel-loader": "^8.2.5",
     "bootstrap": "^4.6.1",
     "bootstrap": "^4.6.1",
@@ -228,12 +233,11 @@
     "eslint-plugin-regex": "^1.8.0",
     "eslint-plugin-regex": "^1.8.0",
     "font-awesome": "^4.7.0",
     "font-awesome": "^4.7.0",
     "handsontable": "=6.2.2",
     "handsontable": "=6.2.2",
-    "i18next-hmr": "^1.7.7",
+    "i18next-hmr": "^1.11.0",
     "jquery-slimscroll": "^1.3.8",
     "jquery-slimscroll": "^1.3.8",
     "jquery.cookie": "~1.4.1",
     "jquery.cookie": "~1.4.1",
     "jshint": "^2.13.0",
     "jshint": "^2.13.0",
     "load-css-file": "^1.0.0",
     "load-css-file": "^1.0.0",
-    "markdown-table": "^1.1.1",
     "material-icons": "^1.11.3",
     "material-icons": "^1.11.3",
     "morgan": "^1.10.0",
     "morgan": "^1.10.0",
     "next-transpile-modules": "^9.0.0",
     "next-transpile-modules": "^9.0.0",

BIN
packages/app/public/static/fonts/Lato-Bold-latin-ext.woff2


BIN
packages/app/public/static/fonts/Lato-Bold-latin.woff2


BIN
packages/app/public/static/fonts/Lato-Regular-latin-ext.woff2


BIN
packages/app/public/static/fonts/Lato-Regular-latin.woff2


BIN
packages/app/public/static/fonts/PressStart2P-latin-ext.woff2


BIN
packages/app/public/static/fonts/PressStart2P-latin.woff2


+ 35 - 15
packages/app/public/static/locales/en_US/admin.json

@@ -3,16 +3,21 @@
     "display_name": "English"
     "display_name": "English"
   },
   },
   "wiki_management_home_page": "Wiki Management Home Page",
   "wiki_management_home_page": "Wiki Management Home Page",
-  "app_settings": "App Settings",
+  "last_login": "Last login",
+  "anyone_with_the_link": "anyone with the link",
+  "only_me": "only me",
+  "only_inside_the_group": "only inside the group",
   "security_settings": {
   "security_settings": {
     "security_settings": "Security Settings",
     "security_settings": "Security Settings",
+    "scope_of_page_disclosure": "Scope of page disclosure",
+    "set_point": "Set point",
     "Guest Users Access": "Guest users access",
     "Guest Users Access": "Guest users access",
     "always_hidden": "Always hidden",
     "always_hidden": "Always hidden",
     "always_displayed": "Always displayed",
     "always_displayed": "Always displayed",
     "displayed_or_hidden": "Displayed / Hidden",
     "displayed_or_hidden": "Displayed / Hidden",
     "Fixed by env var": "This is fixed by the env var <code>{{key}}={{value}}</code>.",
     "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",
+    "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",
     "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",
     "users_without_account": "Users without account is not accessible",
     "example": "Example",
     "example": "Example",
@@ -46,7 +51,6 @@
     "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",
     "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",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "setup_is_not_yet_complete": "Setup is not yet complete",
     "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": "Prevent XSS(Cross Site Scripting)",
     "xss_prevent_setting_link": "Go to Markdown Settings",
     "xss_prevent_setting_link": "Go to Markdown Settings",
     "callback_URL": "Callback URL",
     "callback_URL": "Callback URL",
@@ -75,6 +79,10 @@
       "restricted": "Restricted (Requires approval by administrators)",
       "restricted": "Restricted (Requires approval by administrators)",
       "closed": "Closed (Invitation Only)"
       "closed": "Closed (Invitation Only)"
     },
     },
+    "share_link_management": "Share Link Management",
+    "No_share_links":"No share links",
+    "share_link_notice":"remove all share links",
+    "delete_all_share_links":"Delete all share links",
     "share_link_rights": "Share link rights",
     "share_link_rights": "Share link rights",
     "enable_link_sharing": "Enable link sharing",
     "enable_link_sharing": "Enable link sharing",
     "all_share_links": "All share links",
     "all_share_links": "All share links",
@@ -97,7 +105,6 @@
       "email_authentication": "Email authentication on user registration",
       "email_authentication": "Email authentication on user registration",
       "enable_email_authentication": "Enable email authentication",
       "enable_email_authentication": "Enable email authentication",
       "enable_email_authentication_desc": "Email authentication is going to be performed for user registration.",
       "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."
       "need_complete_mail_setting_warning": "To use the following functions, please complete the mail settings."
     },
     },
     "ldap": {
     "ldap": {
@@ -269,7 +276,8 @@
     "delete_notification_pattern": "Delete notification pattern",
     "delete_notification_pattern": "Delete notification pattern",
     "delete_notification_pattern_desc1": "Delete Path: {{path}}",
     "delete_notification_pattern_desc1": "Delete Path: {{path}}",
     "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
     "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-    "toggle_notification": "Updated setting of {{path}}"
+    "toggle_notification": "Updated setting of {{path}}",
+    "not_found_global_notification_triggerid": "Not found the global notification id"
   },
   },
   "mailer_setup_required":"<a href='/admin/app'>Email settings</a> are required to send.",
   "mailer_setup_required":"<a href='/admin/app'>Email settings</a> are required to send.",
   "admin_top": {
   "admin_top": {
@@ -292,8 +300,6 @@
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>then submit your issue to GitHub.</a>"
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>then submit your issue to GitHub.</a>"
   },
   },
   "v5_page_migration": {
   "v5_page_migration": {
-    "page_tree_not_avaliable" : "Page tree feature is not available yet.",
-    "go_to_settings": "Go to settings to enable the feature",
     "migration_desc": "There are some pages with old v4 compatibility. To take advantage of new features such as page trees and easy renaming, please convert all your pages to v5 compatibility.",
     "migration_desc": "There are some pages with old v4 compatibility. To take advantage of new features such as page trees and easy renaming, please convert all your pages to v5 compatibility.",
     "migration_note": "Note: You will lose unique constraints from the page paths.",
     "migration_note": "Note: You will lose unique constraints from the page paths.",
     "upgrade_to_v5": "Convert to v5 compatibility",
     "upgrade_to_v5": "Convert to v5 compatibility",
@@ -323,9 +329,12 @@
     "site_name": "Site name",
     "site_name": "Site name",
     "sitename_change": "You can change site name which is used for header and HTML title.",
     "sitename_change": "You can change site name which is used for header and HTML title.",
     "header_content": "The contents entered here will be shown in the header etc.",
     "header_content": "The contents entered here will be shown in the header etc.",
-    "site_url_desc": "This is for the site URL setting.",
-    "site_url_warn": "Some features don't work because the site URL is not set.",
-    "siteurl_help": "Site full URL beginning from <code>http://</code> or <code>https://</code>.",
+    "site_url": {
+      "title": "Site URL settings",
+      "desc": "This is for the site URL setting.",
+      "warn": "Some features don't work because the site URL is not set.",
+      "help": "Site full URL beginning from <code>http://</code> or <code>https://</code>."
+    },
     "confidential_name": "Confidential name",
     "confidential_name": "Confidential name",
     "confidential_example": "ex): internal use only",
     "confidential_example": "ex): internal use only",
     "default_language": "Default language for new users",
     "default_language": "Default language for new users",
@@ -371,7 +380,7 @@
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
     "custom_endpoint_change": "Input the URL of the endpoint of an object storage service like MinIO that has a S3-compatible API.  Amazon S3 is used if empty.",
     "plugin_settings": "Plugin settings",
     "plugin_settings": "Plugin settings",
     "enable_plugin_loading": "Enable plugin loading",
     "enable_plugin_loading": "Enable plugin loading",
-    "load_plugins": "Load_plugins",
+    "load_plugins": "Load plugins",
     "enable": "Enable",
     "enable": "Enable",
     "disable": "Disable",
     "disable": "Disable",
     "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
     "use_env_var_if_empty": "If the value in the database is empty, the value of the environment variable <code>{{variable}}</code> is used.",
@@ -467,8 +476,6 @@
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
       "select_search_scope_children_as_default": "Select 'Only children of this tree' as default value of search range",
       "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
       "select_search_scope_children_as_default_desc": "When the setting value is off, 'All pages' is used as default value of search range."
     },
     },
-    "code_highlight": "Code highlight",
-    "nocdn_desc": "This function is disabled when the environment variable <code>NO_CDN=true</code>.<br>Github style has been forcibly applied.",
     "custom_title": "Custom title",
     "custom_title": "Custom title",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
     "custom_title_detail": "You can customize <code>&lt;title&gt;</code> tag. Following placeholders will be automatically replaced:",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - The site name of this wiki.",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - The site name of this wiki.",
@@ -573,7 +580,7 @@
     "export": "Export",
     "export": "Export",
     "cancel": "Cancel",
     "cancel": "Cancel",
     "file": "File",
     "file": "File",
-    "growi_version": "Growi Version",
+    "growi_version": "GROWI Version",
     "collections": "Collections",
     "collections": "Collections",
     "exported_at": "Exported At",
     "exported_at": "Exported At",
     "export_menu": "Export Menu",
     "export_menu": "Export Menu",
@@ -858,6 +865,9 @@
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
     }
   },
   },
+  "cloud_setting_management": {
+    "to_cloud_settings": "Open GROWI.cloud Settings"
+  },
   "audit_log_action_category": {
   "audit_log_action_category": {
     "Page": "Page",
     "Page": "Page",
     "Comment": "Comment",
     "Comment": "Comment",
@@ -1028,5 +1038,15 @@
     "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch",
     "ADMIN_SEARCH_CONNECTION": "Attempting to reconnect to Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Normalize of Elasticsearch indexes",
     "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
     "ADMIN_SEARCH_INDICES_REBUILD": "Rebuild Elasticsearch indexes"
+  },
+  "toaster": {
+    "give_user_admin": "Succeeded to give {{username}} admin",
+    "remove_user_admin": "Succeeded to remove {{username}} admin",
+    "activate_user_success": "Succeeded to activating {{username}}",
+    "deactivate_user_success": "Succeeded to deactivate {{username}}",
+    "remove_user_success": "Succeeded to removing {{username}}",
+    "remove_external_user_success": "Succeeded to remove {{accountId}}",
+    "switch_disable_link_sharing_success": "Succeeded to update share link setting",
+    "failed_to_reset_password":"Failed to reset password"
   }
   }
 }
 }

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

@@ -0,0 +1,100 @@
+{
+  "Show": "Show",
+  "Hide": "Hide",
+  "Add": "Add",
+  "Reset": "Reset",
+  "Sign out": "Logout",
+  "New": "New",
+
+  "meta": {
+    "display_name": "English"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}",
+
+    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
+    "remove_share_link": "Succeeded to remove {{count}} share links"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
+    "please_enable_mailer": "Please setup mailer first."
+  },
+  "headers": {
+    "app_settings": "App Settings"
+  },
+
+  "header_search_box": {
+    "label": {
+      "All pages": "All pages",
+      "This tree": "This tree"
+    },
+    "item_label": {
+      "All pages": "All pages",
+      "This tree": "Only children of this tree"
+    }
+  },
+
+  "share_links": {
+    "Share Link": "Share Link",
+    "Page Path": "Page Path",
+    "expire": "Expiration",
+    "description": "Description"
+  },
+
+  "in_app_notification": {
+    "notification_list": "In-App Notification List",
+    "see_all": "See All",
+    "no_notification": "You don't have any notificatios.",
+    "all": "All",
+    "unopend": "Unread",
+    "mark_all_as_read": "Mark all as read"
+  },
+
+  "personal_dropdown": {
+    "home": "Home",
+    "settings": "Settings",
+    "color_mode": "Color mode",
+    "sidebar_mode": "Sidebar mode",
+    "sidebar_mode_editor": "Sidebar mode on editor",
+    "use_os_settings": "Use OS settings"
+  },
+
+  "copy_to_clipboard": {
+    "Copy to clipboard": "Copy to clipboard",
+    "Page path": "Page path",
+    "Page URL": "Page URL",
+    "Permanent link": "Permanent link",
+    "Page path and permanent link": "Page path and permanent link",
+    "Markdown link": "Markdown link"
+  },
+
+  "crop_image_modal": {
+    "image_crop": "Image Crop",
+    "crop": "Crop",
+    "save": "Save",
+    "cancel": "Cancel"
+  },
+
+  "handsontable_modal": {
+    "title": "Edit Table",
+    "data_import": "Data Import",
+    "save": "Save",
+    "cancel": "Cancel",
+    "done": "Done",
+    "data_import_form": {
+      "select_data_format": "Select Data Format",
+      "import_data": "Import Data",
+      "paste_table_data": "Paste table data",
+      "parse_error": "Parse Error",
+      "cancel": "Cancel",
+      "import": "Import"
+    }
+  },
+
+  "not_found_page": {
+    "page_not_exist": "This page does not exist."
+  }
+}

+ 15 - 82
packages/app/public/static/locales/en_US/translation.json

@@ -28,7 +28,6 @@
   "administrator": "Admin",
   "administrator": "Admin",
   "Tag": "Tag",
   "Tag": "Tag",
   "Tags": "Tags",
   "Tags": "Tags",
-  "New": "New",
   "Close": "Close",
   "Close": "Close",
   "Shortcuts": "Shortcuts",
   "Shortcuts": "Shortcuts",
   "CustomSidebar": "Custom Sidebar",
   "CustomSidebar": "Custom Sidebar",
@@ -78,7 +77,6 @@
   "username": "Username",
   "username": "Username",
   "Created": "Created",
   "Created": "Created",
   "Last updated": "Updated",
   "Last updated": "Updated",
-  "last_login": "Last login",
   "Share": "Share",
   "Share": "Share",
   "Markdown Link": "Markdown Link",
   "Markdown Link": "Markdown Link",
   "Create/Edit Template": "Create/Edit template page",
   "Create/Edit Template": "Create/Edit template page",
@@ -101,8 +99,6 @@
   "Updated": "Updated",
   "Updated": "Updated",
   "Upload new image": "Upload new image",
   "Upload new image": "Upload new image",
   "Connected": "Connected",
   "Connected": "Connected",
-  "Show": "Show",
-  "Hide": "Hide",
   "Loading": "Loading...",
   "Loading": "Loading...",
   "Disclose E-mail": "Disclose E-mail",
   "Disclose E-mail": "Disclose E-mail",
   "page exists": "this page already exists",
   "page exists": "this page already exists",
@@ -115,13 +111,11 @@
   "Create under": "Create page under below:",
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "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",
+  "See_more_detail_on_new_schema": "See more detail on <a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'>{{title}}</a> <i class='icon-share-alt'></i> ",
   "external_account_management": "External Account Management",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
   "UserGroup": "UserGroup",
   "Basic Settings": "Basic Settings",
   "Basic Settings": "Basic Settings",
   "Basic authentication": "Basic authentication",
   "Basic authentication": "Basic authentication",
-  "Register limitation": "Register limitation",
   "The contents entered here will be shown in the header etc": "The contents entered here will be shown in the header etc",
   "The contents entered here will be shown in the header etc": "The contents entered here will be shown in the header etc",
   "Public": "Public",
   "Public": "Public",
   "Anyone with the link": "Anyone with the link",
   "Anyone with the link": "Anyone with the link",
@@ -129,8 +123,6 @@
   "Only me": "Only me",
   "Only me": "Only me",
   "Only inside the group": "Only inside the group",
   "Only inside the group": "Only inside the group",
   "page_list": "Page List",
   "page_list": "Page List",
-  "scope_of_page_disclosure": "Scope of page disclosure",
-  "set_point": "Set point",
   "Reselect the group": "Reselect the group",
   "Reselect the group": "Reselect the group",
   "Shareable link": "Shareable link",
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
@@ -144,7 +136,6 @@
   "edited this page": "edited this page.",
   "edited this page": "edited this page.",
   "List Drafts": "Drafts",
   "List Drafts": "Drafts",
   "Deleted Pages": "Deleted Pages",
   "Deleted Pages": "Deleted Pages",
-  "Sign out": "Logout",
   "Disassociate": "Disassociate",
   "Disassociate": "Disassociate",
   "No bookmarks yet": "No bookmarks yet",
   "No bookmarks yet": "No bookmarks yet",
   "add_bookmark": "Add to Bookmarks",
   "add_bookmark": "Add to Bookmarks",
@@ -161,31 +152,17 @@
   "not_allowed_to_see_this_page": "You cannot see this page",
   "not_allowed_to_see_this_page": "You cannot see this page",
   "Confirm": "Confirm",
   "Confirm": "Confirm",
   "Successfully requested": "Successfully requested.",
   "Successfully requested": "Successfully requested.",
-  "personal_dropdown": {
-    "home": "Home",
-    "settings": "Settings",
-    "color_mode": "Color mode",
-    "sidebar_mode": "Sidebar mode",
-    "sidebar_mode_editor": "Sidebar mode on editor",
-    "use_os_settings": "Use OS settings"
-  },
   "form_validation": {
   "form_validation": {
     "error_message": "Some values ​​are incorrect",
     "error_message": "Some values ​​are incorrect",
     "required": "%s is required",
     "required": "%s is required",
     "invalid_syntax": "The syntax of %s is invalid.",
     "invalid_syntax": "The syntax of %s is invalid.",
     "title_required": "Title is required."
     "title_required": "Title is required."
   },
   },
-  "not_found_page": {
-    "Create Page": "Create Page",
-    "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": {
   "not_creatable_page": {
     "could_not_creata_path": "Couldn't create path."
     "could_not_creata_path": "Couldn't create path."
   },
   },
   "custom_navigation": {
   "custom_navigation": {
-    "no_page_list": "There are no pages under this page.",
-    "link_sharing_is_disabled": "Link sharing is disabled."
+    "no_page_list": "There are no pages under this page."
   },
   },
   "installer": {
   "installer": {
     "setup": "Setup",
     "setup": "Setup",
@@ -241,10 +218,6 @@
     "Shere this page link to public": "Shere this page link to public",
     "Shere this page link to public": "Shere this page link to public",
     "share_link_list": "Share link list",
     "share_link_list": "Share link list",
     "share_link_management": "Share Link Management",
     "share_link_management": "Share Link Management",
-    "No_share_links":"No share links",
-    "Share Link": "Share Link",
-    "Page Path": "Page Path",
-    "share_link_notice":"remove all share links",
     "delete_all_share_links":"Delete all share links",
     "delete_all_share_links":"Delete all share links",
     "expire": "Expiration",
     "expire": "Expiration",
     "Days": "Days",
     "Days": "Days",
@@ -254,30 +227,13 @@
     "Unlimited": "unlimited",
     "Unlimited": "unlimited",
     "Issue": "Issue",
     "Issue": "Issue",
     "share_settings" :"Share settings",
     "share_settings" :"Share settings",
-    "Invalid_Number_of_Date" : "You entered invalid value"
+    "Invalid_Number_of_Date" : "You entered invalid value",
+    "link_sharing_is_disabled": "Link sharing is disabled"
   },
   },
   "API Settings": "API settings",
   "API Settings": "API settings",
   "API Token Settings": "API token settings",
   "API Token Settings": "API token settings",
   "Current API Token": "Current API token",
   "Current API Token": "Current API token",
   "Update API Token": "Update API token",
   "Update API Token": "Update API token",
-  "header_search_box": {
-    "label": {
-      "All pages": "All pages",
-      "This tree": "This tree"
-    },
-    "item_label": {
-      "All pages": "All pages",
-      "This tree": "Only children of this tree"
-    }
-  },
-  "in_app_notification": {
-    "notification_list": "In-App Notification List",
-    "see_all": "See All",
-    "no_notification": "You don't have any notificatios.",
-    "all": "All",
-    "unopend": "Unread",
-    "mark_all_as_read": "Mark all as read"
-  },
   "in_app_notification_settings": {
   "in_app_notification_settings": {
     "in_app_notification_settings": "In-App Notification Settings",
     "in_app_notification_settings": "In-App Notification Settings",
     "subscribe_settings": "Settings to automatically subscribe (Receive notifications) to pages",
     "subscribe_settings": "Settings to automatically subscribe (Receive notifications) to pages",
@@ -322,14 +278,6 @@
       "no_nfd": "textlint rule that disallow to use NFD like UTF8-MAC Sonant mark."
       "no_nfd": "textlint rule that disallow to use NFD like UTF8-MAC Sonant mark."
     }
     }
   },
   },
-  "copy_to_clipboard": {
-    "Copy to clipboard": "Copy to clipboard",
-    "Page path": "Page path",
-    "Page URL": "Page URL",
-    "Permanent link": "Permanent link",
-    "Page path and permanent link": "Page path and permanent link",
-    "Markdown link": "Markdown link"
-  },
   "search_help": {
   "search_help": {
     "title": "Searching Help",
     "title": "Searching Help",
     "and": {
     "and": {
@@ -389,7 +337,8 @@
     "notfound_or_forbidden": "Original page is not found or forbidden.",
     "notfound_or_forbidden": "Original page is not found or forbidden.",
     "already_exists": "Page with the path already exists.",
     "already_exists": "Page with the path already exists.",
     "outdated": "Page is updated someone and now outdated.",
     "outdated": "Page is updated someone and now outdated.",
-    "user_not_admin": "Only admin user can delete"
+    "user_not_admin": "Only admin user can delete",
+    "single_deletion_empty_pages": "Empty pages cannot be single deleted"
   },
   },
   "page_history": {
   "page_history": {
     "revision_list": "Revision list",
     "revision_list": "Revision list",
@@ -523,24 +472,10 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
   "toaster": {
   "toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-    "update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "File upload succeeded.",
     "file_upload_succeeded": "File upload succeeded.",
     "file_upload_failed": "File upload failed.",
     "file_upload_failed": "File upload failed.",
-    "initialize_successed": "Succeeded to initialize {{target}}",
-    "give_user_admin": "Succeeded to give {{username}} admin",
-    "remove_user_admin": "Succeeded to remove {{username}} admin",
-    "activate_user_success": "Succeeded to activating {{username}}",
-    "deactivate_user_success": "Succeeded to deactivate {{username}}",
-    "remove_user_success": "Succeeded to removing {{username}}",
-    "remove_external_user_success": "Succeeded to remove {{accountId}}",
-    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
-    "issue_share_link": "Succeeded to issue new share link",
-    "remove_share_link": "Succeeded to remove {{count}} share links",
-    "switch_disable_link_sharing_success": "Succeeded to update share link setting",
-    "failed_to_reset_password":"Failed to reset password"
+    "save_succeeded": "Saved successfully",
+    "issue_share_link": "Succeeded to issue new share link"
   },
   },
   "template": {
   "template": {
     "modal_label": {
     "modal_label": {
@@ -670,10 +605,9 @@
       "error_duplicate_pages_found": "Multiple pages with the same path name were found. Please rename or delete and try again."
       "error_duplicate_pages_found": "Multiple pages with the same path name were found. Please rename or delete and try again."
     }
     }
   },
   },
-  "to_cloud_settings": "Open GROWI.cloud Settings",
   "login": {
   "login": {
     "Sign in error": "Login error",
     "Sign in error": "Login error",
-    "Registration successful": "Registration successful",
+    "Registration successful": "Registration successful. Please wait for administrator approval.",
     "Setup": "Setup",
     "Setup": "Setup",
     "enabled_ldap_has_configuration_problem":"LDAP is enabled but the configuration has something wrong.",
     "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)"
     "set_env_var_for_logs": "(Please set the environment variables <code>DEBUG=crowi:service:PassportService</code> to get the logs)"
@@ -705,6 +639,8 @@
     "username_should_not_be_null":"Username should not be null. Please check Authentication Mechanism Settings on admin page",
     "username_should_not_be_null":"Username should not be null. Please check Authentication Mechanism Settings on admin page",
     "email_address_is_already_registered":"This email address is already registered.",
     "email_address_is_already_registered":"This email address is already registered.",
     "can_not_register_maximum_number_of_users":"Can not register more than the maximum number of users.",
     "can_not_register_maximum_number_of_users":"Can not register more than the maximum number of users.",
+    "email_settings_is_not_setup":"E-mail settings is not set up. Please ask the administrator.",
+    "email_authentication_is_not_enabled": "Email authentication is not enabled. Please ask the administrator.",
     "failed_to_register":"Failed to register.",
     "failed_to_register":"Failed to register.",
     "successfully_created":"The user {{username}} is successfully created.",
     "successfully_created":"The user {{username}} is successfully created.",
     "can_not_activate_maximum_number_of_users":"Can not activate more than the maximum number of users.",
     "can_not_activate_maximum_number_of_users":"Can not activate more than the maximum number of users.",
@@ -812,13 +748,6 @@
     "belonging_to_no_group": "Could not find the groups you belong to.",
     "belonging_to_no_group": "Could not find the groups you belong to.",
     "manage_user_groups": "Manage user groups"
     "manage_user_groups": "Manage user groups"
   },
   },
-  "crop_image_modal": {
-    "image_crop": "Image Crop",
-    "crop": "Crop",
-    "save": "Save",
-    "reset": "Reset",
-    "cancel": "Cancel"
-  },
   "fix_page_grant": {
   "fix_page_grant": {
     "modal": {
     "modal": {
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
       "no_grant_available": "The list of selectable permissions could not be found. Please modify the permissions on the parent page first and try again.",
@@ -866,5 +795,9 @@
   "footer": {
   "footer": {
     "bookmarks": "Bookmarks",
     "bookmarks": "Bookmarks",
     "recently_created": "Recently Created"
     "recently_created": "Recently Created"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable" : "Page tree feature is not available yet.",
+    "go_to_settings": "Go to settings to enable the feature"
   }
   }
 }
 }

+ 28 - 14
packages/app/public/static/locales/ja_JP/admin.json

@@ -12,7 +12,6 @@
   "Description": "説明",
   "Description": "説明",
   "last_login": "最終ログイン",
   "last_login": "最終ログイン",
   "wiki_management_home_page": "Wiki管理トップ",
   "wiki_management_home_page": "Wiki管理トップ",
-  "app_settings": "アプリ設定",
   "public": "公開",
   "public": "公開",
   "anyone_with_the_link": "リンクを知っている人のみ",
   "anyone_with_the_link": "リンクを知っている人のみ",
   "specified_users": "特定ユーザーのみ",
   "specified_users": "特定ユーザーのみ",
@@ -27,8 +26,8 @@
     "always_displayed": "表示 (固定)",
     "always_displayed": "表示 (固定)",
     "displayed_or_hidden": "表示 / 非表示",
     "displayed_or_hidden": "表示 / 非表示",
     "Fixed by env var": "環境変数 <code>{{forcewikimode}}={{wikimode}}</code> により固定されています。",
     "Fixed by env var": "環境変数 <code>{{forcewikimode}}={{wikimode}}</code> により固定されています。",
-    "Register limitation": "登録の制限",
-    "Register limitation desc": "新しいユーザーを登録する方法を制限します.",
+    "register_limitation": "登録の制限",
+    "register_limitation_desc": "新しいユーザーを登録する方法を制限します。",
     "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
     "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
     "users_without_account": "アカウントを持たないユーザーはアクセス不可",
     "users_without_account": "アカウントを持たないユーザーはアクセス不可",
     "example": "例",
     "example": "例",
@@ -62,7 +61,6 @@
     "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 管理者のみ可能 > 管理者とページ作者が可能 > 誰でも可能",
     "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 管理者のみ可能 > 管理者とページ作者が可能 > 誰でも可能",
     "Authentication mechanism settings": "認証機構設定",
     "Authentication mechanism settings": "認証機構設定",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
-    "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
     "xss_prevent_setting": "XSS(Cross Site Scripting)対策設定",
     "xss_prevent_setting": "XSS(Cross Site Scripting)対策設定",
     "xss_prevent_setting_link": "マークダウン設定ページに移動",
     "xss_prevent_setting_link": "マークダウン設定ページに移動",
     "callback_URL": "コールバックURL",
     "callback_URL": "コールバックURL",
@@ -88,6 +86,10 @@
       "restricted": "制限 (登録完了には管理者の承認が必要)",
       "restricted": "制限 (登録完了には管理者の承認が必要)",
       "closed": "非公開 (登録には管理者による招待が必要)"
       "closed": "非公開 (登録には管理者による招待が必要)"
     },
     },
+    "share_link_management": "共有リンク管理",
+    "No_share_links":"共有リンクが存在しません",
+    "share_link_notice":"共有リンクを全て削除します",
+    "delete_all_share_links":"全ての共有リンクを削除します",
     "share_link_rights": "シェアリンクの権限",
     "share_link_rights": "シェアリンクの権限",
     "enable_link_sharing": "リンクのシェアを許可",
     "enable_link_sharing": "リンクのシェアを許可",
     "all_share_links": "全てのシェアリンク",
     "all_share_links": "全てのシェアリンク",
@@ -110,7 +112,6 @@
       "email_authentication": "ユーザー登録時のメール認証",
       "email_authentication": "ユーザー登録時のメール認証",
       "enable_email_authentication": "メール認証を有効にする",
       "enable_email_authentication": "メール認証を有効にする",
       "enable_email_authentication_desc": "ユーザー登録時にメール認証を行います。",
       "enable_email_authentication_desc": "ユーザー登録時にメール認証を行います。",
-      "please_enable_mailer": "メール認証を有効にするには、メール設定を完了させてください。",
       "need_complete_mail_setting_warning": "以下の機能を使えるようにするには、メール設定を完了させてください。"
       "need_complete_mail_setting_warning": "以下の機能を使えるようにするには、メール設定を完了させてください。"
     },
     },
     "ldap": {
     "ldap": {
@@ -278,7 +279,8 @@
     "delete_notification_pattern": "通知パターンを削除しました。",
     "delete_notification_pattern": "通知パターンを削除しました。",
     "delete_notification_pattern_desc1": "Path: {{path}} を削除します。",
     "delete_notification_pattern_desc1": "Path: {{path}} を削除します。",
     "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
     "delete_notification_pattern_desc2": "Once deleted, it cannot be recovered",
-    "toggle_notification": "{{path}}の通知設定を変更しました"
+    "toggle_notification": "{{path}}の通知設定を変更しました",
+    "not_found_global_notification_triggerid": "アクセス先の通知設定は見つかりませんでした"
   },
   },
   "full_text_search_management": {
   "full_text_search_management": {
     "full_text_search_management": "全文検索管理",
     "full_text_search_management": "全文検索管理",
@@ -324,8 +326,6 @@
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>次に GitHub で Issue を投稿してください。</a>"
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>次に GitHub で Issue を投稿してください。</a>"
   },
   },
   "v5_page_migration": {
   "v5_page_migration": {
-    "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
-    "go_to_settings": "設定する",
     "migration_desc": "公開されているページに 古い v4 互換形式のものが存在します。ページツリーや簡単なリネームなどの新機能を利用するには、全てのページを v5 互換形式に変換してください。",
     "migration_desc": "公開されているページに 古い v4 互換形式のものが存在します。ページツリーや簡単なリネームなどの新機能を利用するには、全てのページを v5 互換形式に変換してください。",
     "migration_note": "注意: ページパスからユニーク制約が失われます。",
     "migration_note": "注意: ページパスからユニーク制約が失われます。",
     "upgrade_to_v5": "v5 互換形式 へ変換",
     "upgrade_to_v5": "v5 互換形式 へ変換",
@@ -355,9 +355,12 @@
     "site_name": "サイト名",
     "site_name": "サイト名",
     "sitename_change": "ヘッダーや HTML タイトルに使用されるサイト名を変更できます。",
     "sitename_change": "ヘッダーや HTML タイトルに使用されるサイト名を変更できます。",
     "header_content": "ここに入力した内容は、ヘッダー等に表示されます。",
     "header_content": "ここに入力した内容は、ヘッダー等に表示されます。",
-    "site_url_desc": "サイトURLを設定します。",
-    "site_url_warn": "サイトURLが設定されていないため、一部機能が動作しない状態になっています。",
-    "siteurl_help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL",
+    "site_url": {
+      "title": "サイトURL設定",
+      "desc": "サイトURLを設定します。",
+      "warn": "サイトURLが設定されていないため、一部機能が動作しない状態になっています。",
+      "help": "<code>http://</code> または <code>https://</code> から始まるサイトのURL"
+    },
     "confidential_name": "コンフィデンシャル表示",
     "confidential_name": "コンフィデンシャル表示",
     "confidential_example": "例: 社外秘",
     "confidential_example": "例: 社外秘",
     "default_language": "新規ユーザーのデフォルト設定言語",
     "default_language": "新規ユーザーのデフォルト設定言語",
@@ -499,8 +502,6 @@
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
       "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
     },
     },
-    "code_highlight": "コードハイライト",
-    "nocdn_desc": "この機能は、環境変数 <code>NO_CDN=true</code> の時は無効化されます。<br>GitHub スタイルが適用されています。",
     "custom_title": "カスタム Title",
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - この Wiki のサイト名",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - この Wiki のサイト名",
@@ -533,7 +534,7 @@
     "export": "エクスポート",
     "export": "エクスポート",
     "cancel": "キャンセル",
     "cancel": "キャンセル",
     "file": "ファイル名",
     "file": "ファイル名",
-    "growi_version": "Growi バージョン",
+    "growi_version": "GROWI バージョン",
     "collections": "コレクション",
     "collections": "コレクション",
     "exported_at": "エクスポートされた時間",
     "exported_at": "エクスポートされた時間",
     "export_menu": "エクスポートメニュー",
     "export_menu": "エクスポートメニュー",
@@ -870,6 +871,9 @@
       "log_type": "https://docs.growi.org/ja/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
       "log_type": "https://docs.growi.org/ja/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
     }
   },
   },
+  "cloud_setting_management": {
+    "to_cloud_settings": "GROWI.cloud の管理画面へ"
+  },
   "audit_log_action_category": {
   "audit_log_action_category": {
     "Page": "ページ",
     "Page": "ページ",
     "Comment": "コメント",
     "Comment": "コメント",
@@ -1040,5 +1044,15 @@
     "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行",
     "ADMIN_SEARCH_CONNECTION": "Elasticsearch の再接続の試行",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "Elasticsearch のインデックスの正規化",
     "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
     "ADMIN_SEARCH_INDICES_REBUILD": "Elasticsearch のインデックスのリビルド"
+  },
+  "toaster": {
+    "give_user_admin": "{{username}}を管理者に設定しました",
+    "remove_user_admin": "{{username}}を管理者から外しました",
+    "activate_user_success": "{{username}}を有効化しました",
+    "deactivate_user_success": "{{username}}を無効化しました",
+    "remove_user_success": "{{username}}を削除しました",
+    "remove_external_user_success": "{{accountId}}を削除しました",
+    "switch_disable_link_sharing_success": "共有リンクの設定を変更しました",
+    "failed_to_reset_password":"パスワードのリセットに失敗しました"
   }
   }
 }
 }

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

@@ -0,0 +1,100 @@
+{
+  "Show": "公開",
+  "Hide": "非公開",
+  "Add": "追加",
+  "Reset": "リセット",
+  "Sign out": "ログアウト",
+  "New": "作成",
+
+  "meta": {
+    "display_name": "日本語"
+  },
+  "toaster": {
+    "create_succeeded": "新しい{{target}}が作成されました",
+    "create_failed": "{{target}}の作成に失敗しました",
+    "update_successed": "{{target}}を更新しました",
+    "update_failed": "{{target}}の更新に失敗しました",
+
+    "remove_share_link_success": "{{shareLinkId}}を削除しました",
+    "remove_share_link": "共有リンクを{{count}}件削除しました"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
+    "please_enable_mailer": "メール認証を有効にするには、メール設定を完了させてください。"
+  },
+  "headers": {
+    "app_settings": "アプリ設定"
+  },
+
+  "header_search_box": {
+    "label": {
+      "All pages": "全てのページ",
+      "This tree": "この階層"
+    },
+    "item_label": {
+      "All pages": "全てのページ",
+      "This tree": "この階層下の子ページのみ"
+    }
+  },
+
+  "share_links": {
+    "Share Link": "共有用リンク",
+    "Page Path": "ページパス",
+    "expire": "有効期限",
+    "description": "概要"
+  },
+
+  "in_app_notification": {
+    "notification_list": "アプリ内通知一覧",
+    "see_all": "通知一覧を見る",
+    "no_notification": "通知はありません",
+    "all": "全て",
+    "unopend": "未読",
+    "mark_all_as_read": "全て既読にする"
+  },
+
+  "personal_dropdown": {
+    "home": "ホーム",
+    "settings": "設定",
+    "color_mode": "カラーモード",
+    "sidebar_mode": "サイドバーモード",
+    "sidebar_mode_editor": "サイドバーモード(編集時)",
+    "use_os_settings": "OS設定を利用する"
+  },
+
+  "copy_to_clipboard": {
+    "Copy to clipboard": "クリップボードにコピー",
+    "Page path": "ページ名",
+    "Page URL": "ページURL",
+    "Permanent link": "パーマリンク",
+    "Page path and permanent link": "ページ名とパーマリンク",
+    "Markdown link": "マークダウン形式のリンク"
+  },
+
+  "crop_image_modal": {
+    "image_crop": "画像の切り抜き",
+    "crop": "トリミング",
+    "save": "保存",
+    "cancel": "キャンセル"
+  },
+
+  "handsontable_modal": {
+    "title": "テーブル編集",
+    "data_import": "データインポート",
+    "save": "保存",
+    "cancel": "キャンセル",
+    "done": "完了",
+    "data_import_form": {
+      "select_data_format": "データフォーマット",
+      "import_data": "インポートデータ",
+      "paste_table_data": "テーブルデータを貼り付け",
+      "parse_error": "パーザーエラー",
+      "cancel": "キャンセル",
+      "import": "インポート"
+    }
+  },
+
+  "not_found_page": {
+    "page_not_exist": "このページは存在しません。"
+  }
+}

+ 16 - 77
packages/app/public/static/locales/ja_JP/translation.json

@@ -28,7 +28,6 @@
   "administrator": "管理者",
   "administrator": "管理者",
   "Tag": "タグ",
   "Tag": "タグ",
   "Tags": "タグ",
   "Tags": "タグ",
-  "New": "作成",
   "Close": "閉じる",
   "Close": "閉じる",
   "Shortcuts": "ショートカット",
   "Shortcuts": "ショートカット",
   "CustomSidebar": "カスタムサイドバー",
   "CustomSidebar": "カスタムサイドバー",
@@ -109,8 +108,7 @@
   "Create under": "ページを以下に作成",
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
   "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設定",
+  "See_more_detail_on_new_schema": "詳しくは<a href='https://docs.growi.org/ja/admin-guide/upgrading/50x.html#新しい-v5-互換形式について' target='_blank'>{{title}}</a><i class='icon-share-alt'></i>を参照ください。",
   "external_account_management": "外部アカウント管理",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
   "UserGroup": "グループ",
   "Basic Settings": "基本設定",
   "Basic Settings": "基本設定",
@@ -134,7 +132,6 @@
   "edited this page": "さんがこのページを編集しました。",
   "edited this page": "さんがこのページを編集しました。",
   "List Drafts": "下書き一覧",
   "List Drafts": "下書き一覧",
   "Deleted Pages": "削除済みページ",
   "Deleted Pages": "削除済みページ",
-  "Sign out": "ログアウト",
   "Disassociate": "連携解除",
   "Disassociate": "連携解除",
   "Color mode": "カラーモード",
   "Color mode": "カラーモード",
   "Sidebar mode": "サイドバーモード",
   "Sidebar mode": "サイドバーモード",
@@ -154,31 +151,17 @@
   "not_allowed_to_see_this_page": "このページは閲覧できません",
   "not_allowed_to_see_this_page": "このページは閲覧できません",
   "Confirm": "確認",
   "Confirm": "確認",
   "Successfully requested": "正常に処理を受け付けました",
   "Successfully requested": "正常に処理を受け付けました",
-  "personal_dropdown": {
-    "home": "ホーム",
-    "settings": "設定",
-    "color_mode": "カラーモード",
-    "sidebar_mode": "サイドバーモード",
-    "sidebar_mode_editor": "サイドバーモード(編集時)",
-    "use_os_settings": "OS設定を利用する"
-  },
   "form_validation": {
   "form_validation": {
     "error_message": "いくつかの値が設定されていません",
     "error_message": "いくつかの値が設定されていません",
     "required": "%sに値を入力してください",
     "required": "%sに値を入力してください",
     "invalid_syntax": "%sの構文が不正です",
     "invalid_syntax": "%sの構文が不正です",
     "title_required": "タイトルを入力してください"
     "title_required": "タイトルを入力してください"
   },
   },
-  "not_found_page": {
-    "Create Page": "ページを作成する",
-    "page_not_exist": "このページは存在しません。",
-    "page_not_exist_alert": "このページは存在しません。新たに作成する必要があります。"
-  },
   "not_creatable_page": {
   "not_creatable_page": {
     "could_not_creata_path": "パスを作成できませんでした。"
     "could_not_creata_path": "パスを作成できませんでした。"
   },
   },
   "custom_navigation": {
   "custom_navigation": {
-    "no_page_list": "このページの配下にはページが存在しません。",
-    "link_sharing_is_disabled": "リンクのシェアは無効化されています"
+    "no_page_list": "このページの配下にはページが存在しません。"
   },
   },
   "installer": {
   "installer": {
     "setup": "セットアップ",
     "setup": "セットアップ",
@@ -234,10 +217,6 @@
     "Shere this page link to public": "外部に共有するリンクを発行する",
     "Shere this page link to public": "外部に共有するリンクを発行する",
     "share_link_list": "共有リンクリスト",
     "share_link_list": "共有リンクリスト",
     "share_link_management": "共有リンク管理",
     "share_link_management": "共有リンク管理",
-    "No_share_links":"共有リンクが存在しません",
-    "Share Link": "共有用リンク",
-    "Page Path": "ページパス",
-    "share_link_notice":"共有リンクを全て削除します",
     "delete_all_share_links":"全ての共有リンクを削除します",
     "delete_all_share_links":"全ての共有リンクを削除します",
     "expire": "有効期限",
     "expire": "有効期限",
     "Days": "日間",
     "Days": "日間",
@@ -247,30 +226,13 @@
     "Unlimited": "無期限",
     "Unlimited": "無期限",
     "Issue": "発行",
     "Issue": "発行",
     "share_settings" :"共有設定",
     "share_settings" :"共有設定",
-    "Invalid_Number_of_Date" : "有効期限の日数には整数を入力してください"
+    "Invalid_Number_of_Date" : "有効期限の日数には整数を入力してください",
+    "link_sharing_is_disabled": "リンクのシェアは無効化されています"
   },
   },
   "API Settings": "API設定",
   "API Settings": "API設定",
   "API Token Settings": "API Token設定",
   "API Token Settings": "API Token設定",
   "Current API Token": "現在のAPI Token",
   "Current API Token": "現在のAPI Token",
   "Update API Token": "API Tokenを更新",
   "Update API Token": "API Tokenを更新",
-  "header_search_box": {
-    "label": {
-      "All pages": "全てのページ",
-      "This tree": "この階層"
-    },
-    "item_label": {
-      "All pages": "全てのページ",
-      "This tree": "この階層下の子ページのみ"
-    }
-  },
-  "in_app_notification": {
-    "notification_list": "アプリ内通知一覧",
-    "see_all": "通知一覧を見る",
-    "no_notification": "通知はありません",
-    "all": "全て",
-    "unopend": "未読",
-    "mark_all_as_read": "全て既読にする"
-  },
   "in_app_notification_settings": {
   "in_app_notification_settings": {
     "in_app_notification_settings": "アプリ内通知設定",
     "in_app_notification_settings": "アプリ内通知設定",
     "subscribe_settings": "自動でページをサブスクライブする(通知を受け取る)設定",
     "subscribe_settings": "自動でページをサブスクライブする(通知を受け取る)設定",
@@ -314,14 +276,6 @@
       "no_nfd": "UTF8-MAC濁点のようなNFDの使用を禁止します。"
       "no_nfd": "UTF8-MAC濁点のようなNFDの使用を禁止します。"
     }
     }
   },
   },
-  "copy_to_clipboard": {
-    "Copy to clipboard": "クリップボードにコピー",
-    "Page path": "ページ名",
-    "Page URL": "ページURL",
-    "Permanent link": "パーマリンク",
-    "Page path and permanent link": "ページ名とパーマリンク",
-    "Markdown link": "マークダウン形式のリンク"
-  },
   "search_help": {
   "search_help": {
     "title": "検索のヘルプ",
     "title": "検索のヘルプ",
     "and": {
     "and": {
@@ -353,7 +307,7 @@
   },
   },
   "page_page": {
   "page_page": {
     "notice": {
     "notice": {
-      "version": "これは現在の版ではありません。",
+      "version": "これは最新のバージョンではありません。",
       "redirected": "リダイレクト元 >>",
       "redirected": "リダイレクト元 >>",
       "redirected_period":"",
       "redirected_period":"",
       "unlinked": "このページへのリダイレクトは削除されました。",
       "unlinked": "このページへのリダイレクトは削除されました。",
@@ -380,7 +334,8 @@
     "notfound_or_forbidden": "元のページが見つからないか、アクセス権がありません。",
     "notfound_or_forbidden": "元のページが見つからないか、アクセス権がありません。",
     "already_exists": "そのパスを持つページは既に存在しています。",
     "already_exists": "そのパスを持つページは既に存在しています。",
     "outdated": "ページが他のユーザーによって更新されました。",
     "outdated": "ページが他のユーザーによって更新されました。",
-    "user_not_admin": "権限のあるユーザーのみが削除できます"
+    "user_not_admin": "権限のあるユーザーのみが削除できます",
+    "single_deletion_empty_pages": "空ページの単体削除はできません"
   },
   },
   "page_history": {
   "page_history": {
     "revision_list": "更新履歴",
     "revision_list": "更新履歴",
@@ -514,24 +469,10 @@
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   },
   "toaster": {
   "toaster": {
-    "create_succeeded": "新しい{{target}}が作成されました",
-    "create_failed": "{{target}}の作成に失敗しました",
-    "update_successed": "{{target}}を更新しました",
-    "update_failed": "{{target}}の更新に失敗しました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
-    "initialize_successed": "{{target}}を初期化しました",
-    "give_user_admin": "{{username}}を管理者に設定しました",
-    "remove_user_admin": "{{username}}を管理者から外しました",
-    "activate_user_success": "{{username}}を有効化しました",
-    "deactivate_user_success": "{{username}}を無効化しました",
-    "remove_user_success": "{{username}}を削除しました",
-    "remove_external_user_success": "{{accountId}}を削除しました",
-    "remove_share_link_success": "{{shareLinkId}}を削除しました",
-    "issue_share_link": "共有リンクを作成しました",
-    "remove_share_link": "共有リンクを{{count}}件削除しました",
-    "switch_disable_link_sharing_success": "共有リンクの設定を変更しました",
-    "failed_to_reset_password":"パスワードのリセットに失敗しました"
+    "save_succeeded": "保存に成功しました",
+    "issue_share_link": "共有リンクを作成しました"
   },
   },
   "template": {
   "template": {
     "modal_label": {
     "modal_label": {
@@ -661,10 +602,9 @@
       "error_duplicate_pages_found": "同名のパスを持つページが複数見つかりました。リネームまたは削除してから再度実行してください"
       "error_duplicate_pages_found": "同名のパスを持つページが複数見つかりました。リネームまたは削除してから再度実行してください"
     }
     }
   },
   },
-  "to_cloud_settings": "GROWI.cloud の管理画面へ",
   "login": {
   "login": {
     "Sign in error": "ログインエラー",
     "Sign in error": "ログインエラー",
-    "Registration successful": "登録完了",
+    "Registration successful": "登録完了しました。管理者の承認をお待ちください。",
     "Setup": "セットアップ",
     "Setup": "セットアップ",
     "enabled_ldap_has_configuration_problem":"LDAPは有効ですが、設定に問題があります。",
     "enabled_ldap_has_configuration_problem":"LDAPは有効ですが、設定に問題があります。",
     "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"
     "set_env_var_for_logs": "(ログを取得するためには、環境変数 <code>DEBUG=crowi:service:PassportService</code> を設定してください。)"
@@ -696,6 +636,8 @@
     "username_should_not_be_null":"Username が null になっています 管理画面の認証機構設定にて設定の確認をしてください",
     "username_should_not_be_null":"Username が null になっています 管理画面の認証機構設定にて設定の確認をしてください",
     "email_address_is_already_registered":"このメールアドレスは既に登録されています。",
     "email_address_is_already_registered":"このメールアドレスは既に登録されています。",
     "can_not_register_maximum_number_of_users":"ユーザー数が上限を超えたため登録できません。",
     "can_not_register_maximum_number_of_users":"ユーザー数が上限を超えたため登録できません。",
+    "email_settings_is_not_setup":"E-mail 設定が完了していません。管理者に問い合わせてください。",
+    "email_authentication_is_not_enabled": "メール認証が有効になっていません。管理者に問い合わせてください。",
     "failed_to_register":"登録に失敗しました。",
     "failed_to_register":"登録に失敗しました。",
     "successfully_created":"{{username}} が作成されました。",
     "successfully_created":"{{username}} が作成されました。",
     "can_not_activate_maximum_number_of_users":"ユーザーが上限に達したためアクティベートできません。",
     "can_not_activate_maximum_number_of_users":"ユーザーが上限に達したためアクティベートできません。",
@@ -803,13 +745,6 @@
     "belonging_to_no_group": "所属しているグループが見つかりませんでした。",
     "belonging_to_no_group": "所属しているグループが見つかりませんでした。",
     "manage_user_groups": "グループ管理"
     "manage_user_groups": "グループ管理"
   },
   },
-  "crop_image_modal": {
-    "image_crop": "画像の切り抜き",
-    "crop": "トリミング",
-    "save": "保存",
-    "reset": "リセット",
-    "cancel": "キャンセル"
-  },
   "fix_page_grant": {
   "fix_page_grant": {
     "modal": {
     "modal": {
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
       "no_grant_available": "選択可能な権限のリストが見つかりませんでした。まず親ページの権限を修正したのちに再試行してください。",
@@ -857,5 +792,9 @@
   "footer": {
   "footer": {
     "bookmarks": "ブックマーク",
     "bookmarks": "ブックマーク",
     "recently_created": "最近作成したページ"
     "recently_created": "最近作成したページ"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
+    "go_to_settings": "設定する"
   }
   }
 }
 }

+ 77 - 14
packages/app/public/static/locales/zh_CN/admin.json

@@ -9,8 +9,8 @@
   "Created": "创建",
   "Created": "创建",
   "Edit": "编辑",
   "Edit": "编辑",
   "Description": "描述",
   "Description": "描述",
+  "last_login": "上次登录",
   "wiki_management_home_page": "Wiki管理首页",
   "wiki_management_home_page": "Wiki管理首页",
-  "app_settings": "系统设置",
   "public": "公共",
   "public": "公共",
   "anyone_with_the_link": "任何人",
   "anyone_with_the_link": "任何人",
   "specified_users": "仅指定用户",
   "specified_users": "仅指定用户",
@@ -25,8 +25,8 @@
     "displayed_or_hidden": "显示/隐藏",
     "displayed_or_hidden": "显示/隐藏",
     "Guest Users Access": "来宾用户访问",
     "Guest Users Access": "来宾用户访问",
 		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
 		"Fixed by env var": "这是由env var<code>%s=%s</code>修复的。",
-		"Register limitation": "注册限制",
-		"Register limitation desc": "限制新用户注册",
+		"register_limitation": "注册限制",
+		"register_limitation_desc": "限制新用户注册",
 		"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
 		"The whitelist of registration permission E-mail address": "注册许可电子邮件地址的白名单",
 		"users_without_account": "无法访问没有帐户的用户",
 		"users_without_account": "无法访问没有帐户的用户",
 		"example": "例子",
 		"example": "例子",
@@ -60,7 +60,6 @@
     "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 仅管理员 > 管理员|作者 > 何人",
     "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 仅管理员 > 管理员|作者 > 何人",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"setup_is_not_yet_complete": "安装尚未完成",
 		"setup_is_not_yet_complete": "安装尚未完成",
-		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
 		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
 		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
 		"xss_prevent_setting_link": "转到Markdown设置",
 		"xss_prevent_setting_link": "转到Markdown设置",
 		"callback_URL": "回调URL",
 		"callback_URL": "回调URL",
@@ -89,6 +88,10 @@
 			"restricted": "受限(需要管理员批准)",
 			"restricted": "受限(需要管理员批准)",
 			"closed": "已关闭(仅限邀请)"
 			"closed": "已关闭(仅限邀请)"
 		},
 		},
+    "share_link_management": "Share Link Management",
+    "No_share_links":"No share links",
+    "share_link_notice":"remove all share links",
+    "delete_all_share_links":"Delete all share links",
     "share_link_rights": "分享链接权",
     "share_link_rights": "分享链接权",
     "enable_link_sharing": "启用链接共享",
     "enable_link_sharing": "启用链接共享",
     "all_share_links": "所有共享链接",
     "all_share_links": "所有共享链接",
@@ -111,7 +114,6 @@
       "email_authentication": "用户注册时的电子邮件身份验证",
       "email_authentication": "用户注册时的电子邮件身份验证",
       "enable_email_authentication": "启用电子邮件身份验证",
       "enable_email_authentication": "启用电子邮件身份验证",
       "enable_email_authentication_desc": "用户注册将执行电子邮件身份验证。",
       "enable_email_authentication_desc": "用户注册将执行电子邮件身份验证。",
-      "please_enable_mailer": "请先设置邮件程序。",
       "need_complete_mail_setting_warning": "要使用以下功能,请完成邮件设置。"
       "need_complete_mail_setting_warning": "要使用以下功能,请完成邮件设置。"
 		},
 		},
 		"ldap": {
 		"ldap": {
@@ -235,6 +237,56 @@
 			"ABLCRule": "Rule"
 			"ABLCRule": "Rule"
 		}
 		}
   },
   },
+  "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": "启用/禁用通知",
+		"link_notification_help": "<strong>只有那些知道“链接的任何人”链接的人才能查看的页面并不总是得到通知。</strong> ",
+		"just_me_notification_help": "<strong>被“仅限我”限制的页在编辑时被通知。</strong>",
+		"group_notification_help": "<strong>被“用户组”限制的页面在编辑时被通知。</strong>",
+		"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}}",
+    "not_found_global_notification_triggerid": "未找到全局通知 ID"
+	},
   "full_text_search_management": {
   "full_text_search_management": {
     "full_text_search_management": "全文搜索管理",
     "full_text_search_management": "全文搜索管理",
 		"elasticsearch_management": "Elasticsearch管理",
 		"elasticsearch_management": "Elasticsearch管理",
@@ -279,8 +331,6 @@
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>然后提交你的问题到GitHub。</a>"
     "submit_bug_report": "<a href='https://github.com/weseek/growi/issues/new?assignees=&labels=bug&template=bug-report.md&title=Bug%3A' target='_blank' rel='noreferrer'>然后提交你的问题到GitHub。</a>"
   },
   },
   "v5_page_migration": {
   "v5_page_migration": {
-    "page_tree_not_avaliable": "Page Tree 功能不可用",
-    "go_to_settings": "进入设置,启用该功能",
     "migration_desc": "有一些页面具有旧的v4兼容性。为了利用新的功能,如页面树和容易重命名,请将您的所有页面转换为v5兼容性。",
     "migration_desc": "有一些页面具有旧的v4兼容性。为了利用新的功能,如页面树和容易重命名,请将您的所有页面转换为v5兼容性。",
     "migration_note": "注意:你将失去页面路径的唯一约束。",
     "migration_note": "注意:你将失去页面路径的唯一约束。",
     "upgrade_to_v5": "转换为v5兼容性",
     "upgrade_to_v5": "转换为v5兼容性",
@@ -310,9 +360,12 @@
     "site_name": "网站名称 ",
     "site_name": "网站名称 ",
     "sitename_change": "您可以更改用于标题和HTML标题的网站名称。",
     "sitename_change": "您可以更改用于标题和HTML标题的网站名称。",
     "header_content": "此处输入的内容将显示在标题等中。",
     "header_content": "此处输入的内容将显示在标题等中。",
-    "site_url_desc": "用于网站URL设置。",
-    "site_url_warn": "某些功能不起作用,因为未设置网站URL。",
-    "siteurl_help": "网站完整URL起始于 <code>http://</code> or <code>https://</code>.",
+    "site_url": {
+      "title": "主页URL设置",
+      "desc": "用于网站URL设置。",
+      "warn": "某些功能不起作用,因为未设置网站URL。",
+      "help": "网站完整URL起始于 <code>http://</code> or <code>https://</code>."
+    },
     "confidential_name": "内部名称",
     "confidential_name": "内部名称",
     "confidential_example": "ex):仅供内部使用",
     "confidential_example": "ex):仅供内部使用",
     "default_language": "新用户的默认语言",
     "default_language": "新用户的默认语言",
@@ -464,8 +517,6 @@
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
       "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
     },
     },
-    "code_highlight": "代码突出显示",
-    "nocdn_desc": "当强制应用环境变量<code>NO_CDN=true</code><br>Github样式时,此函数被禁用。",
     "custom_title": "自定义标题",
     "custom_title": "自定义标题",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
     "custom_title_detail": "您可以自定义<code>&lt;title&gt;</code>标记。<br><code>&123;&123;sitename&&125;&125;</code>将自动替换为应用程序名称,并且<code>&123;&123;page&&125;&125;</code>将替换为页面名称/路径。",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",
@@ -575,7 +626,7 @@
     "export": "导出",
     "export": "导出",
     "cancel": "取消",
     "cancel": "取消",
     "file": "文件",
     "file": "文件",
-    "growi_version": "Growi Version",
+    "growi_version": "GROWI Version",
     "collections": "Collections",
     "collections": "Collections",
     "exported_at": "Exported At",
     "exported_at": "Exported At",
     "export_menu": "导出菜单",
     "export_menu": "导出菜单",
@@ -834,7 +885,9 @@
     "docs_url": {
     "docs_url": {
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
       "log_type": "https://docs.growi.org/en/admin-guide/admin-cookbook/audit-log-setup.html#log-types"
     }
     }
-
+  },
+  "cloud_setting_management": {
+    "to_cloud_settings": "進入 GROWI.cloud 的管理界面"
   },
   },
   "audit_log_action_category": {
   "audit_log_action_category": {
     "Page": "页面",
     "Page": "页面",
@@ -1006,5 +1059,15 @@
     "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接",
     "ADMIN_SEARCH_CONNECTION": "重试Elasticsearch连接",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
     "ADMIN_SEARCH_INDICES_NORMALIZE": "试图重新连接Elasticsearch",
     "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
     "ADMIN_SEARCH_INDICES_REBUILD": "重建 Elasticsearch 索引"
+  },
+  "toaster": {
+    "give_user_admin": "Succeeded to give {{username}} admin",
+    "remove_user_admin": "Succeeded to remove {{username}} admin ",
+		"activate_user_success": "Succeeded to activating {{username}}",
+		"deactivate_user_success": "Succeeded to deactivate {{username}}",
+    "remove_user_success": "Succeeded to removing {{username}}",
+    "remove_external_user_success": "Succeeded to remove {{accountId}}",
+    "switch_disable_link_sharing_success": "成功更新分享链接设置",
+    "failed_to_reset_password":"Failed to reset password"
   }
   }
 }
 }

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

@@ -0,0 +1,100 @@
+{
+	"Show": "显示",
+	"Hide": "隐藏",
+  "Add": "添加",
+  "Reset": "重启",
+	"Sign out": "退出",
+  "New": "新建",
+
+  "meta": {
+    "display_name": "简体中文"
+  },
+  "toaster": {
+    "create_succeeded": "Succeeded to create {{target}}",
+    "create_failed": "Failed to create {{target}}",
+    "update_successed": "Succeeded to update {{target}}",
+    "update_failed": "Failed to update {{target}}",
+
+    "remove_share_link_success": "Succeeded to remove {{shareLinkId}}",
+    "remove_share_link": "Succeeded to remove {{count}} share links"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
+    "please_enable_mailer": "请先设置邮件程序。"
+  },
+  "headers": {
+    "app_settings": "系统设置"
+  },
+
+  "header_search_box": {
+		"label": {
+			"All pages": "所有页面",
+			"This tree": "当前分支"
+		},
+		"item_label": {
+			"All pages": "所有页面",
+			"This tree": "当前分支以下内容"
+		}
+  },
+
+  "share_links": {
+    "Share Link": "Share Link",
+    "Page Path": "Page Path",
+    "expire": "Expiration",
+    "description": "Description"
+  },
+
+  "in_app_notification": {
+    "notification_list": "应用内通知列表",
+    "see_all": "查看通知列表",
+    "no_notification": "您没有任何通知",
+    "all": "全部",
+    "unopend": "未读",
+    "mark_all_as_read" : "标记为已读"
+  },
+
+  "personal_dropdown": {
+    "home": "家",
+    "settings": "设置",
+		"color_mode": "颜色模式",
+		"sidebar_mode": "边栏模式",
+		"sidebar_mode_editor": "编辑器上的边栏模式",
+		"use_os_settings": "使用操作系统设置"
+  },
+
+	"copy_to_clipboard": {
+		"Copy to clipboard": "复制到剪贴板",
+		"Page path": "页面路径",
+		"Page URL": "页面Url",
+		"Parmanent link": "参数化链接",
+		"Page path and parmanent link": "页面路径及参数化链接",
+		"Markdown link": "Markdown链接"
+	},
+
+  "crop_image_modal": {
+    "image_crop": "图像裁剪",
+    "crop": "修剪",
+    "save": "节省",
+    "cancel": "取消"
+  },
+
+  "handsontable_modal": {
+    "title": "编辑表格",
+    "data_import": "数据导入",
+    "save": "节省",
+    "cancel": "取消",
+    "done": "完毕",
+    "data_import_form": {
+      "select_data_format": "数据格式",
+      "import_data": "导入数据",
+      "paste_table_data": "粘贴表格数据",
+      "parse_error": "解析错误",
+      "cancel": "取消",
+      "import": "导入"
+    }
+  },
+
+  "not_found_page": {
+    "page_not_exist": "该页面不存在"
+  }
+}

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

@@ -28,7 +28,6 @@
 	"Admin": "管理",
 	"Admin": "管理",
 	"administrator": "管理员",
 	"administrator": "管理员",
 	"Tags": "Tags",
 	"Tags": "Tags",
-  "New": "新建",
   "Close": "Close",
   "Close": "Close",
 	"Shortcuts": "快捷方式",
 	"Shortcuts": "快捷方式",
   "CustomSidebar": "Custom Sidebar",
   "CustomSidebar": "Custom Sidebar",
@@ -73,7 +72,6 @@
   "username": "用户名",
   "username": "用户名",
 	"Created": "创建",
 	"Created": "创建",
 	"Last updated": "上次更新",
 	"Last updated": "上次更新",
-  "last_login": "上次登录",
 	"Share": "分享",
 	"Share": "分享",
   "Share Link": "分享链接",
   "Share Link": "分享链接",
 	"Markdown Link": "Markdown链接",
 	"Markdown Link": "Markdown链接",
@@ -117,8 +115,7 @@
 	"Create under": "Create page under below:",
 	"Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
   "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设置",
+  "See_more_detail_on_new_schema": "更多详情请见<a href='https://docs.growi.org/en/admin-guide/upgrading/50x.html#about-the-new-v5-compatible-format' target='_blank'> {{title}}</a> <i class='icon-share-alt'></i> ",
 	"Markdown Settings": "Markdown设置",
 	"Markdown Settings": "Markdown设置",
 	"Notification Settings": "通知设置",
 	"Notification Settings": "通知设置",
 	"external_account_management": "外部账户管理",
 	"external_account_management": "外部账户管理",
@@ -126,7 +123,6 @@
   "ChildUserGroup": "儿童用户组",
   "ChildUserGroup": "儿童用户组",
 	"Basic Settings": "基础设置",
 	"Basic Settings": "基础设置",
 	"Basic authentication": "基本身份验证",
 	"Basic authentication": "基本身份验证",
-	"Register limitation": "注册限制",
 	"The contents entered here will be shown in the header etc": "此处输入的内容将显示在标题等中",
 	"The contents entered here will be shown in the header etc": "此处输入的内容将显示在标题等中",
 	"Public": "公共",
 	"Public": "公共",
 	"Anyone with the link": "任何人",
 	"Anyone with the link": "任何人",
@@ -147,7 +143,6 @@
 	"edited this page": "edited this page.",
 	"edited this page": "edited this page.",
 	"List Drafts": "草稿",
 	"List Drafts": "草稿",
 	"Deleted Pages": "已删除页",
 	"Deleted Pages": "已删除页",
-	"Sign out": "退出",
   "Disassociate": "解除关联",
   "Disassociate": "解除关联",
   "No bookmarks yet": "暂无书签",
   "No bookmarks yet": "暂无书签",
   "add_bookmark": "添加到书签",
   "add_bookmark": "添加到书签",
@@ -170,17 +165,11 @@
 		"invalid_syntax": "%s的语法无效。",
 		"invalid_syntax": "%s的语法无效。",
     "title_required": "标题是必需的。"
     "title_required": "标题是必需的。"
   },
   },
-  "not_found_page": {
-    "Create Page": "创建页面",
-    "page_not_exist": "该页面不存在",
-    "page_not_exist_alert": "该页面不存在,请创建一个新页面"
-  },
   "not_creatable_page": {
   "not_creatable_page": {
     "could_not_creata_path": "无法创建路径"
     "could_not_creata_path": "无法创建路径"
   },
   },
   "custom_navigation": {
   "custom_navigation": {
-    "no_page_list": "There are no pages under this page.",
-    "link_sharing_is_disabled": "链接共享已被禁用"
+    "no_page_list": "There are no pages under this page."
   },
   },
 	"installer": {
 	"installer": {
 		"setup": "安装",
 		"setup": "安装",
@@ -236,24 +225,6 @@
 	"API Token Settings": "API token 设置",
 	"API Token Settings": "API token 设置",
 	"Current API Token": "当前 API token",
 	"Current API Token": "当前 API token",
 	"Update API Token": "更新 API token",
 	"Update API Token": "更新 API token",
-	"header_search_box": {
-		"label": {
-			"All pages": "所有页面",
-			"This tree": "当前分支"
-		},
-		"item_label": {
-			"All pages": "所有页面",
-			"This tree": "当前分支以下内容"
-		}
-  },
-  "in_app_notification": {
-    "notification_list": "应用内通知列表",
-    "see_all": "查看通知列表",
-    "no_notification": "您没有任何通知",
-    "all": "全部",
-    "unopend": "未读",
-    "mark_all_as_read" : "标记为已读"
-  },
   "in_app_notification_settings": {
   "in_app_notification_settings": {
     "in_app_notification_settings": "在应用程序通知设置",
     "in_app_notification_settings": "在应用程序通知设置",
     "subscribe_settings": "自动订阅(接收通知)页面的设置",
     "subscribe_settings": "自动订阅(接收通知)页面的设置",
@@ -297,14 +268,6 @@
       "no_nfd": "禁止使用 UTF8-MAC 浊音等 NFD。"
       "no_nfd": "禁止使用 UTF8-MAC 浊音等 NFD。"
     }
     }
   },
   },
-	"copy_to_clipboard": {
-		"Copy to clipboard": "复制到剪贴板",
-		"Page path": "页面路径",
-		"Page URL": "页面Url",
-		"Parmanent link": "参数化链接",
-		"Page path and parmanent link": "页面路径及参数化链接",
-		"Markdown link": "Markdown链接"
-	},
 	"search_help": {
 	"search_help": {
 		"title": "搜索帮助",
 		"title": "搜索帮助",
 		"and": {
 		"and": {
@@ -363,7 +326,8 @@
 		"notfound_or_forbidden": "未找到或禁止原始页。",
 		"notfound_or_forbidden": "未找到或禁止原始页。",
 		"already_exists": "具有该路径的页面已存在",
 		"already_exists": "具有该路径的页面已存在",
 		"outdated": "页面已被某人更新,现在已过时。",
 		"outdated": "页面已被某人更新,现在已过时。",
-		"user_not_admin": "仅管理员用户可以删除"
+		"user_not_admin": "仅管理员用户可以删除",
+    "single_deletion_empty_pages": "空的页面不能被单一删除"
   },
   },
   "page_history": {
   "page_history": {
     "revision_list": "修订清单",
     "revision_list": "修订清单",
@@ -496,21 +460,10 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   },
 	"toaster": {
 	"toaster": {
-    "create_succeeded": "Succeeded to create {{target}}",
-    "create_failed": "Failed to create {{target}}",
-		"update_successed": "Succeeded to update {{target}}",
-    "update_failed": "Failed to update {{target}}",
     "file_upload_succeeded": "文件上传成功",
     "file_upload_succeeded": "文件上传成功",
     "file_upload_failed": "文件上传失败",
     "file_upload_failed": "文件上传失败",
-    "initialize_successed": "Succeeded to initialize {{target}}",
-		"give_user_admin": "Succeeded to give {{username}} admin",
-    "remove_user_admin": "Succeeded to remove {{username}} admin ",
-		"activate_user_success": "Succeeded to activating {{username}}",
-		"deactivate_user_success": "Succeeded to deactivate {{username}}",
-		"remove_user_success": "Succeeded to removing {{username}} ",
-    "remove_external_user_success": "Succeeded to remove {{accountId}} ",
-    "switch_disable_link_sharing_success": "成功更新分享链接设置",
-    "failed_to_reset_password":"Failed to reset password"
+    "save_succeeded": "已成功保存",
+    "issue_share_link": "Succeeded to issue new share link"
   },
   },
 	"template": {
 	"template": {
 		"modal_label": {
 		"modal_label": {
@@ -586,15 +539,10 @@
     "popover_title": "Slack Notification",
     "popover_title": "Slack Notification",
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
   },
   },
-  "security_settings": "安全设置",
   "share_links": {
   "share_links": {
     "Shere this page link to public": "Shere this page link to public",
     "Shere this page link to public": "Shere this page link to public",
     "share_link_list": "Share link list",
     "share_link_list": "Share link list",
     "share_link_management": "Share Link Management",
     "share_link_management": "Share Link Management",
-    "No_share_links":"No share links",
-    "Share Link": "Share Link",
-    "Page Path": "Page Path",
-    "share_link_notice":"remove all share links",
     "delete_all_share_links":"Delete all share links",
     "delete_all_share_links":"Delete all share links",
     "expire": "Expiration",
     "expire": "Expiration",
     "Days": "Days",
     "Days": "Days",
@@ -604,65 +552,9 @@
     "Unlimited": "unlimited",
     "Unlimited": "unlimited",
     "Issue": "Issue",
     "Issue": "Issue",
     "share_settings" :"Share settings",
     "share_settings" :"Share settings",
-    "Invalid_Number_of_Date" : "You entered invalid value"
-  },
-	"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": "启用/禁用通知",
-		"link_notification_help": "<strong>只有那些知道“链接的任何人”链接的人才能查看的页面并不总是得到通知。</strong> ",
-		"just_me_notification_help": "<strong>被“仅限我”限制的页在编辑时被通知。</strong>",
-		"group_notification_help": "<strong>被“用户组”限制的页面在编辑时被通知。</strong>",
-		"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}}"
-	},
-	"personal_dropdown": {
-		"home": "家",
-		"settings": "设置",
-		"color_mode": "颜色模式",
-		"sidebar_mode": "边栏模式",
-		"sidebar_mode_editor": "编辑器上的边栏模式",
-		"use_os_settings": "使用操作系统设置"
-	},
+    "Invalid_Number_of_Date" : "You entered invalid value",
+    "link_sharing_is_disabled": "链接共享已被禁用"
+  },
 	"search_result": {
 	"search_result": {
 		"result_meta": "搜索结果:",
 		"result_meta": "搜索结果:",
 		"deletion_mode_btn_lavel": "选择并删除页面",
 		"deletion_mode_btn_lavel": "选择并删除页面",
@@ -717,10 +609,9 @@
       "error_duplicate_pages_found": "发现多个具有相同路径名称的页面。请重新命名或删除并重试。"
       "error_duplicate_pages_found": "发现多个具有相同路径名称的页面。请重新命名或删除并重试。"
     }
     }
   },
   },
-	"to_cloud_settings": "進入 GROWI.cloud 的管理界面",
 	"login": {
 	"login": {
 		"Sign in error": "登录错误",
 		"Sign in error": "登录错误",
-		"Registration successful": "注册成功",
+		"Registration successful": "注册成功。请等待管理员批准",
 		"Setup": "安装程序",
 		"Setup": "安装程序",
     "enabled_ldap_has_configuration_problem":"启用了LDAP,但配置有问题。",
     "enabled_ldap_has_configuration_problem":"启用了LDAP,但配置有问题。",
     "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"
     "set_env_var_for_logs": "(请设置环境变量 <code>DEBUG=crowi:service:PassportService</code> 以获得日志。)"
@@ -752,6 +643,8 @@
     "username_should_not_be_null":"用户名不应为空。请检查管理页面上的身份验证机制设置",
     "username_should_not_be_null":"用户名不应为空。请检查管理页面上的身份验证机制设置",
 		"email_address_is_already_registered": "此电子邮件地址已注册。",
 		"email_address_is_already_registered": "此电子邮件地址已注册。",
 		"can_not_register_maximum_number_of_users": "注册的用户数不能超过最大值。",
 		"can_not_register_maximum_number_of_users": "注册的用户数不能超过最大值。",
+    "email_settings_is_not_setup":"邮箱设置未设置,请询问管理员。",
+    "email_authentication_is_not_enabled": "电子邮件验证未被激活, 请询问管理员。",
 		"failed_to_register": "注册失败。",
 		"failed_to_register": "注册失败。",
 		"successfully_created": "已成功创建用户{{username}。",
 		"successfully_created": "已成功创建用户{{username}。",
 		"can_not_activate_maximum_number_of_users": "无法激活超过最大用户数的用户。",
 		"can_not_activate_maximum_number_of_users": "无法激活超过最大用户数的用户。",
@@ -859,13 +752,6 @@
     "belonging_to_no_group": "无法找到你所属的团体。",
     "belonging_to_no_group": "无法找到你所属的团体。",
     "manage_user_groups": "管理用户组"
     "manage_user_groups": "管理用户组"
   },
   },
-  "crop_image_modal": {
-    "image_crop": "图像裁剪",
-    "crop": "修剪",
-    "save": "节省",
-    "reset": "重启",
-    "cancel": "取消"
-  },
   "fix_page_grant": {
   "fix_page_grant": {
     "modal": {
     "modal": {
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
       "no_grant_available": "无法找到可选择的权限列表。 请先修改父页的权限,然后再试一次。",
@@ -913,5 +799,9 @@
   "footer": {
   "footer": {
     "bookmarks": "书签",
     "bookmarks": "书签",
     "recently_created": "最近创建页面"
     "recently_created": "最近创建页面"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable": "Page Tree 功能不可用",
+    "go_to_settings": "进入设置,启用该功能"
   }
   }
 }
 }

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
packages/app/resource/locales/en_US/sandbox-diagrams.md


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

@@ -18,7 +18,7 @@ Let's increase the information exchange everyday.
 - We can create a bullet point by adding `-`  at the beginning of the line.
 - We can create a bullet point by adding `-`  at the beginning of the line.
 - We can also copy and paste, drag and drop attachments such as images, PDF, Word/Excel/PowerPoint, etc.
 - We can also copy and paste, drag and drop attachments such as images, PDF, Word/Excel/PowerPoint, etc.
 - Once we finished, press the "**Update**" button to publish the page.
 - Once we finished, press the "**Update**" button to publish the page.
-    - We can also save it by `Ctrl(⌘) +S`.
+    - We can also save it by `Ctrl(⌘) + S`.
 
 
 For more information: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
 For more information: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
 
 
@@ -29,7 +29,7 @@ For more information: [Tutorial#Create New Page](https://docs.growi.org/en/guide
   <div class="card-body">
   <div class="card-body">
     <ul>
     <ul>
       <li>Ctrl(⌘) + "/" to show quick help.</li>
       <li>Ctrl(⌘) + "/" to show quick help.</li>
-      <li>We can write HTML with <a href="https://getbootstrap.com/docs/4.5/components/">Bootstrap 4</a>.</li>
+      <li>We can write HTML with <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a>.</li>
     </ul>
     </ul>
   </div>
   </div>
 </div>
 </div>

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
packages/app/resource/locales/ja_JP/sandbox-diagrams.md


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

@@ -17,7 +17,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 - `- ` を行頭につけると、この文章のような箇条書きを書くことができます
 - `- ` を行頭につけると、この文章のような箇条書きを書くことができます
 - 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
 - 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
-    - `Ctrl(⌘) +S` でも保存できます
+    - `Ctrl(⌘) + S` でも保存できます
 
 
 さらに詳しくはこちら: [チュートリアル#新規ページ作成](https://docs.growi.org/ja/guide/tutorial/create_page.html#新規ページ作成)
 さらに詳しくはこちら: [チュートリアル#新規ページ作成](https://docs.growi.org/ja/guide/tutorial/create_page.html#新規ページ作成)
 
 
@@ -25,7 +25,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
   <div class="card-header bg-primary text-light">Tips</div>
   <div class="card-header bg-primary text-light">Tips</div>
   <div class="card-body"><ul>
   <div class="card-body"><ul>
     <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
     <li>Ctrl(⌘) + "/" でショートカットヘルプを表示します</li>
-    <li>HTML/CSS の記述には、<a href="https://getbootstrap.com/docs/4.5/components/">Bootstrap 4</a> を利用できます</li>
+    <li>HTML/CSS の記述には、<a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4</a> を利用できます</li>
   </ul></div>
   </ul></div>
 </div>
 </div>
 
 

Разница между файлами не показана из-за своего большого размера
+ 1 - 1
packages/app/resource/locales/zh_CN/sandbox-diagrams.md


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

@@ -18,7 +18,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 - 我们可以通过在行首添加`-`来创建一个要点。
 - 我们可以通过在行首添加`-`来创建一个要点。
 - 我们还可以复制和粘贴,拖放附件,如图片、PDF、Word/Excel/PowerPoint等。
 - 我们还可以复制和粘贴,拖放附件,如图片、PDF、Word/Excel/PowerPoint等。
 - 一旦我们完成了,按 "**更新**"按钮来发布页面。
 - 一旦我们完成了,按 "**更新**"按钮来发布页面。
-    - 我们也可以通过`Ctrl(⌘) +S`来保存。
+    - 我们也可以通过`Ctrl(⌘) + S`来保存。
 
 
 了解更多信息: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
 了解更多信息: [Tutorial#Create New Page](https://docs.growi.org/en/guide/tutorial/create_page.html#create-new-page)
 
 
@@ -29,7 +29,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
   <div class="card-body">
   <div class="card-body">
     <ul>
     <ul>
       <li>Ctrl(⌘) + "/" 显示快速帮助。</li>
       <li>Ctrl(⌘) + "/" 显示快速帮助。</li>
-      <li>你可以用 <a href="https://getbootstrap.com/docs/4.5/components/">Bootstrap 4编写HTML</a>.</li>
+      <li>你可以用 <a href="https://getbootstrap.com/docs/4.6/components/">Bootstrap 4编写HTML</a>.</li>
     </ul>
     </ul>
   </div>
   </div>
 </div>
 </div>

+ 27 - 0
packages/app/src/client/interfaces/global-notification.ts

@@ -0,0 +1,27 @@
+export const NotifyType = {
+  Email: 'mail',
+  SLACK: 'slack',
+} as const;
+
+export type NotifyType = typeof NotifyType[keyof typeof NotifyType]
+
+
+export const TriggerEventType = {
+  CREATE: 'pageCreate',
+  EDIT: 'pageEdit',
+  MOVE: 'pageMove',
+  DELETE: 'pageDelete',
+  LIKE: 'pageLike',
+  POST: 'comment',
+} as const;
+
+type TriggerEventType = typeof TriggerEventType[keyof typeof TriggerEventType]
+
+
+export type IGlobalNotification = {
+  triggerPath: string,
+  notifyType: NotifyType,
+  emailToSend: string,
+  slackChannelToSend: string,
+  triggerEvents: TriggerEventType[],
+};

+ 8 - 0
packages/app/src/client/interfaces/notification.ts

@@ -0,0 +1,8 @@
+import type { NotifyType } from './global-notification';
+
+export type INotificationType = {
+  __t?: NotifyType
+  _id: string
+  // TOOD: Define the provider type
+  provider?: any
+}

+ 0 - 84
packages/app/src/client/services/AdminCustomizeContainer.js

@@ -25,7 +25,6 @@ export default class AdminCustomizeContainer extends Container {
     this.state = {
     this.state = {
       retrieveError: null,
       retrieveError: null,
       isEnabledTimeline: false,
       isEnabledTimeline: false,
-      isSavedStatesOfTabChanges: false,
       isEnabledAttachTitleHeader: false,
       isEnabledAttachTitleHeader: false,
 
 
       pageLimitationS: null,
       pageLimitationS: null,
@@ -36,26 +35,10 @@ export default class AdminCustomizeContainer extends Container {
       isEnabledStaleNotification: false,
       isEnabledStaleNotification: false,
       isAllReplyShown: false,
       isAllReplyShown: false,
       isSearchScopeChildrenAsDefault: false,
       isSearchScopeChildrenAsDefault: false,
-      currentHighlightJsStyleId: '',
-      isHighlightJsStyleBorderEnabled: false,
       currentCustomizeTitle: '',
       currentCustomizeTitle: '',
       currentCustomizeHeader: '',
       currentCustomizeHeader: '',
       currentCustomizeCss: '',
       currentCustomizeCss: '',
       currentCustomizeScript: '',
       currentCustomizeScript: '',
-      /* eslint-disable quote-props, no-multi-spaces */
-      highlightJsCssSelectorOptions: {
-        'github':           { name: '[Light] GitHub',         border: false },
-        'github-gist':      { name: '[Light] GitHub Gist',    border: true },
-        'atom-one-light':   { name: '[Light] Atom One Light', border: true },
-        'xcode':            { name: '[Light] Xcode',          border: true },
-        'vs':               { name: '[Light] Vs',             border: true },
-        'atom-one-dark':    { name: '[Dark] Atom One Dark',   border: false },
-        'hybrid':           { name: '[Dark] Hybrid',          border: false },
-        'monokai':          { name: '[Dark] Monokai',         border: false },
-        'tomorrow-night':   { name: '[Dark] Tomorrow Night',  border: false },
-        'vs2015':           { name: '[Dark] Vs 2015',         border: false },
-      },
-      /* eslint-enable quote-props, no-multi-spaces */
     };
     };
     this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
     this.switchPageListLimitationS = this.switchPageListLimitationS.bind(this);
     this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
     this.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
@@ -81,7 +64,6 @@ export default class AdminCustomizeContainer extends Container {
 
 
       this.setState({
       this.setState({
         isEnabledTimeline: customizeParams.isEnabledTimeline,
         isEnabledTimeline: customizeParams.isEnabledTimeline,
-        isSavedStatesOfTabChanges: customizeParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: customizeParams.isEnabledAttachTitleHeader,
         pageLimitationS: customizeParams.pageLimitationS,
         pageLimitationS: customizeParams.pageLimitationS,
         pageLimitationM: customizeParams.pageLimitationM,
         pageLimitationM: customizeParams.pageLimitationM,
@@ -90,16 +72,11 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
-        currentHighlightJsStyleId: customizeParams.styleName,
-        isHighlightJsStyleBorderEnabled: customizeParams.styleBorder,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeHeader: customizeParams.customizeHeader,
         currentCustomizeHeader: customizeParams.customizeHeader,
         currentCustomizeCss: customizeParams.customizeCss,
         currentCustomizeCss: customizeParams.customizeCss,
         currentCustomizeScript: customizeParams.customizeScript,
         currentCustomizeScript: customizeParams.customizeScript,
       });
       });
-
-      // search style name from object for display
-      this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[customizeParams.styleName].name });
     }
     }
     catch (err) {
     catch (err) {
       this.setState({ retrieveError: err });
       this.setState({ retrieveError: err });
@@ -116,13 +93,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ isEnabledTimeline:  !this.state.isEnabledTimeline });
     this.setState({ isEnabledTimeline:  !this.state.isEnabledTimeline });
   }
   }
 
 
-  /**
-   * Switch savedStatesOfTabChanges
-   */
-  switchSavedStatesOfTabChanges() {
-    this.setState({ isSavedStatesOfTabChanges:  !this.state.isSavedStatesOfTabChanges });
-  }
-
   /**
   /**
    * Switch enabledAttachTitleHeader
    * Switch enabledAttachTitleHeader
    */
    */
@@ -180,25 +150,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
     this.setState({ isSearchScopeChildrenAsDefault: !this.state.isSearchScopeChildrenAsDefault });
   }
   }
 
 
-  /**
-   * Switch highlightJsStyle
-   */
-  switchHighlightJsStyle(styleId, styleName, isBorderEnable) {
-    this.setState({ currentHighlightJsStyleId: styleId });
-    this.setState({ currentHighlightJsStyleName: styleName });
-    // recommended settings are applied
-    this.setState({ isHighlightJsStyleBorderEnabled: isBorderEnable });
-
-    this.previewHighlightJsStyle(styleId);
-  }
-
-  /**
-   * Switch highlightJsStyleBorder
-   */
-  switchHighlightJsStyleBorder() {
-    this.setState({ isHighlightJsStyleBorderEnabled: !this.state.isHighlightJsStyleBorderEnabled });
-  }
-
   /**
   /**
    * Change customize Title
    * Change customize Title
    */
    */
@@ -227,17 +178,6 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentCustomizeScript: inpuValue });
     this.setState({ currentCustomizeScript: inpuValue });
   }
   }
 
 
-  /**
-   * Preview hljs style
-   * @param {string} styleId
-   */
-  previewHighlightJsStyle(styleId) {
-    const styleLInk = document.querySelectorAll('#grw-hljs-container-for-demo link')[0];
-    // replace css url
-    // see https://regex101.com/r/gBNZYu/4
-    styleLInk.href = styleLInk.href.replace(/[^/]+\.css$/, `${styleId}.css`);
-  }
-
 
 
   /**
   /**
    * Update function
    * Update function
@@ -247,7 +187,6 @@ export default class AdminCustomizeContainer extends Container {
     try {
     try {
       const response = await apiv3Put('/customize-setting/function', {
       const response = await apiv3Put('/customize-setting/function', {
         isEnabledTimeline: this.state.isEnabledTimeline,
         isEnabledTimeline: this.state.isEnabledTimeline,
-        isSavedStatesOfTabChanges: this.state.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: this.state.isEnabledAttachTitleHeader,
         pageLimitationS: this.state.pageLimitationS,
         pageLimitationS: this.state.pageLimitationS,
         pageLimitationM: this.state.pageLimitationM,
         pageLimitationM: this.state.pageLimitationM,
@@ -260,7 +199,6 @@ export default class AdminCustomizeContainer extends Container {
       const { customizedParams } = response.data;
       const { customizedParams } = response.data;
       this.setState({
       this.setState({
         isEnabledTimeline: customizedParams.isEnabledTimeline,
         isEnabledTimeline: customizedParams.isEnabledTimeline,
-        isSavedStatesOfTabChanges: customizedParams.isSavedStatesOfTabChanges,
         isEnabledAttachTitleHeader: customizedParams.isEnabledAttachTitleHeader,
         isEnabledAttachTitleHeader: customizedParams.isEnabledAttachTitleHeader,
         pageLimitationS: customizedParams.pageLimitationS,
         pageLimitationS: customizedParams.pageLimitationS,
         pageLimitationM: customizedParams.pageLimitationM,
         pageLimitationM: customizedParams.pageLimitationM,
@@ -277,28 +215,6 @@ export default class AdminCustomizeContainer extends Container {
     }
     }
   }
   }
 
 
-  /**
-   * Update code highlight
-   * @memberOf AdminCustomizeContainer
-   */
-  async updateHighlightJsStyle() {
-    try {
-      const response = await apiv3Put('/customize-setting/highlight', {
-        highlightJsStyle: this.state.currentHighlightJsStyleId,
-        highlightJsStyleBorder: this.state.isHighlightJsStyleBorderEnabled,
-      });
-      const { customizedParams } = response.data;
-      this.setState({
-        highlightJsStyle: customizedParams.highlightJsStyle,
-        highlightJsStyleBorder: customizedParams.highlightJsStyleBorder,
-      });
-    }
-    catch (err) {
-      logger.error(err);
-      throw new Error('Failed to update data');
-    }
-  }
-
   /**
   /**
    * Update customTitle
    * Update customTitle
    * @memberOf AdminCustomizeContainer
    * @memberOf AdminCustomizeContainer

+ 12 - 2
packages/app/src/client/services/activate-plugin.ts

@@ -1,12 +1,22 @@
 import { readFileSync } from 'fs';
 import { readFileSync } from 'fs';
 import path from 'path';
 import path from 'path';
 
 
-import { CustomWindow } from '~/interfaces/global';
 import { GrowiPlugin } from '~/interfaces/plugin';
 import { GrowiPlugin } from '~/interfaces/plugin';
 import { initializeGrowiFacade } from '~/utils/growi-facade';
 import { initializeGrowiFacade } from '~/utils/growi-facade';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 import { resolveFromRoot } from '~/utils/project-dir-utils';
 
 
 
 
+declare global {
+  // eslint-disable-next-line vars-on-top, no-var
+  var pluginActivators: {
+    [key: string]: {
+      activate: () => void,
+      deactivate: () => void,
+    },
+  };
+}
+
+
 export type GrowiPluginManifestEntries = [growiPlugin: GrowiPlugin, manifest: any][];
 export type GrowiPluginManifestEntries = [growiPlugin: GrowiPlugin, manifest: any][];
 
 
 
 
@@ -27,7 +37,7 @@ export class ActivatePluginService {
   static activateAll(): void {
   static activateAll(): void {
     initializeGrowiFacade();
     initializeGrowiFacade();
 
 
-    const { pluginActivators } = window as CustomWindow;
+    const { pluginActivators } = window;
 
 
     if (pluginActivators == null) {
     if (pluginActivators == null) {
       return;
       return;

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

@@ -1,10 +1,10 @@
 import { SubscriptionStatusType, Nullable } from '@growi/core';
 import { SubscriptionStatusType, Nullable } from '@growi/core';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
-import { OptionsToSave } from '~/interfaces/editor-settings';
+import { OptionsToSave } from '~/interfaces/page-operation';
+import { useIsEnabledUnsavedWarning } from '~/stores/editor';
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
 
 
-
 import { toastError } from '../util/apiNotification';
 import { toastError } from '../util/apiNotification';
 import { apiPost } from '../util/apiv1-client';
 import { apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
@@ -42,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> => {
 export const updateContentWidth = async(pageId: string, newValue: boolean): Promise<void> => {
   try {
   try {
     await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
     await apiv3Put(`/page/${pageId}/content-width`, { expandContentWidth: newValue });
-    updateBodyClassByView(newValue);
   }
   }
   catch (err) {
   catch (err) {
     toastError(err);
     toastError(err);
@@ -133,41 +119,55 @@ type PageInfo= {
   revisionId: Nullable<string>,
   revisionId: Nullable<string>,
 }
 }
 
 
+type SaveOrUpdateFunction = (markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) => any;
+
 // TODO: define return type
 // 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);
+export const useSaveOrUpdate = (): SaveOrUpdateFunction => {
+  /* eslint-disable react-hooks/rules-of-hooks */
+  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  /* eslint-enable react-hooks/rules-of-hooks */
+
+  return async function(markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) {
+    const { path, pageId, revisionId } = pageInfo;
+
+    const options: OptionsToSave = 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();
+    // }
+
+    const isNoRevisionPage = pageId != null && revisionId == null;
+
+    let res;
+    if (pageId == null || isNoRevisionPage) {
+      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);
     }
     }
-    res = await updatePage(pageId, revisionId, markdown, options);
-  }
 
 
-  return res;
+    // The updateFn should be a promise or asynchronous function to handle the remote mutation
+    // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
+    // Moreover, `async() => false` does not work since it's too fast to be calculated.
+    await mutateIsEnabledUnsavedWarning(new Promise(r => setTimeout(() => r(false), 10)), { optimisticData: () => false });
+
+    return res;
+  };
 };
 };

+ 0 - 21
packages/app/src/client/util/editor.ts

@@ -1,21 +0,0 @@
-import { OptionsToSave } from '~/interfaces/editor-settings';
-
-export const getOptionsToSave = (
-    isSlackEnabled: boolean,
-    slackChannels: string,
-    grant: number,
-    grantUserGroupId: string | null | undefined,
-    grantUserGroupName: string | null | undefined,
-    pageTags: string[],
-    isSyncRevisionToHackmd?: boolean,
-): OptionsToSave => {
-  return {
-    pageTags,
-    isSlackEnabled,
-    slackChannels,
-    grant,
-    grantUserGroupId,
-    grantUserGroupName,
-    isSyncRevisionToHackmd,
-  };
-};

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

@@ -1,46 +1,5 @@
-const WIKI_HEADER_LINK = 120;
-
-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 = element.getBoundingClientRect().top;
-
-  const top = window.pageYOffset + rectTop - offsetTop;
-
-  scrollElement.scrollTo({
-    top,
-    behavior: 'smooth',
-  });
-};
-
-export type SmoothScrollEventCallback = (elem: HTMLElement) => void;
-
-export const addSmoothScrollEvent = (elements: HTMLAnchorElement[], callback?: SmoothScrollEventCallback): void => {
-  elements.forEach((link) => {
-    const href = link.getAttribute('href');
-
-    if (href == null) {
-      return;
-    }
-
-    link.addEventListener('click', (e) => {
-      e.preventDefault();
-
-      // modify location.hash without scroll
-      window.history.pushState({}, '', link.href);
-
-      // smooth scroll
-      const elemId = href.replace('#', '');
-      const targetDom = document.getElementById(elemId);
-      if (targetDom != null) {
-        smoothScrollIntoView(targetDom, WIKI_HEADER_LINK);
-
-        if (callback != null) {
-          callback(targetDom);
-        }
-      }
-    });
-  });
+// option object for react-scroll
+export const DEFAULT_AUTO_SCROLL_OPTS = {
+  smooth: 'easeOutQuint',
+  duration: 1200,
 };
 };

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

@@ -18,12 +18,12 @@ const logger = loggerFactory('growi:appSettings');
 
 
 const AppSetting = (props) => {
 const AppSetting = (props) => {
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
-  const { t } = useTranslation('admin');
+  const { t } = useTranslation(['admin', 'commons']);
 
 
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
       await adminAppContainer.updateAppSettingHandler();
       await adminAppContainer.updateAppSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('app_settings') }));
+      toastSuccess(t('commons:toaster.update_successed', { target: t('commons:headers.app_settings') }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -120,7 +120,7 @@ const AppSetting = (props) => {
               checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
               checked={adminAppContainer.state.isEmailPublishedForNewUser === true}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(true) }}
             />
             />
-            <label className="custom-control-label" htmlFor="radio-email-show">{t('Show')}</label>
+            <label className="custom-control-label" htmlFor="radio-email-show">{t('commons:Show')}</label>
           </div>
           </div>
 
 
           <div className="custom-control custom-radio custom-control-inline">
           <div className="custom-control custom-radio custom-control-inline">
@@ -132,7 +132,7 @@ const AppSetting = (props) => {
               checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
               checked={adminAppContainer.state.isEmailPublishedForNewUser === false}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
               onChange={() => { adminAppContainer.changeIsEmailPublishedForNewUserShow(false) }}
             />
             />
-            <label className="custom-control-label" htmlFor="radio-email-hide">{t('Hide')}</label>
+            <label className="custom-control-label" htmlFor="radio-email-hide">{t('commons:Hide')}</label>
           </div>
           </div>
 
 
         </div>
         </div>

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

@@ -82,14 +82,14 @@ const AppSettingsPageContents = (props: Props) => {
 
 
       <div className="row">
       <div className="row">
         <div className="col-lg-12">
         <div className="col-lg-12">
-          <h2 className="admin-setting-header">{t('app_settings')}</h2>
+          <h2 className="admin-setting-header">{t('headers.app_settings', { ns: 'commons' })}</h2>
           <AppSetting />
           <AppSetting />
         </div>
         </div>
       </div>
       </div>
 
 
       <div className="row mt-5">
       <div className="row mt-5">
         <div className="col-lg-12">
         <div className="col-lg-12">
-          <h2 className="admin-setting-header">{t('Site URL settings')}</h2>
+          <h2 className="admin-setting-header">{t('app_setting.site_url.title')}</h2>
           <SiteUrlSetting />
           <SiteUrlSetting />
         </div>
         </div>
       </div>
       </div>

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

@@ -18,7 +18,7 @@ type Props = {
 
 
 
 
 const FileUploadSetting = (props: Props) => {
 const FileUploadSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['admin', 'commons']);
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
   const { fileUploadType } = adminAppContainer.state;
   const { fileUploadType } = adminAppContainer.state;
   const fileUploadTypes = ['aws', 'gcs', 'gridfs', 'local'];
   const fileUploadTypes = ['aws', 'gcs', 'gridfs', 'local'];
@@ -26,7 +26,7 @@ const FileUploadSetting = (props: Props) => {
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
       await adminAppContainer.updateFileUploadSettingHandler();
       await adminAppContainer.updateFileUploadSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.file_upload_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.file_upload_settings'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);

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

@@ -17,7 +17,7 @@ type Props = {
 
 
 
 
 const MailSetting = (props: Props) => {
 const MailSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['admin', 'commons']);
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
 
 
   const transmissionMethods = ['smtp', 'ses'];
   const transmissionMethods = ['smtp', 'ses'];
@@ -25,7 +25,7 @@ const MailSetting = (props: Props) => {
   async function submitHandler() {
   async function submitHandler() {
     try {
     try {
       await adminAppContainer.updateMailSettingHandler();
       await adminAppContainer.updateMailSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.ses_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.ses_settings'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);

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

@@ -23,7 +23,7 @@ const PluginSetting = (props: Props) => {
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
       await adminAppContainer.updatePluginSettingHandler();
       await adminAppContainer.updatePluginSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.plugin_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:app_setting.plugin_settings'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);

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

@@ -17,14 +17,14 @@ type Props = {
 }
 }
 
 
 const SiteUrlSetting = (props: Props) => {
 const SiteUrlSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation('admin', { keyPrefix: 'app_setting' });
   const { adminAppContainer } = props;
   const { adminAppContainer } = props;
 
 
 
 
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
       await adminAppContainer.updateSiteUrlSettingHandler();
       await adminAppContainer.updateSiteUrlSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('Site URL settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('site_url.title') }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -34,9 +34,9 @@ const SiteUrlSetting = (props: Props) => {
 
 
   return (
   return (
     <React.Fragment>
     <React.Fragment>
-      <p className="card well">{t('admin:app_setting.site_url_desc')}</p>
+      <p className="card well">{t('site_url.desc')}</p>
       {!adminAppContainer.state.isSetSiteUrl
       {!adminAppContainer.state.isSetSiteUrl
-          && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('admin:app_setting.site_url_warn')}</p>)}
+          && (<p className="alert alert-danger"><i className="icon-exclamation"></i> {t('site_url.warn')}</p>)}
 
 
       <div className="row form-group">
       <div className="row form-group">
         <div className="col-md-9 offset-md-3">
         <div className="col-md-9 offset-md-3">
@@ -64,14 +64,14 @@ const SiteUrlSetting = (props: Props) => {
                   />
                   />
                   <p className="form-text text-muted">
                   <p className="form-text text-muted">
                     {/* eslint-disable-next-line react/no-danger */}
                     {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.siteurl_help') }} />
+                    <span dangerouslySetInnerHTML={{ __html: t('site_url.help') }} />
                   </p>
                   </p>
                 </td>
                 </td>
                 <td>
                 <td>
                   <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
                   <input className="form-control" type="text" value={adminAppContainer.state.envSiteUrl || ''} readOnly />
                   <p className="form-text text-muted">
                   <p className="form-text text-muted">
                     {/* eslint-disable-next-line react/no-danger */}
                     {/* eslint-disable-next-line react/no-danger */}
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:app_setting.use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
+                    <span dangerouslySetInnerHTML={{ __html: t('use_env_var_if_empty', { variable: 'APP_SITE_URL' }) }} />
                   </p>
                   </p>
                 </td>
                 </td>
               </tr>
               </tr>

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

@@ -47,7 +47,7 @@ export const ActivityTable : FC<Props> = (props: Props) => {
                 <td>
                 <td>
                   { activity.user != null && (
                   { activity.user != null && (
                     <>
                     <>
-                      <UserPicture user={activity.user} className="picture rounded-circle" />
+                      <UserPicture user={activity.user} />
                       <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
                       <a className="ml-2" href={pagePathUtils.userPageRoot(activity.user)}>{activity.snapshot?.username}</a>
                     </>
                     </>
                   )}
                   )}

+ 26 - 28
packages/app/src/components/Admin/Common/AdminNavigation.jsx

@@ -6,7 +6,7 @@ import Link from 'next/link';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 import urljoin from 'url-join';
 import urljoin from 'url-join';
 
 
-
+import { useGrowiCloudUri, useGrowiAppIdForGrowiCloud } from '../../../stores/context';
 // import AppContainer from '~/client/services/AppContainer';
 // import AppContainer from '~/client/services/AppContainer';
 
 
 // import { withUnstatedContainers } from '../../UnstatedUtils';
 // import { withUnstatedContainers } from '../../UnstatedUtils';
@@ -16,32 +16,30 @@ const AdminNavigation = (props) => {
   // const { appContainer } = props;
   // const { appContainer } = props;
   const pathname = window.location.pathname;
   const pathname = window.location.pathname;
 
 
-  // const growiCloudUri = appContainer.config.env.GROWI_CLOUD_URI;
-  // const growiAppIdForGrowiCloud = appContainer.config.env.GROWI_APP_ID_FOR_GROWI_CLOUD;
+  const { data: growiCloudUri } = useGrowiCloudUri();
+  const { data: growiAppIdForGrowiCloud } = useGrowiAppIdForGrowiCloud();
 
 
   // eslint-disable-next-line react/prop-types
   // eslint-disable-next-line react/prop-types
   const MenuLabel = ({ menu }) => {
   const MenuLabel = ({ menu }) => {
     switch (menu) {
     switch (menu) {
-      /* eslint-disable no-multi-spaces */
-      case 'app':                      return <><i className="icon-fw icon-settings"></i>        { t('app_settings') }</>;
-      case 'security':                 return <><i className="icon-fw icon-shield"></i>          { t('security_settings.security_settings') }</>;
-      case 'markdown':                 return <><i className="icon-fw icon-note"></i>            { t('markdown_settings.markdown_settings') }</>;
-      case 'customize':                return <><i className="icon-fw icon-wrench"></i>          { t('customize_settings.customize_settings') }</>;
-      case 'importer':                 return <><i className="icon-fw icon-cloud-upload"></i>    { t('importer_management.import_data') }</>;
-      case 'export':                   return <><i className="icon-fw icon-cloud-download"></i>  { t('export_archive_data') }</>;
-      case 'notification':             return <><i className="icon-fw icon-bell"></i>            { t('external_notification.external_notification')}</>;
-      case 'slack-integration':        return <><i className="icon-fw icon-shuffle"></i>         { t('slack_integration.slack_integration') }</>;
-      case 'slack-integration-legacy': return <><i className="icon-fw icon-shuffle"></i>         { t('slack_integration_legacy.slack_integration_legacy')}</>;
-      case 'users':                    return <><i className="icon-fw icon-user"></i>            { t('user_management.user_management') }</>;
-      case 'user-groups':              return <><i className="icon-fw icon-people"></i>          { t('user_group_management.user_group_management') }</>;
-      case 'search':                   return <><i className="icon-fw icon-magnifier"></i>
-        { t('full_text_search_management.full_text_search_management') }</>;
-      // TODO: Consider where to place the "AuditLog"
-      case 'audit-log':                return <><i className="icon-fw icon-feed"></i>            { t('audit_log_management.audit_log')}</>;
-      case 'plugins-extention':        return <><i className="icon-fw fa fa-plug"></i>           Plugins Extention </>;
-      case 'cloud':                    return <><i className="icon-fw icon-share-alt"></i>       { t('to_cloud_settings')} </>;
-      default:                         return <><i className="icon-fw icon-home"></i>            { t('wiki_management_home_page') }</>;
-      /* eslint-enable no-multi-spaces */
+      /* eslint-disable no-multi-spaces, max-len */
+      case 'app':                      return <><i className="mr-1 icon-fw icon-settings"></i>{        t('headers.app_settings', { ns: 'commons' }) }</>;
+      case 'security':                 return <><i className="mr-1 icon-fw icon-shield"></i>{          t('security_settings.security_settings') }</>;
+      case 'markdown':                 return <><i className="mr-1 icon-fw icon-note"></i>{            t('markdown_settings.markdown_settings') }</>;
+      case 'customize':                return <><i className="mr-1 icon-fw icon-wrench"></i>{          t('customize_settings.customize_settings') }</>;
+      case 'importer':                 return <><i className="mr-1 icon-fw icon-cloud-upload"></i>{    t('importer_management.import_data') }</>;
+      case 'export':                   return <><i className="mr-1 icon-fw icon-cloud-download"></i>{  t('export_management.export_archive_data') }</>;
+      case 'notification':             return <><i className="mr-1 icon-fw icon-bell"></i>{            t('external_notification.external_notification')}</>;
+      case 'slack-integration':        return <><i className="mr-1 icon-fw icon-shuffle"></i>{         t('slack_integration.slack_integration') }</>;
+      case 'slack-integration-legacy': return <><i className="mr-1 icon-fw icon-shuffle"></i>{         t('slack_integration_legacy.slack_integration_legacy')}</>;
+      case 'users':                    return <><i className="mr-1 icon-fw icon-user"></i>{            t('user_management.user_management') }</>;
+      case 'user-groups':              return <><i className="mr-1 icon-fw icon-people"></i>{          t('user_group_management.user_group_management') }</>;
+      case 'search':                   return <><i className="mr-1 icon-fw icon-magnifier"></i>{       t('full_text_search_management.full_text_search_management') }</>;
+      case 'audit-log':                return <><i className="mr-1 icon-fw icon-feed"></i>{            t('audit_log_management.audit_log')}</>;
+      case 'plugins':                  return <><i className="mr-1 icon-fw icon-puzzle"></i>{           'Plugins Extention'}</>;
+      case 'cloud':                    return <><i className="mr-1 icon-fw icon-share-alt"></i>{       t('cloud_setting_management.to_cloud_settings')} </>;
+      default:                         return <><i className="mr-1 icon-fw icon-home"></i>{            t('wiki_management_home_page') }</>;
+      /* eslint-enable no-multi-spaces, max-len */
     }
     }
   };
   };
 
 
@@ -93,10 +91,10 @@ const AdminNavigation = (props) => {
         <MenuLink menu="slack-integration-legacy" isListGroupItems isActive={isActiveMenu('/slack-integration-legacy')} />
         <MenuLink menu="slack-integration-legacy" isListGroupItems isActive={isActiveMenu('/slack-integration-legacy')} />
         <MenuLink menu="users"        isListGroupItems isActive={isActiveMenu('/users')} />
         <MenuLink menu="users"        isListGroupItems isActive={isActiveMenu('/users')} />
         <MenuLink menu="user-groups"  isListGroupItems isActive={isActiveMenu('/user-groups')} />
         <MenuLink menu="user-groups"  isListGroupItems isActive={isActiveMenu('/user-groups')} />
-        <MenuLink menu="search"       isListGroupItems isActive={isActiveMenu('/search')} />
         <MenuLink menu="audit-log"    isListGroupItems isActive={isActiveMenu('/audit-log')} />
         <MenuLink menu="audit-log"    isListGroupItems isActive={isActiveMenu('/audit-log')} />
-        <MenuLink menu="plugins-extention"    isListGroupItems isActive={isActiveMenu('/plugins-extention')} />
-        {/* {growiCloudUri != null && growiAppIdForGrowiCloud != null
+        <MenuLink menu="plugins"      isListGroupItems isActive={isActiveMenu('/plugins')} />
+        <MenuLink menu="search"       isListGroupItems isActive={isActiveMenu('/search')} />
+        {growiCloudUri != null && growiAppIdForGrowiCloud != null
           && (
           && (
             <a
             <a
               href={`${growiCloudUri}/my/apps/${growiAppIdForGrowiCloud}`}
               href={`${growiCloudUri}/my/apps/${growiAppIdForGrowiCloud}`}
@@ -105,7 +103,7 @@ const AdminNavigation = (props) => {
               <MenuLabel menu="cloud" />
               <MenuLabel menu="cloud" />
             </a>
             </a>
           )
           )
-        } */}
+        }
         {/* eslint-enable no-multi-spaces */}
         {/* eslint-enable no-multi-spaces */}
       </>
       </>
     );
     );
@@ -144,7 +142,7 @@ const AdminNavigation = (props) => {
             {isActiveMenu('/user-groups') &&       <MenuLabel menu="user-groups" />}
             {isActiveMenu('/user-groups') &&       <MenuLabel menu="user-groups" />}
             {isActiveMenu('/search') &&            <MenuLabel menu="search" />}
             {isActiveMenu('/search') &&            <MenuLabel menu="search" />}
             {isActiveMenu('/audit-log') &&         <MenuLabel menu="audit-log" />}
             {isActiveMenu('/audit-log') &&         <MenuLabel menu="audit-log" />}
-            {isActiveMenu('/plugins-extention') && <MenuLabel menu="plugins-extention" />}
+            {isActiveMenu('/plugins') &&           <MenuLabel menu="plugins" />}
             {/* eslint-enable no-multi-spaces */}
             {/* eslint-enable no-multi-spaces */}
           </span>
           </span>
         </button>
         </button>

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

@@ -13,13 +13,12 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeHeaderSetting from './CustomizeHeaderSetting';
 import CustomizeHeaderSetting from './CustomizeHeaderSetting';
-import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeSidebarSetting from './CustomizeSidebarSetting';
 import CustomizeSidebarSetting from './CustomizeSidebarSetting';
 import CustomizeThemeSetting from './CustomizeThemeSetting';
 import CustomizeThemeSetting from './CustomizeThemeSetting';
-import CustomizeTitle from './CustomizeTitle';
+import { CustomizeTitle } from './CustomizeTitle';
 
 
 const logger = loggerFactory('growi:services:AdminCustomizePage');
 const logger = loggerFactory('growi:services:AdminCustomizePage');
 
 
@@ -56,9 +55,6 @@ function Customize(props) {
       <div className="mb-5">
       <div className="mb-5">
         <CustomizeFunctionSetting />
         <CustomizeFunctionSetting />
       </div>
       </div>
-      <div className="mb-5">
-        <CustomizeHighlightSetting />
-      </div>
       <div className="mb-5">
       <div className="mb-5">
         <CustomizeTitle />
         <CustomizeTitle />
       </div>
       </div>

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

@@ -21,7 +21,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {
       await adminCustomizeContainer.updateCustomizeCss();
       await adminCustomizeContainer.updateCustomizeCss();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.custom_css') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_css'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -32,12 +32,12 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
     <React.Fragment>
     <React.Fragment>
       <div className="row">
       <div className="row">
         <div className="col-12">
         <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_setting.custom_css')}</h2>
+          <h2 className="admin-setting-header">{t('admin:customize_settings.custom_css')}</h2>
 
 
           <Card className="card well my-3">
           <Card className="card well my-3">
             <CardBody className="px-0 py-2">
             <CardBody className="px-0 py-2">
-              { t('admin:customize_setting.write_css') }<br />
-              { t('admin:customize_setting.reflect_change') }
+              { t('admin:customize_settings.write_css') }<br />
+              { t('admin:customize_settings.reflect_change') }
             </CardBody>
             </CardBody>
           </Card>
           </Card>
 
 
@@ -50,7 +50,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
             />
             />
             <p className="form-text text-muted text-right">
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
+              {t('admin:customize_settings.ctrl_space')}
             </p>
             </p>
           </div>
           </div>
 
 

+ 19 - 34
packages/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx

@@ -25,7 +25,7 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
 
 
     try {
     try {
       await adminCustomizeContainer.updateCustomizeFunction();
       await adminCustomizeContainer.updateCustomizeFunction();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.function') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.function'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -36,68 +36,53 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
     <React.Fragment>
     <React.Fragment>
       <div className="row">
       <div className="row">
         <div className="col-12">
         <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_setting.function')}</h2>
+          <h2 className="admin-setting-header">{t('admin:customize_settings.function')}</h2>
           <Card className="card well my-3">
           <Card className="card well my-3">
             <CardBody className="px-0 py-2">
             <CardBody className="px-0 py-2">
-              {t('admin:customize_setting.function_desc')}
+              {t('admin:customize_settings.function_desc')}
             </CardBody>
             </CardBody>
           </Card>
           </Card>
 
 
 
 
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <CustomizeFunctionOption
-                optionId="isSavedStatesOfTabChanges"
-                label={t('admin:customize_setting.function_options.tab_switch')}
-                isChecked={adminCustomizeContainer.state.isSavedStatesOfTabChanges}
-                onChecked={() => { adminCustomizeContainer.switchSavedStatesOfTabChanges() }}
-              >
-                <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.tab_switch_desc1')}<br />
-                  {t('admin:customize_setting.function_options.tab_switch_desc2')}
-                </p>
-              </CustomizeFunctionOption>
-            </div>
-          </div>
           <div className="form-group row">
           <div className="form-group row">
             <div className="offset-md-3 col-md-6 text-left">
             <div className="offset-md-3 col-md-6 text-left">
               <CustomizeFunctionOption
               <CustomizeFunctionOption
                 optionId="isEnabledAttachTitleHeader"
                 optionId="isEnabledAttachTitleHeader"
-                label={t('admin:customize_setting.function_options.attach_title_header')}
+                label={t('admin:customize_settings.function_options.attach_title_header')}
                 isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
                 isChecked={adminCustomizeContainer.state.isEnabledAttachTitleHeader}
                 onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
                 onChecked={() => { adminCustomizeContainer.switchEnabledAttachTitleHeader() }}
               >
               >
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.attach_title_header_desc')}
+                  {t('admin:customize_settings.function_options.attach_title_header_desc')}
                 </p>
                 </p>
               </CustomizeFunctionOption>
               </CustomizeFunctionOption>
             </div>
             </div>
           </div>
           </div>
 
 
           <PagingSizeUncontrolledDropdown
           <PagingSizeUncontrolledDropdown
-            label={t('admin:customize_setting.function_options.list_num_s')}
-            desc={t('admin:customize_setting.function_options.list_num_desc_s')}
+            label={t('admin:customize_settings.function_options.list_num_s')}
+            desc={t('admin:customize_settings.function_options.list_num_desc_s')}
             toggleLabel={adminCustomizeContainer.state.pageLimitationS || 20}
             toggleLabel={adminCustomizeContainer.state.pageLimitationS || 20}
             dropdownItemSize={[10, 20, 50, 100]}
             dropdownItemSize={[10, 20, 50, 100]}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationS}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationS}
           />
           />
           <PagingSizeUncontrolledDropdown
           <PagingSizeUncontrolledDropdown
-            label={t('admin:customize_setting.function_options.list_num_m')}
-            desc={t('admin:customize_setting.function_options.list_num_desc_m')}
+            label={t('admin:customize_settings.function_options.list_num_m')}
+            desc={t('admin:customize_settings.function_options.list_num_desc_m')}
             toggleLabel={adminCustomizeContainer.state.pageLimitationM || 10}
             toggleLabel={adminCustomizeContainer.state.pageLimitationM || 10}
             dropdownItemSize={[5, 10, 20, 50, 100]}
             dropdownItemSize={[5, 10, 20, 50, 100]}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationM}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationM}
           />
           />
           <PagingSizeUncontrolledDropdown
           <PagingSizeUncontrolledDropdown
-            label={t('admin:customize_setting.function_options.list_num_l')}
-            desc={t('admin:customize_setting.function_options.list_num_desc_l')}
+            label={t('admin:customize_settings.function_options.list_num_l')}
+            desc={t('admin:customize_settings.function_options.list_num_desc_l')}
             toggleLabel={adminCustomizeContainer.state.pageLimitationL || 50}
             toggleLabel={adminCustomizeContainer.state.pageLimitationL || 50}
             dropdownItemSize={[20, 50, 100, 200]}
             dropdownItemSize={[20, 50, 100, 200]}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationL}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationL}
           />
           />
           <PagingSizeUncontrolledDropdown
           <PagingSizeUncontrolledDropdown
-            label={t('admin:customize_setting.function_options.list_num_xl')}
-            desc={t('admin:customize_setting.function_options.list_num_desc_xl')}
+            label={t('admin:customize_settings.function_options.list_num_xl')}
+            desc={t('admin:customize_settings.function_options.list_num_desc_xl')}
             toggleLabel={adminCustomizeContainer.state.pageLimitationXL || 20}
             toggleLabel={adminCustomizeContainer.state.pageLimitationXL || 20}
             dropdownItemSize={[5, 10, 20, 50, 100]}
             dropdownItemSize={[5, 10, 20, 50, 100]}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationXL}
             onChangeDropdownItem={adminCustomizeContainer.switchPageListLimitationXL}
@@ -107,12 +92,12 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             <div className="offset-md-3 col-md-6 text-left">
             <div className="offset-md-3 col-md-6 text-left">
               <CustomizeFunctionOption
               <CustomizeFunctionOption
                 optionId="isEnabledStaleNotification"
                 optionId="isEnabledStaleNotification"
-                label={t('admin:customize_setting.function_options.stale_notification')}
+                label={t('admin:customize_settings.function_options.stale_notification')}
                 isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
                 isChecked={adminCustomizeContainer.state.isEnabledStaleNotification}
                 onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
                 onChecked={() => { adminCustomizeContainer.switchEnableStaleNotification() }}
               >
               >
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.stale_notification_desc')}
+                  {t('admin:customize_settings.function_options.stale_notification_desc')}
                 </p>
                 </p>
               </CustomizeFunctionOption>
               </CustomizeFunctionOption>
             </div>
             </div>
@@ -122,12 +107,12 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             <div className="offset-md-3 col-md-6 text-left">
             <div className="offset-md-3 col-md-6 text-left">
               <CustomizeFunctionOption
               <CustomizeFunctionOption
                 optionId="isAllReplyShown"
                 optionId="isAllReplyShown"
-                label={t('admin:customize_setting.function_options.show_all_reply_comments')}
+                label={t('admin:customize_settings.function_options.show_all_reply_comments')}
                 isChecked={adminCustomizeContainer.state.isAllReplyShown || false}
                 isChecked={adminCustomizeContainer.state.isAllReplyShown || false}
                 onChecked={() => { adminCustomizeContainer.switchIsAllReplyShown() }}
                 onChecked={() => { adminCustomizeContainer.switchIsAllReplyShown() }}
               >
               >
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.show_all_reply_comments_desc')}
+                  {t('admin:customize_settings.function_options.show_all_reply_comments_desc')}
                 </p>
                 </p>
               </CustomizeFunctionOption>
               </CustomizeFunctionOption>
             </div>
             </div>
@@ -137,12 +122,12 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
             <div className="offset-md-3 col-md-6 text-left">
             <div className="offset-md-3 col-md-6 text-left">
               <CustomizeFunctionOption
               <CustomizeFunctionOption
                 optionId="isSearchScopeChildrenAsDefault"
                 optionId="isSearchScopeChildrenAsDefault"
-                label={t('admin:customize_setting.function_options.select_search_scope_children_as_default')}
+                label={t('admin:customize_settings.function_options.select_search_scope_children_as_default')}
                 isChecked={adminCustomizeContainer.state.isSearchScopeChildrenAsDefault || false}
                 isChecked={adminCustomizeContainer.state.isSearchScopeChildrenAsDefault || false}
                 onChecked={() => { adminCustomizeContainer.switchIsSearchScopeChildrenAsDefault() }}
                 onChecked={() => { adminCustomizeContainer.switchIsSearchScopeChildrenAsDefault() }}
               >
               >
                 <p className="form-text text-muted">
                 <p className="form-text text-muted">
-                  {t('admin:customize_setting.function_options.select_search_scope_children_as_default_desc')}
+                  {t('admin:customize_settings.function_options.select_search_scope_children_as_default_desc')}
                 </p>
                 </p>
               </CustomizeFunctionOption>
               </CustomizeFunctionOption>
             </div>
             </div>

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

@@ -21,7 +21,7 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {
       await adminCustomizeContainer.updateCustomizeHeader();
       await adminCustomizeContainer.updateCustomizeHeader();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.custom_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_header'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -32,19 +32,19 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
     <React.Fragment>
     <React.Fragment>
       <div className="row">
       <div className="row">
         <div className="col-12">
         <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_setting.custom_header')}</h2>
+          <h2 className="admin-setting-header">{t('admin:customize_settings.custom_header')}</h2>
 
 
           <Card className="card well my-3">
           <Card className="card well my-3">
             <CardBody className="px-0 py-2">
             <CardBody className="px-0 py-2">
               <span
               <span
                 // eslint-disable-next-line react/no-danger
                 // eslint-disable-next-line react/no-danger
-                dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_header_detail') }}
+                dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_header_detail') }}
               />
               />
             </CardBody>
             </CardBody>
           </Card>
           </Card>
           <div className="form-text text-muted">
           <div className="form-text text-muted">
             { t('Example') }:
             { t('Example') }:
-            <pre className="hljs">
+            <pre>
               {/* eslint-disable-next-line react/no-unescaped-entities */}
               {/* eslint-disable-next-line react/no-unescaped-entities */}
               <code className="text-wrap">&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js"
               <code className="text-wrap">&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js"
                 defer&gt;&lt;/script&gt;
                 defer&gt;&lt;/script&gt;
@@ -61,7 +61,7 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
             />
             />
             <p className="form-text text-muted text-right">
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
-              {t('admin:customize_setting.ctrl_space')}
+              {t('admin:customize_settings.ctrl_space')}
             </p>
             </p>
           </div>
           </div>
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
           <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />

+ 0 - 148
packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx

@@ -1,148 +0,0 @@
-/* eslint-disable no-useless-escape */
-import React, { useCallback, useState } from 'react';
-
-
-import { useTranslation } from 'next-i18next';
-import {
-  Dropdown, DropdownToggle, DropdownMenu, DropdownItem,
-} from 'reactstrap';
-
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { IHighlightJsCssSelectorOptions } from '~/interfaces/customize';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-
-type Props = {
-  adminCustomizeContainer: AdminCustomizeContainer
-}
-
-type HljsDemoProps = {
-  isHighlightJsStyleBorderEnabled: boolean
-}
-
-const HljsDemo = React.memo((props: HljsDemoProps): JSX.Element => {
-
-  const { isHighlightJsStyleBorderEnabled } = props;
-
-  /* eslint-disable max-len */
-  const html = `<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MersenneTwister</span>(<span class="hljs-params">seed</span>) </span>{
-<span class="hljs-keyword">if</span> (<span class="hljs-built_in">arguments</span>.length == <span class="hljs-number">0</span>) {
-  seed = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime();
-}
-
-<span class="hljs-keyword">this</span>._mt = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(<span class="hljs-number">624</span>);
-<span class="hljs-keyword">this</span>.setSeed(seed);
-}</span>`;
-  /* eslint-enable max-len */
-
-  return (
-    <pre className={`hljs ${!isHighlightJsStyleBorderEnabled && 'hljs-no-border'}`}>
-      {/* eslint-disable-next-line react/no-danger */}
-      <code dangerouslySetInnerHTML={{ __html: html }}></code>
-    </pre>
-  );
-});
-HljsDemo.displayName = 'HljsDemo';
-
-const CustomizeHighlightSetting = (props: Props): JSX.Element => {
-  const { adminCustomizeContainer } = props;
-  const { t } = useTranslation();
-  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
-  const options: IHighlightJsCssSelectorOptions = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
-
-  const onToggleDropdown = useCallback(() => {
-    setIsDropdownOpen(!isDropdownOpen);
-  }, [isDropdownOpen]);
-
-  const onClickSubmit = useCallback(async() => {
-    try {
-      await adminCustomizeContainer.updateHighlightJsStyle();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.code_highlight') }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }, [t, adminCustomizeContainer]);
-
-  const renderMenuItems = useCallback(() => {
-
-    const items = Object.entries(options).map((option) => {
-      const styleId = option[0];
-      const styleName = option[1].name;
-      const isBorderEnable = option[1].border;
-
-      return (
-        <DropdownItem
-          key={styleId}
-          role="presentation"
-          onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}
-        >
-          <a role="menuitem">{styleName}</a>
-        </DropdownItem>
-      );
-    });
-    return items;
-  }, [adminCustomizeContainer, options]);
-
-  return (
-    <React.Fragment>
-      <div className="row">
-        <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_setting.code_highlight')}</h2>
-
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <div className="my-0">
-                <label>{t('admin:customize_setting.theme')}</label>
-              </div>
-              <Dropdown isOpen={isDropdownOpen} toggle={onToggleDropdown}>
-                <DropdownToggle className="text-right col-6" caret>
-                  <span className="float-left">{adminCustomizeContainer.state.currentHighlightJsStyleName}</span>
-                </DropdownToggle>
-                <DropdownMenu className="dropdown-menu" role="menu">
-                  {renderMenuItems()}
-                </DropdownMenu>
-              </Dropdown>
-              <p className="form-text text-warning">
-                {/* eslint-disable-next-line react/no-danger */}
-                <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.nocdn_desc') }} />
-              </p>
-            </div>
-          </div>
-
-          <div className="form-group row">
-            <div className="offset-md-3 col-md-6 text-left">
-              <div className="custom-control custom-switch custom-checkbox-success">
-                <input
-                  type="checkbox"
-                  className="custom-control-input"
-                  id="highlightBorder"
-                  checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
-                  onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
-                />
-                <label className="custom-control-label" htmlFor="highlightBorder">
-                  <strong>Border</strong>
-                </label>
-              </div>
-            </div>
-          </div>
-
-          <div className="form-text text-muted">
-            <label>Examples:</label>
-            <div className="wiki">
-              <HljsDemo isHighlightJsStyleBorderEnabled={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled} />
-            </div>
-          </div>
-
-          <AdminUpdateButtonRow onClick={onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
-        </div>
-      </div>
-    </React.Fragment>
-  );
-};
-
-const CustomizeHighlightSettingWrapper = withUnstatedContainers(CustomizeHighlightSetting, [AdminCustomizeContainer]);
-
-export default CustomizeHighlightSettingWrapper;

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

@@ -1,44 +1,54 @@
-import React, { useCallback, useEffect, useState } from 'react';
+import React, {
+  useCallback, useEffect, useState,
+} from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
+import { useSWRxLayoutSetting } from '~/stores/admin/customize';
 import { useNextThemes } from '~/stores/use-next-themes';
 import { useNextThemes } from '~/stores/use-next-themes';
 
 
+const useIsContainerFluid = () => {
+  const { data: layoutSetting, update: updateLayoutSetting } = useSWRxLayoutSetting();
+  const [isContainerFluid, setIsContainerFluid] = useState<boolean>();
+
+  useEffect(() => {
+    setIsContainerFluid(layoutSetting?.isContainerFluid);
+  }, [layoutSetting?.isContainerFluid]);
+
+  return {
+    isContainerFluid,
+    setIsContainerFluid,
+    updateLayoutSetting,
+  };
+};
+
 const CustomizeLayoutSetting = (): JSX.Element => {
 const CustomizeLayoutSetting = (): JSX.Element => {
   const { t } = useTranslation('admin');
   const { t } = useTranslation('admin');
 
 
   const { resolvedTheme } = useNextThemes();
   const { resolvedTheme } = useNextThemes();
 
 
-  const [isContainerFluid, setIsContainerFluid] = useState(false);
+  const { isContainerFluid, setIsContainerFluid, updateLayoutSetting } = useIsContainerFluid();
   const [retrieveError, setRetrieveError] = useState<any>();
   const [retrieveError, setRetrieveError] = useState<any>();
 
 
-  const retrieveData = useCallback(async() => {
+  const onClickSubmit = useCallback(async() => {
+    if (isContainerFluid == null) { return }
     try {
     try {
-      const res = await apiv3Get('/customize-setting/layout');
-      setIsContainerFluid(res.data.isContainerFluid);
+      await updateLayoutSetting({ isContainerFluid });
+      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.layout'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
-      setRetrieveError(err);
       toastError(err);
       toastError(err);
     }
     }
-  }, []);
-
-  useEffect(() => {
-    retrieveData();
-  }, [retrieveData]);
+  }, [isContainerFluid, updateLayoutSetting, t]);
 
 
-  const onClickSubmit = async() => {
-    try {
-      await apiv3Put('/customize-setting/layout', { isContainerFluid });
-      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.layout') }));
-      retrieveData();
-    }
-    catch (err) {
-      toastError(err);
-    }
-  };
+  if (isContainerFluid == null) {
+    return (
+      <div className="text-muted text-center">
+        <i className="fa fa-2x fa-spinner fa-pulse"></i>
+      </div>
+    );
+  }
 
 
   return (
   return (
     <React.Fragment>
     <React.Fragment>

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

@@ -54,23 +54,21 @@ const CustomizeLogoSetting = (): JSX.Element => {
     try {
     try {
       const response = await apiv3Put('/customize-setting/customize-logo', {
       const response = await apiv3Put('/customize-setting/customize-logo', {
         isDefaultLogo,
         isDefaultLogo,
-        customizedLogoSrc,
       });
       });
       const { customizedParams } = response.data;
       const { customizedParams } = response.data;
       setIsDefaultLogo(customizedParams.isDefaultLogo);
       setIsDefaultLogo(customizedParams.isDefaultLogo);
-      setCustomizedLogoSrc(customizedParams.customizedLogoSrc);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.custom_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [t, isDefaultLogo, customizedLogoSrc]);
+  }, [t, isDefaultLogo]);
 
 
   const onClickDeleteBtn = useCallback(async() => {
   const onClickDeleteBtn = useCallback(async() => {
     try {
     try {
       await apiv3Delete('/customize-setting/delete-brand-logo');
       await apiv3Delete('/customize-setting/delete-brand-logo');
       setCustomizedLogoSrc(null);
       setCustomizedLogoSrc(null);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.current_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -86,7 +84,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       formData.append('file', croppedImage);
       formData.append('file', croppedImage);
       const { data } = await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
       const { data } = await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
       setCustomizedLogoSrc(data.attachment.filePathProxied);
       setCustomizedLogoSrc(data.attachment.filePathProxied);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.current_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -100,7 +98,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       <div className="row">
       <div className="row">
         <div className="col-12">
         <div className="col-12">
           <div className="mb-5">
           <div className="mb-5">
-            <h2 className="border-bottom my-4 admin-setting-header">{t('admin:customize_setting.custom_logo')}</h2>
+            <h2 className="border-bottom my-4 admin-setting-header">{t('admin:customize_settings.custom_logo')}</h2>
             <div className="row">
             <div className="row">
               <div className="col-md-6 col-12 mb-3 mb-md-0">
               <div className="col-md-6 col-12 mb-3 mb-md-0">
                 <h4>
                 <h4>
@@ -115,7 +113,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
                       onChange={() => { setIsDefaultLogo(true) }}
                       onChange={() => { setIsDefaultLogo(true) }}
                     />
                     />
                     <label className="custom-control-label" htmlFor="radioDefaultLogo">
                     <label className="custom-control-label" htmlFor="radioDefaultLogo">
-                      {t('admin:customize_setting.default_logo')}
+                      {t('admin:customize_settings.default_logo')}
                     </label>
                     </label>
                   </div>
                   </div>
                 </h4>
                 </h4>
@@ -134,26 +132,26 @@ const CustomizeLogoSetting = (): JSX.Element => {
                       onChange={() => { setIsDefaultLogo(false) }}
                       onChange={() => { setIsDefaultLogo(false) }}
                     />
                     />
                     <label className="custom-control-label" htmlFor="radioUploadLogo">
                     <label className="custom-control-label" htmlFor="radioUploadLogo">
-                      { t('admin:customize_setting.upload_logo') }
+                      { t('admin:customize_settings.upload_logo') }
                     </label>
                     </label>
                   </div>
                   </div>
                 </h4>
                 </h4>
                 <div className="row mb-3">
                 <div className="row mb-3">
                   <label className="col-sm-4 col-12 col-form-label text-left">
                   <label className="col-sm-4 col-12 col-form-label text-left">
-                    { t('admin:customize_setting.current_logo') }
+                    { t('admin:customize_settings.current_logo') }
                   </label>
                   </label>
                   <div className="col-sm-8 col-12">
                   <div className="col-sm-8 col-12">
                     <p><img src={customizedLogoSrc || DEFAULT_LOGO} className="picture picture-lg " id="settingBrandLogo" width="64" /></p>
                     <p><img src={customizedLogoSrc || DEFAULT_LOGO} className="picture picture-lg " id="settingBrandLogo" width="64" /></p>
                     {(customizedLogoSrc != null) && (
                     {(customizedLogoSrc != null) && (
                       <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
                       <button type="button" className="btn btn-danger" onClick={onClickDeleteBtn}>
-                        { t('admin:customize_setting.delete_logo') }
+                        { t('admin:customize_settings.delete_logo') }
                       </button>
                       </button>
                     )}
                     )}
                   </div>
                   </div>
                 </div>
                 </div>
                 <div className="row">
                 <div className="row">
                   <label className="col-sm-4 col-12 col-form-label text-left">
                   <label className="col-sm-4 col-12 col-form-label text-left">
-                    { t('admin:customize_setting.upload_new_logo') }
+                    { t('admin:customize_settings.upload_new_logo') }
                   </label>
                   </label>
                   <div className="col-sm-8 col-12">
                   <div className="col-sm-8 col-12">
                     <input type="file" onChange={onSelectFile} name="brandLogo" accept="image/*" />
                     <input type="file" onChange={onSelectFile} name="brandLogo" accept="image/*" />

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

@@ -21,7 +21,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {
       await adminCustomizeContainer.updateCustomizeScript();
       await adminCustomizeContainer.updateCustomizeScript();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.custom_script') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_script'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -40,11 +40,11 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
     <React.Fragment>
     <React.Fragment>
       <div className="row">
       <div className="row">
         <div className="col-12">
         <div className="col-12">
-          <h2 className="admin-setting-header">{t('admin:customize_setting.custom_script')}</h2>
+          <h2 className="admin-setting-header">{t('admin:customize_settings.custom_script')}</h2>
           <Card className="card well">
           <Card className="card well">
             <CardBody className="px-0 py-2">
             <CardBody className="px-0 py-2">
-              {t('admin:customize_setting.write_java')}<br />
-              {t('admin:customize_setting.reflect_change')}
+              {t('admin:customize_settings.write_java')}<br />
+              {t('admin:customize_settings.reflect_change')}
             </CardBody>
             </CardBody>
           </Card>
           </Card>
 
 
@@ -79,7 +79,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
 
 
           <div className="form-text text-muted">
           <div className="form-text text-muted">
             Examples:
             Examples:
-            <pre className="hljs"><code>{getExampleCode()}</code></pre>
+            <pre><code className='language-javascript'>{getExampleCode()}</code></pre>
           </div>
           </div>
 
 
           <div className="form-group">
           <div className="form-group">
@@ -91,7 +91,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
             />
             />
             <p className="form-text text-muted text-right">
             <p className="form-text text-muted text-right">
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
               <i className="fa fa-fw fa-keyboard-o" aria-hidden="true" />
-              {t('admin:customize_setting.ctrl_space')}
+              {t('admin:customize_settings.ctrl_space')}
             </p>
             </p>
           </div>
           </div>
 
 

+ 3 - 2
packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx

@@ -8,7 +8,8 @@ import { useSWRxSidebarConfig } from '~/stores/ui';
 import { useNextThemes } from '~/stores/use-next-themes';
 import { useNextThemes } from '~/stores/use-next-themes';
 
 
 const CustomizeSidebarsetting = (): JSX.Element => {
 const CustomizeSidebarsetting = (): JSX.Element => {
-  const { t } = useTranslation('admin');
+  const { t } = useTranslation(['admin', 'commons']);
+
   const {
   const {
     update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
     update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
   } = useSWRxSidebarConfig();
   } = useSWRxSidebarConfig();
@@ -20,7 +21,7 @@ const CustomizeSidebarsetting = (): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
   const onClickSubmit = useCallback(async() => {
     try {
     try {
       await update();
       await update();
-      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.default_sidebar_mode.title') }));
+      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.default_sidebar_mode.title'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);

+ 7 - 5
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -20,12 +20,14 @@ type Props = {
 const CustomizeThemeSetting = (props: Props): JSX.Element => {
 const CustomizeThemeSetting = (props: Props): JSX.Element => {
 
 
   const { adminCustomizeContainer } = props;
   const { adminCustomizeContainer } = props;
-  const { data: currentTheme, mutate: mutateGrowiTheme } = useGrowiTheme();
+  const { data: currentTheme } = useGrowiTheme();
   const { t } = useTranslation();
   const { t } = useTranslation();
 
 
   const selectedHandler = useCallback((themeName) => {
   const selectedHandler = useCallback((themeName) => {
-    mutateGrowiTheme(themeName);
-  }, [mutateGrowiTheme]);
+    // TODO: preview without using mutate of useGrowiTheme
+    // https://github.com/weseek/growi/pull/6860
+    // mutateGrowiTheme(themeName);
+  }, []);
 
 
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
     try {
     try {
@@ -35,7 +37,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
         });
         });
       }
       }
 
 
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.theme') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme'), ns: 'commons' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
@@ -45,7 +47,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
   return (
   return (
     <div className="row">
     <div className="row">
       <div className="col-12">
       <div className="col-12">
-        <h2 className="admin-setting-header">{t('admin:customize_setting.theme')}</h2>
+        <h2 className="admin-setting-header">{t('admin:customize_settings.theme')}</h2>
         <CustomizeThemeOptions onSelected={selectedHandler} currentTheme={currentTheme} />
         <CustomizeThemeOptions onSelected={selectedHandler} currentTheme={currentTheme} />
         <AdminUpdateButtonRow onClick={submitHandler} disabled={adminCustomizeContainer.state.retrieveError != null} />
         <AdminUpdateButtonRow onClick={submitHandler} disabled={adminCustomizeContainer.state.retrieveError != null} />
       </div>
       </div>

+ 0 - 96
packages/app/src/components/Admin/Customize/CustomizeTitle.jsx

@@ -1,96 +0,0 @@
-/* eslint-disable max-len */
-import React from 'react';
-
-import { withTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-import { Card, CardBody } from 'reactstrap';
-
-import AdminCustomizeContainer from '~/client/services/AdminCustomizeContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
-
-class CustomizeTitle extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.onClickSubmit = this.onClickSubmit.bind(this);
-  }
-
-  async onClickSubmit() {
-    const { t, adminCustomizeContainer } = this.props;
-
-    try {
-      await adminCustomizeContainer.updateCustomizeTitle();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_setting.custom_title') }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  render() {
-    const { t, adminCustomizeContainer } = this.props;
-    const { currentCustomizeTitle } = adminCustomizeContainer.state;
-
-    return (
-      <React.Fragment>
-        <div className="row">
-          <div className="col-12">
-            <h2 className="admin-setting-header">{t('admin:customize_setting.custom_title')}</h2>
-          </div>
-
-          <div className="col-12">
-            <Card className="card well">
-              <CardBody className="px-0 py-2">
-                {/* eslint-disable react/no-danger */}
-                <p dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail') }} />
-                <ul>
-                  <li>
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail_placeholder1') }} />
-                  </li>
-                  <li>
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail_placeholder2') }} />
-                  </li>
-                  <li>
-                    <span dangerouslySetInnerHTML={{ __html: t('admin:customize_setting.custom_title_detail_placeholder3') }} />
-                  </li>
-                </ul>
-                {/* eslint-enable react/no-danger */}
-              </CardBody>
-            </Card>
-          </div>
-
-          {/* TODO i18n */}
-          <div className="form-text text-muted col-12">
-            Default Value: <code>&#123;&#123;pagename&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
-            <br />
-            Default Output Example: <code className="xml">&lt;title&gt;Page name - My GROWI&lt;&#047;title&gt;</code>
-          </div>
-          <div className="form-group col-12">
-            <input
-              className="form-control"
-              defaultValue={currentCustomizeTitle}
-              onChange={(e) => { adminCustomizeContainer.changeCustomizeTitle(e.target.value) }}
-            />
-          </div>
-          <div className="col-12">
-            <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminCustomizeContainer.state.retrieveError != null} />
-          </div>
-        </div>
-      </React.Fragment>
-    );
-  }
-
-}
-
-const CustomizeTitleWrapper = withUnstatedContainers(CustomizeTitle, [AdminCustomizeContainer]);
-
-CustomizeTitle.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
-};
-
-export default withTranslation()(CustomizeTitleWrapper);

+ 79 - 0
packages/app/src/components/Admin/Customize/CustomizeTitle.tsx

@@ -0,0 +1,79 @@
+import React, { FC, useState } from 'react';
+
+import { useTranslation } from 'next-i18next';
+import { Card, CardBody } from 'reactstrap';
+
+import { toastSuccess, toastError } from '~/client/util/apiNotification';
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { useCustomizeTitle } from '~/stores/context';
+
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+
+export const CustomizeTitle: FC = () => {
+
+  const { t } = useTranslation('admin');
+
+  const { data: customizeTitle } = useCustomizeTitle();
+
+  const [currentCustomizeTitle, setCrrentCustomizeTitle] = useState(customizeTitle);
+
+  const onClickSubmit = async() => {
+    try {
+      await apiv3Put('/customize-setting/customize-title', {
+        customizeTitle: currentCustomizeTitle,
+      });
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_title'), ns: 'commons' }));
+    }
+    catch (err) {
+      toastError(err);
+    }
+  };
+
+  return (
+    <React.Fragment>
+      <div className="row">
+        <div className="col-12">
+          <h2 className="admin-setting-header">{t('admin:customize_settings.custom_title')}</h2>
+        </div>
+
+        <div className="col-12">
+          <Card className="card well">
+            <CardBody className="px-0 py-2">
+              {/* eslint-disable react/no-danger */}
+              <p dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_title_detail') }} />
+              <ul>
+                <li>
+                  <span dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_title_detail_placeholder1') }} />
+                </li>
+                <li>
+                  <span dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_title_detail_placeholder2') }} />
+                </li>
+                <li>
+                  <span dangerouslySetInnerHTML={{ __html: t('admin:customize_settings.custom_title_detail_placeholder3') }} />
+                </li>
+              </ul>
+              {/* eslint-enable react/no-danger */}
+            </CardBody>
+          </Card>
+        </div>
+
+        {/* TODO i18n */}
+        <div className="form-text text-muted col-12">
+            Default Value: <code>&#123;&#123;pagename&#125;&#125; - &#123;&#123;sitename&#125;&#125;</code>
+          <br />
+            Default Output Example: <code className="xml">&lt;title&gt;Page name - My GROWI&lt;&#047;title&gt;</code>
+        </div>
+        <div className="form-group col-12">
+          <input
+            className="form-control"
+            defaultValue={currentCustomizeTitle}
+            onChange={(e) => { setCrrentCustomizeTitle(e.target.value) }}
+          />
+        </div>
+        <div className="col-12">
+          <AdminUpdateButtonRow onClick={onClickSubmit} disabled={false} />
+        </div>
+      </div>
+    </React.Fragment>
+  );
+};

+ 1 - 1
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx

@@ -146,7 +146,7 @@ const ElasticsearchManagement = () => {
 
 
   return (
   return (
     <>
     <>
-      <div className="row">
+      <div data-testid="admin-full-text-search" className="row">
         <div className="col-md-12">
         <div className="col-md-12">
           <StatusTable
           <StatusTable
             isInitialized={isInitialized}
             isInitialized={isInitialized}

+ 2 - 4
packages/app/src/components/Admin/FullTextSearchManagement.tsx

@@ -4,8 +4,8 @@ import { useTranslation } from 'next-i18next';
 
 
 import ElasticsearchManagement from './ElasticsearchManagement/ElasticsearchManagement';
 import ElasticsearchManagement from './ElasticsearchManagement/ElasticsearchManagement';
 
 
-const FullTextSearchManagement = (): JSX.Element => {
-  const { t } = useTranslation();
+export const FullTextSearchManagement = (): JSX.Element => {
+  const { t } = useTranslation('admin');
 
 
   return (
   return (
     <div data-testid="admin-full-text-search">
     <div data-testid="admin-full-text-search">
@@ -14,5 +14,3 @@ const FullTextSearchManagement = (): JSX.Element => {
     </div>
     </div>
   );
   );
 };
 };
-
-export default FullTextSearchManagement;

+ 11 - 12
packages/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx

@@ -3,14 +3,12 @@ import React from 'react';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
 import PropTypes from 'prop-types';
 
 
-import AdminSocketIoContainer from '~/client/services/AdminSocketIoContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import GrowiArchiveImportOption from '~/models/admin/growi-archive-import-option';
 import GrowiArchiveImportOption from '~/models/admin/growi-archive-import-option';
 import ImportOptionForPages from '~/models/admin/import-option-for-pages';
 import ImportOptionForPages from '~/models/admin/import-option-for-pages';
 import ImportOptionForRevisions from '~/models/admin/import-option-for-revisions';
 import ImportOptionForRevisions from '~/models/admin/import-option-for-revisions';
-
-import { withUnstatedContainers } from '../../../UnstatedUtils';
+import { useAdminSocket } from '~/stores/socket-io';
 
 
 
 
 import ErrorViewer from './ErrorViewer';
 import ErrorViewer from './ErrorViewer';
@@ -103,7 +101,7 @@ class ImportForm extends React.Component {
   }
   }
 
 
   setupWebsocketEventHandler() {
   setupWebsocketEventHandler() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
+    const { socket } = this.props;
 
 
     // websocket event
     // websocket event
     // eslint-disable-next-line object-curly-newline
     // eslint-disable-next-line object-curly-newline
@@ -143,7 +141,7 @@ class ImportForm extends React.Component {
   }
   }
 
 
   teardownWebsocketEventHandler() {
   teardownWebsocketEventHandler() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
+    const { socket } = this.props;
 
 
     socket.removeAllListeners('admin:onProgressForImport');
     socket.removeAllListeners('admin:onProgressForImport');
     socket.removeAllListeners('admin:onTerminateForImport');
     socket.removeAllListeners('admin:onTerminateForImport');
@@ -496,7 +494,7 @@ class ImportForm extends React.Component {
 
 
 ImportForm.propTypes = {
 ImportForm.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
-  adminSocketIoContainer: PropTypes.instanceOf(AdminSocketIoContainer).isRequired,
+  socket: PropTypes.object.isRequired,
 
 
   fileName: PropTypes.string,
   fileName: PropTypes.string,
   innerFileStats: PropTypes.arrayOf(PropTypes.object).isRequired,
   innerFileStats: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -506,13 +504,14 @@ ImportForm.propTypes = {
 
 
 const ImportFormWrapperFc = (props) => {
 const ImportFormWrapperFc = (props) => {
   const { t } = useTranslation('admin');
   const { t } = useTranslation('admin');
+  const { data: socket } = useAdminSocket();
+
+  if (socket == null) {
+    return;
+  }
 
 
-  return <ImportForm t={t} {...props} />;
+  return <ImportForm t={t} socket={socket} {...props} />;
 };
 };
 
 
-/**
- * Wrapper component for using unstated
- */
-const ImportFormWrapper = withUnstatedContainers(ImportFormWrapperFc, [AdminSocketIoContainer]);
 
 
-export default ImportFormWrapper;
+export default ImportFormWrapperFc;

+ 3 - 4
packages/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx

@@ -8,7 +8,7 @@ import { apiv3Delete, apiv3Get } from '~/client/util/apiv3-client';
 
 
 // import { toastSuccess, toastError } from '~/client/util/apiNotification';
 // import { toastSuccess, toastError } from '~/client/util/apiNotification';
 
 
-// import ImportForm from './GrowiArchive/ImportForm';
+import ImportForm from './GrowiArchive/ImportForm';
 import UploadForm from './GrowiArchive/UploadForm';
 import UploadForm from './GrowiArchive/UploadForm';
 
 
 class GrowiArchiveSection extends React.Component {
 class GrowiArchiveSection extends React.Component {
@@ -129,12 +129,11 @@ class GrowiArchiveSection extends React.Component {
         {isTheSameVersion === false && this.renderDefferentVersionAlert()}
         {isTheSameVersion === false && this.renderDefferentVersionAlert()}
         {this.state.fileName != null && isTheSameVersion === true ? (
         {this.state.fileName != null && isTheSameVersion === true ? (
           <div className="px-4">
           <div className="px-4">
-            {/* show ImportForm by https://redmine.weseek.co.jp/issues/100061 */}
-            {/* <ImportForm
+            <ImportForm
               fileName={this.state.fileName}
               fileName={this.state.fileName}
               innerFileStats={this.state.innerFileStats}
               innerFileStats={this.state.innerFileStats}
               onDiscard={this.discardData}
               onDiscard={this.discardData}
-            /> */}
+            />
           </div>
           </div>
         )
         )
           : (
           : (

+ 0 - 91
packages/app/src/components/Admin/ManageExternalAccount.jsx

@@ -1,91 +0,0 @@
-import React, { Fragment } from 'react';
-
-import { useTranslation } from 'next-i18next';
-import PropTypes from 'prop-types';
-
-import AdminExternalAccountsContainer from '~/client/services/AdminExternalAccountsContainer';
-import { toastError } from '~/client/util/apiNotification';
-
-import PaginationWrapper from '../PaginationWrapper';
-import { withUnstatedContainers } from '../UnstatedUtils';
-
-import ExternalAccountTable from './Users/ExternalAccountTable';
-
-
-class ManageExternalAccount extends React.Component {
-
-  constructor(props) {
-    super(props);
-    this.handleExternalAccountPage = this.handleExternalAccountPage.bind(this);
-  }
-
-  UNSAFE_componentWillMount() {
-    this.handleExternalAccountPage(1);
-  }
-
-  async handleExternalAccountPage(selectedPage) {
-    try {
-      await this.props.adminExternalAccountsContainer.retrieveExternalAccountsByPagingNum(selectedPage);
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  render() {
-    const { t, adminExternalAccountsContainer } = this.props;
-    const { activePage, totalAccounts, pagingLimit } = adminExternalAccountsContainer.state;
-
-
-    const pager = (
-      <PaginationWrapper
-        activePage={activePage}
-        changePage={this.handleExternalAccountPage}
-        totalItemsCount={totalAccounts}
-        pagingLimit={pagingLimit}
-        align="center"
-        size="sm"
-      />
-    );
-    return (
-      <Fragment>
-        <p>
-          <a className="btn btn-outline-secondary" href="/admin/users">
-            <i className="icon-fw ti-arrow-left" aria-hidden="true"></i>
-            {t('admin:user_management.back_to_user_management')}
-          </a>
-        </p>
-
-        <h2>{t('admin:user_management.external_account_list')}</h2>
-        {(totalAccounts !== 0) ? (
-          <>
-            {pager}
-            <ExternalAccountTable />
-            {pager}
-          </>
-        )
-          : (
-            <>
-              {t('admin:user_management.external_account_none')}
-            </>
-          )}
-
-      </Fragment>
-    );
-  }
-
-}
-
-ManageExternalAccount.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  adminExternalAccountsContainer: PropTypes.instanceOf(AdminExternalAccountsContainer).isRequired,
-};
-
-const ManageExternalAccountWrapperFC = (props) => {
-  const { t } = useTranslation();
-  return <ManageExternalAccount t={t} {...props} />;
-};
-
-const ManageExternalAccountWrapper = withUnstatedContainers(ManageExternalAccountWrapperFC, [AdminExternalAccountsContainer]);
-
-export default ManageExternalAccountWrapper;

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