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

Merge branch 'fix/107104-vrt-page-delete-modal-is-shown-successfully-failed' of https://github.com/weseek/growi into fix/107104-vrt-page-delete-modal-is-shown-successfully-failed

kaori 3 лет назад
Родитель
Сommit
81651dffde
69 измененных файлов с 221 добавлено и 204 удалено
  1. 11 0
      packages/app/public/static/locales/en_US/commons.json
  2. 0 4
      packages/app/public/static/locales/en_US/translation.json
  3. 11 0
      packages/app/public/static/locales/ja_JP/commons.json
  4. 0 4
      packages/app/public/static/locales/ja_JP/translation.json
  5. 11 0
      packages/app/public/static/locales/zh_CN/commons.json
  6. 0 4
      packages/app/public/static/locales/zh_CN/translation.json
  7. 2 2
      packages/app/src/components/Admin/App/AppSetting.jsx
  8. 2 2
      packages/app/src/components/Admin/App/FileUploadSetting.tsx
  9. 2 2
      packages/app/src/components/Admin/App/MailSetting.tsx
  10. 1 1
      packages/app/src/components/Admin/App/PluginSetting.tsx
  11. 1 1
      packages/app/src/components/Admin/App/SiteUrlSetting.tsx
  12. 1 1
      packages/app/src/components/Admin/Customize/CustomizeCssSetting.tsx
  13. 1 1
      packages/app/src/components/Admin/Customize/CustomizeFunctionSetting.tsx
  14. 1 1
      packages/app/src/components/Admin/Customize/CustomizeHeaderSetting.tsx
  15. 1 1
      packages/app/src/components/Admin/Customize/CustomizeLayoutSetting.tsx
  16. 3 3
      packages/app/src/components/Admin/Customize/CustomizeLogoSetting.tsx
  17. 1 1
      packages/app/src/components/Admin/Customize/CustomizeScriptSetting.tsx
  18. 3 2
      packages/app/src/components/Admin/Customize/CustomizeSidebarSetting.tsx
  19. 1 1
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  20. 1 1
      packages/app/src/components/Admin/Customize/CustomizeTitle.tsx
  21. 1 1
      packages/app/src/components/Admin/MarkdownSetting/IndentForm.tsx
  22. 1 1
      packages/app/src/components/Admin/MarkdownSetting/LineBreakForm.jsx
  23. 1 1
      packages/app/src/components/Admin/MarkdownSetting/PresentationForm.jsx
  24. 1 1
      packages/app/src/components/Admin/MarkdownSetting/XssForm.jsx
  25. 1 1
      packages/app/src/components/Admin/Notification/GlobalNotification.jsx
  26. 1 1
      packages/app/src/components/Admin/Security/SecuritySetting.jsx
  27. 2 2
      packages/app/src/components/Admin/SlackIntegration/CustomBotWithProxySettings.jsx
  28. 1 1
      packages/app/src/components/Admin/SlackIntegration/CustomBotWithoutProxySecretTokenSection.jsx
  29. 2 2
      packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcess.jsx
  30. 2 2
      packages/app/src/components/Admin/SlackIntegration/ManageCommandsProcessWithoutProxy.jsx
  31. 1 1
      packages/app/src/components/Admin/SlackIntegration/OfficialBotSettings.jsx
  32. 1 1
      packages/app/src/components/Admin/SlackIntegration/WithProxyAccordions.jsx
  33. 2 2
      packages/app/src/components/Admin/UserGroup/UserGroupPage.tsx
  34. 5 5
      packages/app/src/components/Admin/UserGroupDetail/UserGroupDetailPage.tsx
  35. 1 0
      packages/app/src/components/Hotkeys/Subscribers/SwitchToMirrorMode.jsx
  36. 6 0
      packages/app/src/components/Layout/AdminLayout.tsx
  37. 1 1
      packages/app/src/components/Layout/RawLayout.tsx
  38. 1 1
      packages/app/src/components/Me/ApiSettings.tsx
  39. 1 1
      packages/app/src/components/Me/BasicInfoSettings.tsx
  40. 1 1
      packages/app/src/components/Me/EditorSettings.tsx
  41. 1 1
      packages/app/src/components/Me/InAppNotificationSettings.tsx
  42. 2 2
      packages/app/src/components/Me/PasswordSettings.jsx
  43. 3 3
      packages/app/src/components/Me/ProfileImageSettings.tsx
  44. 1 1
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  45. 11 13
      packages/app/src/components/Page/DisplaySwitcher.tsx
  46. 1 1
      packages/app/src/components/PageComment.tsx
  47. 22 14
      packages/app/src/components/PageEditor/CodeMirrorEditor.jsx
  48. 2 2
      packages/app/src/components/PasswordResetExecutionForm.tsx
  49. 9 7
      packages/app/src/components/Sidebar/Tag.tsx
  50. 9 18
      packages/app/src/pages/[[...path]].page.tsx
  51. 3 3
      packages/app/src/pages/admin/[[...path]].page.tsx
  52. 1 1
      packages/app/src/pages/forgot-password.page.tsx
  53. 1 1
      packages/app/src/pages/me/[[...path]].page.tsx
  54. 1 1
      packages/app/src/pages/reset-password.page.tsx
  55. 1 1
      packages/app/src/pages/tags.page.tsx
  56. 3 1
      packages/app/src/server/routes/index.js
  57. 33 43
      packages/app/src/stores/ui.tsx
  58. 4 4
      packages/app/src/styles/_editor.scss
  59. 2 2
      packages/app/src/styles/_layout.scss
  60. 1 1
      packages/app/src/styles/_old-ios.scss
  61. 1 1
      packages/app/src/styles/_variables.scss
  62. 2 6
      packages/app/src/styles/style-next.scss
  63. 1 1
      packages/app/src/styles/theme/_apply-colors-light.scss
  64. 2 2
      packages/app/src/styles/theme/_apply-colors.scss
  65. 7 8
      packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts
  66. 2 2
      packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts
  67. 2 2
      packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts
  68. 1 1
      packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts
  69. 5 3
      packages/app/test/cypress/integration/60-home/home.spec.ts

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

@@ -0,0 +1,11 @@
+{
+  "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}}"
+  }
+}

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

@@ -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}}",

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

@@ -0,0 +1,11 @@
+{
+  "meta": {
+    "display_name": "日本語"
+  },
+  "toaster": {
+    "create_succeeded": "新しい{{target}}が作成されました",
+    "create_failed": "{{target}}の作成に失敗しました",
+    "update_successed": "{{target}}を更新しました",
+    "update_failed": "{{target}}の更新に失敗しました"
+  }
+}

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

@@ -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}}を初期化しました",

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

@@ -0,0 +1,11 @@
+{
+  "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}}"
+  }
+}

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

@@ -496,10 +496,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}}",

+ 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('app_settings'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

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

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

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

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

+ 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">

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

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

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

@@ -153,7 +153,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);

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

+ 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) => {

+ 6 - 0
packages/app/src/components/Layout/AdminLayout.tsx

@@ -8,6 +8,9 @@ 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 });
 
 
@@ -54,6 +57,9 @@ const AdminLayout = ({
 
         <SystemVersion />
       </div>
+
+      <HotkeysManager />
+
     </RawLayout>
   );
 };

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

+ 1 - 1
packages/app/src/components/Me/ApiSettings.tsx

@@ -19,7 +19,7 @@ const ApiSettings = React.memo((): JSX.Element => {
       await apiv3Put('/personal-setting/api-token');
       mutateDatabaseData();
 
-      toastSuccess(t('toaster.update_successed', { target: t('page_me_apitoken.api_token') }));
+      toastSuccess(t('toaster.update_successed', { target: t('page_me_apitoken.api_token'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Me/BasicInfoSettings.tsx

@@ -22,7 +22,7 @@ export const BasicInfoSettings = (): JSX.Element => {
     try {
       await updateBasicInfo();
       sync();
-      toastSuccess(t('toaster.update_successed', { target: t('Basic Info') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Basic Info'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Me/EditorSettings.tsx

@@ -234,7 +234,7 @@ export const EditorSettings = memo((): JSX.Element => {
   const updateRulesHandler = useCallback(async() => {
     try {
       await updateEditorSettings({ textlintSettings: { textlintRules } });
-      toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
+      toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings', ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 1 - 1
packages/app/src/components/Me/InAppNotificationSettings.tsx

@@ -54,7 +54,7 @@ const InAppNotificationSettings: FC = () => {
     try {
       const { data } = await apiv3Put('/personal-setting/in-app-notification-settings', { subscribeRules });
       setSubscribeRules(data.subscribeRules);
-      toastSuccess(t('toaster.update_successed', { target: 'InAppNotification Settings' }));
+      toastSuccess(t('toaster.update_successed', { target: 'InAppNotification Settings', ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 2 - 2
packages/app/src/components/Me/PasswordSettings.jsx

@@ -1,7 +1,7 @@
 import React, { useCallback } from 'react';
 
-import PropTypes from 'prop-types';
 import { useTranslation } from 'next-i18next';
+import PropTypes from 'prop-types';
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
@@ -52,7 +52,7 @@ class PasswordSettings extends React.Component {
       if (onSubmit != null) {
         onSubmit();
       }
-      toastSuccess(t('toaster.update_successed', { target: t('Password') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Password'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 3 - 3
packages/app/src/components/Me/ProfileImageSettings.tsx

@@ -48,7 +48,7 @@ const ProfileImageSettings = (): JSX.Element => {
       formData.append('file', croppedImage);
       const response = await apiPostForm('/attachments.uploadProfileImage', formData);
 
-      toastSuccess(t('toaster.update_successed', { target: t('Current Image') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Current Image'), ns: 'commons' }));
 
       // eslint-disable-next-line @typescript-eslint/no-explicit-any
       setUploadedPictureSrc((response as any).attachment.filePathProxied);
@@ -64,7 +64,7 @@ const ProfileImageSettings = (): JSX.Element => {
       await apiPost('/attachments.removeProfileImage');
 
       setUploadedPictureSrc(undefined);
-      toastSuccess(t('toaster.update_successed', { target: t('Current Image') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Current Image'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);
@@ -78,7 +78,7 @@ const ProfileImageSettings = (): JSX.Element => {
       const { userData } = response.data;
       setGravatarEnabled(userData.isGravatarEnabled);
 
-      toastSuccess(t('toaster.update_successed', { target: t('Set Profile Image') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Set Profile Image'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

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

@@ -330,7 +330,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
                     pageId={pageId}
                     revisionId={revisionId}
                     shareLinkId={shareLinkId}
-                    path={path}
+                    path={path ?? currentPathname} // If the page is empty, "path" is undefined
                     disableSeenUserInfoPopover={isSharedUser}
                     showPageControlDropdown={isAbleToShowPageManagement}
                     additionalMenuItemRenderer={additionalMenuItemsRenderer}

+ 11 - 13
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -3,8 +3,8 @@ import React, { useMemo } from 'react';
 import { pagePathUtils } from '@growi/core';
 import { useTranslation } from 'next-i18next';
 import dynamic from 'next/dynamic';
+import { Link } from 'react-scroll';
 
-// import { smoothScrollIntoView } from '~/client/util/smooth-scroll';
 import {
   useCurrentPagePath, useIsSharedUser, useIsEditable, useShareLinkId, useIsNotFound,
 } from '~/stores/context';
@@ -79,18 +79,18 @@ const PageView = React.memo((): JSX.Element => {
             </div>
 
             {/* Comments */}
-            {/* { getCommentListDom != null && !isTopPagePath && ( */}
             { !isTopPagePath && (
               <div className={`mt-2 grw-page-accessories-control ${styles['grw-page-accessories-control']}`}>
-                <button
-                  type="button"
-                  className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
-                  // onClick={() => smoothScrollIntoView(getCommentListDom, WIKI_HEADER_LINK)}
-                >
-                  <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
-                  <span>Comments</span>
-                  <CountBadge count={currentPage?.commentCount} />
-                </button>
+                <Link to={'page-comments'} smooth="easeOutQuart" offset={-100} duration={800}>
+                  <button
+                    type="button"
+                    className="btn btn-block btn-outline-secondary grw-btn-page-accessories rounded-pill d-flex justify-content-between align-items-center"
+                  >
+                    <i className="icon-fw icon-bubbles grw-page-accessories-control-icon"></i>
+                    <span>Comments</span>
+                    <CountBadge count={currentPage?.commentCount} />
+                  </button>
+                </Link>
               </div>
             ) }
 
@@ -109,8 +109,6 @@ PageView.displayName = 'PageView';
 
 
 const DisplaySwitcher = React.memo((): JSX.Element => {
-  // get element for smoothScroll
-  // const getCommentListDom = useMemo(() => { return document.getElementById('page-comments-list') }, []);
 
   const { data: isEditable } = useIsEditable();
 

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

@@ -152,7 +152,7 @@ export const PageComment: FC<PageCommentProps> = memo((props:PageCommentProps):
 
   return (
     <>
-      <div className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
+      <div id="page-comments" className={`${styles['page-comment-styles']} page-comments-row comment-list`}>
         <div className="container-lg">
           <div className="page-comments">
             <h2 className={commentTitleClasses}><i className="icon-fw icon-bubbles"></i>Comments</h2>

+ 22 - 14
packages/app/src/components/PageEditor/CodeMirrorEditor.jsx

@@ -23,7 +23,8 @@ import EditorIcon from './EditorIcon';
 import EmojiPicker from './EmojiPicker';
 import EmojiPickerHelper from './EmojiPickerHelper';
 import GridEditModal from './GridEditModal';
-import geu from './GridEditorUtil';
+// TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
+// import geu from './GridEditorUtil';
 // import HandsontableModal from './HandsontableModal';
 import LinkEditModal from './LinkEditModal';
 import mdu from './MarkdownDrawioUtil';
@@ -152,7 +153,8 @@ class CodeMirrorEditor extends AbstractEditor {
     this.renderCheatsheetModalButton = this.renderCheatsheetModalButton.bind(this);
 
     this.makeHeaderHandler = this.makeHeaderHandler.bind(this);
-    this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
+    // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
+    // this.showGridEditorHandler = this.showGridEditorHandler.bind(this);
     this.showLinkEditHandler = this.showLinkEditHandler.bind(this);
     this.showHandsonTableHandler = this.showHandsonTableHandler.bind(this);
 
@@ -858,9 +860,10 @@ class CodeMirrorEditor extends AbstractEditor {
     cm.focus();
   }
 
-  showGridEditorHandler() {
-    this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
-  }
+  // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
+  // showGridEditorHandler() {
+  //   this.gridEditModal.current.show(geu.getGridHtml(this.getCodeMirror()));
+  // }
 
   showLinkEditHandler() {
     this.linkEditModal.current.show(markdownLinkUtil.getMarkdownLink(this.getCodeMirror()));
@@ -998,15 +1001,16 @@ class CodeMirrorEditor extends AbstractEditor {
       >
         <EditorIcon icon="Image" />
       </Button>,
-      <Button
-        key="nav-item-grid"
-        color={null}
-        size="sm"
-        title="Grid"
-        onClick={this.showGridEditorHandler}
-      >
-        <EditorIcon icon="Grid" />
-      </Button>,
+      // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
+      // <Button
+      //   key="nav-item-grid"
+      //   color={null}
+      //   size="sm"
+      //   title="Grid"
+      //   onClick={this.showGridEditorHandler}
+      // >
+      //   <EditorIcon icon="Grid" />
+      // </Button>,
       <Button
         key="nav-item-table"
         color={null}
@@ -1119,10 +1123,14 @@ class CodeMirrorEditor extends AbstractEditor {
         { this.renderCheatsheetOverlay() }
         { this.renderEmojiPicker() }
 
+        {/*
+        // TODO: re-impl with https://redmine.weseek.co.jp/issues/107248
         <GridEditModal
           ref={this.gridEditModal}
           onSave={(grid) => { return geu.replaceGridWithHtmlWithEditor(this.getCodeMirror(), grid) }}
         />
+         */}
+
         <LinkEditModal
           ref={this.linkEditModal}
           onSave={(linkText) => { return markdownLinkUtil.replaceFocusedMarkdownLinkWithEditor(this.getCodeMirror(), linkText) }}

+ 2 - 2
packages/app/src/components/PasswordResetExecutionForm.tsx

@@ -11,7 +11,7 @@ const logger = loggerFactory('growi:passwordReset');
 
 
 const PasswordResetExecutionForm: FC = () => {
-  const { t } = useTranslation();
+  const { t } = useTranslation(['translation', 'commons']);
 
   const [newPassword, setNewPassword] = useState('');
   const [newPasswordConfirm, setNewPasswordConfirm] = useState('');
@@ -41,7 +41,7 @@ const PasswordResetExecutionForm: FC = () => {
 
       setValidationErrorI18n('');
 
-      toastSuccess(t('toaster.update_successed', { target: t('Password') }));
+      toastSuccess(t('toaster.update_successed', { target: t('Password'), ns: 'commons' }));
     }
     catch (err) {
       toastError(err);

+ 9 - 7
packages/app/src/components/Sidebar/Tag.tsx

@@ -58,13 +58,15 @@ const Tag: FC = () => {
           </div>
         )
         : (
-          <TagList
-            tagData={tagData}
-            totalTags={totalCount}
-            activePage={activePage}
-            onChangePage={setOffsetByPageNumber}
-            pagingLimit={PAGING_LIMIT}
-          />
+          <div data-testid="grw-tags-list">
+            <TagList
+              tagData={tagData}
+              totalTags={totalCount}
+              activePage={activePage}
+              onChangePage={setOffsetByPageNumber}
+              pagingLimit={PAGING_LIMIT}
+            />
+          </div>
         )
       }
 

+ 9 - 18
packages/app/src/pages/[[...path]].page.tsx

@@ -39,7 +39,9 @@ import type { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import { useSWRxCurrentPage, useSWRxIsGrantNormalized, useSWRxPageInfo } from '~/stores/page';
 import { useRedirectFrom } from '~/stores/page-redirect';
 import {
-  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth, useSelectedGrant,
+  EditorMode,
+  useEditorMode, useSelectedGrant,
+  usePreferDrawerModeByUser, usePreferDrawerModeOnEditByUser, useSidebarCollapsed, useCurrentSidebarContents, useCurrentProductNavWidth,
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
@@ -230,8 +232,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   useIsUploadableFile(props.editorConfig.upload.isUploadableFile);
   useIsUploadableImage(props.editorConfig.upload.isUploadableImage);
 
-  // const { data: editorMode } = useEditorMode();
-
   const { pageWithMeta, userUISettings } = props;
 
   const pageId = pageWithMeta?.data._id;
@@ -246,13 +246,13 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
 
   useSWRxCurrentPage(undefined, pageWithMeta?.data ?? null); // store initial data
   useEditingMarkdown(pageWithMeta?.data.revision?.body ?? '');
-
-  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
   const { data: dataPageInfo } = useSWRxPageInfo(pageId);
-
   const { data: grantData } = useSWRxIsGrantNormalized(pageId);
   const { mutate: mutateSelectedGrant } = useSelectedGrant();
 
+  const { data: layoutSetting } = useLayoutSetting({ isContainerFluid: props.isContainerFluid });
+  const { getClassNamesByEditorMode } = useEditorMode();
+
   const shouldRenderPutbackPageModal = pageWithMeta != null
     ? _isTrashPage(pageWithMeta.data.path)
     : false;
@@ -271,17 +271,9 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
   }, [props.currentPathname, router]);
 
   const classNames: string[] = [];
-  // switch (editorMode) {
-  //   case EditorMode.Editor:
-  //     classNames.push('on-edit', 'builtin-editor');
-  //     break;
-  //   case EditorMode.HackMD:
-  //     classNames.push('on-edit', 'hackmd');
-  //     break;
-  // }
-  // if (page == null) {
-  //   classNames.push('not-found-page');
-  // }
+
+  const isSidebar = pagePath === '/Sidebar';
+  classNames.push(...getClassNamesByEditorMode(isSidebar));
 
   const isTopPagePath = isTopPage(pageWithMeta?.data.path ?? '');
 
@@ -301,7 +293,6 @@ const GrowiPage: NextPage<Props> = (props: Props) => {
         {renderHighlightJsStyleTag(props.highlightJsStyle)}
         */}
       </Head>
-      {/* <BasicLayout title={useCustomTitle(props, t('GROWI'))} className={classNames.join(' ')}> */}
       <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')} expandContainer={isContainerFluid}>
         <div className="h-100 d-flex flex-column justify-content-between">
           <header className="py-0 position-relative">

+ 3 - 3
packages/app/src/pages/admin/[[...path]].page.tsx

@@ -86,7 +86,7 @@ type Props = CommonProps & {
   siteUrl: string,
 };
 
-const AdminMarkdownSettingsPage: NextPage<Props> = (props: Props) => {
+const AdminPage: NextPage<Props> = (props: Props) => {
 
   const { t } = useTranslation('admin');
   const router = useRouter();
@@ -335,11 +335,11 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
   }
 
   injectServerConfigurations(context, props);
-  await injectNextI18NextConfigurations(context, props, ['admin']);
+  await injectNextI18NextConfigurations(context, props, ['admin', 'commons']);
 
   return {
     props,
   };
 };
 
-export default AdminMarkdownSettingsPage;
+export default AdminPage;

+ 1 - 1
packages/app/src/pages/forgot-password.page.tsx

@@ -50,7 +50,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
   const props: CommonProps = result.props as CommonProps;
 
-  await injectNextI18NextConfigurations(context, props, ['translation']);
+  await injectNextI18NextConfigurations(context, props, ['translation', 'commons']);
 
   return {
     props,

+ 1 - 1
packages/app/src/pages/me/[[...path]].page.tsx

@@ -198,7 +198,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
   await injectUserUISettings(context, props);
   await injectServerConfigurations(context, props);
-  await injectNextI18NextConfigurations(context, props, ['translation', 'admin']);
+  await injectNextI18NextConfigurations(context, props, ['translation', 'admin', 'commons']);
 
   return {
     props,

+ 1 - 1
packages/app/src/pages/reset-password.page.tsx

@@ -62,7 +62,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
     props.email = email;
   }
 
-  await injectNextI18NextConfigurations(context, props, ['translation']);
+  await injectNextI18NextConfigurations(context, props, ['translation', 'commons']);
 
   return {
     props,

+ 1 - 1
packages/app/src/pages/tags.page.tsx

@@ -63,7 +63,7 @@ const TagPage: NextPage<CommonProps> = (props: Props) => {
       <Head>
       </Head>
       <BasicLayout title={useCustomTitle(props, 'GROWI')} className={classNames.join(' ')}>
-        <div className="grw-container-convertible mb-5 pb-5">
+        <div className="grw-container-convertible mb-5 pb-5" data-testid="tags-page">
           <h2 className="my-3">{`${t('Tags')}(${totalCount})`}</h2>
           <div className="px-3 mb-5 text-center">
             <TagCloudBox tags={tagData} minSize={20} />

+ 3 - 1
packages/app/src/server/routes/index.js

@@ -85,6 +85,9 @@ module.exports = function(crowi, app) {
 
   app.get('/register'                 , applicationInstalled, login.preLogin, login.register);
 
+  // load before "/admin/*"
+  app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
+
   app.get('/admin/*'                    , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
   app.get('/admin'                    , applicationInstalled, loginRequiredStrictly , adminRequired , next.delegateToNext);
   // app.get('/admin/app'                , applicationInstalled, loginRequiredStrictly , adminRequired , admin.app.index);
@@ -153,7 +156,6 @@ module.exports = function(crowi, app) {
 
   // export management for admin
   // app.get('/admin/export'                       , loginRequiredStrictly , adminRequired ,admin.export.index);
-  // app.get('/admin/export/:fileName'             , loginRequiredStrictly , adminRequired ,admin.export.api.validators.export.download(), admin.export.download);
 
   // app.get('/admin/*'                            , loginRequiredStrictly ,adminRequired, admin.notFound.index);
 

+ 33 - 43
packages/app/src/stores/ui.tsx

@@ -1,4 +1,4 @@
-import { RefObject, useEffect } from 'react';
+import { RefObject, useCallback, useEffect } from 'react';
 
 import {
   isClient, isServer, pagePathUtils, Nullable, PageGrant,
@@ -72,29 +72,21 @@ export const useIsMobile = (): SWRResponse<boolean, Error> => {
   return useStaticSWR<boolean, Error>(key, undefined, configuration);
 };
 
-const updateBodyClassesByEditorMode = (newEditorMode: EditorMode, isSidebar = false) => {
-  const bodyElement = document.getElementsByTagName('body')[0];
-  if (bodyElement == null) {
-    logger.warn('The body tag was not successfully obtained');
-    return;
-  }
-  switch (newEditorMode) {
-    case EditorMode.View:
-      bodyElement.classList.remove('on-edit', 'builtin-editor', 'hackmd', 'editing-sidebar');
-      break;
+const getClassNamesByEditorMode = (editorMode: EditorMode | undefined, isSidebar = false): string[] => {
+  const classNames: string[] = [];
+  switch (editorMode) {
     case EditorMode.Editor:
-      bodyElement.classList.add('on-edit', 'builtin-editor');
-      bodyElement.classList.remove('hackmd');
-      // editing /Sidebar
+      classNames.push('editing', 'builtin-editor');
       if (isSidebar) {
-        bodyElement.classList.add('editing-sidebar');
+        classNames.push('editing-sidebar');
       }
       break;
     case EditorMode.HackMD:
-      bodyElement.classList.add('on-edit', 'hackmd');
-      bodyElement.classList.remove('builtin-editor', 'editing-sidebar');
+      classNames.push('editing', 'hackmd');
       break;
   }
+
+  return classNames;
 };
 
 const updateHashByEditorMode = (newEditorMode: EditorMode) => {
@@ -130,8 +122,11 @@ export const determineEditorModeByHash = (): EditorMode => {
   }
 };
 
-let isEditorModeLoaded = false;
-export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
+type EditorModeUtils = {
+  getClassNamesByEditorMode: (isEditingSidebar: boolean) => string[],
+}
+
+export const useEditorMode = (): SWRResponseWithUtils<EditorModeUtils, EditorMode> => {
   const { data: _isEditable } = useIsEditable();
 
   const editorModeByHash = determineEditorModeByHash();
@@ -140,36 +135,31 @@ export const useEditorMode = (): SWRResponse<EditorMode, Error> => {
   const isEditable = !isLoading && _isEditable;
   const initialData = isEditable ? editorModeByHash : EditorMode.View;
 
-  const { data: currentPagePath } = useCurrentPagePath();
-  const isSidebar = currentPagePath === '/Sidebar';
-
-  const swrResponse = useSWRImmutable(
+  const swrResponse = useSWRImmutable<EditorMode>(
     isLoading ? null : ['editorMode', isEditable],
     null,
     { fallbackData: initialData },
   );
 
-  // initial updating
-  if (!isEditorModeLoaded && !isLoading && swrResponse.data != null) {
-    if (isEditable) {
-      updateBodyClassesByEditorMode(swrResponse.data, isSidebar);
+  // construct overriding mutate method
+  const mutateOriginal = swrResponse.mutate;
+  const mutate = useCallback((editorMode: EditorMode, shouldRevalidate?: boolean) => {
+    if (!isEditable) {
+      return Promise.resolve(EditorMode.View); // fixed if not editable
     }
-    isEditorModeLoaded = true;
-  }
-
-  return {
-    ...swrResponse,
-
-    // overwrite mutate
-    mutate: (editorMode: EditorMode, shouldRevalidate?: boolean) => {
-      if (!isEditable) {
-        return Promise.resolve(EditorMode.View); // fixed if not editable
-      }
-      updateBodyClassesByEditorMode(editorMode, isSidebar);
-      updateHashByEditorMode(editorMode);
-      return swrResponse.mutate(editorMode, shouldRevalidate);
-    },
-  };
+    updateHashByEditorMode(editorMode);
+    return mutateOriginal(editorMode, shouldRevalidate);
+  }, [isEditable, mutateOriginal]);
+
+  // construct getClassNamesByEditorMode method
+  const getClassNames = useCallback((isEditingSidebar: boolean) => {
+    return getClassNamesByEditorMode(swrResponse.data, isEditingSidebar);
+  }, [swrResponse.data]);
+
+  return Object.assign(swrResponse, {
+    mutate,
+    getClassNamesByEditorMode: getClassNames,
+  });
 };
 
 export const useIsDeviceSmallerThanMd = (): SWRResponse<boolean, Error> => {

+ 4 - 4
packages/app/src/styles/_on-edit.scss → packages/app/src/styles/_editor.scss

@@ -4,7 +4,7 @@
 @import 'sidebar-wiki';
 
 // global imported
-body.on-edit {
+.layout-root.editing {
   overflow-y: hidden !important;
 
   .grw-navbar {
@@ -279,14 +279,14 @@ body.on-edit {
   }
 }
 
-body.on-edit {
-  .wrapper:not(.growi-layout-fluid) .page-editor-preview-body {
+.layout-root.editing {
+  &:not(.growi-layout-fluid) .page-editor-preview-body {
     .wiki {
       max-width: 980px;
       margin: 0 auto;
     }
   }
-  .wrapper.growi-layout-fluid .page-editor-preview-body {
+  &.growi-layout-fluid .page-editor-preview-body {
     .wiki {
       margin: 0 auto;
     }

+ 2 - 2
packages/app/src/styles/_layout.scss

@@ -6,11 +6,11 @@ body {
   overscroll-behavior-y: none;
 }
 
-.wrapper:not(.growi-layout-fluid) .grw-container-convertible {
+.layout-root:not(.growi-layout-fluid) .grw-container-convertible {
   @extend .container-lg;
 }
 
-.wrapper.growi-layout-fluid .grw-container-convertible {
+.layout-root.growi-layout-fluid .grw-container-convertible {
   @extend .container-fluid;
 }
 

+ 1 - 1
packages/app/src/styles/_old-ios.scss

@@ -1,4 +1,4 @@
-html[old-ios] body:not(.on-edit) {
+html[old-ios] .layout-root:not(.editing) {
   .grw-navbar {
     position: initial !important;
     top: 0 !important;

+ 1 - 1
packages/app/src/styles/_variables.scss

@@ -35,7 +35,7 @@ $grw-logo-width: $grw-sidebar-nav-width;
 $grw-logomark-width: 36px;
 
 // fix tab width to 95 pixels
-// see also '_on-edit.scss'
+// see also '_editor.scss'
 $grw-nav-main-left-tab-width: 95px;
 $grw-nav-main-left-tab-width-mobile: 50px;
 $grw-nav-main-tab-height: 42px;

+ 2 - 6
packages/app/src/styles/style-next.scss

@@ -45,20 +45,16 @@
 // @import 'comment_growi';
 // @import 'create-page';
 // @import 'draft';
-// @import 'editor-attachment';
-// @import 'editor-navbar';
-// @import 'page-content-footer';
+@import 'editor';
 // @import 'handsontable';
 @import 'layout';
 // @import 'login';
 // @import 'me';
-// @import 'mirror_mode';
+@import 'mirror_mode';
 @import 'modal';
 // @import 'navbar';
 // @import 'old-ios';
-@import 'on-edit';
 // @import 'page-duplicate-modal';
-
 // @import 'page-path';
 // @import 'page-tree';
 // @import 'page';

+ 1 - 1
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -252,7 +252,7 @@ $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
 }
 
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
 .grw-editor-navbar-bottom {
   background-color: $gray-50;

+ 2 - 2
packages/app/src/styles/theme/_apply-colors.scss

@@ -503,9 +503,9 @@ ul.pagination {
 }
 
 /*
- * GROWI on-edit
+ * GROWI Editor
  */
-body.on-edit {
+.layout-root.editing {
   .main {
     background-color: darken($bgcolor-global, 2%);
 

+ 7 - 8
packages/app/test/cypress/integration/20-basic-features/access-to-page.spec.ts

@@ -18,13 +18,13 @@ context('Access to page', () => {
   it('/Sandbox with anchor hash is successfully loaded', () => {
     cy.visit('/Sandbox#Headers');
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated
     // https://stackoverflow.com/questions/5041494/selecting-and-manipulating-css-pseudo-elements-such-as-before-and-after-usin/21709814#21709814
-    cy.get('#Headers').invoke('removeClass', 'blink');
+    cy.get('#mdcont-headers').invoke('removeClass', 'blink');
 
     cy.screenshot(`${ssPrefix}-sandbox-headers`);
   });
@@ -32,7 +32,6 @@ context('Access to page', () => {
   it('/Sandbox/Math is successfully loaded', () => {
     cy.visit('/Sandbox/Math');
 
-    cy.get('mjx-container').should('be.visible');
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(2000); // wait for 2 seconds for MathJax.typesetPromise();
 
@@ -71,10 +70,10 @@ context('Access to /me page', () => {
     cy.screenshot(`${ssPrefix}-me`);
   });
 
-  it('Draft page is successfully shown', () => {
-    cy.visit('/me/drafts');
-    cy.screenshot(`${ssPrefix}-draft-page`);
-  });
+  // it('Draft page is successfully shown', () => {
+  //   cy.visit('/me/drafts');
+  //   cy.screenshot(`${ssPrefix}-draft-page`);
+  // });
 
 });
 

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/use-tools.spec.ts

@@ -54,7 +54,7 @@ context('Modal for page operation', () => {
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('.layout-root').should('not.have.class', 'editing');
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-today-page`);
@@ -72,7 +72,7 @@ context('Modal for page operation', () => {
     });
     cy.getByTestid('page-editor').should('be.visible');
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
     cy.getByTestid('grw-contextual-sub-nav').should('be.visible');
     cy.screenshot(`${ssPrefix}create-page-under-specific-page`);

+ 2 - 2
packages/app/test/cypress/integration/21-basic-features-for-guest/access-to-page.spec.ts

@@ -14,8 +14,8 @@ context('Access to page by guest', () => {
     // eslint-disable-next-line cypress/no-unnecessary-waiting
     cy.wait(500);
 
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
     cy.screenshot(`${ssPrefix}-sandbox-headers`);
   });

+ 1 - 1
packages/app/test/cypress/integration/50-sidebar/access-to-side-bar.spec.ts

@@ -60,7 +60,7 @@ context('Access to sidebar', () => {
     cy.get('.CodeMirror textarea').type(content, {force: true});
     cy.screenshot(`${ssPrefix}custom-sidebar-2-custom-sidebar-editor`);
     cy.getByTestid('save-page-btn').click();
-    cy.get('body').should('not.have.class', 'on-edit');
+    cy.get('layout-root').should('not.have.class', 'editing');
 
     // What to do when UserUISettings is not saved in time
     cy.getByTestid('grw-sidebar-nav-primary-custom-sidebar').then(($el) => {

+ 5 - 3
packages/app/test/cypress/integration/60-home/home.spec.ts

@@ -15,7 +15,9 @@ context('Access Home', () => {
     cy.getByTestid('grw-personal-dropdown').click();
     cy.getByTestid('grw-personal-dropdown').find('.dropdown-menu .btn-group > .btn-outline-secondary:eq(0)').click();
 
-    cy.getByTestid('grw-user-page').should('be.visible');
+    cy.get('.grw-users-info').should('be.visible');
+    // for check download toc data
+    cy.get('.toc-link').should('be.visible');
 
     cy.screenshot(`${ssPrefix}-visit-home`);
   });
@@ -34,8 +36,8 @@ context('Access User settings', () => {
     // collapse sidebar
     cy.collapseSidebar(true);
     cy.visit('/me');
-    // hide fab
-    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab // disable fab for sticky-events warning
+    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
   });
 
   it('Access User information', () => {