Răsfoiți Sursa

fix confilct

jam411 3 ani în urmă
părinte
comite
d9a4fd383a
100 a modificat fișierele cu 473 adăugiri și 746 ștergeri
  1. 4 2
      .github/dependabot.yml
  2. 8 0
      .github/workflows/ci-app-prod.yml
  3. 2 2
      .github/workflows/draft-release.yml
  4. 4 4
      .github/workflows/list-unhealthy-branches.yml
  5. 1 1
      .github/workflows/release-rc.yml
  6. 2 2
      .github/workflows/release-slackbot-proxy.yml
  7. 3 3
      .github/workflows/release.yml
  8. 8 4
      .github/workflows/reusable-app-prod.yml
  9. 33 11
      THIRD-PARTY-NOTICES.md
  10. 1 1
      lerna.json
  11. 1 1
      package.json
  12. 0 0
      packages/app/_obsolete/src/client/services/AppContainer.js
  13. 23 30
      packages/app/_obsolete/src/client/services/PageContainer.js
  14. 0 0
      packages/app/_obsolete/src/components/MyDraftList/Draft.tsx
  15. 0 0
      packages/app/_obsolete/src/components/MyDraftList/MyDraftList.jsx
  16. 1 0
      packages/app/docker/Dockerfile
  17. 19 16
      packages/app/package.json
  18. BIN
      packages/app/public/static/fonts/Lato-Bold-latin-ext.woff2
  19. BIN
      packages/app/public/static/fonts/Lato-Bold-latin.woff2
  20. BIN
      packages/app/public/static/fonts/Lato-Regular-latin-ext.woff2
  21. BIN
      packages/app/public/static/fonts/Lato-Regular-latin.woff2
  22. BIN
      packages/app/public/static/fonts/PressStart2P-latin-ext.woff2
  23. BIN
      packages/app/public/static/fonts/PressStart2P-latin.woff2
  24. 0 6
      packages/app/public/static/locales/en_US/admin.json
  25. 17 0
      packages/app/public/static/locales/en_US/commons.json
  26. 10 9
      packages/app/public/static/locales/en_US/translation.json
  27. 0 6
      packages/app/public/static/locales/ja_JP/admin.json
  28. 17 0
      packages/app/public/static/locales/ja_JP/commons.json
  29. 10 9
      packages/app/public/static/locales/ja_JP/translation.json
  30. 0 6
      packages/app/public/static/locales/zh_CN/admin.json
  31. 17 0
      packages/app/public/static/locales/zh_CN/commons.json
  32. 10 10
      packages/app/public/static/locales/zh_CN/translation.json
  33. 2 2
      packages/app/resource/locales/en_US/welcome.md
  34. 2 2
      packages/app/resource/locales/ja_JP/welcome.md
  35. 2 2
      packages/app/resource/locales/zh_CN/welcome.md
  36. 0 73
      packages/app/src/client/services/AdminCustomizeContainer.js
  37. 3 2
      packages/app/src/client/services/page-operation.ts
  38. 4 45
      packages/app/src/client/util/smooth-scroll.ts
  39. 2 2
      packages/app/src/components/Admin/App/AppSetting.jsx
  40. 1 1
      packages/app/src/components/Admin/App/AppSettingsPageContents.tsx
  41. 2 2
      packages/app/src/components/Admin/App/FileUploadSetting.tsx
  42. 2 2
      packages/app/src/components/Admin/App/MailSetting.tsx
  43. 1 1
      packages/app/src/components/Admin/App/PluginSetting.tsx
  44. 1 1
      packages/app/src/components/Admin/App/SiteUrlSetting.tsx
  45. 1 1
      packages/app/src/components/Admin/Common/AdminNavigation.jsx
  46. 0 4
      packages/app/src/components/Admin/Customize/Customize.jsx
  47. 1 1
      packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  48. 1 1
      packages/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  49. 2 2
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  50. 0 148
      packages/app/src/components/Admin/Customize/CustomizeHighlightSetting.tsx
  51. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  52. 3 3
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  53. 2 2
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  54. 3 2
      packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  55. 1 1
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  56. 1 1
      packages/app/src/components/Admin/Customize/CustomizeTitle.tsx
  57. 1 1
      packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.tsx
  58. 2 4
      packages/app/src/components/Admin/FullTextSearchManagement.tsx
  59. 11 12
      packages/app/src/components/Admin/ImportData/GrowiArchive/ImportForm.jsx
  60. 3 4
      packages/app/src/components/Admin/ImportData/GrowiArchiveSection.jsx
  61. 1 1
      packages/app/src/components/Admin/MarkdownSetting/IndentForm.tsx
  62. 1 1
      packages/app/src/components/Admin/MarkdownSetting/LineBreakForm.jsx
  63. 1 1
      packages/app/src/components/Admin/MarkdownSetting/PresentationForm.jsx
  64. 1 1
      packages/app/src/components/Admin/MarkdownSetting/XssForm.jsx
  65. 1 1
      packages/app/src/components/Admin/Notification/GlobalNotification.jsx
  66. 12 13
      packages/app/src/components/Admin/Notification/ManageGlobalNotification.jsx
  67. 1 1
      packages/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx
  68. 1 1
      packages/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx
  69. 2 2
      packages/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx
  70. 1 1
      packages/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx
  71. 1 1
      packages/app/src/components/Admin/Security/SecuritySetting.jsx
  72. 1 1
      packages/app/src/components/Admin/Security/TwitterSecuritySettingContents.jsx
  73. 2 2
      packages/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  74. 1 1
      packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  75. 0 2
      packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx
  76. 2 2
      packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcess.jsx
  77. 2 2
      packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcessWithoutProxy.jsx
  78. 1 1
      packages/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx
  79. 3 4
      packages/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  80. 2 2
      packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  81. 5 5
      packages/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  82. 0 85
      packages/app/src/components/Admin/Users/StatusSuspendedButton.jsx
  83. 38 0
      packages/app/src/components/AlertSiteUrlUndefined.tsx
  84. 3 11
      packages/app/src/components/ArchiveCreateModal.jsx
  85. 33 12
      packages/app/src/components/Common/ImageCropModal.tsx
  86. 23 32
      packages/app/src/components/ContentLinkButtons.tsx
  87. 1 1
      packages/app/src/components/CustomNavigation/CustomNav.jsx
  88. 12 39
      packages/app/src/components/EmptyTrashButton.tsx
  89. 1 1
      packages/app/src/components/EmptyTrashModal.tsx
  90. 35 24
      packages/app/src/components/Fab.tsx
  91. 2 25
      packages/app/src/components/ForbiddenPage.tsx
  92. 1 1
      packages/app/src/components/Hotkeys/HotkeysDetector.jsx
  93. 3 4
      packages/app/src/components/Hotkeys/HotkeysManager.jsx
  94. 1 0
      packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx
  95. 1 1
      packages/app/src/components/IdenticalPathPage.tsx
  96. 4 1
      packages/app/src/components/InAppNotification/PageNotification/PageModelNotification.tsx
  97. 16 8
      packages/app/src/components/InstallerForm.tsx
  98. 8 8
      packages/app/src/components/Layout/AdminLayout.tsx
  99. 2 0
      packages/app/src/components/Layout/BasicLayout.tsx
  100. 1 1
      packages/app/src/components/Layout/RawLayout.tsx

+ 4 - 2
.github/dependabot.yml

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

+ 8 - 0
.github/workflows/ci-app-prod.yml

@@ -36,6 +36,13 @@ on:
       - packages/slack/**
       - packages/ui/**
       - packages/plugin-**
+  workflow_call:
+    inputs:
+      cypress-config-video:
+        description: 'Enable video when running Cypress test'
+        type: boolean
+        default: false
+
 
 jobs:
 
@@ -54,6 +61,7 @@ jobs:
       node-version: 16.x
       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-config-video: ${{ inputs.cypress-config-video || false }}
     secrets:
       SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
 

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

@@ -19,7 +19,7 @@ jobs:
       - uses: actions/checkout@v3
 
       - 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
 
       # Drafts your next Release notes as Pull Requests are merged into "master"
@@ -48,7 +48,7 @@ jobs:
         id: release-version
         run: |
           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
       - name: Create/Update Pull Request

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

@@ -23,10 +23,10 @@ jobs:
       run: |
         export SLACK_ATTACHMENTS_ILLEGAL=`node bin/github-actions/list-branches --illegal`
         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
       if: steps.list-branches.outputs.SLACK_ATTACHMENTS_LENGTH_ILLEGAL > 0

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

@@ -17,7 +17,7 @@ jobs:
         lfs: true
 
     - 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
 
     - name: Docker meta

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

@@ -17,7 +17,7 @@ jobs:
         ref: ${{ github.event.pull_request.base.ref }}
 
     - 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
       with:
         workingDir: packages/slackbot-proxy
@@ -115,7 +115,7 @@ jobs:
         yarn bump-versions:slackbot-proxy
 
     - 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
       with:
         workingDir: packages/slackbot-proxy

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

@@ -38,7 +38,7 @@ jobs:
         sh ./packages/app/bin/github-actions/update-readme.sh
 
     - 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
 
     - name: Update Changelog
@@ -99,7 +99,7 @@ jobs:
         yarn bump-versions:slackbot-proxy
 
     - 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
 
     - name: Commit
@@ -140,7 +140,7 @@ jobs:
       id: suffix
       run: |
         [[ ${{ matrix.flavor }} = "nocdn" ]] && suffix="-nocdn" || suffix=""
-        echo "::set-output name=SUFFIX::$suffix"
+        echo "SUFFIX=$suffix" >> $GITHUB_OUTPUT
 
     - name: Docker meta
       id: meta

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

@@ -10,6 +10,9 @@ on:
         type: boolean
       cypress-report-artifact-name:
         type: string
+      cypress-config-video:
+        type: boolean
+        default: false
     secrets:
       SLACK_WEBHOOK_URL:
         required: true
@@ -70,7 +73,7 @@ jobs:
           packages/app/.env.production* \
           packages/*/package.json \
           packages/*/dist
-        echo ::set-output name=file::production.tar.gz
+        echo "file=production.tar.gz" >> $GITHUB_OUTPUT
 
     - name: Upload production files as artifact
       uses: actions/upload-artifact@v3
@@ -126,8 +129,8 @@ jobs:
     - name: Get Date
       id: get-date
       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)
       id: cache-dependencies
@@ -247,7 +250,7 @@ jobs:
       id: determine-spec-exp
       run: |
         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
       working-directory: ./packages/app
@@ -274,6 +277,7 @@ jobs:
         spec: '${{ steps.determine-spec-exp.outputs.value }}'
         start: yarn server
         wait-on: 'http://localhost:3000'
+        config: video=${{ inputs.cypress-config-video }}
       env:
         MONGO_URI: mongodb://localhost:${{ job.services.mongodb.ports['27017'] }}/growi-vrt
         ELASTICSEARCH_URI: http://localhost:${{ job.services.elasticsearch.ports['9200'] }}/growi

+ 33 - 11
THIRD-PARTY-NOTICES.md

@@ -13,10 +13,12 @@ https://github.com/weseek/growi.
 
 
 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
@@ -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",
   "useWorkspaces": true,
-  "version": "6.0.0-RC.3",
+  "version": "6.0.0-RC.7",
   "packages": [
     "packages/*"
   ]

+ 1 - 1
package.json

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

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


+ 23 - 30
packages/app/src/client/services/PageContainer.js → packages/app/_obsolete/src/client/services/PageContainer.js

@@ -174,38 +174,31 @@ export default class PageContainer extends Container {
    */
   updateStateAfterSave(page, tags, revision, editorMode) {
     // update state of PageContainer
-    const newState = {
-      pageId: page._id,
-      revisionId: revision._id,
-      revisionCreatedAt: new Date(revision.createdAt).getTime() / 1000,
-      remoteRevisionId: revision._id,
-      revisionAuthor: revision.author,
-      revisionIdHackmdSynced: page.revisionHackmdSynced,
-      hasDraftOnHackmd: page.hasDraftOnHackmd,
-      markdown: revision.body,
-      createdAt: page.createdAt,
-      updatedAt: page.updatedAt,
-    };
-    if (tags != null) {
-      newState.tags = tags;
-    }
-    this.setState(newState);
-
-    // 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
-    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();
+    //   }
+    // }
   }
 
   /**

+ 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


+ 1 - 0
packages/app/docker/Dockerfile

@@ -110,6 +110,7 @@ COPY packages/plugin-lsx packages/plugin-lsx
 COPY packages/slack packages/slack
 COPY packages/ui packages/ui
 COPY packages/remark-growi-plugin packages/remark-growi-plugin
+COPY packages/hackmd packages/hackmd
 
 # build
 RUN yarn lerna run build

+ 19 - 16
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.0.0-RC.3",
+  "version": "6.0.0-RC.7",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -45,6 +45,7 @@
     "swagger-jsdoc": "swagger-jsdoc -o tmp/swagger.json -d config/swagger-definition.js",
     "openapi:v3": "yarn cross-env API_VERSION=3 yarn swagger-jsdoc -- \"src/server/routes/apiv3/**/*.js\" \"src/server/models/**/*.js\"",
     "openapi:v1": "yarn cross-env API_VERSION=1 yarn swagger-jsdoc -- \"src/server/*/*.js\" \"src/server/models/**/*.js\"",
+    "resources:hackmd": "yarn lerna run build --scope=@growi/hackmd",
     "resources:dummy": "true",
     "// resources:plugin": "yarn ts-node bin/generate-plugin-definitions-source.ts",
     "// resources:dl-resources": "yarn ts-node bin/download-cdn-resources.ts",
@@ -64,11 +65,12 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.0-RC.3",
-    "@growi/core": "^6.0.0-RC.3",
-    "@growi/plugin-attachment-refs": "^6.0.0-RC.3",
-    "@growi/plugin-lsx": "^6.0.0-RC.3",
-    "@growi/slack": "^6.0.0-RC.3",
+    "@growi/codemirror-textlint": "^6.0.0-RC.7",
+    "@growi/core": "^6.0.0-RC.7",
+    "@growi/hackmd": "^6.0.0-RC.7",
+    "@growi/plugin-attachment-refs": "^6.0.0-RC.7",
+    "@growi/plugin-lsx": "^6.0.0-RC.7",
+    "@growi/slack": "^6.0.0-RC.7",
     "@promster/express": "^7.0.2",
     "@promster/server": "^7.0.4",
     "@slack/events-api": "^3.0.0",
@@ -97,6 +99,7 @@
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
+    "ejs": "^3.1.8",
     "entities": "^2.0.0",
     "esa-node": "^0.2.2",
     "escape-string-regexp": "=4.0.0",
@@ -113,9 +116,9 @@
     "hast-util-select": "^5.0.2",
     "helmet": "^4.6.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-iso-date": "^0.0.1",
     "lucene-query-parser": "^1.2.0",
@@ -130,7 +133,7 @@
     "multer": "~1.4.0",
     "multer-autoreap": "^1.0.3",
     "next": "^12.2.5",
-    "next-i18next": "^11.3.0",
+    "next-i18next": "^12.1.0",
     "next-superjson": "^0.0.4",
     "next-themes": "^0.2.0",
     "nocache": "^3.0.1",
@@ -159,9 +162,9 @@
     "react-image-crop": "^8.3.0",
     "react-markdown": "^8.0.3",
     "react-multiline-clamp": "^2.0.0",
+    "react-scroll": "^1.8.7",
     "react-syntax-highlighter": "^15.5.0",
     "react-use-ripple": "^1.5.2",
-    "react-scroll": "^1.8.7",
     "reactstrap": "^8.9.0",
     "reconnecting-websocket": "^4.4.0",
     "redis": "^3.0.2",
@@ -189,21 +192,20 @@
     "toastr": "^2.1.2",
     "uglifycss": "^0.0.29",
     "universal-bunyan": "^0.9.2",
+    "unstated": "^2.1.1",
     "unzipper": "^0.10.5",
     "url-join": "^4.0.0",
     "usehooks-ts": "^2.6.0",
     "validator": "^13.7.0",
     "ws": "^8.3.0",
-    "xss": "^1.0.6",
-    "unstated": "^2.1.1"
+    "xss": "^1.0.6"
   },
   "// comments for defDependencies": {
     "@handsontable/react": "v3 requires handsontable >= 7.0.0.",
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
-    "@alienfast/i18next-loader": "^1.1.4",
-    "@growi/ui": "^6.0.0-RC.3",
+    "@growi/ui": "^6.0.0-RC.7",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",
@@ -211,6 +213,7 @@
     "@types/express": "^4.17.11",
     "@types/jquery": "^3.5.8",
     "@types/multer": "^1.4.5",
+    "@types/react-scroll": "^1.8.4",
     "autoprefixer": "^9.0.0",
     "babel-loader": "^8.2.5",
     "bootstrap": "^4.6.1",
@@ -228,7 +231,7 @@
     "eslint-plugin-regex": "^1.8.0",
     "font-awesome": "^4.7.0",
     "handsontable": "=6.2.2",
-    "i18next-hmr": "^1.7.7",
+    "i18next-hmr": "^1.11.0",
     "jquery-slimscroll": "^1.3.8",
     "jquery.cookie": "~1.4.1",
     "jshint": "^2.13.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


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

@@ -3,7 +3,6 @@
     "display_name": "English"
   },
   "wiki_management_home_page": "Wiki Management Home Page",
-  "app_settings": "App Settings",
   "security_settings": {
     "security_settings": "Security Settings",
     "Guest Users Access": "Guest users access",
@@ -46,7 +45,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",
     "Authentication mechanism settings": "Authentication Mechanism Settings",
     "setup_is_not_yet_complete": "Setup is not yet complete",
-    "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}",
     "xss_prevent_setting": "Prevent XSS(Cross Site Scripting)",
     "xss_prevent_setting_link": "Go to Markdown Settings",
     "callback_URL": "Callback URL",
@@ -292,8 +290,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>"
   },
   "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_note": "Note: You will lose unique constraints from the page paths.",
     "upgrade_to_v5": "Convert to v5 compatibility",
@@ -467,8 +463,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_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_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.",

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

@@ -0,0 +1,17 @@
+{
+  "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}}"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the {{link}}"
+  },
+  "headers": {
+    "app_settings": "App Settings"
+  }
+}

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

@@ -115,7 +115,7 @@
   "Create under": "Create page under below:",
   "V5 Page Migration": "Convert To V5 Compatibility",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "See more detail on <a href='#'>{{url}}</a> <i class='icon-share-alt'></i> ",
+  "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> ",
   "Site URL settings": "Site URL settings",
   "external_account_management": "External Account Management",
   "UserGroup": "UserGroup",
@@ -184,8 +184,7 @@
     "could_not_creata_path": "Couldn't create path."
   },
   "custom_navigation": {
-    "no_page_list": "There are no pages under this page.",
-    "link_sharing_is_disabled": "Link sharing is disabled."
+    "no_page_list": "There are no pages under this page."
   },
   "installer": {
     "setup": "Setup",
@@ -254,7 +253,8 @@
     "Unlimited": "unlimited",
     "Issue": "Issue",
     "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 Token Settings": "API token settings",
@@ -523,10 +523,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
   "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}}",
@@ -537,7 +533,8 @@
     "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"
+    "failed_to_reset_password":"Failed to reset password",
+    "save_succeeded": "Saved successfully"
   },
   "template": {
     "modal_label": {
@@ -863,5 +860,9 @@
   "footer": {
     "bookmarks": "Bookmarks",
     "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"
   }
 }

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

@@ -12,7 +12,6 @@
   "Description": "説明",
   "last_login": "最終ログイン",
   "wiki_management_home_page": "Wiki管理トップ",
-  "app_settings": "アプリ設定",
   "public": "公開",
   "anyone_with_the_link": "リンクを知っている人のみ",
   "specified_users": "特定ユーザーのみ",
@@ -62,7 +61,6 @@
     "page_delete_rights_caution": "「(子孫ページを含む)ゴミ箱に入れる操作 / 完全に削除する」の権限は、「ゴミ箱に入れる操作 / 完全に削除する」よりも強い権限になるように強制されます。 <br><br> 管理者のみ可能 > 管理者とページ作者が可能 > 誰でも可能",
     "Authentication mechanism settings": "認証機構設定",
     "setup_is_not_yet_complete":"セットアップはまだ完了してません",
-    "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。",
     "xss_prevent_setting": "XSS(Cross Site Scripting)対策設定",
     "xss_prevent_setting_link": "マークダウン設定ページに移動",
     "callback_URL": "コールバックURL",
@@ -324,8 +322,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>"
   },
   "v5_page_migration": {
-    "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
-    "go_to_settings": "設定する",
     "migration_desc": "公開されているページに 古い v4 互換形式のものが存在します。ページツリーや簡単なリネームなどの新機能を利用するには、全てのページを v5 互換形式に変換してください。",
     "migration_note": "注意: ページパスからユニーク制約が失われます。",
     "upgrade_to_v5": "v5 互換形式 へ変換",
@@ -499,8 +495,6 @@
       "select_search_scope_children_as_default": "検索範囲のデフォルト設定を「この階層下の子ページ」にする",
       "select_search_scope_children_as_default_desc": "OFFの場合、検索範囲のデフォルト設定は「全てのページ」になります。"
     },
-    "code_highlight": "コードハイライト",
-    "nocdn_desc": "この機能は、環境変数 <code>NO_CDN=true</code> の時は無効化されます。<br>GitHub スタイルが適用されています。",
     "custom_title": "カスタム Title",
     "custom_title_detail": "<code>&lt;title&gt;</code>タグのコンテンツをカスタマイズできます。以下のプレースホルダーは自動的に置換されます:",
     "custom_title_detail_placeholder1": "<code>&#123;&#123;sitename&#125;&#125;</code> - この Wiki のサイト名",

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

@@ -0,0 +1,17 @@
+{
+  "meta": {
+    "display_name": "日本語"
+  },
+  "toaster": {
+    "create_succeeded": "新しい{{target}}が作成されました",
+    "create_failed": "{{target}}の作成に失敗しました",
+    "update_successed": "{{target}}を更新しました",
+    "update_failed": "{{target}}の更新に失敗しました"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "'サイトURL' が設定されていません。{{link}} から設定してください。"
+  },
+  "headers": {
+    "app_settings": "アプリ設定"
+  }
+}

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

@@ -109,7 +109,7 @@
   "Create under": "ページを以下に作成",
   "V5 Page Migration": "V5 互換形式 への変換",
   "GROWI.5.0_new_schema": "GROWI.5.0における新スキーマについて",
-  "See_more_detail_on_new_schema": "詳しくは<a href='#'>{{url}}</a><i class='icon-share-alt'></i>を参照ください。",
+  "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>を参照ください。",
   "Site URL settings": "サイトURL設定",
   "external_account_management": "外部アカウント管理",
   "UserGroup": "グループ",
@@ -177,8 +177,7 @@
     "could_not_creata_path": "パスを作成できませんでした。"
   },
   "custom_navigation": {
-    "no_page_list": "このページの配下にはページが存在しません。",
-    "link_sharing_is_disabled": "リンクのシェアは無効化されています"
+    "no_page_list": "このページの配下にはページが存在しません。"
   },
   "installer": {
     "setup": "セットアップ",
@@ -247,7 +246,8 @@
     "Unlimited": "無期限",
     "Issue": "発行",
     "share_settings" :"共有設定",
-    "Invalid_Number_of_Date" : "有効期限の日数には整数を入力してください"
+    "Invalid_Number_of_Date" : "有効期限の日数には整数を入力してください",
+    "link_sharing_is_disabled": "リンクのシェアは無効化されています"
   },
   "API Settings": "API設定",
   "API Token Settings": "API Token設定",
@@ -514,10 +514,6 @@
     "page_not_found_in_preview": "\"{{path}}\" というページはありません。"
   },
   "toaster": {
-    "create_succeeded": "新しい{{target}}が作成されました",
-    "create_failed": "{{target}}の作成に失敗しました",
-    "update_successed": "{{target}}を更新しました",
-    "update_failed": "{{target}}の更新に失敗しました",
     "file_upload_succeeded": "ファイルをアップロードしました",
     "file_upload_failed": "ファイルのアップロードに失敗しました",
     "initialize_successed": "{{target}}を初期化しました",
@@ -528,7 +524,8 @@
     "issue_share_link": "共有リンクを作成しました",
     "remove_share_link": "共有リンクを{{count}}件削除しました",
     "switch_disable_link_sharing_success": "共有リンクの設定を変更しました",
-    "failed_to_reset_password":"パスワードのリセットに失敗しました"
+    "failed_to_reset_password":"パスワードのリセットに失敗しました",
+    "save_succeeded": "保存に成功しました"
   },
   "template": {
     "modal_label": {
@@ -854,5 +851,9 @@
   "footer": {
     "bookmarks": "ブックマーク",
     "recently_created": "最近作成したページ"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable" : "Page Tree 機能は現在使用できません。",
+    "go_to_settings": "設定する"
   }
 }

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

@@ -10,7 +10,6 @@
   "Edit": "编辑",
   "Description": "描述",
   "wiki_management_home_page": "Wiki管理首页",
-  "app_settings": "系统设置",
   "public": "公共",
   "anyone_with_the_link": "任何人",
   "specified_users": "仅指定用户",
@@ -60,7 +59,6 @@
     "page_delete_rights_caution": "\"删除/全部删除\"权限(包括后代页面)被强制强于\"删除/完全删除\"权限。 <br> <br> 仅管理员 > 管理员|作者 > 何人",
 		"Authentication mechanism settings": "身份验证机制设置",
 		"setup_is_not_yet_complete": "安装尚未完成",
-		"alert_siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置",
 		"xss_prevent_setting": "阻止XSS(跨站点脚本)",
 		"xss_prevent_setting_link": "转到Markdown设置",
 		"callback_URL": "回调URL",
@@ -279,8 +277,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>"
   },
   "v5_page_migration": {
-    "page_tree_not_avaliable": "Page Tree 功能不可用",
-    "go_to_settings": "进入设置,启用该功能",
     "migration_desc": "有一些页面具有旧的v4兼容性。为了利用新的功能,如页面树和容易重命名,请将您的所有页面转换为v5兼容性。",
     "migration_note": "注意:你将失去页面路径的唯一约束。",
     "upgrade_to_v5": "转换为v5兼容性",
@@ -464,8 +460,6 @@
       "select_search_scope_children_as_default": "选择“当前分支以下内容”, 作为搜索范围的默认值",
       "select_search_scope_children_as_default_desc": "当设置值为“关”时,“所有页面”被作为搜索范围的默认值。"
     },
-    "code_highlight": "代码突出显示",
-    "nocdn_desc": "当强制应用环境变量<code>NO_CDN=true</code><br>Github样式时,此函数被禁用。",
     "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_placeholder1": "<code>&#123;&#123;站点名称&#125;&#125;</code>-此wiki的站点名称。",

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

@@ -0,0 +1,17 @@
+{
+  "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}}"
+  },
+  "alert": {
+    "siteUrl_is_not_set": "主页URL未设置,通过 {{link}} 设置"
+  },
+  "headers": {
+    "app_settings": "系统设置"
+  }
+}

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

@@ -117,7 +117,7 @@
 	"Create under": "Create page under below:",
   "V5 Page Migration": "转换为V5的兼容性",
   "GROWI.5.0_new_schema": "GROWI.5.0 new schema",
-  "See_more_detail_on_new_schema": "更多详情请见<a href='#'>{{url}}</a> <i class='icon-share-alt'></i> ",
+  "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> ",
 	"Site URL settings": "主页URL设置",
 	"Markdown Settings": "Markdown设置",
 	"Notification Settings": "通知设置",
@@ -179,8 +179,7 @@
     "could_not_creata_path": "无法创建路径"
   },
   "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": {
 		"setup": "安装",
@@ -496,10 +495,6 @@
     "page_not_found_in_preview": "\"{{path}}\" is not a GROWI page."
   },
 	"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_failed": "文件上传失败",
     "initialize_successed": "Succeeded to initialize {{target}}",
@@ -507,7 +502,8 @@
 		"deactivate_user_success": "Succeeded to deactivate {{username}}",
 		"remove_user_success": "Succeeded to removing {{username}} ",
     "switch_disable_link_sharing_success": "成功更新分享链接设置",
-    "failed_to_reset_password":"Failed to reset password"
+    "failed_to_reset_password":"Failed to reset password",
+    "save_succeeded": "已成功保存"
   },
 	"template": {
 		"modal_label": {
@@ -583,7 +579,6 @@
     "popover_title": "Slack Notification",
     "popover_desc": "Input channel name. You can notify multiple channels by entering a comma-separated list."
   },
-  "security_settings": "安全设置",
   "share_links": {
     "Shere this page link to public": "Shere this page link to public",
     "share_link_list": "Share link list",
@@ -601,7 +596,8 @@
     "Unlimited": "unlimited",
     "Issue": "Issue",
     "share_settings" :"Share settings",
-    "Invalid_Number_of_Date" : "You entered invalid value"
+    "Invalid_Number_of_Date" : "You entered invalid value",
+    "link_sharing_is_disabled": "链接共享已被禁用"
   },
 	"notification_setting": {
 		"slack_incoming_configuration": "Slack Incoming Webhooks configuration",
@@ -910,5 +906,9 @@
   "footer": {
     "bookmarks": "书签",
     "recently_created": "最近创建页面"
+  },
+  "v5_page_migration": {
+    "page_tree_not_avaliable": "Page Tree 功能不可用",
+    "go_to_settings": "进入设置,启用该功能"
   }
 }

+ 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 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.
-    - 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)
 
@@ -29,7 +29,7 @@ For more information: [Tutorial#Create New Page](https://docs.growi.org/en/guide
   <div class="card-body">
     <ul>
       <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>
   </div>
 </div>

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

@@ -17,7 +17,7 @@ GROWI は個人・法人向けの Wiki | ナレッジベースツールです。
 - `- ` を行頭につけると、この文章のような箇条書きを書くことができます
 - 画像やPDF、Word/Excel/PowerPointなどの添付ファイルも、コピー&ペースト、ドラッグ&ドロップで貼ることができます
 - 書けたら "**更新**" ボタンを押してページを公開しましょう
-    - `Ctrl(⌘) +S` でも保存できます
+    - `Ctrl(⌘) + S` でも保存できます
 
 さらに詳しくはこちら: [チュートリアル#新規ページ作成](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-body"><ul>
     <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>
 </div>
 

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

@@ -18,7 +18,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
 - 我们可以通过在行首添加`-`来创建一个要点。
 - 我们还可以复制和粘贴,拖放附件,如图片、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)
 
@@ -29,7 +29,7 @@ GROWI是一个针对个人和公司的Wiki - 一个知识库工具。
   <div class="card-body">
     <ul>
       <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>
   </div>
 </div>

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

@@ -35,26 +35,10 @@ export default class AdminCustomizeContainer extends Container {
       isEnabledStaleNotification: false,
       isAllReplyShown: false,
       isSearchScopeChildrenAsDefault: false,
-      currentHighlightJsStyleId: '',
-      isHighlightJsStyleBorderEnabled: false,
       currentCustomizeTitle: '',
       currentCustomizeHeader: '',
       currentCustomizeCss: '',
       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.switchPageListLimitationM = this.switchPageListLimitationM.bind(this);
@@ -88,16 +72,11 @@ export default class AdminCustomizeContainer extends Container {
         isEnabledStaleNotification: customizeParams.isEnabledStaleNotification,
         isAllReplyShown: customizeParams.isAllReplyShown,
         isSearchScopeChildrenAsDefault: customizeParams.isSearchScopeChildrenAsDefault,
-        currentHighlightJsStyleId: customizeParams.styleName,
-        isHighlightJsStyleBorderEnabled: customizeParams.styleBorder,
         currentCustomizeTitle: customizeParams.customizeTitle,
         currentCustomizeHeader: customizeParams.customizeHeader,
         currentCustomizeCss: customizeParams.customizeCss,
         currentCustomizeScript: customizeParams.customizeScript,
       });
-
-      // search style name from object for display
-      this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[customizeParams.styleName].name });
     }
     catch (err) {
       this.setState({ retrieveError: err });
@@ -171,25 +150,6 @@ export default class AdminCustomizeContainer extends Container {
     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
    */
@@ -218,17 +178,6 @@ export default class AdminCustomizeContainer extends Container {
     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
@@ -266,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
    * @memberOf AdminCustomizeContainer

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

@@ -4,7 +4,6 @@ import urljoin from 'url-join';
 import { OptionsToSave } from '~/interfaces/editor-settings';
 import loggerFactory from '~/utils/logger';
 
-
 import { toastError } from '../util/apiNotification';
 import { apiPost } from '../util/apiv1-client';
 import { apiv3Post, apiv3Put } from '../util/apiv3-client';
@@ -143,8 +142,10 @@ export const saveOrUpdate = async(optionsToSave: OptionsToSave, pageInfo: PageIn
   // markdown = pageEditor.getMarkdown();
   // }
 
+  const isNoRevisionPage = pageId != null && revisionId == null;
+
   let res;
-  if (pageId == null) {
+  if (pageId == null || isNoRevisionPage) {
     res = await createPage(path, markdown, options);
   }
   else {

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

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

@@ -18,12 +18,12 @@ const logger = loggerFactory('growi:appSettings');
 
 const AppSetting = (props) => {
   const { adminAppContainer } = props;
-  const { t } = useTranslation('admin');
+  const { t } = useTranslation(['admin', 'commons']);
 
   const submitHandler = useCallback(async() => {
     try {
       await adminAppContainer.updateAppSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('app_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('commons:headers.app_settings') }));
     }
     catch (err) {
       toastError(err);

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

@@ -82,7 +82,7 @@ const AppSettingsPageContents = (props: Props) => {
 
       <div className="row">
         <div className="col-lg-12">
-          <h2 className="admin-setting-header">{t('app_settings')}</h2>
+          <h2 className="admin-setting-header">{t('commons:headers.app_settings')}</h2>
           <AppSetting />
         </div>
       </div>

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

@@ -18,7 +18,7 @@ type Props = {
 
 
 const FileUploadSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['admin', 'commons']);
   const { adminAppContainer } = props;
   const { fileUploadType } = adminAppContainer.state;
   const fileUploadTypes = ['aws', 'gcs', 'gridfs', 'local'];
@@ -26,7 +26,7 @@ const FileUploadSetting = (props: Props) => {
   const submitHandler = useCallback(async() => {
     try {
       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) {
       toastError(err);

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

@@ -17,7 +17,7 @@ type Props = {
 
 
 const MailSetting = (props: Props) => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['admin', 'commons']);
   const { adminAppContainer } = props;
 
   const transmissionMethods = ['smtp', 'ses'];
@@ -25,7 +25,7 @@ const MailSetting = (props: Props) => {
   async function submitHandler() {
     try {
       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) {
       toastError(err);

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

@@ -23,7 +23,7 @@ const PluginSetting = (props: Props) => {
   const submitHandler = useCallback(async() => {
     try {
       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) {
       toastError(err);

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

@@ -24,7 +24,7 @@ const SiteUrlSetting = (props: Props) => {
   const submitHandler = useCallback(async() => {
     try {
       await adminAppContainer.updateSiteUrlSettingHandler();
-      toastSuccess(t('toaster.update_successed', { target: t('Site URL settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Site URL settings'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -23,7 +23,7 @@ const AdminNavigation = (props) => {
   const MenuLabel = ({ menu }) => {
     switch (menu) {
       /* eslint-disable no-multi-spaces, max-len */
-      case 'app':                      return <><i className="mr-1 icon-fw icon-settings"></i>{        t('app_settings') }</>;
+      case 'app':                      return <><i className="mr-1 icon-fw icon-settings"></i>{        t('commons:headers.app_settings') }</>;
       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') }</>;

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

@@ -13,7 +13,6 @@ import { withUnstatedContainers } from '../../UnstatedUtils';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeHeaderSetting from './CustomizeHeaderSetting';
-import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLogoSetting from './CustomizeLogoSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
@@ -56,9 +55,6 @@ function Customize(props) {
       <div className="mb-5">
         <CustomizeFunctionSetting />
       </div>
-      <div className="mb-5">
-        <CustomizeHighlightSetting />
-      </div>
       <div className="mb-5">
         <CustomizeTitle />
       </div>

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

@@ -21,7 +21,7 @@ const CustomizeCssSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizeCss();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_css') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_css'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -25,7 +25,7 @@ const CustomizeFunctionSetting = (props: Props): JSX.Element => {
 
     try {
       await adminCustomizeContainer.updateCustomizeFunction();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.function') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.function'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -21,7 +21,7 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizeHeader();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_header'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -44,7 +44,7 @@ const CustomizeHeaderSetting = (props: Props): JSX.Element => {
           </Card>
           <div className="form-text text-muted">
             { t('Example') }:
-            <pre className="hljs">
+            <pre>
               {/* 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"
                 defer&gt;&lt;/script&gt;

+ 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_settings.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_settings.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_settings.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_settings.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;

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

@@ -19,7 +19,7 @@ const CustomizeLayoutSetting = (): JSX.Element => {
   const onClickSubmit = async() => {
     try {
       await apiv3Put('/customize-setting/layout', { isContainerFluid });
-      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.layout') }));
+      toastSuccess(t('toaster.update_successed', { target: t('customize_settings.layout'), ns: 'commons' }));
       mutateLayoutSetting();
     }
     catch (err) {

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

@@ -59,7 +59,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       const { customizedParams } = response.data;
       setIsDefaultLogo(customizedParams.isDefaultLogo);
       setCustomizedLogoSrc(customizedParams.customizedLogoSrc);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_logo'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -70,7 +70,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
     try {
       await apiv3Delete('/customize-setting/delete-brand-logo');
       setCustomizedLogoSrc(null);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -86,7 +86,7 @@ const CustomizeLogoSetting = (): JSX.Element => {
       formData.append('file', croppedImage);
       const { data } = await apiv3PostForm('/customize-setting/upload-brand-logo', formData);
       setCustomizedLogoSrc(data.attachment.filePathProxied);
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.current_logo'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -21,7 +21,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
     try {
       await adminCustomizeContainer.updateCustomizeScript();
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_script') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_script'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -79,7 +79,7 @@ const CustomizeScriptSetting = (props: Props): JSX.Element => {
 
           <div className="form-text text-muted">
             Examples:
-            <pre className="hljs"><code>{getExampleCode()}</code></pre>
+            <pre><code className='language-javascript'>{getExampleCode()}</code></pre>
           </div>
 
           <div className="form-group">

+ 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';
 
 const CustomizeSidebarsetting = (): JSX.Element => {
-  const { t } = useTranslation('admin');
+  const { t } = useTranslation(['admin', 'commons']);
+
   const {
     update, isSidebarDrawerMode, isSidebarClosedAtDockMode, setIsSidebarDrawerMode, setIsSidebarClosedAtDockMode,
   } = useSWRxSidebarConfig();
@@ -20,7 +21,7 @@ const CustomizeSidebarsetting = (): JSX.Element => {
   const onClickSubmit = useCallback(async() => {
     try {
       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) {
       toastError(err);

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

@@ -35,7 +35,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
         });
       }
 
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -22,7 +22,7 @@ export const CustomizeTitle: FC = () => {
       await apiv3Put('/customize-setting/customize-title', {
         customizeTitle: currentCustomizeTitle,
       });
-      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_title') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.custom_title'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -146,7 +146,7 @@ const ElasticsearchManagement = () => {
 
   return (
     <>
-      <div className="row">
+      <div data-testid="admin-full-text-search" className="row">
         <div className="col-md-12">
           <StatusTable
             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';
 
-const FullTextSearchManagement = (): JSX.Element => {
-  const { t } = useTranslation();
+export const FullTextSearchManagement = (): JSX.Element => {
+  const { t } = useTranslation('admin');
 
   return (
     <div data-testid="admin-full-text-search">
@@ -14,5 +14,3 @@ const FullTextSearchManagement = (): JSX.Element => {
     </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 PropTypes from 'prop-types';
 
-import AdminSocketIoContainer from '~/client/services/AdminSocketIoContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import GrowiArchiveImportOption from '~/models/admin/growi-archive-import-option';
 import ImportOptionForPages from '~/models/admin/import-option-for-pages';
 import ImportOptionForRevisions from '~/models/admin/import-option-for-revisions';
-
-import { withUnstatedContainers } from '../../../UnstatedUtils';
+import { useAdminSocket } from '~/stores/socket-io';
 
 
 import ErrorViewer from './ErrorViewer';
@@ -103,7 +101,7 @@ class ImportForm extends React.Component {
   }
 
   setupWebsocketEventHandler() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
+    const { socket } = this.props;
 
     // websocket event
     // eslint-disable-next-line object-curly-newline
@@ -143,7 +141,7 @@ class ImportForm extends React.Component {
   }
 
   teardownWebsocketEventHandler() {
-    const socket = this.props.adminSocketIoContainer.getSocket();
+    const { socket } = this.props;
 
     socket.removeAllListeners('admin:onProgressForImport');
     socket.removeAllListeners('admin:onTerminateForImport');
@@ -496,7 +494,7 @@ class ImportForm extends React.Component {
 
 ImportForm.propTypes = {
   t: PropTypes.func.isRequired, // i18next
-  adminSocketIoContainer: PropTypes.instanceOf(AdminSocketIoContainer).isRequired,
+  socket: PropTypes.object.isRequired,
 
   fileName: PropTypes.string,
   innerFileStats: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -506,13 +504,14 @@ ImportForm.propTypes = {
 
 const ImportFormWrapperFc = (props) => {
   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 ImportForm from './GrowiArchive/ImportForm';
+import ImportForm from './GrowiArchive/ImportForm';
 import UploadForm from './GrowiArchive/UploadForm';
 
 class GrowiArchiveSection extends React.Component {
@@ -129,12 +129,11 @@ class GrowiArchiveSection extends React.Component {
         {isTheSameVersion === false && this.renderDefferentVersionAlert()}
         {this.state.fileName != null && isTheSameVersion === true ? (
           <div className="px-4">
-            {/* show ImportForm by https://redmine.weseek.co.jp/issues/100061 */}
-            {/* <ImportForm
+            <ImportForm
               fileName={this.state.fileName}
               innerFileStats={this.state.innerFileStats}
               onDiscard={this.discardData}
-            /> */}
+            />
           </div>
         )
           : (

+ 1 - 1
packages/app/src/components/Admin/MarkdownSetting/IndentForm.tsx

@@ -26,7 +26,7 @@ const IndentForm = (props: Props) => {
   const onClickSubmit = useCallback(async(props) => {
     try {
       await props.adminMarkDownContainer.updateIndentSetting();
-      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.indent_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.indent_header'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Admin/MarkdownSetting/LineBreakForm.jsx

@@ -27,7 +27,7 @@ class LineBreakForm extends React.Component {
 
     try {
       await this.props.adminMarkDownContainer.updateLineBreakSetting();
-      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.lineBreak_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.lineBreak_header'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Admin/MarkdownSetting/PresentationForm.jsx

@@ -25,7 +25,7 @@ class PresentationForm extends React.Component {
 
     try {
       await this.props.adminMarkDownContainer.updatePresentationSetting();
-      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.presentation_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.presentation_header'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Admin/MarkdownSetting/XssForm.jsx

@@ -28,7 +28,7 @@ class XssForm extends React.Component {
 
     try {
       await this.props.adminMarkDownContainer.updateXssSetting();
-      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.xss_header') }));
+      toastSuccess(t('toaster.update_successed', { target: t('markdown_settings.xss_header'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Admin/Notification/GlobalNotification.jsx

@@ -22,7 +22,7 @@ const GlobalNotification = (props) => {
   const onClickSubmit = useCallback(async() => {
     try {
       await adminNotificationContainer.updateGlobalNotificationForPages();
-      toastSuccess(t('toaster.update_successed', { target: t('external_notification.external_notification') }));
+      toastSuccess(t('toaster.update_successed', { target: t('external_notification.external_notification'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 12 - 13
packages/app/src/components/Admin/Notification/ManageGlobalNotification.jsx

@@ -2,7 +2,6 @@ import React, { useCallback, useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import PropTypes from 'prop-types';
-import urljoin from 'url-join';
 
 import AdminNotificationContainer from '~/client/services/AdminNotificationContainer';
 import { toastError } from '~/client/util/apiNotification';
@@ -32,26 +31,27 @@ const ManageGlobalNotification = (props) => {
   const [slackChannelToSend, setSlackChannelToSend] = useState('');
   const [triggerEvents, setTriggerEvents] = useState(new Set(globalNotification?.triggerEvents));
 
-  const onChangeTriggerEvents = (triggerEvent) => {
+  const onChangeTriggerEvents = useCallback((triggerEvent) => {
+    let newTriggerEvents;
 
     if (triggerEvents.has(triggerEvent)) {
-      triggerEvents.delete(triggerEvent);
-      setTriggerEvents(triggerEvents);
+      newTriggerEvents = ([...triggerEvents].filter(item => item !== triggerEvent));
+      setTriggerEvents(new Set(newTriggerEvents));
     }
     else {
-      triggerEvents.add(triggerEvent);
-      setTriggerEvents(triggerEvents);
+      newTriggerEvents = [...triggerEvents, triggerEvent];
+      setTriggerEvents(new Set(newTriggerEvents));
     }
-  };
+  }, [triggerEvents]);
 
-  const submitHandler = useCallback(async() => {
+  const updateButtonClickedHandler = useCallback(async() => {
 
     const requestParams = {
       triggerPath,
       notifyToType,
-      emailToSend,
-      slackChannelToSend,
-      triggerEvents,
+      toEmail: emailToSend,
+      slackChannels: slackChannelToSend,
+      triggerEvents: [...triggerEvents],
     };
 
     try {
@@ -61,7 +61,6 @@ const ManageGlobalNotification = (props) => {
       else {
         await apiv3Post('/notification-setting/global-notification', requestParams);
       }
-      window.location.href = urljoin(window.location.origin, '/admin/notification#global-notification');
     }
     catch (err) {
       toastError(err);
@@ -270,7 +269,7 @@ const ManageGlobalNotification = (props) => {
       </div>
 
       <AdminUpdateButtonRow
-        onClick={submitHandler}
+        onClick={updateButtonClickedHandler}
         disabled={adminNotificationContainer.state.retrieveError != null}
       />
     </>

+ 1 - 1
packages/app/src/components/Admin/Security/GitHubSecuritySettingContents.jsx

@@ -90,7 +90,7 @@ class GitHubSecurityManagementContents extends React.Component {
                 <i
                   className="icon-exclamation"
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                  dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                 />
               </div>
             )}

+ 1 - 1
packages/app/src/components/Admin/Security/GoogleSecuritySettingContents.jsx

@@ -88,7 +88,7 @@ class GoogleSecurityManagementContents extends React.Component {
                 <i
                   className="icon-exclamation"
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                  dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                 />
               </div>
             )}

+ 2 - 2
packages/app/src/components/Admin/Security/OidcSecuritySettingContents.jsx

@@ -82,7 +82,7 @@ class OidcSecurityManagementContents extends React.Component {
                 <i
                   className="icon-exclamation"
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                  dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                 />
               </div>
             )}
@@ -378,7 +378,7 @@ class OidcSecurityManagementContents extends React.Component {
                     <i
                       className="icon-exclamation"
                       // eslint-disable-next-line max-len
-                      dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                      dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                     />
                   </div>
                 )}

+ 1 - 1
packages/app/src/components/Admin/Security/SamlSecuritySettingContents.jsx

@@ -99,7 +99,7 @@ class SamlSecurityManagementContents extends React.Component {
                 <i
                   className="icon-exclamation"
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                  dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                 />
               </div>
             )}

+ 1 - 1
packages/app/src/components/Admin/Security/SecuritySetting.jsx

@@ -453,7 +453,7 @@ class SecuritySetting extends React.Component {
           ].map(arr => this.renderPageDeletePermission(arr[0], arr[1], arr[2], arr[3]))
         }
 
-        <h4>{t('security_settings.session')}aa</h4>
+        <h4>{t('security_settings.session')}</h4>
         <div className="form-group row">
           <label className="text-left text-md-right col-md-3 col-form-label">{t('security_settings.max_age')}</label>
           <div className="col-md-6">

+ 1 - 1
packages/app/src/components/Admin/Security/TwitterSecuritySettingContents.jsx

@@ -90,7 +90,7 @@ class TwitterSecuritySettingContents extends React.Component {
                 <i
                   className="icon-exclamation"
                   // eslint-disable-next-line max-len
-                  dangerouslySetInnerHTML={{ __html: t('security_settings.alert_siteUrl_is_not_set', { link: `<a href="/admin/app">${t('app_settings')}<i class="icon-login"></i></a>` }) }}
+                  dangerouslySetInnerHTML={{ __html: t('commons:alert.siteUrl_is_not_set', { link: `<a href="/admin/app">${t('commons:headers.app_settings')}<i class="icon-login"></i></a>` }) }}
                 />
               </div>
             )}

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

@@ -50,7 +50,7 @@ const CustomBotWithProxySettings = (props) => {
       if (onPrimaryUpdated != null) {
         onPrimaryUpdated();
       }
-      toastSuccess(t('toaster.update_successed', { target: 'Primary' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Primary', ns: 'commons' }));
     }
     catch (err) {
       toastError(err, 'Failed to change isPrimary');
@@ -77,7 +77,7 @@ const CustomBotWithProxySettings = (props) => {
       await apiv3Put('/slack-integration-settings/proxy-uri', {
         proxyUri: newProxyServerUri,
       });
-      toastSuccess(t('toaster.update_successed', { target: 'Proxy URL' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Proxy URL', ns: 'commons' }));
     }
     catch (err) {
       toastError(err, 'Failed to update');

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

@@ -38,7 +38,7 @@ const CustomBotWithoutProxySecretTokenSection = (props) => {
         onUpdatedSecretToken(inputSigningSecret, inputBotToken);
       }
 
-      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_without_proxy_settings') }));
+      toastSuccess(t('toaster.update_successed', { target: t('admin:slack_integration.custom_bot_without_proxy_settings'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 0 - 2
packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySettings.jsx

@@ -5,8 +5,6 @@ import PropTypes from 'prop-types';
 
 import { useAppTitle } from '~/stores/context';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
 import CustomBotWithoutProxyConnectionStatus from './CustomBotWithoutProxyConnectionStatus';
 import CustomBotWithoutProxySettingsAccordion, { botInstallationStep } from './CustomBotWithoutProxySettingsAccordion';
 

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

@@ -1,8 +1,8 @@
 import React, { useCallback, useState } from 'react';
 
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse, defaultSupportedSlackEventActions } from '@growi/slack';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import loggerFactory from '~/utils/logger';
@@ -265,7 +265,7 @@ const ManageCommandsProcess = ({
         permissionsForSingleUseCommands: permissionsForSingleUseCommandsState,
         permissionsForSlackEventActions: permissionsForEventsState,
       });
-      toastSuccess(t('toaster.update_successed', { target: 'Token' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Token', ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -1,8 +1,8 @@
 import React, { useCallback, useEffect, useState } from 'react';
 
 import { defaultSupportedCommandsNameForBroadcastUse, defaultSupportedCommandsNameForSingleUse, defaultSupportedSlackEventActions } from '@growi/slack';
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 import { apiv3Put } from '~/client/util/apiv3-client';
 import loggerFactory from '~/utils/logger';
@@ -207,7 +207,7 @@ const ManageCommandsProcessWithoutProxy = ({ commandPermission, eventActionsPerm
         commandPermission: editingCommandPermission,
         eventActionsPermission: editingEventActionsPermission,
       });
-      toastSuccess(t('toaster.update_successed', { target: 'the permission for commands' }));
+      toastSuccess(t('toaster.update_successed', { target: 'the permission for commands', ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -46,7 +46,7 @@ const OfficialBotSettings = (props) => {
       if (onPrimaryUpdated != null) {
         onPrimaryUpdated();
       }
-      toastSuccess(t('toaster.update_successed', { target: 'Primary' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Primary', ns: 'commons' }));
     }
     catch (err) {
       toastError(err, 'Failed to change isPrimary');

+ 3 - 4
packages/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx

@@ -12,7 +12,6 @@ import { apiv3Put, apiv3Post } from '~/client/util/apiv3-client';
 import { useSiteUrl } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
 
-import { withUnstatedContainers } from '../../UnstatedUtils';
 import Accordion from '../Common/Accordion';
 
 import ManageCommandsProcess from './ManageCommandsProcess';
@@ -143,7 +142,7 @@ const CustomCopyToClipBoard = (props) => {
   );
 };
 
-const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers((props) => {
+const GeneratingTokensAndRegisteringProxyServiceProcess = (props) => {
   const { t } = useTranslation();
   const { slackAppIntegrationId } = props;
 
@@ -153,7 +152,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers
       if (props.onUpdateTokens != null) {
         props.onUpdateTokens();
       }
-      toastSuccess(t('toaster.update_successed', { target: 'Token' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Token', ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -231,7 +230,7 @@ const GeneratingTokensAndRegisteringProxyServiceProcess = withUnstatedContainers
     </div>
 
   );
-}, []);
+};
 
 const TestProcess = ({
   slackAppIntegrationId, onSubmitForm, onSubmitFormFailed, isLatestConnectionSuccess,

+ 2 - 2
packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx

@@ -93,7 +93,7 @@ export const UserGroupPage: FC = () => {
         description: userGroupData.description,
       });
 
-      toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+      toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
 
       // mutate
       await mutateUserGroups();
@@ -112,7 +112,7 @@ export const UserGroupPage: FC = () => {
         description: userGroupData.description,
       });
 
-      toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+      toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
 
       // mutate
       await mutateUserGroups();

+ 5 - 5
packages/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx

@@ -124,10 +124,10 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
     async(targetGroup: IUserGroupHasId, userGroupData: Partial<IUserGroupHasId>, forceUpdateParents: boolean): Promise<void> => {
       try {
         await updateUserGroup(targetGroup, userGroupData, forceUpdateParents);
-        toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+        toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
       }
       catch {
-        toastError(t('toaster.update_failed', { target: t('UserGroup') }));
+        toastError(t('toaster.update_failed', { target: t('UserGroup'), ns: 'commons' }));
       }
     },
     [t, updateUserGroup],
@@ -205,7 +205,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
         parentId: userGroupData.parent,
       });
 
-      toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+      toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
 
       // mutate
       mutateChildUserGroups();
@@ -244,7 +244,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
         parentId: currentUserGroupId,
       });
 
-      toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+      toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
 
       // mutate
       mutateChildUserGroups();
@@ -296,7 +296,7 @@ const UserGroupDetailPage = (props: Props): JSX.Element => {
         parentId: null,
       });
 
-      toastSuccess(t('toaster.update_successed', { target: t('UserGroup') }));
+      toastSuccess(t('toaster.update_successed', { target: t('UserGroup'), ns: 'commons' }));
 
       // mutate
       mutateChildUserGroups();

+ 0 - 85
packages/app/src/components/Admin/Users/StatusSuspendedButton.jsx

@@ -1,85 +0,0 @@
-import React, { Fragment } from 'react';
-
-import PropTypes from 'prop-types';
-import { useTranslation } from 'next-i18next';
-
-import AdminUsersContainer from '~/client/services/AdminUsersContainer';
-import AppContainer from '~/client/services/AppContainer';
-import { toastSuccess, toastError } from '~/client/util/apiNotification';
-
-import { withUnstatedContainers } from '../../UnstatedUtils';
-
-class StatusSuspendedButton extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.onClickDeactiveBtn = this.onClickDeactiveBtn.bind(this);
-  }
-
-  async onClickDeactiveBtn() {
-    const { t } = this.props;
-
-    try {
-      const username = await this.props.adminUsersContainer.deactivateUser(this.props.user._id);
-      toastSuccess(t('toaster.deactivate_user_success', { username }));
-    }
-    catch (err) {
-      toastError(err);
-    }
-  }
-
-  renderSuspendedBtn() {
-    const { t } = this.props;
-
-    return (
-      <button className="dropdown-item" type="button" onClick={() => { this.onClickDeactiveBtn() }}>
-        <i className="icon-fw icon-ban"></i> {t('admin:user_management.user_table.deactivate_account')}
-      </button>
-    );
-  }
-
-  renderSuspendedAlert() {
-    const { t } = this.props;
-
-    return (
-      <div className="px-4">
-        <i className="icon-fw icon-ban mb-2"></i>{t('admin:user_management.user_table.deactivate_account')}
-        <p className="alert alert-danger">{t('admin:user_management.user_table.your_own')}</p>
-      </div>
-    );
-  }
-
-  render() {
-    const { user } = this.props;
-    const { currentUsername } = this.props.appContainer;
-
-    return (
-      <Fragment>
-        {user.username !== currentUsername ? this.renderSuspendedBtn()
-          : this.renderSuspendedAlert()}
-      </Fragment>
-    );
-  }
-
-}
-
-const StatusSuspendedFormWrapperFC = (props) => {
-  const { t } = useTranslation();
-  return <StatusSuspendedButton t={t} {...props} />;
-};
-
-/**
- * Wrapper component for using unstated
- */
-const StatusSuspendedFormWrapper = withUnstatedContainers(StatusSuspendedFormWrapperFC, [AppContainer, AdminUsersContainer]);
-
-StatusSuspendedButton.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  adminUsersContainer: PropTypes.instanceOf(AdminUsersContainer).isRequired,
-
-  user: PropTypes.object.isRequired,
-};
-
-export default StatusSuspendedFormWrapper;

+ 38 - 0
packages/app/src/components/AlertSiteUrlUndefined.tsx

@@ -0,0 +1,38 @@
+import { useTranslation } from 'next-i18next';
+
+import { useSiteUrl } from '~/stores/context';
+
+const isValidUrl = (str: string): boolean => {
+  try {
+    // eslint-disable-next-line no-new
+    new URL(str);
+    return true;
+  }
+  catch {
+    return false;
+  }
+};
+
+export const AlertSiteUrlUndefined = (): JSX.Element => {
+  const { t } = useTranslation();
+  const { data: siteUrl, error: errorSiteUrl } = useSiteUrl();
+  const isLoadingSiteUrl = siteUrl === undefined && errorSiteUrl === undefined;
+
+  if (isLoadingSiteUrl) {
+    return <></>;
+  }
+
+  if (typeof siteUrl === 'string' && isValidUrl(siteUrl)) {
+    return <></>;
+  }
+
+  return (
+    <div className="alert alert-danger rounded-0 d-edit-none mb-0 px-4 py-2">
+      <i className="icon-exclamation"></i>
+      {
+        t('commons:alert.siteUrl_is_not_set', { link: t('commons:headers.app_settings') })
+      } &gt;&gt; <a href="/admin/app">{t('commons:headers.app_settings')}<i className="icon-login"></i></a>
+    </div>
+  );
+};
+AlertSiteUrlUndefined.displayName = 'AlertSiteUrlUndefined';

+ 3 - 11
packages/app/src/components/ArchiveCreateModal.jsx

@@ -1,17 +1,14 @@
 import React, { useState, useCallback } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 
-import AppContainer from '~/client/services/AppContainer';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 
-import { withUnstatedContainers } from './UnstatedUtils';
-
 
 const ArchiveCreateModal = (props) => {
   const { t } = useTranslation();
@@ -235,7 +232,7 @@ const ArchiveCreateModal = (props) => {
 };
 
 ArchiveCreateModal.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  // appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   isOpen: PropTypes.bool.isRequired,
   onClose: PropTypes.func,
   path: PropTypes.string.isRequired,
@@ -243,9 +240,4 @@ ArchiveCreateModal.propTypes = {
   errorMessage: PropTypes.string,
 };
 
-/**
- * Wrapper component for using unstated
- */
-const ArchiveCreateModalWrapper = withUnstatedContainers(ArchiveCreateModal, [AppContainer]);
-
-export default ArchiveCreateModalWrapper;
+export default ArchiveCreateModal;

+ 33 - 12
packages/app/src/components/Common/ImageCropModal.tsx

@@ -49,6 +49,14 @@ const ImageCropModal: FC<Props> = (props: Props) => {
   const { t } = useTranslation();
   const reset = useCallback(() => {
     if (imageRef) {
+      // Some SVG files may not have width and height properties, causing the render size to be 0x0
+      // Force imageRef to have width and height by create temporary image element then set the imageRef width with tempImage width
+      // Set imageRef width & height by natural width / height if image has no dimension
+      if (imageRef.width === 0 || imageRef.height === 0) {
+        imageRef.width = imageRef.naturalWidth;
+        imageRef.height = imageRef.naturalHeight;
+      }
+      // Get size of Image, min value of width and height
       const size = Math.min(imageRef.width, imageRef.height);
       setCropOtions({
         aspect: 1,
@@ -63,6 +71,7 @@ const ImageCropModal: FC<Props> = (props: Props) => {
 
   useEffect(() => {
     document.body.style.position = 'static';
+    setIsCropImage(true);
     reset();
   }, [reset]);
 
@@ -73,18 +82,22 @@ const ImageCropModal: FC<Props> = (props: Props) => {
   };
 
 
-  const onCropChange = (crop) => {
-    setCropOtions(crop);
-  };
+  const getCroppedImg = async(image: HTMLImageElement, crop: ICropOptions) => {
+    const {
+      naturalWidth: imageNaturalWidth, naturalHeight: imageNaturalHeight, width: imageWidth, height: imageHeight,
+    } = image;
+
+    const {
+      width: cropWidth, height: cropHeight, x, y,
+    } = crop;
 
-  const getCroppedImg = async(image, crop) => {
     const canvas = document.createElement('canvas');
-    const scaleX = image.naturalWidth / image.width;
-    const scaleY = image.naturalHeight / image.height;
-    canvas.width = crop.width;
-    canvas.height = crop.height;
+    const scaleX = imageNaturalWidth / imageWidth;
+    const scaleY = imageNaturalHeight / imageHeight;
+    canvas.width = cropWidth;
+    canvas.height = cropHeight;
     const ctx = canvas.getContext('2d');
-    ctx?.drawImage(image, crop.x * scaleX, crop.y * scaleY, crop.width * scaleX, crop.height * scaleY, 0, 0, crop.width, crop.height);
+    ctx?.drawImage(image, x * scaleX, y * scaleY, cropWidth * scaleX, cropHeight * scaleY, 0, 0, cropWidth, cropHeight);
     try {
       const blob = await canvasToBlob(canvas);
       return blob;
@@ -105,7 +118,6 @@ const ImageCropModal: FC<Props> = (props: Props) => {
   // Clear image and set isImageCrop true on modal close
   const onModalCloseHandler = async() => {
     setImageRef(null);
-    setIsCropImage(true);
     onModalClose();
   };
 
@@ -129,12 +141,21 @@ const ImageCropModal: FC<Props> = (props: Props) => {
       <ModalBody className="my-4">
         {
           isCropImage
-            ? (<ReactCrop src={src} crop={cropOptions} onImageLoaded={onImageLoaded} onChange={onCropChange} circularCrop={isCircular} />)
+            ? (
+              <ReactCrop
+                style={{ backgroundColor: 'transparent' }}
+                src={src}
+                crop={cropOptions}
+                onImageLoaded={onImageLoaded}
+                onChange={crop => setCropOtions(crop)}
+                circularCrop={isCircular}
+              />
+            )
             : (<img style={{ maxWidth: imageRef?.width }} src={imageRef?.src} />)
         }
       </ModalBody>
       <ModalFooter>
-        <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" onClick={reset}>
+        <button type="button" className="btn btn-outline-danger rounded-pill mr-auto" disabled={!isCropImage} onClick={reset}>
           {t('crop_image_modal.reset')}
         </button>
         { !showCropOption && (

+ 23 - 32
packages/app/src/components/ContentLinkButtons.tsx

@@ -1,31 +1,27 @@
-import React, { useCallback } from 'react';
+import React from 'react';
 
-import type { IUserHasId } from '@growi/core';
+import { IUserHasId } from '@growi/core';
+import { Link as ScrollLink } from 'react-scroll';
 
-import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
+import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { RecentlyCreatedIcon } from '~/components/Icons/RecentlyCreatedIcon';
 
 import styles from './ContentLinkButtons.module.scss';
 
-const WIKI_HEADER_LINK = 120;
+const OFFSET = -120;
 
 const BookMarkLinkButton = React.memo(() => {
 
-  const BookMarkLinkButtonClickHandler = useCallback(() => {
-    const getBookMarkListHeaderDom = document.getElementById('bookmarks-list');
-    if (getBookMarkListHeaderDom == null) { return }
-    smoothScrollIntoView(getBookMarkListHeaderDom, WIKI_HEADER_LINK);
-  }, []);
-
   return (
-    <button
-      type="button"
-      className="btn btn-outline-secondary btn-sm px-2"
-      onClick={BookMarkLinkButtonClickHandler}
-    >
-      <i className="fa fa-fw fa-bookmark-o"></i>
-      <span>Bookmarks</span>
-    </button>
+    <ScrollLink to="bookmarks-list" offset={OFFSET} {...DEFAULT_AUTO_SCROLL_OPTS}>
+      <button
+        type="button"
+        className="btn btn-outline-secondary btn-sm px-2"
+      >
+        <i className="fa fa-fw fa-bookmark-o"></i>
+        <span>Bookmarks</span>
+      </button>
+    </ScrollLink>
   );
 });
 
@@ -33,21 +29,16 @@ BookMarkLinkButton.displayName = 'BookMarkLinkButton';
 
 const RecentlyCreatedLinkButton = React.memo(() => {
 
-  const RecentlyCreatedListButtonClickHandler = useCallback(() => {
-    const getRecentlyCreatedListHeaderDom = document.getElementById('recently-created-list');
-    if (getRecentlyCreatedListHeaderDom == null) { return }
-    smoothScrollIntoView(getRecentlyCreatedListHeaderDom, WIKI_HEADER_LINK);
-  }, []);
-
   return (
-    <button
-      type="button"
-      className="btn btn-outline-secondary btn-sm px-3"
-      onClick={RecentlyCreatedListButtonClickHandler}
-    >
-      <i className={`${styles['grw-icon-container-recently-created']} grw-icon-container-recently-created mr-2`}><RecentlyCreatedIcon /></i>
-      <span>Recently Created</span>
-    </button>
+    <ScrollLink to="recently-created-list" offset={OFFSET} {...DEFAULT_AUTO_SCROLL_OPTS}>
+      <button
+        type="button"
+        className="btn btn-outline-secondary btn-sm px-3"
+      >
+        <i className={`${styles['grw-icon-container-recently-created']} grw-icon-container-recently-created mr-2`}><RecentlyCreatedIcon /></i>
+        <span>Recently Created</span>
+      </button>
+    </ScrollLink>
   );
 });
 

+ 1 - 1
packages/app/src/components/CustomNavigation/CustomNav.jsx

@@ -20,7 +20,7 @@ function getBreakpointOneLevelLarger(breakpoint) {
       return 'xl';
     case 'xl':
     default:
-      return '2xl';
+      return 'xxl';
   }
 }
 

+ 12 - 39
packages/app/src/components/EmptyTrashButton.tsx

@@ -1,55 +1,28 @@
-import React, { FC, useCallback } from 'react';
+import React, { useCallback } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import { toastSuccess } from '~/client/util/apiNotification';
-import {
-  IDataWithMeta,
-  IPageHasId,
-  IPageInfo,
-} from '~/interfaces/page';
-import { useEmptyTrashModal } from '~/stores/modal';
-import { useSWRxDescendantsPageListForCurrrentPath, useSWRxPageInfoForList } from '~/stores/page-listing';
+type EmptyTrashButtonProps = {
+  onEmptyTrashButtonClick: () => void,
+  disableEmptyButton: boolean
+};
 
 
-const EmptyTrashButton: FC = () => {
+const EmptyTrashButton = (props: EmptyTrashButtonProps): JSX.Element => {
+  const { onEmptyTrashButtonClick, disableEmptyButton } = props;
   const { t } = useTranslation();
-  const { open: openEmptyTrashModal } = useEmptyTrashModal();
-  const { data: pagingResult, mutate } = useSWRxDescendantsPageListForCurrrentPath();
-
-  const pageIds = pagingResult?.items?.map(page => page._id);
-  const { injectTo } = useSWRxPageInfoForList(pageIds, null, true, true);
-
-  let pageWithMetas: IDataWithMeta<IPageHasId, IPageInfo>[] = [];
-
-  const convertToIDataWithMeta = (page) => {
-    return { data: page };
-  };
-
-  if (pagingResult != null) {
-    const dataWithMetas = pagingResult.items.map(page => convertToIDataWithMeta(page));
-    pageWithMetas = injectTo(dataWithMetas);
-  }
-
-  const deletablePages = pageWithMetas.filter(page => page.meta?.isAbleToDeleteCompletely);
-
-  const onEmptiedTrashHandler = useCallback(() => {
-    toastSuccess(t('empty_trash'));
-
-    mutate();
-  }, [t, mutate]);
 
-  const emptyTrashClickHandler = () => {
-    openEmptyTrashModal(deletablePages, { onEmptiedTrash: onEmptiedTrashHandler, canDelepeAllPages: pagingResult?.totalCount === deletablePages.length });
-  };
+  const emptyTrashButtonHandler = useCallback(() => {
+    onEmptyTrashButtonClick();
+  }, [onEmptyTrashButtonClick]);
 
   return (
     <div className="d-flex align-items-center">
       <button
         type="button"
         className="btn btn-outline-secondary rounded-pill text-danger d-flex align-items-center"
-        disabled={deletablePages.length === 0}
-        onClick={() => emptyTrashClickHandler()}
+        disabled={disableEmptyButton}
+        onClick={emptyTrashButtonHandler}
       >
         <i className="icon-fw icon-trash"></i>
         <div>{t('modal_empty.empty_the_trash')}</div>

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

@@ -19,7 +19,7 @@ const EmptyTrashModal: FC = () => {
 
   const isOpened = emptyTrashModalData?.isOpened ?? false;
 
-  const canDeleteAllpages = emptyTrashModalData?.opts?.canDelepeAllPages ?? false;
+  const canDeleteAllpages = emptyTrashModalData?.opts?.canDeleteAllPages ?? false;
 
   const [errs, setErrs] = useState<Error[] | null>(null);
 

+ 35 - 24
packages/app/src/components/Fab.tsx

@@ -1,11 +1,12 @@
 import React, {
-  useState, useCallback, useEffect, useRef,
+  useState, useCallback, useRef,
 } from 'react';
 
+import { animateScroll } from 'react-scroll';
 import { useRipple } from 'react-use-ripple';
 import StickyEvents from 'sticky-events';
 
-import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
+import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
 import { useCurrentPagePath, useCurrentUser } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import loggerFactory from '~/utils/logger';
@@ -59,39 +60,49 @@ export const Fab = (): JSX.Element => {
   //   };
   // }, [stickyChangeHandler]);
 
-  if (currentPath == null) {
-    return <></>;
-  }
-
-  const renderPageCreateButton = () => {
+  const PageCreateButton = useCallback(() => {
     return (
-      <>
-        <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: '2.3rem', right: '4rem' }}>
-          <button
-            type="button"
-            className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}
-            ref={createBtnRef}
-            onClick={() => openCreateModal(currentPath)}
-          >
-            <CreatePageIcon />
-          </button>
-        </div>
-      </>
+      <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: '2.3rem', right: '4rem' }}>
+        <button
+          type="button"
+          className={`btn btn-lg btn-create-page btn-primary rounded-circle p-0 ${buttonClasses}`}
+          ref={createBtnRef}
+          onClick={currentPath != null
+            ? () => openCreateModal(currentPath)
+            : undefined}
+        >
+          <CreatePageIcon />
+        </button>
+      </div>
     );
-  };
+  }, [animateClasses, buttonClasses, currentPath, openCreateModal]);
 
-  return (
-    <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab">
-      {currentUser != null && renderPageCreateButton()}
+  const ScrollToTopButton = useCallback(() => {
+    const clickHandler = () => {
+      animateScroll.scrollToTop(DEFAULT_AUTO_SCROLL_OPTS);
+    };
+
+    return (
       <div className={`rounded-circle position-absolute ${animateClasses}`} style={{ bottom: 0, right: 0 }} data-testid="grw-fab-return-to-top">
         <button
           type="button"
           className={`btn btn-light btn-scroll-to-top rounded-circle p-0 ${buttonClasses}`}
-          onClick={() => smoothScrollIntoView()}
+          onClick={clickHandler}
         >
           <ReturnTopIcon />
         </button>
       </div>
+    );
+  }, [animateClasses, buttonClasses]);
+
+  if (currentPath == null) {
+    return <></>;
+  }
+
+  return (
+    <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
+      {currentUser != null && <PageCreateButton />}
+      <ScrollToTopButton />
     </div>
   );
 

+ 2 - 25
packages/app/src/components/ForbiddenPage.tsx

@@ -1,12 +1,7 @@
-import React, { useMemo } from 'react';
+import React from 'react';
 
 import { useTranslation } from 'next-i18next';
 
-import CustomNavAndContents from './CustomNavigation/CustomNavAndContents';
-import { DescendantsPageListForCurrentPath } from './DescendantsPageList';
-import PageListIcon from './Icons/PageListIcon';
-
-
 type Props = {
   isLinkSharingDisabled?: boolean,
 }
@@ -14,17 +9,6 @@ type Props = {
 const ForbiddenPage = React.memo((props: Props): JSX.Element => {
   const { t } = useTranslation();
 
-  const navTabMapping = useMemo(() => {
-    return {
-      pagelist: {
-        Icon: PageListIcon,
-        Content: DescendantsPageListForCurrentPath,
-        i18n: t('page_list'),
-        index: 0,
-      },
-    };
-  }, [t]);
-
   return (
     <>
       <div className="row not-found-message-row mb-4">
@@ -40,17 +24,10 @@ const ForbiddenPage = React.memo((props: Props): JSX.Element => {
         <div className="col-sm-12">
           <p className="alert alert-primary py-3 px-4">
             <i className="icon-fw icon-lock" aria-hidden="true" />
-            { props.isLinkSharingDisabled ? t('custom_navigation.link_sharing_is_disabled') : t('Browsing of this page is restricted')}
+            { props.isLinkSharingDisabled ? t('share_links.link_sharing_is_disabled') : t('Browsing of this page is restricted')}
           </p>
         </div>
       </div>
-
-      { !props.isLinkSharingDisabled && (
-        <div className="mt-5">
-          <CustomNavAndContents navTabMapping={navTabMapping} />
-        </div>
-      ) }
-
     </>
   );
 });

+ 1 - 1
packages/app/src/components/Hotkeys/HotkeysDetector.jsx

@@ -1,6 +1,6 @@
 import React, { useMemo, useCallback } from 'react';
-import PropTypes from 'prop-types';
 
+import PropTypes from 'prop-types';
 import { GlobalHotKeys } from 'react-hotkeys';
 
 import HotkeyStroke from '~/client/models/HotkeyStroke';

+ 3 - 4
packages/app/src/components/Hotkeys/HotkeysManager.jsx

@@ -1,13 +1,12 @@
 import React, { useState } from 'react';
 
 import HotkeysDetector from './HotkeysDetector';
-
-import ShowStaffCredit from './Subscribers/ShowStaffCredit';
-import SwitchToMirrorMode from './Subscribers/SwitchToMirrorMode';
-import ShowShortcutsModal from './Subscribers/ShowShortcutsModal';
 import CreatePage from './Subscribers/CreatePage';
 import EditPage from './Subscribers/EditPage';
 import FocusToGlobalSearch from './Subscribers/FocusToGlobalSearch';
+import ShowShortcutsModal from './Subscribers/ShowShortcutsModal';
+import ShowStaffCredit from './Subscribers/ShowStaffCredit';
+import SwitchToMirrorMode from './Subscribers/SwitchToMirrorMode';
 
 // define supported components list
 const SUPPORTED_COMPONENTS = [

+ 1 - 0
packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx

@@ -1,4 +1,5 @@
 import React, { useEffect } from 'react';
+
 import PropTypes from 'prop-types';
 
 const SwitchToMirrorMode = (props) => {

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

@@ -40,7 +40,7 @@ const IdenticalPathAlert : FC<IdenticalPathAlertProps> = (props: IdenticalPathAl
           { path: _path, pageName: _pageName })}<br />
         <span
           // eslint-disable-next-line react/no-danger
-          dangerouslySetInnerHTML={{ __html: t('See_more_detail_on_new_schema', { url: t('GROWI.5.0_new_schema') }) }}
+          dangerouslySetInnerHTML={{ __html: t('See_more_detail_on_new_schema', { title: t('GROWI.5.0_new_schema') }) }}
         />
       </p>
       <p className="mb-1">{t('duplicated_page_alert.select_page_to_see')}</p>

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

@@ -4,6 +4,7 @@ import React, {
 
 import { HasObjectId } from '@growi/core';
 import { PagePathLabel } from '@growi/ui';
+import { useRouter } from 'next/router';
 
 import { IInAppNotificationOpenable } from '~/client/interfaces/in-app-notification-openable';
 import { IInAppNotification } from '~/interfaces/in-app-notification';
@@ -24,6 +25,8 @@ const PageModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable
     notification, actionMsg, actionIcon, actionUsers,
   } = props;
 
+  const router = useRouter();
+
   const snapshot = parseSnapshot(notification.snapshot);
 
   // publish open()
@@ -33,7 +36,7 @@ const PageModelNotification: ForwardRefRenderFunction<IInAppNotificationOpenable
         // jump to target page
         const targetPagePath = notification.target.path;
         if (targetPagePath != null) {
-          window.location.href = targetPagePath;
+          router.push(targetPagePath);
         }
       }
     },

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

@@ -2,8 +2,8 @@ import {
   FormEventHandler, memo, useCallback, useState,
 } from 'react';
 
-import i18next from 'i18next';
-import { useTranslation, i18n } from 'next-i18next';
+import { useTranslation } from 'next-i18next';
+import { useRouter } from 'next/router';
 
 import { i18n as i18nConfig } from '^/config/next-i18next.config';
 
@@ -11,10 +11,13 @@ import { toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 
 const InstallerForm = memo((): JSX.Element => {
-  const { t } = useTranslation();
+  const { t, i18n } = useTranslation();
+
+  const router = useRouter();
 
   const [isValidUserName, setValidUserName] = useState(true);
   const [isSubmittingDisabled, setSubmittingDisabled] = useState(false);
+  const [currentLocale, setCurrentLocale] = useState('en_US');
 
   const checkUserName = useCallback(async(event) => {
     const axios = require('axios').create({
@@ -28,6 +31,11 @@ const InstallerForm = memo((): JSX.Element => {
     setValidUserName(res.data.valid);
   }, []);
 
+  const onClickLanguageItem = useCallback((locale) => {
+    i18n.changeLanguage(locale);
+    setCurrentLocale(locale);
+  }, [i18n]);
+
   const submitHandler: FormEventHandler = useCallback(async(e: any) => {
     e.preventDefault();
 
@@ -59,13 +67,13 @@ const InstallerForm = memo((): JSX.Element => {
         name,
         email,
         password,
-        'app:globalLang': formData['registerForm[app:globalLang]'].value,
+        'app:globalLang': currentLocale,
       },
     };
 
     try {
       await apiv3Post('/installer', data);
-      window.location.href = '/';
+      router.push('/');
     }
     catch (errs) {
       const err = errs[0];
@@ -73,12 +81,12 @@ const InstallerForm = memo((): JSX.Element => {
 
       if (code === 'failed_to_login_after_install') {
         toastError(t('installer.failed_to_login_after_install'));
-        setTimeout(() => { window.location.href = '/login' }, 700); // Wait 700 ms to show toastr
+        setTimeout(() => { router.push('/login') }, 700); // Wait 700 ms to show toastr
       }
 
       toastError(t('installer.failed_to_install'));
     }
-  }, [isSubmittingDisabled, t]);
+  }, [isSubmittingDisabled, currentLocale, router, t]);
 
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const unavailableUserId = isValidUserName
@@ -132,7 +140,7 @@ const InstallerForm = memo((): JSX.Element => {
                         data-testid={`dropdownLanguageMenu-${locale}`}
                         className="dropdown-item"
                         type="button"
-                        onClick={() => { i18next.changeLanguage(locale) }}
+                        onClick={() => { onClickLanguageItem(locale) }}
                       >
                         {fixedT?.('meta.display_name')}
                       </button>

+ 8 - 8
packages/app/src/components/Layout/AdminLayout.tsx

@@ -8,24 +8,21 @@ import { RawLayout } from './RawLayout';
 
 import styles from './Admin.module.scss';
 
+
+const HotkeysManager = dynamic(() => import('../Hotkeys/HotkeysManager'), { ssr: false });
+
 const AdminNotFoundPage = dynamic(() => import('../Admin/NotFoundPage').then(mod => mod.AdminNotFoundPage), { ssr: false });
 
 
 type Props = {
   title: string
   componentTitle: string
-  /**
-   * Set the current option of AdminNavigation
-   * Expected it is in ["home", "app", "security", "markdown", "customize", "importer", "export",
-   * "notification", 'global-notification', "users", "user-groups", "search"]
-   */
-  selectedNavOpt: string
   children?: ReactNode
 }
 
 
 const AdminLayout = ({
-  children, title, selectedNavOpt, componentTitle,
+  children, title, componentTitle,
 }: Props): JSX.Element => {
 
   const AdminNavigation = dynamic(() => import('~/components/Admin/Common/AdminNavigation'), { ssr: false });
@@ -43,7 +40,7 @@ const AdminLayout = ({
           <div className="container-fluid">
             <div className="row">
               <div className="col-lg-3">
-                <AdminNavigation selected={selectedNavOpt} />
+                <AdminNavigation />
               </div>
               <div className="col-lg-9">
                 {children || <AdminNotFoundPage />}
@@ -54,6 +51,9 @@ const AdminLayout = ({
 
         <SystemVersion />
       </div>
+
+      <HotkeysManager />
+
     </RawLayout>
   );
 };

+ 2 - 0
packages/app/src/components/Layout/BasicLayout.tsx

@@ -9,6 +9,7 @@ import Sidebar from '../Sidebar';
 
 import { RawLayout } from './RawLayout';
 
+const AlertSiteUrlUndefined = dynamic(() => import('../AlertSiteUrlUndefined').then(mod => mod.AlertSiteUrlUndefined), { ssr: false });
 const HotkeysManager = dynamic(() => import('../Hotkeys/HotkeysManager'), { ssr: false });
 // const PageCreateModal = dynamic(() => import('../client/js/components/PageCreateModal'), { ssr: false });
 const GrowiNavbarBottom = dynamic(() => import('../Navbar/GrowiNavbarBottom').then(mod => mod.GrowiNavbarBottom), { ssr: false });
@@ -51,6 +52,7 @@ export const BasicLayout = ({
           </div>
 
           <div className="flex-fill mw-0" style={{ position: 'relative' }}>
+            <AlertSiteUrlUndefined />
             {children}
           </div>
         </div>

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

@@ -21,7 +21,7 @@ type Props = {
 }
 
 export const RawLayout = ({ children, title, className }: Props): JSX.Element => {
-  const classNames: string[] = ['wrapper'];
+  const classNames: string[] = ['layout-root'];
   if (className != null) {
     classNames.push(className);
   }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff