Explorar el Código

Merge branch 'master' into fix/107104-vrt-page-delete-modal-is-shown-successfully-failed

cao hace 3 años
padre
commit
5bd41eeeef

+ 11 - 6
packages/app/src/components/InstallerForm.tsx

@@ -2,8 +2,7 @@ import {
   FormEventHandler, memo, useCallback, useState,
   FormEventHandler, memo, useCallback, useState,
 } from 'react';
 } from 'react';
 
 
-import i18next from 'i18next';
-import { useTranslation, i18n } from 'next-i18next';
+import { useTranslation } from 'next-i18next';
 
 
 import { i18n as i18nConfig } from '^/config/next-i18next.config';
 import { i18n as i18nConfig } from '^/config/next-i18next.config';
 
 
@@ -11,10 +10,11 @@ import { toastError } from '~/client/util/apiNotification';
 import { apiv3Post } from '~/client/util/apiv3-client';
 import { apiv3Post } from '~/client/util/apiv3-client';
 
 
 const InstallerForm = memo((): JSX.Element => {
 const InstallerForm = memo((): JSX.Element => {
-  const { t } = useTranslation();
+  const { t, i18n } = useTranslation();
 
 
   const [isValidUserName, setValidUserName] = useState(true);
   const [isValidUserName, setValidUserName] = useState(true);
   const [isSubmittingDisabled, setSubmittingDisabled] = useState(false);
   const [isSubmittingDisabled, setSubmittingDisabled] = useState(false);
+  const [currentLocale, setCurrentLocale] = useState('en_US');
 
 
   const checkUserName = useCallback(async(event) => {
   const checkUserName = useCallback(async(event) => {
     const axios = require('axios').create({
     const axios = require('axios').create({
@@ -28,6 +28,11 @@ const InstallerForm = memo((): JSX.Element => {
     setValidUserName(res.data.valid);
     setValidUserName(res.data.valid);
   }, []);
   }, []);
 
 
+  const onClickLanguageItem = useCallback((locale) => {
+    i18n.changeLanguage(locale);
+    setCurrentLocale(locale);
+  }, [i18n]);
+
   const submitHandler: FormEventHandler = useCallback(async(e: any) => {
   const submitHandler: FormEventHandler = useCallback(async(e: any) => {
     e.preventDefault();
     e.preventDefault();
 
 
@@ -59,7 +64,7 @@ const InstallerForm = memo((): JSX.Element => {
         name,
         name,
         email,
         email,
         password,
         password,
-        'app:globalLang': formData['registerForm[app:globalLang]'].value,
+        'app:globalLang': currentLocale,
       },
       },
     };
     };
 
 
@@ -78,7 +83,7 @@ const InstallerForm = memo((): JSX.Element => {
 
 
       toastError(t('installer.failed_to_install'));
       toastError(t('installer.failed_to_install'));
     }
     }
-  }, [isSubmittingDisabled, t]);
+  }, [isSubmittingDisabled, t, currentLocale]);
 
 
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const hasErrorClass = isValidUserName ? '' : ' has-error';
   const unavailableUserId = isValidUserName
   const unavailableUserId = isValidUserName
@@ -132,7 +137,7 @@ const InstallerForm = memo((): JSX.Element => {
                         data-testid={`dropdownLanguageMenu-${locale}`}
                         data-testid={`dropdownLanguageMenu-${locale}`}
                         className="dropdown-item"
                         className="dropdown-item"
                         type="button"
                         type="button"
-                        onClick={() => { i18next.changeLanguage(locale) }}
+                        onClick={() => { onClickLanguageItem(locale) }}
                       >
                       >
                         {fixedT?.('meta.display_name')}
                         {fixedT?.('meta.display_name')}
                       </button>
                       </button>

+ 43 - 30
packages/app/src/components/Me/EditorSettings.tsx

@@ -1,12 +1,13 @@
 import React, {
 import React, {
-  Dispatch,
+  Dispatch, memo,
   FC, SetStateAction, useCallback, useEffect, useState,
   FC, SetStateAction, useCallback, useEffect, useState,
+  useMemo,
 } from 'react';
 } from 'react';
 
 
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
-import { apiv3Get, apiv3Put } from '~/client/util/apiv3-client';
+import { useEditorSettings } from '~/stores/editor';
 
 
 
 
 type EditorSettingsBodyProps = Record<string, never>;
 type EditorSettingsBodyProps = Record<string, never>;
@@ -194,51 +195,62 @@ const RuleListGroup: FC<RuleListGroupProps> = ({
   );
   );
 };
 };
 
 
+const createRulesFromDefaultList = (rule: { name: string }) => (
+  {
+    name: rule.name,
+    isEnabled: true,
+  }
+);
+
 
 
-export const EditorSettings: FC<EditorSettingsBodyProps> = () => {
+export const EditorSettings = memo((): JSX.Element => {
   const { t } = useTranslation();
   const { t } = useTranslation();
   const [textlintRules, setTextlintRules] = useState<LintRule[]>([]);
   const [textlintRules, setTextlintRules] = useState<LintRule[]>([]);
 
 
-  const initializeEditorSettings = useCallback(async() => {
-    const { data } = await apiv3Get('/personal-setting/editor-settings');
-    const retrievedRules: LintRule[] = data?.textlintSettings?.textlintRules;
+  const { data: dataEditorSettings, update: updateEditorSettings } = useEditorSettings();
 
 
-    // If database is empty, add default rules to state
-    if (retrievedRules != null && retrievedRules.length > 0) {
-      setTextlintRules(retrievedRules);
-    }
-    else {
-      const createRulesFromDefaultList = (rule: { name: string }) => (
-        {
-          name: rule.name,
-          isEnabled: true,
-        }
-      );
+  const defaultRules = useMemo(() => {
+    const defaultCommonRules = commonRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+    const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
+
+    return [...defaultCommonRules, ...defaultJapaneseRules];
+  }, []);
 
 
-      const defaultCommonRules = commonRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
-      const defaultJapaneseRules = japaneseRulesMenuItems.map(rule => createRulesFromDefaultList(rule));
-      setTextlintRules([...defaultCommonRules, ...defaultJapaneseRules]);
+  const initializeEditorSettings = useCallback(() => {
+    if (dataEditorSettings == null) {
+      return;
     }
     }
 
 
-  }, []);
+    const retrievedRules: LintRule[] | undefined = dataEditorSettings?.textlintSettings?.textlintRules;
 
 
-  useEffect(() => {
-    initializeEditorSettings();
-  }, [initializeEditorSettings]);
+    // If database is empty, add default rules to state
+    if (retrievedRules != null && retrievedRules.length > 0) {
+      setTextlintRules(retrievedRules);
+      return;
+    }
+    setTextlintRules(defaultRules);
+  }, [dataEditorSettings, defaultRules]);
 
 
-  const updateRulesHandler = async() => {
+  const updateRulesHandler = useCallback(async() => {
     try {
     try {
-      const { data } = await apiv3Put('/personal-setting/editor-settings', { textlintSettings: { textlintRules: [...textlintRules] } });
-      setTextlintRules(data.textlintSettings.textlintRules);
+      await updateEditorSettings({ textlintSettings: { textlintRules } });
       toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
       toastSuccess(t('toaster.update_successed', { target: 'Updated Textlint Settings' }));
     }
     }
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  };
+  }, [t, textlintRules, updateEditorSettings]);
+
+  useEffect(() => {
+    initializeEditorSettings();
+  }, [initializeEditorSettings]);
 
 
   if (textlintRules == null) {
   if (textlintRules == null) {
-    return <></>;
+    return (
+      <div className="text-muted text-center">
+        <i className="fa fa-2x fa-spinner fa-pulse"></i>
+      </div>
+    );
   }
   }
 
 
   return (
   return (
@@ -270,4 +282,5 @@ export const EditorSettings: FC<EditorSettingsBodyProps> = () => {
       </div>
       </div>
     </div>
     </div>
   );
   );
-};
+});
+EditorSettings.displayName = 'EditorSettings';

+ 1 - 1
packages/app/src/interfaces/editor-settings.ts

@@ -5,7 +5,7 @@ export interface ILintRule {
 }
 }
 
 
 export interface ITextlintSettings {
 export interface ITextlintSettings {
-  neverAskBeforeDownloadLargeFiles: boolean;
+  neverAskBeforeDownloadLargeFiles?: boolean;
   textlintRules: ILintRule[];
   textlintRules: ILintRule[];
 }
 }
 
 

+ 2 - 2
packages/app/src/pages/installer.page.tsx

@@ -23,7 +23,7 @@ import {
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 const { isTrashPage: _isTrashPage } = pagePathUtils;
 
 
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
 async function injectNextI18NextConfigurations(context: GetServerSidePropsContext, props: Props, namespacesRequired?: string[] | undefined): Promise<void> {
-  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired);
+  const nextI18NextConfig = await getNextI18NextConfig(serverSideTranslations, context, namespacesRequired, true);
   props._nextI18Next = nextI18NextConfig._nextI18Next;
   props._nextI18Next = nextI18NextConfig._nextI18Next;
 }
 }
 
 
@@ -65,7 +65,7 @@ export const getServerSideProps: GetServerSideProps = async(context: GetServerSi
 
 
   const props: Props = result.props as Props;
   const props: Props = result.props as Props;
 
 
-  injectNextI18NextConfigurations(context, props, ['translation']);
+  await injectNextI18NextConfigurations(context, props, ['translation']);
 
 
   return {
   return {
     props,
     props,

+ 11 - 5
packages/app/src/stores/editor.tsx

@@ -10,16 +10,19 @@ import { SlackChannels } from '~/interfaces/user-trigger-notification';
 import {
 import {
   useCurrentUser, useDefaultIndentSize, useIsGuestUser,
   useCurrentUser, useDefaultIndentSize, useIsGuestUser,
 } from './context';
 } from './context';
-import { localStorageMiddleware } from './middlewares/sync-to-storage';
+// import { localStorageMiddleware } from './middlewares/sync-to-storage';
 import { useSWRxTagsInfo } from './page';
 import { useSWRxTagsInfo } from './page';
 import { useStaticSWR } from './use-static-swr';
 import { useStaticSWR } from './use-static-swr';
 
 
 
 
 type EditorSettingsOperation = {
 type EditorSettingsOperation = {
-  update: (updateData: Partial<IEditorSettings>) => void,
+  update: (updateData: Partial<IEditorSettings>) => Promise<void>,
   turnOffAskingBeforeDownloadLargeFiles: () => void,
   turnOffAskingBeforeDownloadLargeFiles: () => void,
 }
 }
 
 
+// TODO: Enable localStorageMiddleware
+//   - Unabling localStorageMiddleware occurrs a flickering problem when loading theme.
+//   - see: https://github.com/weseek/growi/pull/6781#discussion_r1000285786
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, IEditorSettings, Error> => {
 export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperation, IEditorSettings, Error> => {
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isGuestUser } = useIsGuestUser();
@@ -27,11 +30,14 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
   const swrResult = useSWRImmutable<IEditorSettings>(
   const swrResult = useSWRImmutable<IEditorSettings>(
     isGuestUser ? null : ['/personal-setting/editor-settings', currentUser?.username],
     isGuestUser ? null : ['/personal-setting/editor-settings', currentUser?.username],
     endpoint => apiv3Get(endpoint).then(result => result.data),
     endpoint => apiv3Get(endpoint).then(result => result.data),
-    { use: [localStorageMiddleware] }, // store to localStorage for initialization fastly
+    {
+      // use: [localStorageMiddleware], // store to localStorage for initialization fastly
+      // fallbackData: undefined,
+    },
   );
   );
 
 
   return withUtils<EditorSettingsOperation, IEditorSettings, Error>(swrResult, {
   return withUtils<EditorSettingsOperation, IEditorSettings, Error>(swrResult, {
-    update: (updateData) => {
+    update: async(updateData) => {
       const { data, mutate } = swrResult;
       const { data, mutate } = swrResult;
 
 
       if (data == null) {
       if (data == null) {
@@ -41,7 +47,7 @@ export const useEditorSettings = (): SWRResponseWithUtils<EditorSettingsOperatio
       mutate({ ...data, ...updateData }, false);
       mutate({ ...data, ...updateData }, false);
 
 
       // invoke API
       // invoke API
-      apiv3Put('/personal-setting/editor-settings', updateData);
+      await apiv3Put('/personal-setting/editor-settings', updateData);
     },
     },
     turnOffAskingBeforeDownloadLargeFiles: async() => {
     turnOffAskingBeforeDownloadLargeFiles: async() => {
       const { data, mutate } = swrResult;
       const { data, mutate } = swrResult;

+ 4 - 8
packages/app/src/stores/middlewares/sync-to-storage.ts

@@ -33,14 +33,10 @@ export const createSyncToStorageMiddlware = (
         initData = storageSerializer.deserialize(itemInStorage);
         initData = storageSerializer.deserialize(itemInStorage);
       }
       }
 
 
-      const swrNext = useSWRNext(key, fetcher, {
-        fallbackData: initData,
-        ...config,
-      });
+      config.fallbackData = initData;
+      const swrNext = useSWRNext(key, fetcher, config);
 
 
-      return {
-        ...swrNext,
-        // override mutate
+      return Object.assign(swrNext, {
         mutate: (data, shouldRevalidate) => {
         mutate: (data, shouldRevalidate) => {
           return swrNext.mutate(data, shouldRevalidate)
           return swrNext.mutate(data, shouldRevalidate)
             .then((value) => {
             .then((value) => {
@@ -48,7 +44,7 @@ export const createSyncToStorageMiddlware = (
               return value;
               return value;
             });
             });
         },
         },
-      };
+      });
     };
     };
   };
   };
 };
 };